diff --git a/.bazelrc b/.bazelrc index 451cc60fdd1..94a425ae91d 100644 --- a/.bazelrc +++ b/.bazelrc @@ -258,6 +258,9 @@ build:windows --host_cxxopt=/std:c++14 # On windows, we still link everything into a single DLL. build:windows --config=monolithic +# On linux, we dynamically link small amount of kernels +build:linux --config=dynamic_kernels + # Make sure to include as little of windows.h as possible build:windows --copt=-DWIN32_LEAN_AND_MEAN build:windows --host_copt=-DWIN32_LEAN_AND_MEAN diff --git a/.github/ISSUE_TEMPLATE/70-tflite-micro-issue.md b/.github/ISSUE_TEMPLATE/70-tflite-micro-issue.md new file mode 100644 index 00000000000..745c2f7b4a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/70-tflite-micro-issue.md @@ -0,0 +1,19 @@ +--- +name: TensorFlow Lite for Microcontrollers Issue +about: Use this template for reporting issues with TensorFlow Lite for microcontrollers +labels: 'comp:micro' + +--- + +@tensorflow/micro + +**System information** +- Host OS Platform and Distribution (e.g., Linux Ubuntu 16.04): +- TensorFlow installed from (source or binary): +- Tensorflow version (commit SHA if source): +- Target platform (e.g. Arm Mbed OS, Arduino Nano 33 etc.): + +**Describe the problem** + +**Please provide the exact sequence of commands/steps when you ran into the problem** + diff --git a/README.md b/README.md index 05b1e4de458..34fa269446f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,20 @@ to [announce@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/announce). See all the [mailing lists](https://www.tensorflow.org/community/forums). +## Feature Prioritization Survey + +The TensorFlow team is working on building/improving features, and understands +that it is very important to prioritize these efforts based on what TF users +need. + +The goal of this short, < 5 minute +[survey](https://google.qualtrics.com/jfe/form/SV_d5nqhCEbkDkQ7ad), is to help +the TensorFlow team better understand what features to prioritize based on your +feedback. Participation is of course optional. + +Take the survey +[HERE](https://google.qualtrics.com/jfe/form/SV_d5nqhCEbkDkQ7ad). + ## Install See the [TensorFlow install guide](https://www.tensorflow.org/install) for the @@ -51,6 +65,9 @@ Windows)*: $ pip install tensorflow-gpu ``` +To update TensorFlow to the latest version, add `--upgrade` flag to the above +commands. + *Nightly binaries are available for testing using the [tf-nightly](https://pypi.python.org/pypi/tf-nightly) and [tf-nightly-gpu](https://pypi.python.org/pypi/tf-nightly-gpu) packages on PyPi.* @@ -147,3 +164,4 @@ Learn more about the ## License [Apache License 2.0](LICENSE) + diff --git a/RELEASE.md b/RELEASE.md index c415315f882..8b7bf729080 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -201,239 +201,387 @@ If you experience any snags when using TF 2.0, please let us know at the [TF 2.0 ## Bug Fixes and Other Changes -* `tf.contrib`: - * Expose `tf.contrib.proto.*` ops in `tf.io` (they will exist in TF2) - -* `tf.data`: - * Add support for TensorArrays to `tf.data Dataset`. - * Integrate Ragged Tensors with `tf.data`. - * All core and experimental tf.data transformations that input user-defined functions can span multiple devices now. - * Extending the TF 2.0 support for `shuffle(..., reshuffle_each_iteration=True)` and `cache()` to work across different Python iterators for the same dataset. - * Removing the `experimental_numa_aware` option from `tf.data.Options`. - * Add `num_parallel_reads` and passing in a Dataset containing filenames into `TextLineDataset` and `FixedLengthRecordDataset`. - * Add support for defaulting the value of `cycle_length` argument of `tf.data.Dataset.interleave` to the number of schedulable CPU cores. - * Promoting `tf.data.experimental.enumerate_dataset` to core as `tf.data.Dataset.enumerate`. - * Promoting `tf.data.experimental.unbatch` to core as `tf.data.Dataset.unbatch`. - * Adds option for introducing slack in the pipeline to reduce CPU contention, via `tf.data.Options().experimental_slack = True` - * Added experimental support for parallel batching to `batch()` and `padded_batch()`. This functionality can be enabled through `tf.data.Options()`. - * Support cancellation of long-running `reduce`. - * Now we use `dataset` node name as prefix instead of the op name, to identify the component correctly in metrics, for pipelines with repeated components. - * Improve the performance of datasets using `from_tensors()`. - * Promoting `unbatch` from experimental to core API. - * Adding support for datasets as inputs to `from_tensors` and `from_tensor_slices` and batching and unbatching of nested datasets. +* `tf.contrib`: -* `tf.distribute`: - * Enable `tf.distribute.experimental.MultiWorkerMirroredStrategy` working in eager mode. - * Callbacks are supported in `MultiWorkerMirroredStrategy`. - * Disable `run_eagerly` and distribution strategy if there are symbolic tensors added to the model using `add_metric` or `add_loss`. - * Loss and gradients should now more reliably be correctly scaled w.r.t. the global batch size when using a `tf.distribute.Strategy`. - * Set default loss reduction as `AUTO` for improving reliability of loss scaling with distribution strategy and custom training loops. `AUTO` indicates that the reduction option will be determined by the usage context. For almost all cases this defaults to `SUM_OVER_BATCH_SIZE`. When used in distribution strategy scope, outside of built-in training loops such as `tf.keras` `compile` and `fit`, we expect reduction value to be 'None' or 'SUM'. Using other values will raise an error. - * Support for multi-host `ncclAllReduce` in Distribution Strategy. + * Expose `tf.contrib.proto.*` ops in `tf.io` (they will exist in TF2) -* `tf.estimator`: - * Replace `tf.contrib.estimator.add_metrics` with `tf.estimator.add_metrics` - * Use `tf.compat.v1.estimator.inputs` instead of `tf.estimator.inputs` - * Replace contrib references with `tf.estimator.experimental.*` for apis in early_s in Estimator - * Canned Estimators will now use keras optimizers by default. An error will be raised if tf.train.Optimizers are used, and you will have to switch to tf.keras.optimizers or tf.compat.v1 canned Estimators. - * A checkpoint converter for canned Estimators has been provided to transition canned Estimators that are warm started from `tf.train.Optimizers` to `tf.keras.optimizers`. - * Losses are scaled in canned estimator v2 and not in the optimizers anymore. If you are using Estimator + distribution strategy + optimikzer v1 then the behavior does not change. This implies that if you are using custom estimator with optimizer v2, you have to scale losses. We have new utilities to help scale losses `tf.nn.compute_average_loss`, `tf.nn.scale_regularization_loss`. +* `tf.data`: -* `tf.keras`: - * Premade models (including Linear and WideDeep) have been introduced for the purpose of replacing Premade estimators. - * Model saving changes - * `model.save` and `tf.saved_model.save` may now save to the TensorFlow SavedModel format. The model can be restored using `tf.keras.models.load_model`. HDF5 files are still supported, and may be used by specifying `save_format="h5"` when saving. - * Raw TensorFlow functions can now be used in conjunction with the Keras Functional API during model creation. This obviates the need for users to create Lambda layers in most cases when using the Functional API. Like Lambda layers, TensorFlow functions that result in Variable creation or assign ops are not supported. - * Add support for passing list of lists to the `metrics` argument in Keras `compile`. - * Add `tf.keras.layers.AbstractRNNCell` as the preferred implementation for RNN cells in TF v2. User can use it to implement RNN cells with custom behavior. - * Keras training and validation curves are shown on the same plot when using the TensorBoard callback. - * Switched Keras `fit/evaluate/predict` execution to use only a single unified path by default unless eager execution has been explicitly disabled, regardless of input type. This unified path places an eager-friendly training step inside of a `tf.function`. With this - 1. All input types are converted to `Dataset`. - 2. The path assumes there is always a distribution strategy. when distribution strategy is not specified the path uses a no-op distribution strategy. - 3. The training step is wrapped in `tf.function` unless `run_eagerly=True` is set in compile. The single path execution code does not yet support all use cases. We fallback to the existing v1 execution paths if your model contains the following: - 1. `sample_weight_mode` in compile - 2. `weighted_metrics` in compile - 3. v1 optimizer - 4. target tensors in compile -If you are experiencing any issues because of this change, please inform us (file an issue) about your use case and you can unblock yourself by setting `experimental_run_tf_function=False` in compile meanwhile. We have seen couple of use cases where the model usage pattern is not as expected and would not work with this change. - 1. output tensors of one layer is used in the constructor of another. - 2. symbolic tensors outside the scope of the model are used in custom loss functions. - The flag can be disabled for these cases and ideally the usage pattern will need to be fixed. - * Mark Keras `set_session` as `compat.v1` only. - * `tf.keras.estimator.model_to_estimator` now supports exporting to `tf.train.Checkpoint format`, which allows the saved checkpoints to be compatible with `model.load_weights`. - * `keras.backend.resize_images` (and consequently, `keras.layers.Upsampling2D`) behavior has changed, a bug in the resizing implementation was fixed. - * Add an `implementation=3` mode for `tf.keras.layers.LocallyConnected2D` and `tf.keras.layers.LocallyConnected1D` layers using `tf.SparseTensor` to store weights, allowing a dramatic speedup for large sparse models. - * Raise error if `batch_size` argument is used when input is dataset/generator/keras sequence. - * Update TF 2.0 `keras.backend.name_scope` to use TF 2.0 `name_scope`. - * Add v2 module aliases for losses, metrics, initializers and optimizers: `tf.losses = tf.keras.losses` & `tf.metrics = tf.keras.metrics` & `tf.initializers = tf.keras.initializers` & `tf.optimizers = tf.keras.optimizers`. - * Updates binary cross entropy logic in Keras when input is probabilities. Instead of converting probabilities to logits, we are using the cross entropy formula for probabilities. - * Added public APIs for `cumsum` and `cumprod` keras backend functions. - * Add support for temporal sample weight mode in subclassed models. - * Raise `ValueError` if an integer is passed to the training APIs. - * Added fault-tolerance support for training Keras model via `model.fit()` with `MultiWorkerMirroredStrategy`, tutorial available. - * Custom Callback tutorial is now available. - * To train with `tf.distribute`, Keras API is recommended over estimator. - * `steps_per_epoch` and `steps` arguments are supported with numpy arrays. - * New error message when unexpected keys are used in sample_weight/class_weight dictionaries - * Losses are scaled in Keras compile/fit and not in the optimizers anymore. If you are using custom training loop, we have new utilities to help scale losses `tf.nn.compute_average_loss`, `tf.nn.scale_regularization_loss`. - * `Layer` apply and add_variable APIs are deprecated. - * Added support for channels first data format in cross entropy losses with logits and support for tensors with unknown ranks. - * Error messages will be raised if `add_update`, `add_metric`, `add_loss`, activity regularizers are used inside of a control flow branch. - * New loss reduction types: - 1. `AUTO`: Indicates that the reduction option will be determined by the usage context. For almost all cases this defaults to `SUM_OVER_BATCH_SIZE`. When used with `tf.distribute.Strategy`, outside of built-in training loops such as `tf.keras` `compile` and `fit`, we expect reduction value to be `SUM` or `NONE`. Using `AUTO` in that case will raise an error. - 2. `NONE`: Weighted losses with one dimension reduced (axis=-1, or axis specified by loss function). When this reduction type used with built-in Keras training loops like `fit`/`evaluate`, the unreduced vector loss is passed to the optimizer but the reported loss will be a scalar value. - 3. `SUM`: Scalar sum of weighted losses. 4. `SUM_OVER_BATCH_SIZE`: Scalar `SUM` divided by number of elements in losses. This reduction type is not supported when used with `tf.distribute.Strategy` outside of built-in training loops like `tf.keras` `compile`/`fit`. - * Wraps losses passed to the `compile` API (strings and v1 losses) which are not instances of v2 `Loss` class in `LossWrapper` class. => All losses will now use `SUM_OVER_BATCH_SIZE` reduction as default. - * `model.add_loss(symbolic_tensor)` should work in ambient eager. - * Update metric name to always reflect what the user has given in compile. Affects following cases - 1. When name is given as 'accuracy'/'crossentropy' - 2. When an aliased function name is used eg. 'mse' - 3. Removing the `weighted` prefix from weighted metric names. - * Allow non-Tensors through v2 losses. - * Add v2 sparse categorical crossentropy metric. - * Add v2 APIs for `AUCCurve` and `AUCSummationMethod` enums. - * `add_update` can now be passed a zero-arg callable in order to support turning off the update when setting `trainable=False` on a Layer of a Model compiled with `run_eagerly=True`. - * Standardize the LayerNormalization API by replacing the args `norm_axis` and `params_axis` with `axis`. - * Fixed critical bugs that help with DenseFeatures usability in TF2 + * Add support for TensorArrays to `tf.data Dataset`. + * Integrate Ragged Tensors with `tf.data`. + * All core and experimental tf.data transformations that input + user-defined functions can span multiple devices now. + * Extending the TF 2.0 support for `shuffle(..., + reshuffle_each_iteration=True)` and `cache()` to work across different + Python iterators for the same dataset. + * Removing the `experimental_numa_aware` option from `tf.data.Options`. + * Add `num_parallel_reads` and passing in a Dataset containing filenames + into `TextLineDataset` and `FixedLengthRecordDataset`. + * Add support for defaulting the value of `cycle_length` argument of + `tf.data.Dataset.interleave` to the number of schedulable CPU cores. + * Promoting `tf.data.experimental.enumerate_dataset` to core as + `tf.data.Dataset.enumerate`. + * Promoting `tf.data.experimental.unbatch` to core as + `tf.data.Dataset.unbatch`. + * Adds option for introducing slack in the pipeline to reduce CPU + contention, via `tf.data.Options().experimental_slack = True` + * Added experimental support for parallel batching to `batch()` and + `padded_batch()`. This functionality can be enabled through + `tf.data.Options()`. + * Support cancellation of long-running `reduce`. + * Now we use `dataset` node name as prefix instead of the op name, to + identify the component correctly in metrics, for pipelines with repeated + components. + * Improve the performance of datasets using `from_tensors()`. + * Promoting `unbatch` from experimental to core API. + * Adding support for datasets as inputs to `from_tensors` and + `from_tensor_slices` and batching and unbatching of nested datasets. -* `tf.lite`: - * Added evaluation script for `COCO` minival - * Add delegate support for `QUANTIZE`. - * Add `GATHER` support to NN API delegate. - * Added support for TFLiteConverter Python API in 2.0. Contains functions from_saved_model, from_keras_file, and from_concrete_functions. - * Add `EXPAND_DIMS` support to NN API delegate TEST. - * Add `narrow_range` attribute to QuantizeAndDequantizeV2 and V3. - * Added support for `tflite_convert` command line tool in 2.0. - * Post-training quantization tool supports quantizing weights shared by multiple operations. The models made with versions of this tool will use INT8 types for weights and will only be executable interpreters from this version onwards. - * Post-training quantization tool supports fp16 weights and GPU delegate acceleration for fp16. - * Add delegate support for `QUANTIZED_16BIT_LSTM`. - * Extracts `NNAPIDelegateKernel` from nnapi_delegate.cc +* `tf.distribute`: -* TensorRT - * Add TensorFlow 2.0-compatible `TrtGraphConverterV2` API for TensorRT conversion. - TensorRT initialization arguments are now passed wrapped in a named-tuple, - `TrtConversionParams`, rather than as separate arguments as in `TrtGraphConverter`. - * Changed API to optimize TensorRT enginges during graph optimization. This is now - done by calling `converter.build()` where previously `is_dynamic_op=False` would - be set. - * `converter.convert()` no longer returns a `tf.function`. Now the funtion must be - accessed from the saved model. - * The `converter.calibrate()` method has been removed. To trigger calibration, a - `calibration_input_fn` should be provided to `converter.convert()`. + * Enable `tf.distribute.experimental.MultiWorkerMirroredStrategy` working + in eager mode. + * Callbacks are supported in `MultiWorkerMirroredStrategy`. + * Disable `run_eagerly` and distribution strategy if there are symbolic + tensors added to the model using `add_metric` or `add_loss`. + * Loss and gradients should now more reliably be correctly scaled w.r.t. + the global batch size when using a `tf.distribute.Strategy`. + * Set default loss reduction as `AUTO` for improving reliability of loss + scaling with distribution strategy and custom training loops. `AUTO` + indicates that the reduction option will be determined by the usage + context. For almost all cases this defaults to `SUM_OVER_BATCH_SIZE`. + When used in distribution strategy scope, outside of built-in training + loops such as `tf.keras` `compile` and `fit`, we expect reduction value + to be 'None' or 'SUM'. Using other values will raise an error. + * Support for multi-host `ncclAllReduce` in Distribution Strategy. -* Other: - * Fix accidental quadratic graph construction cost in graph-mode `tf.gradients()`. - * ResourceVariable's gather op supports batch dimensions. - * ResourceVariable support for `gather_nd`. - * `ResourceVariable` and `Variable` no longer accepts `constraint` in the constructor, nor expose it as a @property. - * Added gradient for `SparseToDense` op. - * Expose a flag that allows the number of threads to vary across Python benchmarks. - * `image.resize` in 2.0 now supports gradients for the new resize kernels. - * `image.resize` now considers proper pixel centers and has new kernels (incl. anti-aliasing). - * Renamed `tf.image` functions to remove duplicate "image" where it is redundant. - * Variadic reduce is supported on CPU Variadic reduce is supported on CPU - * Remove unused `StringViewVariantWrapper`. - * Delete unused `Fingerprint64Map` op registration - * Add broadcasting support to `tf.matmul`. - * Add C++ Gradient for `BatchMatMulV2`. - * Add `tf.math.cumulative_logsumexp` operation. - * Add ellipsis (...) support for `tf.einsum()`. - * Add expand_composites argument to all `nest.*` methods. - * Added `strings.byte_split`. - * Add a new "result_type" parameter to `tf.strings.split`. - * Add name argument to `tf.string_split` and `tf.strings_split`. - * Extend `tf.strings.split` to support inputs with any rank. - * Added `tf.random.binomial`. - * Added `key` and `skip` methods to `random.experimental.Generator`. - * Extend `tf.function` with basic support for CompositeTensors arguments (such as `SparseTensor` and `RaggedTensor`). - * `parallel_for.pfor`: add converters for Softmax, LogSoftmax, IsNaN, All, Any, and MatrixSetDiag. - * `parallel_for`: add converters for LowerTriangularSolve and Cholesky. - * `parallel_for`: add converters for `LogMatrixDeterminant` and `MatrixBandPart`. - * `parallel_for`: Add converter for `MatrixDiag`. - * `parallel_for`: Add converters for `OneHot`, `LowerBound`, `UpperBound`. - * `parallel_for`: add converter for `BroadcastTo`. - * Add `pfor` converter for `Squeeze`. - * Add `RaggedTensor.placeholder()`. - * Add ragged tensor support to `tf.squeeze`. - * Update RaggedTensors to support int32 row_splits. - * Allow `LinearOperator.solve` to take a `LinearOperator`. - * Allow all dtypes for `LinearOperatorCirculant`. - * Introduce MaxParallelism method - * Add `LinearOperatorHouseholder`. - * Adds Philox support to new stateful RNG's XLA path. - * Added `TensorSpec` support for CompositeTensors. - * Added `tf.linalg.tridiagonal_solve` op. - * Added partial_pivoting input parameter to `tf.linalg.tridiagonal_solve`. - * Added gradient to `tf.linalg.tridiagonal_solve`. - * Added `tf.linalg.tridiagonal_mul op`. - * Added GPU implementation of `tf.linalg.tridiagonal_matmul`. - * Added `LinearOperatorToeplitz`. - * Upgraded LIBXSMM to version 1.11. - * Uniform processing of quantized embeddings by Gather and EmbeddingLookup Ops. - * Correct a misstatement in the documentation of the sparse softmax cross entropy logit parameter. - * Add `tf.ragged.boolean_mask`. - * `tf.switch_case` added, which selects a branch_fn based on a branch_index. - * The C++ kernel of gather op supports batch dimensions. - * Fixed default value and documentation for `trainable` arg of tf.Variable. - * `EagerTensor` now supports numpy buffer interface for tensors. - * This change bumps the version number of the `FullyConnected` Op to 5. - * Added new op: `tf.strings.unsorted_segment_join`. - * Added HW acceleration support for `topK_v2`. - * CloudBigtable version updated to v0.10.0 BEGIN_PUBLIC CloudBigtable version updated to v0.10.0. - * Expose `Head` as public API. - * Added `tf.sparse.from_dense` utility function. - * Improved ragged tensor support in `TensorFlowTestCase`. - * Added a function `nested_value_rowids` for ragged tensors. - * Added `tf.ragged.stack`. - * Makes the a-normal form transformation in Pyct configurable as to which nodes are converted to variables and which are not. - * `ResizeInputTensor` now works for all delegates. - * `tf.cond` emits a StatelessIf op if the branch functions are stateless and do not touch any resources. - * Add support of local soft device placement for eager op. - * Pass partial_pivoting to the `_TridiagonalSolveGrad`. - * Add HW acceleration support for `LogSoftMax`. - * Add guard to avoid acceleration of L2 Normalization with input rank != 4 - * Fix memory allocation problem when calling `AddNewInputConstantTensor`. - * Delegate application failure leaves interpreter in valid state - * `tf.while_loop` emits a StatelessWhile op if the cond and body functions are stateless and do not touch any resources. - * `tf.cond`, `tf.while` and if and while in AutoGraph now accept a nonscalar predicate if has a single element. This does not affect non-V2 control flow. - * Fix potential security vulnerability where decoding variant tensors from proto could result in heap out of bounds memory access. - * Only create a GCS directory object if the object does not already exist. - * Introduce `dynamic` constructor argument in Layer and Model, which should be set to `True` when using imperative control flow in the `call` method. - * Begin adding Go wrapper for C Eager API. - * XLA HLO graphs can be inspected with interactive_graphviz tool now. - * Add dataset ops to the graph (or create kernels in Eager execution) during the python Dataset object creation instead doing it during Iterator creation time. - * Add `batch_dims` argument to `tf.gather`. - * The behavior of `tf.gather` is now correct when `axis=None` and `batch_dims<0`. - * Update docstring for gather to properly describe the non-empty `batch_dims` case. - * Removing of dtype in the constructor of initializers and partition_info in call. - * Add `tf.math.nextafter` op. - * Turn on MKL-DNN contraction kernels by default. MKL-DNN dynamically dispatches the best kernel implementation based on CPU vector architecture. To disable them, build with `--define=tensorflow_mkldnn_contraction_kernel=0`. - * `tf.linspace(start, stop, num)` now always uses "stop" as last value (for num > 1) - * Added top-k to precision and recall to keras metrics. - * Add a ragged size op and register it to the op dispatcher - * Transitive dependencies on :`pooling_ops` were removed. Some users may need to add explicit dependencies on :`pooling_ops` if they reference the operators from that library. - * Add `CompositeTensor` base class. - * Malformed gif images could result in an access out of bounds in the color palette of the frame. This has been fixed now - * Add templates and interfaces for creating lookup tables - * `Tensor::UnsafeCopyFromInternal` deprecated in favor `Tensor::BitcastFrom`. - * In `map_vectorization` optimization, reduce the degree of parallelism in the vectorized map node. - * Add variant wrapper for `absl::string_view`. - * Add OpKernels for some stateless maps. - * DType is no longer convertible to an int. Use `dtype.as_datatype_enum` instead of `int(dtype)` to get the same result. - * Support both binary and -1/1 label input in v2 hinge and squared hinge losses. - * Added `LinearOperator.adjoint` and `LinearOperator.H` (alias). - * Expose CriticalSection in core as `tf.CriticalSection`. - * Enhanced graphviz output. - * Add opkernel templates for common table operations. - * Fix callbacks do not log values in eager mode when a deferred build model is used. - * `SignatureDef` util functions have been deprecated. - * Update `Fingerprint64Map` to use aliases - * Add legacy string flat hash map op kernels. - * Add support for `add_metric` in the graph function mode. - * Updating cosine similarity loss - removed the negate sign from cosine similarity. - * Changed default for gradient accumulation for TPU embeddings to true. - * Adds summary trace API for collecting graph and profile information. - * The `precision_mode` argument to `TrtGraphConverter` is now case insensitive. +* `tf.estimator`: + * Replace `tf.contrib.estimator.add_metrics` with + `tf.estimator.add_metrics` + * Use `tf.compat.v1.estimator.inputs` instead of `tf.estimator.inputs` + * Replace contrib references with `tf.estimator.experimental.*` for apis + in early_s in Estimator + * Canned Estimators will now use keras optimizers by default. An error + will be raised if tf.train.Optimizers are used, and you will have to + switch to tf.keras.optimizers or tf.compat.v1 canned Estimators. + * A checkpoint converter for canned Estimators has been provided to + transition canned Estimators that are warm started from + `tf.train.Optimizers` to `tf.keras.optimizers`. + * Losses are scaled in canned estimator v2 and not in the optimizers + anymore. If you are using Estimator + distribution strategy + optimikzer + v1 then the behavior does not change. This implies that if you are using + custom estimator with optimizer v2, you have to scale losses. We have + new utilities to help scale losses `tf.nn.compute_average_loss`, + `tf.nn.scale_regularization_loss`. + +* `tf.keras`: + + * Premade models (including Linear and WideDeep) have been introduced for + the purpose of replacing Premade estimators. + * Model saving changes + * `model.save` and `tf.saved_model.save` may now save to the TensorFlow + SavedModel format. The model can be restored using + `tf.keras.models.load_model`. HDF5 files are still supported, and may be + used by specifying `save_format="h5"` when saving. + * Raw TensorFlow functions can now be used in conjunction with the Keras + Functional API during model creation. This obviates the need for users + to create Lambda layers in most cases when using the Functional API. + Like Lambda layers, TensorFlow functions that result in Variable + creation or assign ops are not supported. + * Add support for passing list of lists to the `metrics` argument in Keras + `compile`. + * Add `tf.keras.layers.AbstractRNNCell` as the preferred implementation + for RNN cells in TF v2. User can use it to implement RNN cells with + custom behavior. + * Keras training and validation curves are shown on the same plot when + using the TensorBoard callback. + * Switched Keras `fit/evaluate/predict` execution to use only a single + unified path by default unless eager execution has been explicitly + disabled, regardless of input type. This unified path places an + eager-friendly training step inside of a `tf.function`. With this + * All input types are converted to `Dataset`. + * The path assumes there is always a distribution strategy. when + distribution strategy is not specified the path uses a no-op + distribution strategy. + * The training step is wrapped in `tf.function` unless `run_eagerly=True` + is set in compile. The single path execution code does not yet support + all use cases. We fallback to the existing v1 execution paths if your + model contains the following: + 1. `sample_weight_mode` in compile + 2. `weighted_metrics` in compile + 3. v1 optimizer + 4. target tensors in compile If you are experiencing any issues because + of this change, please inform us (file an issue) about your use case + and you can unblock yourself by setting + `experimental_run_tf_function=False` in compile meanwhile. We have + seen couple of use cases where the model usage pattern is not as + expected and would not work with this change. + * output tensors of one layer is used in the constructor of another. + * symbolic tensors outside the scope of the model are used in custom loss + functions. The flag can be disabled for these cases and ideally the + usage pattern will need to be fixed. + * Mark Keras `set_session` as `compat.v1` only. + * `tf.keras.estimator.model_to_estimator` now supports exporting to + `tf.train.Checkpoint format`, which allows the saved checkpoints to be + compatible with `model.load_weights`. + * `keras.backend.resize_images` (and consequently, + `keras.layers.Upsampling2D`) behavior has changed, a bug in the resizing + implementation was fixed. + * Add an `implementation=3` mode for `tf.keras.layers.LocallyConnected2D` + and `tf.keras.layers.LocallyConnected1D` layers using `tf.SparseTensor` + to store weights, allowing a dramatic speedup for large sparse models. + * Raise error if `batch_size` argument is used when input is + dataset/generator/keras sequence. + * Update TF 2.0 `keras.backend.name_scope` to use TF 2.0 `name_scope`. + * Add v2 module aliases for losses, metrics, initializers and optimizers: + `tf.losses = tf.keras.losses` & `tf.metrics = tf.keras.metrics` & + `tf.initializers = tf.keras.initializers` & `tf.optimizers = + tf.keras.optimizers`. + * Updates binary cross entropy logic in Keras when input is probabilities. + Instead of converting probabilities to logits, we are using the cross + entropy formula for probabilities. + * Added public APIs for `cumsum` and `cumprod` keras backend functions. + * Add support for temporal sample weight mode in subclassed models. + * Raise `ValueError` if an integer is passed to the training APIs. + * Added fault-tolerance support for training Keras model via `model.fit()` + with `MultiWorkerMirroredStrategy`, tutorial available. + * Custom Callback tutorial is now available. + * To train with `tf.distribute`, Keras API is recommended over estimator. + * `steps_per_epoch` and `steps` arguments are supported with numpy arrays. + * New error message when unexpected keys are used in + sample_weight/class_weight dictionaries + * Losses are scaled in Keras compile/fit and not in the optimizers + anymore. If you are using custom training loop, we have new utilities to + help scale losses `tf.nn.compute_average_loss`, + `tf.nn.scale_regularization_loss`. + * `Layer` apply and add_variable APIs are deprecated. + * Added support for channels first data format in cross entropy losses + with logits and support for tensors with unknown ranks. + * Error messages will be raised if `add_update`, `add_metric`, `add_loss`, + activity regularizers are used inside of a control flow branch. + * New loss reduction types: + * `AUTO`: Indicates that the reduction option will be determined by the + usage context. For almost all cases this defaults to + `SUM_OVER_BATCH_SIZE`. When used with `tf.distribute.Strategy`, outside + of built-in training loops such as `tf.keras` `compile` and `fit`, we + expect reduction value to be `SUM` or `NONE`. Using `AUTO` in that case + will raise an error. + * `NONE`: Weighted losses with one dimension reduced (axis=-1, or axis + specified by loss function). When this reduction type used with built-in + Keras training loops like `fit`/`evaluate`, the unreduced vector loss is + passed to the optimizer but the reported loss will be a scalar value. + * `SUM`: Scalar sum of weighted losses. 4. `SUM_OVER_BATCH_SIZE`: Scalar + `SUM` divided by number of elements in losses. This reduction type is + not supported when used with `tf.distribute.Strategy` outside of + built-in training loops like `tf.keras` `compile`/`fit`. + * Wraps losses passed to the `compile` API (strings and v1 losses) which + are not instances of v2 `Loss` class in `LossWrapper` class. => All + losses will now use `SUM_OVER_BATCH_SIZE` reduction as default. + * `model.add_loss(symbolic_tensor)` should work in ambient eager. + * Update metric name to always reflect what the user has given in compile. + Affects following cases + * When name is given as 'accuracy'/'crossentropy' + * When an aliased function name is used eg. 'mse' + * Removing the `weighted` prefix from weighted metric names. + * Allow non-Tensors through v2 losses. + * Add v2 sparse categorical crossentropy metric. + * Add v2 APIs for `AUCCurve` and `AUCSummationMethod` enums. + * `add_update` can now be passed a zero-arg callable in order to support + turning off the update when setting `trainable=False` on a Layer of a + Model compiled with `run_eagerly=True`. + * Standardize the LayerNormalization API by replacing the args `norm_axis` + and `params_axis` with `axis`. + * Fixed critical bugs that help with DenseFeatures usability in TF2 + +* `tf.lite`: + + * Added evaluation script for `COCO` minival + * Add delegate support for `QUANTIZE`. + * Add `GATHER` support to NN API delegate. + * Added support for TFLiteConverter Python API in 2.0. Contains functions + from_saved_model, from_keras_file, and from_concrete_functions. + * Add `EXPAND_DIMS` support to NN API delegate TEST. + * Add `narrow_range` attribute to QuantizeAndDequantizeV2 and V3. + * Added support for `tflite_convert` command line tool in 2.0. + * Post-training quantization tool supports quantizing weights shared by + multiple operations. The models made with versions of this tool will use + INT8 types for weights and will only be executable interpreters from + this version onwards. + * Post-training quantization tool supports fp16 weights and GPU delegate + acceleration for fp16. + * Add delegate support for `QUANTIZED_16BIT_LSTM`. + * Extracts `NNAPIDelegateKernel` from nnapi_delegate.cc + +* TensorRT + + * Add TensorFlow 2.0-compatible `TrtGraphConverterV2` API for TensorRT + conversion. TensorRT initialization arguments are now passed wrapped in + a named-tuple, `TrtConversionParams`, rather than as separate arguments + as in `TrtGraphConverter`. + * Changed API to optimize TensorRT enginges during graph optimization. + This is now done by calling `converter.build()` where previously + `is_dynamic_op=False` would be set. + * `converter.convert()` no longer returns a `tf.function`. Now the + function must be accessed from the saved model. + * The `converter.calibrate()` method has been removed. To trigger + calibration, a `calibration_input_fn` should be provided to + `converter.convert()`. + +* Other: + + * Fix accidental quadratic graph construction cost in graph-mode + `tf.gradients()`. + * ResourceVariable's gather op supports batch dimensions. + * ResourceVariable support for `gather_nd`. + * `ResourceVariable` and `Variable` no longer accepts `constraint` in the + constructor, nor expose it as a @property. + * Added gradient for `SparseToDense` op. + * Expose a flag that allows the number of threads to vary across Python + benchmarks. + * `image.resize` in 2.0 now supports gradients for the new resize kernels. + * `image.resize` now considers proper pixel centers and has new kernels + (incl. anti-aliasing). + * Renamed `tf.image` functions to remove duplicate "image" where it is + redundant. + * Variadic reduce is supported on CPU Variadic reduce is supported on CPU + * Remove unused `StringViewVariantWrapper`. + * Delete unused `Fingerprint64Map` op registration + * Add broadcasting support to `tf.matmul`. + * Add C++ Gradient for `BatchMatMulV2`. + * Add `tf.math.cumulative_logsumexp` operation. + * Add ellipsis (...) support for `tf.einsum()`. + * Add expand_composites argument to all `nest.*` methods. + * Added `strings.byte_split`. + * Add a new "result_type" parameter to `tf.strings.split`. + * Add name argument to `tf.string_split` and `tf.strings_split`. + * Extend `tf.strings.split` to support inputs with any rank. + * Added `tf.random.binomial`. + * Added `key` and `skip` methods to `random.experimental.Generator`. + * Extend `tf.function` with basic support for CompositeTensors arguments + (such as `SparseTensor` and `RaggedTensor`). + * `parallel_for.pfor`: add converters for Softmax, LogSoftmax, IsNaN, All, + Any, and MatrixSetDiag. + * `parallel_for`: add converters for LowerTriangularSolve and Cholesky. + * `parallel_for`: add converters for `LogMatrixDeterminant` and + `MatrixBandPart`. + * `parallel_for`: Add converter for `MatrixDiag`. + * `parallel_for`: Add converters for `OneHot`, `LowerBound`, `UpperBound`. + * `parallel_for`: add converter for `BroadcastTo`. + * Add `pfor` converter for `Squeeze`. + * Add `RaggedTensor.placeholder()`. + * Add ragged tensor support to `tf.squeeze`. + * Update RaggedTensors to support int32 row_splits. + * Allow `LinearOperator.solve` to take a `LinearOperator`. + * Allow all dtypes for `LinearOperatorCirculant`. + * Introduce MaxParallelism method + * Add `LinearOperatorHouseholder`. + * Adds Philox support to new stateful RNG's XLA path. + * Added `TensorSpec` support for CompositeTensors. + * Added `tf.linalg.tridiagonal_solve` op. + * Added partial_pivoting input parameter to `tf.linalg.tridiagonal_solve`. + * Added gradient to `tf.linalg.tridiagonal_solve`. + * Added `tf.linalg.tridiagonal_mul op`. + * Added GPU implementation of `tf.linalg.tridiagonal_matmul`. + * Added `LinearOperatorToeplitz`. + * Upgraded LIBXSMM to version 1.11. + * Uniform processing of quantized embeddings by Gather and EmbeddingLookup + Ops. + * Correct a misstatement in the documentation of the sparse softmax cross + entropy logit parameter. + * Add `tf.ragged.boolean_mask`. + * `tf.switch_case` added, which selects a branch_fn based on a + branch_index. + * The C++ kernel of gather op supports batch dimensions. + * Fixed default value and documentation for `trainable` arg of + tf.Variable. + * `EagerTensor` now supports numpy buffer interface for tensors. + * This change bumps the version number of the `FullyConnected` Op to 5. + * Added new op: `tf.strings.unsorted_segment_join`. + * Added HW acceleration support for `topK_v2`. + * CloudBigtable version updated to v0.10.0 BEGIN_PUBLIC CloudBigtable + version updated to v0.10.0. + * Expose `Head` as public API. + * Added `tf.sparse.from_dense` utility function. + * Improved ragged tensor support in `TensorFlowTestCase`. + * Added a function `nested_value_rowids` for ragged tensors. + * Added `tf.ragged.stack`. + * Makes the a-normal form transformation in Pyct configurable as to which + nodes are converted to variables and which are not. + * `ResizeInputTensor` now works for all delegates. + * `tf.cond` emits a StatelessIf op if the branch functions are stateless + and do not touch any resources. + * Add support of local soft device placement for eager op. + * Pass partial_pivoting to the `_TridiagonalSolveGrad`. + * Add HW acceleration support for `LogSoftMax`. + * Add guard to avoid acceleration of L2 Normalization with input rank != 4 + * Fix memory allocation problem when calling `AddNewInputConstantTensor`. + * Delegate application failure leaves interpreter in valid state + * `tf.while_loop` emits a StatelessWhile op if the cond and body functions + are stateless and do not touch any resources. + * `tf.cond`, `tf.while` and if and while in AutoGraph now accept a + nonscalar predicate if has a single element. This does not affect non-V2 + control flow. + * Fix potential security vulnerability where decoding variant tensors from + proto could result in heap out of bounds memory access. + * Only create a GCS directory object if the object does not already exist. + * Introduce `dynamic` constructor argument in Layer and Model, which + should be set to `True` when using imperative control flow in the `call` + method. + * Begin adding Go wrapper for C Eager API. + * XLA HLO graphs can be inspected with interactive_graphviz tool now. + * Add dataset ops to the graph (or create kernels in Eager execution) + during the python Dataset object creation instead doing it during + Iterator creation time. + * Add `batch_dims` argument to `tf.gather`. + * The behavior of `tf.gather` is now correct when `axis=None` and + `batch_dims<0`. + * Update docstring for gather to properly describe the non-empty + `batch_dims` case. + * Removing of dtype in the constructor of initializers and partition_info + in call. + * Add `tf.math.nextafter` op. + * Turn on MKL-DNN contraction kernels by default. MKL-DNN dynamically + dispatches the best kernel implementation based on CPU vector + architecture. To disable them, build with + `--define=tensorflow_mkldnn_contraction_kernel=0`. + * `tf.linspace(start, stop, num)` now always uses "stop" as last value + (for num > 1) + * Added top-k to precision and recall to keras metrics. + * Add a ragged size op and register it to the op dispatcher + * Transitive dependencies on :`pooling_ops` were removed. Some users may + need to add explicit dependencies on :`pooling_ops` if they reference + the operators from that library. + * Add `CompositeTensor` base class. + * Malformed gif images could result in an access out of bounds in the + color palette of the frame. This has been fixed now + * Add templates and interfaces for creating lookup tables + * `Tensor::UnsafeCopyFromInternal` deprecated in favor + `Tensor::BitcastFrom`. + * In `map_vectorization` optimization, reduce the degree of parallelism in + the vectorized map node. + * Add variant wrapper for `absl::string_view`. + * Add OpKernels for some stateless maps. + * DType is no longer convertible to an int. Use `dtype.as_datatype_enum` + instead of `int(dtype)` to get the same result. + * Support both binary and -1/1 label input in v2 hinge and squared hinge + losses. + * Added `LinearOperator.adjoint` and `LinearOperator.H` (alias). + * Expose CriticalSection in core as `tf.CriticalSection`. + * Enhanced graphviz output. + * Add opkernel templates for common table operations. + * Fix callbacks do not log values in eager mode when a deferred build + model is used. + * `SignatureDef` util functions have been deprecated. + * Update `Fingerprint64Map` to use aliases + * Add legacy string flat hash map op kernels. + * Add support for `add_metric` in the graph function mode. + * Updating cosine similarity loss - removed the negate sign from cosine + similarity. + * Changed default for gradient accumulation for TPU embeddings to true. + * Adds summary trace API for collecting graph and profile information. + * The `precision_mode` argument to `TrtGraphConverter` is now case + insensitive. ## Thanks to our Contributors @@ -715,7 +863,7 @@ Weweler, Zantares, zjjott, 卜居, 王振华 (Wang Zhenhua), 黄鑫 * Updates `png_archive` dependency to 1.6.37 to not be affected by CVE-2019-7317, CVE-2018-13785, and CVE-2018-14048. -* Updates `sqlite` depenency to 3.28.0 to not be affected by CVE-2018-20506, +* Updates `sqlite` dependency to 3.28.0 to not be affected by CVE-2018-20506, CVE-2018-20346, and CVE-2018-20505. # Release 1.12.2 @@ -901,9 +1049,9 @@ Weweler, Zantares, zjjott, 卜居, 王振华 (Wang Zhenhua), 黄鑫 compilation as a second return argument. * XLA HLO graphs can now be rendered as SVG/HTML. * Estimator - * Replace all occurences of `tf.contrib.estimator.BaselineEstimator` with + * Replace all occurrences of `tf.contrib.estimator.BaselineEstimator` with `tf.estimator.BaselineEstimator` - * Replace all occurences of + * Replace all occurrences of `tf.contrib.estimator.DNNLinearCombinedEstimator` with `tf.estimator.DNNLinearCombinedEstimator` * Replace all occurrences of `tf.contrib.estimator.DNNEstimator` with @@ -915,7 +1063,7 @@ Weweler, Zantares, zjjott, 卜居, 王振华 (Wang Zhenhua), 黄鑫 `tf.estimator.Estimator.experimental_export_all_saved_models`. * Update `regression_head` to the new Head API for Canned Estimator V2. * Switch `multi_class_head` to Head API for Canned Estimator V2. - * Replace all occurences of `tf.contrib.estimator.InMemoryEvaluatorHook` + * Replace all occurrences of `tf.contrib.estimator.InMemoryEvaluatorHook` and `tf.contrib.estimator.make_stop_at_checkpoint_step_hook` with `tf.estimator.experimental.InMemoryEvaluatorHook` and `tf.estimator.experimental.make_stop_at_checkpoint_step_hook` diff --git a/configure.py b/configure.py index fedbd470f2d..93c386240ce 100644 --- a/configure.py +++ b/configure.py @@ -33,7 +33,7 @@ except ImportError: from distutils.spawn import find_executable as which # pylint: enable=g-import-not-at-top -_DEFAULT_CUDA_VERSION = '10.1' +_DEFAULT_CUDA_VERSION = '10' _DEFAULT_CUDNN_VERSION = '7' _DEFAULT_TENSORRT_VERSION = '6' _DEFAULT_CUDA_COMPUTE_CAPABILITIES = '3.5,7.0' diff --git a/tensorflow/BUILD b/tensorflow/BUILD index 603c2a5c45c..081edb21ae1 100644 --- a/tensorflow/BUILD +++ b/tensorflow/BUILD @@ -478,7 +478,7 @@ bzl_library( visibility = ["//visibility:public"], deps = [ "//tensorflow/core/platform:build_config_root_bzl", - "//tensorflow/core/platform:cuda_build_defs_bzl", + "//tensorflow/core/platform/default:cuda_build_defs_bzl", "//third_party/mkl:build_defs_bzl", "//third_party/mkl_dnn:build_defs_bzl", "//third_party/ngraph:build_defs_bzl", diff --git a/tensorflow/c/BUILD b/tensorflow/c/BUILD index cabc3b21e45..efe01f7e049 100644 --- a/tensorflow/c/BUILD +++ b/tensorflow/c/BUILD @@ -108,6 +108,7 @@ tf_cuda_library( ":tf_attrtype", ":tf_status_internal", ":tf_file_statistics", + ":tf_tensor_internal", ] + select({ "//tensorflow:with_xla_support": [ "//tensorflow/compiler/tf2xla:xla_compiler", @@ -251,6 +252,7 @@ tf_cuda_library( "tf_tensor.h", "tf_tensor_internal.h", ], + visibility = ["//tensorflow/c:__subpackages__"], deps = select({ "//tensorflow:android": [ "//tensorflow/core:android_tensorflow_lib_lite", @@ -259,6 +261,7 @@ tf_cuda_library( ":tf_datatype", ":tf_status", "//tensorflow/core:framework", + "//tensorflow/core:protos_all_cc", ], }), ) diff --git a/tensorflow/c/eager/BUILD b/tensorflow/c/eager/BUILD index a8bbf911247..130e9a0c3c7 100644 --- a/tensorflow/c/eager/BUILD +++ b/tensorflow/c/eager/BUILD @@ -37,9 +37,11 @@ tf_cuda_library( "//tensorflow/core:android_tensorflow_lib_lite", ], "//conditions:default": [ + "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/container:fixed_array", "//tensorflow/c:c_api", "//tensorflow/c:c_api_internal", + "//tensorflow/c:tf_tensor_internal", "//tensorflow/core:core_cpu", "//tensorflow/core/common_runtime/eager:attr_builder", "//tensorflow/core/common_runtime/eager:context", @@ -53,6 +55,7 @@ tf_cuda_library( "//tensorflow/core:framework_internal", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core/platform:errors", "//tensorflow/core:protos_all_cc", "//tensorflow/core/profiler/lib:traceme", ], diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc index 8793e308466..66a2a4aaa3c 100644 --- a/tensorflow/c/eager/c_api.cc +++ b/tensorflow/c/eager/c_api.cc @@ -26,10 +26,12 @@ limitations under the License. #include "tensorflow/core/platform/platform.h" // clang-format on +#include "absl/algorithm/container.h" #include "absl/container/fixed_array.h" #include "absl/memory/memory.h" #include "tensorflow/c/c_api.h" #include "tensorflow/c/c_api_internal.h" +#include "tensorflow/c/tf_tensor_internal.h" #include "tensorflow/c/eager/c_api_experimental.h" #include "tensorflow/c/eager/c_api_internal.h" #include "tensorflow/core/common_runtime/device.h" @@ -38,6 +40,7 @@ limitations under the License. #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/framework/function.h" +#include "tensorflow/core/platform/errors.h" #include "tensorflow/core/platform/platform.h" // NOLINT #include "tensorflow/core/protobuf/error_codes.pb.h" #include "tensorflow/core/util/device_name_utils.h" @@ -1007,9 +1010,105 @@ TF_Tensor* TFE_TensorHandleResolve(TFE_TensorHandle* h, TF_Status* status) { } } +void* TFE_TensorHandleDevicePointer(TFE_TensorHandle* h, TF_Status* status) { + if (h == nullptr || h->handle == nullptr) { + status->status = tensorflow::errors::InvalidArgument( + "The passed in handle is a nullptr"); + return nullptr; + } + tensorflow::TensorHandle* handle = h->handle; + + if (handle->IsRemote()) { + status->status = tensorflow::errors::InvalidArgument( + "TFE_TensorHandleDevicePointer may not be called on a remote tensor " + "handle."); + return nullptr; + } + if (handle->device() != nullptr) { + status->status = handle->device()->Sync(); + if (!status->status.ok()) { + return nullptr; + } + } + const tensorflow::Tensor* tensor; + status->status = handle->Tensor(&tensor); + if (!status->status.ok()) { + return nullptr; + } + return const_cast( + static_cast(tensor->tensor_data().data())); +} + +TFE_TensorHandle* TFE_NewTensorHandleFromDeviceMemory( + TFE_Context* ctx, const char* device_name, TF_DataType dtype, + const int64_t* dims, int num_dims, void* data, size_t len, + void (*deallocator)(void* data, size_t len, void* arg), + void* deallocator_arg, TF_Status* status) { + tensorflow::Device* device; + status->status = ctx->context->FindDeviceFromName(device_name, &device); + if (!status->status.ok()) { + deallocator(data, len, deallocator_arg); + return nullptr; + } + std::vector dimvec(num_dims); + for (int i = 0; i < num_dims; ++i) { + dimvec[i] = static_cast(dims[i]); + } + + if (dtype == TF_STRING || dtype == TF_RESOURCE || + !tensorflow::DataTypeCanUseMemcpy( + static_cast(dtype))) { + status->status = tensorflow::errors::InvalidArgument( + "Trying to create a tensor with a pointer to non-pod memory."); + deallocator(data, len, deallocator_arg); + return nullptr; + } + // TODO(apassos) do we need to wrap the deallocator here to make sure to sync + // the device? + TF_ManagedBuffer* buf = + new TF_ManagedBuffer(data, len, deallocator, deallocator_arg); + + tensorflow::Tensor t(static_cast(dtype), + tensorflow::TensorShape(dimvec), buf); + buf->Unref(); + tensorflow::TensorHandle* ret_handle; + status->status = tensorflow::TensorHandle::CreateLocalHandle( + t, device, ctx->context, &ret_handle); + if (!status->status.ok()) { + return nullptr; + } + return new TFE_TensorHandle(ret_handle); +} + +// This function will block till the operation that produces `h` has +// completed. This is only valid on local TFE_TensorHandles. Returns the size in +// bytes of the memory pointed to by the device pointer returned above. +size_t TFE_TensorHandleDeviceMemorySize(TFE_TensorHandle* h, + TF_Status* status) { + if (h == nullptr || h->handle == nullptr) { + status->status = tensorflow::errors::InvalidArgument( + "The passed in handle is a nullptr"); + return 0; + } + tensorflow::TensorHandle* handle = h->handle; + + if (handle->IsRemote()) { + status->status = tensorflow::errors::InvalidArgument( + "TFE_TensorHandleDeviceMemorySize may not be called on a remote tensor " + "handle."); + return 0; + } + const tensorflow::Tensor* tensor; + status->status = handle->Tensor(&tensor); + if (!status->status.ok()) { + return 0; + } + return tensor->TotalBytes(); +} + TFE_Op* TFE_NewOp(TFE_Context* ctx, const char* op_or_function_name, TF_Status* status) { - return NewOrResetOp(ctx, op_or_function_name, status, + return NewOrResetOp(ctx, op_or_function_name, nullptr, status, /* op_to_reset= */ nullptr); } diff --git a/tensorflow/c/eager/c_api_experimental.cc b/tensorflow/c/eager/c_api_experimental.cc index b513fcedc59..aa6bbb2b8e5 100644 --- a/tensorflow/c/eager/c_api_experimental.cc +++ b/tensorflow/c/eager/c_api_experimental.cc @@ -29,9 +29,11 @@ limitations under the License. using tensorflow::string; void TFE_OpReset(TFE_Context* ctx, const char* op_or_function_name, - TF_Status* status, TFE_Op* op_to_reset) { + const char* raw_device_name, TF_Status* status, + TFE_Op* op_to_reset) { if (op_to_reset) { - NewOrResetOp(ctx, op_or_function_name, status, op_to_reset); + NewOrResetOp(ctx, op_or_function_name, raw_device_name, status, + op_to_reset); } else { TF_SetStatus(status, TF_INVALID_ARGUMENT, "op_to_reset should not be nullptr"); diff --git a/tensorflow/c/eager/c_api_experimental.h b/tensorflow/c/eager/c_api_experimental.h index 82cebed76fb..d318185e287 100644 --- a/tensorflow/c/eager/c_api_experimental.h +++ b/tensorflow/c/eager/c_api_experimental.h @@ -22,8 +22,16 @@ limitations under the License. extern "C" { #endif +// Resets `op_to_reset` with `op_or_function_name` and `raw_device_name`. This +// is for performance optimization by reusing an exiting unused op rather than +// creating a new op every time. If `raw_device_name` is `NULL` or empty, it +// does not set the device name. If it's not `NULL`, then it attempts to parse +// and set the device name. It's effectively `TFE_OpSetDevice`, but it is faster +// than seperately calling it because if the existing op has the same +// `raw_device_name`, it skips parsing and just leave as it is. TF_CAPI_EXPORT extern void TFE_OpReset(TFE_Context* ctx, const char* op_or_function_name, + const char* raw_device_name, TF_Status* status, TFE_Op* op_to_reset); TF_CAPI_EXPORT extern void TFE_OpConsumeInput(TFE_Op* op, TFE_TensorHandle* h, @@ -426,6 +434,30 @@ TF_CAPI_EXPORT extern bool TFE_ContextCheckAlive(TFE_Context* ctx, const char* worker_name, TF_Status* status); +// This function will block till the operation that produces `h` has +// completed. This is only valid on local TFE_TensorHandles. The pointer +// returned will be on the device in which the TFE_TensorHandle resides (so e.g. +// for a GPU tensor this will return a pointer to GPU memory). The pointer is +// only guaranteed to be valid until TFE_DeleteTensorHandle is called on this +// TensorHandle. Only supports POD data types. +TF_CAPI_EXPORT extern void* TFE_TensorHandleDevicePointer(TFE_TensorHandle*, + TF_Status*); + +// This function will block till the operation that produces `h` has +// completed. This is only valid on local TFE_TensorHandles. Returns the size in +// bytes of the memory pointed to by the device pointer returned above. +TF_CAPI_EXPORT extern size_t TFE_TensorHandleDeviceMemorySize(TFE_TensorHandle*, + TF_Status*); + +// Creates a new TensorHandle from memory residing in device_name. Takes +// ownership of the memory, and will call deleter to release it after TF +// no longer needs it or in case of error. +TF_CAPI_EXPORT extern TFE_TensorHandle* TFE_NewTensorHandleFromDeviceMemory( + TFE_Context* ctx, const char* device_name, TF_DataType, const int64_t* dims, + int num_dims, void* data, size_t len, + void (*deallocator)(void* data, size_t len, void* arg), + void* deallocator_arg, TF_Status* status); + #ifdef __cplusplus } /* end extern "C" */ #endif diff --git a/tensorflow/c/eager/c_api_experimental_test.cc b/tensorflow/c/eager/c_api_experimental_test.cc index 026fc4d49a0..60a47b4ee43 100644 --- a/tensorflow/c/eager/c_api_experimental_test.cc +++ b/tensorflow/c/eager/c_api_experimental_test.cc @@ -495,5 +495,54 @@ void Executor_MatMul_CPU(bool async) { TEST(CAPI, Executor_MatMul_CPU) { Executor_MatMul_CPU(false); } TEST(CAPI, Executor_MatMul_CPUAsync) { Executor_MatMul_CPU(true); } +void Deleter(void* data, size_t unused, void* tensor_handle) { + TFE_DeleteTensorHandle(static_cast(tensor_handle)); +} + +TEST(CAPI, TensorHandleOnDeviceMemory) { + TF_Status* status = TF_NewStatus(); + TFE_ContextOptions* opts = TFE_NewContextOptions(); + TFE_Context* ctx = TFE_NewContext(opts, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_DeleteContextOptions(opts); + + TFE_TensorHandle* m = TestMatrixTensorHandle(); + TF_Tensor* m_data = TFE_TensorHandleResolve(m, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + float* m_float = static_cast(TF_TensorData(m_data)); + TF_DeviceList* devices = TFE_ContextListDevices(ctx, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + int num_devices = TF_DeviceListCount(devices); + for (int d = 0; d < num_devices; ++d) { + const char* name = TF_DeviceListName(devices, d, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_TensorHandle* copy = TFE_TensorHandleCopyToDevice(m, ctx, name, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + void* data = TFE_TensorHandleDevicePointer(copy, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + size_t size = TFE_TensorHandleDeviceMemorySize(copy, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + int64_t dims[] = {2, 2}; + TFE_TensorHandle* copy_aliased = TFE_NewTensorHandleFromDeviceMemory( + ctx, name, TF_FLOAT, dims, 2, data, size, &Deleter, copy, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TFE_TensorHandle* on_host = + TFE_TensorHandleCopyToDevice(copy_aliased, ctx, "CPU:0", status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + TF_Tensor* resolved = TFE_TensorHandleResolve(on_host, status); + CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status); + const float* resolved_data = + static_cast(TF_TensorData(resolved)); + EXPECT_EQ(0, memcmp(m_float, resolved_data, 4 * sizeof(float))); + TF_DeleteTensor(resolved); + TFE_DeleteTensorHandle(copy_aliased); // Note that this will delete copy. + TFE_DeleteTensorHandle(on_host); + } + TF_DeleteTensor(m_data); + TFE_DeleteTensorHandle(m); + TFE_DeleteContext(ctx); + TF_DeleteStatus(status); +} + } // namespace } // namespace tensorflow diff --git a/tensorflow/c/eager/c_api_internal.cc b/tensorflow/c/eager/c_api_internal.cc index 772fae13faf..f6092715e17 100644 --- a/tensorflow/c/eager/c_api_internal.cc +++ b/tensorflow/c/eager/c_api_internal.cc @@ -17,7 +17,8 @@ limitations under the License. #include "tensorflow/core/platform/host_info.h" TFE_Op* NewOrResetOp(TFE_Context* ctx, const char* op_or_function_name, - TF_Status* status, TFE_Op* op_to_reset) { + const char* raw_device_name, TF_Status* status, + TFE_Op* op_to_reset) { const char* name = op_or_function_name; // Shorthand const tensorflow::AttrTypeMap* types; bool is_function = false; @@ -25,14 +26,17 @@ TFE_Op* NewOrResetOp(TFE_Context* ctx, const char* op_or_function_name, if (!status->status.ok()) { return nullptr; } - auto create_or_reset = [&op_to_reset, &ctx, &name, &types]( - bool is_function, - TFE_OpInferenceContext* inference_ctx) -> TFE_Op* { + auto create_or_reset = + [&op_to_reset, &ctx, &name, &types, &raw_device_name, &status]( + bool is_function, TFE_OpInferenceContext* inference_ctx) -> TFE_Op* { if (op_to_reset) { - op_to_reset->Reset(ctx, name, is_function, types, inference_ctx); + status->status = op_to_reset->Reset(ctx, name, is_function, types, + raw_device_name, inference_ctx); return op_to_reset; } else { - return new TFE_Op(ctx, name, is_function, types, inference_ctx); + TFE_Op* new_op = new TFE_Op(ctx, name, is_function, types, inference_ctx); + status->status = new_op->operation.SetDeviceName(raw_device_name); + return new_op; } }; diff --git a/tensorflow/c/eager/c_api_internal.h b/tensorflow/c/eager/c_api_internal.h index 024ed66e560..29106e2998d 100644 --- a/tensorflow/c/eager/c_api_internal.h +++ b/tensorflow/c/eager/c_api_internal.h @@ -58,7 +58,7 @@ struct TFE_ContextOptions { TFE_DEVICE_PLACEMENT_SILENT}; TFE_ContextMirroringPolicy mirroring_policy{TFE_MIRRORING_NONE}; // If true, lazily copy the remote inputs of a function to the target devices. - bool lazy_remote_inputs_copy = false; + bool lazy_remote_inputs_copy = true; }; struct TFE_Context { @@ -134,11 +134,13 @@ struct TFE_Op { inference_ctx.reset(); } - void Reset(TFE_Context* ctx, const char* op, bool is_function, - const tensorflow::AttrTypeMap* t, - TFE_OpInferenceContext* infer_ctx) { - operation.Reset(ctx->context, op, is_function, t, nullptr); + tensorflow::Status Reset(TFE_Context* ctx, const char* op, bool is_function, + const tensorflow::AttrTypeMap* t, + const char* raw_device_name, + TFE_OpInferenceContext* infer_ctx) { inference_ctx.reset(infer_ctx); + return operation.Reset(ctx->context, op, is_function, t, raw_device_name, + nullptr); } tensorflow::EagerOperation operation; @@ -146,7 +148,8 @@ struct TFE_Op { }; TFE_Op* NewOrResetOp(TFE_Context* ctx, const char* op_or_function_name, - TF_Status* status, TFE_Op* op_to_reset = nullptr); + const char* raw_device_name, TF_Status* status, + TFE_Op* op_to_reset = nullptr); struct TFE_Profiler { explicit TFE_Profiler() { profiler = tensorflow::ProfilerSession::Create(); } diff --git a/tensorflow/c/experimental/filesystem/modular_filesystem.cc b/tensorflow/c/experimental/filesystem/modular_filesystem.cc index be15919fc13..ede2d15c09e 100644 --- a/tensorflow/c/experimental/filesystem/modular_filesystem.cc +++ b/tensorflow/c/experimental/filesystem/modular_filesystem.cc @@ -215,9 +215,24 @@ Status ModularFileSystem::DeleteFile(const std::string& fname) { Status ModularFileSystem::DeleteRecursively(const std::string& dirname, int64* undeleted_files, int64* undeleted_dirs) { - // TODO(mihaimaruseac): Implementation to come in a new change - return Status(error::UNIMPLEMENTED, - "Modular filesystem stub not implemented yet"); + if (undeleted_files == nullptr || undeleted_dirs == nullptr) + return errors::FailedPrecondition( + "DeleteRecursively must not be called with `undeleted_files` or " + "`undeleted_dirs` set to NULL"); + + if (ops_->delete_recursively == nullptr) + return FileSystem::DeleteRecursively(dirname, undeleted_files, + undeleted_dirs); + + UniquePtrTo_TF_Status plugin_status(TF_NewStatus(), TF_DeleteStatus); + std::string translated_name = TranslateName(dirname); + uint64_t plugin_undeleted_files, plugin_undeleted_dirs; + ops_->delete_recursively(filesystem_.get(), translated_name.c_str(), + &plugin_undeleted_files, &plugin_undeleted_dirs, + plugin_status.get()); + *undeleted_files = plugin_undeleted_files; + *undeleted_dirs = plugin_undeleted_dirs; + return StatusFromTF_Status(plugin_status.get()); } Status ModularFileSystem::DeleteDir(const std::string& dirname) { @@ -233,9 +248,14 @@ Status ModularFileSystem::DeleteDir(const std::string& dirname) { } Status ModularFileSystem::RecursivelyCreateDir(const std::string& dirname) { - // TODO(mihaimaruseac): Implementation to come in a new change - return Status(error::UNIMPLEMENTED, - "Modular filesystem stub not implemented yet"); + if (ops_->recursively_create_dir == nullptr) + return FileSystem::RecursivelyCreateDir(dirname); + + UniquePtrTo_TF_Status plugin_status(TF_NewStatus(), TF_DeleteStatus); + std::string translated_name = TranslateName(dirname); + ops_->recursively_create_dir(filesystem_.get(), translated_name.c_str(), + plugin_status.get()); + return StatusFromTF_Status(plugin_status.get()); } Status ModularFileSystem::CreateDir(const std::string& dirname) { @@ -324,8 +344,8 @@ Status ModularFileSystem::CopyFile(const std::string& src, if (ops_->copy_file == nullptr) return FileSystem::CopyFile(src, target); UniquePtrTo_TF_Status plugin_status(TF_NewStatus(), TF_DeleteStatus); - const std::string& translated_src = TranslateName(src); - const std::string& translated_target = TranslateName(target); + std::string translated_src = TranslateName(src); + std::string translated_target = TranslateName(target); ops_->copy_file(filesystem_.get(), translated_src.c_str(), translated_target.c_str(), plugin_status.get()); return StatusFromTF_Status(plugin_status.get()); diff --git a/tensorflow/c/experimental/filesystem/modular_filesystem_test.cc b/tensorflow/c/experimental/filesystem/modular_filesystem_test.cc index 4a464d937cf..cf665d8f981 100644 --- a/tensorflow/c/experimental/filesystem/modular_filesystem_test.cc +++ b/tensorflow/c/experimental/filesystem/modular_filesystem_test.cc @@ -151,7 +151,8 @@ TEST_P(ModularFileSystemTest, TestTranslateName) { const std::string generic_path = GetURIForPath("some_path"); FileSystem* fs = nullptr; Status s = env_->GetFileSystemForFile(generic_path, &fs); - if (fs == nullptr || !s.ok()) GTEST_SKIP() << "No filesystem registered"; + if (fs == nullptr || !s.ok()) + GTEST_SKIP() << "No filesystem registered: " << s; // First, test some interesting corner cases concerning empty URIs if (GetParam().empty()) { @@ -201,7 +202,7 @@ TEST_P(ModularFileSystemTest, TestCreateFileNonExisting) { TEST_P(ModularFileSystemTest, TestCreateFileExistingDir) { const std::string filepath = GetURIForPath("a_file"); Status status = env_->CreateDir(filepath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; std::unique_ptr new_file; status = env_->NewWritableFile(filepath, &new_file); @@ -212,7 +213,7 @@ TEST_P(ModularFileSystemTest, TestCreateFilePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; const std::string new_path = GetURIForPath("a_file/a_file"); std::unique_ptr new_file; @@ -237,7 +238,7 @@ TEST_P(ModularFileSystemTest, TestAppendFileNonExisting) { TEST_P(ModularFileSystemTest, TestAppendFileExistingDir) { const std::string filepath = GetURIForPath("a_file"); Status status = env_->CreateDir(filepath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; std::unique_ptr new_file; status = env_->NewAppendableFile(filepath, &new_file); @@ -248,7 +249,8 @@ TEST_P(ModularFileSystemTest, TestCreateThenAppendFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; std::unique_ptr same_file; status = env_->NewAppendableFile(filepath, &same_file); @@ -259,7 +261,8 @@ TEST_P(ModularFileSystemTest, TestAppendFilePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_path = GetURIForPath("a_file/a_file"); std::unique_ptr same_file; @@ -284,7 +287,7 @@ TEST_P(ModularFileSystemTest, TestReadFileNonExisting) { TEST_P(ModularFileSystemTest, TestReadFileExistingDir) { const std::string filepath = GetURIForPath("a_file"); Status status = env_->CreateDir(filepath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; std::unique_ptr new_file; status = env_->NewRandomAccessFile(filepath, &new_file); @@ -295,7 +298,8 @@ TEST_P(ModularFileSystemTest, TestCreateThenReadFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; std::unique_ptr same_file; status = env_->NewRandomAccessFile(filepath, &same_file); @@ -306,7 +310,8 @@ TEST_P(ModularFileSystemTest, TestReadFilePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_path = GetURIForPath("a_file/a_file"); std::unique_ptr same_file; @@ -331,7 +336,7 @@ TEST_P(ModularFileSystemTest, TestCreateMemoryRegionNonExisting) { TEST_P(ModularFileSystemTest, TestCreateMemoryRegionExistingDir) { const std::string filepath = GetURIForPath("a_file"); Status status = env_->CreateDir(filepath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; std::unique_ptr new_file; status = env_->NewReadOnlyMemoryRegionFromFile(filepath, &new_file); @@ -342,7 +347,8 @@ TEST_P(ModularFileSystemTest, TestCreateMemoryRegionFromEmptyFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; std::unique_ptr region; status = env_->NewReadOnlyMemoryRegionFromFile(filepath, ®ion); @@ -353,21 +359,23 @@ TEST_P(ModularFileSystemTest, TestCreateMemoryRegionFromFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = new_file->Append(test_data); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = new_file->Flush(); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = new_file->Close(); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; std::unique_ptr region; status = env_->NewReadOnlyMemoryRegionFromFile(filepath, ®ion); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); if (!status.ok()) - GTEST_SKIP() << "NewReadOnlyMemoryRegionFromFile() not supported"; + GTEST_SKIP() << "NewReadOnlyMemoryRegionFromFile() not supported: " + << status; EXPECT_EQ(region->length(), test_data.size()); EXPECT_STREQ(reinterpret_cast(region->data()), test_data.c_str()); @@ -377,7 +385,8 @@ TEST_P(ModularFileSystemTest, TestCreateMemoryRegionFromFilePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; std::string new_path = GetURIForPath("a_file/a_file"); std::unique_ptr region; @@ -401,7 +410,8 @@ TEST_P(ModularFileSystemTest, TestCreateDirWhichIsFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->CreateDir(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::ALREADY_EXISTS); @@ -410,7 +420,7 @@ TEST_P(ModularFileSystemTest, TestCreateDirWhichIsFile) { TEST_P(ModularFileSystemTest, TestCreateDirTwice) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; status = env_->CreateDir(dirpath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::ALREADY_EXISTS); @@ -420,18 +430,98 @@ TEST_P(ModularFileSystemTest, TestCreateDirPathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_path = GetURIForPath("a_file/a_dir"); status = env_->CreateDir(new_path); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); } +TEST_P(ModularFileSystemTest, TestRecursivelyCreateDir) { + const std::string dirpath = GetURIForPath("a/path/to/a/dir"); + Status status = env_->RecursivelyCreateDir(dirpath); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); +} + +TEST_P(ModularFileSystemTest, TestRecursivelyCreateDirInATree) { + const std::string dirpath = GetURIForPath("a/path/to/a/dir"); + Status status = env_->RecursivelyCreateDir(dirpath); + if (!status.ok()) + GTEST_SKIP() << "RecursivelyCreateDir() not supported: " << status; + + const std::string new_dirpath = GetURIForPath("a/path/to/a/another/dir"); + status = env_->RecursivelyCreateDir(new_dirpath); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); +} + +TEST_P(ModularFileSystemTest, TestRecursivelyCreateDirWhichIsFile) { + const std::string filepath = GetURIForPath("a_file"); + std::unique_ptr new_file; + Status status = env_->NewWritableFile(filepath, &new_file); + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; + + status = env_->RecursivelyCreateDir(filepath); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); +} + +TEST_P(ModularFileSystemTest, TestRecursivelyCreateDirTwice) { + const std::string dirpath = GetURIForPath("a/path/to/a/dir"); + Status status = env_->RecursivelyCreateDir(dirpath); + if (!status.ok()) + GTEST_SKIP() << "RecursivelyCreateDir() not supported: " << status; + + status = env_->RecursivelyCreateDir(dirpath); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); +} + +TEST_P(ModularFileSystemTest, TestRecursivelyCreateDirPathIsInvalid) { + const std::string filepath = GetURIForPath("a_file"); + std::unique_ptr file; + Status status = env_->NewWritableFile(filepath, &file); + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; + + const std::string new_path = GetURIForPath("a_file/a_dir"); + status = env_->RecursivelyCreateDir(new_path); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); +} + +TEST_P(ModularFileSystemTest, TestRecursivelyCreateDirFromNestedDir) { + const std::string parent_path = GetURIForPath("some/path"); + Status status = env_->RecursivelyCreateDir(parent_path); + if (!status.ok()) + GTEST_SKIP() << "RecursivelyCreateDir() not supported: " << status; + + const std::string new_dirpath = GetURIForPath("some/path/that/is/extended"); + status = env_->RecursivelyCreateDir(new_dirpath); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); +} + +TEST_P(ModularFileSystemTest, TestRecursivelyCreateDirFromNestedFile) { + const std::string parent_path = GetURIForPath("some/path"); + Status status = env_->RecursivelyCreateDir(parent_path); + if (!status.ok()) + GTEST_SKIP() << "RecursivelyCreateDir() not supported: " << status; + + const std::string filepath = GetURIForPath("some/path/to_a_file"); + std::unique_ptr file; + status = env_->NewWritableFile(filepath, &file); + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; + + const std::string new_dirpath = GetURIForPath("some/path/to_a_file/error"); + status = env_->RecursivelyCreateDir(new_dirpath); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); +} + TEST_P(ModularFileSystemTest, TestDeleteFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->DeleteFile(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -440,12 +530,13 @@ TEST_P(ModularFileSystemTest, TestDeleteFile) { TEST_P(ModularFileSystemTest, TestDeleteFileFromDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; const std::string filepath = GetURIForPath("a_dir/a_file"); std::unique_ptr new_file; status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->DeleteFile(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -460,7 +551,7 @@ TEST_P(ModularFileSystemTest, TestDeleteFileDoesNotExist) { TEST_P(ModularFileSystemTest, TestDeleteFileWhichIsDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; status = env_->DeleteFile(dirpath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); @@ -470,7 +561,8 @@ TEST_P(ModularFileSystemTest, TestDeleteFilePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_path = GetURIForPath("a_file/a_new_file"); status = env_->DeleteFile(new_path); @@ -480,7 +572,7 @@ TEST_P(ModularFileSystemTest, TestDeleteFilePathIsInvalid) { TEST_P(ModularFileSystemTest, TestDeleteDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; status = env_->DeleteDir(dirpath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -489,7 +581,7 @@ TEST_P(ModularFileSystemTest, TestDeleteDirectory) { TEST_P(ModularFileSystemTest, TestDeleteDirectoryFromDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; const std::string target_path = GetURIForPath("a_dir/another_dir"); EXPECT_EQ(env_->CreateDir(target_path).code(), Code::OK); @@ -507,12 +599,13 @@ TEST_P(ModularFileSystemTest, TestDeleteDirectoryDoesNotExist) { TEST_P(ModularFileSystemTest, TestDeleteDirectoryNotEmpty) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; const std::string filepath = GetURIForPath("a_dir/a_file"); std::unique_ptr new_file; status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->DeleteDir(dirpath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); @@ -522,7 +615,8 @@ TEST_P(ModularFileSystemTest, TestDeleteDirectoryWhichIsFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->DeleteDir(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); @@ -532,23 +626,152 @@ TEST_P(ModularFileSystemTest, TestDeleteDirectoryPathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_path = GetURIForPath("a_file/a_dir"); status = env_->DeleteDir(new_path); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); } +TEST_P(ModularFileSystemTest, TestDeleteRecursivelyEmpty) { + const std::string dirpath = GetURIForPath("a_dir"); + Status status = env_->CreateDir(dirpath); + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; + + int64 undeleted_files = 0; + int64 undeleted_dirs = 0; + status = env_->DeleteRecursively(dirpath, &undeleted_files, &undeleted_dirs); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); + EXPECT_EQ(undeleted_files, 0); + EXPECT_EQ(undeleted_dirs, 0); +} + +TEST_P(ModularFileSystemTest, TestDeleteRecursivelyNotEmpty) { + const std::string dirpath = GetURIForPath("a_dir"); + Status status = env_->CreateDir(dirpath); + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; + + const std::string some_path = GetURIForPath("a_dir/another_dir"); + status = env_->CreateDir(some_path); + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; + + const std::string another_path = GetURIForPath("a_dir/yet_another_dir"); + status = env_->CreateDir(another_path); + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; + + const std::string filepath = GetURIForPath("a_dir/a_file"); + std::unique_ptr new_file; + status = env_->NewWritableFile(filepath, &new_file); + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; + + int64 undeleted_files = 0; + int64 undeleted_dirs = 0; + status = env_->DeleteRecursively(dirpath, &undeleted_files, &undeleted_dirs); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); + EXPECT_EQ(undeleted_files, 0); + EXPECT_EQ(undeleted_dirs, 0); +} + +TEST_P(ModularFileSystemTest, TestDeleteRecursivelyDoesNotExist) { + const std::string dirpath = GetURIForPath("a_dir"); + + int64 undeleted_files = 0; + int64 undeleted_dirs = 0; + Status status = + env_->DeleteRecursively(dirpath, &undeleted_files, &undeleted_dirs); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::NOT_FOUND); + EXPECT_EQ(undeleted_files, 0); + EXPECT_EQ(undeleted_dirs, 1); +} + +TEST_P(ModularFileSystemTest, TestDeleteRecursivelyAFile) { + const std::string filepath = GetURIForPath("a_file"); + std::unique_ptr new_file; + Status status = env_->NewWritableFile(filepath, &new_file); + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; + + int64 undeleted_files = 0; + int64 undeleted_dirs = 0; + status = env_->DeleteRecursively(filepath, &undeleted_files, &undeleted_dirs); + EXPECT_EQ(undeleted_files, 0); + EXPECT_EQ(undeleted_dirs, 0); +} + +TEST_P(ModularFileSystemTest, TestDeleteRecursivelyPathIsInvalid) { + const std::string filepath = GetURIForPath("a_file"); + std::unique_ptr file; + Status status = env_->NewWritableFile(filepath, &file); + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; + + const std::string new_path = GetURIForPath("a_file/a_dir"); + int64 undeleted_files, undeleted_dirs; + status = env_->DeleteRecursively(new_path, &undeleted_files, &undeleted_dirs); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); +} + +TEST_P(ModularFileSystemTest, TestDeleteRecursivelyANestedDir) { + const std::string parent_path = GetURIForPath("parent/path"); + Status status = env_->RecursivelyCreateDir(parent_path); + if (!status.ok()) + GTEST_SKIP() << "RecursivelyCreateDir() not supported: " << status; + + const std::string new_dirpath = GetURIForPath("parent/path/that/is/extended"); + status = env_->RecursivelyCreateDir(new_dirpath); + if (!status.ok()) + GTEST_SKIP() << "RecursivelyCreateDir() not supported: " << status; + + const std::string path = GetURIForPath("parent/path/that"); + int64 undeleted_files = 0; + int64 undeleted_dirs = 0; + status = env_->DeleteRecursively(path, &undeleted_files, &undeleted_dirs); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); + EXPECT_EQ(undeleted_files, 0); + EXPECT_EQ(undeleted_dirs, 0); + + // Parent directory must still exist + status = env_->FileExists(parent_path); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); +} + +TEST_P(ModularFileSystemTest, TestDeleteRecursivelyANestedFile) { + const std::string parent_path = GetURIForPath("some/path"); + Status status = env_->RecursivelyCreateDir(parent_path); + if (!status.ok()) + GTEST_SKIP() << "RecursivelyCreateDir() not supported: " << status; + + const std::string filepath = GetURIForPath("some/path/to_a_file"); + std::unique_ptr file; + status = env_->NewWritableFile(filepath, &file); + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; + + int64 undeleted_files = 0; + int64 undeleted_dirs = 0; + status = env_->DeleteRecursively(filepath, &undeleted_files, &undeleted_dirs); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); + EXPECT_EQ(undeleted_files, 0); + EXPECT_EQ(undeleted_dirs, 0); + + // Parent directory must still exist + status = env_->FileExists(parent_path); + EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); +} + TEST_P(ModularFileSystemTest, TestRenameFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); status = env_->RenameFile(filepath, new_filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported"; + if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported: " << status; status = env_->FileExists(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::NOT_FOUND); @@ -560,16 +783,18 @@ TEST_P(ModularFileSystemTest, TestRenameFileOverwrite) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); std::unique_ptr new_file; status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->RenameFile(filepath, new_filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported"; + if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported: " << status; status = env_->FileExists(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::NOT_FOUND); @@ -588,7 +813,8 @@ TEST_P(ModularFileSystemTest, TestRenameFileDestinationParentNotFound) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_filepath = GetURIForPath("a_dir/a_file"); status = env_->RenameFile(filepath, new_filepath); @@ -598,7 +824,7 @@ TEST_P(ModularFileSystemTest, TestRenameFileDestinationParentNotFound) { TEST_P(ModularFileSystemTest, TestRenameFileSourceIsDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); status = env_->RenameFile(dirpath, new_filepath); @@ -609,11 +835,12 @@ TEST_P(ModularFileSystemTest, TestRenameFileTargetIsDirectory) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string dirpath = GetURIForPath("a_dir"); status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; status = env_->RenameFile(filepath, dirpath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); @@ -623,7 +850,8 @@ TEST_P(ModularFileSystemTest, TestRenameFileSourcePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string old_filepath = GetURIForPath("a_file/x"); const std::string new_filepath = GetURIForPath("a_new_file"); @@ -635,12 +863,14 @@ TEST_P(ModularFileSystemTest, TestRenameFileTargetPathIsInvalid) { const std::string old_filepath = GetURIForPath("a_file"); std::unique_ptr old_file; Status status = env_->NewWritableFile(old_filepath, &old_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_filepath = GetURIForPath("a_file/a_new_file"); status = env_->RenameFile(old_filepath, new_filepath); @@ -651,25 +881,26 @@ TEST_P(ModularFileSystemTest, TestRenameFileCompareContents) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = file->Append(test_data); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Flush(); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = file->Close(); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); status = env_->RenameFile(filepath, new_filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported"; + if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported: " << status; uint64 size; status = env_->GetFileSize(new_filepath, &size); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported"; + if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported: " << status; EXPECT_EQ(size, test_data.size()); } @@ -677,12 +908,13 @@ TEST_P(ModularFileSystemTest, TestCopyFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); status = env_->CopyFile(filepath, new_filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "CopyFile() not supported"; + if (!status.ok()) GTEST_SKIP() << "CopyFile() not supported: " << status; status = env_->FileExists(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -694,16 +926,18 @@ TEST_P(ModularFileSystemTest, TestCopyFileOverwrite) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); std::unique_ptr new_file; status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->CopyFile(filepath, new_filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "CopyFile() not supported"; + if (!status.ok()) GTEST_SKIP() << "CopyFile() not supported: " << status; status = env_->FileExists(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -721,7 +955,7 @@ TEST_P(ModularFileSystemTest, TestCopyFileSourceNotFound) { TEST_P(ModularFileSystemTest, TestCopyFileSourceIsDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); status = env_->CopyFile(dirpath, new_filepath); @@ -732,11 +966,12 @@ TEST_P(ModularFileSystemTest, TestCopyFileTargetIsDirectory) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr new_file; Status status = env_->NewWritableFile(filepath, &new_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string dirpath = GetURIForPath("a_dir"); status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; status = env_->CopyFile(filepath, dirpath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); @@ -746,7 +981,8 @@ TEST_P(ModularFileSystemTest, TestCopyFileSourcePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string old_filepath = GetURIForPath("a_file/x"); const std::string new_filepath = GetURIForPath("a_new_file"); @@ -758,12 +994,14 @@ TEST_P(ModularFileSystemTest, TestCopyFileTargetPathIsInvalid) { const std::string old_filepath = GetURIForPath("a_file"); std::unique_ptr old_file; Status status = env_->NewWritableFile(old_filepath, &old_file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string new_filepath = GetURIForPath("a_file/a_new_file"); status = env_->CopyFile(old_filepath, new_filepath); @@ -774,30 +1012,31 @@ TEST_P(ModularFileSystemTest, TestCopyFileCompareContents) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = file->Append(test_data); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Flush(); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = file->Close(); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; const std::string new_filepath = GetURIForPath("a_new_file"); status = env_->CopyFile(filepath, new_filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported"; + if (!status.ok()) GTEST_SKIP() << "RenameFile() not supported: " << status; uint64 size; status = env_->GetFileSize(filepath, &size); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported"; + if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported: " << status; EXPECT_EQ(size, test_data.size()); status = env_->GetFileSize(new_filepath, &size); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported"; + if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported: " << status; EXPECT_EQ(size, test_data.size()); } @@ -805,7 +1044,8 @@ TEST_P(ModularFileSystemTest, TestFileExists) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->FileExists(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -814,7 +1054,7 @@ TEST_P(ModularFileSystemTest, TestFileExists) { TEST_P(ModularFileSystemTest, TestFileExistsButIsDirectory) { const std::string filepath = GetURIForPath("a_file"); Status status = env_->CreateDir(filepath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; status = env_->FileExists(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -830,7 +1070,8 @@ TEST_P(ModularFileSystemTest, TestFileExistsPathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string target_path = GetURIForPath("a_file/a_new_file"); status = env_->FileExists(target_path); @@ -843,7 +1084,8 @@ TEST_P(ModularFileSystemTest, TestFilesExist) { for (const auto& filename : filenames) { std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; } EXPECT_TRUE(env_->FilesExist(filenames, /*status=*/nullptr)); @@ -865,11 +1107,12 @@ TEST_P(ModularFileSystemTest, TestFilesExistAllFailureModes) { }; Status status = env_->CreateDir(filenames[0]); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; std::unique_ptr file; status = env_->NewWritableFile(filenames[1], &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; std::vector statuses; EXPECT_FALSE(env_->FilesExist(filenames, &statuses)); @@ -894,12 +1137,13 @@ TEST_P(ModularFileSystemTest, TestStatEmptyFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; FileStatistics stat; status = env_->Stat(filepath, &stat); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Stat() not supported"; + if (!status.ok()) GTEST_SKIP() << "Stat() not supported: " << status; EXPECT_FALSE(stat.is_directory); EXPECT_EQ(stat.length, 0); } @@ -908,20 +1152,21 @@ TEST_P(ModularFileSystemTest, TestStatNonEmptyFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = file->Append(test_data); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Flush(); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = file->Close(); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; FileStatistics stat; status = env_->Stat(filepath, &stat); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Stat() not supported"; + if (!status.ok()) GTEST_SKIP() << "Stat() not supported: " << status; EXPECT_FALSE(stat.is_directory); EXPECT_EQ(stat.length, test_data.size()); } @@ -929,12 +1174,12 @@ TEST_P(ModularFileSystemTest, TestStatNonEmptyFile) { TEST_P(ModularFileSystemTest, TestStatDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; FileStatistics stat; status = env_->Stat(dirpath, &stat); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Stat() not supported"; + if (!status.ok()) GTEST_SKIP() << "Stat() not supported: " << status; EXPECT_TRUE(stat.is_directory); } @@ -949,7 +1194,8 @@ TEST_P(ModularFileSystemTest, TestStatPathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string target_path = GetURIForPath("a_file/a_new_file"); FileStatistics stat; @@ -960,7 +1206,7 @@ TEST_P(ModularFileSystemTest, TestStatPathIsInvalid) { TEST_P(ModularFileSystemTest, TestIsDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; status = env_->IsDirectory(dirpath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); @@ -970,7 +1216,8 @@ TEST_P(ModularFileSystemTest, TestIsDirectoryFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = env_->IsDirectory(filepath); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::FAILED_PRECONDITION); @@ -986,7 +1233,8 @@ TEST_P(ModularFileSystemTest, TestIsDirectoryPathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string target_path = GetURIForPath("a_file/a_new_file"); status = env_->IsDirectory(target_path); @@ -997,12 +1245,13 @@ TEST_P(ModularFileSystemTest, TestGetFileSizeEmptyFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; uint64 size; status = env_->GetFileSize(filepath, &size); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported"; + if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported: " << status; EXPECT_EQ(size, 0); } @@ -1010,27 +1259,28 @@ TEST_P(ModularFileSystemTest, TestGetFileSizeNonEmptyFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = file->Append(test_data); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Flush(); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = file->Close(); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; uint64 size; status = env_->GetFileSize(filepath, &size); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported"; + if (!status.ok()) GTEST_SKIP() << "GetFileSize() not supported: " << status; EXPECT_EQ(size, test_data.size()); } TEST_P(ModularFileSystemTest, TestGetFileSizeDirectory) { const std::string dirpath = GetURIForPath("a_dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; uint64 size; status = env_->GetFileSize(dirpath, &size); @@ -1048,7 +1298,8 @@ TEST_P(ModularFileSystemTest, TestGetFileSizePathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string target_path = GetURIForPath("a_file/a_new_file"); uint64 size; @@ -1059,7 +1310,7 @@ TEST_P(ModularFileSystemTest, TestGetFileSizePathIsInvalid) { TEST_P(ModularFileSystemTest, TestGetChildren) { const std::string dirpath = GetURIForPath("dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; // If updating, make sure to update expected_children below. const std::vector filenames = { @@ -1069,7 +1320,8 @@ TEST_P(ModularFileSystemTest, TestGetChildren) { for (const auto& filename : filenames) { std::unique_ptr file; status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; } // If updating, make sure to update expected_children below. @@ -1079,13 +1331,13 @@ TEST_P(ModularFileSystemTest, TestGetChildren) { }; for (const auto& dirname : dirnames) { status = env_->CreateDir(dirname); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; } std::vector children; status = env_->GetChildren(dirpath, &children); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetChildren() not supported"; + if (!status.ok()) GTEST_SKIP() << "GetChildren() not supported: " << status; // All entries must show up in the vector. // Must contain only the last name in filenames and dirnames. @@ -1100,7 +1352,7 @@ TEST_P(ModularFileSystemTest, TestGetChildren) { TEST_P(ModularFileSystemTest, TestGetChildrenEmpty) { const std::string dirpath = GetURIForPath("dir"); Status status = env_->CreateDir(dirpath); - if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported"; + if (!status.ok()) GTEST_SKIP() << "CreateDir() not supported: " << status; std::vector children; status = env_->GetChildren(dirpath, &children); @@ -1112,7 +1364,8 @@ TEST_P(ModularFileSystemTest, TestGetChildrenOfFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; std::vector children; status = env_->GetChildren(filepath, &children); @@ -1130,7 +1383,8 @@ TEST_P(ModularFileSystemTest, TestGetChildrenPathIsInvalid) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string target_path = GetURIForPath("a_file/a_new_dir"); std::vector children; @@ -1151,19 +1405,22 @@ TEST_P(ModularFileSystemTest, TestGetMatchingPaths) { for (const auto& filename : matching_filenames) { std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; } for (const auto& filename : other_filenames) { std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; } std::vector results; Status status = env_->GetMatchingPaths(GetURIForPath("/a*"), &results); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetMatchingPaths() not supported"; + if (!status.ok()) + GTEST_SKIP() << "GetMatchingPaths() not supported: " << status; EXPECT_EQ(results.size(), matching_filenames.size()); for (const auto& match : matching_filenames) EXPECT_NE(std::find(results.begin(), results.end(), match), results.end()); @@ -1187,13 +1444,15 @@ TEST_P(ModularFileSystemTest, TestGetMatchingPathsEmptyPattern) { for (const auto& filename : filenames) { std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; } std::vector results; Status status = env_->GetMatchingPaths(GetURIForPath(""), &results); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetMatchingPaths() not supported"; + if (!status.ok()) + GTEST_SKIP() << "GetMatchingPaths() not supported: " << status; EXPECT_EQ(results.size(), 1); EXPECT_NE(std::find(results.begin(), results.end(), GetURIForPath("")), results.end()); @@ -1210,13 +1469,15 @@ TEST_P(ModularFileSystemTest, TestGetMatchingPathsLiteralMatch) { for (const auto& filename : filenames) { std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; } std::vector results; Status status = env_->GetMatchingPaths(filenames[0], &results); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "GetMatchingPaths() not supported"; + if (!status.ok()) + GTEST_SKIP() << "GetMatchingPaths() not supported: " << status; EXPECT_EQ(results.size(), 1); EXPECT_NE(std::find(results.begin(), results.end(), filenames[0]), results.end()); @@ -1233,12 +1494,14 @@ TEST_P(ModularFileSystemTest, TestGetMatchingPathsNoMatch) { for (const auto& filename : filenames) { std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; } std::vector results; Status status = env_->GetMatchingPaths(GetURIForPath("x?y*"), &results); - if (!status.ok()) GTEST_SKIP() << "GetMatchingPaths() not supported"; + if (!status.ok()) + GTEST_SKIP() << "GetMatchingPaths() not supported: " << status; EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); EXPECT_EQ(results.size(), 0); } @@ -1247,18 +1510,19 @@ TEST_P(ModularFileSystemTest, TestAppendAndTell) { const std::string filename = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; int64 position; status = file->Tell(&position); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Tell() not supported"; + if (!status.ok()) GTEST_SKIP() << "Tell() not supported: " << status; EXPECT_EQ(position, 0); const std::string test_data("asdf"); status = file->Append(test_data); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Tell(&position); EXPECT_EQ(status.code(), Code::OK); @@ -1269,35 +1533,38 @@ TEST_P(ModularFileSystemTest, TestClose) { const std::string filename = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filename, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; status = file->Close(); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; } TEST_P(ModularFileSystemTest, TestRoundTrip) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = file->Append(test_data); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Flush(); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = file->Close(); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; std::unique_ptr read_file; status = env_->NewRandomAccessFile(filepath, &read_file); - if (!status.ok()) GTEST_SKIP() << "NewRandomAccessFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewRandomAccessFile() not supported: " << status; char scratch[64 /* big enough to accomodate test_data */] = {0}; StringPiece result; @@ -1310,24 +1577,26 @@ TEST_P(ModularFileSystemTest, TestRoundTripWithAppendableFile) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = file->Append(test_data); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Flush(); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = file->Close(); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; std::unique_ptr same_file; status = env_->NewAppendableFile(filepath, &same_file); - if (!status.ok()) GTEST_SKIP() << "NewAppendableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewAppendableFile() not supported: " << status; const std::string more_test_data("qwer"); EXPECT_EQ(same_file->Append(more_test_data).code(), Code::OK); @@ -1336,7 +1605,8 @@ TEST_P(ModularFileSystemTest, TestRoundTripWithAppendableFile) { std::unique_ptr read_file; status = env_->NewRandomAccessFile(filepath, &read_file); - if (!status.ok()) GTEST_SKIP() << "NewRandomAccessFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewRandomAccessFile() not supported: " << status; char scratch[64 /* big enough for test_data and more_test_data */] = {0}; StringPiece result; @@ -1355,24 +1625,26 @@ TEST_P(ModularFileSystemTest, TestReadOutOfRange) { const std::string filepath = GetURIForPath("a_file"); std::unique_ptr file; Status status = env_->NewWritableFile(filepath, &file); - if (!status.ok()) GTEST_SKIP() << "NewWritableFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewWritableFile() not supported: " << status; const std::string test_data("asdf"); status = file->Append(test_data); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Append() not supported"; + if (!status.ok()) GTEST_SKIP() << "Append() not supported: " << status; status = file->Flush(); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Flush() not supported"; + if (!status.ok()) GTEST_SKIP() << "Flush() not supported: " << status; status = file->Close(); EXPECT_PRED2(UninmplementedOrReturnsCode, status, Code::OK); - if (!status.ok()) GTEST_SKIP() << "Close() not supported"; + if (!status.ok()) GTEST_SKIP() << "Close() not supported: " << status; std::unique_ptr read_file; status = env_->NewRandomAccessFile(filepath, &read_file); - if (!status.ok()) GTEST_SKIP() << "NewRandomAccessFile() not supported"; + if (!status.ok()) + GTEST_SKIP() << "NewRandomAccessFile() not supported: " << status; char scratch[64 /* must be bigger than test_data */] = {0}; StringPiece result; diff --git a/tensorflow/c/tf_tensor.cc b/tensorflow/c/tf_tensor.cc index 2ad778d6057..dd13a1de1bf 100644 --- a/tensorflow/c/tf_tensor.cc +++ b/tensorflow/c/tf_tensor.cc @@ -62,39 +62,6 @@ void deallocate_buffer(void* data, size_t len, void* arg) { } } // namespace tensorflow -namespace { -class TF_ManagedBuffer : public TensorBuffer { - public: - TF_ManagedBuffer(void* data, size_t len, - void (*deallocator)(void* data, size_t len, void* arg), - void* deallocator_arg) - : TensorBuffer(data), - len_(len), - deallocator_(deallocator), - deallocator_arg_(deallocator_arg) {} - - const size_t len_; - void (*const deallocator_)(void* data, size_t len, void* arg); - void* const deallocator_arg_; - - ~TF_ManagedBuffer() override { - (*deallocator_)(data(), len_, deallocator_arg_); - } - - size_t size() const override { return len_; } - TensorBuffer* root_buffer() override { return this; } - void FillAllocationDescription( - tensorflow::AllocationDescription* proto) const override { - tensorflow::int64 rb = size(); - proto->set_requested_bytes(rb); - proto->set_allocator_name(tensorflow::cpu_allocator()->Name()); - } - - // Prevents input forwarding from mutating this buffer. - bool OwnsMemory() const override { return false; } -}; - -} // namespace TF_Tensor* TF_AllocateTensor(TF_DataType dtype, const int64_t* dims, int num_dims, size_t len) { diff --git a/tensorflow/c/tf_tensor_internal.h b/tensorflow/c/tf_tensor_internal.h index ea7d49b5966..0572c4826e2 100644 --- a/tensorflow/c/tf_tensor_internal.h +++ b/tensorflow/c/tf_tensor_internal.h @@ -17,6 +17,7 @@ limitations under the License. #define TENSORFLOW_C_TF_TENSOR_INTERNAL_H_ #include "tensorflow/c/tf_datatype.h" +#include "tensorflow/core/framework/allocation_description.pb.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/tensor_shape.h" @@ -30,6 +31,38 @@ typedef struct TF_Tensor { ::tensorflow::Tensor tensor; } TF_Tensor; +class TF_ManagedBuffer : public tensorflow::TensorBuffer { + public: + TF_ManagedBuffer(void* data, size_t len, + void (*deallocator)(void* data, size_t len, void* arg), + void* deallocator_arg) + : TensorBuffer(data), + len_(len), + deallocator_(deallocator), + deallocator_arg_(deallocator_arg) {} + + ~TF_ManagedBuffer() override { + (*deallocator_)(data(), len_, deallocator_arg_); + } + + size_t size() const override { return len_; } + TensorBuffer* root_buffer() override { return this; } + void FillAllocationDescription( + tensorflow::AllocationDescription* proto) const override { + tensorflow::int64 rb = size(); + proto->set_requested_bytes(rb); + proto->set_allocator_name(tensorflow::cpu_allocator()->Name()); + } + + // Prevents input forwarding from mutating this buffer. + bool OwnsMemory() const override { return false; } + + private: + const size_t len_; + void (*const deallocator_)(void* data, size_t len, void* arg); + void* const deallocator_arg_; +}; + namespace tensorflow { class TensorCApi { diff --git a/tensorflow/compiler/aot/tfcompile.bzl b/tensorflow/compiler/aot/tfcompile.bzl index f274b030933..fb81266a048 100644 --- a/tensorflow/compiler/aot/tfcompile.bzl +++ b/tensorflow/compiler/aot/tfcompile.bzl @@ -39,7 +39,7 @@ def tf_library( enable_xla_hlo_profiling = False, mlir_components = None, deps = None, - tags = None): + tags = []): """Runs tfcompile to compile a TensorFlow graph into executable code. Given an invocation of tf_library(name="foo", ...), generates the following diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD index bcdfa019459..4526090d68a 100644 --- a/tensorflow/compiler/jit/BUILD +++ b/tensorflow/compiler/jit/BUILD @@ -27,6 +27,17 @@ package_group( ], ) +# defs.cc/h only contains string constants, and can be included in mobile +# builds. +filegroup( + name = "mobile_srcs_no_runtime", + srcs = [ + "defs.cc", + "defs.h", + ], + visibility = [":friends"], +) + # Target that bundles up the XLA CPU and GPU JIT devices. cc_library( name = "jit", @@ -71,6 +82,19 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "xla_mlir_gpu_jit", + visibility = ["//visibility:public"], + deps = if_cuda_or_rocm([ + ":jit_compilation_passes", + "//tensorflow/compiler/jit/kernels:xla_ops", + "//tensorflow/compiler/tf2xla/kernels:xla_ops", + "//tensorflow/compiler/tf2xla/kernels:xla_dummy_ops", + "//tensorflow/compiler/xla/service:mlir_gpu_plugin", + ]), + alwayslink = 1, +) + cc_library( name = "xla_cpu_device", srcs = ["xla_cpu_device.cc"], diff --git a/tensorflow/compiler/jit/compilability_check_util.cc b/tensorflow/compiler/jit/compilability_check_util.cc index b8f04f7d791..14ade0ea920 100644 --- a/tensorflow/compiler/jit/compilability_check_util.cc +++ b/tensorflow/compiler/jit/compilability_check_util.cc @@ -509,10 +509,10 @@ RecursiveCompilabilityChecker::OperationFilter CreateOperationFilter( auto it = uncompilable_nodes->find(function_identifier); if (it == uncompilable_nodes->end()) { std::vector - uncompileable_node_info{std::move(node_info)}; + uncompilable_node_info{std::move(node_info)}; uncompilable_nodes->emplace( std::move(function_identifier), - std::make_pair(function, std::move(uncompileable_node_info))); + std::make_pair(function, std::move(uncompilable_node_info))); } else { it->second.second.emplace_back(std::move(node_info)); } diff --git a/tensorflow/compiler/jit/deadness_analysis.cc b/tensorflow/compiler/jit/deadness_analysis.cc index 912991e267a..34bd89afda1 100644 --- a/tensorflow/compiler/jit/deadness_analysis.cc +++ b/tensorflow/compiler/jit/deadness_analysis.cc @@ -96,7 +96,7 @@ limitations under the License. // Symbolic > NonSymbolic. The lattice has height = 2 so two iterations are // sufficient to converge. // -// We first do an optimisitc analysis and, if it does not converge, we then fall +// We first do an optimistic analysis and, if it does not converge, we then fall // back to a pessimistic analysis. The optimistic analysis assigns the same // symbolic predicate to all the merge nodes whose preceding enter nodes have // the same frame name on the first iteration. On the second iteration, if all @@ -1255,7 +1255,7 @@ Status DeadnessAnalysisImpl::GetFrameBasedTopologicalOrder( } else if (IsRootExit(node)) { ++num_exits_for_frame[cf.frame_name]; } - // Edge NextIteration->Merge is counted before starting the traveral to + // Edge NextIteration->Merge is counted before starting the traversal to // break the backedges. if (IsMerge(node)) { for (const Edge* e : node->in_edges()) { @@ -1458,11 +1458,11 @@ Status DeadnessAnalysisImpl::PopulateFrame(absl::Span topo, for (Node* n : topo) { // The nodes added to should_revisit in the previous loop need to be - // revisited now. Reprocesing these initial nodes may add *their* consumers - // to should_revisit, and these newly added nodes will also be processed by - // this very same loop. Since we're traversing the graph in topological - // order (producers before consumers) and HandleNode(n) can only ever add - // n's consumers to should_revisit, we won't "miss" an addition to + // revisited now. Reprocessing these initial nodes may add *their* + // consumers to should_revisit, and these newly added nodes will also be + // processed by this very same loop. Since we're traversing the graph in + // topological order (producers before consumers) and HandleNode(n) can only + // ever add n's consumers to should_revisit, we won't "miss" an addition to // should_revisit. if (should_revisit[n->id()]) { VLOG(4) << "Revisiting " << n->name(); diff --git a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.h b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.h index 50e4149bc08..8b627cd959a 100644 --- a/tensorflow/compiler/jit/encapsulate_subgraphs_pass.h +++ b/tensorflow/compiler/jit/encapsulate_subgraphs_pass.h @@ -95,7 +95,7 @@ extern const char* const kXlaNumResourceArgsAttr; extern const char* const kXlaHasReferenceVarsAttr; // Sorts each node's control inputs by their names. This guarantees that for two -// structually equivalent GraphDefs, we get the same traversal ordering on +// structurally equivalent GraphDefs, we get the same traversal ordering on // node's control input fields. // TODO(hpucha): Move the utilities to a more appropriate place. void SortControlInputs(GraphDef* gdef); diff --git a/tensorflow/compiler/jit/encapsulate_util.h b/tensorflow/compiler/jit/encapsulate_util.h index 406e4a797a4..9ddbe4d5cc9 100644 --- a/tensorflow/compiler/jit/encapsulate_util.h +++ b/tensorflow/compiler/jit/encapsulate_util.h @@ -72,7 +72,7 @@ extern const char kXlaLiftedArgOutsideCompilationAttrName[]; // Attribute indicating that this is an IdentityN node receiving inputs for a // outside compilation Placeholder node (the original outside compilation node -// is moved out of TPU comutation, and we left a Placeholder node there). +// is moved out of TPU computation, and we left a Placeholder node there). // Attribute value will be a string, which is the outside compilation cluster // name for the outside compilation Placeholder node. extern const char kXlaOutsideCompilationInputsAttrName[]; diff --git a/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc b/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc index 26f830c59c3..a6f2bd41275 100644 --- a/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc +++ b/tensorflow/compiler/jit/extract_outside_compilation_pass_test.cc @@ -941,7 +941,7 @@ TEST_F(ExtractOutsideCompilationForFunctionTest, // "const0" // "identity0" = "const0" (outside compilation cluster "0") // "identity1" = "const0" "^identity0" (outside compilation cluster "1", - // control depdent on cluster "0") + // control dependent on cluster "0") // "identity2" = "identity1" FunctionDefLibrary fdl; { diff --git a/tensorflow/compiler/jit/flags.cc b/tensorflow/compiler/jit/flags.cc index 53f9b70c876..1cf71298b05 100644 --- a/tensorflow/compiler/jit/flags.cc +++ b/tensorflow/compiler/jit/flags.cc @@ -48,6 +48,15 @@ bool SetterForXlaAutoJitFlag(const string& value) { return true; } + if (value == "fusible") { + mark_for_compilation_flags->xla_auto_jit_flag + .optimization_level_single_gpu = 1; + mark_for_compilation_flags->xla_auto_jit_flag.optimization_level_general = + 1; + mark_for_compilation_flags->tf_xla_ops_to_cluster = "FUSIBLE"; + return true; + } + absl::string_view value_sv(value); if (!absl::ConsumePrefix(&value_sv, "single-gpu(") || !absl::ConsumeSuffix(&value_sv, ")") || @@ -65,7 +74,9 @@ void AppendMarkForCompilationPassFlagsInternal(std::vector* flag_list) { Flag("tf_xla_auto_jit", SetterForXlaAutoJitFlag, "0", "Control compilation of operators into XLA computations on CPU and " "GPU devices. 0 = use ConfigProto setting; -1 = off; 1 = on for " - "things very likely to be improved; 2 = on for everything. " + "things very likely to be improved; 2 = on for everything; " + "(experimental) fusible = only for Tensorflow operations that XLA " + "knows how to fuse. " "If set to single-gpu() then this resolves to for single-GPU " "graphs (graphs that have at least one node placed on a GPU and no " "more than one GPU is in use through the entire graph) and 0 " @@ -78,6 +89,23 @@ void AppendMarkForCompilationPassFlagsInternal(std::vector* flag_list) { Flag("tf_xla_max_cluster_size", &mark_for_compilation_flags->tf_xla_max_cluster_size, "Maximum number of operators in an XLA compilation."), + Flag( + "tf_xla_ops_to_cluster", + &mark_for_compilation_flags->tf_xla_ops_to_cluster, + "(experimental) " + "Limit the operations clustered by XLA to these operations. " + "If multiple, separate them with commas. Shortcuts: " + " PW: All point-wise operations." + " RED: All reduction operations." + " MISC: Mixed operations." + " PWRED: TF operations that get converted to PW+RED operation in XLA." + " REDUCEWINDOW: TF operations like MaxPool/AvgPool that get " + "converted to ReduceWindow in XLA." + " REDUCEWINDOWPW: Operation that get converted to ReduceWindow + PW " + "(LRN, LRNGrad)." + " BN: TF FusedBatchNorm* operations." + " FUSIBLE: All TF operations that XLA can fuse (All the above). " + "You can also put any TF operation name, e.g. 'FUSIBLE,Matmul'."), Flag("tf_xla_clustering_debug", &mark_for_compilation_flags->tf_xla_clustering_debug, "Dump graphs during XLA compilation."), diff --git a/tensorflow/compiler/jit/flags.h b/tensorflow/compiler/jit/flags.h index 9307874133c..87a89841b91 100644 --- a/tensorflow/compiler/jit/flags.h +++ b/tensorflow/compiler/jit/flags.h @@ -55,6 +55,9 @@ struct MarkForCompilationPassFlags { // Maximum number of operators in an XLA compilation. int32 tf_xla_max_cluster_size; + // If non-empty, limit XLA clustering to the following TF operations. + string tf_xla_ops_to_cluster; + // Dump graphs during XLA compilation. bool tf_xla_clustering_debug; diff --git a/tensorflow/compiler/jit/graphcycles/graphcycles.h b/tensorflow/compiler/jit/graphcycles/graphcycles.h index ce171a2ead0..bbf61016fb3 100644 --- a/tensorflow/compiler/jit/graphcycles/graphcycles.h +++ b/tensorflow/compiler/jit/graphcycles/graphcycles.h @@ -123,7 +123,7 @@ class GraphCycles { absl::Span Successors(int32 node) const; absl::Span Predecessors(int32 node) const; - // Return a copy of the sucessors set. This is needed for code using the + // Return a copy of the successors set. This is needed for code using the // collection while modifying the GraphCycles. std::vector SuccessorsCopy(int32 node) const; // Return a copy of the predecessors set. This is needed for code using the diff --git a/tensorflow/compiler/jit/mark_for_compilation_pass.cc b/tensorflow/compiler/jit/mark_for_compilation_pass.cc index 4ca52a26bbd..9f2bc3f7f32 100644 --- a/tensorflow/compiler/jit/mark_for_compilation_pass.cc +++ b/tensorflow/compiler/jit/mark_for_compilation_pass.cc @@ -1076,6 +1076,35 @@ StatusOr IsIdentityDrivingConstsInLoop(Node* node) { return true; } +absl::flat_hash_set GetOrCreateWhitelist() { + absl::flat_hash_map>* whitelist_table = + tensorflow::GetWhitelistTable(); + MarkForCompilationPassFlags* flags = GetMarkForCompilationPassFlags(); + absl::flat_hash_set whitelist; + + for (auto s : absl::StrSplit(flags->tf_xla_ops_to_cluster, ',')) { + if (s == "FUSIBLE") { + for (auto pair : *whitelist_table) { + whitelist.insert(pair.second.begin(), pair.second.end()); + } + } else if (whitelist_table->contains(s)) { + auto v = whitelist_table->at(s); + whitelist.insert(v.begin(), v.end()); + } else if (!s.empty()) { + // Should be a user provided TF operation. + whitelist.insert(string(s)); + } + } + + if (VLOG_IS_ON(2) && !whitelist.empty()) { + std::vector vwhitelist(whitelist.begin(), whitelist.end()); + absl::c_sort(vwhitelist); + VLOG(2) << "XLA clustering will only consider the following TF operations: " + << absl::StrJoin(vwhitelist, " "); + } + return whitelist; +} + Status MarkForCompilationPassImpl::FindCompilationCandidates() { OptimizerOptions opts; std::unique_ptr pflr( @@ -1087,9 +1116,8 @@ Status MarkForCompilationPassImpl::FindCompilationCandidates() { TF_RETURN_IF_ERROR(BackwardsConstAnalysis( *graph_, /*compile_time_const_arg_indices=*/nullptr, &compile_time_const_nodes, lib_runtime)); - // Iterate over nodes in sorted order so that compiler fuel is deterministic. - // We can't simply pass op_nodes().begin() and op_nodes().end to the + // We can't simply pass op_nodes().begin() and op_nodes().end() to the // std::vector constructor because they're not proper iterators, with // iterator_traits defined and so on. std::vector sorted_nodes; @@ -1108,6 +1136,19 @@ Status MarkForCompilationPassImpl::FindCompilationCandidates() { VLOG(2) << "sorted_nodes.size() = " << sorted_nodes.size(); + auto whitelist = GetOrCreateWhitelist(); + + std::vector vall_ops = XlaOpRegistry::GetAllRegisteredOps(); + absl::flat_hash_set all_ops(vall_ops.begin(), vall_ops.end()); + // Check that user's provided TF operation really exists. + for (auto s : whitelist) { + if (!all_ops.contains(string(s))) { + return errors::InvalidArgument( + "The operation '", s, + "' passed to --tf_xla_ops_to_cluster is not supported by XLA."); + } + } + for (Node* node : sorted_nodes) { if (*debug_options_.fuel <= 0) { VLOG(1) @@ -1145,6 +1186,12 @@ Status MarkForCompilationPassImpl::FindCompilationCandidates() { continue; } + if (!whitelist.empty() && !whitelist.contains(node->def().op())) { + VLOG(1) << "Rejecting " << node->name() + << " as it is not listed in --tf_xla_ops_to_cluster."; + continue; + } + if (compile_time_const_nodes[node->id()]) { const OpDef* op_def; TF_RETURN_IF_ERROR( @@ -1366,7 +1413,7 @@ Status MarkForCompilationPassImpl::Run() { void MarkForCompilationPassImpl::DumpPostClusteringGraphs() { DumpGraphToFile("mark_for_compilation", *graph_, flib_def_); - // We also dump out an annoated version of the TF graph where the nodes + // We also dump out an annotated version of the TF graph where the nodes // names are prefixed with the cluster names. This can help visualizing the // clustering decisions on TensorBoard. Graph new_graph(graph_->op_registry()); @@ -1714,7 +1761,301 @@ Status MarkForCompilationPass::RunForTest( return MarkForCompilation(options, debug_options); } +absl::flat_hash_map>* GetWhitelistTable() { + // Table format: category name: {list of TF operations in that category} + static absl::flat_hash_map>* result = + new absl::flat_hash_map>{ + // Unary + {"PW", + {"ComplexAbs", "Angle", "Conj", "Abs", "Acos", "Acosh", "Asin", + "Atan", "Atanh", "Ceil", "Cos", "Cosh", "Sin", "Exp", "Expm1", + "Floor", "IsFinite", "IsInf", "IsNan", "Inv", "Reciprocal", "Log", + "Log1p", "Invert", "LogicalNot", "Ndtri", "Neg", "Rint", "Round", + "Rsqrt", "Sigmoid", "Sign", "Sinh", "Softplus", "Softsign", "Sqrt", + "Square", "Tan", "Tanh", "Real", "Imag", "Erf", "Erfc", "Erfinv", + "Lgamma", "Digamma", + // Binary + "Add", "AddV2", "Sub", "Mul", "Div", "Atan2", "Complex", "DivNoNan", + "MulNoNan", "FloorDiv", "Xlogy", "Xdivy", "FloorMod", "BitwiseAnd", + "BitwiseOr", "BitwiseXor", "LeftShift", "RightShift", "LogicalAnd", + "LogicalOr", "Mod", "Maximum", "Minimum", "RealDiv", + "ReciprocalGrad", "RsqrtGrad", "SqrtGrad", "TruncateDiv", + "TruncateMod", "Equal", "NotEqual", "Greater", "GreaterEqual", + "Less", "LessEqual", "SigmoidGrad", "SoftplusGrad", "SoftsignGrad", + "TanhGrad", "Pow", "SquaredDifference", "ApproximateEqual", + // Others + "AddN", "Bitcast", "Cast", "ClipByValue", "Const", "Empty", + "Identity", "IdentityN", "Relu", "Relu6", "ReluGrad", "Relu6Grad", + "LeakyReluGrad", "Elu", "EluGrad", "Selu", "SeluGrad", "Select", + "SelectV2", "Transpose", "ConjugateTranspose", + "_UnaryOpsComposition", + // The following 4 operations are converted to identity + "PlaceholderWithDefault", "PreventGradient", "StopGradient", + "Snapshot"}}, + // clang-format off + {"RED", + {"All", "Any", "Min", "Max", "Mean", "Prod", "Sum"}}, + // clang-format on + {"PWRED", + {"ArgMax", "ArgMin", "DiagPart", "Softmax", + "SparseSoftmaxCrossEntropyWithLogits", "LogSoftmax"}}, + {"REDUCEWINDOW", + {"ArgMax", "ArgMin", "DiagPart", "Softmax", + "SparseSoftmaxCrossEntropyWithLogits", "LogSoftmax"}}, + {"REDUCEWINDOWPW", {"BiasAddGrad", "LRN", "LRNGrad"}}, + {"BN", + {"FusedBatchNorm", "FusedBatchNormV2", "FusedBatchNormV3", + "_FusedBatchNormEx", "FusedBatchNormGrad", "FusedBatchNormGradV2", + "FusedBatchNormGradV3"}}, + {"SORT", {"TopKV2"}}, // XLA version much faster then TF version. + {"MISC", + // clang-format off + {"BroadcastTo", "ExpandDims", "Fill", "NoOp", + "Range", "Rank", "Reshape", "Shape", "ShapeN", "Size", "Squeeze", + "Transpose", "ZerosLike", "OnesLike", "BiasAdd" /*PW + Broadcast*/, + "BroadcastArgs", "BroadcastGradientArgs", "OneHot", "Concat", "ConcatV2", + "ConcatOffset", "Const", "MirrorPad", "Pack", "Pad", "PadV2", "Reverse", + "ReverseV2", "ReverseSequence", "Slice", "Split", "SplitV", + "StridedSlice", "StridedSliceGrad", "ResourceStridedSliceAssign", + "Tile", "Transpose", "InvertPermutation", "Unpack"}}}; + // clang-format on + return result; +} + namespace testing { void ResetClusterSequenceNumber() { cluster_sequence_num = 0; } + +absl::flat_hash_set GetKnownXLAWhitelistOp() { + absl::flat_hash_set result{"AdjustContrastv2", + "AdjustHue", + "AdjustSaturation", + "Asinh", + "Assert", + "AssignAddVariableOp", + "AssignSubVariableOp", + "AssignVariableOp", + "AvgPool", + "AvgPool3D", + "AvgPool3DGrad", + "AvgPoolGrad", + "BatchMatMul", + "BatchMatMulV2", + "BatchToSpace", + "BatchToSpaceND", + "BesselI0e", + "BesselI1e", + "Betainc", + "BiasAddV1", + "Bucketize", + "Case", + "CheckNumerics", + "Cholesky", + "ControlTrigger", + "Conv2D", + "Conv2DBackpropFilter", + "Conv2DBackpropInput", + "Conv3D", + "Conv3DBackpropFilterV2", + "Conv3DBackpropInputV2", + "Cross", + "Cumprod", + "Cumsum", + "DataFormatDimMap", + "DataFormatVecPermute", + "DepthToSpace", + "DepthwiseConv2dNative", + "DepthwiseConv2dNativeBackpropFilter", + "DepthwiseConv2dNativeBackpropInput", + "Dequantize", + "Diag", + "DynamicStitch", + "Einsum", + "EmptyTensorList", + "ExtractImagePatches", + "FFT", + "FFT2D", + "FFT3D", + "FakeParam", + "FakeQuantWithMinMaxArgs", + "FakeQuantWithMinMaxArgsGradient", + "FakeQuantWithMinMaxVars", + "FakeQuantWithMinMaxVarsGradient", + "Gather", + "GatherNd", + "GatherV2", + "HSVToRGB", + "IFFT", + "IFFT2D", + "IFFT3D", + "IRFFT", + "IRFFT2D", + "IRFFT3D", + "If", + "InTopKV2", + "L2Loss", + "LeakyRelu", + "LinSpace", + "ListDiff", + "LogMatrixDeterminant", + "MatMul", + "MatrixBandPart", + "MatrixDiag", + "MatrixDiagPart", + "MatrixDiagPartV2", + "MatrixDiagPartV3", + "MatrixDiagV2", + "MatrixDiagV3", + "MatrixInverse", + "MatrixSetDiag", + "MatrixSetDiagV2", + "MatrixSetDiagV3", + "MatrixSolve", + "MatrixTriangularSolve", + "MaxPool", + "MaxPool3D", + "MaxPool3DGrad", + "MaxPool3DGradGrad", + "MaxPoolGrad", + "MaxPoolGradGrad", + "MaxPoolGradGradV2", + "MaxPoolGradV2", + "MaxPoolV2", + "Multinomial", + "NextAfter", + "NonMaxSuppressionV4", + "ParallelDynamicStitch", + "ParameterizedTruncatedNormal", + "PartitionedCall", + "Qr", + "QuantizeAndDequantizeV2", + "QuantizeAndDequantizeV3", + "RFFT", + "RFFT2D", + "RFFT3D", + "RGBToHSV", + "RandomShuffle", + "RandomStandardNormal", + "RandomUniform", + "RandomUniformInt", + "ReadVariableOp", + "ResizeBilinear", + "ResizeBilinearGrad", + "ResizeNearestNeighbor", + "ResourceApplyAdaMax", + "ResourceApplyAdadelta", + "ResourceApplyAdagrad", + "ResourceApplyAdagradDA", + "ResourceApplyAdagradV2", + "ResourceApplyAdam", + "ResourceApplyAddSign", + "ResourceApplyCenteredRMSProp", + "ResourceApplyFtrl", + "ResourceApplyFtrlV2", + "ResourceApplyGradientDescent", + "ResourceApplyKerasMomentum", + "ResourceApplyMomentum", + "ResourceApplyPowerSign", + "ResourceApplyProximalAdagrad", + "ResourceApplyProximalGradientDescent", + "ResourceApplyRMSProp", + "ResourceGather", + "ResourceScatterAdd", + "ResourceScatterDiv", + "ResourceScatterMax", + "ResourceScatterMin", + "ResourceScatterMul", + "ResourceScatterNdAdd", + "ResourceScatterNdSub", + "ResourceScatterNdUpdate", + "ResourceScatterSub", + "ResourceScatterUpdate", + "Roll", + "ScatterNd", + "SelfAdjointEigV2", + "SoftmaxCrossEntropyWithLogits", + "SpaceToBatch", + "SpaceToBatchND", + "SpaceToDepth", + "SparseMatMul", + "SparseToDense", + "StackCloseV2", + "StackPopV2", + "StackPushV2", + "StackV2", + "StatefulPartitionedCall", + "StatefulStandardNormalV2", + "StatefulTruncatedNormal", + "StatefulUniform", + "StatefulUniformFullInt", + "StatefulUniformInt", + "StatelessIf", + "StatelessMultinomial", + "StatelessRandomNormal", + "StatelessRandomUniform", + "StatelessRandomUniformInt", + "StatelessTruncatedNormal", + "StatelessWhile", + "Svd", + "SymbolicGradient", + "TensorArrayCloseV3", + "TensorArrayConcatV3", + "TensorArrayGatherV3", + "TensorArrayGradV3", + "TensorArrayReadV3", + "TensorArrayScatterV3", + "TensorArraySizeV3", + "TensorArraySplitV3", + "TensorArrayV3", + "TensorArrayWriteV3", + "TensorListElementShape", + "TensorListFromTensor", + "TensorListGather", + "TensorListGetItem", + "TensorListLength", + "TensorListPopBack", + "TensorListPushBack", + "TensorListReserve", + "TensorListSetItem", + "TensorListStack", + "TensorScatterAdd", + "TensorScatterSub", + "TensorScatterUpdate", + "TridiagonalSolve", + "TruncatedNormal", + "UnsortedSegmentMax", + "UnsortedSegmentMin", + "UnsortedSegmentProd", + "UnsortedSegmentSum", + "VarIsInitializedOp", + "VariableShape", + "While", + "XlaBroadcastHelper", + "XlaConv", + "XlaDequantize", + "XlaDot", + "XlaDynamicSlice", + "XlaDynamicUpdateSlice", + "XlaEinsum", + "XlaIf", + "XlaKeyValueSort", + "XlaPad", + "XlaRecv", + "XlaReduce", + "XlaReduceWindow", + "XlaReplicaId", + "XlaSelectAndScatter", + "XlaSelfAdjointEig", + "XlaSend", + "XlaSharding", + "XlaSort", + "XlaSvd", + "XlaWhile", + "_Arg", + "_ArrayToList", + "_ListToArray", + "_Retval"}; + return result; +} + } // namespace testing } // namespace tensorflow diff --git a/tensorflow/compiler/jit/mark_for_compilation_pass.h b/tensorflow/compiler/jit/mark_for_compilation_pass.h index 7adfc1419bf..0c9b40776f5 100644 --- a/tensorflow/compiler/jit/mark_for_compilation_pass.h +++ b/tensorflow/compiler/jit/mark_for_compilation_pass.h @@ -20,6 +20,7 @@ limitations under the License. #ifndef TENSORFLOW_COMPILER_JIT_MARK_FOR_COMPILATION_PASS_H_ #define TENSORFLOW_COMPILER_JIT_MARK_FOR_COMPILATION_PASS_H_ +#include "absl/container/flat_hash_set.h" #include "tensorflow/compiler/jit/compilability_check_util.h" #include "tensorflow/core/common_runtime/optimization_registry.h" @@ -56,11 +57,16 @@ bool IsCompilable(FunctionLibraryRuntime* flr, const NodeDef& ndef, RecursiveCompilabilityChecker::UncompilableNodesMap* uncompilable_node_info = nullptr); +absl::flat_hash_map>* GetWhitelistTable(); + namespace testing { // DO NOT USE IN PRODUCTION. // // Resets some internal state to let us write reliable unit tests. void ResetClusterSequenceNumber(); + +// Return a list of operation that we choose not to put into the whitelist. +absl::flat_hash_set GetKnownXLAWhitelistOp(); } // namespace testing } // namespace tensorflow diff --git a/tensorflow/compiler/jit/mark_for_compilation_pass_test.cc b/tensorflow/compiler/jit/mark_for_compilation_pass_test.cc index f10b4d0b4cb..c670f2e54f1 100644 --- a/tensorflow/compiler/jit/mark_for_compilation_pass_test.cc +++ b/tensorflow/compiler/jit/mark_for_compilation_pass_test.cc @@ -1803,6 +1803,35 @@ TEST(XlaCompilationTest, StagePipelinePreservedByClusterScopingPass) { EXPECT_NE(clusters["relu0"], clusters["relu1"]); } } +TEST(XlaCompilationTest, XLALiteWhitelist) { + auto* whitelist_table = tensorflow::GetWhitelistTable(); + absl::flat_hash_set hwhitelist; + std::vector vall_ops = XlaOpRegistry::GetAllRegisteredOps(); + absl::flat_hash_set all_ops(vall_ops.begin(), vall_ops.end()); + // Check that all the operations in the table are existing TF operations + for (auto pair : *whitelist_table) { + hwhitelist.insert(pair.second.begin(), pair.second.end()); + for (auto op : pair.second) { + ASSERT_TRUE(all_ops.contains(op)); + } + } + + // Check that all registered XLA operation are in the whitelist + // table or are known to not be in it. + + absl::flat_hash_set known_not_in_list = + tensorflow::testing::GetKnownXLAWhitelistOp(); + std::vector unknow_op; + for (string op : vall_ops) { + if (!hwhitelist.contains(op) && !known_not_in_list.contains(op)) { + unknow_op.push_back(op); + } + } + EXPECT_TRUE(unknow_op.empty()) + << "Someone added support for a new TF opeations inside XLA. They must " + "be included in the XLALite whitelist or blacklist:\n" + << absl::StrJoin(unknow_op, "\n"); +} } // namespace } // namespace tensorflow diff --git a/tensorflow/compiler/jit/node_matchers.h b/tensorflow/compiler/jit/node_matchers.h index 0d4f02c236b..ea47394bf7d 100644 --- a/tensorflow/compiler/jit/node_matchers.h +++ b/tensorflow/compiler/jit/node_matchers.h @@ -187,7 +187,7 @@ impl::NodeMatcherProperties Op(string op); // Matches a node with assigned device `assigned_device`. impl::NodeMatcherProperties AssignedDevice(string assigned_device); -// Matches a node with a boolean typed attrbute named `name` and with value +// Matches a node with a boolean typed attribute named `name` and with value // `value`. template impl::NodeMatcherProperties Attr(const string& name, ValueTy value) { diff --git a/tensorflow/compiler/jit/node_matchers_test.cc b/tensorflow/compiler/jit/node_matchers_test.cc index c3f0dfece85..8edb3e456c4 100644 --- a/tensorflow/compiler/jit/node_matchers_test.cc +++ b/tensorflow/compiler/jit/node_matchers_test.cc @@ -125,7 +125,7 @@ TEST(NodeMatchers, CheckControlDependence) { "is any node"); } -TEST(NodeMatchers, ConstVaulue) { +TEST(NodeMatchers, ConstValue) { Scope root = Scope::NewRootScope().ExitOnError(); Output placeholder = ops::Placeholder(root.WithOpName("placeholder"), DT_FLOAT); diff --git a/tensorflow/compiler/jit/ops/xla_ops.cc b/tensorflow/compiler/jit/ops/xla_ops.cc index 0217ba71929..b1cf2166721 100644 --- a/tensorflow/compiler/jit/ops/xla_ops.cc +++ b/tensorflow/compiler/jit/ops/xla_ops.cc @@ -110,7 +110,7 @@ Merges the outputs from the PartitionedCall node and the _XlaRun node. Unlike the TensorFlow Merge op, which requires inputs of some types to be placed on the host, the _XlaMerge op can merge inputs of all types when placed on the device. This prevents the need for copy operations, in -particluar when an XLA cluster has int32 outputs. The _XlaMerge up does not +particular when an XLA cluster has int32 outputs. The _XlaMerge up does not have a value_index output that identifies the chosen input. )"); diff --git a/tensorflow/compiler/jit/xla_device_context.cc b/tensorflow/compiler/jit/xla_device_context.cc index c1fb2f6671f..996ad09e2a9 100644 --- a/tensorflow/compiler/jit/xla_device_context.cc +++ b/tensorflow/compiler/jit/xla_device_context.cc @@ -262,7 +262,7 @@ void XlaDeviceContext::CopyDeviceTensorToCPU(const Tensor* device_tensor, << xla_tensor->shaped_buffer().ToString(); // For devices don't allow sync on completion, the device execution is // deferred. We check the execution stream status here to avoid wrong - // results from a failed stream being propogated to following + // results from a failed stream being propagated to following // host-side ops. if (!device_allows_sync_on_completion) { done_status.Update(xla_tensor->RefreshStatusOfStreams()); diff --git a/tensorflow/compiler/jit/xla_gpu_device.cc b/tensorflow/compiler/jit/xla_gpu_device.cc index 0a533152d50..8dc75c969a4 100644 --- a/tensorflow/compiler/jit/xla_gpu_device.cc +++ b/tensorflow/compiler/jit/xla_gpu_device.cc @@ -109,6 +109,14 @@ Status XlaGpuDeviceFactory::CreateDevices( VLOG(1) << "Failed to create XLA_GPU device: " << platform.status(); return Status::OK(); } + + auto iter = session_options.config.device_count().find("GPU"); + if (iter != session_options.config.device_count().end() && + iter->second == 0) { + // Device count for GPU is 0. + return Status::OK(); + } + string allowed_gpus = session_options.config.gpu_options().visible_device_list(); absl::optional> gpu_ids = diff --git a/tensorflow/compiler/jit/xla_kernel_creator_util.cc b/tensorflow/compiler/jit/xla_kernel_creator_util.cc index 96bde65003f..6441dd3ed28 100644 --- a/tensorflow/compiler/jit/xla_kernel_creator_util.cc +++ b/tensorflow/compiler/jit/xla_kernel_creator_util.cc @@ -222,7 +222,7 @@ Status CreateXlaKernel(FunctionLibraryRuntime* flr, const NodeDef& node_def, // using xla::ComputationDataHandle, which is just a symbolic handle that // xla::ComputationBuilder assigns. How does this handle gets assigned for // constant arguments? Even constant arguments get an _Arg node in the graph - // instatiated for Function compilation. The tf2xla kernel for constant _Arg + // instantiated for Function compilation. The tf2xla kernel for constant _Arg // nodes takes the constant value, converts it to XlaLiteral, and feeds it // to xla::ComputationBuilder.ConstantLiteral, which returns the handle. This // constant XlaLiteral is included in the HLO graph, and subsequently, in diff --git a/tensorflow/compiler/jit/xla_launch_util.cc b/tensorflow/compiler/jit/xla_launch_util.cc index ddaaefcef7d..402a5990a25 100644 --- a/tensorflow/compiler/jit/xla_launch_util.cc +++ b/tensorflow/compiler/jit/xla_launch_util.cc @@ -84,9 +84,9 @@ VariableInfo::~VariableInfo() { } } -// Returns a vector of VaribleInfo instances for the resource variable inputs to -// the kernel with context `ctx`. The input indices for the resource variable -// inputs are in `variable_indices`. +// Returns a vector of VariableInfo instances for the resource variable inputs +// to the kernel with context `ctx`. The input indices for the resource +// variable inputs are in `variable_indices`. static Status GetVariableInfosFromCtxInputs( OpKernelContext* ctx, absl::Span variable_indices, std::vector* result) { diff --git a/tensorflow/compiler/mlir/BUILD b/tensorflow/compiler/mlir/BUILD index fdd3941e136..b54d5867487 100644 --- a/tensorflow/compiler/mlir/BUILD +++ b/tensorflow/compiler/mlir/BUILD @@ -73,6 +73,7 @@ cc_library( "//tensorflow/compiler/mlir/xla:lhlo", "//tensorflow/compiler/mlir/xla:lhlo_fuse_linalg", "//tensorflow/compiler/mlir/xla:lhlo_legalize_to_affine", + "//tensorflow/compiler/mlir/xla:lhlo_legalize_to_gpu", "//tensorflow/compiler/mlir/xla:lhlo_legalize_to_linalg", "//tensorflow/compiler/mlir/xla:xla_dialect_registration", "//tensorflow/compiler/mlir/xla:xla_legalize_control_flow", diff --git a/tensorflow/compiler/mlir/lite/BUILD b/tensorflow/compiler/mlir/lite/BUILD index 2744c98f939..6f08ca3b5e8 100644 --- a/tensorflow/compiler/mlir/lite/BUILD +++ b/tensorflow/compiler/mlir/lite/BUILD @@ -282,8 +282,13 @@ cc_library( ":validators", "//tensorflow/compiler/mlir/lite/quantization:quantization_lib", "//tensorflow/compiler/mlir/tensorflow", + "//tensorflow/compiler/mlir/tensorflow:mangling_util", + "//tensorflow/compiler/xla:status", + "//tensorflow/compiler/xla:statusor", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core/platform:logging", "@com_google_absl//absl/memory", "@llvm//:support", "@local_config_mlir//:Analysis", diff --git a/tensorflow/compiler/mlir/lite/flatbuffer_import.cc b/tensorflow/compiler/mlir/lite/flatbuffer_import.cc index 8f24ad441a6..11d97120d00 100644 --- a/tensorflow/compiler/mlir/lite/flatbuffer_import.cc +++ b/tensorflow/compiler/mlir/lite/flatbuffer_import.cc @@ -98,6 +98,17 @@ using xla::StatusOr; namespace errors = tensorflow::errors; namespace tfl = mlir::TFL; +using llvm::cl::opt; + +// Commandline flag to enable the control of flatbuffer import. +bool use_external_constant; + +// NOLINTNEXTLINE +static opt use_external_constant_flag( + "use-external-constant", + llvm::cl::desc("Use external constant during flatbuffer import"), + llvm::cl::location(use_external_constant), llvm::cl::init(false)); + namespace { bool IsScalar(const TensorT& tensor) { // TODO(b/138222071) We can't distinguish scalars and unranked tensors @@ -391,6 +402,21 @@ StatusOr ConvertIntBuffer( } } +StatusOr BuildExternalConstOp(const tflite::TensorT& tensor, + int32_t buffer_index, + OpBuilder builder, Location loc) { + TF_ASSIGN_OR_RETURN(auto type, GetTensorType(tensor, builder, + /*shapeless_are_scalars=*/true, + /*is_constant=*/true)); + auto shaped_type = type.dyn_cast(); + if (!shaped_type) { + return errors::Internal("Constant doesn't have a shape"); + } + auto op = builder.create( + loc, shaped_type, builder.getI32IntegerAttr(buffer_index)); + return op.getOperation(); +} + StatusOr BuildConstOp(const tflite::TensorT& tensor, const std::vector& buffer, OpBuilder builder, Location loc) { @@ -608,8 +634,8 @@ StatusOr ConvertSubgraph( const std::vector& func_names, const std::vector>& buffers, Location base_loc, Builder builder, - const std::vector& ordered_output_arrays, - bool is_entry_point) { + const std::vector& ordered_output_arrays, bool is_entry_point, + bool use_external_constant) { llvm::SmallVector ret_types; llvm::SmallVector input_types; @@ -723,8 +749,11 @@ StatusOr ConvertSubgraph( auto& const_tensor = *subgraph.tensors[input_num]; auto const_loc = TensorLoc(const_tensor, builder, base_loc); auto op_or_err = - BuildConstOp(const_tensor, buffers[const_tensor.buffer]->data, - op_builder, const_loc); + use_external_constant + ? BuildExternalConstOp(const_tensor, const_tensor.buffer, + op_builder, const_loc) + : BuildConstOp(const_tensor, buffers[const_tensor.buffer]->data, + op_builder, const_loc); if (!op_or_err.ok()) { return emitError(const_loc, op_or_err.status().ToString()), op_or_err.status(); @@ -768,8 +797,11 @@ StatusOr ConvertSubgraph( auto& const_tensor = *subgraph.tensors[index]; auto const_loc = TensorLoc(const_tensor, builder, base_loc); auto op_or_err = - BuildConstOp(const_tensor, buffers[const_tensor.buffer]->data, - op_builder, const_loc); + use_external_constant + ? BuildExternalConstOp(const_tensor, const_tensor.buffer, + op_builder, const_loc) + : BuildConstOp(const_tensor, buffers[const_tensor.buffer]->data, + op_builder, const_loc); if (!op_or_err.ok()) { return emitError(const_loc, op_or_err.status().ToString()), op_or_err.status(); @@ -804,7 +836,8 @@ std::string SubgraphName(unsigned index, const tflite::SubGraphT& subgraph) { OwningModuleRef tflite::FlatBufferToMlir( absl::string_view buffer, MLIRContext* context, Location base_loc, - const std::vector& ordered_output_arrays) { + const std::vector& ordered_output_arrays, + bool use_external_constant) { auto model_ptr = FlatBufferModel::VerifyAndBuildFromBuffer(buffer.data(), buffer.length()); if (nullptr == model_ptr) { @@ -858,7 +891,8 @@ OwningModuleRef tflite::FlatBufferToMlir( // Only the entry point needs pseudo_input_ops // TODO(b/131175224,b/132239787) Support multiple entry points builder, ordered_output_arrays, - /*is_entry_point=*/e.index() == 0); + /*is_entry_point=*/e.index() == 0, + /*use_external_constant=*/use_external_constant); if (!func_or_error.ok()) { return emitError(base_loc, "could not translate function ") << subgraph->name, @@ -872,7 +906,8 @@ OwningModuleRef tflite::FlatBufferToMlir( } static OwningModuleRef FlatBufferFileToMlirTrans(llvm::SourceMgr* source_mgr, - MLIRContext* context) { + MLIRContext* context, + bool use_external_constant) { const llvm::MemoryBuffer* input = source_mgr->getMemoryBuffer(source_mgr->getMainFileID()); std::string error; @@ -889,11 +924,12 @@ static OwningModuleRef FlatBufferFileToMlirTrans(llvm::SourceMgr* source_mgr, return tflite::FlatBufferToMlir( absl::string_view(input->getBufferStart(), input->getBufferSize()), - context, loc, outputs); + context, loc, outputs, use_external_constant); } static mlir::TranslateToMLIRRegistration FlatBufferFileToMlirTransReg( "tflite-flatbuffer-to-mlir", [](llvm::SourceMgr& source_mgr, MLIRContext* context) { - return FlatBufferFileToMlirTrans(&source_mgr, context); + return FlatBufferFileToMlirTrans(&source_mgr, context, + use_external_constant); }); diff --git a/tensorflow/compiler/mlir/lite/flatbuffer_import.h b/tensorflow/compiler/mlir/lite/flatbuffer_import.h index c277d739851..66b31c54c80 100644 --- a/tensorflow/compiler/mlir/lite/flatbuffer_import.h +++ b/tensorflow/compiler/mlir/lite/flatbuffer_import.h @@ -29,10 +29,13 @@ namespace tflite { // If ordered_output_arrays is not empty, then the imported mlir function will // only return nodes in ordered_output_arrays in the same order. Returns nullptr // on failure, and more specific errors will be emitted via the context. +// If `use_external_constant` is true, it will create `tfl.external_const` +// instead of `tfl.const`. mlir::OwningModuleRef FlatBufferToMlir( absl::string_view buffer, mlir::MLIRContext* context, mlir::Location base_loc, - const std::vector& ordered_output_arrays); + const std::vector& ordered_output_arrays, + bool use_external_constant = false); } // namespace tflite #endif // TENSORFLOW_COMPILER_MLIR_LITE_FLATBUFFER_IMPORT_H_ diff --git a/tensorflow/compiler/mlir/lite/flatbuffer_translate.cc b/tensorflow/compiler/mlir/lite/flatbuffer_translate.cc index 9205ebb101c..3ed8eb87eb9 100644 --- a/tensorflow/compiler/mlir/lite/flatbuffer_translate.cc +++ b/tensorflow/compiler/mlir/lite/flatbuffer_translate.cc @@ -374,6 +374,10 @@ class Translator { mlir::TF::WhileOp op, const std::vector& operands, const std::vector& results); + BufferOffset BuildNumericVerifyOperator( + mlir::TFL::NumericVerifyOp op, const std::vector& operands, + const std::vector& results); + Optional CreateFlexOpCustomOptions( const ::tensorflow::NodeDef& node_def, const mlir::Location& loc); @@ -414,8 +418,8 @@ class Translator { // is marked as a stateful operand. bool IsStatefulOperand(mlir::Operation* op, int operand_index); - // Returns a unique name for `op`. - std::string UniqueName(mlir::Operation* op); + // Returns a unique name for `val`. + std::string UniqueName(mlir::Value* val); ModuleOp module_; @@ -445,8 +449,8 @@ class Translator { std::vector failed_custom_ops_; }; -std::string Translator::UniqueName(mlir::Operation* op) { - return name_mapper_.GetUniqueName(op); +std::string Translator::UniqueName(mlir::Value* val) { + return name_mapper_.GetUniqueName(val); } Optional> Translator::BuildBuffer( @@ -610,6 +614,21 @@ BufferOffset Translator::BuildWhileOperator( builtin_options); } +BufferOffset Translator::BuildNumericVerifyOperator( + mlir::TFL::NumericVerifyOp op, const std::vector& operands, + const std::vector& results) { + float tolerance = op.tolerance().convertToFloat(); + std::vector custom_options(sizeof(float)); + memcpy(custom_options.data(), &tolerance, sizeof(float)); + auto opcode_index = + GetOpcodeIndex("NumericVerify", tflite::BuiltinOperator_CUSTOM); + return tflite::CreateOperator( + builder_, opcode_index, builder_.CreateVector(operands), + builder_.CreateVector(results), tflite::BuiltinOptions_NONE, + /*builtin_options=*/0, builder_.CreateVector(custom_options), + tflite::CustomOptionsFormat_FLEXBUFFERS); +} + Optional Translator::CreateFlexOpCustomOptions( const ::tensorflow::NodeDef& node_def, const mlir::Location& loc) { std::string node_def_str; @@ -736,6 +755,9 @@ Optional> Translator::BuildOperator( auto builtin_code = GetBuiltinOpCode(inst); if (!builtin_code) { + if (auto verify_op = dyn_cast(inst)) { + return BuildNumericVerifyOperator(verify_op, operands, results); + } inst->emitOpError("is not a supported TFLite op"); return llvm::None; } @@ -864,17 +886,7 @@ void Translator::InitializeNamesFromAttribute(FuncOp fn, bool* has_input_attr) { return; } for (const auto& it : llvm::enumerate(term->getOperands())) { - // TODO(jpienaar): If this isn't due to an op, then we'd need to either - // ensure the name that will be assigned to the buffer is the same, or - // insert an op so that we can have a buffer named such. This cannot - // currently happen due to pseudo_input nodes. - if (auto op = it.value()->getDefiningOp()) { - name_mapper_.InitOpName(op, output_names[it.index()].trim()); - } else { - fn.emitWarning() << "output is not due to an op and '" - << output_names[it.index()] - << "' may not be a named output"; - } + name_mapper_.InitOpName(it.value(), output_names[it.index()].trim()); } } } @@ -941,16 +953,9 @@ Optional> Translator::BuildSubGraph(FuncOp fn) { for (auto& inst : bb) { if (inst.isKnownTerminator()) break; - std::string name = UniqueName(&inst); - for (size_t i = 0, e = inst.getNumResults(); i < e; ++i) { - // Tensors are named by adding result index to name for the particular - // operation such as name:0, name:1, name:2 etc. Default port is zero so - // the first result can be specified without the port. This is based on - // TensorFlow's naming scheme for inputs in the NodeDef proto. - std::string suffix = i > 0 ? absl::StrCat(":", i) : ""; - if (!build_tensor_and_buffer(inst.getResult(i), name + suffix)) { - return llvm::None; - } + for (auto val : inst.getResults()) { + std::string name = UniqueName(val); + if (!build_tensor_and_buffer(val, name)) return llvm::None; } // Skip constant ops as they don't represent a TFLite operator. diff --git a/tensorflow/compiler/mlir/lite/ir/tfl_ops.td b/tensorflow/compiler/mlir/lite/ir/tfl_ops.td index c339d96baed..fdc256acf41 100644 --- a/tensorflow/compiler/mlir/lite/ir/tfl_ops.td +++ b/tensorflow/compiler/mlir/lite/ir/tfl_ops.td @@ -577,6 +577,19 @@ def TFL_ConstOp : Op { + let summary = "External const op."; + + let description = [{ + External const op holds a `buffer_index` which points to a constant + in the flatbuffer. + }]; + + let arguments = (ins I32Attr:$buffer_index); + + let results = (outs AnyTensor:$output); +} + def TFL_Conv2DOp : TFL_ConvOp<"conv_2d", "Convolution", 0>; def TFL_CosOp: TFL_Op<"cos", [ @@ -3113,6 +3126,27 @@ the output tensor can vary depending on how many true values there are in ); } +def TFL_NumericVerifyOp : Op { + + let summary = "Verifies the numericals of the two operands"; + + let description = [{ + The NumericVerify op is a debugging op to verify the numericals of the two + activations. It is a custom op in TFLite. + }]; + + let arguments = (ins + TensorOf<[QI8, QUI8, QI16, QUI16]>:$input, + TensorOf<[F32]>:$ref, + + // Attributes + DefaultValuedAttr:$tolerance + ); + + let results = (outs); +} + def SVDFResultConstraint: PredOpTrait< "the input and result tensor elemental types must be same", TCresVTEtIsSameAsOp<0, 0>>; diff --git a/tensorflow/compiler/mlir/lite/python/graphdef_to_tfl_flatbuffer.cc b/tensorflow/compiler/mlir/lite/python/graphdef_to_tfl_flatbuffer.cc index 51bd1e4540c..e2ba0cb822b 100644 --- a/tensorflow/compiler/mlir/lite/python/graphdef_to_tfl_flatbuffer.cc +++ b/tensorflow/compiler/mlir/lite/python/graphdef_to_tfl_flatbuffer.cc @@ -57,8 +57,10 @@ const char kDetectionPostProcessOp[] = "type: 'int'} attr : { name: 'max_detections' type: 'int'} attr : { " "name: 'nms_iou_threshold' type: 'float'} attr : { name: " "'nms_score_threshold' type: 'float'} attr : { name: 'num_classes' type: " - "'int'} attr : { name: 'w_scale' type: 'int'} attr : { name: 'x_scale' " - "type: 'int'} attr : { name: 'y_scale' type: 'int'}"; + "'int'} attr : { name: 'w_scale' type: 'float'} attr : { name: 'x_scale' " + "type: 'float'} attr : { name: 'y_scale' type: 'float'} attr { name: " + "'detections_per_class' type: 'int' default_value { i : 100 }} attr { " + "name: 'use_regular_nms' type: 'bool' default_value { b : false }}"; // Converts the toco::IODataType to tensorflow::DataType. Only contains the // conversion mapping for constants defined in TFLite Python API. diff --git a/tensorflow/compiler/mlir/lite/quantization/quantization_driver.cc b/tensorflow/compiler/mlir/lite/quantization/quantization_driver.cc index 359b9ee4e48..a145e75465e 100644 --- a/tensorflow/compiler/mlir/lite/quantization/quantization_driver.cc +++ b/tensorflow/compiler/mlir/lite/quantization/quantization_driver.cc @@ -458,13 +458,15 @@ void QuantizationDriver::QuantizeValue(Value *value, QuantParams params, void QuantizationDriver::RequantizeOpResult(Operation *op, int index, RequantizeState *state) { if (state->pos == RequantizeState::NO_REQUANTIZE) return; - builder_.setInsertionPoint(op->getBlock(), ++Block::iterator(op)); + builder_.setInsertionPointAfter(op); Value *value = op->getResult(index); if (state->pos == RequantizeState::ON_OUTPUT) { - Operation *op = value->getUses().begin().getUser(); // `quantize` op - // The requantize op is inserted between `quantize` and `dequantize` ops. - value = op->getResult(0); - builder_.setInsertionPoint(op->getBlock(), ++Block::iterator(op)); + Operation *user = value->getUses().begin().getUser(); + if (llvm::isa(user)) { + // The requantize op is inserted between `quantize` and `dequantize` ops. + value = user->getResult(0); + builder_.setInsertionPointAfter(user); + } } RequantizeValue(value, state, op->getLoc()); } diff --git a/tensorflow/compiler/mlir/lite/quantization/quantization_utils.cc b/tensorflow/compiler/mlir/lite/quantization/quantization_utils.cc index eec93e9ae6a..ca10809be69 100644 --- a/tensorflow/compiler/mlir/lite/quantization/quantization_utils.cc +++ b/tensorflow/compiler/mlir/lite/quantization/quantization_utils.cc @@ -416,7 +416,7 @@ bool RemoveRedundantStatsOps(mlir::FuncOp func, if (res->hasOneUse()) { if (auto next_stats = llvm::dyn_cast( *res->getUsers().begin())) { - // quantization parameters can be propgated to next_stats + // quantization parameters can be propagated to next_stats redundant_stats_ops.insert(next_stats); // add next_stats to the work list so propagation can // continue. diff --git a/tensorflow/compiler/mlir/lite/quantization/quantization_utils.h b/tensorflow/compiler/mlir/lite/quantization/quantization_utils.h index c9f9d6619a3..0f7ec91ebc6 100644 --- a/tensorflow/compiler/mlir/lite/quantization/quantization_utils.h +++ b/tensorflow/compiler/mlir/lite/quantization/quantization_utils.h @@ -31,6 +31,7 @@ limitations under the License. #include "mlir/IR/BlockAndValueMapping.h" // TF:local_config_mlir #include "mlir/IR/Function.h" // TF:local_config_mlir #include "mlir/IR/MLIRContext.h" // TF:local_config_mlir +#include "mlir/IR/Matchers.h" // TF:local_config_mlir #include "mlir/IR/PatternMatch.h" // TF:local_config_mlir #include "mlir/IR/StandardTypes.h" // TF:local_config_mlir #include "mlir/Support/LLVM.h" // TF:local_config_mlir @@ -144,12 +145,16 @@ struct ConvertStatsToQDQs : public OpRewritePattern { // // Full integer quantization disallows "hybrid" operands or results. // Weight quantization allows "hybrid" operands and results. -template +template struct QuantizationPattern : public RewritePattern { - using BaseType = QuantizationPattern; + using BaseType = QuantizationPattern; - explicit QuantizationPattern(MLIRContext* context) - : RewritePattern(DQ::getOperationName(), 1, context) {} + explicit QuantizationPattern(MLIRContext* context, bool enable_verify, + float error_tolerance, bool single_layer_verify) + : RewritePattern(DQ::getOperationName(), 1, context), + enable_verify(enable_verify), + error_tolerance(error_tolerance), + single_layer_verify(single_layer_verify) {} PatternMatchResult matchAndRewrite(Operation* op, PatternRewriter& rewriter) const override { @@ -230,7 +235,7 @@ struct QuantizationPattern : public RewritePattern { } } - rewriter.setInsertionPoint(quantized_op); + rewriter.setInsertionPointAfter(quantized_op); OperationState new_state(quantized_op->getLoc(), quantized_op->getName().getStringRef(), inputs, output_types, quantized_op->getAttrs()); @@ -239,9 +244,64 @@ struct QuantizationPattern : public RewritePattern { output.getFirst()->replaceAllUsesWith( new_op->getResult(output.getSecond())); } + + // To verify the numericals, the original floating-point ops are + // preserved in the graph. The result of these floating-point ops are sent + // to a numeric verifier op as the reference. + if (enable_verify) { + // For constant operands, the floating-point constant is duplicated in + // case it is quantized. + for (int i = 0, e = new_op->getNumOperands(); i != e; ++i) { + auto def = new_op->getOperand(i)->getDefiningOp(); + if (auto q = llvm::dyn_cast_or_null(def)) { + DenseFPElementsAttr attr; + if (!matchPattern(q.input(), m_Constant(&attr))) { + continue; + } + auto cst = rewriter.create(new_op->getLoc(), attr); + quantized_op->setOperand(i, cst.getResult()); + } + } + + for (int i = 0, e = new_op->getNumResults(); i != e; ++i) { + if (!quantized_op->getResult(i) + ->getType() + .cast() + .getElementType() + .isa()) { + continue; + } + rewriter.setInsertionPointAfter(new_op); + FloatAttr tolerance = rewriter.getF32FloatAttr(error_tolerance); + // Verify the quantized value by sending the result to the verifier. + rewriter.create(quantized_op->getLoc(), + new_op->getResult(i), + quantized_op->getResult(i), tolerance); + + if (single_layer_verify) continue; + + // Find the Dequantize/Dequantize users of the new op results, and + // replace the usage. Then all the floating-point ops are connected. + // N.B. the return op will use this floating-point result. + for (auto user : new_op->getResult(i)->getUsers()) { + // Skip the Requantize op, and we know it has a single user. + if (llvm::isa(user)) { + user = *user->getResult(0)->getUsers().begin(); + } + if (auto dequantize = llvm::dyn_cast(user)) { + dequantize.getResult()->replaceAllUsesWith( + quantized_op->getResult(i)); + } + } + } + } } return matchSuccess(); } + + bool enable_verify; + float error_tolerance; + bool single_layer_verify; }; // Converts quantize ops with unsigned quantized types to these with signed @@ -342,14 +402,14 @@ ElementsAttr Quantize(Attribute real_value, Type tensor_type); // parameters in this type is based on the min and max element of the // attribute. When the elements in the `attr` are not in floating-point, or // the value range isn't straddling zero, an empty type is returned. The min/max -// are ajusted to be symmetric if `symmetric` flag is set to True. And +// are adjusted to be symmetric if `symmetric` flag is set to True. And // `symmetric` can only be set to true when it is signed and narrow_range. Type GetUniformQuantizedTypeForWeight(ElementsAttr attr, bool symmetric, unsigned num_bits, bool is_sign, bool narrow_range); // Returns the per channel quantized type for an element attribute. -// `quant_dim` defines the quantization axis. The channel min/max are ajusted +// `quant_dim` defines the quantization axis. The channel min/max are adjusted // to be symmetric if `symmetric` flag is set to True. And `symmetric` can only // be set to true when it is signed and narrow_range. Type GetUniformQuantizedPerAxisTypeForWeight(ElementsAttr attr, int quant_dim, diff --git a/tensorflow/compiler/mlir/lite/tests/debuginfo/BUILD b/tensorflow/compiler/mlir/lite/tests/debuginfo/BUILD index e6618a3e40e..2498a106f8f 100644 --- a/tensorflow/compiler/mlir/lite/tests/debuginfo/BUILD +++ b/tensorflow/compiler/mlir/lite/tests/debuginfo/BUILD @@ -32,6 +32,7 @@ filegroup( "//tensorflow/compiler/mlir/lite:flatbuffer_to_string", "//tensorflow/compiler/mlir/lite:tf_tfl_translate", "@llvm//:FileCheck", + "@llvm//:not", ], ) diff --git a/tensorflow/compiler/mlir/lite/tests/debuginfo/concrete_function_error.py b/tensorflow/compiler/mlir/lite/tests/debuginfo/concrete_function_error.py index 8af59ed7c0a..0bb386f4829 100644 --- a/tensorflow/compiler/mlir/lite/tests/debuginfo/concrete_function_error.py +++ b/tensorflow/compiler/mlir/lite/tests/debuginfo/concrete_function_error.py @@ -36,7 +36,7 @@ class TestGraphDebugInfo(object): @tf.function( input_signature=[tf.TensorSpec(shape=[3, 3], dtype=tf.float32)]) def model(x): - y = tf.math.reciprocal(x) # Not supported + y = tf.math.betainc(x, 0.5, 1.0) # Not supported return y + y func = model.get_concrete_function() @@ -47,14 +47,14 @@ class TestGraphDebugInfo(object): # pylint: disable=line-too-long # CHECK-LABEL: testConcreteFunctionDebugInfo -# CHECK: error: 'tf.Reciprocal' op is neither a custom op nor a flex op +# CHECK: error: 'tf.Betainc' op is neither a custom op nor a flex op # CHECK: attrs=attr_protos, op_def=op_def) # CHECK: ^ # CHECK: {{.*tensorflow/python/ops/gen_math_ops.py:[0-9]+:[0-9]+: note: called from}} -# CHECK: "Reciprocal", x=x, name=name) +# CHECK: "Betainc", a=a, b=b, x=x, name=name) # CHECK: ^ # CHECK: {{.*tensorflow/compiler/mlir/lite/tests/debuginfo/concrete_function_error.py:[0-9]+:[0-9]+: note: called from}} -# CHECK: y = tf.math.reciprocal(x) # Not supported +# CHECK: y = tf.math.betainc(x, 0.5, 1.0) # Not supported # CHECK: ^ # CHECK: :0: error: failed while converting: 'main' diff --git a/tensorflow/compiler/mlir/lite/tests/debuginfo/saved_model_error.py b/tensorflow/compiler/mlir/lite/tests/debuginfo/saved_model_error.py index 322330f1b9b..a4011226f14 100644 --- a/tensorflow/compiler/mlir/lite/tests/debuginfo/saved_model_error.py +++ b/tensorflow/compiler/mlir/lite/tests/debuginfo/saved_model_error.py @@ -33,7 +33,7 @@ class TestModule(tf.Module): @tf.function(input_signature=[tf.TensorSpec(shape=[3, 3], dtype=tf.float32)]) def model(self, x): - y = tf.math.reciprocal(x) # Not supported + y = tf.math.betainc(x, 0.5, 1.0) # Not supported return y + y @@ -56,14 +56,14 @@ class TestGraphDebugInfo(object): # pylint: disable=line-too-long # CHECK-LABEL: testSavedModelDebugInfo -# CHECK: error: 'tf.Reciprocal' op is neither a custom op nor a flex op +# CHECK: error: 'tf.Betainc' op is neither a custom op nor a flex op # CHECK: attrs=attr_protos, op_def=op_def) # CHECK: ^ # CHECK: {{.*tensorflow/python/ops/gen_math_ops.py:[0-9]+:[0-9]+: note: called from}} -# CHECK: "Reciprocal", x=x, name=name) +# CHECK: "Betainc", a=a, b=b, x=x, name=name) # CHECK: ^ # CHECK: {{.*tensorflow/compiler/mlir/lite/tests/debuginfo/saved_model_error.py:[0-9]+:[0-9]+: note: called from}} -# CHECK: y = tf.math.reciprocal(x) # Not supported +# CHECK: y = tf.math.betainc(x, 0.5, 1.0) # Not supported # CHECK: ^ # CHECK: :0: error: failed while converting: 'main' diff --git a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt index c1bb797ebee..b16ea3fa584 100644 --- a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt +++ b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt @@ -1,6 +1,6 @@ -# RUN: tf_tfl_translate -mlir-pretty-debuginfo -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes=1,224,224,3 -tf-output-arrays=MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm -tf-debug-info=%s.debug %s -o - 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +# RUN: not tf_tfl_translate -mlir-pretty-debuginfo -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes=1,224,224,3 -tf-output-arrays=MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm -tf-debug-info=%s.debug %s -o - 2>&1 | FileCheck %s -# CHECK: fake/user/code/file_C.py:27:1: error: 'tf.Conv2D' op attribute 'data_format' failed to satisfy constraint: 'NHWC' or 'NCHW' convnet data format +# CHECK: fake/user/code/file_C.py: error: 'tf.Conv2D' op attribute 'data_format' failed to satisfy constraint: 'NHWC' or 'NCHW' convnet data format node { name: "input" diff --git a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt.debug b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt.debug index 60a7d40139d..afc854e9cc6 100644 --- a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt.debug +++ b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.line.part.pbtxt.debug @@ -57,7 +57,7 @@ files: "fake/user/code/file_x.py" files: "fake/user/code/file_y.py" files: "fake/user/code/file_z.py" traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/beta" + key: "MobilenetV1/Conv2d_0/BatchNorm/beta@" value { file_line_cols { file_index: 33 @@ -66,7 +66,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/beta/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/beta/read@" value { file_line_cols { file_index: 49 @@ -75,7 +75,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/gamma" + key: "MobilenetV1/Conv2d_0/BatchNorm/gamma@" value { file_line_cols { file_index: 38 @@ -84,7 +84,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/gamma/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/gamma/read@" value { file_line_cols { file_index: 49 @@ -93,7 +93,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean@" value { file_line_cols { file_index: 44 @@ -102,7 +102,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean/read@" value { file_line_cols { file_index: 49 @@ -111,7 +111,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance@" value { file_line_cols { file_index: 44 @@ -120,7 +120,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance/read@" value { file_line_cols { file_index: 49 @@ -129,7 +129,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/weights" + key: "MobilenetV1/Conv2d_0/weights@" value { file_line_cols { file_index: 54 @@ -138,7 +138,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/weights/read" + key: "MobilenetV1/Conv2d_0/weights/read@" value { file_line_cols { file_index: 49 @@ -147,7 +147,7 @@ traces { } } traces { - key: "MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm" + key: "MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm@" value { file_line_cols { file_index: 5 @@ -156,7 +156,7 @@ traces { } } traces { - key: "MobilenetV1/MobilenetV1/Conv2d_0/Conv2D" + key: "MobilenetV1/MobilenetV1/Conv2d_0/Conv2D@" value { file_line_cols { file_index: 2 @@ -165,7 +165,7 @@ traces { } } traces { - key: "input" + key: "input@" value { file_line_cols { file_index: 40 diff --git a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt index d3dcbc65719..f2d8f6762cd 100644 --- a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt +++ b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt @@ -1,9 +1,9 @@ -# RUN: tf_tfl_translate -mlir-pretty-debuginfo -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes=1,224,224,3 -tf-output-arrays=MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm -tf-debug-info=%s.debug %s -o - 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +# RUN: not tf_tfl_translate -mlir-pretty-debuginfo -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes=1,224,224,3 -tf-output-arrays=MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm -tf-debug-info=%s.debug %s -o - 2>&1 | FileCheck %s -# CHECK: fake/user/code/file_C.py:27:1: error: 'tf.Conv2D' op attribute 'data_format' failed to satisfy constraint: 'NHWC' or 'NCHW' convnet data format -# CHECK: fake/user/code/file_D.py:28:1: note: called from -# CHECK: fake/user/code/file_E.py:29:1: note: called from -# CHECK: fake/user/code/file_F.py:30:1: note: called from +# CHECK: fake/user/code/file_C.py: error: 'tf.Conv2D' op attribute 'data_format' failed to satisfy constraint: 'NHWC' or 'NCHW' convnet data format +# CHECK: fake/user/code/file_D.py: note: called from +# CHECK: fake/user/code/file_E.py: note: called from +# CHECK: fake/user/code/file_F.py: note: called from node { name: "input" diff --git a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt.debug b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt.debug index f2ffc62583a..d221ae5c644 100644 --- a/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt.debug +++ b/tensorflow/compiler/mlir/lite/tests/debuginfo/v1_1.0_224_frozen.wrong_attr.stack.part.pbtxt.debug @@ -57,7 +57,7 @@ files: "fake/user/code/file_x.py" files: "fake/user/code/file_y.py" files: "fake/user/code/file_z.py" traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/beta" + key: "MobilenetV1/Conv2d_0/BatchNorm/beta@" value { file_line_cols { file_index: 33 @@ -66,7 +66,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/beta/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/beta/read@" value { file_line_cols { file_index: 49 @@ -75,7 +75,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/gamma" + key: "MobilenetV1/Conv2d_0/BatchNorm/gamma@" value { file_line_cols { file_index: 38 @@ -84,7 +84,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/gamma/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/gamma/read@" value { file_line_cols { file_index: 49 @@ -93,7 +93,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean@" value { file_line_cols { file_index: 44 @@ -102,7 +102,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_mean/read@" value { file_line_cols { file_index: 49 @@ -111,7 +111,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance@" value { file_line_cols { file_index: 44 @@ -120,7 +120,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance/read" + key: "MobilenetV1/Conv2d_0/BatchNorm/moving_variance/read@" value { file_line_cols { file_index: 49 @@ -129,7 +129,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/weights" + key: "MobilenetV1/Conv2d_0/weights@" value { file_line_cols { file_index: 54 @@ -138,7 +138,7 @@ traces { } } traces { - key: "MobilenetV1/Conv2d_0/weights/read" + key: "MobilenetV1/Conv2d_0/weights/read@" value { file_line_cols { file_index: 49 @@ -147,7 +147,7 @@ traces { } } traces { - key: "MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm" + key: "MobilenetV1/MobilenetV1/Conv2d_0/BatchNorm/FusedBatchNorm@" value { file_line_cols { file_index: 5 @@ -156,7 +156,7 @@ traces { } } traces { - key: "MobilenetV1/MobilenetV1/Conv2d_0/Conv2D" + key: "MobilenetV1/MobilenetV1/Conv2d_0/Conv2D@" value { file_line_cols { file_index: 2 @@ -177,7 +177,7 @@ traces { } } traces { - key: "input" + key: "input@" value { file_line_cols { file_index: 40 diff --git a/tensorflow/compiler/mlir/lite/tests/flatbuffer2mlir/external_constant.mlir b/tensorflow/compiler/mlir/lite/tests/flatbuffer2mlir/external_constant.mlir new file mode 100644 index 00000000000..762fab20a27 --- /dev/null +++ b/tensorflow/compiler/mlir/lite/tests/flatbuffer2mlir/external_constant.mlir @@ -0,0 +1,14 @@ +// RUN: flatbuffer_translate -mlir-to-tflite-flatbuffer %s -o - | flatbuffer_translate --tflite-flatbuffer-to-mlir --use-external-constant - -o - | FileCheck --dump-input-on-failure %s +// Ensure that `tfl.external_const` is imported when the flag `-use-external-constant` is enabled. + +func @main(tensor<40x37xf32>, tensor<40x37xf32>) -> tensor<40x40xf32> { +^bb0(%arg0: tensor<40x37xf32>, %arg1: tensor<40x37xf32>): + %cst = constant dense<1.0> : tensor<40xf32> + %0:2 = "tfl.fully_connected"(%arg0, %arg1, %cst) {fused_activation_function = "NONE", keep_num_dims = false, weights_format = "DEFAULT"} : (tensor<40x37xf32>, tensor<40x37xf32>, tensor<40xf32>) -> (tensor<40x40xf32>, tensor<40x40xf32>) + return %0 : tensor<40x40xf32> + +// CHECK-LABEL: func @main(%arg0: tensor<40x37xf32>, %arg1: tensor<40x37xf32>) -> tensor<40x40xf32> +// CHECK: %[[CONST:[0-9]+]] = "tfl.external_const"() {buffer_index = 3 : i32} : () -> tensor<40xf32> +// CHECK-NEXT: %[[FULL:[0-9]+]]:2 = "tfl.fully_connected"(%arg0, %arg1, %[[CONST]]) {fused_activation_function = "NONE", keep_num_dims = false, weights_format = "DEFAULT"} +// CHECK-NEXT: return %[[FULL]]#0 +} diff --git a/tensorflow/compiler/mlir/lite/tests/legalize-tf.mlir b/tensorflow/compiler/mlir/lite/tests/legalize-tf.mlir index ec618ffa276..e22198da6ea 100644 --- a/tensorflow/compiler/mlir/lite/tests/legalize-tf.mlir +++ b/tensorflow/compiler/mlir/lite/tests/legalize-tf.mlir @@ -1290,3 +1290,53 @@ func @assert_remove(%arg0: tensor<1xi32>, %arg1: tensor<1xi32>) -> tensor<1xi1> // CHECK-NOT: Assert // CHECK: return } + +func @reciprocal_f16(%arg0: tensor<8xf16>) -> tensor<8xf16> { + %0 = "tf.Reciprocal"(%arg0) : (tensor<8xf16>) -> tensor<8xf16> + return %0: tensor<8xf16> + +// CHECK-LABEL: reciprocal_f16 +// CHECK: %cst = constant dense<1.000000e+00> : tensor<1xf16> +// CHECK: "tfl.div"(%cst, %arg0) {fused_activation_function = "NONE"} : (tensor<1xf16>, tensor<8xf16>) -> tensor<8xf16> +// CHECK: return +} + +func @reciprocal_f32(%arg0: tensor<8xf32>) -> tensor<8xf32> { + %0 = "tf.Reciprocal"(%arg0) : (tensor<8xf32>) -> tensor<8xf32> + return %0: tensor<8xf32> + +// CHECK-LABEL: reciprocal_f32 +// CHECK: %cst = constant dense<1.000000e+00> : tensor<1xf32> +// CHECK: "tfl.div"(%cst, %arg0) {fused_activation_function = "NONE"} : (tensor<1xf32>, tensor<8xf32>) -> tensor<8xf32> +// CHECK: return +} + +func @reciprocal_complex_f32(%arg0: tensor<8xcomplex>) -> tensor<8xcomplex> { + %0 = "tf.Reciprocal"(%arg0) : (tensor<8xcomplex>) -> tensor<8xcomplex> + return %0: tensor<8xcomplex> + +// CHECK-LABEL: reciprocal_complex_f32 +// CHECK: %cst = constant opaque<"tf", "0x746674656E736F722464747970653A2044545F434F4D504C455836342074656E736F725F7368617065207B2064696D207B2073697A653A2031207D207D2074656E736F725F636F6E74656E743A20225C3030305C3030305C3230303F5C3030305C3030305C3030305C30303022"> : tensor<1xcomplex> +// CHECK: "tfl.div"(%cst, %arg0) {fused_activation_function = "NONE"} : (tensor<1xcomplex>, tensor<8xcomplex>) -> tensor<8xcomplex> +// CHECK: return +} + +func @reciprocal_i32(%arg0: tensor<8xi32>) -> tensor<8xi32> { + %0 = "tf.Reciprocal"(%arg0) : (tensor<8xi32>) -> tensor<8xi32> + return %0: tensor<8xi32> + +// CHECK-LABEL: reciprocal_i32 +// CHECK: %cst = constant dense<1> : tensor<1xi32> +// CHECK: "tfl.div"(%cst, %arg0) {fused_activation_function = "NONE"} : (tensor<1xi32>, tensor<8xi32>) -> tensor<8xi32> +// CHECK: return +} + +func @reciprocal_i64(%arg0: tensor<8xi64>) -> tensor<8xi64> { + %0 = "tf.Reciprocal"(%arg0) : (tensor<8xi64>) -> tensor<8xi64> + return %0: tensor<8xi64> + +// CHECK-LABEL: reciprocal_i64 +// CHECK: %cst = constant dense<1> : tensor<1xi64> +// CHECK: "tfl.div"(%cst, %arg0) {fused_activation_function = "NONE"} : (tensor<1xi64>, tensor<8xi64>) -> tensor<8xi64> +// CHECK: return +} diff --git a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/BUILD b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/BUILD index b6910aa966e..c13df3faafc 100644 --- a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/BUILD +++ b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/BUILD @@ -16,5 +16,6 @@ filegroup( "//tensorflow/compiler/mlir/lite:flatbuffer_to_string", "//tensorflow/compiler/mlir/lite:flatbuffer_translate", "@llvm//:FileCheck", + "@llvm//:not", ], ) diff --git a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_builtin.mlir b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_builtin.mlir index 408fb516dac..284cb207394 100644 --- a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_builtin.mlir +++ b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_builtin.mlir @@ -1,10 +1,10 @@ -// RUN: flatbuffer_translate -mlir-to-tflite-flatbuffer %s -emit-builtin-tflite-ops=false -o - | flatbuffer_to_string - | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 -# CHECK: loc("disable_builtin.mlir":2:1): is a TFLite builtin op but builtin emission is not enabled -# CHECK-NEXT: Verification failed. +// RUN: not flatbuffer_translate -mlir-to-tflite-flatbuffer -emit-builtin-tflite-ops=false %s 2>&1 | FileCheck %s + +// CHECK: 'tfl.add' op is a TFLite builtin op but builtin emission is not enabled func @main(tensor<3x2xi32>) -> tensor<3x2xi32> { ^bb0(%arg0: tensor<3x2xi32>): - %0 = "std.constant" () {name = "Const2", value = dense<10> : tensor} : () -> tensor - %1 = "tf.Add" (%0, %1) {name = "add"} : (tensor, tensor<3x2xi32>) -> tensor<3x2xi32> + %0 = "std.constant"() {name = "Const2", value = dense<10> : tensor} : () -> tensor + %1 = "tfl.add"(%0, %arg0) {fused_activation_function = "NONE", name = "add"} : (tensor, tensor<3x2xi32>) -> tensor<3x2xi32> return %1 : tensor<3x2xi32> } diff --git a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_flex.mlir b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_flex.mlir index a1346582580..e77cd69cbc7 100644 --- a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_flex.mlir +++ b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/disable_flex.mlir @@ -1,7 +1,8 @@ -// RUN: flatbuffer_translate -mlir-to-tflite-flatbuffer %s 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 -// CHECK: error: 'tf.Div' op is neither a custom op nor a flex op -// CHECK: error: failed while converting: 'main' -// CHECK: Ops that can be supported by the flex runtime (enabled via setting the -emit-select-tf-ops flag): Div. +// RUN: not flatbuffer_translate -mlir-to-tflite-flatbuffer %s 2>&1 | FileCheck %s + +// CHECK: error: 'tf.Div' op is neither a custom op nor a flex op +// CHECK: error: failed while converting: 'main' +// CHECK: Ops that can be supported by the flex runtime (enabled via setting the -emit-select-tf-ops flag): Div. func @main(tensor<4xf32>) -> tensor<4xf32> { ^bb0(%arg0: tensor<4xf32>): diff --git a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/numeric_verify.mlir b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/numeric_verify.mlir new file mode 100644 index 00000000000..8b2f6ea8b0e --- /dev/null +++ b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/numeric_verify.mlir @@ -0,0 +1,49 @@ +// RUN: flatbuffer_translate -mlir-to-tflite-flatbuffer %s -o - | flatbuffer_to_string - | FileCheck %s + +// CHECK: { +// CHECK-NEXT: version: 3, +// CHECK-NEXT: operator_codes: [ { +// CHECK-NEXT: builtin_code: CUSTOM, +// CHECK-NEXT: custom_code: "NumericVerify" +// CHECK-NEXT: } ], +// CHECK-NEXT: subgraphs: [ { +// CHECK-NEXT: tensors: [ { +// CHECK-NEXT: shape: [ 4 ], +// CHECK-NEXT: buffer: 1, +// CHECK-NEXT: name: "arg0", +// CHECK-NEXT: quantization: { +// CHECK-EMPTY: +// CHECK-NEXT: } +// CHECK-NEXT: }, { +// CHECK-NEXT: shape: [ 4 ], +// CHECK-NEXT: type: UINT8, +// CHECK-NEXT: buffer: 2, +// CHECK-NEXT: name: "arg1", +// CHECK-NEXT: quantization: { +// CHECK-NEXT: scale: [ 0.1 ], +// CHECK-NEXT: zero_point: [ 0 ] +// CHECK-NEXT: } +// CHECK-NEXT: } ], +// CHECK-NEXT: inputs: [ 0, 1 ], +// CHECK-NEXT: outputs: [ 0 ], +// CHECK-NEXT: operators: [ { +// CHECK-NEXT: inputs: [ 1, 0 ], +// CHECK-NEXT: outputs: [ ], +// CHECK-NEXT: custom_options: [ 205, 204, 204, 61 ] +// CHECK-NEXT: } ], +// CHECK-NEXT: name: "main" +// CHECK-NEXT: } ], +// CHECK-NEXT: description: "MLIR Converted.", +// CHECK-NEXT: buffers: [ { +// CHECK-EMPTY: +// CHECK-NEXT: }, { +// CHECK-EMPTY: +// CHECK-NEXT: }, { +// CHECK-EMPTY: +// CHECK-NEXT: } ] +// CHECK-NEXT:} + +func @main(%arg0: tensor<4xf32>, %arg1: tensor<4x!quant.uniform>) -> tensor<4xf32> { + "tfl.NumericVerify"(%arg1, %arg0) {tolerance = 0.1 : f32} : (tensor<4x!quant.uniform>, tensor<4xf32>) -> () + return %arg0 : tensor<4xf32> +} diff --git a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/unknown-op.mlir b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/unknown-op.mlir index c5bcccc9193..7e9f66baa90 100644 --- a/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/unknown-op.mlir +++ b/tensorflow/compiler/mlir/lite/tests/mlir2flatbuffer/unknown-op.mlir @@ -1,4 +1,4 @@ -// RUN: flatbuffer_translate -mlir-to-tflite-flatbuffer %s -o - 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +// RUN: not flatbuffer_translate -mlir-to-tflite-flatbuffer %s -o - 2>&1 | FileCheck %s func @main(tensor<3x2xi32>) -> tensor<3x2xi32> { ^bb0(%arg0: tensor<3x2xi32>): diff --git a/tensorflow/compiler/mlir/lite/tests/prepare-quantize.mlir b/tensorflow/compiler/mlir/lite/tests/prepare-quantize.mlir index 06068aa8406..cd111176163 100644 --- a/tensorflow/compiler/mlir/lite/tests/prepare-quantize.mlir +++ b/tensorflow/compiler/mlir/lite/tests/prepare-quantize.mlir @@ -418,6 +418,18 @@ func @QuantizeConcatResToAllRequantizeArg(tensor<1x2x!quant.uniform> } +// CHECK-LABEL: RequantizeAlreadyQuantizedModel +func @RequantizeAlreadyQuantizedModel(%arg0: tensor<1x73x73x64x!quant.uniform>, %arg1: tensor<1x147x147x96x!quant.uniform>) -> tensor<1x73x73x160x!quant.uniform> { + %9 = "tfl.max_pool_2d"(%arg1) {filter_height = 3 : i32, filter_width = 3 : i32, fused_activation_function = "NONE", padding = "VALID", stride_h = 2 : i32, stride_w = 2 : i32} : (tensor<1x147x147x96x!quant.uniform>) -> tensor<1x73x73x96x!quant.uniform> + %10 = "tfl.concatenation"(%arg0, %9) {axis = 3 : i32, fused_activation_function = "NONE"} : (tensor<1x73x73x64x!quant.uniform>, tensor<1x73x73x96x!quant.uniform>) -> tensor<1x73x73x160x!quant.uniform> + return %10 : tensor<1x73x73x160x!quant.uniform> + +// CHECK: %0 = "tfl.max_pool_2d"(%arg1) {filter_height = 3 : i32, filter_width = 3 : i32, fused_activation_function = "NONE", padding = "VALID", stride_h = 2 : i32, stride_w = 2 : i32} : (tensor<1x147x147x96x!quant.uniform>) -> tensor<1x73x73x96x!quant.uniform> +// CHECK: %1 = "tfl.quantize"(%0) {qtype = tensor<1x73x73x96x!quant.uniform>} : (tensor<1x73x73x96x!quant.uniform>) -> tensor<1x73x73x96x!quant.uniform> +// CHECK: %2 = "tfl.concatenation"(%arg0, %1) {axis = 3 : i32, fused_activation_function = "NONE"} : (tensor<1x73x73x64x!quant.uniform>, tensor<1x73x73x96x!quant.uniform>) -> tensor<1x73x73x160x!quant.uniform> +// CHECK: return %2 : tensor<1x73x73x160x!quant.uniform> +} + // CHECK-LABEL: QuantizeChain func @QuantizeChain(tensor<1x224x224x3x!quant.uniform>) -> tensor<1x36x16xf32> { ^bb0(%arg0: tensor<1x224x224x3x!quant.uniform>): diff --git a/tensorflow/compiler/mlir/lite/tests/quantize.mlir b/tensorflow/compiler/mlir/lite/tests/quantize.mlir index a68d584b9d5..225123eb3d3 100644 --- a/tensorflow/compiler/mlir/lite/tests/quantize.mlir +++ b/tensorflow/compiler/mlir/lite/tests/quantize.mlir @@ -1,4 +1,5 @@ // RUN: tf-opt %s -tfl-prepare-quantize -tfl-quantize | FileCheck %s +// RUN: tf-opt %s -tfl-prepare-quantize -tfl-quantize -tfl-numeric-verify | FileCheck --check-prefix=DEBUG %s // CHECK-LABEL: QuantizeFloatConst func @QuantizeFloatConst() -> tensor { @@ -48,22 +49,31 @@ func @DequantizeAndQuantize() -> tensor<2x2x!quant.uniform>) -> tensor<1x112x112x32x!quant.uniform> { ^bb0(%arg0: tensor<1x224x224x3x!quant.uniform>): %cst = constant dense<-1.23697901> : tensor<32xf32> %2 = "tfl.dequantize"(%arg0) : (tensor<1x224x224x3x!quant.uniform>) -> tensor<1x224x224x3xf32> - %3 = "tfl.pseudo_qconst"() {qtype = tensor<32x3x3x3x!quant.uniform:f32, 0.021826678373682216:151>>, value = dense<-76> : tensor<32x3x3x3xi8>} : () -> tensor<32x3x3x3x!quant.uniform:f32, 0.021826678373682216:151>> - %4 = "tfl.dequantize"(%3) : (tensor<32x3x3x3x!quant.uniform:f32, 0.021826678373682216:151>>) -> tensor<32x3x3x3xf32> + %w = constant dense<-1.0> : tensor<32x3x3x3xf32> + %3 = "tfl.quantize"(%w) {qtype = tensor<32x3x3x3x!quant.uniform:f32, 0.1>>} : (tensor<32x3x3x3xf32>) -> tensor<32x3x3x3x!quant.uniform:f32, 0.1>> + %4 = "tfl.dequantize"(%3) : (tensor<32x3x3x3x!quant.uniform:f32, 0.1>>) -> tensor<32x3x3x3xf32> %5 = "tfl.conv_2d"(%2, %4, %cst) {dilation_h_factor = 2 : i32, dilation_w_factor = 3 : i32, fused_activation_function = "NONE", padding = "SAME", stride_h = 4 : i32, stride_w = 5 : i32} : (tensor<1x224x224x3xf32>, tensor<32x3x3x3xf32>, tensor<32xf32>) -> tensor<1x112x112x32xf32> %6 = "tfl.quantize"(%5) {qtype = tensor<1x112x112x32x!quant.uniform>} : (tensor<1x112x112x32xf32>) -> tensor<1x112x112x32x!quant.uniform> return %6 : tensor<1x112x112x32x!quant.uniform> -// CHECK: %[[cst0:.*]] = "tfl.pseudo_qconst"() {qtype = tensor<32x!quant.uniform>, value = dense<-7254> : tensor<32xi32>} -// CHECK: %[[cst1:.*]] = "tfl.pseudo_qconst"() {qtype = tensor<32x3x3x3x!quant.uniform:f32, 0.021826678373682216:151>>, value = dense<-76> : tensor<32x3x3x3xi8>} -// CHECK: %[[conv:.*]] = "tfl.conv_2d"(%arg0, %[[cst1]], %[[cst0]]) {dilation_h_factor = 2 : i32, dilation_w_factor = 3 : i32, fused_activation_function = "NONE", padding = "SAME", stride_h = 4 : i32, stride_w = 5 : i32} : (tensor<1x224x224x3x!quant.uniform>, tensor<32x3x3x3x!quant.uniform:f32, 0.021826678373682216:151>>, tensor<32x!quant.uniform>) -> tensor<1x112x112x32x!quant.uniform> +// CHECK: %[[cst0:.*]] = "tfl.pseudo_qconst"() {qtype = tensor<32x!quant.uniform>, value = dense<-1583> : tensor<32xi32>} +// CHECK: %[[cst1:.*]] = "tfl.pseudo_qconst"() {qtype = tensor<32x3x3x3x!quant.uniform:f32, 1.000000e-01>>, value = dense<1> : tensor<32x3x3x3xi8>} +// CHECK: %[[conv:.*]] = "tfl.conv_2d"(%arg0, %[[cst1]], %[[cst0]]) // CHECK: return %[[conv]] : tensor<1x112x112x32x!quant.uniform> -} +// DEBUG: %[[wt:.*]] = constant dense<-1.000000e+00> : tensor<32x3x3x3xf32> +// DEBUG: %[[bias:.*]] = constant dense<-1.23697901> : tensor<32xf32> +// DEBUG: %[[act:.*]] = "tfl.dequantize"(%arg0) : (tensor<1x224x224x3x!quant.uniform>) -> tensor<1x224x224x3xf32> +// DEBUG: %[[f_conv:.*]] = "tfl.conv_2d"(%[[act]], %[[wt]], %[[bias]]) +// DEBUG: %[[q_conv:.*]] = "tfl.conv_2d" +// DEBUG: "tfl.NumericVerify"(%[[q_conv]], %[[f_conv]]) {tolerance = 1.000000e-01 : f32} +// DEBUG: return %[[q_conv]] : tensor<1x112x112x32x!quant.uniform> +} // CHECK-LABEL: QuantizeDepthwiseConv2D func @QuantizeDepthwiseConv2D(tensor<1x224x224x3x!quant.uniform>) -> tensor<1x112x112x32x!quant.uniform> { @@ -212,6 +222,7 @@ func @QuantizeMaxPool2D(tensor<1x6x6x16x!quant.uniform } // CHECK-LABEL: QuantizeSplit +// DEBUG-LABEL: QuantizeSplit func @QuantizeSplit(%arg: tensor<4x!quant.uniform>, %cst: tensor) -> (tensor<2x!quant.uniform>,tensor<2x!quant.uniform>) { %0 = "tfl.dequantize"(%arg) : (tensor<4x!quant.uniform>) -> tensor<4xf32> %1:2 = "tfl.split"(%cst, %0) {num_splits = 2 : i32} : (tensor, tensor<4xf32>) -> (tensor<2xf32>, tensor<2xf32>) @@ -221,6 +232,11 @@ func @QuantizeSplit(%arg: tensor<4x!quant.uniform>, %cst: tensor, tensor<4x!quant.uniform>) // CHECK: return %[[sp]]#0, %[[sp]]#1 + +// DEUBG: %[[f_split:.*]]:2 = "tfl.split" +// DEUBG: %[[q_split:.*]]:2 = "tfl.split" +// DEUBG: "tfl.NumericVerify"(%[[q_split]]#1, %[[f_split]]#1) {tolerance = 1.000000e-01 : f32} +// DEUBG: "tfl.NumericVerify"(%[[q_split]]#0, %[[f_split]]#0) {tolerance = 1.000000e-01 : f32} } // CHECK-LABEL: QuantizeSplitUnusedResults @@ -265,6 +281,7 @@ func @QuantizeMultipleUsers(%arg1: tensor<4x!quant.uniform>) -> (te } // CHECK-LABEL: NotQuantizePow +// DEBUG-LABEL: NotQuantizePow func @NotQuantizePow(%arg0: tensor<4x!quant.uniform>, %arg1: tensor<4x!quant.uniform>) -> (tensor<4x!quant.uniform>) { %1 = "tfl.dequantize"(%arg0) : (tensor<4x!quant.uniform>) -> tensor<4xf32> @@ -279,4 +296,6 @@ func @NotQuantizePow(%arg0: tensor<4x!quant.uniform>, // CHECK-NEXT: %[[pow:.*]] = tfl.pow %[[dq1]], %[[dq2]] // CHECK-NEXT: %[[q:.*]] = "tfl.quantize"(%[[pow]]) // CHECK-NEXT: return %[[q]] + +// DEBUG-NOT: "tfl.NumericVerify" } diff --git a/tensorflow/compiler/mlir/lite/transforms/extract_ophint.cc b/tensorflow/compiler/mlir/lite/transforms/extract_ophint.cc index 63cf4240224..52eb6216e90 100644 --- a/tensorflow/compiler/mlir/lite/transforms/extract_ophint.cc +++ b/tensorflow/compiler/mlir/lite/transforms/extract_ophint.cc @@ -413,13 +413,13 @@ void PreprocessTopoSortGraph( } operation_to_in_degrees->try_emplace(&op, input_ops.size()); for (auto* input_op : input_ops) { - auto preceeding_op_it = operation_to_outputs->find(input_op); - if (preceeding_op_it == operation_to_outputs->end()) { + auto preceding_op_it = operation_to_outputs->find(input_op); + if (preceding_op_it == operation_to_outputs->end()) { auto result = operation_to_outputs->try_emplace( input_op, llvm::DenseSet()); - preceeding_op_it = result.first; + preceding_op_it = result.first; } - preceeding_op_it->second.insert(&op); + preceding_op_it->second.insert(&op); } } } diff --git a/tensorflow/compiler/mlir/lite/transforms/legalize_patterns.td b/tensorflow/compiler/mlir/lite/transforms/legalize_patterns.td index 8ce698d7d30..9e9dfa5874f 100644 --- a/tensorflow/compiler/mlir/lite/transforms/legalize_patterns.td +++ b/tensorflow/compiler/mlir/lite/transforms/legalize_patterns.td @@ -233,6 +233,18 @@ def : Pat<(TF_FakeQuantWithMinMaxVarsOp $inputs, (ConvertToQuantTypeFromAttrs $inputs, $min, $max, $num_bits, $narrow_range)))>; +// TODO(rocky): Not all of the attributes are handled correctly. Make this +// more general if there is a need. +def : Pat<(TF_QuantizeAndDequantizeV2Op $inputs, + (ConstantOp F32ElementsAttr:$min), + (ConstantOp F32ElementsAttr:$max), + $signed_input, $num_bits, $range_given, $round_mode, + $narrow_range, $axis), + (TFL_DequantizeOp + (TFL_QuantizeOp $inputs, + (ConvertToQuantTypeFromAttrs $inputs, $min, $max, + $num_bits, $narrow_range)))>; + def : Pat<(TF_RankOp $input), (TFL_RankOp $input)>; def : Pat<(TF_SquaredDifferenceOp $l, $r), (TFL_SquaredDifferenceOp $l, $r)>; diff --git a/tensorflow/compiler/mlir/lite/transforms/legalize_tf.cc b/tensorflow/compiler/mlir/lite/transforms/legalize_tf.cc index 0512bc98cab..698ba4d4483 100644 --- a/tensorflow/compiler/mlir/lite/transforms/legalize_tf.cc +++ b/tensorflow/compiler/mlir/lite/transforms/legalize_tf.cc @@ -22,6 +22,7 @@ limitations under the License. // constant folding support for the TensorFlow ops. #include +#include #include #include "llvm/ADT/APInt.h" @@ -42,6 +43,13 @@ limitations under the License. #include "tensorflow/compiler/mlir/lite/utils/attribute_utils.h" #include "tensorflow/compiler/mlir/lite/utils/validators.h" #include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.h" +#include "tensorflow/compiler/mlir/tensorflow/utils/mangling_util.h" +#include "tensorflow/compiler/xla/status.h" +#include "tensorflow/compiler/xla/statusor.h" +#include "tensorflow/core/framework/tensor.pb.h" +#include "tensorflow/core/framework/tensor_shape.pb.h" +#include "tensorflow/core/framework/types.pb.h" +#include "tensorflow/core/protobuf/error_codes.pb.h" namespace mlir { namespace TFL { @@ -50,6 +58,9 @@ namespace TFL { // The actual LegalizeTF Pass. namespace { +using xla::Status; +using xla::StatusOr; + // Legalize operations in functions. struct LegalizeTF : public FunctionPass { void runOnFunction() override; @@ -80,6 +91,7 @@ DECL_CONVERT_OP(Split); DECL_CONVERT_OP(SplitV); DECL_CONVERT_OP(StridedSlice); DECL_CONVERT_OP(Unpack); +DECL_CONVERT_OP(Reciprocal); #undef DECL_CONVERT_OP @@ -383,6 +395,103 @@ PatternMatchResult ConvertTFAssertOp::matchAndRewrite( return matchSuccess(); } +StatusOr CreateConstOpWithSingleValue(PatternRewriter* rewriter, + Location loc, + ShapedType shaped_type, + int value) { + Type element_type = shaped_type.getElementType(); + ShapedType ranked_tensor_type = RankedTensorType::get({1}, element_type); + Type type = ranked_tensor_type; + Attribute attr; + switch (element_type.getKind()) { + case mlir::StandardTypes::F16: { + auto floatType = mlir::FloatType::getF16(element_type.getContext()); + auto floatAttr = + mlir::FloatAttr::get(floatType, static_cast(value)); + std::vector floatValues({floatAttr}); + attr = DenseElementsAttr::get(ranked_tensor_type, floatValues); + break; + } + case mlir::StandardTypes::F32: { + attr = DenseElementsAttr::get(ranked_tensor_type, + static_cast(value)); + break; + } + case mlir::StandardTypes::Complex: { + auto etype = element_type.cast().getElementType(); + if (etype.isF32()) { + auto dialect = etype.getContext()->getRegisteredDialect("tf"); + tensorflow::TensorProto repr; + repr.set_dtype(tensorflow::DT_COMPLEX64); + + tensorflow::TensorShapeProto* shape = repr.mutable_tensor_shape(); + shape->set_unknown_rank(false); + shape->add_dim()->set_size(int64_t{1}); + std::string content; + auto complex_value = + std::complex(static_cast(value), 0.0f); + content.assign(reinterpret_cast(&complex_value), + sizeof(complex_value)); + repr.set_tensor_content(content); + std::string mangled = tensorflow::mangling_util::MangleTensor(repr); + + attr = + mlir::OpaqueElementsAttr::get(dialect, ranked_tensor_type, mangled); + break; + } + return Status(tensorflow::error::INVALID_ARGUMENT, "Unsupported type"); + } + case mlir::StandardTypes::Integer: { + const auto& itype = element_type.cast(); + switch (itype.getWidth()) { + case 8: + attr = DenseElementsAttr::get(ranked_tensor_type, + static_cast(value)); + break; + case 16: + attr = DenseElementsAttr::get(ranked_tensor_type, + static_cast(value)); + break; + case 32: + attr = DenseElementsAttr::get(ranked_tensor_type, + static_cast(value)); + break; + case 64: + attr = DenseElementsAttr::get(ranked_tensor_type, + static_cast(value)); + break; + default: + return Status(tensorflow::error::INVALID_ARGUMENT, + "Unsupported type"); + } + break; + } + default: + return Status(tensorflow::error::INVALID_ARGUMENT, "Unsupported type"); + } + return rewriter->create(loc, type, attr); +} + +PatternMatchResult ConvertTFReciprocalOp::matchAndRewrite( + Operation* op, PatternRewriter& rewriter) const { + auto tf_reciprocal_op = cast(op); + + auto status_or_const_op = CreateConstOpWithSingleValue( + &rewriter, op->getLoc(), + tf_reciprocal_op.x()->getType().cast(), 1); + if (!status_or_const_op.ok()) { + return matchFailure(); + } + + StringAttr fused_activation_function = + StringAttr::get("NONE", rewriter.getContext()); + + rewriter.replaceOpWithNewOp(op, status_or_const_op.ValueOrDie(), + tf_reciprocal_op.x(), + fused_activation_function); + return matchSuccess(); +} + void LegalizeTF::runOnFunction() { OwningRewritePatternList patterns; auto* ctx = &getContext(); @@ -390,12 +499,11 @@ void LegalizeTF::runOnFunction() { // Add the generated patterns to the list. populateWithGenerated(ctx, &patterns); - patterns - .insert( - ctx); + patterns.insert(ctx); applyPatternsGreedily(func, patterns); } diff --git a/tensorflow/compiler/mlir/lite/transforms/lower_static_tensor_list.cc b/tensorflow/compiler/mlir/lite/transforms/lower_static_tensor_list.cc index 3f50c3ad1c1..7c02342eedd 100644 --- a/tensorflow/compiler/mlir/lite/transforms/lower_static_tensor_list.cc +++ b/tensorflow/compiler/mlir/lite/transforms/lower_static_tensor_list.cc @@ -71,9 +71,7 @@ class TensorListPatternRewriter : public PatternRewriter { explicit TensorListPatternRewriter(FuncOp fn) : PatternRewriter(fn.getContext()) {} - Operation *createOperation(const OperationState &state) override { - return OpBuilder::createOperation(state); - } + Operation *insert(Operation *op) override { return OpBuilder::insert(op); } }; /// Lower TensorList ops in functions for subsequent legalization. diff --git a/tensorflow/compiler/mlir/lite/transforms/optimize.cc b/tensorflow/compiler/mlir/lite/transforms/optimize.cc index d8697a8c4e0..1313bae97a1 100644 --- a/tensorflow/compiler/mlir/lite/transforms/optimize.cc +++ b/tensorflow/compiler/mlir/lite/transforms/optimize.cc @@ -394,14 +394,14 @@ struct FuseBinaryOpToFollowingAffineOp : public OpRewritePattern { // w * (x ' c) + b => (w ' c) x + b // so we have to update the weight. bool is_mul = llvm::isa(binary_op); - auto new_fitler = + auto new_filter = filter_cst.mapValues(filter_type.getElementType(), [&](APFloat it) { return (is_mul ? it * cst_value : it / cst_value).bitcastToAPInt(); }); // We recreate the constant op in case it is shared by the other ops. This // might increase the model size. auto new_filter_op = rewriter.create( - fc_op.getLoc(), filter->getType(), new_fitler); + fc_op.getLoc(), filter->getType(), new_filter); fc_op.setOperand(0, binary_op->getOperand(0)); if (fc_op.filter() != filter) { // This filter goes through quantize and dequantize ops. Then we just diff --git a/tensorflow/compiler/mlir/lite/transforms/optimize_functional_ops.cc b/tensorflow/compiler/mlir/lite/transforms/optimize_functional_ops.cc index 173785ba5b0..59dc271400e 100644 --- a/tensorflow/compiler/mlir/lite/transforms/optimize_functional_ops.cc +++ b/tensorflow/compiler/mlir/lite/transforms/optimize_functional_ops.cc @@ -132,8 +132,8 @@ class FoldIfOp : public OpRewritePattern { // Erases functions from the given candidates that are not referenced by any of // the ops in the module. -static void EraseDeadFuncs(const FuncSet& candiate_funcs, ModuleOp module) { - if (candiate_funcs.empty()) return; +static void EraseDeadFuncs(const FuncSet& candidate_funcs, ModuleOp module) { + if (candidate_funcs.empty()) return; SymbolTable manager(module); @@ -149,7 +149,7 @@ static void EraseDeadFuncs(const FuncSet& candiate_funcs, ModuleOp module) { } }); - for (FuncOp func : candiate_funcs) { + for (FuncOp func : candidate_funcs) { if (!in_use_funcs.count(func)) manager.erase(func); } } diff --git a/tensorflow/compiler/mlir/lite/transforms/prepare_tf.cc b/tensorflow/compiler/mlir/lite/transforms/prepare_tf.cc index 823efdc3ef5..45248ddc01c 100644 --- a/tensorflow/compiler/mlir/lite/transforms/prepare_tf.cc +++ b/tensorflow/compiler/mlir/lite/transforms/prepare_tf.cc @@ -132,7 +132,7 @@ struct InsertTFLQuantOpsAfterTFFakeQuantOp int quant_dim = -1; if (PerAxis) { - // This is a special case that the quant_dim is the last dimentions. + // This is a special case that the quant_dim is the last dimensions. quant_dim = res->getType().template cast().getRank() - 1; } // Use the min/max from the operands and the num_bits and narrow_range diff --git a/tensorflow/compiler/mlir/lite/transforms/quantize.cc b/tensorflow/compiler/mlir/lite/transforms/quantize.cc index 9d4c935063c..e47e97a60e8 100644 --- a/tensorflow/compiler/mlir/lite/transforms/quantize.cc +++ b/tensorflow/compiler/mlir/lite/transforms/quantize.cc @@ -35,6 +35,26 @@ limitations under the License. #include "tensorflow/compiler/mlir/lite/transforms/passes.h" #include "tensorflow/compiler/mlir/lite/utils/validators.h" +// NOLINTNEXTLINE +static llvm::cl::opt enable_numeric_verify( + "tfl-numeric-verify", llvm::cl::value_desc("bool"), + llvm::cl::desc("Whether verify numericals at runtime."), + llvm::cl::init(false)); + +// NOLINTNEXTLINE +static llvm::cl::opt error_tolerance( + "tfl-error-tolerance", llvm::cl::value_desc("float"), + llvm::cl::desc("Error tolerance for numeric verify. Valid when " + "`-tfl-numeric-verify` is set."), + llvm::cl::init(1e-1f)); + +// NOLINTNEXTLINE +static llvm::cl::opt enable_single_layer_verify( + "tfl-single-layer-verify", llvm::cl::value_desc("bool"), + llvm::cl::desc("Whether verify numericals layer by layer. Valid when " + "`-tfl-numeric-verify` is set."), + llvm::cl::init(false)); + namespace mlir { namespace TFL { @@ -45,9 +65,11 @@ namespace { // Full integer quantization rewrite pattern for TFLite. struct TFLFullQuantization - : public QuantizationPattern { - explicit TFLFullQuantization(MLIRContext* ctx) : BaseType(ctx) {} + : public QuantizationPattern { + explicit TFLFullQuantization(MLIRContext* ctx, bool verify_numeric, + float tolerance, bool verify_single_layer) + : BaseType(ctx, verify_numeric, tolerance, verify_single_layer) {} static bool AllowHybridOperand() { return false; } static bool AllowHybridResult() { return false; } }; @@ -64,7 +86,8 @@ void QuantizePass::runOnFunction() { auto func = getFunction(); auto* ctx = func.getContext(); TFL::populateWithGenerated(ctx, &patterns); - patterns.insert(ctx); + patterns.insert( + ctx, enable_numeric_verify, error_tolerance, enable_single_layer_verify); applyPatternsGreedily(func, patterns); } } // namespace diff --git a/tensorflow/compiler/mlir/lite/utils/lstm_utils.cc b/tensorflow/compiler/mlir/lite/utils/lstm_utils.cc index faf6427cedd..92a8ad49bf4 100644 --- a/tensorflow/compiler/mlir/lite/utils/lstm_utils.cc +++ b/tensorflow/compiler/mlir/lite/utils/lstm_utils.cc @@ -98,7 +98,7 @@ Value* SliceRankedTensor(OpBuilder* builder, Value* input, ArrayRef size_values, mlir::Location location) { // If the size of the tensor to be sliced from the input overflows - // the input tensor's dimenions, return 0-valued tensor of the requested + // the input tensor's dimensions, return 0-valued tensor of the requested // shape. ArrayRef input_shape = GetRankedTensorShape(input); for (int i = 0; i < input_shape.size(); i++) { diff --git a/tensorflow/compiler/mlir/op_or_arg_name_mapper.cc b/tensorflow/compiler/mlir/op_or_arg_name_mapper.cc index cc1afdc6c19..6b8dd7b0c14 100644 --- a/tensorflow/compiler/mlir/op_or_arg_name_mapper.cc +++ b/tensorflow/compiler/mlir/op_or_arg_name_mapper.cc @@ -24,6 +24,7 @@ limitations under the License. #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatVariadic.h" #include "mlir/IR/Location.h" // TF:local_config_mlir #include "mlir/IR/Operation.h" // TF:local_config_mlir #include "mlir/IR/Value.h" // TF:local_config_mlir @@ -71,26 +72,26 @@ llvm::StringRef OpOrArgNameMapper::GetUniqueName(llvm::StringRef prefix) { } } -llvm::StringRef OpOrArgNameMapper::GetUniqueName(OpOrArg op_or_arg) { - auto& name = op_or_arg_to_name_[op_or_arg]; +llvm::StringRef OpOrArgNameMapper::GetUniqueName(OpOrVal op_or_val) { + auto& name = op_or_val_to_name_[op_or_val]; if (!name.empty()) return StringViewToRef(name); // Update the value in the map with unique name. - llvm::StringRef ref = GetUniqueName(GetName(op_or_arg)); + llvm::StringRef ref = GetUniqueName(GetName(op_or_val)); name = StringRefToView(ref); return ref; } -absl::string_view OpOrArgNameMapper::GetUniqueNameView(OpOrArg op_or_arg) { - auto& name = op_or_arg_to_name_[op_or_arg]; +absl::string_view OpOrArgNameMapper::GetUniqueNameView(OpOrVal op_or_val) { + auto& name = op_or_val_to_name_[op_or_val]; if (!name.empty()) return name; // Update the value in the map with unique name. - name = StringRefToView(GetUniqueName(GetName(op_or_arg))); + name = StringRefToView(GetUniqueName(GetName(op_or_val))); return name; } -int OpOrArgNameMapper::InitOpName(OpOrArg op_or_arg, llvm::StringRef name) { +int OpOrArgNameMapper::InitOpName(OpOrVal op_or_val, llvm::StringRef name) { auto it = name_to_count_.try_emplace(name, 0); - op_or_arg_to_name_[op_or_arg] = StringRefToView(it.first->first()); + op_or_val_to_name_[op_or_val] = StringRefToView(it.first->first()); return it.first->second++; } @@ -139,22 +140,31 @@ std::string GetNameFromLoc(mlir::Location loc) { } } // anonymous namespace -std::string OpOrArgLocNameMapper::GetName(OpOrArg op_or_arg) { - if (auto* op = op_or_arg.dyn_cast()) { +std::string OpOrArgLocNameMapper::GetName(OpOrVal op_or_val) { + if (auto* op = op_or_val.dyn_cast()) { auto name_from_loc = GetNameFromLoc(op->getLoc()); if (!name_from_loc.empty()) return name_from_loc; // If the location is none of the expected types, then simply use name // generated using the op type. return op->getName().getStringRef(); } - - if (auto* arg = op_or_arg.dyn_cast()) - return GetNameFromLoc(arg->getLoc()); - + auto* val = op_or_val.dyn_cast(); + auto name_from_loc = GetNameFromLoc(val->getLoc()); + if (!name_from_loc.empty()) return name_from_loc; + // If the location is none of the expected types, then simply use name + // generated using the op type. Follow TF convention and append the result + // index unless 0. + if (auto* result = llvm::dyn_cast(val)) { + if (result->getResultNumber() > 0) + return llvm::formatv("{0}:{1}", + result->getOwner()->getName().getStringRef(), + result->getResultNumber()); + return result->getOwner()->getName().getStringRef(); + } return ""; } -std::string OpOrArgStripNameMapper::GetName(OpOrArg op_or_arg) { +std::string OpOrArgStripNameMapper::GetName(OpOrVal op_or_val) { return llvm::APInt(32, count_++).toString(/*Radix=*/36, /*Signed=*/false); } diff --git a/tensorflow/compiler/mlir/op_or_arg_name_mapper.h b/tensorflow/compiler/mlir/op_or_arg_name_mapper.h index ecd2facd9a2..6517349146e 100644 --- a/tensorflow/compiler/mlir/op_or_arg_name_mapper.h +++ b/tensorflow/compiler/mlir/op_or_arg_name_mapper.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_COMPILER_MLIR_OP_OR_ARG_NAME_MAPPER_H_ -#define TENSORFLOW_COMPILER_MLIR_OP_OR_ARG_NAME_MAPPER_H_ +#ifndef TENSORFLOW_COMPILER_MLIR_op_or_val_NAME_MAPPER_H_ +#define TENSORFLOW_COMPILER_MLIR_op_or_val_NAME_MAPPER_H_ #include @@ -28,28 +28,29 @@ limitations under the License. namespace tensorflow { -// PointerUnion for operation and argument. -using OpOrArg = llvm::PointerUnion; +// PointerUnion for operation and value. +// TODO(jpienaar): Rename the files. +using OpOrVal = llvm::PointerUnion; -// Mapper from operation or argument to name. +// Mapper from operation or value to name. class OpOrArgNameMapper { public: // Returns unique name for the given prefix. llvm::StringRef GetUniqueName(llvm::StringRef prefix); - // Returns unique name for the operation or argument. - llvm::StringRef GetUniqueName(OpOrArg op_or_arg); + // Returns unique name for the operation or value. + llvm::StringRef GetUniqueName(OpOrVal op_or_val); - // Returns unique name as a string_view for the operation or argument. - absl::string_view GetUniqueNameView(OpOrArg op_or_arg); + // Returns unique name as a string_view for the operation or value. + absl::string_view GetUniqueNameView(OpOrVal op_or_val); - // Initializes operation or argument to map to name. Returns number of - // operations or arguments already named 'name' which should be 0 else + // Initializes operation or value to map to name. Returns number of + // operations or value already named 'name' which should be 0 else // GetUniqueName could return the same names for different operations or - // arguments. + // values. // Note: Its up to the caller to decide the behavior when assigning two - // operations or arguments to the same name. - int InitOpName(OpOrArg op_or_arg, llvm::StringRef name); + // operations or values to the same name. + int InitOpName(OpOrVal op_or_val, llvm::StringRef name); virtual ~OpOrArgNameMapper(); @@ -59,35 +60,35 @@ class OpOrArgNameMapper { virtual bool IsUnique(llvm::StringRef name); // Returns a constant view of the underlying map. - const llvm::DenseMap& GetMap() const { - return op_or_arg_to_name_; + const llvm::DenseMap& GetMap() const { + return op_or_val_to_name_; } private: - // Returns name from the location of the operation or argument. - virtual std::string GetName(OpOrArg op_or_arg) = 0; + // Returns name from the location of the operation or value. + virtual std::string GetName(OpOrVal op_or_val) = 0; // Maps string name to count. This map is used to help keep track of unique - // names for operations or arguments. + // names for operations or values. llvm::StringMap name_to_count_; - // Maps operation or argument to name. Value in map is a view of the string + // Maps operation or values to name. Value in map is a view of the string // name in `name_to_count_`. Names in `name_to_count_` are never removed. - llvm::DenseMap op_or_arg_to_name_; + llvm::DenseMap op_or_val_to_name_; }; -// OpOrArgNameMapper that returns, for operations or arguments not initialized +// OpOrArgNameMapper that returns, for operations or values not initialized // to a specific name, a name based on the location of the operation or -// argument. +// value. class OpOrArgLocNameMapper : public OpOrArgNameMapper { private: - std::string GetName(OpOrArg op_or_arg) override; + std::string GetName(OpOrVal op_or_val) override; }; -// OpOrArgNameMapper that returns, for operations or arguments not initialized +// OpOrArgNameMapper that returns, for operations or values not initialized // to a specific name, a short name. class OpOrArgStripNameMapper : public OpOrArgNameMapper { private: - std::string GetName(OpOrArg op_or_arg) override; + std::string GetName(OpOrVal op_or_val) override; // Number of ops mapped. int count_ = 0; @@ -95,4 +96,4 @@ class OpOrArgStripNameMapper : public OpOrArgNameMapper { } // namespace tensorflow -#endif // TENSORFLOW_COMPILER_MLIR_OP_OR_ARG_NAME_MAPPER_H_ +#endif // TENSORFLOW_COMPILER_MLIR_op_or_val_NAME_MAPPER_H_ diff --git a/tensorflow/compiler/mlir/tensorflow/BUILD b/tensorflow/compiler/mlir/tensorflow/BUILD index 5f93210f06e..64905ddec40 100644 --- a/tensorflow/compiler/mlir/tensorflow/BUILD +++ b/tensorflow/compiler/mlir/tensorflow/BUILD @@ -11,7 +11,6 @@ package_group( includes = ["@local_config_mlir//:subpackages"], packages = [ "//tensorflow/compiler/...", - "//tensorflow/core/tfrt_delegate/...", "//tensorflow/python/...", ], ) @@ -176,6 +175,7 @@ cc_library( "//tensorflow/compiler/mlir/lite:validators", "//tensorflow/core:framework", "//tensorflow/core:lib", + "//tensorflow/core/platform:logging", "@llvm//:support", "@local_config_mlir//:Analysis", "@local_config_mlir//:CallOpInterfacesIncGen", @@ -252,6 +252,7 @@ cc_library( "transforms/sink_constant.cc", "transforms/test_side_effect_analysis.cc", "transforms/tpu_cluster_formation.cc", + "transforms/tpu_dynamic_padding_mapper.cc", "transforms/tpu_merge_variables_with_execute.cc", "transforms/tpu_rewrite_pass.cc", "translate/breakup-islands.cc", @@ -921,6 +922,7 @@ cc_library( ":lower_tf_inc_gen", ":tensorflow", "//tensorflow/core:framework", + "@llvm//:support", "@local_config_mlir//:IR", ], alwayslink = 1, diff --git a/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.cc b/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.cc index 898393479b0..36a2560b7c8 100644 --- a/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.cc +++ b/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.cc @@ -30,11 +30,14 @@ limitations under the License. #include "mlir/IR/Block.h" // TF:local_config_mlir #include "mlir/IR/Builders.h" // TF:local_config_mlir #include "mlir/IR/Location.h" // TF:local_config_mlir +#include "mlir/IR/Module.h" // TF:local_config_mlir #include "mlir/IR/Operation.h" // TF:local_config_mlir #include "mlir/IR/StandardTypes.h" // TF:local_config_mlir +#include "mlir/IR/Value.h" // TF:local_config_mlir #include "mlir/Support/LLVM.h" // TF:local_config_mlir #include "mlir/Support/LogicalResult.h" // TF:local_config_mlir #include "tensorflow/compiler/mlir/tensorflow/ir/tf_device.h" +#include "tensorflow/compiler/mlir/tensorflow/ir/tf_executor.h" #include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.h" #include "tensorflow/compiler/mlir/tensorflow/ir/tf_types.h" #include "tensorflow/compiler/tf2xla/resource_operation_table.h" @@ -75,6 +78,37 @@ int64_t GetOrCreateIdForVarHandle(TF::VarHandleOp handle, int64_t* next_id, return emplace_res.first->second; } +// If the return value for `func_op` at `return_index` is a pass-through of an +// argument of this function, returns the argument index; otherwise, returns -1. +int64_t FindPassthroughArgumentForReturnValue(int64_t return_index, + FuncOp func_op) { + auto value = + func_op.getBody().front().getTerminator()->getOperand(return_index); + assert(mlir::getElementTypeOrSelf(value->getType()).isa()); + int64_t arg_index = -1; + auto try_parse_arg_index = [&arg_index](Value* v) { + auto resource_arg = llvm::dyn_cast(v); + if (resource_arg) arg_index = resource_arg->getArgNumber(); + return arg_index; + }; + while (try_parse_arg_index(value) == -1) { + auto op = value->getDefiningOp(); + assert(op); + int64_t res_num = llvm::dyn_cast(value)->getResultNumber(); + if (auto graph = llvm::dyn_cast(op)) { + value = graph.GetFetch().getOperand(res_num); + } else if (auto island = llvm::dyn_cast(op)) { + value = island.GetYield().getOperand(res_num); + } else if (llvm::isa(op) || + llvm::isa(op)) { + value = op->getOperand(res_num); + } else { + return -1; + } + } + return arg_index; +} + } // namespace ResourceAliasAnalysis::ResourceAliasAnalysis(Operation* op) { @@ -108,7 +142,8 @@ void ResourceAliasAnalysis::AnalyzeFunction(FuncOp func_op) { result_ids.insert(operand_it->getSecond().begin(), operand_it->getSecond().end()); }; - // TODO(yuanzx): Consider control-flow ops. + auto module = func_op.getParentOfType(); + func_op.walk([&](Operation* op) { if (auto var_handle = llvm::dyn_cast(op)) { resource_value_to_ids_[var_handle.resource()].insert( @@ -122,7 +157,7 @@ void ResourceAliasAnalysis::AnalyzeFunction(FuncOp func_op) { std::get<1>(operand_and_result)); } } else if (auto replicate = llvm::dyn_cast(op)) { - // The nested block for RepliateOp is handled separately in side-effect + // The nested block for ReplicateOp is handled separately in side-effect // analysis. Inside that block, we can still treat its block arguments as // different resources. for (auto arg : replicate.GetBody().getArguments()) { @@ -131,6 +166,49 @@ void ResourceAliasAnalysis::AnalyzeFunction(FuncOp func_op) { resource_value_to_ids_[arg].insert(next_unique_id++); } } + } else if (auto while_op = llvm::dyn_cast(op)) { + auto body = llvm::cast(module.lookupSymbol(while_op.body())); + // If a result is a passthrough of the body input, use the corresponding + // operand's resource IDs. + for (auto result : llvm::enumerate(while_op.getResults())) { + if (!mlir::getElementTypeOrSelf(result.value()->getType()) + .isa()) { + continue; + } + int64_t passthrough_operand = + FindPassthroughArgumentForReturnValue(result.index(), body); + if (passthrough_operand >= 0) { + forward_input_to_output(while_op.getOperand(passthrough_operand), + result.value()); + } else { + resource_value_to_ids_[result.value()].insert(kUnknownResourceId); + } + } + } else if (auto if_op = llvm::dyn_cast(op)) { + auto then_branch = + llvm::cast(module.lookupSymbol(if_op.then_branch())); + auto else_branch = + llvm::cast(module.lookupSymbol(if_op.else_branch())); + // If a result is a passthrough of both branches' inputs, merge the + // resource IDs of corresponding operands for the two inputs. + for (auto result : llvm::enumerate(if_op.getResults())) { + if (!mlir::getElementTypeOrSelf(result.value()->getType()) + .isa()) { + continue; + } + int64_t passthrough_then_arg = + FindPassthroughArgumentForReturnValue(result.index(), then_branch); + int64_t passthrough_else_arg = + FindPassthroughArgumentForReturnValue(result.index(), else_branch); + if (passthrough_then_arg >= 0 && passthrough_else_arg >= 0) { + forward_input_to_output(if_op.getOperand(passthrough_then_arg + 1), + result.value()); + forward_input_to_output(if_op.getOperand(passthrough_else_arg + 1), + result.value()); + } else { + resource_value_to_ids_[result.value()].insert(kUnknownResourceId); + } + } } else { for (auto result : op->getResults()) { if (!mlir::getElementTypeOrSelf(result->getType()) @@ -223,6 +301,18 @@ bool OpIsDeclaration(Operation* op, !FindAccessedResources(op, alias_analysis).empty()); } +// Returns if `op` is know to not have any side effect. +bool OpIsKnownToHaveNoSideEffect(Operation* op) { + if (op->hasNoSideEffect()) return true; + if (auto if_op = llvm::dyn_cast(op)) { + return if_op.is_stateless(); + } + if (auto while_op = llvm::dyn_cast(op)) { + return while_op.is_stateless(); + } + return false; +} + } // namespace void SideEffectAnalysis::TrackAccess(int64_t resource_id, Operation* op, @@ -242,11 +332,15 @@ void SideEffectAnalysis::TrackAccess(int64_t resource_id, Operation* op, auto& info = per_resource_access_info_[resource_id]; if (read_only) { info.reads_since_last_write.push_back(op); - // Resource read must have carried control dependencies of unknown write. - info.tracked_last_unknown_write = true; + // Resource read must have carried control dependencies of unknown write. It + // can only avoid adding control edges (from uknown accesses) for a later + // write, but not for a later read, because this read can be reordered with + // a later read. + info.tracked_last_unknown_write_for_write = true; } else { // Resource write must have carried control dependencies of unknown access. - info.tracked_last_unknown_write = true; + info.tracked_last_unknown_write_for_read = true; + info.tracked_last_unknown_write_for_write = true; info.tracked_last_unknown_read = true; info.last_write = op; info.reads_since_last_write.clear(); @@ -305,7 +399,7 @@ void SideEffectAnalysis::AnalyzeRegion( // region, and tracking resource accesses in per_resource_access_info_. // Returns whether an access to `resource` can skip control edges from - // prevoius accesses to unknown resources, due to that earlier accesses to + // previous accesses to unknown resources, due to that earlier accesses to // `resource` already indirectly tracked previous accesses to uknown // resources. `read_only` specifies the type of access of the current op being // considered. @@ -318,8 +412,8 @@ void SideEffectAnalysis::AnalyzeRegion( unknown_it == per_resource_access_info_.end() || unknown_it->getSecond().reads_since_last_write.empty(); return read_only - ? it->second.tracked_last_unknown_write - : it->second.tracked_last_unknown_write && + ? it->second.tracked_last_unknown_write_for_read + : it->second.tracked_last_unknown_write_for_write && (it->second.tracked_last_unknown_read || no_unknown_read); }; @@ -340,7 +434,7 @@ void SideEffectAnalysis::AnalyzeRegion( if (OpIsDeclaration(&op, alias_analysis)) continue; auto resource_op_info = GetResourceInfoForOp(&op); - if (!resource_op_info && op.hasNoSideEffect()) continue; + if (!resource_op_info && OpIsKnownToHaveNoSideEffect(&op)) continue; llvm::SmallDenseSet resources = resource_op_info ? FindAccessedResources(&op, alias_analysis) diff --git a/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.h b/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.h index 3d65217db27..98df0941340 100644 --- a/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.h +++ b/tensorflow/compiler/mlir/tensorflow/analysis/side_effect_analysis.h @@ -105,7 +105,7 @@ class SideEffectAnalysis { void ConsumeChildAnalyses( llvm::SmallVector&& children); - // Updates control_predecessors_ for `op` that is being visted, on the given + // Updates control_predecessors_ for `op` that is being visited, on the given // `resource_id`. void AddPredecessorsForAccess(int64_t resource_id, Operation* op, bool read_only); @@ -124,17 +124,22 @@ class SideEffectAnalysis { sorted_control_successors_; // Internal per-resource data structure when we build the dependencies. - struct PerResourceAcessInfo { + struct PerResourceAccessInfo { // Last op that writes the resource before the current op being analyzed. Operation* last_write = nullptr; // Read ops since last_write before the current op being analyzed. llvm::SmallVector reads_since_last_write; // Whether previous accesses of this resource already tracked last unknown - // read/write. + // read for the current access being analyzed. bool tracked_last_unknown_read = false; - bool tracked_last_unknown_write = false; + // Whether previous accesses of this resource already tracked last unknown + // write for a the current read being analyzed. + bool tracked_last_unknown_write_for_read = false; + // Whether previous accesses of this resource already tracked last unknown + // write for a the current write being analyzed. + bool tracked_last_unknown_write_for_write = false; }; - llvm::SmallDenseMap + llvm::SmallDenseMap per_resource_access_info_; }; diff --git a/tensorflow/compiler/mlir/tensorflow/ir/tf_generated_ops.td b/tensorflow/compiler/mlir/tensorflow/ir/tf_generated_ops.td index 5b5c028c89d..e1f0a0e53e7 100644 --- a/tensorflow/compiler/mlir/tensorflow/ir/tf_generated_ops.td +++ b/tensorflow/compiler/mlir/tensorflow/ir/tf_generated_ops.td @@ -1269,6 +1269,93 @@ def TF_DivNoNanOp : TF_Op<"DivNoNan", [Broadcastable, NoSideEffect]>, TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; } +def TF_DynamicStitchOp : TF_Op<"DynamicStitch", [NoSideEffect, SameVariadicOperandSize]> { + let summary = [{ +Interleave the values from the `data` tensors into a single tensor. + }]; + + let description = [{ +Builds a merged tensor such that + +```python + merged[indices[m][i, ..., j], ...] = data[m][i, ..., j, ...] +``` + +For example, if each `indices[m]` is scalar or vector, we have + +```python + # Scalar indices: + merged[indices[m], ...] = data[m][...] + + # Vector indices: + merged[indices[m][i], ...] = data[m][i, ...] +``` + +Each `data[i].shape` must start with the corresponding `indices[i].shape`, +and the rest of `data[i].shape` must be constant w.r.t. `i`. That is, we +must have `data[i].shape = indices[i].shape + constant`. In terms of this +`constant`, the output shape is + + merged.shape = [max(indices)] + constant + +Values are merged in order, so if an index appears in both `indices[m][i]` and +`indices[n][j]` for `(m,i) < (n,j)` the slice `data[n][j]` will appear in the +merged result. If you do not need this guarantee, ParallelDynamicStitch might +perform better on some devices. + +For example: + +```python + indices[0] = 6 + indices[1] = [4, 1] + indices[2] = [[5, 2], [0, 3]] + data[0] = [61, 62] + data[1] = [[41, 42], [11, 12]] + data[2] = [[[51, 52], [21, 22]], [[1, 2], [31, 32]]] + merged = [[1, 2], [11, 12], [21, 22], [31, 32], [41, 42], + [51, 52], [61, 62]] +``` + +This method can be used to merge partitions created by `dynamic_partition` +as illustrated on the following example: + +```python + # Apply function (increments x_i) on elements for which a certain condition + # apply (x_i != -1 in this example). + x=tf.constant([0.1, -1., 5.2, 4.3, -1., 7.4]) + condition_mask=tf.not_equal(x,tf.constant(-1.)) + partitioned_data = tf.dynamic_partition( + x, tf.cast(condition_mask, tf.int32) , 2) + partitioned_data[1] = partitioned_data[1] + 1.0 + condition_indices = tf.dynamic_partition( + tf.range(tf.shape(x)[0]), tf.cast(condition_mask, tf.int32) , 2) + x = tf.dynamic_stitch(condition_indices, partitioned_data) + # Here x=[1.1, -1., 6.2, 5.3, -1, 8.4], the -1. values remain + # unchanged. +``` + +
+ +
+ }]; + + let arguments = (ins + Variadic:$indices, + Variadic:$data + ); + + let results = (outs + TF_Tensor:$merged + ); + + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<1>; + TF_DerivedOperandSizeAttr N = TF_DerivedOperandSizeAttr<0>; + + let verifier = [{ + return Verify(*this); + }]; +} + def TF_EinsumOp : TF_Op<"Einsum", [NoSideEffect]> { let summary = [{ Tensor contraction according to Einstein summation convention. @@ -1317,7 +1404,7 @@ Operations are applied to the input(s) according to the following rules: Considering the batch matrix multiplication equation again (`bij,bjk->bik`), the contracted axis label is `j`. - (e) Expand Diagonal: If the output subcripts contain repeated (explicit) axis + (e) Expand Diagonal: If the output subscripts contain repeated (explicit) axis labels, the opposite operation of (a) is applied. For example, in the equation `i->iii`, and input shape `[3]`, the output of shape `[3, 3, 3]` are all zeros, except for the (generalized) diagonal which is populated @@ -1325,7 +1412,7 @@ Operations are applied to the input(s) according to the following rules: Note: This operation is not supported by `np.einsum` or `tf.einsum`; it is provided to enable computing the symbolic gradient of `tf.einsum`. -The output subcripts must contain only labels appearing in at least one of the +The output subscripts must contain only labels appearing in at least one of the input subscripts. Furthermore, all dimensions mapping to the same axis label must be equal. @@ -1337,7 +1424,7 @@ according to standard NumPy broadcasting The broadcasted dimensions are placed in the corresponding location of the ellipsis in the output subscript. If the broadcasted dimensions are non-empty -and the output subcripts do not contain ellipsis, then an InvalidArgument error +and the output subscripts do not contain ellipsis, then an InvalidArgument error is raised. @compatibility(numpy) @@ -2054,6 +2141,10 @@ See also `tf.batch_gather` and `tf.gather_nd`. TF_DerivedOperandTypeAttr Tindices = TF_DerivedOperandTypeAttr<1>; TF_DerivedOperandTypeAttr Tparams = TF_DerivedOperandTypeAttr<0>; TF_DerivedOperandTypeAttr Taxis = TF_DerivedOperandTypeAttr<2>; + + let verifier = [{ + return Verify(*this); + }]; } def TF_GreaterOp : TF_Op<"Greater", [Broadcastable, NoSideEffect]>, @@ -2368,6 +2459,55 @@ def TF_LeakyReluOp : TF_Op<"LeakyRelu", [NoSideEffect, SameOperandsAndResultType let hasFolder = 1; } +def TF_LeftShiftOp : TF_Op<"LeftShift", [Broadcastable, NoSideEffect]>, + WithBroadcastableBinOpBuilder { + let summary = "Elementwise computes the bitwise left-shift of `x` and `y`."; + + let description = [{ +If `y` is negative, or greater than or equal to the width of `x` in bits the +result is implementation defined. + +Example: + +```python +import tensorflow as tf +from tensorflow.python.ops import bitwise_ops +import numpy as np +dtype_list = [tf.int8, tf.int16, tf.int32, tf.int64] + +for dtype in dtype_list: + lhs = tf.constant([-1, -5, -3, -14], dtype=dtype) + rhs = tf.constant([5, 0, 7, 11], dtype=dtype) + + left_shift_result = bitwise_ops.left_shift(lhs, rhs) + + print(left_shift_result) + +# This will print: +# tf.Tensor([ -32 -5 -128 0], shape=(4,), dtype=int8) +# tf.Tensor([ -32 -5 -384 -28672], shape=(4,), dtype=int16) +# tf.Tensor([ -32 -5 -384 -28672], shape=(4,), dtype=int32) +# tf.Tensor([ -32 -5 -384 -28672], shape=(4,), dtype=int64) + +lhs = np.array([-2, 64, 101, 32], dtype=np.int8) +rhs = np.array([-1, -5, -3, -14], dtype=np.int8) +bitwise_ops.left_shift(lhs, rhs) +# +``` + }]; + + let arguments = (ins + TF_IntTensor:$x, + TF_IntTensor:$y + ); + + let results = (outs + TF_IntTensor:$z + ); + + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; +} + def TF_LessOp : TF_Op<"Less", [Broadcastable, NoSideEffect]>, WithBroadcastableCmpOpBuilder { let summary = "Returns the truth value of (x < y) element-wise."; @@ -4366,10 +4506,10 @@ def TF_ResourceApplyAdamOp : TF_Op<"ResourceApplyAdam", []> { let summary = "Update '*var' according to the Adam algorithm."; let description = [{ -$$lr_t := \text{learning\_rate} * \sqrt{1 - beta_2^t} / (1 - beta_1^t)$$ -$$m_t := beta_1 * m_{t-1} + (1 - beta_1) * g$$ -$$v_t := beta_2 * v_{t-1} + (1 - beta_2) * g * g$$ -$$variable := variable - lr_t * m_t / (\sqrt{v_t} + \epsilon)$$ +$$\text{lr}_t := \mathrm{learning_rate} * \sqrt{1 - \beta_2^t} / (1 - \beta_1^t)$$ +$$m_t := \beta_1 * m_{t-1} + (1 - \beta_1) * g$$ +$$v_t := \beta_2 * v_{t-1} + (1 - \beta_2) * g * g$$ +$$\text{variable} := \text{variable} - \text{lr}_t * m_t / (\sqrt{v_t} + \epsilon)$$ }]; let arguments = (ins @@ -4413,9 +4553,7 @@ def TF_ResourceApplyGradientDescentOp : TF_Op<"ResourceApplyGradientDescent", [] } def TF_ResourceApplyKerasMomentumOp : TF_Op<"ResourceApplyKerasMomentum", []> { - let summary = [{ -Update '*var' according to the momentum scheme. - }]; + let summary = "Update '*var' according to the momentum scheme."; let description = [{ Set use_nesterov = True if you want to use Nesterov momentum. @@ -4581,6 +4719,58 @@ reverse(t, dims) ==> [[[[8, 9, 10, 11], TF_DerivedOperandTypeAttr Tidx = TF_DerivedOperandTypeAttr<1>; } +def TF_RightShiftOp : TF_Op<"RightShift", [Broadcastable, NoSideEffect]>, + WithBroadcastableBinOpBuilder { + let summary = "Elementwise computes the bitwise right-shift of `x` and `y`."; + + let description = [{ +Performs a logical shift for unsigned integer types, and an arithmetic shift +for signed integer types. + +If `y` is negative, or greater than or equal to than the width of `x` in bits +the result is implementation defined. + +Example: + +```python +import tensorflow as tf +from tensorflow.python.ops import bitwise_ops +import numpy as np +dtype_list = [tf.int8, tf.int16, tf.int32, tf.int64] + +for dtype in dtype_list: + lhs = tf.constant([-1, -5, -3, -14], dtype=dtype) + rhs = tf.constant([5, 0, 7, 11], dtype=dtype) + + right_shift_result = bitwise_ops.right_shift(lhs, rhs) + + print(right_shift_result) + +# This will print: +# tf.Tensor([-1 -5 -1 -1], shape=(4,), dtype=int8) +# tf.Tensor([-1 -5 -1 -1], shape=(4,), dtype=int16) +# tf.Tensor([-1 -5 -1 -1], shape=(4,), dtype=int32) +# tf.Tensor([-1 -5 -1 -1], shape=(4,), dtype=int64) + +lhs = np.array([-2, 64, 101, 32], dtype=np.int8) +rhs = np.array([-1, -5, -3, -14], dtype=np.int8) +bitwise_ops.right_shift(lhs, rhs) +# +``` + }]; + + let arguments = (ins + TF_IntTensor:$x, + TF_IntTensor:$y + ); + + let results = (outs + TF_IntTensor:$z + ); + + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; +} + def TF_RoundOp : TF_Op<"Round", [NoSideEffect, SameOperandsAndResultType]> { let summary = [{ Rounds the values of a tensor to the nearest integer, element-wise. @@ -5122,6 +5312,34 @@ x = [[[[1, 2, 3, 4], TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; } +def TF_SparseSoftmaxCrossEntropyWithLogitsOp : TF_Op<"SparseSoftmaxCrossEntropyWithLogits", [NoSideEffect]> { + let summary = [{ +Computes softmax cross entropy cost and gradients to backpropagate. + }]; + + let description = [{ +Unlike `SoftmaxCrossEntropyWithLogits`, this operation does not accept +a matrix of label probabilities, but rather a single label per row +of features. This label is considered to have probability 1.0 for the +given row. + +Inputs are the logits, not probabilities. + }]; + + let arguments = (ins + TF_FpTensor:$features, + TF_I32OrI64Tensor:$labels + ); + + let results = (outs + TF_FpTensor:$loss, + TF_FpTensor:$backprop + ); + + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; + TF_DerivedOperandTypeAttr Tlabels = TF_DerivedOperandTypeAttr<1>; +} + def TF_SparseToDenseOp : TF_Op<"SparseToDense", [NoSideEffect]> { let summary = "Converts a sparse representation into a dense tensor."; @@ -5455,8 +5673,67 @@ receive 0, 0, and 1, respectively. The appropriate bits in `begin_mask` and TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; TF_DerivedOperandTypeAttr Index = TF_DerivedOperandTypeAttr<1>; - let verifier = [{ - return Verify(*this); + let verifier = [{ return VerifyStridedSliceBase(*this); }]; + + let extraClassDeclaration = [{ + // If sliced shape is able to be deduced, returns true, updates + // `begin_indices`, `end_indices`, and `strides` with their canonical + // values, respectively. + bool GetSlicedBoundRanges( + ::llvm::ArrayRef shape, + ::llvm::SmallVectorImpl *begin_indices, + ::llvm::SmallVectorImpl *end_indices, + ::llvm::SmallVectorImpl *strides); + }]; +} + +def TF_StridedSliceGradOp : TF_Op<"StridedSliceGrad", [NoSideEffect]> { + let summary = "Returns the gradient of `StridedSlice`."; + + let description = [{ +Since `StridedSlice` cuts out pieces of its `input` which is size +`shape`, its gradient will have the same shape (which is passed here +as `shape`). The gradient will be zero in any element that the slice +does not select. + +Arguments are the same as StridedSliceGrad with the exception that +`dy` is the input gradient to be propagated and `shape` is the +shape of `StridedSlice`'s `input`. + }]; + + let arguments = (ins + TF_I32OrI64Tensor:$shape, + TF_I32OrI64Tensor:$begin, + TF_I32OrI64Tensor:$end, + TF_I32OrI64Tensor:$strides, + TF_Tensor:$dy, + + DefaultValuedAttr:$begin_mask, + DefaultValuedAttr:$end_mask, + DefaultValuedAttr:$ellipsis_mask, + DefaultValuedAttr:$new_axis_mask, + DefaultValuedAttr:$shrink_axis_mask + ); + + let results = (outs + TF_Tensor:$output + ); + + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<4>; + TF_DerivedOperandTypeAttr Index = TF_DerivedOperandTypeAttr<0>; + + let verifier = [{ return Verify(*this); }]; + + let extraClassDeclaration = [{ + // If sliced shape is able to be deduced, returns true, updates `shape` + // with the final shape after performing StridedSlice, and updates + // `begin_indices`, `end_indices`, and `strides` with their canonical + // values, respectively. + bool GetSlicedShapeAndBoundRanges( + ::llvm::SmallVectorImpl *shape, + ::llvm::SmallVectorImpl *begin_indices, + ::llvm::SmallVectorImpl *end_indices, + ::llvm::SmallVectorImpl *strides); }]; } @@ -5986,7 +6263,7 @@ def TF_UniqueOp : TF_Op<"Unique", [NoSideEffect]> { let description = [{ This operation returns a tensor `y` containing all of the unique elements of `x` sorted in the same order that they occur in `x`; `x` does not need to be sorted. -This operation also returns a tensor `idx` the same size as `x` that contains +This operation also returns a tensor `idx` the same size as `x` that contains the index of each value of `x` in the unique output `y`. In other words: `y[idx[i]] = x[i] for i in [0, 1,...,rank(x) - 1]` @@ -6057,6 +6334,205 @@ This is the opposite of `pack`. let verifier = [{ return Verify(*this); }]; } +def TF_UnsortedSegmentMaxOp : TF_Op<"UnsortedSegmentMax", [NoSideEffect]> { + let summary = "Computes the maximum along segments of a tensor."; + + let description = [{ +Read +[the section on segmentation](https://tensorflow.org/api_docs/python/tf/math#Segmentation) +for an explanation of segments. + +This operator is similar to the unsorted segment sum operator found +[(here)](../../../api_docs/python/math_ops.md#UnsortedSegmentSum). +Instead of computing the sum over segments, it computes the maximum such that: + +\\(output_i = \max_{j...} data[j...]\\) where max is over tuples `j...` such +that `segment_ids[j...] == i`. + +If the maximum is empty for a given segment ID `i`, it outputs the smallest +possible value for the specific numeric type, +`output[i] = numeric_limits::lowest()`. + +If the given segment ID `i` is negative, then the corresponding value is +dropped, and will not be included in the result. + +
+ +
+ +For example: + +``` python +c = tf.constant([[1,2,3,4], [5,6,7,8], [4,3,2,1]]) +tf.unsorted_segment_max(c, tf.constant([0, 1, 0]), num_segments=2) +# ==> [[ 4, 3, 3, 4], +# [5, 6, 7, 8]] +``` + }]; + + let arguments = (ins + TF_IntOrFpTensor:$data, + TF_I32OrI64Tensor:$segment_ids, + TF_I32OrI64Tensor:$num_segments + ); + + let results = (outs + TF_IntOrFpTensor:$output + ); + + TF_DerivedOperandTypeAttr Tindices = TF_DerivedOperandTypeAttr<1>; + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; + TF_DerivedOperandTypeAttr Tnumsegments = TF_DerivedOperandTypeAttr<2>; + + let verifier = [{ return VerifyUnsortedSegmentReduction(*this); }]; +} + +def TF_UnsortedSegmentMinOp : TF_Op<"UnsortedSegmentMin", [NoSideEffect]> { + let summary = "Computes the minimum along segments of a tensor."; + + let description = [{ +Read +[the section on segmentation](https://tensorflow.org/api_docs/python/tf/math#Segmentation) +for an explanation of segments. + +This operator is similar to the unsorted segment sum operator found +[(here)](../../../api_docs/python/math_ops.md#UnsortedSegmentSum). +Instead of computing the sum over segments, it computes the minimum such that: + +\\(output_i = \min_{j...} data_[j...]\\) where min is over tuples `j...` such +that `segment_ids[j...] == i`. + +If the minimum is empty for a given segment ID `i`, it outputs the largest +possible value for the specific numeric type, +`output[i] = numeric_limits::max()`. + +For example: + +``` python +c = tf.constant([[1,2,3,4], [5,6,7,8], [4,3,2,1]]) +tf.unsorted_segment_min(c, tf.constant([0, 1, 0]), num_segments=2) +# ==> [[ 1, 2, 2, 1], +# [5, 6, 7, 8]] +``` + +If the given segment ID `i` is negative, then the corresponding value is +dropped, and will not be included in the result. + }]; + + let arguments = (ins + TF_IntOrFpTensor:$data, + TF_I32OrI64Tensor:$segment_ids, + TF_I32OrI64Tensor:$num_segments + ); + + let results = (outs + TF_IntOrFpTensor:$output + ); + + TF_DerivedOperandTypeAttr Tindices = TF_DerivedOperandTypeAttr<1>; + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; + TF_DerivedOperandTypeAttr Tnumsegments = TF_DerivedOperandTypeAttr<2>; + + let verifier = [{ return VerifyUnsortedSegmentReduction(*this); }]; +} + +def TF_UnsortedSegmentProdOp : TF_Op<"UnsortedSegmentProd", [NoSideEffect]> { + let summary = "Computes the product along segments of a tensor."; + + let description = [{ +Read +[the section on segmentation](https://tensorflow.org/api_docs/python/tf/math#Segmentation) +for an explanation of segments. + +This operator is similar to the unsorted segment sum operator found +[(here)](../../../api_docs/python/math_ops.md#UnsortedSegmentSum). +Instead of computing the sum over segments, it computes the product of all +entries belonging to a segment such that: + +\\(output_i = \prod_{j...} data[j...]\\) where the product is over tuples +`j...` such that `segment_ids[j...] == i`. + +For example: + +``` python +c = tf.constant([[1,2,3,4], [5,6,7,8], [4,3,2,1]]) +tf.unsorted_segment_prod(c, tf.constant([0, 1, 0]), num_segments=2) +# ==> [[ 4, 6, 6, 4], +# [5, 6, 7, 8]] +``` + +If there is no entry for a given segment ID `i`, it outputs 1. + +If the given segment ID `i` is negative, then the corresponding value is +dropped, and will not be included in the result. + }]; + + let arguments = (ins + TensorOf<[BF16, F16, F32, F64, I16, I32, I64, I8, TF_Complex128, TF_Complex64, TF_Qint32, TF_Qint8, TF_Quint8, TF_Uint16, TF_Uint32, TF_Uint64, TF_Uint8]>:$data, + TF_I32OrI64Tensor:$segment_ids, + TF_I32OrI64Tensor:$num_segments + ); + + let results = (outs + TensorOf<[BF16, F16, F32, F64, I16, I32, I64, I8, TF_Complex128, TF_Complex64, TF_Qint32, TF_Qint8, TF_Quint8, TF_Uint16, TF_Uint32, TF_Uint64, TF_Uint8]>:$output + ); + + TF_DerivedOperandTypeAttr Tindices = TF_DerivedOperandTypeAttr<1>; + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; + TF_DerivedOperandTypeAttr Tnumsegments = TF_DerivedOperandTypeAttr<2>; + + let verifier = [{ return VerifyUnsortedSegmentReduction(*this); }]; +} + +def TF_UnsortedSegmentSumOp : TF_Op<"UnsortedSegmentSum", [NoSideEffect]> { + let summary = "Computes the sum along segments of a tensor."; + + let description = [{ +Read +[the section on segmentation](https://tensorflow.org/api_docs/python/tf/math#Segmentation) +for an explanation of segments. + +Computes a tensor such that +\\(output[i] = \sum_{j...} data[j...]\\) where the sum is over tuples `j...` such +that `segment_ids[j...] == i`. Unlike `SegmentSum`, `segment_ids` +need not be sorted and need not cover all values in the full +range of valid values. + +If the sum is empty for a given segment ID `i`, `output[i] = 0`. +If the given segment ID `i` is negative, the value is dropped and will not be +added to the sum of the segment. + +`num_segments` should equal the number of distinct segment IDs. + +
+ +
+ +``` python +c = tf.constant([[1,2,3,4], [5,6,7,8], [4,3,2,1]]) +tf.unsorted_segment_sum(c, tf.constant([0, 1, 0]), num_segments=2) +# ==> [[ 5, 5, 5, 5], +# [5, 6, 7, 8]] +``` + }]; + + let arguments = (ins + TensorOf<[BF16, F16, F32, F64, I16, I32, I64, I8, TF_Complex128, TF_Complex64, TF_Qint32, TF_Qint8, TF_Quint8, TF_Uint16, TF_Uint32, TF_Uint64, TF_Uint8]>:$data, + TF_I32OrI64Tensor:$segment_ids, + TF_I32OrI64Tensor:$num_segments + ); + + let results = (outs + TensorOf<[BF16, F16, F32, F64, I16, I32, I64, I8, TF_Complex128, TF_Complex64, TF_Qint32, TF_Qint8, TF_Quint8, TF_Uint16, TF_Uint32, TF_Uint64, TF_Uint8]>:$output + ); + + TF_DerivedOperandTypeAttr Tindices = TF_DerivedOperandTypeAttr<1>; + TF_DerivedOperandTypeAttr T = TF_DerivedOperandTypeAttr<0>; + TF_DerivedOperandTypeAttr Tnumsegments = TF_DerivedOperandTypeAttr<2>; + + let verifier = [{ return VerifyUnsortedSegmentReduction(*this); }]; +} + def TF_VariableShapeOp : TF_Op<"VariableShape", []> { let summary = "Returns the shape of the variable pointed to by `resource`."; diff --git a/tensorflow/compiler/mlir/tensorflow/ir/tf_ops.cc b/tensorflow/compiler/mlir/tensorflow/ir/tf_ops.cc index 9d2f634161c..aa0a04b4a89 100644 --- a/tensorflow/compiler/mlir/tensorflow/ir/tf_ops.cc +++ b/tensorflow/compiler/mlir/tensorflow/ir/tf_ops.cc @@ -24,6 +24,8 @@ limitations under the License. #include #include "llvm/ADT/APInt.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Sequence.h" #include "llvm/ADT/SmallVector.h" @@ -726,6 +728,101 @@ void DivOp::getCanonicalizationPatterns(OwningRewritePatternList &results, results.insert(context); } +//===----------------------------------------------------------------------===// +// DynamicStitchOp +//===----------------------------------------------------------------------===// + +static LogicalResult Verify(DynamicStitchOp op) { + if (op.N() < 1) return op.emitOpError("requires attribute N with value >= 1"); + + if (RankedTensorType out_ty = op.getType().dyn_cast()) { + if (out_ty.getRank() == 0) { + return op.emitOpError("requires non scalar output"); + } + } + + llvm::SmallDenseSet index_values; + bool all_indices_const = true; + int32_t max_index = -1; + llvm::Optional> inferred_item_shape; + for (auto it : llvm::zip(op.indices(), op.data())) { + Value *index = std::get<0>(it); + + DenseIntElementsAttr index_attr; + if (matchPattern(index, m_Constant(&index_attr))) { + for (int32_t index : index_attr.getValues()) { + if (index < 0) + return op.emitOpError() + << "requires non-negative index values; found " << index; + max_index = std::max(index, max_index); + index_values.insert(index); + } + } else { + all_indices_const = false; + } + + Value *data = std::get<1>(it); + RankedTensorType index_ty = index->getType().dyn_cast(); + RankedTensorType data_ty = data->getType().dyn_cast(); + if (!index_ty || !data_ty) continue; + + int64_t index_rank = index_ty.getRank(); + ArrayRef data_shape = data_ty.getShape(); + ArrayRef index_shape = index_ty.getShape(); + if (failed(mlir::verifyCompatibleShape(index_shape, + data_shape.take_front(index_rank)))) + return op.emitOpError() << "requires shape of data with type " << data_ty + << " to have prefix matching with shape of the " + "corresponding index type " + << index_ty; + + ArrayRef item_shape = data_shape.drop_front(index_rank); + if (!inferred_item_shape) { + inferred_item_shape = llvm::to_vector<4>(item_shape); + continue; + } + + if (failed(mlir::verifyCompatibleShape(item_shape, *inferred_item_shape))) + return op.emitOpError() << "has inconsistent shaped data and index " + "pairs; inferred item shapes [" + << llvm::makeArrayRef(*inferred_item_shape) + << "] and [" << item_shape << "] don't match"; + for (int i = 0, e = item_shape.size(); i < e; ++i) { + int64_t &inferred_dim = (*inferred_item_shape)[i]; + int64_t dim = item_shape[i]; + if (ShapedType::isDynamic(inferred_dim)) inferred_dim = dim; + } + } + + // If all indices are constants, then verify that they cover all indices in + // the range [0, max_index] and the output type is legal. + if (all_indices_const) { + for (int32_t i = 0; i <= max_index; i++) { + if (!index_values.count(i)) + return op.emitOpError() << "missing index " << i; + } + + if (inferred_item_shape) { + SmallVector expected_shape; + expected_shape.push_back(max_index + 1); + expected_shape.append(inferred_item_shape->begin(), + inferred_item_shape->end()); + + auto out_ty = op.getType().cast(); + auto expected_out_ty = + RankedTensorType::get(expected_shape, out_ty.getElementType()); + + if (!AreCastCompatible(out_ty, expected_out_ty)) { + return op.emitOpError() << "has invalid output type; should be " + "compatible with inferred type " + << expected_out_ty; + } + } + } + + return success(); +} + //===----------------------------------------------------------------------===// // EinsumOp //===----------------------------------------------------------------------===// @@ -891,6 +988,44 @@ static LogicalResult Verify(FusedBatchNormOp op) { return success(); } +//===----------------------------------------------------------------------===// +// GatherV2Op +//===----------------------------------------------------------------------===// + +static LogicalResult Verify(GatherV2Op op) { + int64_t batch_dims = op.batch_dims().getSExtValue(); + if (auto ty = op.indices()->getType().dyn_cast()) { + int64_t rank = ty.getRank(); + if (batch_dims > rank || batch_dims < -rank) + return op.emitOpError() + << "batch_dims (" << batch_dims << ") must be in range [" << -rank + << ", " << rank + 1 << ")"; + if (batch_dims < 0) batch_dims += rank; + } + + if (!HasRankAtMost(op.axis(), 1)) + return op.emitOpError("requires axis to have rank at most 1"); + + DenseIntElementsAttr axis_attr; + if (matchPattern(op.axis(), m_Constant(&axis_attr))) { + int64_t axis = (*axis_attr.begin()).getSExtValue(); + if (auto ty = op.params()->getType().dyn_cast()) { + int64_t rank = ty.getRank(); + if (axis >= rank || axis < -rank) + return op.emitOpError() << "axis (" << axis << ") must be in range [" + << -rank << ", " << rank << ")"; + if (axis < 0) axis += rank; + } + + if (batch_dims >= 0 && axis >= 0 && axis < batch_dims) { + return op.emitOpError() << "requires axis (" << axis + << ") to be greater than or equal to batch_dims (" + << batch_dims << ")"; + } + } + return success(); +} + //===----------------------------------------------------------------------===// // IfOp //===----------------------------------------------------------------------===// @@ -1752,12 +1887,14 @@ void SumOp::build(Builder *builder, OperationState &result, Value *input, // elements. Here, the number of elements should be less than 32 to support // 32-bit mask attributes. // - None of the strides values are zero. -// -static LogicalResult Verify(StridedSliceOp op) { +// - Ellipsis mask can have at most one bit set. + +template +static LogicalResult VerifyStridedSliceBase(OpTy op) { // Expected size for operands begin, end and strides vector operands. int64_t expected_size = -1; - for (Value *val : llvm::drop_begin(op.getOperands(), 1)) { + for (Value *val : {op.begin(), op.end(), op.strides()}) { auto operand_ty = val->getType().dyn_cast(); if (!operand_ty || !operand_ty.hasStaticShape()) { // TensorFlow constant ops may have non-static shape because the shape is @@ -1797,11 +1934,179 @@ static LogicalResult Verify(StridedSliceOp op) { return op.emitOpError("requires non-zero strides"); } - // TODO(hinsu): Validate attributes. + // Use bit compares to ensure ellipsis_mask is 0 or a power of 2, i.e. there + // exists only no more than one ellipsis. + uint32_t ellipsis_mask = op.ellipsis_mask().getZExtValue(); + if (ellipsis_mask != 0 && !llvm::isPowerOf2_32(ellipsis_mask)) + return op.emitOpError("cannot have multiple ellipses"); return success(); } +// Clamps the given `val`: returns `low` if `val` is less than `low`; returns +// `high` if `high` is less than `val`; otherwise returns `val`. +template +constexpr const T &Clamp(const T &val, const T &low, const T &high) { + assert(!(high < low)); + return (val < low) ? low : (high < val) ? high : val; +} + +// For the given `input_shape`, calculates the sliced shape using the given +// `begin`, `end`, and `stride` ranges and `begin_mask` and `end_mask` masks. +// Updates the result back to `input_shape`. At the same time, canonicalizes +// `begin`, `end`, and `strides. The calculation follows tf.StridedSlice op +// semantics. +static void CalculateSlicedShapeAndBoundRanges( + MutableArrayRef input_shape, int32_t begin_mask, int32_t end_mask, + MutableArrayRef begin, MutableArrayRef end, + MutableArrayRef stride) { + assert(input_shape.size() <= 32); // Only 32-bit masks are supported. + + // Make sure ranges' ranks are consistent with the input. + assert(input_shape.size() == begin.size()); + assert(input_shape.size() == end.size()); + assert(input_shape.size() == stride.size()); + + for (int i = 0, e = input_shape.size(); i < e; ++i) { + if (ShapedType::isDynamic(input_shape[i])) continue; + + int64_t dim_i = input_shape[i]; + int64_t begin_i = begin[i]; + int64_t end_i = end[i]; + int64_t stride_i = stride[i]; + + // [0]: mask for begin, [1]: mask for end + int64_t masks[] = {begin_mask & (1 << i), end_mask & (1 << i)}; + // [0]: bound for begin, [1]: bound for end + int64_t bounds[] = {stride_i > 0 ? 0 : -1, + stride_i > 0 ? dim_i : dim_i - 1}; + + // Canonicalizes the given range `point` (begin/end) according to the + // current dimension. `c` means case: 0 for begin, 1 for end. + auto canonicalize = [&](int64_t point, int c) { + if (masks[c]) return stride_i > 0 ? bounds[c] : bounds[(c + 1) & 1]; + + // Add dim as offset to negative range point. + point = point < 0 ? dim_i + point : point; + return Clamp(point, bounds[0], bounds[1]); + }; + + begin_i = canonicalize(begin_i, 0); + end_i = canonicalize(end_i, 1); + + int64_t interval_len = end_i - begin_i; + int64_t size_i = 0; + // If internal length is zero or has different sign from stride, it's a + // degenerated case: we are slicing nothing. Otherwise, calculate the sliced + // size. + if (interval_len != 0 && (interval_len < 0) == (stride_i < 0)) + size_i = (interval_len / stride_i) + (interval_len % stride_i != 0); + + input_shape[i] = size_i; + begin[i] = begin_i; + end[i] = end_i; + stride[i] = stride_i; + } +} + +bool StridedSliceOp::GetSlicedBoundRanges( + ArrayRef shape, SmallVectorImpl *begin_indices, + SmallVectorImpl *end_indices, SmallVectorImpl *strides) { + if (this->ellipsis_mask().getZExtValue() || + this->new_axis_mask().getZExtValue() || + this->shrink_axis_mask().getZExtValue()) + return false; // TODO(antiagainst): support these masks + + // TODO(hinsu): Support lowering for ops with dynamic begin and end values + // when it is possible to derive indices based on mask attributes. + DenseIntElementsAttr begin_indices_attr, end_indices_attr, strides_attr; + if (!matchPattern(this->begin(), m_Constant(&begin_indices_attr)) || + !matchPattern(this->end(), m_Constant(&end_indices_attr)) || + !matchPattern(this->strides(), m_Constant(&strides_attr))) + return false; + + auto input_shape = llvm::to_vector<4>(shape); + int rank = input_shape.size(); + + begin_indices->clear(); + begin_indices->reserve(rank); + end_indices->clear(); + end_indices->reserve(rank); + strides->clear(); + strides->reserve(rank); + + for (const APInt &index : begin_indices_attr) + begin_indices->push_back(index.getSExtValue()); + for (const APInt &index : end_indices_attr) + end_indices->push_back(index.getSExtValue()); + for (const APInt &stride : strides_attr) + strides->push_back(stride.getSExtValue()); + + CalculateSlicedShapeAndBoundRanges( + input_shape, this->begin_mask().getZExtValue(), + this->end_mask().getZExtValue(), *begin_indices, *end_indices, *strides); + return true; +} + +//===----------------------------------------------------------------------===// +// StridedSliceGradOp +//===----------------------------------------------------------------------===// + +static LogicalResult Verify(StridedSliceGradOp op) { + auto shape_type = op.shape()->getType().dyn_cast(); + if (shape_type && shape_type.getRank() != 1) + return op.emitOpError("'shape' operand must be 1D tensor, but got ") + << shape_type.getRank() << "D tensor"; + + if (failed(VerifyStridedSliceBase(op))) return failure(); + + // TODO(antiagainst): verify the gradient op.dy()'s shape is consistent with + // the sliced type from StridedSlice. + + return success(); +} + +bool StridedSliceGradOp::GetSlicedShapeAndBoundRanges( + SmallVectorImpl *shape, SmallVectorImpl *begin_indices, + SmallVectorImpl *end_indices, SmallVectorImpl *strides) { + if (this->ellipsis_mask().getZExtValue() || + this->new_axis_mask().getZExtValue() || + this->shrink_axis_mask().getZExtValue()) + return false; // TODO(antiagainst): support these masks + + DenseIntElementsAttr shape_attr; + DenseIntElementsAttr begin_indices_attr, end_indices_attr, strides_attr; + if (!matchPattern(this->shape(), m_Constant(&shape_attr)) || + !matchPattern(this->begin(), m_Constant(&begin_indices_attr)) || + !matchPattern(this->end(), m_Constant(&end_indices_attr)) || + !matchPattern(this->strides(), m_Constant(&strides_attr))) + return false; + + int rank = std::distance(shape_attr.begin(), shape_attr.end()); + + shape->clear(); + shape->reserve(rank); + begin_indices->clear(); + begin_indices->reserve(rank); + end_indices->clear(); + end_indices->reserve(rank); + strides->clear(); + strides->reserve(rank); + + for (const APInt &dim : shape_attr) shape->push_back(dim.getSExtValue()); + for (const APInt &index : begin_indices_attr) + begin_indices->push_back(index.getSExtValue()); + for (const APInt &index : end_indices_attr) + end_indices->push_back(index.getSExtValue()); + for (const APInt &stride : strides_attr) + strides->push_back(stride.getSExtValue()); + + CalculateSlicedShapeAndBoundRanges(*shape, this->begin_mask().getZExtValue(), + this->end_mask().getZExtValue(), + *begin_indices, *end_indices, *strides); + return true; +} + //===----------------------------------------------------------------------===// // TensorListReserveOp //===----------------------------------------------------------------------===// @@ -1943,6 +2248,49 @@ static LogicalResult Verify(UnpackOp op) { return success(); } +//===----------------------------------------------------------------------===// +// Unsorted segment reduction ops +//===----------------------------------------------------------------------===// + +template +static LogicalResult VerifyUnsortedSegmentReduction(Op op) { + if (!HasRankAtMost(op.num_segments(), 0)) + return op.emitOpError("number of segments should be a 0-D tensor"); + + auto data_type = op.data()->getType().template dyn_cast(); + auto segment_ids_type = + op.segment_ids()->getType().template dyn_cast(); + if (data_type && segment_ids_type) { + if (data_type.getRank() < segment_ids_type.getRank()) + return op.emitOpError( + "requires segment ids rank to be less than or equal to data's rank"); + + int index = 0; + for (auto shape_pair : + llvm::zip_first(segment_ids_type.getShape(), data_type.getShape())) { + int64_t segment_id_dim = std::get<0>(shape_pair); + int64_t data_dim = std::get<1>(shape_pair); + if (!ShapedType::isDynamic(segment_id_dim) && + !ShapedType::isDynamic(data_dim) && segment_id_dim != data_dim) + return op.emitOpError( + "requires segment ids shape to be a prefix of data shape, " + "but dimension #") + << index << " differs: " << segment_id_dim << " vs. " + << data_dim; + ++index; + } + } + + DenseIntElementsAttr num_segments_attr; + if (matchPattern(op.num_segments(), m_Constant(&num_segments_attr))) { + int64_t num_segments = (*num_segments_attr.begin()).getSExtValue(); + if (num_segments < 0) + return op.emitOpError("num of segments cannot be negative"); + } + + return success(); +} + //===----------------------------------------------------------------------===// // VariableShapeOp //===----------------------------------------------------------------------===// diff --git a/tensorflow/compiler/mlir/tensorflow/tests/BUILD b/tensorflow/compiler/mlir/tensorflow/tests/BUILD index 9f47185e90a..ef93af93b40 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/BUILD +++ b/tensorflow/compiler/mlir/tensorflow/tests/BUILD @@ -15,5 +15,6 @@ filegroup( data = [ "//tensorflow/compiler/mlir:tf-opt", "@llvm//:FileCheck", + "@llvm//:not", ], ) diff --git a/tensorflow/compiler/mlir/tensorflow/tests/decompose_resource_ops.mlir b/tensorflow/compiler/mlir/tensorflow/tests/decompose_resource_ops.mlir index 67d58b41199..0776aafc1a1 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/decompose_resource_ops.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/decompose_resource_ops.mlir @@ -65,3 +65,162 @@ func @decompose_resource_apply_gradient_descent(%arg0: tensor) -> () { return } +// ----- + +// Tests that composite tf.ResourceApplyKerasMomentum (non-Nesterov) operation +// is decomposed. + +// CHECK-LABEL: func @decompose_resource_apply_keras_momentum_non_nesterov +// CHECK-SAME: (%[[LR:.*]]: tensor, %[[GRAD:.*]]: tensor, %[[MOMENTUM:.*]]: tensor) +func @decompose_resource_apply_keras_momentum_non_nesterov(%arg0: tensor, %arg1: tensor, %arg2: tensor) -> () { + + // CHECK: %[[VAR_HANDLE:[0-9]*]] = "tf.VarHandleOp" + // CHECK: %[[ACCUM_HANDLE:[0-9]*]] = "tf.VarHandleOp" + %0 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + %1 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + + // CHECK: %[[ACCUM:[0-9]*]] = "tf.ReadVariableOp"(%[[ACCUM_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> + // CHECK: %[[ACCUM_MOMENTUM:[0-9]*]] = "tf.Mul"(%[[ACCUM]], %[[MOMENTUM]]) + // CHECK: %[[GRAD_LR:[0-9]*]] = "tf.Mul"(%[[GRAD]], %[[LR]]) + // CHECK: %[[NEW_ACCUM:[0-9]*]] = "tf.Sub"(%[[ACCUM_MOMENTUM]], %[[GRAD_LR]]) + // CHECK: "tf.AssignVariableOp"(%[[ACCUM_HANDLE]], %[[NEW_ACCUM]]) + + // CHECK: %[[VAR:[0-9]*]] = "tf.ReadVariableOp"(%[[VAR_HANDLE]]) + // CHECK: %[[NEW_VAR:[0-9]*]] = "tf.AddV2"(%[[VAR]], %[[NEW_ACCUM]]) + // CHECK: "tf.AssignVariableOp"(%[[VAR_HANDLE]], %[[NEW_VAR]]) + + "tf.ResourceApplyKerasMomentum"(%0, %1, %arg0, %arg1, %arg2) {use_locking = false, use_nesterov = false} : (tensor<*x!tf.resource>, tensor<*x!tf.resource>, tensor, tensor, tensor) -> () + + return +} + +// ----- + +// Tests that composite tf.ResourceApplyKerasMomentum (with Nesterov) operation +// is decomposed. + +// CHECK-LABEL: func @decompose_resource_apply_keras_momentum_nesterov +// CHECK-SAME: (%[[LR:.*]]: tensor, %[[GRAD:.*]]: tensor, %[[MOMENTUM:.*]]: tensor) +func @decompose_resource_apply_keras_momentum_nesterov(%arg0: tensor, %arg1: tensor, %arg2: tensor) -> () { + + // CHECK: %[[VAR_HANDLE:[0-9]*]] = "tf.VarHandleOp" + // CHECK: %[[ACCUM_HANDLE:[0-9]*]] = "tf.VarHandleOp" + %0 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + %1 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + + // CHECK: %[[ACCUM:[0-9]*]] = "tf.ReadVariableOp"(%[[ACCUM_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> + // CHECK: %[[ACCUM_MOMENTUM:[0-9]*]] = "tf.Mul"(%[[ACCUM]], %[[MOMENTUM]]) + // CHECK: %[[GRAD_LR:[0-9]*]] = "tf.Mul"(%[[GRAD]], %[[LR]]) + // CHECK: %[[NEW_ACCUM:[0-9]*]] = "tf.Sub"(%[[ACCUM_MOMENTUM]], %[[GRAD_LR]]) + // CHECK: "tf.AssignVariableOp"(%[[ACCUM_HANDLE]], %[[NEW_ACCUM]]) + + // CHECK: %[[NEW_ACCUM_MOMENTUM:[0-9]*]] = "tf.Mul"(%[[NEW_ACCUM]], %[[MOMENTUM]]) + // CHECK: %[[NEW_DELTA:[0-9]*]] = "tf.Sub"(%[[NEW_ACCUM_MOMENTUM]], %[[GRAD_LR]]) + // CHECK: %[[VAR:[0-9]*]] = "tf.ReadVariableOp"(%[[VAR_HANDLE]]) + // CHECK: %[[NEW_VAR:[0-9]*]] = "tf.AddV2"(%[[VAR]], %[[NEW_DELTA]]) + // CHECK: "tf.AssignVariableOp"(%[[VAR_HANDLE]], %[[NEW_VAR]]) + + "tf.ResourceApplyKerasMomentum"(%0, %1, %arg0, %arg1, %arg2) {use_locking = false, use_nesterov = true} : (tensor<*x!tf.resource>, tensor<*x!tf.resource>, tensor, tensor, tensor) -> () + + return +} + +// ----- + +// Tests that composite tf.ResourceApplyAdam (non-Nesterov) operation is +// decomposed. + +// CHECK-LABEL: func @decompose_resource_apply_adam_non_nesterov +// CHECK-SAME: ([[BETA1_POWER:%.*]]: tensor, [[BETA2_POWER:%.*]]: tensor, [[LR:%.*]]: tensor, [[BETA1:%.*]]: tensor, [[BETA2:%.*]]: tensor, [[EPSILON:%.*]]: tensor, [[GRAD:%.*]]: tensor) +func @decompose_resource_apply_adam_non_nesterov(%arg0: tensor, %arg1: tensor, %arg2: tensor, %arg3: tensor, %arg4: tensor, %arg5: tensor, %arg6: tensor) -> () { + +// CHECK: [[ONE:%.*]] = "tf.Const"() {value = dense<1.000000e+00> : tensor} +// CHECK: [[VAR_HANDLE:%.*]] = "tf.VarHandleOp"() +// CHECK: [[M_HANDLE:%.*]] = "tf.VarHandleOp"() +// CHECK: [[V_HANDLE:%.*]] = "tf.VarHandleOp"() +// CHECK: [[ONE_MINUS_BETA2_POWER:%.*]] = "tf.Sub"([[ONE]], [[BETA2_POWER]]) +// CHECK: [[SQRT_ONE_MINUS_BETA2_POWER:%.*]] = "tf.Sqrt"([[ONE_MINUS_BETA2_POWER]]) +// CHECK: [[ONE_MINUS_BETA1_POWER:%.*]] = "tf.Sub"([[ONE]], [[BETA1_POWER]]) +// CHECK: [[ALPHA_NO_LR:%.*]] = "tf.Div"([[SQRT_ONE_MINUS_BETA2_POWER]], [[ONE_MINUS_BETA1_POWER]]) +// CHECK: [[ALPHA:%.*]] = "tf.Mul"([[LR]], [[ALPHA_NO_LR]]) +// CHECK: [[OLD_M:%.*]] = "tf.ReadVariableOp"([[M_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> +// CHECK: [[BETA1_OLD_M:%.*]] = "tf.Mul"([[BETA1]], [[OLD_M]]) +// CHECK: [[ONE_MINUS_BETA1:%.*]] = "tf.Sub"([[ONE]], [[BETA1]]) +// CHECK: [[ONE_MINUS_BETA1_GRAD:%.*]] = "tf.Mul"([[ONE_MINUS_BETA1]], [[GRAD]]) +// CHECK: [[NEW_M:%.*]] = "tf.AddV2"([[BETA1_OLD_M]], [[ONE_MINUS_BETA1_GRAD]]) +// CHECK: [[OLD_V:%.*]] = "tf.ReadVariableOp"([[V_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> +// CHECK: [[BETA2_OLD_V:%.*]] = "tf.Mul"([[BETA2]], [[OLD_V]]) +// CHECK: [[ONE_MINUS_BETA2:%.*]] = "tf.Sub"([[ONE]], [[BETA2]]) +// CHECK: [[GRAD_SQUARE:%.*]] = "tf.Square"([[GRAD]]) +// CHECK: [[V_DELTA:%.*]] = "tf.Mul"([[ONE_MINUS_BETA2]], [[GRAD_SQUARE]]) +// CHECK: [[NEW_V:%.*]] = "tf.AddV2"([[BETA2_OLD_V]], [[V_DELTA]]) +// CHECK: [[ALPHA_NEW_M:%.*]] = "tf.Mul"([[ALPHA]], [[NEW_M]]) +// CHECK: [[SQRT_NEW_V:%.*]] = "tf.Sqrt"([[NEW_V]]) +// CHECK: [[SQRT_NEW_V_EPSILON:%.*]] = "tf.AddV2"([[SQRT_NEW_V]], [[EPSILON]]) +// CHECK: [[VAR_DELTA:%.*]] = "tf.Div"([[ALPHA_NEW_M]], [[SQRT_NEW_V_EPSILON]]) +// CHECK: [[OLD_VAR:%.*]] = "tf.ReadVariableOp"([[VAR_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> +// CHECK: [[NEW_VAR:%.*]] = "tf.Sub"([[OLD_VAR]], [[VAR_DELTA]]) +// CHECK: "tf.AssignVariableOp"([[VAR_HANDLE]], [[NEW_VAR]]) +// CHECK: "tf.AssignVariableOp"([[M_HANDLE]], [[NEW_M]]) +// CHECK: "tf.AssignVariableOp"([[V_HANDLE]], [[NEW_V]]) + + %0 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + %1 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + %2 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + + "tf.ResourceApplyAdam"(%0, %1, %2, %arg0, %arg1, %arg2, %arg3, %arg4, %arg5, %arg6) {use_locking = false, use_nesterov = false} : (tensor<*x!tf.resource>, tensor<*x!tf.resource>, tensor<*x!tf.resource>, tensor, tensor, tensor, tensor, tensor, tensor, tensor) -> () + + return +} + +// ----- + +// Tests that composite tf.ResourceApplyAdam (with Nesterov) operation is +// decomposed. + +// CHECK-LABEL: func @decompose_resource_apply_adam_nesterov( +// CHECK-SAME: [[BETA1_POWER:%.*]]: tensor, [[BETA2_POWER:%.*]]: tensor, [[LR:%.*]]: tensor, [[BETA1:%.*]]: tensor, [[BETA2:%.*]]: tensor, [[EPSILON:%.*]]: tensor, [[GRAD:%.*]]: tensor) { +func @decompose_resource_apply_adam_nesterov(%arg0: tensor, %arg1: tensor, %arg2: tensor, %arg3: tensor, %arg4: tensor, %arg5: tensor, %arg6: tensor) -> () { + +// CHECK: [[ONE:%.*]] = "tf.Const"() {value = dense<1.000000e+00> : tensor} +// CHECK: [[VAR_HANDLE:%.*]] = "tf.VarHandleOp"() {container = "c", shared_name = "v"} +// CHECK: [[M_HANDLE:%.*]] = "tf.VarHandleOp"() {container = "c", shared_name = "v"} +// CHECK: [[V_HANDLE:%.*]] = "tf.VarHandleOp"() {container = "c", shared_name = "v"} +// CHECK: [[VAL_82:%.*]] = "tf.Sub"([[ONE]], [[BETA2_POWER]]) +// CHECK: [[VAL_83:%.*]] = "tf.Sqrt"([[VAL_82]]) +// CHECK: [[VAL_84:%.*]] = "tf.Sub"([[ONE]], [[BETA1_POWER]]) +// CHECK: [[VAL_85:%.*]] = "tf.Div"([[VAL_83]], [[VAL_84]]) +// CHECK: [[VAL_86:%.*]] = "tf.Mul"([[LR]], [[VAL_85]]) +// CHECK: [[OLD_M:%.*]] = "tf.ReadVariableOp"([[M_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> +// CHECK: [[VAL_88:%.*]] = "tf.Mul"([[BETA1]], [[OLD_M]]) +// CHECK: [[VAL_89:%.*]] = "tf.Sub"([[ONE]], [[BETA1]]) +// CHECK: [[VAL_90:%.*]] = "tf.Mul"([[VAL_89]], [[GRAD]]) +// CHECK: [[NEW_M:%.*]] = "tf.AddV2"([[VAL_88]], [[VAL_90]]) +// CHECK: [[OLD_V:%.*]] = "tf.ReadVariableOp"([[V_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> +// CHECK: [[VAL_93:%.*]] = "tf.Mul"([[BETA2]], [[OLD_V]]) +// CHECK: [[VAL_94:%.*]] = "tf.Sub"([[ONE]], [[BETA2]]) +// CHECK: [[VAL_95:%.*]] = "tf.Square"([[GRAD]]) +// CHECK: [[VAL_96:%.*]] = "tf.Mul"([[VAL_94]], [[VAL_95]]) +// CHECK: [[NEW_V:%.*]] = "tf.AddV2"([[VAL_93]], [[VAL_96]]) +// CHECK: [[VAL_98:%.*]] = "tf.Mul"([[NEW_M]], [[BETA1]]) +// CHECK: [[VAL_99:%.*]] = "tf.Sub"([[ONE]], [[BETA1]]) +// CHECK: [[VAL_100:%.*]] = "tf.Mul"([[VAL_99]], [[GRAD]]) +// CHECK: [[VAL_101:%.*]] = "tf.AddV2"([[VAL_98]], [[VAL_100]]) +// CHECK: [[VAL_102:%.*]] = "tf.Mul"([[VAL_86]], [[VAL_101]]) +// CHECK: [[VAL_103:%.*]] = "tf.Sqrt"([[NEW_V]]) +// CHECK: [[VAL_104:%.*]] = "tf.AddV2"([[VAL_103]], [[EPSILON]]) +// CHECK: [[VAL_105:%.*]] = "tf.Div"([[VAL_102]], [[VAL_104]]) +// CHECK: [[OLD_VAR:%.*]] = "tf.ReadVariableOp"([[VAR_HANDLE]]) : (tensor<*x!tf.resource>) -> tensor<*xf32> +// CHECK: [[NEW_VAR:%.*]] = "tf.Sub"([[OLD_VAR]], [[VAL_105]]) +// CHECK: "tf.AssignVariableOp"([[VAR_HANDLE]], [[NEW_VAR]]) : (tensor<*x!tf.resource>, tensor<*xf32>) -> () +// CHECK: "tf.AssignVariableOp"([[M_HANDLE]], [[NEW_M]]) : (tensor<*x!tf.resource>, tensor<*xf32>) -> () +// CHECK: "tf.AssignVariableOp"([[V_HANDLE]], [[NEW_V]]) : (tensor<*x!tf.resource>, tensor<*xf32>) -> () + + %0 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + %1 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + %2 = "tf.VarHandleOp"() {container = "c", shared_name = "v"} : () -> tensor<*x!tf.resource> + + "tf.ResourceApplyAdam"(%0, %1, %2, %arg0, %arg1, %arg2, %arg3, %arg4, %arg5, %arg6) {use_locking = false, use_nesterov = true} : (tensor<*x!tf.resource>, tensor<*x!tf.resource>, tensor<*x!tf.resource>, tensor, tensor, tensor, tensor, tensor, tensor, tensor) -> () + + return +} diff --git a/tensorflow/compiler/mlir/tensorflow/tests/functionalize-if-fail.mlir b/tensorflow/compiler/mlir/tensorflow/tests/functionalize-if-fail.mlir index e05e7cd662f..2cfe423129c 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/functionalize-if-fail.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/functionalize-if-fail.mlir @@ -1,7 +1,8 @@ -// RUN: tf-opt %s --run-tf-graph-optimization --graph-passes=FunctionalizeControlFlowForXlaPass 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +// RUN: not tf-opt %s --run-tf-graph-optimization --graph-passes=FunctionalizeControlFlowForXlaPass 2>&1 | FileCheck %s -// CHECK: FunctionalizeControlFlowPass: Graph contains node with inputs predicated on incompatible predicates: {s(Cond:0,then)} and {s(Cond:0,else)} +// CHECK: error: FunctionalizeControlFlowForXlaPass: Graph contains node with inputs predicated on incompatible predicates: {s(Cond:0,then)} and {s(Cond:0,else)} // CHECK-NEXT: for node {{[{][{]node Add[}][}]}} + func @main() { %0 = "_tf._TPUReplicate"() {computation = @foo, Tinputs = [], Tbroadcast_inputs = [], NumVariables = 0, Tguaranteed_constants = [], output_types = []} : () -> !_tf.control loc("_TPUReplicate") return diff --git a/tensorflow/compiler/mlir/tensorflow/tests/graph_pruning.mlir b/tensorflow/compiler/mlir/tensorflow/tests/graph_pruning.mlir index 8585790564b..771ad5e30d8 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/graph_pruning.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/graph_pruning.mlir @@ -167,3 +167,16 @@ func @control_fetch(%arg0 : i32) { } return } + +// Check that @main function is pruned. +// CHECK-LABEL: func @main +func @main() { + tf_executor.graph { + // CHECK-NOT: tf_executor.island + %0 = tf_executor.island { + tf_executor.yield + } + tf_executor.fetch + } + return +} diff --git a/tensorflow/compiler/mlir/tensorflow/tests/graph_pruning_skip_main.mlir b/tensorflow/compiler/mlir/tensorflow/tests/graph_pruning_skip_main.mlir new file mode 100644 index 00000000000..86568cccd0f --- /dev/null +++ b/tensorflow/compiler/mlir/tensorflow/tests/graph_pruning_skip_main.mlir @@ -0,0 +1,14 @@ +// RUN: tf-opt %s -tf-executor-graph-pruning=skip-main-func | FileCheck %s --dump-input=fail + +// Check that @main function is skipped by default. +// CHECK-LABEL: func @main +func @main() { + tf_executor.graph { + // CHECKT: tf_executor.island + %0 = tf_executor.island { + tf_executor.yield + } + tf_executor.fetch + } + return +} diff --git a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/BUILD b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/BUILD index 39ab5ef0811..6c4d6d2b2ab 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/BUILD +++ b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/BUILD @@ -15,5 +15,6 @@ filegroup( data = [ "//tensorflow/compiler/mlir:tf-mlir-translate", "@llvm//:FileCheck", + "@llvm//:not", ], ) diff --git a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-function-resource-args.pbtxt b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-function-resource-args.pbtxt new file mode 100644 index 00000000000..a3f78e282bc --- /dev/null +++ b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-function-resource-args.pbtxt @@ -0,0 +1,99 @@ +# RUN: tf-mlir-translate -graphdef-to-mlir %s -tf-output-arrays=func_call -o - | FileCheck %s + +node { + name: "x" + op: "VarHandleOp" + device: "/CPU:0" + attr { + key: "container" + value { + s: "a" + } + } + attr { + key: "dtype" + value { + type: DT_INT64 + } + } + attr { + key: "shape" + value { + shape { + } + } + } + attr { + key: "shared_name" + value { + s: "x" + } + } +} +node { + name: "func_call" + op: "test_func_name" + input: "x" + input: "x" + attr { + key: "_disable_call_shape_inference" + value { + b: true + } + } +} +library { + function { + signature { + name: "test_func_name" + input_arg { + name: "a_0" + type: DT_RESOURCE + } + input_arg { + name: "a_1" + type: DT_RESOURCE + } + output_arg { + name: "a" + type: DT_RESOURCE + } + } + resource_arg_unique_id { + key: 0 + value: 0 + } + resource_arg_unique_id { + key: 1 + value: 0 + } + ret { + key: "a" + value: "a_0" + } + attr { + key: "_disable_call_shape_inference" + value { + b: true + } + } + } +} + +# Check that the `resource_arg_unique_id` for each argument is propagated to the +# `tf.resource_arg_unique_id` argument attribute of the function +# @test_func_name0. + +# CHECK: func @main +# CHECK: tf_executor.graph +# CHECK: "tf.VarHandleOp"() +# CHECK: "tf.LegacyCall" +# CHECK-SAME: {_disable_call_shape_inference = true, f = @test_func_name0} +# CHECK: tf_executor.fetch +# CHECK: return +# CHECK: func @test_func_name0 +# CHECK-SAME: tf.resource_arg_unique_id = 0 +# CHECK-SAME tf.resource_arg_unique_id = 0 +# CHECK: tf_executor.graph +# CHECK: tf_executor.fetch +# CHECK: return diff --git a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-malformed.pbtxt b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-malformed.pbtxt index 0a5aba285dc..a201ccee1fa 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-malformed.pbtxt +++ b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-malformed.pbtxt @@ -1,6 +1,6 @@ -# RUN: tf-mlir-translate -graphdef-to-mlir %s -o - 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +# RUN: not tf-mlir-translate -graphdef-to-mlir %s -o - 2>&1 | FileCheck %s this is not a valid graph def -#CHECK: {{(.|\.)+ Error parsing Protobuf:.*}} -#CHECK: {{(.|\.)+ Graph import failed: Invalid argument: Could not parse input file}} +# CHECK: Error parsing Protobuf +# CHECK: Graph import failed: Invalid argument: Could not parse input proto diff --git a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-undefined-output.pbtxt b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-undefined-output.pbtxt index 6816088322d..4a778f1945e 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-undefined-output.pbtxt +++ b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/graph-undefined-output.pbtxt @@ -1,4 +1,4 @@ -# RUN: tf-mlir-translate -graphdef-to-mlir %s -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes='' -tf-output-arrays=NotANodeInTheGraph -o - 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +# RUN: not tf-mlir-translate -graphdef-to-mlir %s -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes='' -tf-output-arrays=NotANodeInTheGraph -o - 2>&1 | FileCheck %s # CHECK: Graph import failed: Invalid argument: Output NotANodeInTheGraph was not found in graph diff --git a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/invalid-output-index.pbtxt b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/invalid-output-index.pbtxt index 6fec080be58..77107824319 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/invalid-output-index.pbtxt +++ b/tensorflow/compiler/mlir/tensorflow/tests/graphdef2mlir/invalid-output-index.pbtxt @@ -1,4 +1,4 @@ -# RUN: tf-mlir-translate -graphdef-to-mlir %s -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes='' -tf-output-arrays=input:1 -o - 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +# RUN: not tf-mlir-translate -graphdef-to-mlir %s -tf-input-arrays=input -tf-input-data-types=DT_FLOAT -tf-input-shapes='' -tf-output-arrays=input:1 -o - 2>&1 | FileCheck %s # CHECK: Graph import failed: Invalid argument: Invalid output index 1 specified for node: input diff --git a/tensorflow/compiler/mlir/tensorflow/tests/lower_tf.mlir b/tensorflow/compiler/mlir/tensorflow/tests/lower_tf.mlir index 60ffc924ae5..4d386f4448e 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/lower_tf.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/lower_tf.mlir @@ -115,6 +115,16 @@ func @pad(%arg0: tensor<3xf32>) -> tensor<6xf32> { return %0 : tensor<6xf32> } +// CHECK-LABEL: func @pad_bf16 +func @pad_bf16(%arg0: tensor<3xbf16>) -> tensor<6xbf16> { + %padding = "tf.Const"() { value = dense<[[1, 2]]> : tensor<1x2xi64> } : () -> tensor<1x2xi64> + // CHECK-DAG: [[PAD:%.+]] = "tf.Const"() { + // CHECK-DAG: [[CST:%.+]] = "tf.Const"() {value = dense<0.000000e+00> : tensor} + // CHECK: "tf.PadV2"(%arg0, [[PAD]], [[CST]]) + %0 = "tf.Pad"(%arg0, %padding) : (tensor<3xbf16>, tensor<1x2xi64>) -> tensor<6xbf16> + return %0 : tensor<6xbf16> +} + // CHECK-LABEL: func @BiasAddGrad_NHWC func @BiasAddGrad_NHWC(%arg0: tensor<2x3x4x5xf32>) -> tensor<5xf32> { // CHECK: "tf.Const"() {value = dense<[0, 1, 2]> : tensor<3xi64>} @@ -266,3 +276,92 @@ func @addN_variant(%arg0: tensor>>, %arg1: tensor>>, tensor>>, tensor>>) -> tensor>> return %0 : tensor>> } + +// CHECK-LABEL: func @DynamicStitch_simple +func @DynamicStitch_simple(%arg0: tensor<2x2xf32>) -> tensor<2x2xf32> { + // CHECK-DAG: %[[SHAPE:.*]] = "tf.Const"() {value = dense<[-1, 2]> : tensor<2xi64>} : () -> tensor<2xi64> + // CHECK-DAG: %[[INP:.*]] = "tf.Reshape"(%arg0, %[[SHAPE]]) : (tensor<2x2xf32>, tensor<2xi64>) -> tensor<2x2xf32> + // CHECK-DAG: %[[ITEMS:.*]]:2 = "tf.Unpack"(%[[INP]]) {axis = 0 : i64} : (tensor<2x2xf32>) -> (tensor<2xf32>, tensor<2xf32>) + // CHECK-DAG: %[[AXIS:.*]] = "tf.Const"() {value = dense<0> : tensor} : () -> tensor + // CHECK-DAG: %[[RESULT:.*]] = "tf.ConcatV2"(%[[ITEMS]]#1, %[[ITEMS]]#0, %[[AXIS]]) : (tensor<2xf32>, tensor<2xf32>, tensor) -> tensor<2x2xf32> + // CHECK: return %[[RESULT]] + + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2xf32>) -> tensor<2x2xf32> + return %0 : tensor<2x2xf32> +} + +// CHECK-LABEL: DynamicStitch_scalar_matrix_indices +func @DynamicStitch_scalar_matrix_indices(%arg0: tensor<2xf32>, %arg1: tensor<2x2x2xf32>) -> (tensor<5x2xf32>) { + // CHECK-DAG: %[[SHAPE:.*]] = "tf.Const"() {value = dense<[-1, 2]> : tensor<2xi64>} : () -> tensor<2xi64> + // CHECK-DAG: %[[INP0:.*]] = "tf.Reshape"(%arg0, %[[SHAPE]]) : (tensor<2xf32>, tensor<2xi64>) -> tensor<1x2xf32> + // CHECK-DAG: %[[ITEMS0:.*]] = "tf.Unpack"(%[[INP0]]) {axis = 0 : i64} : (tensor<1x2xf32>) -> tensor<2xf32> + // CHECK-DAG: %[[INP1:.*]] = "tf.Reshape"(%arg1, %[[SHAPE]]) : (tensor<2x2x2xf32>, tensor<2xi64>) -> tensor<4x2xf32> + // CHECK-DAG: %[[ITEMS1:.*]]:4 = "tf.Unpack"(%[[INP1]]) {axis = 0 : i64} : (tensor<4x2xf32>) -> (tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, tensor<2xf32>) + // CHECK-DAG: %[[AXIS:.*]] = "tf.Const"() {value = dense<0> : tensor} : () -> tensor + // CHECK-DAG: %6 = "tf.ConcatV2"(%[[ITEMS1]]#3, %[[ITEMS1]]#2, %[[ITEMS1]]#1, %[[ITEMS1]]#0, %[[ITEMS0]], %[[AXIS]]) : (tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, tensor) -> tensor<5x2xf32> + + %indices0 = "tf.Const"() {value = dense<4> : tensor} : () -> tensor + %indices1 = "tf.Const"() {value = dense<[[3, 2], [1, 0]]> : tensor<2x2xi32>} : () -> tensor<2x2xi32> + %0 = "tf.DynamicStitch"(%indices0, %indices1, %arg0, %arg1) : (tensor, tensor<2x2xi32>, tensor<2xf32>, tensor<2x2x2xf32>) -> tensor<5x2xf32> + return %0 : tensor<5x2xf32> +} + +// Verify that custom types are lowered and have legal output. +// CHECK-LABEL: func @DynamicStitch_uint8 +func @DynamicStitch_uint8(%arg0: tensor<2x2x!tf.uint8>) -> tensor<2x2x!tf.uint8> { + // CHECK-NOT: tf.DynamicStitch + + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2x!tf.uint8>) -> tensor<2x2x!tf.uint8> + return %0 : tensor<2x2x!tf.uint8> +} + +// CHECK-LABEL: func @DynamicStitch_scalar_item +func @DynamicStitch_scalar_item(%arg0: tensor<2xf32>) -> tensor<2xf32> { + // CHECK-DAG: %[[SHAPE:.*]] = "tf.Const"() {value = dense<-1> : tensor<1xi64>} : () -> tensor<1xi64> + // CHECK-DAG: %[[INP:.*]] = "tf.Reshape"(%arg0, %[[SHAPE]]) : (tensor<2xf32>, tensor<1xi64>) -> tensor<2xf32> + // CHECK-DAG: %[[ITEMS]]:2 = "tf.Unpack"(%[[INP]]) {axis = 0 : i64} : (tensor<2xf32>) -> (tensor, tensor) + // CHECK-DAG: %[[AXIS:.*]] = "tf.Const"() {value = dense<0> : tensor} : () -> tensor + // CHECK-DAG: %[[RESULT]] = "tf.ConcatV2"(%[[ITEMS]]#1, %[[ITEMS]]#0, %[[AXIS]]) : (tensor, tensor, tensor) -> tensor<2xf32> + // CHECK: return %[[RESULT]] + + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2xf32>) -> tensor<2xf32> + return %0 : tensor<2xf32> +} + +// CHECK-LABEL: func @DynamicStitch_matrix_item +func @DynamicStitch_matrix_item(%arg0: tensor<2x2x2xf32>) -> tensor<2x2x2xf32> { + // CHECK-DAG: %[[SHAPE:.*]] = "tf.Const"() {value = dense<[-1, 2, 2]> : tensor<3xi64>} : () -> tensor<3xi64> + // CHECK-DAG: %[[INP:.*]] = "tf.Reshape"(%arg0, %[[SHAPE]]) : (tensor<2x2x2xf32>, tensor<3xi64>) -> tensor<2x2x2xf32> + // CHECK-DAG: %[[ITEMS:.*]]:2 = "tf.Unpack"(%[[INP]]) {axis = 0 : i64} : (tensor<2x2x2xf32>) -> (tensor<2x2xf32>, tensor<2x2xf32>) + // CHECK-DAG: %[[AXIS:.*]] = "tf.Const"() {value = dense<0> : tensor} : () -> tensor + // CHECK-DAG: %[[RESULT:.*]] = "tf.ConcatV2"(%[[ITEMS]]#1, %[[ITEMS]]#0, %[[AXIS]]) : (tensor<2x2xf32>, tensor<2x2xf32>, tensor) -> tensor<2x2x2xf32> + // CHECK: return %[[RESULT]] + + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2x2xf32>) -> tensor<2x2x2xf32> + return %0 : tensor<2x2x2xf32> +} + +// CHECK-LABEL: func @DynamicStitch_dynamic +func @DynamicStitch_dynamic(%arg0: tensor<*xi32>, %arg1: tensor<*xf32>) -> tensor<*xf32> { + // CHECK: tf.DynamicStitch + %0 = "tf.DynamicStitch"(%arg0, %arg1) : (tensor<*xi32>, tensor<*xf32>) -> tensor<*xf32> + return %0 : tensor<*xf32> +} + +// CHECK-LABEL: func @DynamicStitch_duplicates +func @DynamicStitch_duplicates(%arg0: tensor<2x2xf32>) -> tensor<1x2xf32> { + // CHECK-DAG: %[[SHAPE:.*]] = "tf.Const"() {value = dense<[-1, 2]> : tensor<2xi64>} : () -> tensor<2xi64> + // CHECK-DAG: %[[INP:.*]] = "tf.Reshape"(%arg0, %[[SHAPE]]) : (tensor<2x2xf32>, tensor<2xi64>) -> tensor<2x2xf32> + // CHECK-DAG: %[[ITEMS:.*]]:2 = "tf.Unpack"(%[[INP]]) {axis = 0 : i64} : (tensor<2x2xf32>) -> (tensor<2xf32>, tensor<2xf32>) + // CHECK-DAG: %[[AXIS:.*]] = "tf.Const"() {value = dense<0> : tensor} : () -> tensor + // CHECK-DAG: %[[RESULT:.*]] = "tf.ConcatV2"(%[[ITEMS]]#1, %[[AXIS]]) : (tensor<2xf32>, tensor) -> tensor<1x2xf32> + // CHECK: return %[[RESULT]] + + %indices = "tf.Const"() {value = dense<[0, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2xf32>) -> tensor<1x2xf32> + return %0 : tensor<1x2xf32> +} diff --git a/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/BUILD b/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/BUILD index a4b7baeb90c..976ad56a895 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/BUILD +++ b/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/BUILD @@ -15,5 +15,6 @@ filegroup( data = [ "//tensorflow/compiler/mlir:tf-mlir-translate", "@llvm//:FileCheck", + "@llvm//:not", ], ) diff --git a/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/function-resource-args.mlir b/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/function-resource-args.mlir new file mode 100644 index 00000000000..24cb7b703c6 --- /dev/null +++ b/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/function-resource-args.mlir @@ -0,0 +1,62 @@ +// RUN: tf-mlir-translate -mlir-to-graphdef %s -o - | FileCheck %s + +func @main() -> tensor<*x!tf.resource> attributes {tf.entry_function = {inputs = "", outputs = "func_call"}} { + %0 = tf_executor.graph { + %outputs, %control = tf_executor.island wraps "tf.VarHandleOp"() {container = "a", device = "/CPU:0", dtype = i64, name = "x", shape = "tfshape$", shared_name = "x"} : () -> tensor>> + %outputs_0, %control_1 = tf_executor.island wraps "tf.LegacyCall"(%outputs, %outputs) {_disable_call_shape_inference = true, f = @test_func_name0} : (tensor>>, tensor>>) -> tensor<*x!tf.resource> + tf_executor.fetch %outputs_0 : tensor<*x!tf.resource> + } + return %0 : tensor<*x!tf.resource> +} +func @test_func_name0(%arg0: tensor<*x!tf.resource> {tf.resource_arg_unique_id = 0 : i64}, %arg1: tensor<*x!tf.resource> {tf.resource_arg_unique_id = 0 : i64}) -> tensor<*x!tf.resource> attributes {tf._disable_call_shape_inference = true} { + %0 = tf_executor.graph { + tf_executor.fetch %arg0 : tensor<*x!tf.resource> + } + return %0 : tensor<*x!tf.resource> +} + +// Check that the `tf.resource_arg_unique_id` argument attributes of +// test_func_name0 are propagated to the function's arg_attr and +// resource_arg_unique_id. + +// CHECK: name: "x" +// CHECK: op: "VarHandleOp" + +// CHECK: name: "func_call" +// CHECK: input: "x" +// CHECK: input: "x" + +// CHECK: library +// CHECK: function +// CHECK: signature +// CHECK: input_arg +// CHECK: type: DT_RESOURCE +// CHECK: input_arg +// CHECK: type: DT_RESOURCE +// CHECK: output_arg +// CHECK: type: DT_RESOURCE +// CHECK: ret + +// Check _resource_arg_unique_id for each argument. Since they alias each other, +// both values are 0. +// CHECK: arg_attr +// CHECK-NEXT: key: 0 +// CHECK-NEXT: value +// CHECK: key: "_resource_arg_unique_id" +// CHECK-NEXT: value +// CHECK-NEXT: i: 0 +// CHECK: arg_attr +// CHECK-NEXT: key: 1 +// CHECK-NEXT: value +// CHECK: key: "_resource_arg_unique_id" +// CHECK-NEXT: value +// CHECK-NEXT: i: 0 + +// Check resource_arg_unique_id for each argument. Since they alias each other, +// both values are 0. +// CHECK: resource_arg_unique_id +// CHECK-NEXT: key: 0 +// CHECK-NEXT: value: 0 +// CHECK: resource_arg_unique_id +// CHECK-NEXT: key: 1 +// CHECK-NEXT: value: 0 diff --git a/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/missing-main.mlir b/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/missing-main.mlir index f73e93369d5..09e23984d13 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/missing-main.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/mlir2graphdef/missing-main.mlir @@ -1,4 +1,4 @@ -// RUN: tf-mlir-translate -mlir-to-graphdef %s -o - 2>&1 | FileCheck %s; test ${PIPESTATUS[0]} -ne 0 +// RUN: not tf-mlir-translate -mlir-to-graphdef %s -o - 2>&1 | FileCheck %s // CHECK: Graph export failed: Failed precondition: entry function `main` must be present @@ -7,4 +7,3 @@ func @const() { %0:2 = "_tf.Const"() {device = "TPU:0", name = "const", dtype = "tfdtype$DT_INT32", value = dense<[1, 2]> : tensor<2xi32>} : () -> (tensor<2xi32>, !_tf.control) return } - diff --git a/tensorflow/compiler/mlir/tensorflow/tests/shape_inference.mlir b/tensorflow/compiler/mlir/tensorflow/tests/shape_inference.mlir index 5a3c531023c..2c3c72869b0 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/shape_inference.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/shape_inference.mlir @@ -79,4 +79,87 @@ module attributes {tf.versions = {bad_consumers = [], min_consumer = 0 : i32, pr %1 = "tf.Conv2DBackpropInput"(%0, %arg0, %arg1) {data_format = "NHWC", dilations = [1, 1, 1, 1], explicit_paddings = [], padding = "VALID", strides = [1, 1, 1, 1], use_cudnn_on_gpu = true} : (tensor<4xi32>, tensor<3x3x32x64xf32>, tensor<200x24x24x64xf32>) -> tensor return %1 : tensor } + + // CHECK-LABEL: func @shape_from_if_to_branch_functions + func @shape_from_if_to_branch_functions(%arg0: tensor, %arg1: tensor<1x2x3xf32>) -> tensor<1x2x3xf32> { + %0 = "tf.If"(%arg0, %arg1) {Tcond = i1, Tin = ["tfdtype$DT_FLOAT"], Tout = ["tfdtype$DT_FLOAT"], _xla_propagate_compile_time_consts = true, device = "", else_branch = @if_else_branch, is_stateless = true, name = "if", output_shapes = ["tfshape$"], then_branch = @if_then_branch} : (tensor, tensor<1x2x3xf32>) -> tensor<1x2x3xf32> + return %0 : tensor<1x2x3xf32> + } + + // CHECK-LABEL: func @if_then_branch + // CHECK-SAME: (%arg0: tensor<1x2x3xf32>) -> tensor<1x2x3xf32> + func @if_then_branch(%arg0: tensor<*xf32>) -> tensor<*xf32> { + // CHECK: return + // CHECK-SAME: tensor<1x2x3xf32> + return %arg0 : tensor<*xf32> + } + + // CHECK-LABEL: func @if_else_branch + // CHECK-SAME: (%arg0: tensor<1x2x3xf32>) -> tensor<1x2x3xf32> + func @if_else_branch(%arg0: tensor<*xf32>) -> tensor<*xf32> { + // CHECK: "tf.Identity"(%arg0) : (tensor<1x2x3xf32>) -> tensor<1x2x3xf32> + %0 = "tf.Identity"(%arg0) : (tensor<*xf32>) -> (tensor<*xf32>) + // CHECK: return + // CHECK-SAME: tensor<1x2x3xf32> + return %0 : tensor<*xf32> + } + + // CHECK-LABEL: func @shape_from_while_to_cond_body_functions + func @shape_from_while_to_cond_body_functions(%arg0: tensor<4xf32>) -> tensor<4xf32> { + %0 = "tf.While"(%arg0) {cond = @while_cond_func, body = @while_body_func, is_stateless = true} : (tensor<4xf32>) -> tensor<4xf32> + return %0 : tensor<4xf32> + } + + // CHECK-LABEL: func @while_cond_func + // CHECK-SAME: %arg0: tensor<4xf32>) -> tensor + func @while_cond_func(%arg0: tensor<*xf32>) -> tensor { + %0 = "tf.Const"() {value = dense<[1.000000e-04,2.000000e-04,3.000000e-04,4.000000e-04]> : tensor<4xf32>} : () -> tensor<4xf32> + %1 = "tf.Const"() {value = dense<0> : tensor} : () -> tensor + // CHECK: tf.Equal + // CHECK-SAME: (tensor<4xf32>, tensor<4xf32>) -> tensor<*xi1> + // TODO(ycao): Investigate why result type of tf.Equal is not inferred. + %2 = "tf.Equal"(%0, %arg0) : (tensor<4xf32>, tensor<*xf32>) -> tensor<*xi1> + %3 = "tf.Any"(%2, %1) : (tensor<*xi1>, tensor) -> (tensor) + return %3 : tensor + } + + // CHECK-LABEL: func @while_body_func + func @while_body_func(%arg0: tensor<*xf32>) -> tensor<*xf32> { + %0 = "tf.Const"() {value = dense<1.000000e-04> : tensor} : () -> tensor + // CHECK: tf.AddV2 + // CHECK-SAME: (tensor<4xf32>, tensor) -> tensor<4xf32> + %1 = "tf.AddV2"(%arg0, %0) : (tensor<*xf32>, tensor) -> tensor<*xf32> + // CHECK: return + // CHECK-SAME: tensor<4xf32> + return %1 : tensor<*xf32> + } + + // CHECK-LABEL: func @invalid_function_reused_by_control_flows + func @invalid_function_reused_by_control_flows(%arg0: tensor, %arg1: tensor<1x2x3xf32>) -> tensor<1x2x3xf32> { + // expected-warning @+1 {{unable to refine shape}} + %0 = "tf.If"(%arg0, %arg1) {Tcond = i1, Tin = ["tfdtype$DT_FLOAT"], Tout = ["tfdtype$DT_FLOAT"], _xla_propagate_compile_time_consts = true, device = "", else_branch = @reused_if_else_branch, is_stateless = true, name = "if", output_shapes = ["tfshape$"], then_branch = @reused_if_then_branch} : (tensor, tensor<1x2x3xf32>) -> tensor<1x2x3xf32> + // expected-warning @+1 {{unable to refine shape}} + %1 = "tf.If"(%arg0, %0) {Tcond = i1, Tin = ["tfdtype$DT_FLOAT"], Tout = ["tfdtype$DT_FLOAT"], _xla_propagate_compile_time_consts = true, device = "", else_branch = @reused_if_else_branch, is_stateless = true, name = "if", output_shapes = ["tfshape$"], then_branch = @reused_if_then_branch} : (tensor, tensor<1x2x3xf32>) -> tensor<1x2x3xf32> + return %0 : tensor<1x2x3xf32> + } + + // CHECK-LABEL: func @reused_if_then_branch + // CHECK-SAME: (%arg0: tensor<*xf32>) -> tensor<*xf32> + // expected-error @+1 {{expected control flow function reused_if_then_branch to have exactly 1 use}} + func @reused_if_then_branch(%arg0: tensor<*xf32>) -> tensor<*xf32> { + // CHECK: return + // CHECK-SAME: tensor<*xf32> + return %arg0 : tensor<*xf32> + } + + // CHECK-LABEL: func @reused_if_else_branch + // CHECK-SAME: (%arg0: tensor<*xf32>) -> tensor<*xf32> + // expected-error @+1 {{expected control flow function reused_if_else_branch to have exactly 1 use}} + func @reused_if_else_branch(%arg0: tensor<*xf32>) -> tensor<*xf32> { + // CHECK: "tf.Identity"(%arg0) : (tensor<*xf32>) -> tensor<*xf32> + %0 = "tf.Identity"(%arg0) : (tensor<*xf32>) -> (tensor<*xf32>) + // CHECK: return + // CHECK-SAME: tensor<*xf32> + return %0 : tensor<*xf32> + } } diff --git a/tensorflow/compiler/mlir/tensorflow/tests/side-effect-analysis-test.mlir b/tensorflow/compiler/mlir/tensorflow/tests/side-effect-analysis-test.mlir index 678c2373a1b..9b17956f399 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/side-effect-analysis-test.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/side-effect-analysis-test.mlir @@ -120,14 +120,14 @@ func @aliasing_reads_writes(%arg0: tensor<32xf32>) -> () { // CHECK-LABEL: func @unknown_side_effecting_op func @unknown_side_effecting_op(%arg0: tensor<32xf32>) -> () { -// expected-remark@above {{ID: 13}} +// expected-remark@above {{ID: 14}} tf_executor.graph { - // expected-remark@above {{ID: 11}} - // expected-remark@above {{Successors: {12}}} + // expected-remark@above {{ID: 12}} + // expected-remark@above {{Successors: {13}}} // CHECK: tf_executor.island %island = tf_executor.island { - // expected-remark@above {{ID: 9}} - // expected-remark@above {{Successors: {10}}} + // expected-remark@above {{ID: 10}} + // expected-remark@above {{Successors: {11}}} %vh0 = "tf.VarHandleOp"() {container = "c", shared_name = "v0"} : () -> tensor<*x!tf.resource>> // expected-remark@above {{ID: 0}} %vh1 = "tf.VarHandleOp"() {container = "c", shared_name = "v1"} : () -> tensor<*x!tf.resource>> @@ -141,30 +141,34 @@ func @unknown_side_effecting_op(%arg0: tensor<32xf32>) -> () { "tf._UnknownSideEffectingOp_"() : () -> () // expected-remark@above {{ID: 4}} // expected-remark@above {{Predecessors: {2,3}}} - // expected-remark@above {{Successors: {5,6}}} + // expected-remark@above {{Successors: {5,6,7}}} %read1 = "tf.ReadVariableOp"(%vh1) : (tensor<*x!tf.resource>>) -> tensor<32xf32> // expected-remark@above {{ID: 5}} // expected-remark@above {{Predecessors: {4}}} - // expected-remark@above {{Successors: {7}}} - "tf.AssignVariableOp"(%vh0, %read1) : (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + // expected-remark@above {{Successors: {8}}} + %read2 = "tf.ReadVariableOp"(%vh1) : (tensor<*x!tf.resource>>) -> tensor<32xf32> // expected-remark@above {{ID: 6}} // expected-remark@above {{Predecessors: {4}}} // expected-remark@above {{Successors: {8}}} - "tf.AssignVariableOp"(%vh1, %read0) : (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + "tf.AssignVariableOp"(%vh0, %read1) : (tensor<*x!tf.resource>>, tensor<32xf32>) -> () // expected-remark@above {{ID: 7}} - // expected-remark@above {{Predecessors: {5}}} - // expected-remark@above {{Successors: {8}}} - tf_executor.yield + // expected-remark@above {{Predecessors: {4}}} + // expected-remark@above {{Successors: {9}}} + "tf.AssignVariableOp"(%vh1, %read0) : (tensor<*x!tf.resource>>, tensor<32xf32>) -> () // expected-remark@above {{ID: 8}} - // expected-remark@above {{Predecessors: {6,7}}} + // expected-remark@above {{Predecessors: {5,6}}} + // expected-remark@above {{Successors: {9}}} + tf_executor.yield + // expected-remark@above {{ID: 9}} + // expected-remark@above {{Predecessors: {7,8}}} } tf_executor.fetch %island : !tf_executor.control - // expected-remark@above {{ID: 10}} - // expected-remark@above {{Predecessors: {9}}} + // expected-remark@above {{ID: 11}} + // expected-remark@above {{Predecessors: {10}}} } return - // expected-remark@above {{ID: 12}} - // expected-remark@above {{Predecessors: {11}}} + // expected-remark@above {{ID: 13}} + // expected-remark@above {{Predecessors: {12}}} } // ----- @@ -270,3 +274,466 @@ func @with_replicate( // expected-remark@above {{ID: 11}} // expected-remark@above {{Predecessors: {10}}} } + +// ----- + +// Tests that the pass does not add control dependencies a stateless if op. + +// CHECK-LABEL: func @stateless_if_op +func @stateless_if_op( + // expected-remark@above {{ID: 8}} + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor) { + tf_executor.graph { + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Successors: {7}}} + // CHECK: tf_executor.island + %island = tf_executor.island { + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Successors: {5}}} + %r0 = "tf.ReadVariableOp"(%arg0) : + // expected-remark@above {{ID: 0}} + // expected-remark@above {{Successors: {2}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + %if = "tf.If"(%arg1, %arg1) { + // expected-remark@above {{ID: 1}} + then_branch = @if_then, else_branch = @if_else, is_stateless = true} + : (tensor, tensor) -> tensor + "tf.AssignVariableOp"(%arg0, %r0) : + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {0}}} + // expected-remark@above {{Successors: {3}}} + (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + tf_executor.yield + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Predecessors: {2}}} + } + tf_executor.fetch %island : !tf_executor.control + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Predecessors: {4}}} + } + return + // expected-remark@above {{ID: 7}} + // expected-remark@above {{Predecessors: {6}}} +} + +// CHECK-LABEL: func @if_then +func @if_then(%arg0: tensor) -> tensor { + // expected-remark@above {{ID: 5}} + %graph = tf_executor.graph { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %island:2 = tf_executor.island { + // expected-remark@above {{ID: 1}} + // expected-remark@above {{Successors: {2}}} + tf_executor.yield %arg0 : tensor + // expected-remark@above {{ID: 0}} + } + tf_executor.fetch %island#0 : tensor + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {1}}} + } + return %graph : tensor + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} +} + +// CHECK-LABEL: func @if_else +func @if_else(%arg0: tensor) -> tensor { + // expected-remark@above {{ID: 5}} + %graph = tf_executor.graph { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %island:2 = tf_executor.island { + // expected-remark@above {{ID: 1}} + // expected-remark@above {{Successors: {2}}} + tf_executor.yield %arg0 : tensor + // expected-remark@above {{ID: 0}} + } + tf_executor.fetch %island#0 : tensor + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {1}}} + } + return %graph : tensor + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} +} + +// ----- + +// Tests that the pass does not add control dependencies a stateless while op. + +// CHECK-LABEL: func @stateless_if_op +func @stateless_if_op( + // expected-remark@above {{ID: 8}} + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor) { + tf_executor.graph { + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Successors: {7}}} + // CHECK: tf_executor.island + %island = tf_executor.island { + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Successors: {5}}} + %r0 = "tf.ReadVariableOp"(%arg0) : + // expected-remark@above {{ID: 0}} + // expected-remark@above {{Successors: {2}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + %if = "tf.While"(%arg1) { + // expected-remark@above {{ID: 1}} + body = @while_body, cond = @while_cond, is_stateless = true} + : (tensor) -> tensor + "tf.AssignVariableOp"(%arg0, %r0) : + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {0}}} + // expected-remark@above {{Successors: {3}}} + (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + tf_executor.yield + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Predecessors: {2}}} + } + tf_executor.fetch %island : !tf_executor.control + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Predecessors: {4}}} + } + return + // expected-remark@above {{ID: 7}} + // expected-remark@above {{Predecessors: {6}}} +} + +// CHECK-LABEL: func @while_body +func @while_body(%arg0: tensor) -> tensor { + // expected-remark@above {{ID: 5}} + %graph = tf_executor.graph { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %island:2 = tf_executor.island { + // expected-remark@above {{ID: 1}} + // expected-remark@above {{Successors: {2}}} + tf_executor.yield %arg0 : tensor + // expected-remark@above {{ID: 0}} + } + tf_executor.fetch %island#0 : tensor + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {1}}} + } + return %graph : tensor + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} +} + +// CHECK-LABEL: func @while_cond +func @while_cond(%arg0: tensor) -> tensor { + // expected-remark@above {{ID: 5}} + %graph = tf_executor.graph { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %island:2 = tf_executor.island { + // expected-remark@above {{ID: 1}} + // expected-remark@above {{Successors: {2}}} + tf_executor.yield %arg0 : tensor + // expected-remark@above {{ID: 0}} + } + tf_executor.fetch %island#0 : tensor + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {1}}} + } + return %graph : tensor + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} +} + +// ----- + +// Tests that the pass tracks control dependencies for variables from an if op's +// output. + +// CHECK-LABEL: func @output_of_if_op +func @output_of_if_op( + // expected-remark@above {{ID: 12}} + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor<*x!tf.resource>>, + %arg2: tensor) { + tf_executor.graph { + // expected-remark@above {{ID: 10}} + // expected-remark@above {{Successors: {11}}} + // CHECK: tf_executor.island + %island = tf_executor.island { + // expected-remark@above {{ID: 8}} + // expected-remark@above {{Successors: {9}}} + %id0 = "tf.Identity"(%arg0) : (tensor<*x!tf.resource>>) + // expected-remark@above {{ID: 0}} + -> tensor<*x!tf.resource>> + %if:3 = "tf.If"(%arg2, %id0, %arg1) { + // expected-remark@above {{ID: 1}} + // expected-remark@above {{Successors: {2,3,4}}} + then_branch = @if_then, else_branch = @if_else, is_stateless = false} + : (tensor, tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>) -> + (tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>) + %r0 = "tf.ReadVariableOp"(%if#0) : + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {1}}} + // expected-remark@above {{Successors: {5,6}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + %r1 = "tf.ReadVariableOp"(%if#1) : + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Predecessors: {1}}} + // expected-remark@above {{Successors: {5}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + %r2 = "tf.ReadVariableOp"(%if#2) : + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {1}}} + // expected-remark@above {{Successors: {5,6}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + "tf.AssignVariableOp"(%arg0, %r0) : + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Predecessors: {2,3,4}}} + // expected-remark@above {{Successors: {7}}} + (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + "tf.AssignVariableOp"(%arg1, %r0) : + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Predecessors: {2,4}}} + // expected-remark@above {{Successors: {7}}} + (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + tf_executor.yield + // expected-remark@above {{ID: 7}} + // expected-remark@above {{Predecessors: {5,6}}} + } + tf_executor.fetch %island : !tf_executor.control + // expected-remark@above {{ID: 9}} + // expected-remark@above {{Predecessors: {8}}} + } + return + // expected-remark@above {{ID: 11}} + // expected-remark@above {{Predecessors: {10}}} +} + +// CHECK-LABEL: func @if_then +func @if_then( + // expected-remark@above {{ID: 7}} + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor<*x!tf.resource>>) -> + (tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>) { + %graph:3 = tf_executor.graph { + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Successors: {6}}} + %island:4 = tf_executor.island { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %u0 = "tf._UnknownSideEffectingOp_"() : () + // expected-remark@above {{ID: 0}} + // expected-remark@above {{Successors: {2}}} + -> tensor<*x!tf.resource>> + %id0 = "tf.Identity"(%arg0) : (tensor<*x!tf.resource>>) + // expected-remark@above {{ID: 1}} + -> tensor<*x!tf.resource>> + tf_executor.yield %u0, %id0, %id0 : + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {0}}} + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> + } + tf_executor.fetch %island#0, %island#1, %island#2 : + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> + } + return %graph#0, %graph#1, %graph#2 : + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Predecessors: {5}}} + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> +} + +// CHECK-LABEL: func @if_else +func @if_else( + // expected-remark@above {{ID: 7}} + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor<*x!tf.resource>>) -> + (tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>) { + %graph:3 = tf_executor.graph { + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Successors: {6}}} + %island:4 = tf_executor.island { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %id0 = "tf.Identity"(%arg0) : (tensor<*x!tf.resource>>) + // expected-remark@above {{ID: 0}} + -> tensor<*x!tf.resource>> + %id1 = "tf.Identity"(%arg1) : (tensor<*x!tf.resource>>) + // expected-remark@above {{ID: 1}} + -> tensor<*x!tf.resource>> + tf_executor.yield %id0, %id0, %id1 : + // expected-remark@above {{ID: 2}} + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> + } + tf_executor.fetch %island#0, %island#1, %island#2 : + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> + } + return %graph#0, %graph#1, %graph#2 : + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Predecessors: {5}}} + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> +} + +// ----- + +// Tests that the pass tracks control dependencies for variables from a while +// op's output. + +// CHECK-LABEL: func @output_of_while_op +func @output_of_while_op( + // expected-remark@above {{ID: 12}} + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor<*x!tf.resource>>, + %arg2: tensor) { + tf_executor.graph { + // expected-remark@above {{ID: 10}} + // expected-remark@above {{Successors: {11}}} + // CHECK: tf_executor.island + %island = tf_executor.island { + // expected-remark@above {{ID: 8}} + // expected-remark@above {{Successors: {9}}} + %id0 = "tf.Identity"(%arg0) : (tensor<*x!tf.resource>>) + // expected-remark@above {{ID: 0}} + -> tensor<*x!tf.resource>> + %while:4 = "tf.While"(%arg2, %id0, %arg1, %arg1) { + // expected-remark@above {{ID: 1}} + // expected-remark@above {{Successors: {2,3,4}}} + body = @while_body, cond = @while_cond, is_stateless = false} + : (tensor, tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>) -> + (tensor, tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>) + %r0 = "tf.ReadVariableOp"(%while#1) : + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {1}}} + // expected-remark@above {{Successors: {5}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + %r1 = "tf.ReadVariableOp"(%while#1) : + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Predecessors: {1}}} + // expected-remark@above {{Successors: {5}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + %r2 = "tf.ReadVariableOp"(%while#2) : + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {1}}} + // expected-remark@above {{Successors: {6}}} + (tensor<*x!tf.resource>>) -> tensor<32xf32> + "tf.AssignVariableOp"(%arg0, %r0) : + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Predecessors: {2,3}}} + // expected-remark@above {{Successors: {7}}} + (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + "tf.AssignVariableOp"(%arg1, %r0) : + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Predecessors: {4}}} + // expected-remark@above {{Successors: {7}}} + (tensor<*x!tf.resource>>, tensor<32xf32>) -> () + tf_executor.yield + // expected-remark@above {{ID: 7}} + // expected-remark@above {{Predecessors: {5,6}}} + } + tf_executor.fetch %island : !tf_executor.control + // expected-remark@above {{ID: 9}} + // expected-remark@above {{Predecessors: {8}}} + } + return + // expected-remark@above {{ID: 11}} + // expected-remark@above {{Predecessors: {10}}} +} + +// CHECK-LABEL: func @while_body +func @while_body( + // expected-remark@above {{ID: 7}} + %pred: tensor, + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor<*x!tf.resource>>, + %arg2: tensor<*x!tf.resource>>) -> + (tensor, tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>) { + %graph:4 = tf_executor.graph { + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Successors: {6}}} + %island:5 = tf_executor.island { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %id0 = "tf.Identity"(%arg0) : (tensor<*x!tf.resource>>) + // expected-remark@above {{ID: 0}} + -> tensor<*x!tf.resource>> + %u0 = "tf._UnknownSideEffectingOp_"() : () + // expected-remark@above {{ID: 1}} + // expected-remark@above {{Successors: {2}}} + -> tensor<*x!tf.resource>> + tf_executor.yield %pred, %id0, %arg1, %u0 : + // expected-remark@above {{ID: 2}} + // expected-remark@above {{Predecessors: {1}}} + tensor, tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> + } + tf_executor.fetch %island#0, %island#1, %island#2, %island#3 : + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} + tensor, tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> + } + return %graph#0, %graph#1, %graph#2, %graph#3 : + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Predecessors: {5}}} + tensor, tensor<*x!tf.resource>>, + tensor<*x!tf.resource>>, + tensor<*x!tf.resource>> +} + +// CHECK-LABEL: func @while_cond +func @while_cond( + // expected-remark@above {{ID: 7}} + %pred: tensor, + %arg0: tensor<*x!tf.resource>>, + %arg1: tensor<*x!tf.resource>>, + %arg2: tensor<*x!tf.resource>>) -> tensor { + %graph = tf_executor.graph { + // expected-remark@above {{ID: 5}} + // expected-remark@above {{Successors: {6}}} + %island:2 = tf_executor.island { + // expected-remark@above {{ID: 3}} + // expected-remark@above {{Successors: {4}}} + %const = "tf.Const"() { value = dense<0> : tensor } : () -> tensor + // expected-remark@above {{ID: 0}} + %eq = "tf.Equal"(%pred, %const) : (tensor, tensor) -> tensor + // expected-remark@above {{ID: 1}} + tf_executor.yield %eq : tensor + // expected-remark@above {{ID: 2}} + } + tf_executor.fetch %island#0 : tensor + // expected-remark@above {{ID: 4}} + // expected-remark@above {{Predecessors: {3}}} + } + return %graph : tensor + // expected-remark@above {{ID: 6}} + // expected-remark@above {{Predecessors: {5}}} +} diff --git a/tensorflow/compiler/mlir/tensorflow/tests/tf-ops.mlir b/tensorflow/compiler/mlir/tensorflow/tests/tf-ops.mlir index 90aa6e73f79..29c5af0e419 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/tf-ops.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/tf-ops.mlir @@ -1538,6 +1538,14 @@ func @testStridedSlice(%input: tensor<4x8xf32>, %begin: tensor<2xi32>, %end: ten // ----- +func @testStridedSlice(%input: tensor<4x8xf32>, %begin: tensor<2xi64>, %end: tensor<2xi64>, %strides: tensor<2xi64>) -> tensor { + // expected-error @+1 {{cannot have multiple ellipses}} + %0 = "tf.StridedSlice"(%input, %begin, %end, %strides) {ellipsis_mask = 3}: (tensor<4x8xf32>, tensor<2xi64>, tensor<2xi64>, tensor<2xi64>) -> tensor + return %0 : tensor +} + +// ----- + func @testOneHot(%indices: tensor<3xi32>, %depth: tensor, %on_value: tensor, %off_value: tensor) -> tensor<3x5xf32> { %result = "tf.OneHot"(%indices, %depth, %on_value, %off_value) {axis = -1 : i64} : (tensor<3xi32>, tensor, tensor, tensor) -> tensor<3x5xf32> return %result : tensor<3x5xf32> @@ -1823,3 +1831,272 @@ func @testAxisDim(%input: tensor<2x6xf32>) { %0:2 = "tf.Unpack"(%input) {axis = -1} : (tensor<2x6xf32>) -> (tensor<6xf32>, tensor<6xf32>) return } + +// ----- + +//===--------------------------------------------------------------------===// +// tf.UnsortedSegment{Max|Min|Prod|Sum} +//===--------------------------------------------------------------------===// + +// CHECK-LABEL: unsortedSegmentReduction +func @unsortedSegmentReduction(%data: tensor, %segment_ids: tensor<7x?xi32>, %num_segments: tensor) { + // CHECK: tf.UnsortedSegmentMin + %0 = "tf.UnsortedSegmentMin"(%data, %segment_ids, %num_segments) : (tensor, tensor<7x?xi32>, tensor) -> (tensor) + return +} + +// ----- + +func @unsortedSegmentReduction(%data: tensor<7x10x8xf32>, %segment_ids: tensor<7x10xi32>, %num_segments: tensor<2x3xi32>) { + // expected-error @+1 {{number of segments should be a 0-D tensor}} + %0 = "tf.UnsortedSegmentMax"(%data, %segment_ids, %num_segments) : (tensor<7x10x8xf32>, tensor<7x10xi32>, tensor<2x3xi32>) -> (tensor) + return +} + +// ----- + +func @unsortedSegmentReduction(%data: tensor<7x10x8xf32>, %segment_ids: tensor<7x9xi32>, %num_segments: tensor) { + // expected-error @+1 {{requires segment ids shape to be a prefix of data shape, but dimension #1 differs: 9 vs. 10}} + %0 = "tf.UnsortedSegmentProd"(%data, %segment_ids, %num_segments) : (tensor<7x10x8xf32>, tensor<7x9xi32>, tensor) -> (tensor) + return +} + +// ----- + +func @unsortedSegmentReduction(%data: tensor<7x10x8xf32>, %segment_ids: tensor<7x10x8x1xi32>, %num_segments: tensor) { + // expected-error @+1 {{requires segment ids rank to be less than or equal to data's rank}} + %0 = "tf.UnsortedSegmentSum"(%data, %segment_ids, %num_segments) : (tensor<7x10x8xf32>, tensor<7x10x8x1xi32>, tensor) -> (tensor) + return +} + +// ----- + +func @unsortedSegmentReduction(%data: tensor<7x10x8xf32>, %segment_ids: tensor<7x10xi32>) { + %num_segments = "tf.Const"() {value = dense<-5> : tensor} : () -> (tensor) + // expected-error @+1 {{num of segments cannot be negative}} + %0 = "tf.UnsortedSegmentSum"(%data, %segment_ids, %num_segments) : (tensor<7x10x8xf32>, tensor<7x10xi32>, tensor) -> (tensor) + return +} + +// ----- + + +//===--------------------------------------------------------------------===// +// tf.GatherV2 +//===--------------------------------------------------------------------===// + +func @testGatherV2(%arg0: tensor<16x2x3xf32>, %arg1: tensor<16x5xi32>) -> tensor<16x2x5x3xf32> { + %0 = "tf.Const"() { value = dense<[-1]> : tensor<1xi32> } : () -> tensor<1xi32> + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -1 : i64} : (tensor<16x2x3xf32>, tensor<16x5xi32>, tensor<1xi32>) -> tensor<16x2x5x3xf32> + return %1 : tensor<16x2x5x3xf32> +} + +// ----- + +// Verify that the batch_dims can be equal to the rank of the indices. +func @testGatherV2(%arg0: tensor<16x4xf32>, %arg1: tensor<16xi32>) -> tensor<16xf32> { + %0 = "tf.Const"() { value = dense<[1]> : tensor<1xi32> } : () -> tensor<1xi32> + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = 1 : i64} : (tensor<16x4xf32>, tensor<16xi32>, tensor<1xi32>) -> tensor<16xf32> + return %1 : tensor<16xf32> +} + +// ----- + +func @testGatherV2(%arg0: tensor<16x2x3xf32>, %arg1: tensor<16x5xi32>) -> tensor<16x2x5x3xf32> { + %0 = "tf.Const"() { value = dense<[-1]> : tensor<1xi32> } : () -> tensor<1xi32> + // expected-error @+1 {{batch_dims (-3) must be in range [-2, 3)}} + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -3 : i64} : (tensor<16x2x3xf32>, tensor<16x5xi32>, tensor<1xi32>) -> tensor<16x2x5x3xf32> + return %1 : tensor<16x2x5x3xf32> +} + +// ----- + +func @testGatherV2(%arg0: tensor<16x2x3xf32>, %arg1: tensor<16x5xi32>) -> tensor<16x2x5x3xf32> { + %0 = "tf.Const"() { value = dense<[[-4]]> : tensor<1x1xi32> } : () -> tensor<1x1xi32> + // expected-error @+1 {{requires axis to have rank at most 1}} + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -1 : i64} : (tensor<16x2x3xf32>, tensor<16x5xi32>, tensor<1x1xi32>) -> tensor<16x2x5x3xf32> + return %1 : tensor<16x2x5x3xf32> +} + +// ----- + +func @testGatherV2(%arg0: tensor<16x2x3xf32>, %arg1: tensor<16x5xi32>) -> tensor<16x2x5x3xf32> { + %0 = "tf.Const"() { value = dense<[-4]> : tensor<1xi32> } : () -> tensor<1xi32> + // expected-error @+1 {{axis (-4) must be in range [-3, 3)}} + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -1 : i64} : (tensor<16x2x3xf32>, tensor<16x5xi32>, tensor<1xi32>) -> tensor<16x2x5x3xf32> + return %1 : tensor<16x2x5x3xf32> +} + +// ----- + +func @testGatherV2(%arg0: tensor<16x2x3xf32>, %arg1: tensor<16x5xi32>) -> tensor<16x2x5x3xf32> { + %0 = "tf.Const"() { value = dense<[0]> : tensor<1xi32> } : () -> tensor<1xi32> + // expected-error @+1 {{requires axis (0) to be greater than or equal to batch_dims (1)}} + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -1 : i64} : (tensor<16x2x3xf32>, tensor<16x5xi32>, tensor<1xi32>) -> tensor<16x2x5x3xf32> + return %1 : tensor<16x2x5x3xf32> +} + +// ----- + +//===--------------------------------------------------------------------===// +// tf.StridedSliceGrad +//===--------------------------------------------------------------------===// + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %begin: tensor<2xi64>, %end: tensor<2xi64>, %strides: tensor<2xi64>, %shape: tensor<2xi64>) -> tensor { + // CHECK: tf.StridedSliceGrad + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) : (tensor<2xi64>, tensor<2xi64>, tensor<2xi64>, tensor<2xi64>, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %begin: tensor, %end: tensor<2xi64>, %strides: tensor<2xi64>, %shape: tensor<2xi64>) -> tensor { + // expected-error @+1 {{requires begin, end and strides to be 1D tensors}} + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) : (tensor<2xi64>, tensor, tensor<2xi64>, tensor<2xi64>, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %begin: tensor<32xi64>, %end: tensor<2xi64>, %strides: tensor<2xi64>, %shape: tensor<2xi64>) -> tensor { + // expected-error @+1 {{with less than 32 elements}} + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) : (tensor<2xi64>, tensor<32xi64>, tensor<2xi64>, tensor<2xi64>, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %begin: tensor, %end: tensor<3xi64>, %strides: tensor<2xi64>, %shape: tensor<2xi64>) -> tensor { + // expected-error @+1 {{have the same number of elements}} + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) : (tensor<2xi64>, tensor, tensor<3xi64>, tensor<2xi64>, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %shape: tensor<2xi64>) -> tensor { + %begin = "tf.Const"() { value = dense<[0, 0]> : tensor<2xi64> } : () -> tensor + %end = "tf.Const"() { value = dense<[5, 10]> : tensor<2xi64> } : () -> tensor + %strides = "tf.Const"() { value = dense<[2, 3, 4]> : tensor<3xi64> } : () -> tensor + + // expected-error @+1 {{have the same number of elements}} + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) : (tensor<2xi64>, tensor, tensor, tensor, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %begin: tensor<2xi64>, %end: tensor<2xi64>, %shape: tensor<2xi64>) -> tensor { + %strides = "tf.Const"() { value = dense<[2, 0]> : tensor<2xi32> } : () -> tensor<2xi32> + + // expected-error @+1 {{requires non-zero strides}} + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) : (tensor<2xi64>, tensor<2xi64>, tensor<2xi64>, tensor<2xi32>, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %begin: tensor<2xi64>, %end: tensor<2xi64>, %strides: tensor<2xi64>, %shape: tensor<2xi64>) -> tensor { + // expected-error @+1 {{cannot have multiple ellipses}} + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) {ellipsis_mask = 3} : (tensor<2xi64>, tensor<2xi64>, tensor<2xi64>, tensor<2xi64>, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @stridedSliceGrad(%dy: tensor<4x8xf32>, %begin: tensor<2xi64>, %end: tensor<2xi64>, %strides: tensor<2xi64>, %shape: tensor<1x2xi64>) -> tensor { + // expected-error @+1 {{'shape' operand must be 1D tensor, but got 2D tensor}} + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %dy) : (tensor<1x2xi64>, tensor<2xi64>, tensor<2xi64>, tensor<2xi64>, tensor<4x8xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @testDynamicStitch(%arg0: tensor<2x2xf32>) -> tensor<2x2xf32> { + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2xf32>) -> tensor<2x2xf32> + return %0 : tensor<2x2xf32> +} + +// ----- + +func @testDynamicStitch() -> tensor<2x2xf32> { + // expected-error @+1 {{requires attribute N with value >= 1}} + %0 = "tf.DynamicStitch"() : () -> (tensor<2x2xf32>) + return %0 : tensor<2x2xf32> +} + +// ----- + +func @testDynamicStitch(%arg0: tensor<2x2xf32>) -> tensor { + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + // expected-error @+1 {{requires non scalar output}} + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2xf32>) -> tensor + return %0 : tensor +} + +// ----- + +func @testDynamicStitch(%arg0: tensor<2x2xf32>) -> tensor<2x2xf32> { + %indices = "tf.Const"() {value = dense<[-1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + // expected-error @+1 {{requires non-negative index values; found -1}} + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2xf32>) -> tensor<2x2xf32> + return %0 : tensor<2x2xf32> +} + +// ----- + +func @testDynamicStitch(%arg0: tensor<3x2xf32>) -> tensor<2x2xf32> { + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + // expected-error @+1 {{requires shape of data with type 'tensor<3x2xf32>' to have prefix matching with shape of the corresponding index type 'tensor<2xi32>'}} + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<3x2xf32>) -> tensor<2x2xf32> + return %0 : tensor<2x2xf32> +} + +// ----- + +func @testDynamicStitch(%arg0: tensor<2xf32>, %arg1: tensor<2x2x3xf32>) -> (tensor<5x2xf32>) { + %indices0 = "tf.Const"() {value = dense<4> : tensor} : () -> tensor + %indices1 = "tf.Const"() {value = dense<[[3, 2], [1, 0]]> : tensor<2x2xi32>} : () -> tensor<2x2xi32> + + // expected-error @+1 {{inconsistent shaped data and index pairs; inferred item shapes [2] and [3] don't match}} + %0 = "tf.DynamicStitch"(%indices0, %indices1, %arg0, %arg1) : (tensor, tensor<2x2xi32>, tensor<2xf32>, tensor<2x2x3xf32>) -> tensor<5x2xf32> + return %0 : tensor<5x2xf32> +} + +// ----- + +func @testDynamicStitch(%arg0: tensor<2x2xf32>) -> tensor<2x2xf32> { + %indices = "tf.Const"() {value = dense<[2, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + // expected-error @+1 {{missing index 1}} + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2xf32>) -> tensor<2x2xf32> + return %0 : tensor<2x2xf32> +} + +// ----- + +func @testDynamicStitch(%arg0: tensor<2x2xf32>) -> tensor<3x2xf32> { + %indices = "tf.Const"() {value = dense<[1, 0]> : tensor<2xi32>} : () -> tensor<2xi32> + // expected-error @+1 {{has invalid output type; should be compatible with inferred type 'tensor<2x2xf32>'}} + %0 = "tf.DynamicStitch"(%indices, %arg0) : (tensor<2xi32>, tensor<2x2xf32>) -> tensor<3x2xf32> + return %0 : tensor<3x2xf32> +} + +// ----- + +func @testDynamicStitch(%arg0: tensor, %arg1: tensor) -> (tensor<*xf32>) { + // expected-error @+1 {{requires shape of data with type 'tensor' to have prefix matching with shape of the corresponding index type 'tensor'}} + %0 = "tf.DynamicStitch"(%arg0, %arg1) : (tensor, tensor) -> tensor<*xf32> + return %0 : tensor<*xf32> +} + +// ----- + +func @testDynamicStitch(%arg0: tensor, %arg1: tensor<2x?xf32>) -> (tensor<2x3x2xf32>) { + %indices0 = "tf.Const"() {value = dense<1> : tensor} : () -> tensor + %indices1 = "tf.Const"() {value = dense<0> : tensor} : () -> tensor + + // expected-error @+1 {{has invalid output type; should be compatible with inferred type 'tensor<2x2x3xf32>'}} + %0 = "tf.DynamicStitch"(%indices0, %indices1, %arg0, %arg1) : (tensor, tensor, tensor, tensor<2x?xf32>) -> tensor<2x3x2xf32> + return %0 : tensor<2x3x2xf32> +} diff --git a/tensorflow/compiler/mlir/tensorflow/tests/tpu_cluster_formation.mlir b/tensorflow/compiler/mlir/tensorflow/tests/tpu_cluster_formation.mlir index e2c81c9c2de..86e6f1bd55b 100644 --- a/tensorflow/compiler/mlir/tensorflow/tests/tpu_cluster_formation.mlir +++ b/tensorflow/compiler/mlir/tensorflow/tests/tpu_cluster_formation.mlir @@ -344,7 +344,8 @@ func @replication(%arg0: tensor, %arg1: tensor, %arg2: tensor) -> // CHECK: %[[OP_B:[0-9]*]] = "tf.opB" // CHECK: %[[OP_C:[0-9]*]] = "tf.opC" // CHECK: %[[REPLICATE:[0-9]*]]:4 = tf_device.replicate -// CHECK-SAME: ([%[[ARG_0]], %[[OP_A]]] as %[[RI_0:[a-z0-9]*]]: tensor, [%[[OP_B]], %[[ARG_1]]] as %[[RI_1:[a-z0-9]*]]: tensor) +// CHECK-DAG: [%[[ARG_0]], %[[OP_A]]] as %[[RI_0:[a-z0-9]*]]: tensor +// CHECK-DAG: [%[[OP_B]], %[[ARG_1]]] as %[[RI_1:[a-z0-9]*]]: tensor // CHECK-SAME: n = 2 : i32 // CHECK-NEXT: %[[LAUNCH:[0-9]*]]:2 = "tf_device.launch"() ( { // CHECK: %[[OP_D:[0-9]*]] = "tf.opD"(%[[RI_0]], %[[RI_1]], %[[ARG_2]], %[[OP_C]]) @@ -357,6 +358,32 @@ func @replication(%arg0: tensor, %arg1: tensor, %arg2: tensor) -> // CHECK: return %[[REPLICATE]]#0, %[[REPLICATE]]#3 +// Test `tf.TPUReplicatedInput` ops are sorted by their `index` attribute. +// Non-negative `index` should preceed `index` of -1, and ordering of ops with +// `index` of -1 does not matter. +// CHECK-LABEL: func @sort_replicated_input +// CHECK-SAME: (%[[ARG_0:.*]]: tensor, %[[ARG_1:.*]]: tensor, %[[ARG_2:.*]]: tensor, %[[ARG_3:.*]]: tensor, %[[ARG_4:.*]]: tensor, %[[ARG_5:.*]]: tensor) +func @sort_replicated_input(%arg0: tensor, %arg1: tensor, %arg2: tensor, %arg3: tensor, %arg4: tensor, %arg5: tensor) { + %0 = "tf.TPUReplicatedInput"(%arg0, %arg0) {index = -1 : i64} : (tensor, tensor) -> tensor + %1 = "tf.TPUReplicatedInput"(%arg1, %arg1) {index = 2 : i64} : (tensor, tensor) -> tensor + %2 = "tf.TPUReplicatedInput"(%arg2, %arg2) {index = 0 : i64} : (tensor, tensor) -> tensor + %3 = "tf.TPUReplicatedInput"(%arg3, %arg3) {index = -1 : i64} : (tensor, tensor) -> tensor + %4 = "tf.TPUReplicatedInput"(%arg4, %arg4) {index = 1 : i64} : (tensor, tensor) -> tensor + %5 = "tf.TPUReplicatedInput"(%arg5, %arg5) {index = -1 : i64} : (tensor, tensor) -> tensor + "tf.opA"(%0, %1, %2, %3, %4, %5) {_tpu_replicate = "replicate", device = "device"} : (tensor, tensor, tensor, tensor, tensor, tensor) -> () + "tf.TPUReplicateMetadata"() {_tpu_replicate = "replicate", device = "device", num_replicas = 2, topology = "topology"} : () -> () + return +} + +// CHECK: tf_device.replicate +// CHECK-SAME: [%[[ARG_2]], %[[ARG_2]]] as %{{[a-z0-9]*}} +// CHECK-SAME: [%[[ARG_4]], %[[ARG_4]]] as %{{[a-z0-9]*}} +// CHECK-SAME: [%[[ARG_1]], %[[ARG_1]]] as %{{[a-z0-9]*}} +// CHECK-DAG: [%[[ARG_0]], %[[ARG_0]]] as %{{[a-z0-9]*}} +// CHECK-DAG: [%[[ARG_3]], %[[ARG_3]]] as %{{[a-z0-9]*}} +// CHECK-DAG: [%[[ARG_5]], %[[ARG_5]]] as %{{[a-z0-9]*}} + + // ----- @@ -441,3 +468,44 @@ func @leftover_replicated_output(%arg0: tensor) { %0:2 = "tf.TPUReplicatedOutput"(%arg0) : (tensor) -> (tensor, tensor) return } + + +// ----- + + +// Test bad TPUReplicatedInput positive `index` attribute. +func @bad_positive_index_input(%arg0: tensor) { + // expected-error@+1 {{'tf.TPUReplicatedInput' index is not in range [-1, 1), got 1}} + %0 = "tf.TPUReplicatedInput"(%arg0, %arg0) {index = 1 : i64} : (tensor, tensor) -> tensor + "tf.opA"(%0) {_tpu_replicate = "replicate", device = "device", name = "name"} : (tensor) -> () + "tf.TPUReplicateMetadata"() {_tpu_replicate = "replicate", device = "device", num_replicas = 2, topology = "topology"} : () -> () + return +} + + +// ----- + + +// Test bad TPUReplicatedInput negative `index` attribute. +func @bad_negative_index_input(%arg0: tensor) { + // expected-error@+1 {{'tf.TPUReplicatedInput' index is not in range [-1, 1), got -2}} + %0 = "tf.TPUReplicatedInput"(%arg0, %arg0) {index = -2 : i64} : (tensor, tensor) -> tensor + "tf.opA"(%0) {_tpu_replicate = "replicate", device = "device", name = "name"} : (tensor) -> () + "tf.TPUReplicateMetadata"() {_tpu_replicate = "replicate", device = "device", num_replicas = 2, topology = "topology"} : () -> () + return +} + + +// ----- + + +// Test TPUReplicatedInput with conflicting `index` attribute. This will result +// in gaps in the TPUReplicatedInput ordering. +func @input_index_gaps(%arg0: tensor) { + // expected-error@+1 {{failed to sort 'tf.TPUReplicatedInput' ops, gap(s) found in indices}} + %0 = "tf.TPUReplicatedInput"(%arg0, %arg0) {index = 1 : i64} : (tensor, tensor) -> tensor + %1 = "tf.TPUReplicatedInput"(%arg0, %arg0) {index = 1 : i64} : (tensor, tensor) -> tensor + "tf.opA"(%0, %1) {_tpu_replicate = "replicate", device = "device", name = "name"} : (tensor, tensor) -> () + "tf.TPUReplicateMetadata"() {_tpu_replicate = "replicate", device = "device", num_replicas = 2, topology = "topology"} : () -> () + return +} diff --git a/tensorflow/compiler/mlir/tensorflow/tests/tpu_dynamic_padding_mapper.mlir b/tensorflow/compiler/mlir/tensorflow/tests/tpu_dynamic_padding_mapper.mlir new file mode 100644 index 00000000000..99461a5eaf9 --- /dev/null +++ b/tensorflow/compiler/mlir/tensorflow/tests/tpu_dynamic_padding_mapper.mlir @@ -0,0 +1,329 @@ +// RUN: tf-opt %s -split-input-file -verify-diagnostics -tf-tpu-dynamic-padding | FileCheck %s --dump-input=fail + +// Test single argument with padding map lifted to associated encapsulated +// function. +// +// Padding map "\10\02\18\01": +// arg_index: 0 +// shape_index: 2 +// padding_arg_index: 1 +// CHECK-LABEL: func @single_arg_single_shape +func @single_arg_single_shape(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + "tf_device.launch_func"(%ri_0, %ri_1) {device = "", func = @func0, padding_map = ["\10\02\18\01"]} : (tensor, tensor) -> () + tf_device.return + } + return +} + +// CHECK-LABEL: func @func0 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor {xla_hlo.padding_map = {padding_arg_indices = [1 : i32], shape_indices = [2 : i32]}}, %{{[a-z0-9]+}}: tensor) +func @func0(%arg0: tensor, %arg1: tensor) { + return +} + +// Test single argument with multiple padding maps lifted to associated +// encapsulated function. +// +// Padding map "\10\02\18\01": +// arg_index: 0 +// shape_index: 2 +// padding_arg_index: 1 +// +// Padding map "\10\03\18\02": +// arg_index: 0 +// shape_index: 3 +// padding_arg_index: 2 +// CHECK-LABEL: func @single_arg_multiple_shapes +func @single_arg_multiple_shapes(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor, [%arg0, %arg0] as %ri_2: tensor) {n = 2 : i32} { + "tf_device.launch_func"(%ri_0, %ri_1, %ri_2) {device = "", func = @func1, padding_map = ["\10\02\18\01", "\10\03\18\02"]} : (tensor, tensor, tensor) -> () + tf_device.return + } + return +} + +// CHECK-LABEL: func @func1 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor {xla_hlo.padding_map = {padding_arg_indices = [1 : i32, 2 : i32], shape_indices = [2 : i32, 3 : i32]}}, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor) +func @func1(%arg0: tensor, %arg1: tensor, %arg2: tensor) { + return +} + +// Test multiple arguments with multiple padding maps lifted to associated +// encapsulated function. +// +// Padding map "\10\02\18\01": +// arg_index: 0 +// shape_index: 2 +// padding_arg_index: 1 +// +// Padding map "\10\03\18\02": +// arg_index: 0 +// shape_index: 3 +// padding_arg_index: 2 +// +// Padding map "\08\04\10\01\18\03": +// arg_index: 4 +// shape_index: 1 +// padding_arg_index: 3 +// CHECK-LABEL: func @multiple_args +func @multiple_args(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor, [%arg0, %arg0] as %ri_2: tensor, [%arg0, %arg0] as %ri_3: tensor, [%arg0, %arg0] as %ri_4: tensor) {n = 2 : i32} { + "tf_device.launch_func"(%ri_0, %ri_1, %ri_2, %ri_3, %ri_4) {device = "", func = @func2, padding_map = ["\10\02\18\01", "\10\03\18\02", "\08\04\10\01\18\03"]} : (tensor, tensor, tensor, tensor, tensor) -> () + tf_device.return + } + return +} + +// CHECK-LABEL: func @func2 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor {xla_hlo.padding_map = {padding_arg_indices = [1 : i32, 2 : i32], shape_indices = [2 : i32, 3 : i32]}}, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor {xla_hlo.padding_map = {padding_arg_indices = [3 : i32], shape_indices = [1 : i32]}}) +func @func2(%arg0: tensor, %arg1: tensor, %arg2: tensor, %arg3: tensor, %arg4: tensor) { + return +} + +// Test remapping of replicated inputs to encapsulated function arguments. +// +// Padding map "\10\02\18\01": +// arg_index: 0 +// shape_index: 2 +// padding_arg_index: 1 +// CHECK-LABEL: func @remap_indices +func @remap_indices(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + "tf_device.launch_func"(%ri_1, %arg0, %ri_0) {device = "", func = @func3, padding_map = ["\10\02\18\01"]} : (tensor, tensor, tensor) -> () + tf_device.return + } + return +} + +// CHECK-LABEL: func @func3 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor {xla_hlo.padding_map = {padding_arg_indices = [0 : i32], shape_indices = [2 : i32]}}) +func @func3(%arg0: tensor, %arg1: tensor, %arg2: tensor) { + return +} + +// Test no padding maps are added to encapsulated function if there is no +// replication. +// +// Padding map "\10\02\18\01": +// arg_index: 0 +// shape_index: 2 +// padding_arg_index: 1 +// CHECK-LABEL: func @no_replicate +func @no_replicate(%arg0: tensor) { + "tf_device.launch_func"(%arg0, %arg0, %arg0) {device = "", func = @func4, padding_map = ["\10\02\18\01"]} : (tensor, tensor, tensor) -> () + return +} + +// CHECK-LABEL: func @func4 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor) +func @func4(%arg0: tensor, %arg1: tensor, %arg2: tensor) { + return +} + +// Test encapsulated function is not modified when there are no padding maps. +// CHECK-LABEL: func @no_padding_map +func @no_padding_map(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + "tf_device.launch_func"(%ri_1, %arg0, %ri_0) {device = "", func = @func5} : (tensor, tensor, tensor) -> () + tf_device.return + } + return +} + +// CHECK-LABEL: func @func5 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor) +func @func5(%arg0: tensor, %arg1: tensor, %arg2: tensor) { + return +} + +// Test encapsulated function is not modified when padding maps is empty. +// CHECK-LABEL: func @empty_padding_map +func @empty_padding_map(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + "tf_device.launch_func"(%ri_1, %arg0, %ri_0) {device = "", func = @func6, padding_map = []} : (tensor, tensor, tensor) -> () + tf_device.return + } + return +} + +// CHECK-LABEL: func @func6 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor, %{{[a-z0-9]+}}: tensor) +func @func6(%arg0: tensor, %arg1: tensor, %arg2: tensor) { + return +} + +// Test unused padding map is not added to the encapsulated function. +// +// Padding map "\10\02\18\01": +// arg_index: 0 +// shape_index: 2 +// padding_arg_index: 1 +// CHECK-LABEL: func @unused_padding_map +func @unused_padding_map(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + "tf_device.launch_func"(%ri_1) {device = "", func = @func7, padding_map = ["\10\02\18\01"]} : (tensor) -> () + tf_device.return + } + return +} + +// CHECK-LABEL: func @func7 +// CHECK-SAME: (%{{[a-z0-9]+}}: tensor) +func @func7(%arg0: tensor) { + return +} + +// ----- + +// Test bad padding map attribute (not an array). +func @bad_padding_map() { + tf_device.replicate {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op requires 'padding_map' array attribute}} + "tf_device.launch_func"() {device = "", func = @_func, padding_map = 0 : i32} : () -> () + tf_device.return + } + return +} + +func @_func() { + return +} + +// ----- + +// Test bad padding map attribute (element in array is not a string). +func @bad_padding_map_element() { + tf_device.replicate {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op bad 'padding_map' attribute at index 0, not a string}} + "tf_device.launch_func"() {device = "", func = @_func, padding_map = [0 : i32]} : () -> () + tf_device.return + } + return +} + +func @_func() { + return +} + +// ----- + +// Test unparsable padding map. +func @bad_padding_map_proto() { + tf_device.replicate {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op bad 'padding_map' attribute at index 0, failed to parse 'z' as tensorflow::tpu::PaddingMap}} + "tf_device.launch_func"() {device = "", func = @_func, padding_map = ["z"]} : () -> () + tf_device.return + } + return +} + +func @_func() { + return +} + +// ----- + +// Test negative arg index. +// +// Padding map "\08\FF\FF\FF\FF\FF\FF\FF\FF\FF\01\10\02\18\01": +// arg_index: -1 +// shape_index: 2 +// padding_arg_index: 1 +func @negative_arg_index(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op bad 'padding_map' attribute at index 0, arg_index must be in [0, 2), got -1}} + "tf_device.launch_func"(%ri_0, %ri_1) {device = "", func = @_func, padding_map = ["\08\FF\FF\FF\FF\FF\FF\FF\FF\FF\01\10\02\18\01"]} : (tensor, tensor) -> () + tf_device.return + } + return +} + +func @_func(%arg0: tensor, %arg1: tensor) { + return +} + +// ----- + +// Test out of bound arg index. +// +// Padding map "\08\02\10\02\18\01": +// arg_index: 2 +// shape_index: 2 +// padding_arg_index: 1 +func @bad_arg_index(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op bad 'padding_map' attribute at index 0, arg_index must be in [0, 2), got 2}} + "tf_device.launch_func"(%ri_0, %ri_1) {device = "", func = @_func, padding_map = ["\08\02\10\02\18\01"]} : (tensor, tensor) -> () + tf_device.return + } + return +} + +func @_func(%arg0: tensor, %arg1: tensor) { + return +} + +// ----- + +// Test negative padding arg index. +// +// Padding map "\08\01\10\02\18\FF\FF\FF\FF\FF\FF\FF\FF\FF\01": +// arg_index: 1 +// shape_index: 2 +// padding_arg_index: -1 +func @negative_padding_arg_index(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op bad 'padding_map' attribute at index 0, padding_arg_index must be in [0, 2), got -1}} + "tf_device.launch_func"(%ri_0, %ri_1) {device = "", func = @_func, padding_map = ["\08\01\10\02\18\FF\FF\FF\FF\FF\FF\FF\FF\FF\01"]} : (tensor, tensor) -> () + tf_device.return + } + return +} + +func @_func(%arg0: tensor, %arg1: tensor) { + return +} + +// ----- + +// Test out of bound padding arg index. +// +// Padding map "\08\01\10\02\18\02": +// arg_index: 1 +// shape_index: 2 +// padding_arg_index: 2 +func @bad_padding_arg_index(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op bad 'padding_map' attribute at index 0, padding_arg_index must be in [0, 2), got 2}} + "tf_device.launch_func"(%ri_0, %ri_1) {device = "", func = @_func, padding_map = ["\08\01\10\02\18\02"]} : (tensor, tensor) -> () + tf_device.return + } + return +} + +func @_func(%arg0: tensor, %arg1: tensor) { + return +} + +// ----- + +// Test arg that requires a padding arg but padding arg is not an arg to the +// encapsulated function. +// +// Padding map "\10\02\18\01": +// arg_index: 0 +// shape_index: 2 +// padding_arg_index: 1 +func @missing_padding_arg(%arg0: tensor) { + tf_device.replicate([%arg0, %arg0] as %ri_0: tensor, [%arg0, %arg0] as %ri_1: tensor) {n = 2 : i32} { + // expected-error@+1 {{'tf_device.launch_func' op bad 'padding_map' attribute at index 0, unused padding_arg_index 1}} + "tf_device.launch_func"(%ri_0) {device = "", func = @_func, padding_map = ["\10\02\18\01"]} : (tensor) -> () + tf_device.return + } + return +} + +func @_func(%arg0: tensor) { + return +} diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/bridge.cc b/tensorflow/compiler/mlir/tensorflow/transforms/bridge.cc index c08d17104ea..7ef3449e3e9 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/bridge.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/bridge.cc @@ -40,6 +40,7 @@ void CreateTPUBridge(OpPassManager &pm) { pm.addPass(TF::CreateResourceDeviceInferencePass()); pm.addPass(TFDevice::CreateClusterOutliningPass()); + pm.addPass(CreateTPUDynamicPaddingMapperPass()); pm.addPass(CreateTPURewritePass()); pm.addNestedPass(TFDevice::CreateReplicateInvariantOpHoistingPass()); pm.addNestedPass(CreateFunctionalToExecutorDialectConversionPass()); diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.cc b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.cc index b70d14fd43b..456f90ed725 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.cc @@ -20,7 +20,23 @@ limitations under the License. namespace mlir { namespace TF { +namespace { +// Returns int or float DenseElementsAttr with scalar shape with the given +// element type and the integer value. +static DenseElementsAttr GetScalarOfType(Type ty, int64_t raw_value) { + RankedTensorType scalar_ty = RankedTensorType::get({}, ty); + if (auto float_ty = ty.dyn_cast_or_null()) { + FloatAttr attr = FloatAttr::get(float_ty, raw_value); + return DenseElementsAttr::get(scalar_ty, attr); + } + + auto int_ty = ty.cast(); + IntegerAttr attr = IntegerAttr::get(int_ty, raw_value); + return DenseElementsAttr::get(scalar_ty, attr); +} + #include "tensorflow/compiler/mlir/tensorflow/transforms/generated_decompose_resource_ops.inc" +} // namespace void PopulateDecomposeResourceOpsPatterns(MLIRContext *context, OwningRewritePatternList *patterns) { diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.h b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.h index 813fc649059..3a816233fdf 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.h +++ b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.h @@ -25,6 +25,9 @@ namespace TF { // Populates rewrite patterns that decompose composite resource operations into // primitive ones like ReadVariableOp, AssignVariableOp and other computations // to facilitate transformations like resource op lifting. +// NOTE: These patterns do not support `use_locking=true` for a lot of resource +// operations. So decomposition may not be correct outside of backends like XLA, +// which automatically locks all resource variables. void PopulateDecomposeResourceOpsPatterns(MLIRContext *context, OwningRewritePatternList *patterns); diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.td b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.td index 29c99cdc3d0..3c98f30de7b 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.td +++ b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops.td @@ -16,6 +16,11 @@ include "mlir/IR/OpBase.td" include "mlir/Dialect/StandardOps/Ops.td" include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.td" +// Here, the element type can be any integer or float type. But, note that only +// 32 bit integers are supported for the values. +class GetScalarOfType : NativeCodeCall< + "GetScalarOfType(getElementTypeOrSelf($0)," # value # ")">; + def CreateTFReadVariableOp: NativeCodeCall< "$_builder.create(" " $0.getLoc()," @@ -52,7 +57,7 @@ def DecomposeAssignSubVariableOp : // attribute. def DecomposeResourceApplyGradientDescentOp : Pat< - (TF_ResourceApplyGradientDescentOp:$src_op $resource, $alpha, $delta, $_), + (TF_ResourceApplyGradientDescentOp:$src_op $resource, $alpha, $delta, BoolAttr:$_), (TF_AssignVariableOp $resource, (TF_SubOp @@ -61,3 +66,149 @@ def DecomposeResourceApplyGradientDescentOp : ) ) >; + +// This decomposition is only correct inside XLA as it ignores use_locking +// attribute. +// accum = accum * momentum - lr * grad +// var += accum +def DecomposeResourceApplyKerasMomentumOpNonNesterov : + Pattern< + (TF_ResourceApplyKerasMomentumOp:$src_op + $var_resource, $accum_resource, $lr, $grad, $momentum, + BoolAttr:$_, ConstBoolAttrFalse:$use_nesterov + ), + [(TF_SubOp:$accum_new + (TF_MulOp + (CreateTFReadVariableOp $src_op, $grad, $accum_resource), + $momentum + ), + (TF_MulOp $grad, $lr)), + (TF_AssignVariableOp $accum_resource, $accum_new), + (TF_AssignAddVariableOp $var_resource, $accum_new) + ] + >; + +// This decomposition is only correct inside XLA as it ignores use_locking +// attribute. +// accum = accum * momentum - lr * grad +// var += accum * momentum - lr * grad +def DecomposeResourceApplyKerasMomentumOpNesterov : + Pattern< + (TF_ResourceApplyKerasMomentumOp:$src_op + $var_resource, $accum_resource, $lr, $grad, $momentum, + BoolAttr:$_, ConstBoolAttrTrue:$use_nesterov + ), + [(TF_SubOp:$accum_new + (TF_MulOp + (CreateTFReadVariableOp $src_op, $grad, $accum_resource), + $momentum + ), + (TF_MulOp:$grad_lr $grad, $lr) + ), + (TF_AssignVariableOp $accum_resource, $accum_new), + (TF_AssignAddVariableOp + $var_resource, + (TF_SubOp + (TF_MulOp $accum_new, $momentum), + $grad_lr + ) + ) + ] + >; + +// Pattern to Decompose ResourceApplyAdam without Nesterov momentum. +// This decomposition is only correct inside XLA as it ignores use_locking +// attribute. +// alpha <- learning_rate * sqrt(1 - beta2^t) / (1 - beta1^t) +// m_t <- beta1 * m_{t-1} + (1 - beta1) * g_t +// v_t <- beta2 * v_{t-1} + (1 - beta2) * g_t * g_t +// variable <- variable - alpha * m_t / (sqrt(v_t) + epsilon) +def DecomposeResourceApplyAdamNonNesterov : + Pattern< + (TF_ResourceApplyAdamOp:$src_op + $var_resource, $m_resource, $v_resource, $beta1_power, $beta2_power, $lr, + $beta1, $beta2, $epsilon, $grad, BoolAttr:$_, + ConstBoolAttrFalse:$use_nesterov), + [ + (TF_ConstOp:$one (GetScalarOfType<1> $grad)), + (TF_MulOp:$alpha + $lr, + (TF_DivOp + (TF_SqrtOp (TF_SubOp $one, $beta2_power)), + (TF_SubOp $one, $beta1_power) + ) + ), + (TF_AddV2Op:$new_m + (TF_MulOp $beta1, (CreateTFReadVariableOp $src_op, $grad, $m_resource)), + (TF_MulOp (TF_SubOp $one, $beta1), $grad) + ), + (TF_AddV2Op:$new_v + (TF_MulOp $beta2, (CreateTFReadVariableOp $src_op, $grad, $v_resource)), + (TF_MulOp + (TF_SubOp $one, $beta2), + (TF_SquareOp $grad) + ) + ), + (TF_AssignSubVariableOp + $var_resource, + (TF_DivOp + (TF_MulOp $alpha, $new_m), + (TF_AddV2Op (TF_SqrtOp $new_v), $epsilon) + ) + ), + (TF_AssignVariableOp $m_resource, $new_m), + (TF_AssignVariableOp $v_resource, $new_v) + ] + >; + +// Pattern to Decompose ResourceApplyAdam with Nesterov momentum. +// This decomposition is only correct inside XLA as it ignores use_locking +// attribute. +// alpha <- learning_rate * sqrt(1 - beta2^t) / (1 - beta1^t) +// m_t <- beta1 * m_{t-1} + (1 - beta1) * g_t +// v_t <- beta2 * v_{t-1} + (1 - beta2) * g_t * g_t +// variable <- variable - (alpha * (m_t * beta1 + (1 - beta1) * g_t) / +// (sqrt(v_t) + epsilon)) +def DecomposeResourceApplyAdamNesterov : + Pattern< + (TF_ResourceApplyAdamOp:$src_op + $var_resource, $m_resource, $v_resource, $beta1_power, $beta2_power, $lr, + $beta1, $beta2, $epsilon, $grad, BoolAttr:$_, + ConstBoolAttrTrue:$use_nesterov), + [ + (TF_ConstOp:$one (GetScalarOfType<1> $grad)), + (TF_MulOp:$alpha + $lr, + (TF_DivOp + (TF_SqrtOp (TF_SubOp $one, $beta2_power)), + (TF_SubOp $one, $beta1_power) + ) + ), + (TF_AddV2Op:$new_m + (TF_MulOp $beta1, (CreateTFReadVariableOp $src_op, $grad, $m_resource)), + (TF_MulOp (TF_SubOp $one, $beta1), $grad) + ), + (TF_AddV2Op:$new_v + (TF_MulOp $beta2, (CreateTFReadVariableOp $src_op, $grad, $v_resource)), + (TF_MulOp + (TF_SubOp $one, $beta2), + (TF_SquareOp $grad) + ) + ), + (TF_AssignSubVariableOp + $var_resource, + (TF_DivOp + (TF_MulOp + $alpha, + (TF_AddV2Op + (TF_MulOp $new_m, $beta1), + (TF_MulOp (TF_SubOp $one, $beta1), $grad) + ) + ), + (TF_AddV2Op (TF_SqrtOp $new_v), $epsilon) + ) + ), + (TF_AssignVariableOp $m_resource, $new_m), + (TF_AssignVariableOp $v_resource, $new_v) + ] + >; diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops_pass.cc b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops_pass.cc index b7be4ff8742..61fc12d6ab9 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops_pass.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/decompose_resource_ops_pass.cc @@ -34,6 +34,9 @@ namespace { // %res_val = tf.ReadVariableOp(%res) // %1 = tf.AddV2(%res_val, %0) // tf.AssignVariableOp(%res, %1) +// NOTE: This pass does not support `use_locking=true` for a lot of resource +// operations. So decomposition may not be correct outside of backends like XLA, +// which automatically locks all resource variables. struct DecomposeResourceOps : public FunctionPass { void runOnFunction() override { // Add lowering patterns to the list. diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/graph_pruning.cc b/tensorflow/compiler/mlir/tensorflow/transforms/graph_pruning.cc index 882e769ff4c..23cdebc4323 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/graph_pruning.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/graph_pruning.cc @@ -86,17 +86,36 @@ namespace { // This transformation pass prunes a TF graph eliminating dead-nodes. struct GraphPruning : public FunctionPass { void runOnFunction() override { - getFunction().walk([](tf_executor::GraphOp graph) { PruneGraph(graph); }); + FuncOp func = getFunction(); + if (func.getName() == "main" && skip_main_func) return; + func.walk([](tf_executor::GraphOp graph) { PruneGraph(graph); }); } + + struct Options : public PassOptions { + Option skip_main_func{ + *this, "skip-main-func", + llvm::cl::desc("skip graph pruning for main function"), + llvm::cl::init(false)}; + }; + + explicit GraphPruning(bool skip_main_func) + : FunctionPass(), skip_main_func(skip_main_func) {} + + explicit GraphPruning(const Options& option) + : GraphPruning(option.skip_main_func) {} + + private: + bool skip_main_func; }; } // namespace -std::unique_ptr> CreateTFExecutorGraphPruningPass() { - return std::make_unique(); +std::unique_ptr> CreateTFExecutorGraphPruningPass( + bool skip_main_func) { + return std::make_unique(skip_main_func); } -static PassRegistration pass( +static PassRegistration pass( "tf-executor-graph-pruning", "Prune unreachable nodes in a TensorFlow Graph."); diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/lower_tf.cc b/tensorflow/compiler/mlir/tensorflow/transforms/lower_tf.cc index 89941c2fab4..f19ecaa5c0a 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/lower_tf.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/lower_tf.cc @@ -17,6 +17,8 @@ limitations under the License. #include +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" #include "mlir/IR/Attributes.h" // TF:local_config_mlir #include "mlir/IR/Diagnostics.h" // TF:local_config_mlir #include "mlir/IR/MLIRContext.h" // TF:local_config_mlir @@ -58,22 +60,13 @@ static DenseIntElementsAttr GetI64ElementsAttrForSeq(int start, int end, static DenseElementsAttr GetScalarOfType(Type ty, int64_t raw_value) { RankedTensorType scalar_ty = RankedTensorType::get({}, ty); if (auto float_ty = ty.dyn_cast_or_null()) { - const auto &float_semantics = float_ty.getFloatSemantics(); - Builder builder(ty.getContext()); - if (&float_semantics == &APFloat::IEEEsingle()) - return DenseElementsAttr::get(scalar_ty, - builder.getF32FloatAttr(raw_value)); - if (&float_semantics == &APFloat::IEEEdouble()) - return DenseElementsAttr::get(scalar_ty, - builder.getF64FloatAttr(raw_value)); - - assert(false && "unhandled IEEE float kind"); - return {}; + FloatAttr attr = FloatAttr::get(float_ty, raw_value); + return DenseElementsAttr::get(scalar_ty, attr); } auto int_ty = ty.cast(); - APInt value(int_ty.getWidth(), raw_value, /*isSigned=*/true); - return DenseElementsAttr::get(scalar_ty, value); + IntegerAttr attr = IntegerAttr::get(int_ty, raw_value); + return DenseElementsAttr::get(scalar_ty, attr); } // Returns reduction indices to use while lowering tf.BiasAddGrad op to tf.Sum @@ -84,7 +77,7 @@ DenseIntElementsAttr GetBiasAddGradReductionIndices(int64_t rank, tensorflow::TensorFormat format; if (!FormatFromString(data_format.getValue().str(), &format)) return {}; - // Reudce along all dimensions except the feature dimension. + // Reduce along all dimensions except the feature dimension. int64_t feature_dim = GetTensorFeatureDimIndex(rank, format); llvm::SmallVector dims_to_reduce(rank - 1); std::iota(dims_to_reduce.begin(), dims_to_reduce.begin() + feature_dim, 0); @@ -143,6 +136,101 @@ class LowerAddNOp : public OpRewritePattern { } }; +// Lowers DynamicStitch op with constant indices and with static input and +// output shapes using Reshape, UnPack and ConcatV2 op. +// +// %indices0 = "tf.Const"() {value = dense<4> : tensor} +// %indices1 = "tf.Const"() {value = dense<[[3, 2], [1, 0]]> : +// tensor<2x2xi32>} %0 = "tf.DynamicStitch"(%indices0, %indices1, %arg0, +// %arg1) +// : (tensor, tensor<2x2xi32>, tensor<2xf32>, tensor<2x2x2xf32>) +// -> tensor<5x2xf32> +// +// is lowered to +// +// %shape = "tf.Const"() {value = dense<[-1, 2]> : tensor<2xi64>} +// %inp0 = "tf.Reshape"(%arg0, %shape) +// : (tensor<2xf32>, tensor<2xi64>) -> tensor<1x2xf32> +// %inp1 = "tf.Reshape"(%arg1, %shape) +// : (tensor<2x2x2xf32>, tensor<2xi64>) -> tensor<4x2xf32> +// %items0 = "tf.Unpack"(%[[INP0]]) {axis = 0 : i64} +// : (tensor<1x2xf32>) -> tensor<2xf32> +// %items1:4 = "tf.Unpack"(%[[INP1]]) {axis = 0 : i64} +// : (tensor<4x2xf32>) -> (tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, +// tensor<2xf32>) +// %axis = "tf.Const"() {value = dense<0> : tensor} +// %0 = "tf.ConcatV2"(items1#3, items1#2, items1#1, items1#0, %items0, %axis) +// : (tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, +// tensor<2xf32>, tensor) -> tensor<5x2xf32> +// +class LowerDynamicStitchOp : public OpRewritePattern { + public: + explicit LowerDynamicStitchOp(MLIRContext *context) + : OpRewritePattern(context) {} + + PatternMatchResult matchAndRewrite(DynamicStitchOp op, + PatternRewriter &rewriter) const override { + // Static output type is used to compute intermediate values. Note that the + // output type doesn't have to be static but if input types and indices are + // constant, then the output type can be statically determined. + RankedTensorType out_ty = op.getType().dyn_cast(); + if (!out_ty || !out_ty.hasStaticShape()) return matchFailure(); + + // Extract out all the constant indices' attributes and verify that data + // types are static. + SmallVector indices; + indices.reserve(op.N()); + for (auto it : llvm::zip(op.indices(), op.data())) { + Value *index = std::get<0>(it); + Value *data = std::get<1>(it); + + DenseIntElementsAttr index_attr; + if (!matchPattern(index, m_Constant(&index_attr))) return matchFailure(); + indices.push_back(index_attr); + + RankedTensorType data_ty = data->getType().dyn_cast(); + if (!data_ty || !data_ty.hasStaticShape()) return matchFailure(); + } + + // Compute type of each of the items and shape to use while reshaping inputs + // so that they can be unpacked to extract out individual items. + ArrayRef item_shape = out_ty.getShape().drop_front(1); + auto item_ty = RankedTensorType::get(item_shape, out_ty.getElementType()); + + SmallVector packed_shape; + packed_shape.push_back(-1); + packed_shape.append(item_shape.begin(), item_shape.end()); + Location loc = op.getLoc(); + auto packed_shape_val = rewriter.create( + loc, GetI64ElementsAttr(packed_shape, &rewriter)); + + // Prepare each of the output item by unpacking data and then putting it to + // the specified index. + SmallVector values(out_ty.getDimSize(0)); + for (auto it : llvm::zip(indices, op.data())) { + DenseIntElementsAttr index_attr = std::get<0>(it); + Value *data = std::get<1>(it); + + auto reshaped_data = + rewriter.create(loc, data, packed_shape_val); + auto num_items = + reshaped_data.getType().cast().getShape()[0]; + auto items = rewriter.create( + loc, SmallVector(num_items, item_ty), reshaped_data, + /*axis=*/APInt(64, 0)); + for (auto index_item : llvm::zip(index_attr, items.getResults())) { + int64_t output_index = std::get<0>(index_item).getSExtValue(); + Value *item = std::get<1>(index_item); + values[output_index] = item; + } + } + + auto axis = rewriter.create(loc, rewriter.getI64IntegerAttr(0)); + rewriter.replaceOpWithNewOp(op, op.getType(), values, axis); + return matchSuccess(); + } +}; + // Lowers Pack op to ConcatV2 op after changing shape of the inputs with // ExpandDims op. // @@ -193,8 +281,7 @@ class LowerPackOp : public OpRewritePattern { void PopulateLoweringTFPatterns(MLIRContext *context, OwningRewritePatternList *patterns) { - patterns->insert(context); - patterns->insert(context); + patterns->insert(context); populateWithGenerated(context, patterns); } diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/passes.h b/tensorflow/compiler/mlir/tensorflow/transforms/passes.h index fca1c02bc62..c9c97735848 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/passes.h +++ b/tensorflow/compiler/mlir/tensorflow/transforms/passes.h @@ -79,7 +79,8 @@ std::unique_ptr> CreateSwitchFoldPass(); std::unique_ptr> CreateTFExecutorIslandCoarseningPass(); // Create a pass to prune tf_executor.graph from dead nodes. -std::unique_ptr> CreateTFExecutorGraphPruningPass(); +std::unique_ptr> CreateTFExecutorGraphPruningPass( + bool skip_main_func = false); // Prunes unreachable operations of a tf_executor.graph operation. void PruneGraph(GraphOp graph); @@ -129,6 +130,10 @@ namespace TFTPU { // `_tpu_replicate` attribute. std::unique_ptr> CreateTPUClusterFormationPass(); +// Creates a pass that remaps and assigns padding map from a +// `tf_device.launch_func` `padding_map` attribute to its encapsulated function. +std::unique_ptr> CreateTPUDynamicPaddingMapperPass(); + // Creates a pass that rewrites `tf_device.launch_func` on TPUs into TPU runtime // ops. std::unique_ptr> CreateTPURewritePass(); diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/resource_device_inference.cc b/tensorflow/compiler/mlir/tensorflow/transforms/resource_device_inference.cc index 616c2cb10e8..6dc3e87f8ec 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/resource_device_inference.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/resource_device_inference.cc @@ -201,8 +201,7 @@ void ResourceDeviceInference::runOnModule() { // Helper that propagates an op's recorded operand device assignments to its // called function's arguments. auto propagate_operands_to_callee_arguments = - [&](Operation* caller, - llvm::iterator_range caller_operands, + [&](Operation* caller, Operation::operand_range caller_operands, llvm::StringRef called_func_name, const PerFunctionResult& caller_res) { auto callee = diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference.cc b/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference.cc index 812100ced64..39b7fbb4d07 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference.cc @@ -17,17 +17,21 @@ limitations under the License. #include #include +#include #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/iterator_range.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/FormatVariadic.h" #include "mlir/Dialect/StandardOps/Ops.h" // TF:local_config_mlir #include "mlir/IR/Block.h" // TF:local_config_mlir #include "mlir/IR/Builders.h" // TF:local_config_mlir +#include "mlir/IR/Diagnostics.h" // TF:local_config_mlir #include "mlir/IR/Location.h" // TF:local_config_mlir #include "mlir/IR/Operation.h" // TF:local_config_mlir #include "mlir/IR/StandardTypes.h" // TF:local_config_mlir +#include "mlir/IR/SymbolTable.h" // TF:local_config_mlir #include "mlir/Pass/Pass.h" // TF:local_config_mlir #include "mlir/Pass/PassRegistry.h" // TF:local_config_mlir #include "mlir/Support/LLVM.h" // TF:local_config_mlir @@ -64,7 +68,8 @@ Optional> InferShapeForFunctionReturnType( // Manually fold tf.Cast that precedes the return instruction and only differs // in shape refinement level. for (OpOperand& arg_op : return_op.getOperation()->getOpOperands()) { - if (auto cast_op = dyn_cast(arg_op.get()->getDefiningOp())) { + Operation* arg_defining_op = arg_op.get()->getDefiningOp(); + if (auto cast_op = dyn_cast_or_null(arg_defining_op)) { // Shape inference should not change the element type. if (cast_op.SrcT() != cast_op.DstT()) continue; // We only refine the result shape if the result a dynamic shape, the @@ -155,7 +160,7 @@ bool InferShapeForSingleOperation(Operation* op, Dialect* tf_dialect, std::vector input_tensors(op->getNumOperands()); std::vector input_shapes( op->getNumOperands()); - std::vector tensors; + std::vector tensors(op->getNumOperands()); for (auto it : llvm::enumerate(op->getOperands())) { Value* operand = it.value(); size_t index = it.index(); @@ -163,8 +168,7 @@ bool InferShapeForSingleOperation(Operation* op, Dialect* tf_dialect, // If the operand is constant, then convert it to Tensor. ElementsAttr attr; if (matchPattern(operand, m_Constant(&attr))) { - tensors.emplace_back(); - tensorflow::Tensor* input_tensor = &tensors.back(); + tensorflow::Tensor* input_tensor = &tensors[index]; auto status = tensorflow::ConvertToTensor(attr, input_tensor); if (status.ok()) { input_tensors[index] = input_tensor; @@ -252,6 +256,92 @@ bool InferShapeForSingleOperation(Operation* op, Dialect* tf_dialect, return changed; } +// Updates input types and refine shapes inside body of functions that are +// attached to ControlFlow ops (If/While). These functions include Then/Else +// branches of IfOp and Cond/Body functions of WhileOp. These functions share +// following common properties: +// 1) They are never reused, ie. having a single use in module. +// 2) Their input types match those of their parent ops (excluding inputs like +// predicate). +// Returns a boolean indicating whether any change has been applied. +LogicalResult RefineShapeForControlFlowFunc(FuncOp func, + llvm::ArrayRef input_types, + int64_t graph_version, + int64_t max_iteration) { + ModuleOp module = func.getParentOfType(); + auto func_uses = func.getSymbolUses(module); + int num_uses = std::distance(func_uses->begin(), func_uses->end()); + if (num_uses != 1) { + func.emitError(llvm::formatv( + "expected control flow function {0} to have exactly 1 use, found {1}.", + func.getName(), num_uses)); + return failure(); + } + + FunctionType func_type = func.getType(); + if (input_types == func_type.getInputs()) return success(); + + func.setType(FunctionType::get(input_types, func_type.getResults(), + func.getContext())); + + for (auto arg_and_idx : llvm::enumerate(func.getArguments())) { + arg_and_idx.value()->setType(input_types[arg_and_idx.index()]); + } + + auto res = + InferShapeUntilFixPoint(&func.getBody(), graph_version, max_iteration); + if (failed(res)) return res; + + auto new_return_types = InferShapeForFunctionReturnType(func); + if (new_return_types.hasValue()) { + func.setType(FunctionType::get(input_types, new_return_types.getValue(), + func.getContext())); + } + + return success(); +} + +template +LogicalResult PropagateShapeToIfWhileOpFunctions( + OpTy op, llvm::ArrayRef func_names, int64_t graph_version, + int64_t max_iteration) { + llvm::SmallVector input_types; + input_types.reserve(std::distance(op.input().begin(), op.input().end())); + for (Value* v : op.input()) { + input_types.push_back(v->getType()); + } + + ModuleOp module = op.template getParentOfType(); + + bool success = true; + for (auto func_name : func_names) { + FuncOp func = module.lookupSymbol(func_name); + if (failed(RefineShapeForControlFlowFunc(func, input_types, graph_version, + max_iteration))) { + success = false; + } + } + return mlir::success(success); +} + +LogicalResult PropagateShapeIntoAttachedFunctions(Operation* op, + int64_t graph_version, + int64_t max_iteration) { + if (auto if_op = dyn_cast(op)) { + return PropagateShapeToIfWhileOpFunctions( + if_op, {if_op.then_branch(), if_op.else_branch()}, graph_version, + max_iteration); + } else if (auto while_op = dyn_cast(op)) { + return PropagateShapeToIfWhileOpFunctions( + while_op, {while_op.cond(), while_op.body()}, graph_version, + max_iteration); + } + + // TODO(ycao): Implement support for Call op, including function reuse. + + return success(); +} + LogicalResult InferShapeUntilFixPoint(Region* region, int64_t graph_version, int64_t max_iteration) { MLIRContext* ctx = region->getContext(); @@ -273,10 +363,20 @@ LogicalResult InferShapeUntilFixPoint(Region* region, int64_t graph_version, if (op->getDialect() != tf_dialect) return; // Before attempting inference, just try to fold the operation. - if (failed(folder.tryToFold(op))) - changed |= InferShapeForSingleOperation(op, tf_dialect, graph_version); + if (succeeded(folder.tryToFold(op))) return; + + // Best-effort shape inference in attached functions. Do not return + // failure even if it doesn't get to fixed point. + if (failed(PropagateShapeIntoAttachedFunctions(op, graph_version, + max_iteration))) { + op->emitWarning() << "unable to refine shape of attached function " + "arguments and bodies"; + } + + changed |= InferShapeForSingleOperation(op, tf_dialect, graph_version); }); } + if (changed) { return region->getParentOp()->emitWarning() << "Shape inference did not reach stable state after " diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference_pass.cc b/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference_pass.cc index 637b14346b0..d5b86173b69 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference_pass.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/shape_inference_pass.cc @@ -46,7 +46,6 @@ namespace { // This transformation pass propagate shapes on the TensorFlow graph. // It is a ModulePass in order to be able to change function types. -// TODO(aminim): at the moment the shape inference is intra-procedural. struct ShapeInference : public ModulePass { void runOnModule() override { auto module = getModule(); diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/tpu_cluster_formation.cc b/tensorflow/compiler/mlir/tensorflow/transforms/tpu_cluster_formation.cc index 3fb311ff415..7a840aa0d12 100644 --- a/tensorflow/compiler/mlir/tensorflow/transforms/tpu_cluster_formation.cc +++ b/tensorflow/compiler/mlir/tensorflow/transforms/tpu_cluster_formation.cc @@ -259,6 +259,39 @@ void MovePrecedingClusterUsers(tf_device::LaunchOp launch_op, for (Operation* user : preceding_users) user->moveBefore(op_after_launch_op); } +// Sorts `tf.TPUReplicatedInput` ops by `index` attribute. Ops with an `index` +// of -1 are always after ops with a non negative `index`, and an arbitrary +// ordering is used as there are no dependencies on their relative ordering. +LogicalResult SortTPUReplicatedInputsByIndex( + llvm::ArrayRef inputs, + llvm::SmallVectorImpl* sorted_inputs) { + const int input_size = inputs.size(); + sorted_inputs->resize(input_size, nullptr); + int last_index = input_size - 1; + + for (Operation* input : inputs) { + int64_t index = + llvm::cast(input).index().getLimitedValue(); + + if (index >= input_size || index < -1) + return input->emitError() << "'" << input->getName().getStringRef() + << "' index is not in range [-1, " << input_size + << "), got " << index; + + if (index == -1) + (*sorted_inputs)[last_index--] = input; + else + (*sorted_inputs)[index] = input; + } + + if (llvm::any_of(*sorted_inputs, [](Operation* op) { return op == nullptr; })) + return inputs.front()->emitError() + << "failed to sort '" << inputs.front()->getName().getStringRef() + << "' ops, gap(s) found in indices"; + + return success(); +} + // Creates a `tf_device.replicate` to represent replication for the cluster, if // necessary. LogicalResult ReplicateCluster(tf_device::LaunchOp launch_op, @@ -270,14 +303,18 @@ LogicalResult ReplicateCluster(tf_device::LaunchOp launch_op, return launch_op.emitError() << "requires '" << kNumReplicasAttr << "' int attribute to be at least 1"; - // Collect all used TPUReplicatedInput ops. - llvm::SmallSetVector replicated_input_ops; + // Collect all used TPUReplicatedInput ops and sort by `index`. + llvm::SmallSetVector unique_replicated_input_ops; mlir::visitUsedValuesDefinedAbove( launch_op.body(), launch_op.body(), [&](mlir::OpOperand* operand) { Operation* def = operand->get()->getDefiningOp(); if (def && llvm::isa(def)) - replicated_input_ops.insert(def); + unique_replicated_input_ops.insert(def); }); + llvm::SmallVector replicated_input_ops; + if (failed(SortTPUReplicatedInputsByIndex( + unique_replicated_input_ops.getArrayRef(), &replicated_input_ops))) + return failure(); // Check if number of operands of each used TPUReplicatedInput op matches // `num_replicas`. Collect all their operands and associated type for creating diff --git a/tensorflow/compiler/mlir/tensorflow/transforms/tpu_dynamic_padding_mapper.cc b/tensorflow/compiler/mlir/tensorflow/transforms/tpu_dynamic_padding_mapper.cc new file mode 100644 index 00000000000..f2f885dbcc8 --- /dev/null +++ b/tensorflow/compiler/mlir/tensorflow/transforms/tpu_dynamic_padding_mapper.cc @@ -0,0 +1,212 @@ +/* Copyright 2019 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 +#include +#include + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/FormatVariadic.h" +#include "mlir/IR/Attributes.h" // TF:local_config_mlir +#include "mlir/IR/Block.h" // TF:local_config_mlir +#include "mlir/IR/Builders.h" // TF:local_config_mlir +#include "mlir/IR/Function.h" // TF:local_config_mlir +#include "mlir/IR/Module.h" // TF:local_config_mlir +#include "mlir/IR/Value.h" // TF:local_config_mlir +#include "mlir/Pass/Pass.h" // TF:local_config_mlir +#include "mlir/Pass/PassRegistry.h" // TF:local_config_mlir +#include "mlir/Support/LogicalResult.h" // TF:local_config_mlir +#include "tensorflow/compiler/mlir/tensorflow/ir/tf_device.h" +#include "tensorflow/compiler/mlir/tensorflow/transforms/passes.h" +#include "tensorflow/core/protobuf/tpu/dynamic_padding.pb.h" + +namespace mlir { +namespace TFTPU { + +constexpr char kPaddingMapAttr[] = "padding_map"; + +// This pass remaps and assigns padding maps to an encapsulated function's +// arguments from a `tf_device.launch_func` `padding_map` attribute. Remapping +// is from replicated input index to encapsulated function's operand index +// (user). + +namespace { +struct TPUDynamicPaddingMapper : public ModulePass { + void runOnModule() override; +}; + +// Creates a mapping from replicated input index (in `tf_device.replicate` op) +// to `tf_device.launch_func` operand index. +llvm::SmallDenseMap GetRemappedReplicatedInputIndices( + tf_device::LaunchFuncOp launch_func, tf_device::ReplicateOp replicate) { + Block* replicate_block = &replicate.GetBody(); + + llvm::SmallDenseMap remapped_indices; + for (auto operand_and_idx : llvm::enumerate(launch_func.getOperands())) + if (auto block_arg = llvm::dyn_cast(operand_and_idx.value())) + if (block_arg->getOwner() == replicate_block) + remapped_indices[block_arg->getArgNumber()] = operand_and_idx.index(); + + return remapped_indices; +} + +// Extracts `padding_map` from `tf_device.launch_func` and remaps the associated +// replicated input indices to the encapsulated function operand indices. An +// error will be returned if an index is not found or parsing failed. +LogicalResult GetRemappedPaddings( + tf_device::LaunchFuncOp launch_func, int num_replicated_args, + const llvm::SmallDenseMap& remapped_indices, + llvm::SmallVectorImpl* remapped_paddings) { + auto bad_index_msg = [num_replicated_args](int32_t index, + llvm::StringRef arg_type, + int32_t arg_index) { + return llvm::formatv( + "bad '{0}' attribute at index {1}, {2} must be in [0, {3}), got " + "{4}", + kPaddingMapAttr, index, arg_type, num_replicated_args, arg_index) + .str(); + }; + + Attribute padding_map_attr = launch_func.getAttr(kPaddingMapAttr); + if (!padding_map_attr) return success(); + + auto padding_map = padding_map_attr.dyn_cast(); + if (!padding_map) + return launch_func.emitOpError() + << "requires '" << kPaddingMapAttr << "' array attribute"; + + for (auto padding_attr_and_idx : llvm::enumerate(padding_map)) { + int idx = padding_attr_and_idx.index(); + auto& padding_attr = padding_attr_and_idx.value(); + auto padding = padding_attr.dyn_cast(); + if (!padding) + return launch_func.emitOpError( + llvm::formatv("bad '{0}' attribute at index {1}, not a string", + kPaddingMapAttr, padding_attr_and_idx.index())); + + tensorflow::tpu::PaddingMap padding_proto; + if (!padding_proto.ParseFromString(padding.getValue().str())) + return launch_func.emitOpError(llvm::formatv( + "bad '{0}' attribute at index {1}, failed to parse '{2}' as " + "tensorflow::tpu::PaddingMap", + kPaddingMapAttr, idx, padding.getValue())); + + const int32_t arg_index = padding_proto.arg_index(); + if (arg_index >= num_replicated_args || arg_index < 0) + return launch_func.emitOpError() + << bad_index_msg(idx, "arg_index", arg_index); + + const int32_t padding_arg_index = padding_proto.padding_arg_index(); + if (padding_arg_index >= num_replicated_args || padding_arg_index < 0) + return launch_func.emitOpError() + << bad_index_msg(idx, "padding_arg_index", padding_arg_index); + + auto arg_index_it = remapped_indices.find(arg_index); + // Skip unused arguments. + if (arg_index_it == remapped_indices.end()) continue; + + auto padding_arg_index_it = remapped_indices.find(padding_arg_index); + if (padding_arg_index_it == remapped_indices.end()) + return launch_func.emitOpError(llvm::formatv( + "bad '{0}' attribute at index {1}, unused padding_arg_index {2}", + kPaddingMapAttr, idx, padding_arg_index)); + + padding_proto.set_arg_index(arg_index_it->second); + padding_proto.set_padding_arg_index(padding_arg_index_it->getSecond()); + remapped_paddings->push_back(std::move(padding_proto)); + } + + return success(); +} + +// Inserts padding maps for relevant arguments as argument attributes on the +// encapsulated function. The padding maps will be in the form of: +// %arg0 : type {xla_hlo.padding_map = {shape_indices = [...], +// padding_arg_indices = [...]}} +void AnnotateFunctionArgumentsWithPaddings( + FuncOp func, + llvm::ArrayRef remapped_paddings) { + // Group paddings by arg index. + llvm::SmallDenseMap, + llvm::SmallVector>> + paddings; + for (const auto& padding : remapped_paddings) { + auto& it = paddings[padding.arg_index()]; + it.first.push_back(padding.shape_index()); + it.second.push_back(padding.padding_arg_index()); + } + + Builder builder(func.getContext()); + for (const auto& padding : paddings) { + auto shape_indices = builder.getNamedAttr( + "shape_indices", builder.getI32ArrayAttr(padding.getSecond().first)); + auto padding_arg_indices = builder.getNamedAttr( + "padding_arg_indices", + builder.getI32ArrayAttr(padding.getSecond().second)); + func.setArgAttr( + padding.getFirst(), "xla_hlo.padding_map", + builder.getDictionaryAttr({shape_indices, padding_arg_indices})); + } +} + +LogicalResult RemapAndAssignPaddingMaps(tf_device::LaunchFuncOp launch_func, + SymbolTable* symbol_table) { + auto replicate = + llvm::dyn_cast_or_null(launch_func.getParentOp()); + // LaunchFunc is not replicated, there will be no padding. + if (!replicate) return success(); + const int num_replicated_args = replicate.GetBody().getNumArguments(); + + auto func = symbol_table->lookup(launch_func.func()); + if (!func) return success(); + + llvm::SmallDenseMap remapped_indices = + GetRemappedReplicatedInputIndices(launch_func, replicate); + + llvm::SmallVector remapped_paddings; + if (failed(GetRemappedPaddings(launch_func, num_replicated_args, + remapped_indices, &remapped_paddings))) + return failure(); + + AnnotateFunctionArgumentsWithPaddings(func, remapped_paddings); + + return success(); +} + +void TPUDynamicPaddingMapper::runOnModule() { + ModuleOp module = getModule(); + SymbolTable symbol_table(module); + module.walk([&](tf_device::LaunchFuncOp launch_func) { + RemapAndAssignPaddingMaps(launch_func, &symbol_table); + }); +} +} // anonymous namespace + +std::unique_ptr> CreateTPUDynamicPaddingMapperPass() { + return std::make_unique(); +} + +static PassRegistration pass( + "tf-tpu-dynamic-padding", + "Remaps padding map from replicated inputs to argument ordering on " + "encapsulated function"); + +} // namespace TFTPU +} // namespace mlir diff --git a/tensorflow/compiler/mlir/tensorflow/translate/executor_to_control_dialect.cc b/tensorflow/compiler/mlir/tensorflow/translate/executor_to_control_dialect.cc index 280f8f195de..8a4f8aacc0d 100644 --- a/tensorflow/compiler/mlir/tensorflow/translate/executor_to_control_dialect.cc +++ b/tensorflow/compiler/mlir/tensorflow/translate/executor_to_control_dialect.cc @@ -45,7 +45,7 @@ struct ExecutorToControlDialectConversion // Replace all uses of value `v` with a list of new values. Because number of // new values might be greater than 1, users of `v` might be replaced with their -// clones in case of non-resizble operands list. +// clones in case of non-resizable operands list. void ReplaceAllUsesOfValueWithValues(Value *v, Operation::operand_range new_values) { int new_values_size = std::distance(new_values.begin(), new_values.end()); diff --git a/tensorflow/compiler/mlir/tensorflow/translate/export_graphdef.cc b/tensorflow/compiler/mlir/tensorflow/translate/export_graphdef.cc index 58242e62f1c..9d572209b31 100644 --- a/tensorflow/compiler/mlir/tensorflow/translate/export_graphdef.cc +++ b/tensorflow/compiler/mlir/tensorflow/translate/export_graphdef.cc @@ -197,7 +197,7 @@ class Exporter { // Each NextIteration node in the original graph is converted to a pair of // source and sink operations in the MLIR, and we use the following two maps - // to pair and convet them back to a single NextIteration node. We choose to + // to pair and convert them back to a single NextIteration node. We choose to // the "name" attribute, which is from the unique node name, to find out the // pairs: When scanning the operations in the block, the source operations // are inserted to the name_to_inst_ first, and the other "sink" operation @@ -258,6 +258,14 @@ StatusOr> Exporter::GetArgumentNode( *node_def->mutable_device() = device_attr.getValue().str(); } + if (auto resource_arg_unique_id_attr = + func.getArgAttrOfType( + index, "tf.resource_arg_unique_id")) { + AttrValue unique_id_attr; + unique_id_attr.set_i(resource_arg_unique_id_attr.getInt()); + (*node_def->mutable_attr())["_resource_arg_unique_id"] = unique_id_attr; + } + return node_def; } @@ -639,6 +647,14 @@ Status Exporter::ConvertLibFunction(const GraphExportConfig& configs, if (auto attr = function.getAttrOfType(stateful_string)) { func_def.mutable_signature()->set_is_stateful(true); } + for (int64 i = 0; i < function.getNumArguments(); ++i) { + if (auto resource_arg_unique_id_attr = + function.getArgAttrOfType( + i, "tf.resource_arg_unique_id")) { + (*func_def.mutable_resource_arg_unique_id())[i] = + resource_arg_unique_id_attr.getInt(); + } + } // Ignore the gradient and is_stateful attribute on the function as they have // been handled above. diff --git a/tensorflow/compiler/mlir/tensorflow/translate/import_model.cc b/tensorflow/compiler/mlir/tensorflow/translate/import_model.cc index 7bc7c914f56..868faed9b0b 100644 --- a/tensorflow/compiler/mlir/tensorflow/translate/import_model.cc +++ b/tensorflow/compiler/mlir/tensorflow/translate/import_model.cc @@ -121,7 +121,7 @@ class NameUniquifier : public OpOrArgNameMapper { private: bool IsUnique(llvm::StringRef name) override { return !flib_.Contains(name); } - std::string GetName(OpOrArg op_or_arg) override { + std::string GetName(OpOrVal op_or_val) override { DCHECK(false) << "Unimplemented"; return ""; } @@ -163,11 +163,15 @@ class ImporterBase { StatusOr InferLibFunctionType(const FunctionBody& fbody); // Extracts arg and ret nodes from FunctionBody. + // `resource_arg_unique_ids` will be filled with the unique IDs of resource + // variables, as a list of {index, ID} pairs. void GetArgsAndRetsFromFunctionBody( const FunctionBody& fbody, absl::InlinedVector* arg_nodes, absl::InlinedVector* ret_nodes, - absl::InlinedVector* control_ret_nodes); + absl::InlinedVector* control_ret_nodes, + absl::InlinedVector, 4>* + resource_arg_unique_ids); // Prepares converting the graph to an MLIR module. This step removes the // backedges of the graph, orders the nodes and infers the shapes. @@ -180,7 +184,9 @@ class ImporterBase { const absl::InlinedVector& arg_nodes, const absl::InlinedVector& ret_nodes, const absl::InlinedVector& control_ret_nodes, - llvm::ArrayRef attrs); + llvm::ArrayRef attrs, + const absl::InlinedVector, 4>& + resource_arg_unique_ids); // Finds out the function definition for the given function name from the // graph and converts it to a function of the module. This method is called @@ -1000,7 +1006,9 @@ StatusOr ImporterBase::ConvertAttributeValue( void ImporterBase::GetArgsAndRetsFromFunctionBody( const FunctionBody& fbody, absl::InlinedVector* arg_nodes, absl::InlinedVector* ret_nodes, - absl::InlinedVector* control_ret_nodes) { + absl::InlinedVector* control_ret_nodes, + absl::InlinedVector, 4>* + resource_arg_unique_ids) { arg_nodes->reserve(fbody.arg_nodes.size()); ret_nodes->reserve(fbody.ret_nodes.size()); for (auto arg : fbody.arg_nodes) { @@ -1009,6 +1017,9 @@ void ImporterBase::GetArgsAndRetsFromFunctionBody( for (auto ret : fbody.ret_nodes) { ret_nodes->emplace_back(ret, 0); } + for (const auto& entry : fbody.fdef.resource_arg_unique_id()) { + resource_arg_unique_ids->push_back(entry); + } *control_ret_nodes = fbody.control_ret_nodes; } @@ -1101,12 +1112,14 @@ Status ImporterBase::ConvertLibFunction(llvm::StringRef func_name) { absl::InlinedVector arg_nodes; absl::InlinedVector ret_nodes; absl::InlinedVector control_ret_nodes; + absl::InlinedVector, 4> resource_arg_unique_ids; GetArgsAndRetsFromFunctionBody(*fbody, &arg_nodes, &ret_nodes, - &control_ret_nodes); + &control_ret_nodes, &resource_arg_unique_ids); TF_RETURN_IF_ERROR(child_importer.Convert( mlir_func_name, func_type, arg_nodes, ret_nodes, control_ret_nodes, - llvm::makeArrayRef(attributes.begin(), attributes.end()))); + llvm::makeArrayRef(attributes.begin(), attributes.end()), + resource_arg_unique_ids)); return Status::OK(); } @@ -1121,7 +1134,9 @@ Status ImporterBase::Convert( const absl::InlinedVector& arg_nodes, const absl::InlinedVector& ret_nodes, const absl::InlinedVector& control_ret_nodes, - llvm::ArrayRef attrs) { + llvm::ArrayRef attrs, + const absl::InlinedVector, 4>& + resource_arg_unique_ids) { // TODO(b/122040776): Uses debug info for FunctionDef. auto function = mlir::FuncOp::create(mlir::UnknownLoc::get(context_), func_name, func_type, attrs); @@ -1144,8 +1159,14 @@ Status ImporterBase::Convert( // pairs. TF_RETURN_IF_ERROR(AddBackedges()); - return ConvertFunctionArgAndRets(function, graph, func_type.getInputs(), - arg_nodes, ret_nodes, control_ret_nodes); + TF_RETURN_IF_ERROR(ConvertFunctionArgAndRets(function, graph, + func_type.getInputs(), arg_nodes, + ret_nodes, control_ret_nodes)); + for (const auto& entry : resource_arg_unique_ids) { + function.setArgAttr(entry.first, "tf.resource_arg_unique_id", + builder_.getI64IntegerAttr(entry.second)); + } + return Status::OK(); } Status ImporterBase::ConvertFunctionArgAndRets( @@ -1710,10 +1731,14 @@ class GraphDefImporter : public ImporterBase { // output nodes, for function graphs. Arguments and return values are // determined by node op type. Type and shape information of the function are // inferred by the shape refiner in ImporterBase. + // `resource_arg_unique_ids` will be filled with the unique IDs of resource + // variables, as a list of {index, ID} pairs. StatusOr GetArgsRetsAndTypesFromFunctionGraph( mlir::MLIRContext* context, absl::InlinedVector* arg_nodes, - absl::InlinedVector* ret_nodes); + absl::InlinedVector* ret_nodes, + absl::InlinedVector, 4>* + resource_arg_unique_ids); }; StatusOr GraphDefImporter::Convert( @@ -1734,6 +1759,7 @@ StatusOr GraphDefImporter::Convert( absl::InlinedVector arg_nodes; absl::InlinedVector ret_nodes; absl::InlinedVector control_ret_nodes; + absl::InlinedVector, 4> resource_arg_unique_ids; llvm::SmallVector attrs; if (specs.graph_as_function) { if (specs.prune_unused_nodes || !specs.inputs.empty() || @@ -1742,9 +1768,10 @@ StatusOr GraphDefImporter::Convert( "Pruning of graph is currently unsupported when the main graph is " "converted to a function."); - TF_ASSIGN_OR_RETURN(func_type, - importer.GetArgsRetsAndTypesFromFunctionGraph( - context, &arg_nodes, &ret_nodes)); + TF_ASSIGN_OR_RETURN( + func_type, + importer.GetArgsRetsAndTypesFromFunctionGraph( + context, &arg_nodes, &ret_nodes, &resource_arg_unique_ids)); if (!arg_nodes.empty() || !ret_nodes.empty()) { mlir::Builder b(context); @@ -1805,7 +1832,8 @@ StatusOr GraphDefImporter::Convert( {producer, min_consumer, bad_consumers}))); TF_RETURN_IF_ERROR(importer.ImporterBase::Convert( - "main", func_type, arg_nodes, ret_nodes, control_ret_nodes, attrs)); + "main", func_type, arg_nodes, ret_nodes, control_ret_nodes, attrs, + resource_arg_unique_ids)); return module; } @@ -1918,7 +1946,9 @@ StatusOr GraphDefImporter::InferMainFunctionType( StatusOr GraphDefImporter::GetArgsRetsAndTypesFromFunctionGraph( mlir::MLIRContext* context, absl::InlinedVector* arg_nodes, - absl::InlinedVector* ret_nodes) { + absl::InlinedVector* ret_nodes, + absl::InlinedVector, 4>* + resource_arg_unique_ids) { auto add_node = [](Node* node, absl::InlinedVector* nodes) { auto* attr = node->attrs().Find("index"); if (!attr) @@ -1959,6 +1989,12 @@ GraphDefImporter::GetArgsRetsAndTypesFromFunctionGraph( TF_ASSIGN_OR_RETURN(auto type, InferOutputType(*arg_node.node, /*idx=*/0, builder)); arg_types.push_back(type); + tensorflow::int64 resource_arg_unique_id; + if (TryGetNodeAttr(arg_node.node->attrs(), "_resource_arg_unique_id", + &resource_arg_unique_id)) { + resource_arg_unique_ids->emplace_back(arg_node_and_idx.index(), + resource_arg_unique_id); + } } llvm::SmallVector ret_types; @@ -2243,12 +2279,14 @@ Status DiagnoseMultipleConcreteFunctions(const SavedObjectGraph& object_graph, if (object.function().concrete_functions_size() != 1) { llvm::SmallVector names; for (llvm::StringRef s : object_names.GetExportedNames(node_id)) { - names.push_back(s.str()); + names.push_back("'" + s.str() + "'"); } return errors::InvalidArgument( - "Exported function '", absl::StrJoin(names, ","), - "' with multiple concrete functions. Check if you have " - "@tf.function(input_signature=[...]) on this function."); + "Exported function with exported name(s) ", + absl::StrJoin(names, ", "), + " with multiple concrete functions. Add " + "@tf.function(input_signature=[...]) on this function, or use a " + "narrower list of exported names that excludes this function."); } } } @@ -2281,8 +2319,8 @@ class StructuredValueLinearizer { // Returns the list of index paths to each leaf of the StructuredValue, // in a linearized order matching `tf.nest.flatten`. // - // If an error ocurred during the linearization process, an error message with - // `error_context` prepended will be included in the returned status. + // If an error occurred during the linearization process, an error message + // with `error_context` prepended will be included in the returned status. StatusOr> GetLeafIndexPaths( llvm::StringRef error_context) const; diff --git a/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util.cc b/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util.cc index dc9ec6aa8ea..4e914a5a20d 100644 --- a/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util.cc +++ b/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util.cc @@ -26,6 +26,7 @@ limitations under the License. #include "mlir/Pass/Pass.h" // TF:local_config_mlir #include "mlir/Pass/PassManager.h" // TF:local_config_mlir #include "mlir/Transforms/Passes.h" // TF:local_config_mlir +#include "tensorflow/compiler/mlir/tensorflow/transforms/passes.h" #include "tensorflow/compiler/mlir/tensorflow/transforms/shape_inference.h" #include "tensorflow/compiler/mlir/tensorflow/utils/convert_type.h" #include "tensorflow/compiler/mlir/tensorflow/utils/dump_mlir_util.h" @@ -209,7 +210,17 @@ Status ConvertMLIRToXlaComputation(mlir::ModuleOp module_op, bool use_tuple_args, bool return_tuple) { mlir::PassManager tf2xla(module_op.getContext()); tf2xla.addNestedPass(mlir::createCanonicalizerPass()); - tf2xla.addNestedPass(mlir::xla_hlo::createLegalizeTFPass()); + tf2xla.addPass(mlir::xla_hlo::createLegalizeTFControlFlowPass()); + // We need to run LegalizeTFPass 2 times because first + // LegalizeTFPass(allow_partial_conversion=true) can expose more graph pruning + // and canonicalization opportunities that are necessary for the second + // LegalizeTFPass(allow_partial_conversion=false) invocation. + tf2xla.addNestedPass(mlir::xla_hlo::createLegalizeTFPass(true)); + tf2xla.addPass(mlir::tf_executor::CreateTFExecutorGraphPruningPass( + /*skip_main_func=*/true)); + tf2xla.addNestedPass(mlir::createCanonicalizerPass()); + tf2xla.addNestedPass( + mlir::xla_hlo::createLegalizeTFPass(false)); { // Make sure we catch any error reported by MLIR and forward it to the TF diff --git a/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util_test.cc b/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util_test.cc index 1668cf615f0..b007687952a 100644 --- a/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util_test.cc +++ b/tensorflow/compiler/mlir/tensorflow/utils/compile_mlir_util_test.cc @@ -141,15 +141,14 @@ TEST(CompileSerializedMlirToXlaHloTest, CompileTimeConstantFoldedSuccess) { auto status_or_hlo_module = xla::HloModule::CreateFromProto( compilation_result.computation->proto(), module_config); ASSERT_TRUE(status_or_hlo_module.ok()); - string expected_hlo_module_string = R"(HloModule main.7 + string expected_hlo_module_string = R"(HloModule main.6 -ENTRY %main.7 (arg_tuple.1: (f32[10,19], f32[19,10])) -> (f32[10,19]) { +ENTRY %main.6 (arg_tuple.1: (f32[10,19], f32[19,10])) -> (f32[10,19]) { %arg_tuple.1 = (f32[10,19]{1,0}, f32[19,10]{1,0}) parameter(0) %get-tuple-element.2 = f32[10,19]{1,0} get-tuple-element((f32[10,19]{1,0}, f32[19,10]{1,0}) %arg_tuple.1), index=0 - %constant.4 = s64[2]{0} constant({10, 19}) %get-tuple-element.3 = f32[19,10]{1,0} get-tuple-element((f32[10,19]{1,0}, f32[19,10]{1,0}) %arg_tuple.1), index=1 - %reshape.5 = f32[10,19]{1,0} reshape(f32[19,10]{1,0} %get-tuple-element.3) - ROOT %tuple.6 = (f32[10,19]{1,0}) tuple(f32[10,19]{1,0} %reshape.5) + %reshape.4 = f32[10,19]{1,0} reshape(f32[19,10]{1,0} %get-tuple-element.3) + ROOT %tuple.5 = (f32[10,19]{1,0}) tuple(f32[10,19]{1,0} %reshape.4) } )"; diff --git a/tensorflow/compiler/mlir/tensorflow/utils/convert_tensor.h b/tensorflow/compiler/mlir/tensorflow/utils/convert_tensor.h index f4b6f702cef..7e982bb489b 100644 --- a/tensorflow/compiler/mlir/tensorflow/utils/convert_tensor.h +++ b/tensorflow/compiler/mlir/tensorflow/utils/convert_tensor.h @@ -37,18 +37,18 @@ StatusOr ConvertTensorProto(const TensorProto& input_tensor, StatusOr ConvertTensor(const Tensor& input_tensor, mlir::Builder* builder); -// Converts a shape from MLIR to an TensorFlow tensor shape proto. +// Converts a shape from MLIR to a TensorFlow tensor shape proto. void ConvertToTensorShapeProto(llvm::ArrayRef shape, TensorShapeProto* output_shape); -// Converts an MLIR type with static tensor shape to an TensorFlow tensor shape. +// Converts an MLIR type to a TensorFlow tensor shape. PartialTensorShape ConvertTypeToTensorShape(const mlir::Type& type); -// Converts an MLIR elements attribute to an TensorFlow tensor proto. +// Converts an MLIR elements attribute to a TensorFlow tensor proto. Status ConvertToTensorProto(mlir::ElementsAttr attr, TensorProto* output_tensor); -// Converts an MLIR elements attribute to an TensorFlow tensor. +// Converts an MLIR elements attribute to a TensorFlow tensor. Status ConvertToTensor(mlir::ElementsAttr attr, Tensor* output_tensor); // Decodes the given opaque elements attribute holding tensor content into a diff --git a/tensorflow/compiler/mlir/xla/BUILD b/tensorflow/compiler/mlir/xla/BUILD index bf71bcda776..17629440871 100644 --- a/tensorflow/compiler/mlir/xla/BUILD +++ b/tensorflow/compiler/mlir/xla/BUILD @@ -162,6 +162,25 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "lhlo_legalize_to_gpu", + srcs = ["transforms/lhlo_legalize_to_gpu.cc"], + hdrs = ["transforms/map_lhlo_to_scalar_op.h"], + deps = [ + ":lhlo", + "@com_google_absl//absl/memory", + "@llvm//:support", + "@local_config_mlir//:GPUDialect", + "@local_config_mlir//:IR", + "@local_config_mlir//:Linalg", + "@local_config_mlir//:LoopOps", + "@local_config_mlir//:Pass", + "@local_config_mlir//:StandardOps", + "@local_config_mlir//:Transforms", + ], + alwayslink = 1, +) + cc_library( name = "lhlo_fuse_linalg", srcs = ["transforms/lhlo_fuse_linalg.cc"], @@ -360,6 +379,7 @@ cc_library( srcs = ["type_to_shape.cc"], hdrs = ["type_to_shape.h"], deps = [ + ":hlo", "//tensorflow/compiler/mlir/tensorflow:convert_tensor", "//tensorflow/compiler/mlir/tensorflow:convert_type", "//tensorflow/compiler/xla:shape_util", @@ -407,6 +427,7 @@ cc_library( "//tensorflow/compiler/xla:xla_data_proto_cc", "//tensorflow/compiler/xla/client:xla_builder", "//tensorflow/compiler/xla/client/lib:matrix", + "//tensorflow/compiler/xla/client/lib:slicing", "//tensorflow/compiler/xla/service:hlo", "//tensorflow/stream_executor/lib", "@llvm//:support", diff --git a/tensorflow/compiler/mlir/xla/hlo_function_importer.cc b/tensorflow/compiler/mlir/xla/hlo_function_importer.cc index 1da4fd04ffb..fe468e26ff6 100644 --- a/tensorflow/compiler/mlir/xla/hlo_function_importer.cc +++ b/tensorflow/compiler/mlir/xla/hlo_function_importer.cc @@ -436,8 +436,10 @@ StatusOr HloFunctionImporter::ImportInstruction( // part of the HLO instruction. They are only a convenience in the XLA // builder API. NoAttributeCase(kAdd, AddOp); + NoAttributeCase(kAfterAll, AfterAllOp); NoAttributeCase(kAnd, AndOp); NoAttributeCase(kAtan2, Atan2Op); + NoAttributeCase(kBitcastConvert, BitcastConvertOp); NoAttributeCase(kConvert, ConvertOp); NoAttributeCase(kClamp, ClampOp); NoAttributeCase(kComplex, ComplexOp); @@ -560,6 +562,9 @@ StatusOr HloFunctionImporter::ConvertTensorType( } StatusOr HloFunctionImporter::ConvertType(const Shape& shape) { + if (shape.IsToken()) { + return mlir::xla_hlo::TokenType::get(builder_->getContext()); + } if (shape.IsTuple()) { mlir::Type mlir_type; llvm::SmallVector contents; diff --git a/tensorflow/compiler/mlir/xla/ir/hlo_ops.cc b/tensorflow/compiler/mlir/xla/ir/hlo_ops.cc index 08967372bcb..41e561fd731 100644 --- a/tensorflow/compiler/mlir/xla/ir/hlo_ops.cc +++ b/tensorflow/compiler/mlir/xla/ir/hlo_ops.cc @@ -694,10 +694,9 @@ static LogicalResult Verify(PadOp op) { input_shape[i] + padding_low_val + padding_high_val + std::max(input_shape[i] - 1, 0LL) * padding_interior_val; if (expected_output != output_shape[i]) { - return op.emitOpError( - llvm::formatv("expected output shape ({0}) and " - "output shape ({1}) should match", - expected_output, output_shape[i])); + return op.emitOpError(llvm::formatv( + "expected output shape's dimension #{0} to be {1} but found {2}", i, + expected_output, output_shape[i])); } } @@ -1056,10 +1055,28 @@ XlaHloDialect::XlaHloDialect(MLIRContext* context) #include "tensorflow/compiler/mlir/xla/ir/hlo_ops.cc.inc" >(); addInterfaces(); - + addTypes(); // Support unknown operations because not all XLA operations are registered. // allowUnknownOperations(); } +Type XlaHloDialect::parseType(DialectAsmParser& parser) const { + StringRef data_type; + if (parser.parseKeyword(&data_type)) return Type(); + + if (data_type == "token") return TokenType::get(getContext()); + parser.emitError(parser.getNameLoc()) + << "unknown xla_hlo type: " << data_type; + return nullptr; +} + +void XlaHloDialect::printType(Type type, DialectAsmPrinter& os) const { + if (type.isa()) { + os << "token"; + return; + } + os << ""; +} + } // namespace xla_hlo } // namespace mlir diff --git a/tensorflow/compiler/mlir/xla/ir/hlo_ops.h b/tensorflow/compiler/mlir/xla/ir/hlo_ops.h index 09a9cec968f..9610a787b7d 100644 --- a/tensorflow/compiler/mlir/xla/ir/hlo_ops.h +++ b/tensorflow/compiler/mlir/xla/ir/hlo_ops.h @@ -21,6 +21,7 @@ limitations under the License. #include "llvm/ADT/StringRef.h" #include "mlir/IR/Attributes.h" // TF:local_config_mlir #include "mlir/IR/Dialect.h" // TF:local_config_mlir +#include "mlir/IR/DialectImplementation.h" // TF:local_config_mlir #include "mlir/IR/Location.h" // TF:local_config_mlir #include "mlir/IR/MLIRContext.h" // TF:local_config_mlir #include "mlir/IR/OpDefinition.h" // TF:local_config_mlir @@ -45,6 +46,30 @@ class XlaHloDialect : public Dialect { // value with the desired resultant type. Operation *materializeConstant(OpBuilder &builder, Attribute value, Type type, Location loc) override; + + // Parses a type registered to this dialect. + Type parseType(DialectAsmParser &parser) const override; + + // Prints a type registered to this dialect. + void printType(Type type, DialectAsmPrinter &os) const override; +}; + +namespace HLOTypes { +enum Kind { + Token = Type::FIRST_XLA_HLO_TYPE, +}; +} // namespace HLOTypes + +class TokenType : public Type::TypeBase { + public: + using Base::Base; + + static TokenType get(MLIRContext *context) { + return Base::get(context, HLOTypes::Token); + } + + // Support method to enable LLVM-style type casting. + static bool kindof(unsigned kind) { return kind == HLOTypes::Token; } }; #define GET_OP_CLASSES diff --git a/tensorflow/compiler/mlir/xla/ir/hlo_ops.td b/tensorflow/compiler/mlir/xla/ir/hlo_ops.td index 3c4fd473eb6..6eeb32e804c 100644 --- a/tensorflow/compiler/mlir/xla/ir/hlo_ops.td +++ b/tensorflow/compiler/mlir/xla/ir/hlo_ops.td @@ -41,6 +41,9 @@ class HLO_Op traits> : // XLA type definitions. //===----------------------------------------------------------------------===// +// Token type. +def HLO_Token : Type()">, "token">; + // Any integer tensor types def HLO_IntTensor : TensorOf<[HLO_Int]>; @@ -324,6 +327,25 @@ def HLO_XorOp : HLO_BinaryLogicalElementwiseOp<"xor">, BASE_HLO_XorOp; //===----------------------------------------------------------------------===// // XLA control flow op definitions. //===----------------------------------------------------------------------===// + +def HLO_AfterAllOp : HLO_Op<"after_all", []> { + + string summary = "AfterAll operator"; + + string description = [{ + AfterAll takes a variadic number of tokens and produces a single token. + Tokens are primitive types which can be threaded between side-effecting + operations to enforce ordering. AfterAll can be used as a join of tokens + for ordering a operation after a set operations. + + See https://www.tensorflow.org/xla/operation_semantics#afterall. + }]; + + let arguments = (ins Variadic:$operands); + let results = (outs HLO_Token); + let hasCustomHLOConverter = 1; +} + def HLO_ConditionalOp: HLO_Op<"conditional", [NoSideEffect]> { string summary = "Conditional operator"; @@ -515,9 +537,6 @@ def HLO_DynamicSliceOp: HLO_Op<"dynamic-slice", ); let results = (outs HLO_Tensor:$result); - - let hasCustomHLOConverter = 1; - let hasCanonicalizer = 1; } @@ -530,9 +549,6 @@ def HLO_DynamicUpdateSliceOp: HLO_Op<"dynamic-update-slice", ); let results = (outs HLO_Tensor:$result); - - // TODO(b/129422361) Requires a custom constructor. - let hasCustomHLOConverter = 1; } @@ -586,6 +602,14 @@ def HLO_BatchNormTrainingOp : HLO_Op<"batch_norm_training", [NoSideEffect]>, let results = (outs HLO_Tuple); } +def HLO_BitcastConvertOp : HLO_Op<"bitcast_convert", + [NoSideEffect, SameOperandsAndResultShape]>, BASE_HLO_BitcastConvertOp { + + let arguments = (ins HLO_Tensor:$operand); + let results = (outs HLO_Tensor); + let hasCustomHLOConverter = 1; +} + def HLO_BroadcastOp : HLO_Op<"broadcast", [NoSideEffect, SameOperandsAndResultElementType]>, BASE_HLO_BroadcastOp { let arguments = (ins @@ -759,7 +783,7 @@ def HLO_UnaryEinsumOp: HLO_Op<"unary_einsum", [NoSideEffect]> { let hasCanonicalizer = 1; - // UnarayEinsumOp is unconditionally canonicalized to the binary EinsumOp so + // UnaryEinsumOp is unconditionally canonicalized to the binary EinsumOp so // the HLO converter shouldn't be invoked. let hasCustomHLOConverter = 1; } @@ -986,6 +1010,20 @@ def HLO_ReturnOp : HLO_Op<"return", [Terminator]> { // TODO(hinsu): Implement custom printer and parser. } +def HLO_TorchIndexSelectOp : HLO_Op<"torch_index_select", [NoSideEffect]> { + let arguments = (ins + HLO_Tensor:$input, + HLO_Tensor:$index, + I64Attr:$dim, + I64Attr:$batch_dims + ); + + let results = (outs HLO_Tensor); + + // TODO(hinsu): Canonicalize to lower this client side HLO op to server + // side HLO ops. +} + //===----------------------------------------------------------------------===// // XLA RngUniform Operator. //===----------------------------------------------------------------------===// diff --git a/tensorflow/compiler/mlir/xla/ir/hlo_ops_base.td b/tensorflow/compiler/mlir/xla/ir/hlo_ops_base.td index a6d4210b60c..3be2c26a1bf 100644 --- a/tensorflow/compiler/mlir/xla/ir/hlo_ops_base.td +++ b/tensorflow/compiler/mlir/xla/ir/hlo_ops_base.td @@ -656,6 +656,20 @@ class BASE_HLO_BatchNormTrainingOp { }]; } +class BASE_HLO_BitcastConvertOp { + string summary = "BitcastConvert operator"; + + string description = [{ + 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 the conversion is an element-wise one. Bitcast is implemented as a + low-level cast, so machines with different floating-point representations + will give different results. + + See https://www.tensorflow.org/xla/operation_semantics#bitcastconverttype. + }]; +} + class BASE_HLO_BroadcastOp { string summary = "Broadcast a tensor to a higher rank by prepending dimensions"; diff --git a/tensorflow/compiler/mlir/xla/mlir_hlo_to_hlo.cc b/tensorflow/compiler/mlir/xla/mlir_hlo_to_hlo.cc index 26cd512aa85..5c351876440 100644 --- a/tensorflow/compiler/mlir/xla/mlir_hlo_to_hlo.cc +++ b/tensorflow/compiler/mlir/xla/mlir_hlo_to_hlo.cc @@ -16,8 +16,13 @@ limitations under the License. #include "tensorflow/compiler/mlir/xla/mlir_hlo_to_hlo.h" #include +#include #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatVariadic.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SMLoc.h" #include "llvm/Support/SourceMgr.h" @@ -30,10 +35,12 @@ limitations under the License. #include "mlir/IR/Matchers.h" // TF:local_config_mlir #include "mlir/IR/Module.h" // TF:local_config_mlir #include "mlir/IR/Operation.h" // TF:local_config_mlir +#include "mlir/IR/StandardTypes.h" // TF:local_config_mlir #include "mlir/IR/TypeUtilities.h" // TF:local_config_mlir #include "tensorflow/compiler/mlir/xla/ir/hlo_ops.h" #include "tensorflow/compiler/mlir/xla/type_to_shape.h" #include "tensorflow/compiler/xla/client/lib/matrix.h" +#include "tensorflow/compiler/xla/client/lib/slicing.h" #include "tensorflow/compiler/xla/client/xla_builder.h" #include "tensorflow/compiler/xla/comparison_util.h" #include "tensorflow/compiler/xla/literal_util.h" @@ -52,6 +59,10 @@ using ::tensorflow::uint32; using ::tensorflow::uint64; using ::tensorflow::uint8; +constexpr char kPaddingMapAttr[] = "xla_hlo.padding_map"; +constexpr char kShapeIndicesAttr[] = "shape_indices"; +constexpr char kPaddingArgIndicesAttr[] = "padding_arg_indices"; + // Passes through everything except for unique_ptr, on which it calls get(). // This exists to allow the generated code to call XLA functions that take a raw // pointer. In particular, PrecisionConfig is passed to xla::Dot and xla::Conv @@ -426,6 +437,16 @@ namespace mlir { namespace xla_hlo { namespace { +LogicalResult ExportXlaOp(AfterAllOp op, OpLoweringContext ctx) { + auto& value_map = *ctx.values; + std::vector tokens(op.operands().size()); + for (auto index_and_value : llvm::enumerate(op.operands())) { + tokens[index_and_value.index()] = value_map[index_and_value.value()]; + } + value_map[op] = xla::AfterAll(ctx.builder, tokens); + return mlir::success(); +} + LogicalResult ExportXlaOp(AllReduceOp op, OpLoweringContext ctx) { auto& value_map = *ctx.values; xla::XlaComputation computation; @@ -446,6 +467,14 @@ LogicalResult ExportXlaOp(AllReduceOp op, OpLoweringContext ctx) { return success(); } +LogicalResult ExportXlaOp(BitcastConvertOp op, OpLoweringContext ctx) { + auto& value_map = *ctx.values; + value_map[op] = xla::BitcastConvertType( + value_map[op.operand()], + xla::TypeToPrimitiveType(getElementTypeOrSelf(op.getType()))); + return success(); +} + LogicalResult ExportXlaOp(BroadcastInDimOp op, OpLoweringContext ctx) { auto type = op.getType().dyn_cast(); if (!type) return failure(); @@ -512,14 +541,6 @@ LogicalResult ExportXlaOp(CopyOp op, OpLoweringContext ctx) { return failure(); } -LogicalResult ExportXlaOp(DynamicSliceOp op, OpLoweringContext ctx) { - return failure(); -} - -LogicalResult ExportXlaOp(DynamicUpdateSliceOp op, OpLoweringContext ctx) { - return failure(); -} - LogicalResult ExportXlaOp(FftOp op, OpLoweringContext ctx) { return failure(); } LogicalResult ExportXlaOp(GatherOp op, OpLoweringContext ctx) { @@ -726,6 +747,18 @@ StatusOr CreateLiteralFromAttr(Type type, ElementsAttr attr) { ELEMENTS_ATTR_TO_LITERAL(xla::PrimitiveType::U16, uint16) ELEMENTS_ATTR_TO_LITERAL(xla::PrimitiveType::U32, uint32) ELEMENTS_ATTR_TO_LITERAL(xla::PrimitiveType::U64, uint64) + case xla::PrimitiveType::BF16: { + xla::Array source_data(shape.dimensions()); + auto attr_values = attr.getValues(); + std::vector values_double(source_data.num_elements()); + for (auto index_and_value : llvm::enumerate(attr_values)) { + values_double[index_and_value.index()] = + index_and_value.value().convertToDouble(); + } + source_data.SetValues(values_double); + return xla::LiteralUtil::ConvertF64ToBF16( + xla::LiteralUtil::CreateFromArray(source_data)); + } default: return tensorflow::errors::Internal(absl::StrCat( "Unsupported type: ", xla::PrimitiveType_Name(shape.element_type()))); @@ -886,6 +919,171 @@ LogicalResult ConvertToHloModule::LowerRegionAsComputation( /*is_entry_function=*/false, func); } +std::string PaddingMapBadArrayAttrMsg(llvm::StringRef attr_name, int index) { + return llvm::formatv( + "requires '{0}' array attribute in '{1}' dict at arg {2}", + attr_name, kPaddingMapAttr, index) + .str(); +} + +std::string PaddingMapMismatchedArraySizeMsg(int arg_index, + int shape_indices_size, + int padding_arg_indices_size) { + return llvm::formatv( + "requires '{0}' and '{1}' array attributes in '{2}' dic at arg " + "{3} to be of the same size, got sizes {4} and {5}", + kShapeIndicesAttr, kPaddingArgIndicesAttr, kPaddingMapAttr, + arg_index, shape_indices_size, padding_arg_indices_size) + .str(); +} + +std::string PaddingMapBadIntAttrMsg(llvm::StringRef attr_name, int arg_index, + int element_index) { + return llvm::formatv( + "requires element {0} in '{1}' array of '{2}' dict at arg {3} " + "to be an int attribute", + element_index, attr_name, kPaddingMapAttr, arg_index) + .str(); +} + +std::string PaddingMapBadIndexMsg(llvm::StringRef attr_name, int arg_index, + int element_index, int max, int32_t value) { + return llvm::formatv( + "requires element {0} in '{1}' array of '{2}' dict at arg {3} " + "to be in range [0, {4}), got {5}", + element_index, attr_name, kPaddingMapAttr, arg_index, max, value) + .str(); +} + +std::string PaddingMapNegativeShapeIndexMsg(int arg_index, int element_index, + int32_t value) { + return llvm::formatv( + "requires element {0} in '{1}' array of '{2}' dict at arg {3} to " + "be non-negative, got {4}", + element_index, kShapeIndicesAttr, kPaddingMapAttr, arg_index, + value) + .str(); +} + +std::string PaddingMapUniqueShapeIndexMsg(int arg_index, int element_index, + int32_t value) { + return llvm::formatv( + "requires elements in '{0}' array of '{1}' dict at arg {2} to be " + "unique, got duplicate element {3} at index {4}", + kShapeIndicesAttr, kPaddingMapAttr, arg_index, value, + element_index) + .str(); +} + +void AddDynamicParameterBindingEntry(xla::DynamicParameterBindingProto* binding, + int arg_index, int32_t shape_index, + int32_t padding_arg_index, + bool use_tuple_args) { + auto* entry = binding->add_entries(); + entry->set_target_param_dim_num(shape_index); + if (use_tuple_args) { + entry->set_target_param_num(0); + entry->add_target_param_index(arg_index); + entry->set_dynamic_param_num(0); + entry->add_dynamic_param_index(padding_arg_index); + } else { + entry->set_target_param_num(arg_index); + entry->set_dynamic_param_num(padding_arg_index); + } +} + +// Validates and populates dynamic parameter bindings from a module's entry +// function `xla_hlo.padding_map` argument attributes to a `xla::HloModuleProto` +// `DynamicParameterBindingProto`. +LogicalResult AddDynamicParameterBindings(mlir::ModuleOp module, + xla::HloModuleProto* hlo_module_proto, + bool use_tuple_args) { + auto entry_func = module.lookupSymbol("main"); + if (!entry_func) return success(); + + auto* dynamic_parameter_binding = + hlo_module_proto->mutable_dynamic_parameter_binding(); + for (int i = 0, e = entry_func.getNumArguments(); i < e; ++i) { + auto padding_map_attr = entry_func.getArgAttr(i, kPaddingMapAttr); + if (!padding_map_attr) continue; + auto padding_map = padding_map_attr.dyn_cast(); + if (!padding_map) + return entry_func.emitError() << "requires '" << kPaddingMapAttr + << "' dict attribute at arg " << i; + + auto shape_indices = + padding_map.get(kShapeIndicesAttr).dyn_cast_or_null(); + if (!shape_indices) + return entry_func.emitError( + PaddingMapBadArrayAttrMsg(kShapeIndicesAttr, i)); + + auto padding_arg_indices = + padding_map.get(kPaddingArgIndicesAttr).dyn_cast_or_null(); + if (!padding_arg_indices) + return entry_func.emitError( + PaddingMapBadArrayAttrMsg(kPaddingArgIndicesAttr, i)); + + if (shape_indices.size() != padding_arg_indices.size()) + return entry_func.emitError(PaddingMapMismatchedArraySizeMsg( + i, shape_indices.size(), padding_arg_indices.size())); + + llvm::SmallDenseSet used_shape_indices; + auto arg_type = + entry_func.getArgument(i)->getType().dyn_cast(); + for (auto shape_and_padding : llvm::enumerate(llvm::zip( + shape_indices.getValue(), padding_arg_indices.getValue()))) { + const int element_index = shape_and_padding.index(); + auto shape_index_attr = + std::get<0>(shape_and_padding.value()).dyn_cast(); + if (!shape_index_attr) + return entry_func.emitError( + PaddingMapBadIntAttrMsg(kShapeIndicesAttr, i, element_index)); + + auto padding_arg_index_attr = + std::get<1>(shape_and_padding.value()).dyn_cast(); + if (!padding_arg_index_attr) + return entry_func.emitError( + PaddingMapBadIntAttrMsg(kPaddingArgIndicesAttr, i, element_index)); + + const int32_t shape_index = shape_index_attr.getInt(); + if (arg_type && (shape_index < 0 || shape_index >= arg_type.getRank())) + return entry_func.emitError( + PaddingMapBadIndexMsg(kShapeIndicesAttr, i, element_index, + arg_type.getRank(), shape_index)); + else if (shape_index < 0) + return entry_func.emitError( + PaddingMapNegativeShapeIndexMsg(i, element_index, shape_index)); + + if (!used_shape_indices.insert(shape_index).second) + return entry_func.emitError( + PaddingMapUniqueShapeIndexMsg(i, element_index, shape_index)); + + const int32_t padding_arg_index = padding_arg_index_attr.getInt(); + if (padding_arg_index < 0 || padding_arg_index >= e) + return entry_func.emitError(PaddingMapBadIndexMsg( + kPaddingArgIndicesAttr, i, element_index, e, padding_arg_index)); + + Type padding_arg_type = + entry_func.getArgument(padding_arg_index)->getType(); + if (auto tensor_type = padding_arg_type.dyn_cast()) + if (tensor_type.getRank() != 0) + return entry_func.emitError() + << "requires arg " << padding_arg_index + << " to be a scalar for use as a dynamic parameter"; + + if (!mlir::getElementTypeOrSelf(padding_arg_type).isa()) + return entry_func.emitError() + << "requires arg " << padding_arg_index + << " to be of an int type for use as a dynamic parameter"; + + AddDynamicParameterBindingEntry(dynamic_parameter_binding, i, shape_index, + padding_arg_index, use_tuple_args); + } + } + + return success(); +} + } // namespace Status ConvertMlirHloToHlo(mlir::ModuleOp module, xla::HloProto* hlo_proto, @@ -895,6 +1093,10 @@ Status ConvertMlirHloToHlo(mlir::ModuleOp module, xla::HloProto* hlo_proto, if (failed(converter.Run())) return diag_handler.ConsumeStatus(); auto hlo_module = converter.ConsumeMainProto(); hlo_proto->mutable_hlo_module()->Swap(&hlo_module); + if (failed(AddDynamicParameterBindings( + module, hlo_proto->mutable_hlo_module(), use_tuple_args))) + return diag_handler.ConsumeStatus(); + return Status::OK(); } diff --git a/tensorflow/compiler/mlir/xla/tests/hlo-legalize-to-lhlo.mlir b/tensorflow/compiler/mlir/xla/tests/hlo-legalize-to-lhlo.mlir index 2a027240fbe..7927598a350 100644 --- a/tensorflow/compiler/mlir/xla/tests/hlo-legalize-to-lhlo.mlir +++ b/tensorflow/compiler/mlir/xla/tests/hlo-legalize-to-lhlo.mlir @@ -1,4 +1,4 @@ -// RUN: tf-opt -hlo-legalize-to-lhlo %s -o - | FileCheck %s +// RUN: tf-opt -hlo-legalize-to-lhlo %s -o - | FileCheck %s --dump-input=always // CHECK-LABEL: func @attrs func @attrs_copy(%operand: memref<2x2xf32>, %result: memref<2x2xf32>) { @@ -110,7 +110,7 @@ func @convert(%operand: memref<2x2xf32>, %result: memref<2x2xf32>) { %tensor_operand = tensor_load %operand : memref<2x2xf32> %tensor_result = "xla_hlo.convert"(%tensor_operand) : (tensor<2x2xf32>) -> tensor<2x2xf32> - // CHECK-NEXT: "xla_lhlo.convert"(%{{.*}}, %{{.*}}) + // CHECK-NEXT: return tensor_store %tensor_result, %result : memref<2x2xf32> return } diff --git a/tensorflow/compiler/mlir/xla/tests/legalize-tf.mlir b/tensorflow/compiler/mlir/xla/tests/legalize-tf.mlir index c95b2c86960..a09a1813a3b 100644 --- a/tensorflow/compiler/mlir/xla/tests/legalize-tf.mlir +++ b/tensorflow/compiler/mlir/xla/tests/legalize-tf.mlir @@ -87,6 +87,27 @@ func @broadcast_div(%arg0: tensor<1xi32>, %arg1: tensor<1x2xi32>) -> tensor<1x2x return %0: tensor<1x2xi32> } +// CHECK-LABEL: func @shift_left +func @shift_left(%arg0: tensor<4xi32>, %arg1: tensor<4xi32>) -> tensor<4xi32> { + // CHECK: xla_hlo.shift_left %arg0, %arg1 : tensor<4xi32> + %0 = "tf.LeftShift"(%arg0, %arg1) : (tensor<4xi32>, tensor<4xi32>) -> tensor<4xi32> + return %0 : tensor<4xi32> +} + +// CHECK-LABEL: func @div_dynamic +func @div_dynamic(%arg0: tensor, %arg1: tensor) -> tensor { + // CHECK: "xla_hlo.div"(%arg0, %arg1) {broadcast_dimensions = dense<1> : tensor<1xi64>} + %0 = "tf.Div"(%arg0, %arg1) : (tensor, tensor) -> tensor + return %0: tensor +} + +// CHECK-LABEL: func @div_unranked +func @div_unranked(%arg0: tensor<*xi32>, %arg1: tensor) -> tensor { + // CHECK: tf.Div + %0 = "tf.Div"(%arg0, %arg1) : (tensor<*xi32>, tensor) -> tensor + return %0: tensor +} + // CHECK-LABEL: func @maximum func @maximum(%arg0: tensor<4xf32>, %arg1: tensor<4xf32>) -> tensor<4xf32> { // CHECK: xla_hlo.max %arg0, %arg1 : tensor<4xf32> @@ -145,6 +166,34 @@ func @broadcast_sub(%arg0: tensor<1xi32>, %arg1: tensor<1x2xi32>) -> tensor<1x2x return %0: tensor<1x2xi32> } +// CHECK-LABEL: func @shift_right +func @shift_right(%arg0: tensor<4xi32>, %arg1: tensor<4xi32>) -> tensor<4xi32> { + // CHECK: xla_hlo.shift_right_arithmetic %arg0, %arg1 : tensor<4xi32> + %0 = "tf.RightShift"(%arg0, %arg1) : (tensor<4xi32>, tensor<4xi32>) -> tensor<4xi32> + return %0 : tensor<4xi32> +} + +// CHECK-LABEL: func @broadcast_shift_right +func @broadcast_shift_right(%arg0: tensor<4xi32>, %arg1: tensor<2x4xi32>) -> tensor<2x4xi32> { + // CHECK: "xla_hlo.shift_right_arithmetic"(%arg0, %arg1) {broadcast_dimensions = dense<1> : tensor<1xi64>} + %0 = "tf.RightShift"(%arg0, %arg1) : (tensor<4xi32>, tensor<2x4xi32>) -> tensor<2x4xi32> + return %0 : tensor<2x4xi32> +} + +// CHECK-LABEL: func @shift_right_unsigned +func @shift_right_unsigned(%arg0: tensor<4x!tf.uint8>, %arg1: tensor<4x!tf.uint8>) -> tensor<4x!tf.uint8> { + // CHECK: tf.RightShift + %0 = "tf.RightShift"(%arg0, %arg1) : (tensor<4x!tf.uint8>, tensor<4x!tf.uint8>) -> tensor<4x!tf.uint8> + return %0 : tensor<4x!tf.uint8> +} + +// CHECK-LABEL: func @broadcast_shift_right_unsigned +func @broadcast_shift_right_unsigned(%arg0: tensor<4x!tf.uint8>, %arg1: tensor<2x4x!tf.uint8>) -> tensor<2x4x!tf.uint8> { + // CHECK: tf.RightShift + %0 = "tf.RightShift"(%arg0, %arg1) : (tensor<4x!tf.uint8>, tensor<2x4x!tf.uint8>) -> tensor<2x4x!tf.uint8> + return %0 : tensor<2x4x!tf.uint8> +} + // CHECK-LABEL: func @and func @and(%arg0: tensor<2xi1>) -> tensor<2xi1> { // CHECK-NEXT: xla_hlo.and @@ -166,6 +215,13 @@ func @and_dynamic(%arg0: tensor, %arg1: tensor<1xi1>) -> tensor { return %0: tensor } +// CHECK-LABEL: func @and_unranked +func @and_unranked(%arg0: tensor<*xi1>, %arg1: tensor<*xi1>) -> tensor<*xi1> { + // CHECK: tf.LogicalAnd + %0 = "tf.LogicalAnd"(%arg0, %arg1) : (tensor<*xi1>, tensor<*xi1>) -> tensor<*xi1> + return %0: tensor<*xi1> +} + // CHECK-LABEL: func @or func @or(%arg0: tensor<2xi1>) -> tensor<2xi1> { // CHECK-NEXT: xla_hlo.or @@ -310,6 +366,20 @@ func @floordiv_f16_broadcast(%arg0: tensor<2x3xf16>, %arg1: tensor<3xf16>) -> te return %0: tensor<2x3xf16> } +// CHECK-LABEL: func @floordiv_dynamic +func @floordiv_dynamic(%arg0: tensor, %arg1: tensor) -> tensor { + // CHECK: tf.FloorDiv + %0 = "tf.FloorDiv"(%arg0, %arg1) : (tensor, tensor) -> tensor + return %0: tensor +} + +// CHECK-LABEL: func @floordiv_unranked +func @floordiv_unranked(%arg0: tensor<*xi32>, %arg1: tensor<*xi32>) -> tensor<*xi32> { + // CHECK: tf.FloorDiv + %0 = "tf.FloorDiv"(%arg0, %arg1) : (tensor<*xi32>, tensor<*xi32>) -> tensor<*xi32> + return %0: tensor<*xi32> +} + // CHECK-LABEL: func @floormod_broadcast_numerator func @floormod_broadcast_numerator(%arg0: tensor<3xi32>, %arg1: tensor<2x3xi32>) -> tensor<2x3xi32> { // CHECK-DAG: [[REM:%.+]] = "xla_hlo.remainder"(%arg0, %arg1) {broadcast_dimensions = dense<1> : tensor<1xi64>} @@ -344,6 +414,20 @@ func @floormod_broadcast_denominator(%arg0: tensor<2x3xi32>, %arg1: tensor<3xi32 return %0: tensor<2x3xi32> } +// CHECK-LABEL: func @floormod_dynamic +func @floormod_dynamic(%arg0: tensor, %arg1: tensor) -> tensor { + // CHECK: tf.FloorMod + %0 = "tf.FloorMod"(%arg0, %arg1) : (tensor, tensor) -> tensor + return %0: tensor +} + +// CHECK-LABEL: func @floormod_unranked +func @floormod_unranked(%arg0: tensor<*xi32>, %arg1: tensor<*xi32>) -> tensor<*xi32> { + // CHECK: tf.FloorMod + %0 = "tf.FloorMod"(%arg0, %arg1) : (tensor<*xi32>, tensor<*xi32>) -> tensor<*xi32> + return %0: tensor<*xi32> +} + // CHECK-LABEL: func @broadcast_to func @broadcast_to(%arg0: tensor<16xf32>) -> tensor<16x16x16x16xf32> { %cst = "tf.Const"() { value = dense<16> : tensor<4xi32> } : () -> tensor<4xi32> @@ -415,6 +499,13 @@ func @equal_incompatible_shape_both_dynamic(%arg0: tensor, %arg1: tensor< return %0: tensor<*xi1> } +// CHECK-LABEL: func @equal_unranked +func @equal_unranked(%arg0: tensor<*xi32>, %arg1: tensor<*xi32>) -> tensor<*xi1> { + // CHECK: "tf.Equal" + %0 = "tf.Equal"(%arg0, %arg1) { incompatible_shape_error = false } : (tensor<*xi32>, tensor<*xi32>) -> tensor<*xi1> + return %0: tensor<*xi1> +} + // CHECK-LABEL: func @notequal func @notequal(%arg0: tensor<2xi32>) -> tensor<2xi1> { // CHECK-NEXT: "xla_hlo.compare"(%arg0, %arg0) {comparison_direction = "NE"} @@ -482,6 +573,20 @@ func @broadcast_greater(%arg0: tensor<1xi32>, %arg1: tensor<1x2xi32>) -> tensor< return %0: tensor<1x2xi1> } +// CHECK-LABEL: func @greater_dynamic +func @greater_dynamic(%arg0: tensor) -> tensor { + // CHECK: "xla_hlo.compare"(%arg0, %arg0) {comparison_direction = "GT"} + %0 = "tf.Greater"(%arg0, %arg0) : (tensor, tensor) -> tensor + return %0: tensor +} + +// CHECK-LABEL: func @greater_uranked +func @greater_uranked(%arg0: tensor<*xi32>) -> tensor<*xi1> { + // CHECK: "tf.Greater" + %0 = "tf.Greater"(%arg0, %arg0) : (tensor<*xi32>, tensor<*xi32>) -> tensor<*xi1> + return %0: tensor<*xi1> +} + // CHECK-LABEL: func @greater_equal func @greater_equal(%arg0: tensor<2xi32>) -> tensor<2xi1> { // CHECK-NEXT: "xla_hlo.compare"(%arg0, %arg0) {comparison_direction = "GE"} @@ -699,9 +804,7 @@ func @opaque_const() -> tensor>> { // CHECK-LABEL: matmul_notranspose // CHECK-SAME: (%[[A:.*]]: tensor<5x7xf32>, %[[B:.*]]: tensor<7x11xf32>) func @matmul_notranspose(%a: tensor<5x7xf32>, %b: tensor<7x11xf32>) -> tensor<5x11xf32> { - // CHECK: %[[UPDATED_A:.*]] = "xla_hlo.transpose"(%[[A]]) {permutation = dense<[0, 1]> : tensor<2xi64>} - // CHECK: %[[UPDATED_B:.*]] = "xla_hlo.transpose"(%[[B]]) {permutation = dense<[0, 1]> : tensor<2xi64>} - // CHECK: "xla_hlo.dot"(%[[UPDATED_A]], %[[UPDATED_B]]) + // CHECK: "xla_hlo.dot"(%[[A]], %[[B]]) %0 = "tf.MatMul"(%a, %b) {transpose_a = false, transpose_b = false} : (tensor<5x7xf32>, tensor<7x11xf32>) -> tensor<5x11xf32> return %0 : tensor<5x11xf32> @@ -710,9 +813,8 @@ func @matmul_notranspose(%a: tensor<5x7xf32>, %b: tensor<7x11xf32>) -> tensor<5x // CHECK-LABEL: matmul_transpose_b // CHECK-SAME: (%[[A:.*]]: tensor<5x7xf32>, %[[B:.*]]: tensor<11x7xf32>) func @matmul_transpose_b(%a: tensor<5x7xf32>, %b: tensor<11x7xf32>) -> tensor<5x11xf32> { - // CHECK: %[[UPDATED_A:.*]] = "xla_hlo.transpose"(%[[A]]) {permutation = dense<[0, 1]> : tensor<2xi64>} // CHECK: %[[UPDATED_B:.*]] = "xla_hlo.transpose"(%[[B]]) {permutation = dense<[1, 0]> : tensor<2xi64>} - // CHECK: "xla_hlo.dot"(%[[UPDATED_A]], %[[UPDATED_B]]) + // CHECK: "xla_hlo.dot"(%[[A]], %[[UPDATED_B]]) %0 = "tf.MatMul"(%a, %b) {transpose_a = false, transpose_b = true} : (tensor<5x7xf32>, tensor<11x7xf32>) -> tensor<5x11xf32> return %0 : tensor<5x11xf32> @@ -1021,7 +1123,7 @@ func @rfft_1D(%arg0: tensor<8xf32>) -> tensor<8xcomplex> { // CHECK-LABEL: @transpose_noop func @transpose_noop(%arg0: tensor<2x3xf32>) -> tensor<2x3xf32> { %permutation = "tf.Const"() {value = dense<[0, 1]> : tensor<2xi64>} : () -> (tensor<2xi64>) - // CHECK: "xla_hlo.transpose" + // CHECK: return %arg0 %0 = "tf.Transpose"(%arg0, %permutation) : (tensor<2x3xf32>, tensor<2xi64>) -> tensor<2x3xf32> return %0 : tensor<2x3xf32> } @@ -1246,6 +1348,13 @@ func @log_unranked(%arg0: tensor<*xf32>) -> tensor<*xf32> { return %0 : tensor<*xf32> } +// CHECK-LABEL: func @not_op_unranked +func @not_op_unranked(%arg0: tensor<*xi1>) -> tensor<*xi1> { + // CHECK: "xla_hlo.not"(%arg0) : (tensor<*xi1>) -> tensor<*xi1> + %0 = "tf.LogicalNot"(%arg0) : (tensor<*xi1>) -> tensor<*xi1> + return %0 : tensor<*xi1> +} + // CHECK-LABEL: @neg func @neg(%arg0: tensor<2xf32>) -> tensor<2xf32> { // CHECK: "xla_hlo.neg"(%arg0) : (tensor<2xf32>) -> tensor<2xf32> @@ -1528,23 +1637,45 @@ func @strided_slice_range_clamping(%input: tensor<4x8xf32>) -> tensor<0x3xf32> { return %output : tensor<0x3xf32> } -// CHECK-LABEL: strided_slice_shrink_axis -func @strided_slice_shrink_axis(%input: tensor<4x8xf32>) -> tensor { - %begin = "tf.Const"() {value = dense<[1, 3]> : tensor<2xi32>} : () -> (tensor<2xi32>) - %end = "tf.Const"() {value = dense<[2, 4]> : tensor<2xi32>} : () -> (tensor<2xi32>) - %strides = "tf.Const"() {value = dense<[1, 3]> : tensor<2xi32>} : () -> (tensor<2xi32>) +// CHECK-LABEL: strided_slice_begin_end_mask +// CHECK-SAME: %[[INPUT:[a-z0-9]+]]: tensor<4x128x1024xf32> +func @strided_slice_begin_end_mask(%input: tensor<4x128x1024xf32>) { - // CHECK: %[[SLICED:.*]] = "xla_hlo.slice" - // CHECK-DAG-SAME: start_indices = dense<[1, 3]> - // CHECK-DAG-SAME: limit_indices = dense<[2, 4]> - // CHECK-DAG-SAME: strides = dense<[1, 3]> - // CHECK-SAME: -> tensor<1x1xf32> + // For StridedSlice + // Dim #: 0, 1, 2 + // Input shape: [4, 128, 1024] + // Begin: 1, 4, -3 + // End: 8, 65, 42 + // Stride: 1, 4, -1 + // Begin mask: 1, 0, 0 (= 1) + // End mask: 0, 0, 1 (= 4) - // CHECK: "xla_hlo.reshape"(%[[SLICED]]) : (tensor<1x1xf32>) -> tensor + // So result shape: + // Dim #0: begin mask (1) -> begin = 0; end 8 canonicalized to 4: so 4 + // Dim #1: 4 to 65 stride 4: so 16 + // Dim #2: begin -3 + 1024 = 1021; end mask (1) -> end = -1: so 1022 + // result shape: [4, 16, 1022] - %output = "tf.StridedSlice"(%input, %begin, %end, %strides) {shrink_axis_mask = 3 - : i64} : (tensor<4x8xf32>, tensor<2xi32>, tensor<2xi32>, tensor<2xi32>) -> tensor - return %output : tensor + // As output shape of StridedSlice differs, a reshape will follow. + + %begin = "tf.Const"() {value = dense<[1, 4, -3]> : tensor<3xi32>} : () -> (tensor<3xi32>) + %end = "tf.Const"() {value = dense<[8, 65, 42]> : tensor<3xi32>} : () -> (tensor<3xi32>) + %strides = "tf.Const"() {value = dense<[1, 4, -1]> : tensor<3xi32>} : () -> (tensor<3xi32>) + + // CHECK: %[[REVERSE:.*]] = "xla_hlo.reverse"(%[[INPUT]]) + + // CHECK: %[[SLICE:.*]] = "xla_hlo.slice"(%[[REVERSE]]) + // CHECK-DAG-SAME: limit_indices = dense<[4, 65, 1024]> + // CHECK-DAG-SAME: start_indices = dense<[0, 4, 2]> + // CHECK-DAG-SAME: strides = dense<[1, 4, 1]> + // CHECK-SAME: -> tensor<4x16x1022xf32> + + %0 = "tf.StridedSlice"(%input, %begin, %end, %strides) {begin_mask = 1, end_mask = 4} : (tensor<4x128x1024xf32>, tensor<3xi32>, tensor<3xi32>, tensor<3xi32>) -> tensor + + // CHECK: "xla_hlo.reshape"(%[[SLICE]]) + // CHECK-SAME: -> tensor + + return } //===----------------------------------------------------------------------===// @@ -1811,17 +1942,14 @@ func @rng_uniform(%arg0: tensor<3xi32>) -> tensor<12x12x64xf32> { // Range op legalizations. //===----------------------------------------------------------------------===// -// CHECK-LABEL: range -func @range() -> tensor<5xf32> { - %0 = "tf.Const"() {device = "", dtype = "tfdtype$DT_FLOAT", name = "range/start", value = dense<0.000000e+00> : tensor} : () -> tensor +// CHECK-LABEL: func @range +// CHECK-SAME: [[START:%.*]]: tensor, [[DELTA:%.*]]: tensor +func @range(%arg0: tensor, %arg1: tensor) -> tensor<5xf32> { %1 = "tf.Const"() {device = "", dtype = "tfdtype$DT_FLOAT", name = "range/limit", value = dense<5.000000e+00> : tensor} : () -> tensor - %2 = "tf.Const"() {device = "", dtype = "tfdtype$DT_FLOAT", name = "range/delta", value = dense<1.000000e+00> : tensor} : () -> tensor - // CHECK-DAG: [[START:%.*]] = xla_hlo.constant dense<0.000000e+00> : tensor - // CHECK-DAG: [[DELTA:%.*]] = xla_hlo.constant dense<1.000000e+00> : tensor // CHECK-DAG: [[IOTA:%.*]] = "xla_hlo.iota" // CHECK-DAG: [[MUL:%.*]] = "xla_hlo.mul"([[IOTA]], [[DELTA]]) {broadcast_dimensions = dense<[]> : tensor<0xi64>} // CHECK: "xla_hlo.add"([[MUL]], [[START]]) {broadcast_dimensions = dense<[]> : tensor<0xi64>} - %3 = "tf.Range"(%0, %1, %2) {Tidx = "tfdtype$DT_FLOAT", device = "", name = "range"} : (tensor, tensor, tensor) -> tensor<5xf32> + %3 = "tf.Range"(%arg0, %1, %arg1) {Tidx = "tfdtype$DT_FLOAT", device = "", name = "range"} : (tensor, tensor, tensor) -> tensor<5xf32> return %3 : tensor<5xf32> } @@ -2178,3 +2306,133 @@ func @unpack_dynamic(%input: tensor) -> (tensor, tensor) -> (tensor, tensor) return %0#0, %0#1 : tensor, tensor } + +//===----------------------------------------------------------------------===// +// tf.UnsortedSegment{Max|Min|Prod|Sum} legalization +//===----------------------------------------------------------------------===// + +// CHECK-LABEL: @unsorted_segment_sum +// CHECK-SAME: [[DATA:%.*]]: tensor<8x16x64xf32> +// CHECK-SAME: [[SI:%.*]]: tensor<8x16xi32> +func @unsorted_segment_sum(%data: tensor<8x16x64xf32>, %segment_ids : tensor<8x16xi32>) -> (tensor<4x64xf32>) { + %num_segments = "tf.Const"() {value = dense<4> : tensor} : () -> tensor + // CHECK: [[ZERO:%.*]] = xla_hlo.constant dense<0.000000e+00> : tensor + // CHECK: [[INIT:%.*]] = "xla_hlo.broadcast"([[ZERO]]) {broadcast_sizes = dense<[4, 64]> : tensor<2xi64>} : (tensor) -> tensor<4x64xf32> + // CHECK: [[SCATTER:%.*]] = "xla_hlo.scatter"([[INIT]], [[SI]], [[DATA]]) ( { + // CHECK: ^{{.*}}([[LHS:%.*]]: tensor, [[RHS:%.*]]: tensor): + // CHECK: [[ADD:%.*]] = xla_hlo.add [[LHS]], [[RHS]] : tensor + // CHECK: "xla_hlo.return"([[ADD]]) + // CHECK: }) {indices_are_sorted = false, scatter_dimension_numbers = {index_vector_dim = 2 : i64, inserted_window_dims = dense<0> : tensor<1xi64>, scatter_dims_to_operand_dims = dense<0> : tensor<1xi64>, update_window_dims = dense<2> : tensor<1xi64>}, unique_indices = false} : (tensor<4x64xf32>, tensor<8x16xi32>, tensor<8x16x64xf32>) -> tensor<4x64xf32> + // CHECK: return [[SCATTER]] + %0 = "tf.UnsortedSegmentSum"(%data, %segment_ids, %num_segments) : (tensor<8x16x64xf32>, tensor<8x16xi32>, tensor) -> (tensor<4x64xf32>) + return %0: tensor<4x64xf32> +} + +// CHECK-LABEL: @unsorted_segment_prod +// CHECK-SAME: [[DATA:%.*]]: tensor<8x?x64xf32> +// CHECK-SAME: [[SI:%.*]]: tensor +func @unsorted_segment_prod(%data: tensor<8x?x64xf32>, %segment_ids : tensor) -> (tensor<4x?xf32>) { + %num_segments = "tf.Const"() {value = dense<4> : tensor} : () -> tensor + // CHECK: [[ONE:%.*]] = xla_hlo.constant dense<1.000000e+00> : tensor + // CHECK: [[INIT:%.*]] = "xla_hlo.broadcast"([[ONE]]) {broadcast_sizes = dense<[4, 64]> : tensor<2xi64>} : (tensor) -> tensor<4x64xf32> + // CHECK: [[SCATTER:%.*]] = "xla_hlo.scatter"([[INIT]], [[SI]], [[DATA]]) ( { + // CHECK: ^{{.*}}([[LHS:%.*]]: tensor, [[RHS:%.*]]: tensor): + // CHECK: [[MUL:%.*]] = xla_hlo.mul [[LHS]], [[RHS]] : tensor + // CHECK: "xla_hlo.return"([[MUL]]) + // CHECK: }) {indices_are_sorted = false, scatter_dimension_numbers = {index_vector_dim = 2 : i64, inserted_window_dims = dense<0> : tensor<1xi64>, scatter_dims_to_operand_dims = dense<0> : tensor<1xi64>, update_window_dims = dense<2> : tensor<1xi64>}, unique_indices = false} : (tensor<4x64xf32>, tensor, tensor<8x?x64xf32>) -> tensor<4x?xf32> + // CHECK: return [[SCATTER]] + %0 = "tf.UnsortedSegmentProd"(%data, %segment_ids, %num_segments) : (tensor<8x?x64xf32>, tensor, tensor) -> (tensor<4x?xf32>) + return %0: tensor<4x?xf32> +} + +// CHECK-LABEL: @unsorted_segment_min +func @unsorted_segment_min(%data: tensor<8x?x64xf32>, %segment_ids : tensor) -> (tensor<4x?xf32>) { + %num_segments = "tf.Const"() {value = dense<4> : tensor} : () -> tensor + // CHECK: xla_hlo.constant dense<0x7F800000> : tensor + // CHECK: xla_hlo.scatter + // CHECK: xla_hlo.min + %0 = "tf.UnsortedSegmentMin"(%data, %segment_ids, %num_segments) : (tensor<8x?x64xf32>, tensor, tensor) -> (tensor<4x?xf32>) + return %0: tensor<4x?xf32> +} + +// CHECK-LABEL: @unsorted_segment_max +func @unsorted_segment_max(%data: tensor<8x?x64xf32>, %segment_ids : tensor) -> (tensor<4x?xf32>) { + %num_segments = "tf.Const"() {value = dense<4> : tensor} : () -> tensor + // CHECK: xla_hlo.constant dense<0xFF800000> : tensor + // CHECK: xla_hlo.scatter + // CHECK: xla_hlo.max + %0 = "tf.UnsortedSegmentMax"(%data, %segment_ids, %num_segments) : (tensor<8x?x64xf32>, tensor, tensor) -> (tensor<4x?xf32>) + return %0: tensor<4x?xf32> +} + +//===----------------------------------------------------------------------===// +// tf.GatherV2 legalization +//===----------------------------------------------------------------------===// + +// CHECK-LABEL: @gather_v2 +func @gather_v2(%arg0: tensor<16x2x3xf32>, %arg1: tensor<16x5xi32>) -> tensor<16x2x5x3xf32> { + // CHECK: "xla_hlo.torch_index_select"(%arg0, %arg1) {batch_dims = 1 : i64, dim = 2 : i64} : (tensor<16x2x3xf32>, tensor<16x5xi32>) -> tensor<16x2x5x3xf32> + %0 = "tf.Const"() { value = dense<[-1]> : tensor<1xi32> } : () -> tensor<1xi32> + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -1 : i64} : (tensor<16x2x3xf32>, tensor<16x5xi32>, tensor<1xi32>) -> tensor<16x2x5x3xf32> + return %1 : tensor<16x2x5x3xf32> +} + +// CHECK-LABEL: @gather_v2_dynamic +func @gather_v2_dynamic(%arg0: tensor, %arg1: tensor) -> tensor<*xf32> { + // CHECK: "xla_hlo.torch_index_select"(%arg0, %arg1) {batch_dims = 1 : i64, dim = 2 : i64} : (tensor, tensor) -> tensor<*xf32> + %0 = "tf.Const"() { value = dense<[-1]> : tensor<1xi32> } : () -> tensor<1xi32> + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -1 : i64} : (tensor, tensor, tensor<1xi32>) -> tensor<*xf32> + return %1 : tensor<*xf32> +} + +// CHECK-LABEL: @gather_v2_unranked +func @gather_v2_unranked(%arg0: tensor<*xf32>, %arg1: tensor<*xi32>) -> tensor<*xf32> { + // CHECK: tf.GatherV2 + %0 = "tf.Const"() { value = dense<[-1]> : tensor<1xi32> } : () -> tensor<1xi32> + %1 = "tf.GatherV2"(%arg0, %arg1, %0) {batch_dims = -1 : i64} : (tensor<*xf32>, tensor<*xi32>, tensor<1xi32>) -> tensor<*xf32> + return %1 : tensor<*xf32> +} + +//===----------------------------------------------------------------------===// +// tf.StridedSliceGrad legalization +//===----------------------------------------------------------------------===// + +// CHECK-LABEL: strided_slice_grad +// CHECK-SAME: [[GRAD:%.*]]: tensor<4x16x1022xf32> +func @strided_slice_grad(%grad: tensor<4x16x1022xf32>) -> tensor<4x128x1024xf32> { + + // For StridedSlice + // Dim #: 0, 1, 2 + // Input shape: [4, 128, 1024] + // Begin: 1, 4, -3 + // End: 8, 65, 42 + // Stride: 1, 4, -1 + // Begin mask: 1, 0, 0 (= 1) + // End mask: 0, 0, 1 (= 4) + + // So result shape: + // Dim #0: begin mask (1) -> begin = 0; end 8 canonicalized to 4: so 4 + // Dim #1: 4 to 65 stride 4: so 16 + // Dim #2: begin -3 + 1024 = 1021; end mask (1) -> end = -1: so 1022 + // result shape: [4, 16, 1022] + + // To pad back: + // Dim #: 0, 1, 2 + // Pad low: 0, 4, 0 + // Pad interm: 0, 3, 0 + // Pad high: 0, 63, 2 + + %shape = "tf.Const"() {value = dense<[4, 128, 1024]> : tensor<3xi32>} : () -> (tensor<3xi32>) + %begin = "tf.Const"() {value = dense<[1, 4, -3]> : tensor<3xi32>} : () -> (tensor<3xi32>) + %end = "tf.Const"() {value = dense<[8, 65, 42]> : tensor<3xi32>} : () -> (tensor<3xi32>) + %strides = "tf.Const"() {value = dense<[1, 4, -1]> : tensor<3xi32>} : () -> (tensor<3xi32>) + + // CHECK: [[RESHAPE:%.*]] = "xla_hlo.reshape"(%arg0) : (tensor<4x16x1022xf32>) -> tensor<4x16x1022xf32> + // CHECK: [[REVERSE:%.*]] = "xla_hlo.reverse"([[RESHAPE]]) {dimensions = dense<2> : tensor<1xi64>} : (tensor<4x16x1022xf32>) -> tensor<4x16x1022xf32> + // CHECK: [[ZERO:%.*]] = xla_hlo.constant dense<0.000000e+00> : tensor + // CHECK: [[PAD:%.*]] = "xla_hlo.pad"([[REVERSE]], [[ZERO]]) {edge_padding_high = dense<[0, 63, 2]> : tensor<3xi64>, edge_padding_low = dense<[0, 4, 0]> : tensor<3xi64>, interior_padding = dense<[0, 3, 0]> : tensor<3xi64>} : (tensor<4x16x1022xf32>, tensor) -> tensor<4x128x1024xf32> + + %0 = "tf.StridedSliceGrad"(%shape, %begin, %end, %strides, %grad) {begin_mask = 1, end_mask = 4} : (tensor<3xi32>, tensor<3xi32>, tensor<3xi32>, tensor<3xi32>, tensor<4x16x1022xf32>) -> tensor<4x128x1024xf32> + // CHECK: return [[PAD]] + return %0: tensor<4x128x1024xf32> +} diff --git a/tensorflow/compiler/mlir/xla/tests/lhlo-fuse-linalg.mlir b/tensorflow/compiler/mlir/xla/tests/lhlo-fuse-linalg.mlir index 2d23a5fb1f9..cc618e71438 100644 --- a/tensorflow/compiler/mlir/xla/tests/lhlo-fuse-linalg.mlir +++ b/tensorflow/compiler/mlir/xla/tests/lhlo-fuse-linalg.mlir @@ -1,7 +1,7 @@ // RUN: tf-opt -lhlo-fuse-linalg %s -o - | FileCheck %s #map0 = (d0, d1) -> (d0, d1) -#pointwise_2d_trait = {indexing_maps = [#map0, #map0, #map0], iterator_types = ["parallel", "parallel"], n_views = [2, 1]} +#pointwise_2d_trait = {args_in = 2, args_out = 1, indexing_maps = [#map0, #map0, #map0], iterator_types = ["parallel", "parallel"]} func @fusion(%multiplier: memref<2x2xf32>, %summand_1: memref<2x2xf32>, %summand_2: memref<2x2xf32>, %result: memref<2x2xf32>) { %temp_result = alloc() {temp = true} : memref<2x2xf32> @@ -18,7 +18,6 @@ func @fusion(%multiplier: memref<2x2xf32>, %summand_1: memref<2x2xf32>, dealloc %temp_result : memref<2x2xf32> "xla_lhlo.terminator"() : () -> () } - // CHECK-LABEL: func @fusion // CHECK-NOT: linalg.generic // CHECK: loop.for @@ -28,3 +27,52 @@ func @fusion(%multiplier: memref<2x2xf32>, %summand_1: memref<2x2xf32>, // CHECK: addf // CHECK: linalg.generic // CHECK: mulf + +func @fusion_of_three(%arg0: memref<100x10xf32>, + %arg1: memref<100xf32>, + %arg2: memref<100x10xf32>) { + %0 = alloc() {temp = true} : memref<100x10xf32> + linalg.generic { + args_in = 1 : i64, + args_out = 1 : i64, + indexing_maps = [(d0, d1) -> (d0), (d0, d1) -> (d0, d1)], + iterator_types = ["parallel", "parallel"] + } %arg1, %0 { + ^bb0(%arg3: f32, %arg4: f32): // no predecessors + linalg.yield %arg3 : f32 + }: memref<100xf32>, memref<100x10xf32> + %1 = alloc() {temp = true} : memref<100x10xf32> + linalg.generic { + args_in = 2 : i64, + args_out = 1 : i64, + indexing_maps = [(d0, d1) -> (d0, d1), (d0, d1) -> (d0, d1), (d0, d1) -> (d0, d1)], + iterator_types = ["parallel", "parallel"] + } %arg0, %0, %1 { + ^bb0(%arg3: f32, %arg4: f32, %arg5: f32): // no predecessors + %2 = subf %arg3, %arg4 : f32 + linalg.yield %2 : f32 + }: memref<100x10xf32>, memref<100x10xf32>, memref<100x10xf32> + dealloc %0 : memref<100x10xf32> + linalg.generic { + args_in = 1 : i64, + args_out = 1 : i64, + indexing_maps = [(d0, d1) -> (d0, d1), (d0, d1) -> (d0, d1)], + iterator_types = ["parallel", "parallel"] + } %1, %arg2 { + ^bb0(%arg3: f32, %arg4: f32): // no predecessors + %2 = exp %arg3 : f32 + linalg.yield %2 : f32 + }: memref<100x10xf32>, memref<100x10xf32> + dealloc %1 : memref<100x10xf32> + return +} +// CHECK-LABEL: func @fusion +// CHECK-NOT: linalg.generic +// CHECK: loop.for +// CHECK: loop.for +// CHECK-NOT: loop.for +// CHECK: linalg.generic +// CHECK: linalg.generic +// CHECK: subf +// CHECK: linalg.generic +// CHECK: exp diff --git a/tensorflow/compiler/mlir/xla/tests/lhlo-legalize-to-gpu.mlir b/tensorflow/compiler/mlir/xla/tests/lhlo-legalize-to-gpu.mlir new file mode 100644 index 00000000000..d2fe8846412 --- /dev/null +++ b/tensorflow/compiler/mlir/xla/tests/lhlo-legalize-to-gpu.mlir @@ -0,0 +1,35 @@ +// RUN: tf-opt %s -lhlo-legalize-to-gpu -split-input-file | FileCheck %s + +func @reduce(%arg: memref<100x10xf32>, + %init: memref, + %result: memref<100xf32>) { + "xla_lhlo.reduce"(%arg, %init, %result) ( { + ^bb0(%lhs: memref, %rhs: memref, %res: memref): + "xla_lhlo.add"(%lhs, %rhs, %res) + : (memref, memref, memref) -> () + "xla_lhlo.terminator"() : () -> () + } ) {dimensions = dense<[1]> : tensor<1xi64>} + : (memref<100x10xf32>, memref, memref<100xf32>) -> () + return +} + +// CHECK: func @reduce(%[[ARG0:.*]]: memref<100x10xf32>, %[[ARG1:.*]]: memref, %[[ARG2:.*]]: memref<100xf32>) { +// CHECK-DAG: %[[C100:.*]] = constant 100 : index +// CHECK-DAG: %[[C1:.*]] = constant 1 : index +// CHECK: "gpu.launch"(%[[C1]], %[[C1]], %[[C1]], %[[C100]], %[[C1]], %[[C1]], %[[ARG0]], %[[ARG1]], %[[ARG2]]) ( { +// CHECK: ^bb0({{.*}} %[[VAL:.*]]: memref<100x10xf32>, %[[INIT:.*]]: memref, %[[RES:.*]]: memref<100xf32>) +// CHECK: %[[ACC:.*]] = load %[[INIT]][] : memref +// CHECK: store %[[ACC]], %[[RES]][%[[IDX:.*]]] : memref<100xf32> +// CHECK-DAG: %[[LB:.*]] = constant 0 : index +// CHECK-DAG: %[[UB:.*]] = constant 10 : index +// CHECK-DAG: %[[STEP:.*]] = constant 1 : index +// CHECK: loop.for %[[IDX1:.*]] = %[[LB]] to %[[UB]] step %[[STEP]] { +// CHECK: %[[LHS:.*]] = linalg.slice %[[RES]][%[[IDX]]] : memref<100xf32>, index, memref +// CHECK: %[[RHS:.*]] = linalg.slice %[[VAL]][%[[IDX]], %[[IDX1]]] : memref<100x10xf32>, index, index, memref +// CHECK: "xla_lhlo.add"(%[[LHS]], %[[RHS]], %[[LHS]]) : (memref, memref, memref) -> () +// CHECK: } +// CHECK: "gpu.return"() : () -> () +// CHECK: }) +// CHECK: return +// CHECK: } +// CHECK: } diff --git a/tensorflow/compiler/mlir/xla/tests/lhlo-legalize-to-linalg.mlir b/tensorflow/compiler/mlir/xla/tests/lhlo-legalize-to-linalg.mlir index 1003e84ac85..42e0098e1d5 100644 --- a/tensorflow/compiler/mlir/xla/tests/lhlo-legalize-to-linalg.mlir +++ b/tensorflow/compiler/mlir/xla/tests/lhlo-legalize-to-linalg.mlir @@ -18,7 +18,10 @@ func @element_wise(%lhs: memref<2x2xf32>, %rhs: memref<2x2xf32>, // CHECK-LABEL: func @element_wise_scalar func @element_wise_scalar(%lhs: memref, %rhs: memref, %result: memref) { -// CHECK: "xla_lhlo.add" +// CHECK: %[[LHS:.*]] = load +// CHECK: %[[RHS:.*]] = load +// CHECK: %[[RES:.*]] = addf %[[LHS]], %[[RHS]] +// CHECK: store %[[RES]] // CHECK-NEXT: return "xla_lhlo.add"(%lhs, %rhs, %result) : (memref, memref, memref) -> () @@ -132,7 +135,7 @@ func @iota(%out: memref<7x10xf32>) { "xla_lhlo.iota"(%out) {iota_dimension = 1 : i64} : (memref<7x10xf32>) -> () return } -// CHECK: linalg.indexed_generic {indexing_maps = [#[[RESULT_MAP]]] +// CHECK: linalg.indexed_generic {{{.*}}indexing_maps = [#[[RESULT_MAP]]] // CHECK-NEXT: ^bb0(%[[D0:.*]]: index, %[[D1:.*]]: index, %[[RESULT:.*]]: f32): // CHECK-NEXT: %[[INT_CAST:.*]] = index_cast %[[D1]] : index to i32 // CHECK-NEXT: %[[FLOAT_CAST:.*]] = sitofp %[[INT_CAST]] : i32 to f32 @@ -158,6 +161,31 @@ func @broadcast(%operand: memref<5x7x1xf32>, %result: memref<7x10x6x4x5xf32>) { : (memref<5x7x1xf32>, memref<7x10x6x4x5xf32>) -> () return } -// CHECK: linalg.generic {indexing_maps = [#[[OPERAND_MAP]], #[[RESULT_MAP]]] +// CHECK: linalg.generic {{{.*}}indexing_maps = [#[[OPERAND_MAP]], #[[RESULT_MAP]]] // CHECK-NEXT: ^bb0(%[[OPERAND:.*]]: f32, %[[RESULT:.*]]: f32): // CHECK-NEXT: linalg.yield %[[OPERAND]] : f32 + +// ----- + +// CHECK-DAG: #[[RESULT_MAP:.*]] = (d0, d1, d2) -> (d0, d1, d2) +// CHECK-LABEL: func @broadcast_scalar +func @broadcast_scalar(%operand: memref, %result: memref<7x10x6xf32>) { + "xla_lhlo.broadcast_in_dim"(%operand, %result) + {broadcast_dimensions = dense<[]> : tensor<0xi64>} + : (memref, memref<7x10x6xf32>) -> () + return +} +// CHECK: linalg.generic {{{.*}}indexing_maps = [#[[RESULT_MAP]]] +// CHECK-NEXT: ^bb0(%[[RESULT:.*]]: f32): +// CHECK-NEXT: %[[CONST:.*]] = load %{{.*}} : memref +// CHECK-NEXT: linalg.yield %[[CONST]] : f32 + +// ----- + +// CHECK-LABEL: func @constant +func @constant(%value: memref) { + "xla_lhlo.constant"(%value) {value = dense<10> : tensor} : (memref) -> () + return +} +// CHECK: %[[CONSTANT:.*]] = constant 10 : i32 +// CHECK: store %[[CONSTANT]], %{{.*}}[] : memref diff --git a/tensorflow/compiler/mlir/xla/tests/ops.mlir b/tensorflow/compiler/mlir/xla/tests/ops.mlir index 4f142f294e4..a315a2318b5 100644 --- a/tensorflow/compiler/mlir/xla/tests/ops.mlir +++ b/tensorflow/compiler/mlir/xla/tests/ops.mlir @@ -1,6 +1,17 @@ // RUN: tf-opt %s -verify-diagnostics -split-input-file | tf-opt | FileCheck %s -// Tests for ops with custom constraints, verifiers, printer or parser methods. +// Tests for types, ops with custom constraints, verifiers, printer or parser +// methods. + +// CHECK-LABEL: func @token_type() -> !xla_hlo.token +func @token_type() -> !xla_hlo.token + +// ----- + +// expected-error@+1 {{unknown xla_hlo type: foobar}} +func @invalid_type() -> !xla_hlo.foobar + +// ----- // CHECK-LABEL: func @broadcast func @broadcast(%arg0: tensor<3xi32>) -> tensor<1x2x3xi32> { diff --git a/tensorflow/compiler/mlir/xla/tests/translate/BUILD b/tensorflow/compiler/mlir/xla/tests/translate/BUILD index deb4e2ce231..857ee2896a2 100644 --- a/tensorflow/compiler/mlir/xla/tests/translate/BUILD +++ b/tensorflow/compiler/mlir/xla/tests/translate/BUILD @@ -19,5 +19,6 @@ filegroup( data = [ "//tensorflow/compiler/mlir:tf-mlir-translate", "@llvm//:FileCheck", + "@llvm//:not", ], ) diff --git a/tensorflow/compiler/mlir/xla/tests/translate/dynamic_parameter_binding.mlir b/tensorflow/compiler/mlir/xla/tests/translate/dynamic_parameter_binding.mlir new file mode 100644 index 00000000000..a4ad784fce8 --- /dev/null +++ b/tensorflow/compiler/mlir/xla/tests/translate/dynamic_parameter_binding.mlir @@ -0,0 +1,114 @@ +// RUN: tf-mlir-translate -split-input-file -mlir-hlo-to-hlo %s | FileCheck %s --dump-input=fail +// RUN: tf-mlir-translate -split-input-file -mlir-hlo-to-hlo -emit-use-tuple-args %s | FileCheck %s --check-prefix=TUPLE --dump-input=fail + +// Test entry function with no dynamic parameter bindings. + +func @main(%arg0: tensor<10xf32>, %arg1: tensor) { + return +} + +// CHECK-LABEL: hlo_module +// CHECK: dynamic_parameter_binding +// CHECK-NEXT: } + +// TUPLE-LABEL: hlo_module +// TUPLE: dynamic_parameter_binding +// TUPLE-NEXT: } + +// ----- + +// Test entry function with single dynamic parameter binding on an argument. + +func @main(%arg0: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [0 : i32], padding_arg_indices = [1 : i32]}}, %arg1: tensor) { + return +} + +// CHECK-LABEL: hlo_module +// CHECK: dynamic_parameter_binding +// CHECK-NEXT: entries +// CHECK-NEXT: dynamic_param_num: 1 +// CHECK-NEXT: } +// CHECK-NOT: entries + +// TUPLE-LABEL: hlo_module +// TUPLE: dynamic_parameter_binding +// TUPLE-NEXT: entries +// TUPLE-NEXT: dynamic_param_index: 1 +// TUPLE-NEXT: target_param_index: 0 +// TUPLE-NEXT: } +// TUPLE-NOT: entries + +// ----- + +// Test entry function with multiple dynamic parameter bindings on an argument. + +func @main(%arg0: tensor<8x10xf32> {xla_hlo.padding_map = {shape_indices = [0 : i32, 1 : i32], padding_arg_indices = [1 : i32, 2 : i32]}}, %arg1: tensor, %arg2: tensor) { + return +} + +// CHECK-LABEL: hlo_module +// CHECK: dynamic_parameter_binding +// CHECK-NEXT: entries +// CHECK-NEXT: dynamic_param_num: 1 +// CHECK-NEXT: } +// CHECK-NEXT: entries +// CHECK-NEXT: dynamic_param_num: 2 +// CHECK-NEXT: target_param_dim_num: 1 +// CHECK-NEXT: } +// CHECK-NOT: entries + +// TUPLE-LABEL: hlo_module +// TUPLE: dynamic_parameter_binding +// TUPLE-NEXT: entries +// TUPLE-NEXT: dynamic_param_index: 1 +// TUPLE-NEXT: target_param_index: 0 +// TUPLE-NEXT: } +// TUPLE-NEXT: entries +// TUPLE-NEXT: dynamic_param_index: 2 +// TUPLE-NEXT: target_param_index: 0 +// TUPLE-NEXT: target_param_dim_num: 1 +// TUPLE-NEXT: } +// TUPLE-NOT: entries + +// ----- + +// Test entry function with multiple dynamic parameter bindings on multiple +// arguments. + +func @main(%arg0: tensor<8x10xf32> {xla_hlo.padding_map = {shape_indices = [0 : i32, 1 : i32], padding_arg_indices = [1 : i32, 2 : i32]}}, %arg1: tensor, %arg2: tensor, %arg3: tensor<10x8x6xi32> {xla_hlo.padding_map = {shape_indices = [2 : i32], padding_arg_indices = [4 : i32]}}, %arg4: tensor) { + return +} + +// CHECK-LABEL: hlo_module +// CHECK: dynamic_parameter_binding +// CHECK-NEXT: entries +// CHECK-NEXT: dynamic_param_num: 1 +// CHECK-NEXT: } +// CHECK-NEXT: entries +// CHECK-NEXT: dynamic_param_num: 2 +// CHECK-NEXT: target_param_dim_num: 1 +// CHECK-NEXT: } +// CHECK-NEXT: entries +// CHECK-NEXT: dynamic_param_num: 4 +// CHECK-NEXT: target_param_num: 3 +// CHECK-NEXT: target_param_dim_num: 2 +// CHECK-NEXT: } +// CHECK-NOT: entries + +// TUPLE-LABEL: hlo_module +// TUPLE: dynamic_parameter_binding +// TUPLE-NEXT: entries +// TUPLE-NEXT: dynamic_param_index: 1 +// TUPLE-NEXT: target_param_index: 0 +// TUPLE-NEXT: } +// TUPLE-NEXT: entries +// TUPLE-NEXT: dynamic_param_index: 2 +// TUPLE-NEXT: target_param_index: 0 +// TUPLE-NEXT: target_param_dim_num: 1 +// TUPLE-NEXT: } +// TUPLE-NEXT: entries +// TUPLE-NEXT: dynamic_param_index: 4 +// TUPLE-NEXT: target_param_index: 3 +// TUPLE-NEXT: target_param_dim_num: 2 +// TUPLE-NEXT: } +// TUPLE-NOT: entries diff --git a/tensorflow/compiler/mlir/xla/tests/translate/dynamic_parameter_binding_invalid.mlir b/tensorflow/compiler/mlir/xla/tests/translate/dynamic_parameter_binding_invalid.mlir new file mode 100644 index 00000000000..6a7c64733c0 --- /dev/null +++ b/tensorflow/compiler/mlir/xla/tests/translate/dynamic_parameter_binding_invalid.mlir @@ -0,0 +1,159 @@ +// RUN: not tf-mlir-translate -split-input-file -mlir-hlo-to-hlo %s -o - 2>&1 | FileCheck %s + +// Test bad `xla_hlo.padding_map` attribute type. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = ""}) { + return +} + +// CHECK: requires 'xla_hlo.padding_map' dict attribute at arg 1 + +// ----- + +// Test missing `shape_indices` attribute in `xla_hlo.padding_map`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {}}) { + return +} + +// CHECK: requires 'shape_indices' array attribute in 'xla_hlo.padding_map' dict at arg 1 + +// ----- + +// Test bad `shape_indices` attribute type in `xla_hlo.padding_map`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = ""}}) { + return +} + +// CHECK: requires 'shape_indices' array attribute in 'xla_hlo.padding_map' dict at arg 1 + +// ----- + +// Test missing `padding_arg_indices` attribute in `xla_hlo.padding_map`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = []}}) { + return +} + +// CHECK: requires 'padding_arg_indices' array attribute in 'xla_hlo.padding_map' dict at arg 1 + +// ----- + +// Test bad `padding_arg_indices` attribute type in `xla_hlo.padding_map`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [], padding_arg_indices = ""}}) { + return +} + +// CHECK: requires 'padding_arg_indices' array attribute in 'xla_hlo.padding_map' dict at arg 1 + +// ----- + +// Test mismatched `shape_indices` and `padding_arg_indices` lengths. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32 ], padding_arg_indices = [ 0: i32, 0 : i32 ]}}) { + return +} + +// CHECK: requires 'shape_indices' and 'padding_arg_indices' array attributes in 'xla_hlo.padding_map' dic at arg 1 to be of the same size, got sizes 1 and 2 + +// ----- + +// Test non integer attribute in `shape_indices`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32, 0.0: f32 ], padding_arg_indices = [ 0: i32, 0: i32 ]}}) { + return +} + +// CHECK: requires element 1 in 'shape_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be an int attribute + +// ----- + +// Test non integer attribute in `padding_arg_indices`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32, 0: i32 ], padding_arg_indices = [ 0: i32, 0.0: f32 ]}}) { + return +} + +// CHECK: requires element 1 in 'padding_arg_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be an int attribute + +// ----- + +// Test negative out of range shape index in `shape_indices`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ -1: i32 ], padding_arg_indices = [ 0: i32 ]}}) { + return +} + +// CHECK: requires element 0 in 'shape_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be in range [0, 1), got -1 + +// ----- + +// Test positive out of range shape index in `shape_indices`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 1: i32 ], padding_arg_indices = [ 0: i32 ]}}) { + return +} + +// CHECK: requires element 0 in 'shape_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be in range [0, 1), got 1 + +// ----- + +// Test negative shape index in `shape_indices` for unranked argument. + +func @main(%arg0: tensor, %arg1: tensor<*xf32> {xla_hlo.padding_map = {shape_indices = [ -1: i32 ], padding_arg_indices = [ 0: i32 ]}}) { + return +} + +// CHECK: requires element 0 in 'shape_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be non-negative, got -1 + +// ----- + +// Test duplicate shape indices in `shape_indices`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32, 0: i32 ], padding_arg_indices = [ 0: i32, 0: i32 ]}}) { + return +} + +// CHECK: requires elements in 'shape_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be unique, got duplicate element 0 at index 1 + +// ----- + +// Test negative out of range shape index in `padding_arg_indices`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32 ], padding_arg_indices = [ -1: i32 ]}}) { + return +} + +// CHECK: requires element 0 in 'padding_arg_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be in range [0, 2), got -1 + +// ----- + +// Test positive out of range shape index in `padding_arg_indices`. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32 ], padding_arg_indices = [ 2: i32 ]}}) { + return +} + +// CHECK: requires element 0 in 'padding_arg_indices' array of 'xla_hlo.padding_map' dict at arg 1 to be in range [0, 2), got 2 + +// ----- + +// Test non scalar padding argument. + +func @main(%arg0: tensor<8xi32>, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32 ], padding_arg_indices = [ 0: i32 ]}}) { + return +} + +// CHECK: requires arg 0 to be a scalar for use as a dynamic parameter + +// ----- + +// Test non integer type padding argument. + +func @main(%arg0: tensor, %arg1: tensor<10xf32> {xla_hlo.padding_map = {shape_indices = [ 0: i32 ], padding_arg_indices = [ 0: i32 ]}}) { + return +} + +// CHECK: requires arg 0 to be of an int type for use as a dynamic parameter diff --git a/tensorflow/compiler/mlir/xla/tests/translate/export.mlir b/tensorflow/compiler/mlir/xla/tests/translate/export.mlir index 85ed317f8c6..442780a520c 100644 --- a/tensorflow/compiler/mlir/xla/tests/translate/export.mlir +++ b/tensorflow/compiler/mlir/xla/tests/translate/export.mlir @@ -1,6 +1,19 @@ // RUN: tf-mlir-translate -split-input-file -mlir-hlo-to-hlo-text %s | FileCheck %s -// CHECK-LABEL: HloModule +// CHECK: HloModule +func @main(%arg0: !xla_hlo.token, %arg1: !xla_hlo.token) -> !xla_hlo.token { + %0 = "xla_hlo.after_all"(%arg0, %arg1) : (!xla_hlo.token, !xla_hlo.token) -> !xla_hlo.token + return %0 : !xla_hlo.token +} + +// CHECK: ENTRY +// CHECK: %[[ARG0:.*]] = token[] parameter(0) +// CHECK: %[[ARG1:.*]] = token[] parameter(1) +// CHECK: ROOT %[[RESULT:.*]] = token[] after-all(token[] %[[ARG0]], token[] %[[ARG1]]) + +// ----- + +// CHECK: HloModule func @main(%arg0: tensor<10xf32>) -> tensor<10xf32> { %0 = "xla_hlo.all_reduce"(%arg0) ({ // Perform max reduction inside the region @@ -19,7 +32,7 @@ func @main(%arg0: tensor<10xf32>) -> tensor<10xf32> { } // CHECK: %[[COMPUTATION:.*]] ({{.*}}: f32[], {{.*}}: f32[]) -> f32[] -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[10] parameter(0) // CHECK: ROOT %[[RESULT:.*]] = f32[10] all-reduce(f32[10] %[[ARG0]]) // CHECK-SAME: channel_id=5 @@ -28,39 +41,39 @@ func @main(%arg0: tensor<10xf32>) -> tensor<10xf32> { // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%input: tensor<2x2x2x2xf32>, %scale: tensor<2xf32>, %mean: tensor<2xf32>, %variance: tensor<2xf32>, %grad_output: tensor<2x2x2x2xf32>) -> tuple, tensor<2xf32>, tensor<2xf32>> { %0 = "xla_hlo.batch_norm_grad" (%input, %scale, %mean, %variance, %grad_output) {epsilon = 0.001 : f32, feature_index = 0 : i64} : (tensor<2x2x2x2xf32>, tensor<2xf32>, tensor<2xf32>, tensor<2xf32>, tensor<2x2x2x2xf32>) -> tuple, tensor<2xf32>, tensor<2xf32>> return %0 : tuple, tensor<2xf32>, tensor<2xf32>> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[VAL_1:%.*]] = f32[2,2,2,2] parameter(0) // CHECK: [[VAL_2:%.*]] = f32[2] parameter(1) // CHECK: [[VAL_3:%.*]] = f32[2] parameter(2) // CHECK: [[VAL_4:%.*]] = f32[2] parameter(3) // CHECK: [[VAL_5:%.*]] = f32[2,2,2,2] parameter(4) -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: (f32[2,2,2,2], f32[2], f32[2]) batch-norm-grad(f32[2,2,2,2] [[VAL_1]], f32[2] [[VAL_2]], f32[2] [[VAL_3]], f32[2] [[VAL_4]], f32[2,2,2,2] [[VAL_5]]), epsilon=0.001, feature_index=0 // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%input: tensor<2x2x2x2xf32>, %scale: tensor<2xf32>, %offset: tensor<2xf32>) -> tuple, tensor<2xf32>, tensor<2xf32>> { %0 = "xla_hlo.batch_norm_training" (%input, %scale, %offset) {epsilon = 0.001 : f32, feature_index = 3 : i64} : (tensor<2x2x2x2xf32>, tensor<2xf32>, tensor<2xf32>) -> tuple, tensor<2xf32>, tensor<2xf32>> return %0 : tuple, tensor<2xf32>, tensor<2xf32>> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[VAL_1:%.*]] = f32[2,2,2,2] parameter(0) // CHECK: [[VAL_2:%.*]] = f32[2] parameter(1) // CHECK: [[VAL_3:%.*]] = f32[2] parameter(2) -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: (f32[2,2,2,2], f32[2], f32[2]) batch-norm-training(f32[2,2,2,2] [[VAL_1]], f32[2] [[VAL_2]], f32[2] [[VAL_3]]), epsilon=0.001, feature_index=3 // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<4xi32>, %arg1: tensor<4xi32>) -> (tensor<4xi32>, tensor<4xi32>, tensor<4xi32>, tensor<4xi32>) { // CHECK: [[VAL_1:%.*]] = s32[4] parameter(0) // CHECK: [[VAL_2:%.*]] = s32[4] parameter(1) @@ -76,14 +89,14 @@ func @main(%arg0: tensor<4xi32>, %arg1: tensor<4xi32>) -> (tensor<4xi32>, tensor // CHECK: [[SHRL:%.*]] = s32[4] shift-right-logical(s32[4] [[VAL_1]], s32[4] [[VAL_2]]) %3 = xla_hlo.shift_right_logical %arg0, %arg1 : tensor<4xi32> - // CHECK-LABEL: ROOT + // CHECK: ROOT // CHECK-SAME: [[VAL_7:%.*]] = (s32[4], s32[4], s32[4], s32[4]) tuple(s32[4] [[ATAN2]], s32[4] [[SHL]], s32[4] [[SHRA]], s32[4] [[SHRL]]) return %0, %1, %2, %3 : tensor<4xi32>, tensor<4xi32>, tensor<4xi32>, tensor<4xi32> } // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<1x4xi32>, %arg1: tensor<2x4xi32>, %arg2: tensor<2x3x4xi32>) -> tensor<2x3x4xi32> { // Same rank degenerate broadcast // CHECK: [[ARG_0:%.*]] = s32[1,4] parameter(0) @@ -103,7 +116,7 @@ func @main(%arg0: tensor<1x4xi32>, %arg1: tensor<2x4xi32>, %arg2: tensor<2x3x4xi // CHECK-NEXT: [[BROADCAST_3:%.*]] = s32[2,1,4] broadcast(s32[1,4] [[ARG_0]]), dimensions={1,2} // CHECK-NEXT: [[RESHAPE_2:%.*]] = s32[2,4] reshape(s32[2,1,4] [[BROADCAST_3]]) // CHECK-NEXT: [[BROADCAST_4:%.*]] = s32[2,3,4] broadcast(s32[2,4] [[RESHAPE_2]]), dimensions={0,2} - // CHECK-LABEL: ROOT + // CHECK: ROOT // CHECK-SAME: s32[2,3,4] add(s32[2,3,4] [[BROADCAST_4]], s32[2,3,4] [[ARG_2]]) %2 = "xla_hlo.add"(%arg0, %arg2) {broadcast_dimensions = dense<[1,2]> : tensor<2xi64>} : (tensor<1x4xi32>, tensor<2x3x4xi32>) -> tensor<2x3x4xi32> return %2 : tensor<2x3x4xi32> @@ -111,7 +124,19 @@ func @main(%arg0: tensor<1x4xi32>, %arg1: tensor<2x4xi32>, %arg2: tensor<2x3x4xi // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule +func @main(%arg0: tensor<2xi32>) -> tensor<2xf32> { + %0 = "xla_hlo.bitcast_convert"(%arg0) : (tensor<2xi32>) -> tensor<2xf32> + return %0 : tensor<2xf32> +} + +// CHECK: ENTRY +// CHECK: %[[ARG:.*]] = s32[2] parameter(0) +// CHECK: ROOT %[[RESULT:.*]] = f32[2] bitcast-convert(s32[2] %[[ARG]]) + +// ----- + +// CHECK: HloModule func @main(%arg0: tensor<4xi32>) -> tensor<1x2x3x4xi32> { // CHECK: [[ARG:%.*]] = s32[4] parameter(0) // CHECK-NEXT: ROOT %broadcast.2 = s32[1,2,3,4] broadcast(s32[4] [[ARG]]), dimensions={3} @@ -121,7 +146,7 @@ func @main(%arg0: tensor<4xi32>) -> tensor<1x2x3x4xi32> { // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<1xf32>) -> tensor<1x10xf32> { %result = "xla_hlo.broadcast_in_dim"(%arg0) { broadcast_dimensions = dense<0> : tensor<1xi64> @@ -129,13 +154,13 @@ func @main(%arg0: tensor<1xf32>) -> tensor<1x10xf32> { return %result : tensor<1x10xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[ARG:%.*]] = f32[1] parameter(0) // CHECK: ROOT %broadcast.2 = f32[1,10] broadcast(f32[1] [[ARG]]), dimensions={0} // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<4xi32>) -> tensor<4xi32> { %0 = call @callee(%arg0, %arg0) : (tensor<4xi32>, tensor<4xi32>) -> tensor<4xi32> %1 = call @callee(%0, %0) : (tensor<4xi32>, tensor<4xi32>) -> tensor<4xi32> @@ -150,24 +175,24 @@ func @callee(%arg0: tensor<4xi32>, %arg1: tensor<4xi32>) -> tensor<4xi32> { // CHECK: [[CALLEE_1:%.*]] ([[ARG_1:.*]]: s32[4], [[ARG_2:.*]]: s32[4]) -> s32[4] { // CHECK: %[[ARG_1]] = s32[4] parameter(0) // CHECK: %[[ARG_2]] = s32[4] parameter(1) -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: s32[4] add(s32[4] %[[ARG_1]], s32[4] %[[ARG_2]]) // CHECK: [[CALLEE_2:%.*]] ([[ARG_3:.*]]: s32[4], [[ARG_4:.*]]: s32[4]) -> s32[4] { // CHECK: %[[ARG_3]] = s32[4] parameter(0) // CHECK: %[[ARG_4]] = s32[4] parameter(1) -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: s32[4] add(s32[4] %[[ARG_3]], s32[4] %[[ARG_4]]) // CHECK: ENTRY [[MAIN:%.*]] ([[ARG:.*]]: s32[4]) -> s32[4] { // CHECK: %[[ARG]] = s32[4] parameter(0) // CHECK: [[CALL_OUT:%.*]] = s32[4] call(s32[4] %[[ARG]], s32[4] %[[ARG]]), to_apply=[[CALLEE_1]] -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: s32[4] call(s32[4] [[CALL_OUT]], s32[4] [[CALL_OUT]]), to_apply=[[CALLEE_2]] // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<4xi32>) -> (tensor<4xi32>, tensor<4xi32>) { %0:2 = call @callee(%arg0, %arg0) : (tensor<4xi32>, tensor<4xi32>) -> (tensor<4xi32>, tensor<4xi32>) return %0#0, %0#1 : tensor<4xi32>, tensor<4xi32> @@ -182,18 +207,18 @@ func @callee(%arg0: tensor<4xi32>, %arg1: tensor<4xi32>) -> (tensor<4xi32>, tens // Get name of callee computation // CHECK: [[CALLEE:%.*]] ({{.*}}) -> ({{.*}}) { -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK-SAME: [[MAIN:%.*]] ([[ARG:.*]]: s32[4]) -> (s32[4], s32[4]) { // CHECK: %[[ARG]] = s32[4] parameter(0) // CHECK: [[CALL_OUT:%.*]] = (s32[4], s32[4]) call(s32[4] %[[ARG]], s32[4] %[[ARG]]), to_apply=[[CALLEE]] // CHECK: [[OUT_0:%.*]] = s32[4] get-tuple-element((s32[4], s32[4]) [[CALL_OUT]]), index=0 // CHECK: [[OUT_1:%.*]] = s32[4] get-tuple-element((s32[4], s32[4]) [[CALL_OUT]]), index=1 -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: (s32[4], s32[4]) tuple(s32[4] [[OUT_0]], s32[4] [[OUT_1]]) // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0 : tensor<5x2xf32>, %arg1 : tensor<5x5xf32>, %arg2 : tensor<5x7xf32>) -> tensor<5x14xf32> { @@ -203,7 +228,7 @@ func @main(%arg0 : tensor<5x2xf32>, return %result : tensor<5x14xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[5,2] parameter(0) // CHECK: %[[ARG1:.*]] = f32[5,5] parameter(1) // CHECK: %[[ARG2:.*]] = f32[5,7] parameter(2) @@ -211,7 +236,7 @@ func @main(%arg0 : tensor<5x2xf32>, // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main() -> tensor<2x2x1x1xf32> { // CHECK: constant.{{.*}} = s64[] constant(1) %cst = constant dense<1> : tensor @@ -237,12 +262,15 @@ func @main() -> tensor<2x2x1x1xf32> { // CHECK: s32[2,2] constant({ { 3, 2 }, { 1, 4 } }) %cst_5 = constant dense<[[3, 2], [1, 4]]> : tensor<2x2xi32> + // CHECK: bf16[4] constant({1, 2, 3, 4}) + %cst_6 = constant dense<[1.000000e+00, 2.000000e+00, 3.000000e+00, 4.000000e+00]> : tensor<4xbf16> + return %cst_0 : tensor<2x2x1x1xf32> } // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0 : tensor<100x26x26x32xf32>, %arg1 : tensor<3x3x1x32xf32>) -> tensor<100x28x28x1xf32> { %result = "xla_hlo.conv"(%arg0, %arg1) { batch_group_count = 1 : i64, @@ -266,7 +294,7 @@ func @main(%arg0 : tensor<100x26x26x32xf32>, %arg1 : tensor<3x3x1x32xf32>) -> te return %result : tensor<100x28x28x1xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[100,26,26,32] parameter(0) // CHECK: %[[ARG1:.*]] = f32[3,3,1,32] parameter(1) // CHECK: ROOT %[[RESULT:.*]] = f32[100,28,28,1] convolution(f32[100,26,26,32] %[[ARG0]], f32[3,3,1,32] %[[ARG1]]), @@ -275,31 +303,31 @@ func @main(%arg0 : tensor<100x26x26x32xf32>, %arg1 : tensor<3x3x1x32xf32>) -> te // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<2xi32>) -> tensor<2xf32> { %0 = "xla_hlo.convert"(%arg0) : (tensor<2xi32>) -> tensor<2xf32> return %0 : tensor<2xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG:.*]] = s32[2] parameter(0) // CHECK: ROOT %[[RESULT:.*]] = f32[2] convert(s32[2] %[[ARG]]) // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<2xi32>) -> tensor<2xi32> { %0 = "xla_hlo.copy"(%arg0) : (tensor<2xi32>) -> tensor<2xi32> return %0 : tensor<2xi32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[ARG:%.*]] = s32[2] parameter(0) // CHECK: ROOT %[[RESULT:.*]] = s32[2] copy(s32[2] [[ARG]]) // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<10xf32>) -> tensor<10xf32> { %0 = xla_hlo.constant dense<[[0, 2, 4, 6], [1, 3, 5, 7]]> : tensor<2x4xi32> %1 = "xla_hlo.cross-replica-sum"(%arg0) {replica_groups = dense<[[0, 2, 4, 6], [1, 3, 5, 7]]> : tensor<2x4xi64>} : (tensor<10xf32>) -> tensor<10xf32> @@ -309,7 +337,7 @@ func @main(%arg0: tensor<10xf32>) -> tensor<10xf32> { // CHECK: %[[SUM_COMPUTATION:.*]] ([[ARG0:.*]]: f32[], [[ARG1:.*]]: f32[]) -> f32[] // CHECK: ROOT %[[RESULT:.*]] = f32[] add(f32[] %[[ARG0]], f32[] %[[ARG1]]) -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[10] parameter(0) // CHECK: ROOT %[[RESULT:.*]] = f32[10] all-reduce(f32[10] %[[ARG0]]) // CHECK-SAME: replica_groups={{[{][{]}}0,2,4,6},{1,3,5,7{{[}][}]}} @@ -317,7 +345,7 @@ func @main(%arg0: tensor<10xf32>) -> tensor<10xf32> { // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<3x4xi32>, %arg1: tensor<4x5xi32>) -> tensor<3x5xi32> { // Simple einsum is lowered to HLO dot op. // CHECK: dot(s32[3,4] %{{.*}}, s32[4,5] %{{.*}}), lhs_contracting_dims={1}, rhs_contracting_dims={0} @@ -327,7 +355,7 @@ func @main(%arg0: tensor<3x4xi32>, %arg1: tensor<4x5xi32>) -> tensor<3x5xi32> { // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<200x100x300xf32>, %arg1: tensor<10x2xi32>) -> tensor<10x300xf32> { // CHECK: [[ARG0:%.*]] = f32[200,100,300] parameter(0) // CHECK: [[ARG1:%.*]] = s32[10,2] parameter(1) @@ -344,31 +372,31 @@ func @main(%arg0: tensor<200x100x300xf32>, %arg1: tensor<10x2xi32>) -> tensor<10 // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg: tensor<4x2xf32>) -> tensor { %0 = "xla_hlo.get_dimension_size"(%arg) {dimension = 1 : i32} : (tensor<4x2xf32>) -> tensor return %0 : tensor } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[ARG:%.*]] = f32[4,2] parameter(0) // CHECK: s32[] get-dimension-size(f32[4,2] [[ARG]]), dimensions={1} // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tuple, tensor>) -> tensor { %0 = "xla_hlo.get_tuple_element"(%arg0) {index = 0 : i32} : (tuple, tensor>) -> tensor return %0 : tensor } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = (f32[], s32[]) parameter(0) // CHECK: ROOT %[[RESULT:.*]] = f32[] get-tuple-element((f32[], s32[]) %[[ARG0]]), index=0 // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main() -> tensor<1x10xf32> { %result = "xla_hlo.iota"() { iota_dimension = 1 : i64 @@ -376,26 +404,26 @@ func @main() -> tensor<1x10xf32> { return %result : tensor<1x10xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: ROOT %[[RESULT:.*]] = f32[1,10] iota(), iota_dimension=1 // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg: tensor<4x6xf32>, %pad: tensor) -> tensor<13x19xf32> { %0 = "xla_hlo.pad"(%arg, %pad) {edge_padding_high = dense<[4,5]> : tensor<2xi64>, edge_padding_low = dense<[2,3]> : tensor<2xi64>, interior_padding = dense<1> : tensor<2xi64>} : (tensor<4x6xf32>, tensor) -> tensor<13x19xf32> return %0 : tensor<13x19xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[ARG:%.*]] = f32[4,6] parameter(0) // CHECK: [[PADDING_VAL:%.*]] = f32[] parameter(1) -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: f32[13,19] pad(f32[4,6] [[ARG]], f32[] [[PADDING_VAL]]), padding=2_4_1x3_5_1 // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0 : tensor<1x10xf32>, %arg1 : tensor<1x10xi32>, %arg2 : tensor, %arg3 : tensor) -> (tensor<1xf32>, tensor<1xi32>) { %result0, %result1 = "xla_hlo.reduce"(%arg0, %arg1, %arg2, %arg3) ( { ^bb0(%fa: tensor, %ia : tensor, %fb: tensor, %ib: tensor): // no predecessors @@ -412,7 +440,7 @@ func @main(%arg0 : tensor<1x10xf32>, %arg1 : tensor<1x10xi32>, %arg2 : tensor (f32[1], s32[1]) // CHECK: %[[RESULT:.*]] = (f32[1], s32[1]) reduce(f32[1,10] %[[ARG0]], s32[1,10] %[[ARG1]], f32[] %[[ARG2]], s32[] %[[ARG3]]), dimensions={1}, to_apply=%[[REGION]] // CHECK: %[[RESULT0:.*]] = f32[1] get-tuple-element((f32[1], s32[1]) %[[RESULT]]), index=0 @@ -421,7 +449,7 @@ func @main(%arg0 : tensor<1x10xf32>, %arg1 : tensor<1x10xi32>, %arg2 : tensor) -> tensor<2x3x5x7xi32> { %0 = xla_hlo.constant dense<-2147483648> : tensor %1 = "xla_hlo.reduce_window"(%arg0, %0) ( { @@ -441,7 +469,7 @@ func @main(%arg0: tensor<2x17x31x7xi32>) -> tensor<2x3x5x7xi32> { // CHECK: %[[MAX_COMPUTATION:.*]] ([[ARG0:.*]]: s32[], [[ARG1:.*]]: s32[]) -> s32[] // CHECK: ROOT %[[RESULT:.*]] = s32[] maximum(s32[] %[[ARG0]], s32[] %[[ARG1]]) -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK-DAG: %[[ARG0:.*]] = s32[2,17,31,7] parameter(0) // CHECK-DAG: %[[INIT:.*]] = s32[] constant(-2147483648) // CHECK: ROOT %[[RESULT:.*]] = s32[2,5,8,7] reduce-window(s32[2,17,31,7] %[[ARG0]], s32[] %constant.2), @@ -450,19 +478,19 @@ func @main(%arg0: tensor<2x17x31x7xi32>) -> tensor<2x3x5x7xi32> { // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<2xf32>) -> tensor<1x2xf32> { %0 = "xla_hlo.reshape"(%arg0) : (tensor<2xf32>) -> tensor<1x2xf32> return %0 : tensor<1x2xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[2] parameter(0) // CHECK: ROOT %[[RESULT:.*]] = f32[1,2] reshape(f32[2] %[[ARG0]]) // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0 : tensor<10x11x12x13xf32>) -> tensor<10x11x12x13xf32> { %result = "xla_hlo.reverse"(%arg0) { dimensions = dense<[1,2]> : tensor<2xi64> @@ -470,13 +498,13 @@ func @main(%arg0 : tensor<10x11x12x13xf32>) -> tensor<10x11x12x13xf32> { return %result : tensor<10x11x12x13xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[10,11,12,13] parameter(0) // CHECK: ROOT %[[RESULT:.*]] = f32[10,11,12,13] reverse(f32[10,11,12,13] %[[ARG0]]), dimensions={1,2} // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main() -> tensor<2x3x5xf32> { %0 = xla_hlo.constant dense<0.000000e+00> : tensor %1 = xla_hlo.constant dense<1.000000e+00> : tensor @@ -485,14 +513,14 @@ func @main() -> tensor<2x3x5xf32> { return %3 : tensor<2x3x5xf32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK-DAG: %[[A:.*]] = f32[] constant(0) // CHECK-DAG: %[[B:.*]] = f32[] constant(1) // CHECK: ROOT %[[RESULT:.*]] = f32[2,3,5] rng(f32[] %[[A]], f32[] %[[B]]), distribution=rng_uniform // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%input_tensor: tensor<200x100x300xf32>, %scatter_indices: tensor<10x2xi32>, %updates: tensor<10x300xf32>) -> tensor<200x100x300xf32> { %0 = "xla_hlo.scatter" (%input_tensor, %scatter_indices, %updates) ({ ^bb0(%lhs: tensor, %rhs: tensor): // no predecessors @@ -512,16 +540,16 @@ func @main(%input_tensor: tensor<200x100x300xf32>, %scatter_indices: tensor<10x2 } // CHECK: [[COMPUTATION:%.*]] ({{.*}}: f32[], {{.*}}: f32[]) -> f32[] -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[VAL_1:%.*]] = f32[200,100,300] parameter(0) // CHECK: [[VAL_2:%.*]] = s32[10,2] parameter(1) // CHECK: [[VAL_3:%.*]] = f32[10,300] parameter(2) -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: f32[200,100,300] scatter(f32[200,100,300] [[VAL_1]], s32[10,2] [[VAL_2]], f32[10,300] [[VAL_3]]), update_window_dims={1}, inserted_window_dims={0,1}, scatter_dims_to_operand_dims={0,1}, index_vector_dim=1, indices_are_sorted=true, unique_indices=true, to_apply=[[COMPUTATION]] // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor, %arg1: tensor<2x3xi32>, %arg2: tensor<2x3xi32>) -> tensor<2x3xi32> { // CHECK: %[[ARG0:.*]] = pred[] parameter(0) // CHECK: %[[COND:.*]] = pred[2,3] broadcast(pred[] %[[ARG0]]), dimensions={} @@ -535,7 +563,7 @@ func @main(%arg0: tensor, %arg1: tensor<2x3xi32>, %arg2: tensor<2x3xi32>) -> // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<10x24x24x64xf32>, %arg1: tensor<10x12x12x64xf32>) -> tensor<10x24x24x64xf32> { %0 = xla_hlo.constant dense<0.000000e+00> : tensor %1 = "xla_hlo.select_and_scatter"(%arg0, %arg1, %0) ( { @@ -559,7 +587,7 @@ func @main(%arg0: tensor<10x24x24x64xf32>, %arg1: tensor<10x12x12x64xf32>) -> te // CHECK: %[[SCATTER_COMPUTATION:.*]] ([[ARG0:.*]]: f32[], [[ARG1:.*]]: f32[]) -> f32[] { // CHECK: ROOT %[[RESULT:.*]] = f32[] add(f32[] %[[ARG0]], f32[] %[[ARG1]]) -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[10,24,24,64] parameter(0) // CHECK: %[[ARG1:.*]] = f32[10,12,12,64] parameter(1) // CHECK: %[[INIT:.*]] = f32[] constant(0) @@ -571,20 +599,20 @@ func @main(%arg0: tensor<10x24x24x64xf32>, %arg1: tensor<10x12x12x64xf32>) -> te // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg: tensor<3x4xi32>) -> tensor<1x2xi32> { %0 = "xla_hlo.slice"(%arg) {start_indices = dense<[1, 0]> : tensor<2xi64>, limit_indices = dense<[2, 4]> : tensor<2xi64>, strides = dense<[1, 2]> : tensor<2xi64>} : (tensor<3x4xi32>) -> tensor<1x2xi32> return %0 : tensor<1x2xi32> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: [[ARG:%.*]] = s32[3,4] parameter(0) -// CHECK-LABEL: ROOT +// CHECK: ROOT // CHECK-SAME: s32[1,2] slice(s32[3,4] [[ARG]]), slice={[1:2:1], [0:4:2]} // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<1x2x3x4xi32>) -> tensor<2x1x4x3xi32> { // CHECK: [[ARG:%.*]] = s32[1,2,3,4] parameter(0) @@ -595,20 +623,20 @@ func @main(%arg0: tensor<1x2x3x4xi32>) -> tensor<2x1x4x3xi32> { // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor, %arg1 : tensor) -> tuple, tensor> { %result = "xla_hlo.tuple"(%arg0, %arg1) {} : (tensor, tensor) -> tuple, tensor> return %result : tuple, tensor> } -// CHECK-LABEL: ENTRY +// CHECK: ENTRY // CHECK: %[[ARG0:.*]] = f32[] parameter(0) // CHECK: %[[ARG1:.*]] = s32[] parameter(1) // CHECK: ROOT %[[RESULT:.*]] = (f32[], s32[]) tuple(f32[] %[[ARG0]], s32[] %[[ARG1]]) // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg_f32: tensor<4xf32>, %arg_i32: tensor<4xi32>) -> (tensor<4xf32>, tensor<4xf32>, tensor<4xi32>, tensor<4xi32>) { // CHECK: [[ARG_F32:%.*]] = f32[4] parameter(0) // CHECK: [[EXPM1:%.*]] = f32[4] exponential-minus-one(f32[4] [[ARG_F32]]) @@ -629,7 +657,7 @@ func @main(%arg_f32: tensor<4xf32>, %arg_i32: tensor<4xi32>) -> (tensor<4xf32>, // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%arg0: tensor<4xi1>, %arg1: tensor<4xi1>) -> tensor<4xi1> { // CHECK: [[VAL_1:%.*]] = pred[4] parameter(0) // CHECK: [[VAL_2:%.*]] = pred[4] parameter(1) @@ -640,7 +668,7 @@ func @main(%arg0: tensor<4xi1>, %arg1: tensor<4xi1>) -> tensor<4xi1> { // ----- -// CHECK-LABEL: HloModule +// CHECK: HloModule func @main(%input0: tensor<16x16xf32>, %input1: tensor<16x16xi32>) { %0 = "xla_hlo.sort"(%input0, %input1) ( { ^bb0(%arg0: tensor, %arg1: tensor, %arg2: tensor, %arg3: tensor): diff --git a/tensorflow/compiler/mlir/xla/tests/translate/import.hlotxt b/tensorflow/compiler/mlir/xla/tests/translate/import.hlotxt index a68e0237b14..5f9670be2f1 100644 --- a/tensorflow/compiler/mlir/xla/tests/translate/import.hlotxt +++ b/tensorflow/compiler/mlir/xla/tests/translate/import.hlotxt @@ -43,6 +43,15 @@ ENTRY %dummy_main (Arg_0.1: f32[]) -> f32[] { ROOT %add.5 = f32[4] add(f32[4] %add.3, f32[] %add.4) } +// CHECK-LABEL: func @test_after_all +// CHECK-SAME: ([[VAL_0:%.*]]: !xla_hlo.token, [[VAL_1:%.*]]: !xla_hlo.token) -> !xla_hlo.token +%test_after_all (token0: token[], token1: token[] ) -> token[] { + token0 = token[] parameter(0) + token1 = token[] parameter(1) + // CHECK-NEXT: "xla_hlo.after_all"([[VAL_0]], [[VAL_1]]) {name = "{{.*}}"} : (!xla_hlo.token, !xla_hlo.token) -> !xla_hlo.token + ROOT after-all = token[] after-all(token0, token1) +} + // CHECK-LABEL: func @test_and %test_and (Arg_0.1: pred[4], Arg_1.2: pred[4]) -> pred[4] { %Arg_0.1 = pred[4] parameter(0) diff --git a/tensorflow/compiler/mlir/xla/transforms/canonicalize.td b/tensorflow/compiler/mlir/xla/transforms/canonicalize.td index 37f6d7deaa3..d510a3df994 100644 --- a/tensorflow/compiler/mlir/xla/transforms/canonicalize.td +++ b/tensorflow/compiler/mlir/xla/transforms/canonicalize.td @@ -38,7 +38,7 @@ def DynamicSliceToSlice: Pat<(HLO_DynamicSliceOp HLO_Tensor:$input, (BuildSliceLimits $starting_indices, $slice_sizes), (BuildSliceStrides $input))>; -def UnaryToBianryEinsumEq : NativeCodeCall< +def UnaryToBinaryEinsumEq : NativeCodeCall< "$_builder.getStringAttr(\",\" + $0.getValue().str())">; // Convert UnaryEinsumOp to EinsumOp with two operands with redundant first @@ -46,4 +46,4 @@ def UnaryToBianryEinsumEq : NativeCodeCall< def UnaryEinsumToEinsum : Pat< (HLO_UnaryEinsumOp $operand, $equation), (HLO_EinsumOp (HLO_ConstOp (GetScalarOfType<1> $operand)), - $operand, (UnaryToBianryEinsumEq $equation))>; + $operand, (UnaryToBinaryEinsumEq $equation))>; diff --git a/tensorflow/compiler/mlir/xla/transforms/legalize_tf.cc b/tensorflow/compiler/mlir/xla/transforms/legalize_tf.cc index 02a9c7e69e0..520df1d92b9 100644 --- a/tensorflow/compiler/mlir/xla/transforms/legalize_tf.cc +++ b/tensorflow/compiler/mlir/xla/transforms/legalize_tf.cc @@ -100,6 +100,9 @@ static DenseIntElementsAttr GetI64ElementsAttr(ArrayAttr attr) { return DenseIntElementsAttr::get(ty, attr.getValue()); } +// Returns axis in HLO format from TF elements attr with exactly one element +// containing axis in the TensorFlow format. TensorFlow format supports negative +// indexing unlike HLO. static IntegerAttr GetHLOAxisFromTFAxis(ElementsAttr attr, int64_t rank, Builder *b) { SmallVector index(attr.getType().getRank(), 0); @@ -110,6 +113,15 @@ static IntegerAttr GetHLOAxisFromTFAxis(ElementsAttr attr, int64_t rank, return b->getI64IntegerAttr(axis); } +static IntegerAttr GetHLOAxisFromTFAxis(IntegerAttr attr, int64_t rank, + Builder *b) { + int64_t axis = attr.getInt(); + if (axis < 0) { + axis += rank; + } + return b->getI64IntegerAttr(axis); +} + // If `value` is an IntegerAttr, returns the integer value for the HLO axis // corresponding to the tensorflow axis. In particular, the tensorflow axis can // be negative, in which case, the corresponding HLO axis is @@ -149,7 +161,7 @@ tensorflow::TensorShape ToTensorShape(llvm::ArrayRef sizes) { llvm::SmallVector(sizes.begin(), sizes.end())); } -// Returns minimum value for the given int or float element type. +// Returns minimal value for the given int or float element type. static ConstOp GetMinValueForType(Type ty, Location loc, PatternRewriter *rewriter) { RankedTensorType scalar_ty = RankedTensorType::get({}, ty); @@ -167,6 +179,24 @@ static ConstOp GetMinValueForType(Type ty, Location loc, return rewriter->create(loc, attr); } +// Returns maximal value for the given int or float element type. +static ConstOp GetMaxValueForType(Type ty, Location loc, + PatternRewriter *rewriter) { + RankedTensorType scalar_ty = RankedTensorType::get({}, ty); + + DenseElementsAttr attr; + if (auto float_ty = ty.dyn_cast_or_null()) { + APFloat pos_inf = + APFloat::getInf(float_ty.getFloatSemantics(), /*negative=*/false); + attr = DenseElementsAttr::get(scalar_ty, pos_inf); + } else { + auto int_ty = ty.cast(); + APInt max_val = APInt::getSignedMaxValue(int_ty.getWidth()); + attr = DenseElementsAttr::get(scalar_ty, max_val); + } + return rewriter->create(loc, attr); +} + // Returns int or float scalar DenseElementsAttr attribute with the given // element type and the value. static ConstOp GetScalarConstOfType(Type ty, Location loc, int64_t raw_value, @@ -448,7 +478,7 @@ static DenseIntElementsAttr TFSliceSizes2HLOSliceSizes( // `element_types`, create two block arguments, one for lhs and one for rhs, and // generates xla_hlo.compare op to compare them with the given `direction`. // -// Note that this right now only does comparsion on the first pair of block +// Note that this right now only does comparision on the first pair of block // arguments. static void BuildSortComparisonBody(llvm::ArrayRef element_types, StringRef direction, Region *body, @@ -1165,41 +1195,27 @@ class ConvertStridedSliceOp : public OpRewritePattern { auto result_ty = op.getType().dyn_cast(); if (!result_ty || !result_ty.hasStaticShape()) return matchFailure(); - // TODO(hinsu): Support non-zero mask values. Currently only - // 'shrink_axis_mask' is supported. - for (StringRef mask : - {"begin_mask", "end_mask", "ellipsis_mask", "new_axis_mask"}) { - auto attr = op.getAttrOfType(mask); - if (attr && attr.getValue() != 0) return matchFailure(); - } - - // TODO(hinsu): Support lowering for ops with dynamic begin and end values - // when it is possible to derive indices based on mask attributes. - DenseIntElementsAttr begin_indices, end_indices, strides; - if (!matchPattern(op.begin(), m_Constant(&begin_indices)) || - !matchPattern(op.end(), m_Constant(&end_indices)) || - !matchPattern(op.strides(), m_Constant(&strides))) + SmallVector begin_indices, end_indices, strides; + if (!op.GetSlicedBoundRanges(input_shape, &begin_indices, &end_indices, + &strides)) return matchFailure(); SmallVector hlo_begin_indices, hlo_end_indices, hlo_strides, dims_to_reverse; int64_t input_rank = input_ty.getRank(); - for (auto *vec : {&hlo_begin_indices, &hlo_end_indices, &hlo_strides}) { - vec->reserve(input_rank); - } + hlo_begin_indices.reserve(input_rank); + hlo_end_indices.reserve(input_rank); + hlo_strides.reserve(input_rank); - int64_t indices_elements = begin_indices.getNumElements(); + int64_t indices_elements = begin_indices.size(); if (input_rank < indices_elements) return matchFailure(); // Convert from TensorFlow negative or out of range indices and strides // values to legal HLO Slice attributes. for (int i = 0, e = indices_elements; i != e; i++) { - int64_t begin = begin_indices.getValue(i).getInt(); - int64_t end = end_indices.getValue(i).getInt(); - int64_t stride = strides.getValue(i).getInt(); - - if (begin < 0) begin = input_shape[i] + begin; - if (end < 0) end = input_shape[i] + end; + int64_t begin = begin_indices[i]; + int64_t end = end_indices[i]; + int64_t stride = strides[i]; if (stride < 0) { // Negative stride means that the output values are computed starting @@ -1239,6 +1255,89 @@ class ConvertStridedSliceOp : public OpRewritePattern { } }; +// Converts tf.StridedSliceGrad to HLO reshape, reverse and padding ops. +// +// tf.StridedSlice is taking slice of the input tensor. tf.StridedSliceGrad does +// the reverse: it propagates the graident for the sliced tensor to the original +// input tensor by doing padding with zeros. The main logic is calculating the +// indices and strides for padding. +class ConvertStridedSliceGradOp + : public OpRewritePattern { + public: + using OpRewritePattern::OpRewritePattern; + + PatternMatchResult matchAndRewrite(TF::StridedSliceGradOp op, + PatternRewriter &rewriter) const override { + // We need constant input shape to perform padding calculations later. + DenseIntElementsAttr input_shape_attr; + if (!matchPattern(op.shape(), m_Constant(&input_shape_attr))) + return matchFailure(); + + // We also need constant begin/end indices and strides to perform padding + // calculations. + // Bounded shape after performing strided slice + SmallVector shape; + // Bounded begin, end, and strides for strided slice + SmallVector begin_indices, end_indices, strides; + if (!op.GetSlicedShapeAndBoundRanges(&shape, &begin_indices, &end_indices, + &strides)) + return matchFailure(); + + Value *grad = op.dy(); + Type element_type = grad->getType().cast().getElementType(); + + // Perform reshape to undo any new/shrink axies done by strided slice. + grad = rewriter.create( + op.getLoc(), RankedTensorType::get(shape, element_type), grad); + + SmallVector padding_low, padding_high, padding_interm; + SmallVector dims_to_reverse; + padding_low.reserve(shape.size()); + padding_high.reserve(shape.size()); + padding_interm.reserve(shape.size()); + + // Prepare padding parameters for each dimension. + for (int i = 0, e = shape.size(); i < e; ++i) { + int64_t input_dim = (*(input_shape_attr.begin() + i)).getSExtValue(); + if (strides[i] > 0) { + padding_low.push_back(begin_indices[i]); + padding_interm.push_back(strides[i] - 1); + + // Pad the upper dimension up to the expected input shape. It's not + // sufficient simply to use end_indices[i] to compute the padding in + // cases where the stride does not divide evenly into the interval + // between begin_indices[i] and end_indices[i]. + int64_t size = + padding_low[i] + shape[i] + (shape[i] - 1) * padding_interm[i]; + padding_high.push_back(input_dim - size); + } else { + dims_to_reverse.push_back(i); + padding_high.push_back(input_dim - begin_indices[i] - 1); + padding_interm.push_back(-strides[i] - 1); + + // Pad the lower dimension up to the expected input shape. + int64_t size = + padding_high[i] + shape[i] + (shape[i] - 1) * padding_interm[i]; + padding_low.push_back(input_dim - size); + } + } + + if (!dims_to_reverse.empty()) { + grad = rewriter.create( + op.getLoc(), grad->getType(), grad, + GetI64ElementsAttr(dims_to_reverse, &rewriter)); + } + + auto zero = GetScalarConstOfType(element_type, op.getLoc(), 0, &rewriter); + rewriter.replaceOpWithNewOp( + op, op.getType(), grad, zero, + GetI64ElementsAttr(padding_low, &rewriter), + GetI64ElementsAttr(padding_high, &rewriter), + GetI64ElementsAttr(padding_interm, &rewriter)); + return matchSuccess(); + } +}; + /// Converts the RangeOp tensorflow op to a xla_hlo.iota op with a scaling and /// offset applied to generate the range values. The output tensor needs to /// have a static shape. @@ -2149,7 +2248,7 @@ class ConvertTopKV2Op : public OpRewritePattern { // Converts tf.Unpack to a series of XLA HLO slice ops. // // Each slice takes one element along the dimension to unpack and takes the full -// range for all other dimenions. Each slice is then reshaped to drop the +// range for all other dimensions. Each slice is then reshaped to drop the // dimension to unpack (which is always of size 1). // TODO(antiagainst): consider changing this into a TF internal lowering pass. class ConvertUnpackOp : public OpRewritePattern { @@ -2193,6 +2292,136 @@ class ConvertUnpackOp : public OpRewritePattern { } }; +// Converts TF unsorted segment reduction ops to XLA HLO scatter op. +// +// TF unsorted segment reduction op peforms the following calculation: +// +// Assume segment ids' shape is [SI0, SI1, ..., SIm] and data's shape is +// [D0, D1, ..., Dn]. Note that segment ids' shape must be a prefix of data's +// shape, so we can have data's shape represented as [SI0, SI1, ..., SIm, +// Dm+1, ..., Dn]. Then +// output[segment_ids[SI_i0, SI_i1, ..., SI_im], D_im+1, ..., D_in] = +// over data[SI_i0, SI_i1, ..., SI_im, D_im+1, ..., D_in] +// where SI_iN is in the range of [0, SIN) and D_iN is in the range of [0, DN). +// +// The op will be translated to XLA HLO scatter with the following parameters: +// * Update window dims is [segment_id_rank, data_rank). +// * Inserted window dims is {0}. +// * Scatter dims to operand dims mapping is {0}. +// * Index vector dim is segment_id_rank. +template +class GenericConvertUnsortedSegmentReductionOp : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + PatternMatchResult matchAndRewrite(OpTy op, + PatternRewriter &rewriter) const override { + auto data_type = op.data()->getType().template dyn_cast(); + if (!data_type) return this->matchFailure(); + int64_t data_rank = data_type.getRank(); + + auto segment_ids_type = + op.segment_ids()->getType().template dyn_cast(); + if (!segment_ids_type) return this->matchFailure(); + int64_t segment_ids_rank = segment_ids_type.getRank(); + + DenseIntElementsAttr num_segments_attr; + if (!matchPattern(op.num_segments(), m_Constant(&num_segments_attr))) + return this->matchFailure(); + + // The final shape for TF unsorted segment reduction op is [num_segments] + + // data_shape[segment_ids_rank:]. + SmallVector output_shape; + output_shape.push_back((*num_segments_attr.begin()).getSExtValue()); + auto suffix = data_type.getShape().drop_front(segment_ids_rank); + output_shape.append(suffix.begin(), suffix.end()); + auto output_type = + RankedTensorType::get(output_shape, data_type.getElementType()); + + // Broadccast the initial value for reduction. This will become the + // 'operand' parameter to scatter to for the final scatter op. + Value *init = ConcreteClass::GetInitialValue(data_type.getElementType(), + op.getLoc(), rewriter); + auto broadcasted_init = rewriter.create( + op.getLoc(), output_type, init, + GetI64ElementsAttr(output_shape, &rewriter)); + + // Parameters for the generated scatter op. + auto range = llvm::seq(segment_ids_rank, data_rank); + SmallVector update_window_dims(range.begin(), range.end()); + SmallVector inserted_window_dims(1, 0); + SmallVector scatter_dims_to_operand_dims(1, 0); + int64_t index_vector_dim = segment_ids_rank; + + // Put all parameters in a StructAttr. + auto dims_attr = ScatterDimensionNumbers::get( + GetI64ElementsAttr(update_window_dims, &rewriter), + GetI64ElementsAttr(inserted_window_dims, &rewriter), + GetI64ElementsAttr(scatter_dims_to_operand_dims, &rewriter), + rewriter.getI64IntegerAttr(index_vector_dim), rewriter.getContext()); + + auto scatter = + rewriter.create(op.getLoc(), op.getType(), broadcasted_init, + op.segment_ids(), op.data(), dims_attr); + BuildReduceBody(data_type.getElementType(), + &scatter.update_computation(), &rewriter); + + rewriter.replaceOp(op, scatter.getResult()); + return this->matchSuccess(); + } +}; + +class ConvertUnsortedSegmentMaxOp + : public GenericConvertUnsortedSegmentReductionOp< + ConvertUnsortedSegmentMaxOp, TF::UnsortedSegmentMaxOp, MaxOp> { + public: + using GenericConvertUnsortedSegmentReductionOp:: + GenericConvertUnsortedSegmentReductionOp; + + static Value *GetInitialValue(Type reduce_element_type, Location loc, + PatternRewriter &rewriter) { + return GetMinValueForType(reduce_element_type, loc, &rewriter); + } +}; + +class ConvertUnsortedSegmentMinOp + : public GenericConvertUnsortedSegmentReductionOp< + ConvertUnsortedSegmentMinOp, TF::UnsortedSegmentMinOp, MinOp> { + public: + using GenericConvertUnsortedSegmentReductionOp:: + GenericConvertUnsortedSegmentReductionOp; + + static Value *GetInitialValue(Type reduce_element_type, Location loc, + PatternRewriter &rewriter) { + return GetMaxValueForType(reduce_element_type, loc, &rewriter); + } +}; + +class ConvertUnsortedSegmentProdOp + : public GenericConvertUnsortedSegmentReductionOp< + ConvertUnsortedSegmentProdOp, TF::UnsortedSegmentProdOp, MulOp> { + public: + using GenericConvertUnsortedSegmentReductionOp:: + GenericConvertUnsortedSegmentReductionOp; + + static Value *GetInitialValue(Type reduce_element_type, Location loc, + PatternRewriter &rewriter) { + return GetScalarConstOfType(reduce_element_type, loc, 1, &rewriter); + } +}; + +class ConvertUnsortedSegmentSumOp + : public GenericConvertUnsortedSegmentReductionOp< + ConvertUnsortedSegmentSumOp, TF::UnsortedSegmentSumOp, AddOp> { + public: + using GenericConvertUnsortedSegmentReductionOp:: + GenericConvertUnsortedSegmentReductionOp; + + static Value *GetInitialValue(Type reduce_element_type, Location loc, + PatternRewriter &rewriter) { + return GetScalarConstOfType(reduce_element_type, loc, 0, &rewriter); + } +}; + #include "tensorflow/compiler/mlir/xla/transforms/generated_legalize_tf.inc" LogicalResult legalizeTF(Operation *op, bool allow_partial_conversion) { @@ -2212,10 +2441,13 @@ LogicalResult legalizeTF(Operation *op, bool allow_partial_conversion) { ConvertMaxPoolOp, ConvertRangeOp, ConvertSigmoidOp, ConvertSoftmaxOp, ConvertSoftmaxOp, ConvertSplitOp, ConvertSplitVOp, - ConvertStridedSliceOp, ConvertTopKV2Op, ConvertUnpackOp, ConvertMeanOp, - ConvertSumOp, ConvertMaxOp, ConvertAllOp, ConvertAnyOp, ConvertTileOp, - ConvertMaxPoolGradOp, ConvertOneHotOp, ConvertConv2DBackpropInputOp, - ConvertConv2DBackpropFilterOp>(op->getContext()); + ConvertStridedSliceOp, ConvertStridedSliceGradOp, ConvertTopKV2Op, + ConvertUnpackOp, ConvertMeanOp, ConvertSumOp, ConvertMaxOp, ConvertAllOp, + ConvertAnyOp, ConvertTileOp, ConvertMaxPoolGradOp, ConvertOneHotOp, + ConvertConv2DBackpropInputOp, ConvertConv2DBackpropFilterOp, + ConvertUnsortedSegmentMaxOp, ConvertUnsortedSegmentMinOp, + ConvertUnsortedSegmentProdOp, ConvertUnsortedSegmentSumOp>( + op->getContext()); ConversionTarget target(*context); target.addLegalDialect(); diff --git a/tensorflow/compiler/mlir/xla/transforms/legalize_tf_control_flow.cc b/tensorflow/compiler/mlir/xla/transforms/legalize_tf_control_flow.cc index d2177041ba7..ac14bca6b2b 100644 --- a/tensorflow/compiler/mlir/xla/transforms/legalize_tf_control_flow.cc +++ b/tensorflow/compiler/mlir/xla/transforms/legalize_tf_control_flow.cc @@ -64,7 +64,7 @@ createLegalizeTFControlFlowPass() { namespace { -void Detuple(Value* tuple, llvm::iterator_range replace, +void Detuple(Value* tuple, Operation::result_range replace, OpBuilder* builder) { // De-tuple the results of the xla hlo conditional result. for (auto result_it : llvm::enumerate(replace)) { diff --git a/tensorflow/compiler/mlir/xla/transforms/legalize_tf_patterns.td b/tensorflow/compiler/mlir/xla/transforms/legalize_tf_patterns.td index 14075134f11..ad91cf07c3c 100644 --- a/tensorflow/compiler/mlir/xla/transforms/legalize_tf_patterns.td +++ b/tensorflow/compiler/mlir/xla/transforms/legalize_tf_patterns.td @@ -20,6 +20,11 @@ include "mlir/Dialect/StandardOps/Ops.td" include "tensorflow/compiler/mlir/tensorflow/ir/tf_ops.td" include "tensorflow/compiler/mlir/xla/ir/hlo_ops.td" +def SignedIntTensor : TensorOf<[I1, I8, I16, I32, I64]>; + +// IEEE compliant floating point tensors. +def IEEEFloatTensor : TensorOf<[F16, F32, F64]>; + //===----------------------------------------------------------------------===// // BatchNorm op patterns. //===----------------------------------------------------------------------===// @@ -32,6 +37,19 @@ def TrueBoolAttr : AttrConstraint>; def CastValueToI64: NativeCodeCall< "CastValueToI64($0->getLoc(), $1, &$_builder)">; +// Here, $0 is an ElementsAttr with exactly one element of type integer. $1 is +// the corresponding value of ranked tensor type whose axis is referred in $0. +def GetHLOAxisFromTFAxis : NativeCodeCall< + "GetHLOAxisFromTFAxis(" + "$0, $1->getType().cast().getRank(), &$_builder)">; + +// Same as the above but with $1 of type operand_range from variadic TensorFlow +// input. +def GetHLOAxisFromTFAxisVariadic : NativeCodeCall< + "GetHLOAxisFromTFAxis(" + "$0, (*$1.begin())->getType().cast().getRank(), " + "&$_builder)">; + def : Pattern< (TF_FusedBatchNormOp:$root $x, $scale, $offset, $mean, $variance, $epsilon, $data_format, FalseBoolAttr:$is_training), @@ -74,12 +92,13 @@ def AreBroadcastCompatible : Constraint, "types must be broadcastable">; class DirectBinaryPat - : Pat<(FromOp $l, $r), + : Pat<(FromOp AnyRankedTensor:$l, AnyRankedTensor:$r), (ToOp $l, $r, (BinBroadcastDimensions $l, $r))>; foreach fromToBinPair = [[TF_AddOp, HLO_AddOp], [TF_AddV2Op, HLO_AddOp], [TF_DivOp, HLO_DivOp], + [TF_LeftShiftOp, HLO_ShiftLeftOp], [TF_MaximumOp, HLO_MaxOp], [TF_MinimumOp, HLO_MinOp], [TF_MulOp, HLO_MulOp], @@ -88,18 +107,22 @@ foreach fromToBinPair = [[TF_AddOp, HLO_AddOp], [TF_SubOp, HLO_SubOp]] in def : DirectBinaryPat; +def LowerRightShiftSigned : + Pat<(TF_RightShiftOp AnyRankedTensor:$l, AnyRankedTensor:$r), + (HLO_ShiftRightArithmeticOp $l, $r, (BinBroadcastDimensions $l, $r)), + [(SignedIntTensor $r)]>; + +// TODO(hinsu): Lower unsigned types to HLO_ShiftRightLogical once the HLO op +// supports unsigned integers. + def : Pat<(TF_ComplexOp $r, $i), (HLO_ComplexOp $r, $i)>; -def IntegerTensor : TensorOf<[I1, I8, I16, I32, I64]>; - -// IEEE compliant floating point tensors. -def IEEEFloatTensor : TensorOf<[F16, F32, F64]>; - // Performs a substitution of FloorDiv, pseudo code below: // // return floor(div(x, y)) -def : Pat<(TF_FloorDivOp IEEEFloatTensor:$l, IEEEFloatTensor:$r), - (HLO_FloorOp (HLO_DivOp $l, $r, (BinBroadcastDimensions $l, $r)))>; +def : Pat<(TF_FloorDivOp AnyRankedTensor:$l, AnyRankedTensor:$r), + (HLO_FloorOp (HLO_DivOp $l, $r, (BinBroadcastDimensions $l, $r))), + [(IEEEFloatTensor $l)]>; // Performs a substitution of FloorDir for integer tensors, which required // additional correction for a negative numerator / denominator. Equivalent @@ -118,7 +141,9 @@ def : Pat<(TF_FloorDivOp IEEEFloatTensor:$l, IEEEFloatTensor:$r), // without returning the broadcast of 'r' to broadcast('l', 'r'). // // NOTE: This should be optimized for unsigned integers. -def : Pat<(TF_FloorDivOp IntegerTensor:$l, IntegerTensor:$r), +// Requires static shaped inputs to create constant splats and computation of +// broadcast attributes. +def : Pat<(TF_FloorDivOp AnyStaticShapeTensor:$l, AnyStaticShapeTensor:$r), (HLO_SelectOp (HLO_CompareOp (HLO_CompareOp $l, (HLO_ConstOp (ConstantSplat<"0"> $l)), @@ -133,14 +158,17 @@ def : Pat<(TF_FloorDivOp IntegerTensor:$l, IntegerTensor:$r), (HLO_ConstOp (ConstantSplat<"1"> $r)), (NullDenseIntElementsAttr)), (BinBroadcastDimensions $l, $r))), - (HLO_AbsOp:$abs $r), (BinBroadcastDimensions $neg, $abs)))>; + (HLO_AbsOp:$abs $r), (BinBroadcastDimensions $neg, $abs))), + [(SignedIntTensor $l)]>; // Performs a substitution of FloorMod designed to correct for possibly negative // values. Pseudocode shown below: // // T trunc_mod = std::fmod(x, y); // return trunc_mod != 0 && (y < 0 != trunc_mod < 0) ? trunc_mod + y -def : Pat<(TF_FloorModOp $l, $r), +// Requires static shaped inputs to create constant splats and computation of +// broadcast attributes. +def : Pat<(TF_FloorModOp AnyStaticShapeTensor:$l, AnyStaticShapeTensor:$r), (HLO_SelectOp (HLO_AndOp (HLO_CompareOp @@ -173,8 +201,9 @@ def : Pat<(TF_BroadcastToOp:$result AnyRankedTensor:$input, $shape), //===----------------------------------------------------------------------===// class DirectLogicalBinaryPat - : Pat<(FromOp IntegerTensor:$l, IntegerTensor:$r), - (ToOp $l, $r, (BinBroadcastDimensions $l, $r))>; + : Pat<(FromOp AnyRankedTensor:$l, AnyRankedTensor:$r), + (ToOp $l, $r, (BinBroadcastDimensions $l, $r)), + [(SignedIntTensor $l)]>; foreach fromToBinPair = [[TF_LogicalAndOp, HLO_AndOp], [TF_LogicalOrOp, HLO_OrOp], @@ -186,7 +215,7 @@ foreach fromToBinPair = [[TF_LogicalAndOp, HLO_AndOp], //===----------------------------------------------------------------------===// class DirectComparePat - : Pat<(FromOp $l, $r), + : Pat<(FromOp AnyRankedTensor:$l, AnyRankedTensor:$r), (HLO_CompareOp $l, $r, (BinBroadcastDimensions $l, $r), direction)>; def : DirectComparePat; @@ -195,7 +224,7 @@ def : DirectComparePat; def : DirectComparePat; class EqualityPat - : Pat<(FromOp $l, $r, + : Pat<(FromOp AnyRankedTensor:$l, AnyRankedTensor:$r, TrueBoolAttr:$incompatible_shape_error), (HLO_CompareOp $l, $r, (BinBroadcastDimensions $l, $r), direction), [(AreBroadcastCompatible $l, $r)]>; @@ -221,11 +250,6 @@ def OneElementAttr : ElementsAttrBase, "Scalar ElementsAttr">; -def GetHLOAxisFromTFAxis : NativeCodeCall< - "GetHLOAxisFromTFAxis(" - "$0, (*$1.begin())->getType().cast().getRank(), " - "&$_builder)">; - def HasRankedFirstOperand : ConstraintgetType().isa()">>; @@ -241,7 +265,8 @@ def IsShapedTensor // if HLO constant op is introduced as an replacement for the TensorFlow // Constant op. def : Pat<(TF_ConcatV2Op $inputs, (TF_ConstOp OneElementAttr:$axis)), - (HLO_ConcatenateOp $inputs, (GetHLOAxisFromTFAxis $axis, $inputs)), + (HLO_ConcatenateOp $inputs, + (GetHLOAxisFromTFAxisVariadic $axis, $inputs)), [(HasRankedFirstOperand $inputs)]>; //===----------------------------------------------------------------------===// @@ -264,6 +289,20 @@ def : Pat<(TF_RFFTOp $input, (TF_ConstOp I32ElementsAttr:$fft_length)), (HLO_FftOp $input, HLO_FFT_TYPE_RFFT, (CastElementsToI64Elements $fft_length))>; +//===----------------------------------------------------------------------===// +// GatherV2 op patterns. +//===----------------------------------------------------------------------===// + +// Here, $params and $indices needs to be ranked so that $axis and $batch_dims +// attributes can be converted from TensorFlow axis format supporting negative +// indexing to the HLO format. +def LegalizeGatherV2 : + Pat<(TF_GatherV2Op AnyRankedTensor:$params, AnyRankedTensor:$indices, + (TF_ConstOp $axis), $batch_dims), + (HLO_TorchIndexSelectOp $params, $indices, + (GetHLOAxisFromTFAxis $axis, $params), + (GetHLOAxisFromTFAxis $batch_dims, $indices))>; + //===----------------------------------------------------------------------===// // Pad op patterns. //===----------------------------------------------------------------------===// @@ -389,6 +428,7 @@ foreach Mapping = [ [TF_ImagOp, HLO_ImagOp], [TF_IsFiniteOp, HLO_IsFiniteOp], [TF_LogOp, HLO_LogOp], + [TF_LogicalNotOp, HLO_NotOp], [TF_NegOp, HLO_NegOp], [TF_RealOp, HLO_RealOp], [TF_RsqrtOp, HLO_RsqrtOp], diff --git a/tensorflow/compiler/mlir/xla/transforms/lhlo_fuse_linalg.cc b/tensorflow/compiler/mlir/xla/transforms/lhlo_fuse_linalg.cc index 928bfc20cdb..a8a2eb77586 100644 --- a/tensorflow/compiler/mlir/xla/transforms/lhlo_fuse_linalg.cc +++ b/tensorflow/compiler/mlir/xla/transforms/lhlo_fuse_linalg.cc @@ -66,12 +66,17 @@ struct LhloFuseLinalg : public FunctionPass { llvm::SmallDenseSet erase_set; SmallVector linalg_ops; func.walk([&](LinalgOp op) { linalg_ops.push_back(op); }); - linalg::Aliases aliases; - linalg::LinalgDependenceGraph graph(aliases, linalg_ops); for (auto* op : llvm::reverse(linalg_ops)) { for (unsigned id = 0, e = LinalgOp(op).getNumInputs(); id < e; ++id) { + linalg::Aliases aliases; + linalg::LinalgDependenceGraph graph(aliases, linalg_ops); if (auto info = fuseProducerOf(b, op, id, graph, &folder)) { - erase_set.insert(info->originalProducer.getOperation()); + auto originalOp = info->originalProducer.getOperation(); + erase_set.insert(originalOp); + auto originalOpInLinalgOpsVector = std::find_if( + linalg_ops.begin(), linalg_ops.end(), + [&](const Operation* op) { return op == originalOp; }); + *originalOpInLinalgOpsVector = info->fusedProducer.getOperation(); } } } diff --git a/tensorflow/compiler/mlir/xla/transforms/lhlo_legalize_to_gpu.cc b/tensorflow/compiler/mlir/xla/transforms/lhlo_legalize_to_gpu.cc new file mode 100644 index 00000000000..9f1f90cb2f0 --- /dev/null +++ b/tensorflow/compiler/mlir/xla/transforms/lhlo_legalize_to_gpu.cc @@ -0,0 +1,204 @@ +/* Copyright 2019 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. +==============================================================================*/ + +// This file implements logic for lowering LHLO dialect to GPU dialect. + +#include + +#include "absl/memory/memory.h" +#include "llvm/ADT/ArrayRef.h" +#include "mlir/Dialect/GPU/GPUDialect.h" // TF:local_config_mlir +#include "mlir/Dialect/Linalg/IR/LinalgOps.h" // TF:local_config_mlir +#include "mlir/Dialect/LoopOps/LoopOps.h" // TF:local_config_mlir +#include "mlir/Dialect/StandardOps/Ops.h" // TF:local_config_mlir +#include "mlir/IR/Attributes.h" // TF:local_config_mlir +#include "mlir/IR/BlockAndValueMapping.h" // TF:local_config_mlir +#include "mlir/IR/Builders.h" // TF:local_config_mlir +#include "mlir/IR/Function.h" // TF:local_config_mlir +#include "mlir/IR/Location.h" // TF:local_config_mlir +#include "mlir/IR/MLIRContext.h" // TF:local_config_mlir +#include "mlir/IR/Operation.h" // TF:local_config_mlir +#include "mlir/IR/PatternMatch.h" // TF:local_config_mlir +#include "mlir/IR/StandardTypes.h" // TF:local_config_mlir +#include "mlir/Pass/Pass.h" // TF:local_config_mlir +#include "mlir/Transforms/DialectConversion.h" // TF:local_config_mlir +#include "tensorflow/compiler/mlir/xla/ir/lhlo_ops.h" +#include "tensorflow/compiler/mlir/xla/transforms/map_lhlo_to_scalar_op.h" + +namespace mlir { +namespace xla_lhlo { +namespace { + +// A simple translation of LHLO reduce operations to a corresponding gpu +// launch operation. The transformation does no tiling and also only supports +// 1d results. +class LhloReduceToGPULaunchConverter : public OpConversionPattern { + public: + using OpConversionPattern::OpConversionPattern; + + PatternMatchResult matchAndRewrite( + ReduceOp reduce_op, ArrayRef args, + ConversionPatternRewriter& rewriter) const final { + auto loc = reduce_op.getLoc(); + // Only support 1d reductions for now. + int64_t size = 0; + for (auto result : reduce_op.out()) { + auto shaped_type = result->getType().dyn_cast(); + if (!shaped_type || shaped_type.getRank() != 1) { + return matchFailure(); + } + auto dim_size = shaped_type.getDimSize(0); + if (size && size != dim_size) { + return matchFailure(); + } + size = dim_size; + } + + auto reducing_dimension = *reduce_op.dimensions().int_value_begin(); + + // Require all inputs to have the same shape. + int64_t reduce_dim_size = 0; + for (auto input : reduce_op.operands()) { + auto shaped_type = input->getType().dyn_cast(); + if (!shaped_type || !shaped_type.hasStaticShape()) { + return matchFailure(); + } + reduce_dim_size = + shaped_type.getDimSize(reducing_dimension.getSExtValue()); + } + + // Create a launch that is parallel in the result dimension. + auto block_size_x = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), size)); + auto one = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), 1)); + auto launch_op = rewriter.create( + loc, one, one, one, block_size_x, one, one, reduce_op.getOperands()); + // Map reduce operands and correspondign launch values. + BlockAndValueMapping mapping; + for (auto pair : + llvm::zip(reduce_op.getOperands(), launch_op.getKernelArguments())) { + mapping.map(std::get<0>(pair), std::get<1>(pair)); + } + { + OpBuilder::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToEnd(&launch_op.body().front()); + auto index = launch_op.getThreadIds().x; + + // Load the initial value and store it to the output. + for (auto pair : llvm::zip(reduce_op.init_values(), reduce_op.out())) { + auto init_value = rewriter.create( + loc, mapping.lookup(std::get<0>(pair))); + rewriter.create(loc, init_value, + mapping.lookup(std::get<1>(pair)), + ArrayRef{index}); + } + + // Insert a loop into the body to compute the reduction. The loop ranges + // from [0.dim). + auto zero = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), 0)); + // TODO(b/137624192) Use dimOp to make it shape independent. + auto upper = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), reduce_dim_size)); + auto step = rewriter.create( + loc, rewriter.getIndexType(), + rewriter.getIntegerAttr(rewriter.getIndexType(), 1)); + auto loop = rewriter.create(loc, zero, upper, step); + + rewriter.setInsertionPointToStart(loop.getBody()); + // Compute memrefs for the value to reduce. This makes it easier to just + // inline the body. + auto output = mapping.lookup(*reduce_op.out().begin()); + // TODO(herhut) Move this to the SliceOp builder. + auto resType = MemRefType::get( + llvm::None, output->getType().cast().getElementType(), + makeStridedLinearLayoutMap(llvm::None, + MemRefType::getDynamicStrideOrOffset(), + rewriter.getContext())); + auto accumulator = rewriter.create( + loc, resType, output, ArrayRef{launch_op.getThreadIds().x}); + llvm::SmallVector indexings; + auto input_buffer = *reduce_op.operands().begin(); + auto input_type = input_buffer->getType().cast(); + for (int64_t dim = 0; dim < input_type.getRank(); ++dim) { + indexings.push_back(dim == reducing_dimension + ? loop.getInductionVar() + : launch_op.getThreadIds().x); + } + // TODO(herhut) Move this to the SliceOp builder. + auto input = mapping.lookup(*reduce_op.operand_begin()); + auto rhs = rewriter.create( + loc, + MemRefType::get( + llvm::None, input_type.getElementType(), + makeStridedLinearLayoutMap(llvm::None, + MemRefType::getDynamicStrideOrOffset(), + rewriter.getContext())), + input, indexings); + + // Now copy over the actual body of the reduction, leaving out the + // terminator. + mapping.map(reduce_op.body().front().getArgument(0), accumulator); + mapping.map(reduce_op.body().front().getArgument(1), rhs); + mapping.map(reduce_op.body().front().getArgument(2), accumulator); + for (auto& nested : reduce_op.body().front().without_terminator()) { + auto clone = rewriter.clone(nested, mapping); + for (auto pair : llvm::zip(nested.getResults(), clone->getResults())) { + mapping.map(std::get<0>(pair), std::get<1>(pair)); + } + } + + // Finally, insert the terminator for the launchOp. + rewriter.setInsertionPointToEnd(&launch_op.body().front()); + rewriter.create(loc); + } + + rewriter.eraseOp(reduce_op); + return matchSuccess(); + }; +}; + +struct LhloLegalizeToGpu : public FunctionPass { + void runOnFunction() override { + OwningRewritePatternList patterns; + ConversionTarget target(getContext()); + target.addLegalDialect(); + target.addIllegalOp(); + auto func = getFunction(); + patterns.insert(func.getContext()); + if (failed(applyPartialConversion(func, target, patterns, nullptr))) { + signalPassFailure(); + } + } +}; + +} // namespace + +std::unique_ptr> createLegalizeToGpuPass() { + return absl::make_unique(); +} + +static PassRegistration legalize_pass( + "lhlo-legalize-to-gpu", "Legalize from LHLO dialect to GPU dialect"); + +} // namespace xla_lhlo +} // namespace mlir diff --git a/tensorflow/compiler/mlir/xla/transforms/lhlo_legalize_to_linalg.cc b/tensorflow/compiler/mlir/xla/transforms/lhlo_legalize_to_linalg.cc index c4787d9bfd9..af7383c5101 100644 --- a/tensorflow/compiler/mlir/xla/transforms/lhlo_legalize_to_linalg.cc +++ b/tensorflow/compiler/mlir/xla/transforms/lhlo_legalize_to_linalg.cc @@ -87,15 +87,12 @@ class PointwiseToLinalgConverter : public OpConversionPattern { result_or_body_arg.emplace_back(memrefType.getElementType()); } - // Define the number of input memref/output memrefs. - SmallVector nmemrefs{ - rewriter.getI64IntegerAttr(bodyArgTypes.size()), - rewriter.getI64IntegerAttr(bodyResultTypes.size())}; - auto linalgOp = rewriter.create( - loc, args, rewriter.getArrayAttr(indexingMaps), + loc, args, + rewriter.getI64IntegerAttr(bodyArgTypes.size()), // args_in + rewriter.getI64IntegerAttr(bodyResultTypes.size()), // args_out + rewriter.getArrayAttr(indexingMaps), GetNParallelLoopsAttrs(nloops, rewriter), - rewriter.getArrayAttr(nmemrefs), /*doc=*/nullptr, /*fun=*/nullptr, /*library_call=*/nullptr); // Add a block to the region. @@ -118,6 +115,34 @@ class PointwiseToLinalgConverter : public OpConversionPattern { } }; +template +class ScalarPointwiseToStandardConverter : public OpConversionPattern { + public: + using OpConversionPattern::OpConversionPattern; + + PatternMatchResult matchAndRewrite( + LhloOp lhlo_op, ArrayRef args, + ConversionPatternRewriter& rewriter) const final { + auto loc = lhlo_op.getLoc(); + auto argType = + lhlo_op.getOperand(0)->getType().template dyn_cast(); + if (!argType || !argType.getElementType().isIntOrFloat() || + (argType.getRank() != 0)) { + return ConversionPattern::matchFailure(); + } + + // Create two loads from the input. + auto lhs = rewriter.create(loc, lhlo_op.lhs()); + auto rhs = rewriter.create(loc, lhlo_op.rhs()); + Operation* op = MapLhloOpToStdScalarOp( + llvm::cast(lhlo_op), argType.getElementType(), + llvm::ArrayRef{lhs, rhs}, rewriter); + rewriter.create(loc, op->getResult(0), lhlo_op.out()); + rewriter.eraseOp(lhlo_op); + return ConversionPattern::matchSuccess(); + } +}; + class BroadcastInDimConverter : public OpConversionPattern { public: using OpConversionPattern::OpConversionPattern; @@ -125,14 +150,54 @@ class BroadcastInDimConverter : public OpConversionPattern { PatternMatchResult matchAndRewrite( BroadcastInDimOp broadcastOp, ArrayRef args, ConversionPatternRewriter& rewriter) const final { - unsigned operandIndex = 0; - unsigned resultIndex = 1; auto operandMemrefType = - broadcastOp.getOperand(operandIndex)->getType().dyn_cast(); + broadcastOp.operand()->getType().dyn_cast(); auto resultMemrefType = - broadcastOp.getOperand(resultIndex)->getType().dyn_cast(); + broadcastOp.output()->getType().dyn_cast(); if (!operandMemrefType || !resultMemrefType) return matchFailure(); + auto broadcastDims = broadcastOp.broadcast_dimensions(); + if (!broadcastDims.hasValue()) return matchFailure(); + return broadcastDims.getValue().getIntValues().empty() + ? emitScalarBroadcast(broadcastOp, args, resultMemrefType, + &rewriter) + : emitNonScalarBroadcast(broadcastOp, args, operandMemrefType, + resultMemrefType, &rewriter); + } + + private: + PatternMatchResult emitScalarBroadcast( + BroadcastInDimOp broadcastOp, ArrayRef args, + MemRefType resultMemrefType, ConversionPatternRewriter* rewriter) const { + unsigned nloops = resultMemrefType.getRank(); + SmallVector indexingMaps{ + AffineMapAttr::get(rewriter->getMultiDimIdentityMap(nloops))}; + auto loc = broadcastOp.getLoc(); + auto linalgOp = rewriter->create( + loc, broadcastOp.output(), + rewriter->getI64IntegerAttr(0), // args_in + rewriter->getI64IntegerAttr(1), // args_out + rewriter->getArrayAttr(indexingMaps), + GetNParallelLoopsAttrs(nloops, *rewriter), + /*doc=*/nullptr, /*fun=*/nullptr, /*library_call=*/nullptr); + + // Add a block to the region. + auto* region = &linalgOp.region(); + auto* block = rewriter->createBlock(region, region->end()); + block->addArguments(resultMemrefType.getElementType()); + + rewriter->setInsertionPointToEnd(block); + auto scalar = + rewriter->create(loc, broadcastOp.operand(), llvm::None); + rewriter->create(loc, scalar.getResult()); + rewriter->eraseOp(broadcastOp); + return matchSuccess(); + } + + PatternMatchResult emitNonScalarBroadcast( + BroadcastInDimOp broadcastOp, ArrayRef args, + MemRefType operandMemrefType, MemRefType resultMemrefType, + ConversionPatternRewriter* rewriter) const { SmallVector bodyArgTypes{operandMemrefType.getElementType()}; unsigned nloops = resultMemrefType.getRank(); @@ -143,9 +208,6 @@ class BroadcastInDimConverter : public OpConversionPattern { auto operandShape = operandMemrefType.getShape(); int index = 0; - if (!broadcastOp.broadcast_dimensions().hasValue()) { - return matchFailure(); - } for (const auto& broadcastSize : broadcastOp.broadcast_dimensions().getValue().getIntValues()) { int size = broadcastSize.getSExtValue(); @@ -157,33 +219,28 @@ class BroadcastInDimConverter : public OpConversionPattern { } // Construct the indexing maps needed for linalg.generic ops. - SmallVector indexingMaps; - indexingMaps.emplace_back(AffineMapAttr::get( - AffineMap::get(nloops, /*symbolCount=*/0, dimExprs))); - indexingMaps.emplace_back( - AffineMapAttr::get(rewriter.getMultiDimIdentityMap(nloops))); - - // Define the number of input memref/output memrefs. - SmallVector nmemrefs{ - rewriter.getI64IntegerAttr(bodyArgTypes.size()), - rewriter.getI64IntegerAttr(1)}; + SmallVector indexingMaps{ + AffineMapAttr::get(AffineMap::get(nloops, /*symbolCount=*/0, dimExprs)), + AffineMapAttr::get(rewriter->getMultiDimIdentityMap(nloops))}; auto loc = broadcastOp.getLoc(); - auto linalgOp = rewriter.create( - loc, args, rewriter.getArrayAttr(indexingMaps), - GetNParallelLoopsAttrs(nloops, rewriter), - rewriter.getArrayAttr(nmemrefs), + auto linalgOp = rewriter->create( + loc, args, + rewriter->getI64IntegerAttr(bodyArgTypes.size()), // args_in + rewriter->getI64IntegerAttr(1), // args_out + rewriter->getArrayAttr(indexingMaps), + GetNParallelLoopsAttrs(nloops, *rewriter), /*doc=*/nullptr, /*fun=*/nullptr, /*library_call=*/nullptr); // Add a block to the region. auto* region = &linalgOp.region(); - auto* block = rewriter.createBlock(region, region->end()); + auto* block = rewriter->createBlock(region, region->end()); block->addArguments(bodyArgTypes); block->addArguments(resultMemrefType.getElementType()); - rewriter.setInsertionPointToEnd(block); - rewriter.create(loc, block->getArgument(operandIndex)); - rewriter.eraseOp(broadcastOp); + rewriter->setInsertionPointToEnd(block); + rewriter->create(loc, block->getArgument(0)); + rewriter->eraseOp(broadcastOp); return matchSuccess(); } }; @@ -208,15 +265,13 @@ class IotaConverter : public OpConversionPattern { indexingMaps.emplace_back( AffineMapAttr::get(rewriter.getMultiDimIdentityMap(nloops))); - // Define the number of input memref/output memrefs. - SmallVector nmemrefs{rewriter.getI64IntegerAttr(0), - rewriter.getI64IntegerAttr(1)}; - auto loc = iotaOp.getLoc(); auto linalgOp = rewriter.create( - loc, args, rewriter.getArrayAttr(indexingMaps), + loc, args, + rewriter.getI64IntegerAttr(0), // args_in + rewriter.getI64IntegerAttr(1), // args_out + rewriter.getArrayAttr(indexingMaps), GetNParallelLoopsAttrs(nloops, rewriter), - rewriter.getArrayAttr(nmemrefs), /*doc=*/nullptr, /*fun=*/nullptr, /*library_call=*/nullptr); // Add a block to the region. @@ -241,10 +296,29 @@ class IotaConverter : public OpConversionPattern { } }; +class ConstConverter : public OpConversionPattern { + public: + using OpConversionPattern::OpConversionPattern; + + PatternMatchResult matchAndRewrite( + ConstOp constOp, ArrayRef args, + ConversionPatternRewriter& rewriter) const final { + auto loc = constOp.getLoc(); + auto valueAttr = constOp.value().cast(); + if (valueAttr.getType().getRank() != 0) return matchFailure(); + auto stdConstOp = + rewriter.create(loc, valueAttr.getValue({})); + rewriter.create(loc, stdConstOp, constOp.getOperand()); + rewriter.eraseOp(constOp); + return matchSuccess(); + } +}; + void populateLHLOToLinalgConversionPattern(MLIRContext* context, OwningRewritePatternList* patterns) { // clang-format off patterns->insert, PointwiseToLinalgConverter, @@ -255,7 +329,9 @@ void populateLHLOToLinalgConversionPattern(MLIRContext* context, PointwiseToLinalgConverter, PointwiseToLinalgConverter, PointwiseToLinalgConverter, - PointwiseToLinalgConverter>(context); + PointwiseToLinalgConverter, + ScalarPointwiseToStandardConverter + >(context); // clang-format on } @@ -273,9 +349,10 @@ void populateLHLOToLinalgConversionPattern(MLIRContext* context, // %0 = addf %arg4, %arg5 : f32 // "linalg.yield"(%0) : (f32) -> () // }) { +// args_in = 2, +// args_out = 1, // indexing_maps = [#map0, #map0, #map0], // iterator_types = ["parallel", "parallel"], -// n_views = [2, 1] // } : (memref<2x2xf32>, memref<2x2xf32>, memref<2x2xf32>) -> () // } struct LhloLegalizeToLinalg : public FunctionPass { diff --git a/tensorflow/compiler/mlir/xla/transforms/lower_complex_patterns.td b/tensorflow/compiler/mlir/xla/transforms/lower_complex_patterns.td index 252a10fc412..d8a5ae6c6de 100644 --- a/tensorflow/compiler/mlir/xla/transforms/lower_complex_patterns.td +++ b/tensorflow/compiler/mlir/xla/transforms/lower_complex_patterns.td @@ -107,8 +107,8 @@ def : Pat<(HLO_AbsOp HLO_ComplexTensor:$val), (NullDenseIntElementsAttr))), (HLO_ConstOp (ConstantSplat<"0"> $real)))>; -// Expononetial can be lowered to an exponential on the real component and a -// sum of sinusoids of the imageinary component, which equates to a normal +// Exponential can be lowered to an exponential on the real component and a +// sum of sinusoids of the imaginary component, which equates to a normal // exponential operator multiplied by Euler's formula. // // Exp(a + ib) = Exp(a) * Exp(ib) = Exp(a) * (Cos(b) + iSin(b)) diff --git a/tensorflow/compiler/mlir/xla/transforms/map_lhlo_to_scalar_op.h b/tensorflow/compiler/mlir/xla/transforms/map_lhlo_to_scalar_op.h index 4107548a26b..11e3af7649b 100644 --- a/tensorflow/compiler/mlir/xla/transforms/map_lhlo_to_scalar_op.h +++ b/tensorflow/compiler/mlir/xla/transforms/map_lhlo_to_scalar_op.h @@ -157,7 +157,7 @@ inline Operation* MapLhloOpToStdScalarOp( if (element_type.isa()) { Optional predicate = getIntCmpPredicate(lhlo_op.comparison_direction()); - assert(predicate.hasValue() && "expected valid comparision direction"); + assert(predicate.hasValue() && "expected valid comparison direction"); return b.create>(lhlo_op.getLoc(), predicate.getValue(), lhs, rhs); } diff --git a/tensorflow/compiler/mlir/xla/transforms/passes.h b/tensorflow/compiler/mlir/xla/transforms/passes.h index 6dd3abe5a92..d659a3a87f4 100644 --- a/tensorflow/compiler/mlir/xla/transforms/passes.h +++ b/tensorflow/compiler/mlir/xla/transforms/passes.h @@ -65,6 +65,9 @@ std::unique_ptr> createLegalizeToAffinePass(); // Lowers from LHLO dialect to Linalg dialect. std::unique_ptr> createLegalizeToLinalgPass(); +// Lowers from LHLO dialect to GPU dialect. +std::unique_ptr> createLegalizeToGpuPass(); + // Fuses linalg ops obtained after LHLO lowering. std::unique_ptr> createLhloFuseLinalg(); diff --git a/tensorflow/compiler/mlir/xla/type_to_shape.cc b/tensorflow/compiler/mlir/xla/type_to_shape.cc index b4449f4d426..37c657c99ae 100644 --- a/tensorflow/compiler/mlir/xla/type_to_shape.cc +++ b/tensorflow/compiler/mlir/xla/type_to_shape.cc @@ -24,6 +24,7 @@ limitations under the License. #include "mlir/Support/DebugStringHelper.h" // TF:local_config_mlir #include "tensorflow/compiler/mlir/tensorflow/utils/convert_tensor.h" #include "tensorflow/compiler/mlir/tensorflow/utils/convert_type.h" +#include "tensorflow/compiler/mlir/xla/ir/hlo_ops.h" #include "tensorflow/compiler/xla/shape_util.h" #include "tensorflow/compiler/xla/statusor.h" #include "tensorflow/core/framework/tensor_shape.h" @@ -165,6 +166,8 @@ Shape TypeToShape(mlir::Type type) { } return ShapeUtil::MakeTupleShape(shapes); } + case mlir::xla_hlo::HLOTypes::Token: + return ShapeUtil::MakeTokenShape(); default: break; } diff --git a/tensorflow/compiler/tests/BUILD b/tensorflow/compiler/tests/BUILD index f545c6be143..01a0f0a86f2 100644 --- a/tensorflow/compiler/tests/BUILD +++ b/tensorflow/compiler/tests/BUILD @@ -75,6 +75,7 @@ tf_xla_py_test( name = "adadelta_test", size = "medium", srcs = ["adadelta_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -88,6 +89,7 @@ tf_xla_py_test( name = "adagrad_test", size = "small", srcs = ["adagrad_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -102,6 +104,7 @@ tf_xla_py_test( name = "adagrad_da_test", size = "small", srcs = ["adagrad_da_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -115,6 +118,7 @@ tf_xla_py_test( name = "adam_test", size = "small", srcs = ["adam_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -131,6 +135,7 @@ tf_xla_py_test( srcs = ["add_n_test.py"], # TensorList ops are not implemented in the on-demand compilation model yet. disabled_backends = ["cpu_ondemand"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -145,6 +150,7 @@ tf_xla_py_test( name = "argminmax_test", size = "small", srcs = ["argminmax_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -158,6 +164,7 @@ tf_xla_py_test( name = "binary_ops_test", size = "medium", srcs = ["binary_ops_test.py"], + python_version = "PY3", shard_count = 5, tags = [ "optonly", # Times out frequently in fastbuild mode. @@ -183,6 +190,7 @@ tf_xla_py_test( "cpu", "gpu", ], + python_version = "PY3", shard_count = 2, tags = [ "optonly", # Times out frequently in fastbuild mode. @@ -202,6 +210,7 @@ tf_xla_py_test( name = "bucketize_op_test", size = "small", srcs = ["bucketize_op_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -215,6 +224,7 @@ tf_xla_py_test( name = "categorical_op_test", size = "small", srcs = ["categorical_op_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -229,6 +239,7 @@ tf_xla_py_test( name = "cholesky_op_test", size = "medium", srcs = ["cholesky_op_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -245,6 +256,7 @@ tf_xla_py_test( name = "cond_test", size = "small", srcs = ["cond_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/compiler/tf2xla/python:xla", @@ -261,6 +273,7 @@ tf_xla_py_test( name = "self_adjoint_eig_op_test", size = "medium", srcs = ["self_adjoint_eig_op_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -283,6 +296,7 @@ tf_xla_py_test( "cpu", "cpu_ondemand", ], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -301,6 +315,7 @@ tf_xla_py_test( size = "small", timeout = "moderate", srcs = ["matrix_inverse_op_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -316,6 +331,7 @@ tf_xla_py_test( size = "small", timeout = "moderate", srcs = ["matrix_solve_op_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:linalg_ops", @@ -330,6 +346,7 @@ tf_xla_py_test( size = "small", timeout = "moderate", srcs = ["matrix_triangular_solve_op_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -345,6 +362,7 @@ tf_xla_py_test( name = "clustering_test", size = "small", srcs = ["clustering_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -358,6 +376,7 @@ tf_xla_py_test( name = "concat_ops_test", size = "medium", srcs = ["concat_ops_test.py"], + python_version = "PY3", tags = ["many_xla_args"], deps = [ ":xla_test", @@ -375,6 +394,7 @@ tf_xla_py_test( name = "conv2d_test", size = "medium", srcs = ["conv2d_test.py"], + python_version = "PY3", shard_count = 10, deps = [ ":test_utils", @@ -393,6 +413,7 @@ tf_xla_py_test( name = "conv3d_test", size = "medium", srcs = ["conv3d_test.py"], + python_version = "PY3", shard_count = 5, deps = [ ":xla_test", @@ -409,6 +430,7 @@ tf_xla_py_test( name = "depthwise_conv_op_test", size = "medium", srcs = ["depthwise_conv_op_test.py"], + python_version = "PY3", shard_count = 5, tags = [ "no_rocm", @@ -432,6 +454,7 @@ tf_xla_py_test( name = "dynamic_slice_ops_test", size = "small", srcs = ["dynamic_slice_ops_test.py"], + python_version = "PY3", deps = [ "//tensorflow/compiler/tests:xla_test", "//tensorflow/compiler/tf2xla/python:xla", @@ -448,6 +471,7 @@ tf_xla_py_test( "cpu", "gpu", ], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -461,6 +485,7 @@ tf_xla_py_test( name = "reshape_op_test", size = "small", srcs = ["reshape_op_test.py"], + python_version = "PY3", deps = [ "//tensorflow/compiler/tests:xla_test", "//tensorflow/compiler/tf2xla/python:xla", @@ -474,6 +499,7 @@ tf_xla_py_test( name = "dynamic_stitch_test", size = "small", srcs = ["dynamic_stitch_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -487,6 +513,7 @@ tf_xla_py_test( name = "extract_image_patches_op_test", size = "small", srcs = ["extract_image_patches_op_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -499,6 +526,7 @@ tf_xla_py_test( name = "eager_test", size = "medium", srcs = ["eager_test.py"], + python_version = "PY3", tags = [ "multi_and_single_gpu", ], @@ -518,6 +546,7 @@ tf_xla_py_test( name = "fifo_queue_test", size = "medium", srcs = ["fifo_queue_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -532,6 +561,7 @@ tf_xla_py_test( name = "fft_test", size = "medium", srcs = ["fft_test.py"], + python_version = "PY3", shard_count = 6, tags = ["optonly"], deps = [ @@ -548,6 +578,7 @@ tf_xla_py_test( name = "slice_ops_test", size = "small", srcs = ["slice_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -561,6 +592,7 @@ tf_xla_py_test( name = "ftrl_test", size = "medium", srcs = ["ftrl_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -575,6 +607,7 @@ tf_xla_py_test( name = "function_test", size = "small", srcs = ["function_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -587,6 +620,7 @@ tf_xla_py_test( name = "image_ops_test", size = "small", srcs = ["image_ops_test.py"], + python_version = "PY3", shard_count = 10, tags = [ "optonly", # Times out frequently in fastbuild mode. @@ -604,6 +638,7 @@ tf_xla_py_test( name = "listdiff_op_test", size = "small", srcs = ["listdiff_op_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -619,6 +654,7 @@ tf_xla_py_test( name = "lrn_ops_test", size = "medium", srcs = ["lrn_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -633,6 +669,7 @@ tf_xla_py_test( name = "manip_ops_test", size = "small", srcs = ["manip_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -647,6 +684,7 @@ tf_xla_py_test( size = "medium", timeout = "long", srcs = ["matrix_band_part_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -662,6 +700,7 @@ tf_xla_py_test( size = "medium", timeout = "long", srcs = ["matrix_diag_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -674,6 +713,7 @@ tf_xla_py_test( name = "momentum_test", size = "small", srcs = ["momentum_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -688,6 +728,7 @@ tf_xla_py_test( name = "nary_ops_test", size = "small", srcs = ["nary_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -701,6 +742,7 @@ tf_xla_py_test( name = "nullary_ops_test", size = "small", srcs = ["nullary_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:control_flow_ops", @@ -713,6 +755,7 @@ tf_xla_py_test( name = "pooling_ops_test", size = "medium", srcs = ["pooling_ops_test.py"], + python_version = "PY3", shard_count = 10, deps = [ ":xla_test", @@ -728,6 +771,7 @@ tf_xla_py_test( name = "pooling_ops_3d_test", size = "medium", srcs = ["pooling_ops_3d_test.py"], + python_version = "PY3", shard_count = 10, deps = [ ":xla_test", @@ -743,6 +787,7 @@ tf_xla_py_test( name = "proximal_adagrad_test", size = "medium", srcs = ["proximal_adagrad_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -756,6 +801,7 @@ tf_xla_py_test( name = "proximal_gradient_descent_test", size = "medium", srcs = ["proximal_gradient_descent_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -774,6 +820,25 @@ tf_xla_py_test( "cpu", "cpu_ondemand", ], + python_version = "PY3", + shard_count = 5, + tags = ["optonly"], + deps = [ + ":xla_test", + "//tensorflow/python:array_ops", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:training", + "@absl_py//absl/testing:parameterized", + ], +) + +tf_xla_py_test( + name = "unstack_test", + size = "medium", + srcs = ["unstack_test.py"], + python_version = "PY3", shard_count = 5, tags = ["optonly"], deps = [ @@ -791,6 +856,7 @@ tf_xla_py_test( name = "random_ops_test", size = "small", srcs = ["random_ops_test.py"], + python_version = "PY3", shard_count = 10, tags = [ "notap", # TODO(b/141057424): flaky on TPU @@ -810,6 +876,7 @@ tf_xla_py_test( name = "reduce_ops_test", size = "medium", srcs = ["reduce_ops_test.py"], + python_version = "PY3", shard_count = 5, deps = [ ":xla_test", @@ -826,6 +893,7 @@ tf_xla_py_test( name = "reduce_window_test", size = "small", srcs = ["reduce_window_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/compiler/tf2xla/python:xla", @@ -841,6 +909,7 @@ tf_xla_py_test( name = "reverse_ops_test", size = "medium", srcs = ["reverse_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -852,6 +921,7 @@ tf_xla_py_test( name = "reverse_sequence_op_test", size = "medium", srcs = ["reverse_sequence_op_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -865,6 +935,7 @@ tf_xla_py_test( name = "rmsprop_test", size = "small", srcs = ["rmsprop_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -879,6 +950,7 @@ tf_xla_py_test( name = "scan_ops_test", size = "small", srcs = ["scan_ops_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -893,6 +965,7 @@ tf_xla_py_test( name = "segment_reduction_ops_test", size = "medium", srcs = ["segment_reduction_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -907,6 +980,7 @@ tf_xla_py_test( name = "spacetobatch_op_test", size = "medium", srcs = ["spacetobatch_op_test.py"], + python_version = "PY3", shard_count = 3, deps = [ ":xla_test", @@ -921,6 +995,7 @@ tf_xla_py_test( name = "sparse_to_dense_op_test", size = "small", srcs = ["sparse_to_dense_op_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -934,6 +1009,7 @@ tf_xla_py_test( name = "stack_ops_test", size = "small", srcs = ["stack_ops_test.py"], + python_version = "PY3", tags = ["config-cuda-only"], use_xla_device = False, deps = [ @@ -949,6 +1025,7 @@ tf_xla_py_test( name = "stateful_random_ops_test", size = "medium", srcs = ["stateful_random_ops_test.py"], + python_version = "PY3", shard_count = 10, tags = ["optonly"], deps = [ @@ -965,6 +1042,7 @@ tf_xla_py_test( name = "stateless_random_ops_test", size = "medium", srcs = ["stateless_random_ops_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -982,6 +1060,7 @@ tf_xla_py_test( srcs = ["tensor_array_ops_test.py"], # TensorArray ops are not implemented in the on-demand compilation model yet. disabled_backends = ["cpu_ondemand"], + python_version = "PY3", tags = ["config-cuda-only"], use_xla_device = False, deps = [ @@ -1005,6 +1084,7 @@ tf_xla_py_test( srcs = ["tensor_list_ops_test.py"], # TensorList ops are not implemented in the on-demand compilation model yet. disabled_backends = ["cpu_ondemand"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -1019,6 +1099,7 @@ tf_xla_py_test( name = "ternary_ops_test", size = "medium", srcs = ["ternary_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -1034,6 +1115,7 @@ tf_xla_py_test( name = "unary_ops_test", size = "medium", srcs = ["unary_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -1049,6 +1131,7 @@ tf_xla_py_test( name = "fused_batchnorm_test", size = "medium", srcs = ["fused_batchnorm_test.py"], + python_version = "PY3", deps = [ ":test_utils", ":xla_test", @@ -1068,6 +1151,7 @@ tf_xla_py_test( name = "variable_ops_test", size = "small", srcs = ["variable_ops_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -1086,6 +1170,7 @@ tf_xla_py_test( name = "while_test", size = "small", srcs = ["while_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/compiler/tf2xla/python:xla", @@ -1100,6 +1185,7 @@ tf_xla_py_test( name = "gather_test", size = "medium", srcs = ["gather_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -1114,6 +1200,7 @@ tf_xla_py_test( name = "gather_nd_op_test", size = "medium", srcs = ["gather_nd_op_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -1126,6 +1213,7 @@ tf_xla_py_test( name = "scatter_nd_op_test", size = "medium", srcs = ["scatter_nd_op_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -1139,6 +1227,7 @@ tf_xla_py_test( name = "sort_ops_test", size = "medium", srcs = ["sort_ops_test.py"], + python_version = "PY3", shard_count = 1, # Times out in fastbuild mode. tags = ["optonly"], @@ -1154,6 +1243,7 @@ tf_xla_py_test( name = "data_format_ops_test", size = "small", srcs = ["data_format_ops_test.py"], + python_version = "PY3", deps = [ "//tensorflow/compiler/tests:xla_test", "//tensorflow/python:array_ops", @@ -1167,6 +1257,7 @@ tf_xla_py_test( name = "xla_device_test", size = "small", srcs = ["xla_device_test.py"], + python_version = "PY3", tags = ["optonly"], deps = [ ":xla_test", @@ -1180,7 +1271,8 @@ cuda_py_test( name = "xla_device_gpu_test", size = "small", srcs = ["xla_device_gpu_test.py"], - additional_deps = [ + xla_enable_strict_auto_jit = False, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -1188,16 +1280,16 @@ cuda_py_test( "//tensorflow/python:framework", "//tensorflow/python:math_ops", ], - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "jit_test", size = "medium", srcs = ["jit_test.py"], - additional_deps = [ + shard_count = 5, + xla_enable_strict_auto_jit = False, + deps = [ ":test_utils", - "//tensorflow/python/compiler/xla:compiler_py", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -1207,25 +1299,24 @@ cuda_py_test( "//tensorflow/python:gradients", "//tensorflow/python:math_ops", "//tensorflow/python:nn_ops", + "//tensorflow/python/compiler/xla:compiler_py", ], - shard_count = 5, - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "dense_layer_test", size = "medium", srcs = ["dense_layer_test.py"], - additional_deps = [ + xla_enable_strict_auto_jit = False, + deps = [ ":test_utils", - "//tensorflow/python/compiler/xla:compiler_py", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:layers", "//tensorflow/python:variables", + "//tensorflow/python/compiler/xla:compiler_py", ], - xla_enable_strict_auto_jit = False, ) cc_library( @@ -1300,7 +1391,8 @@ py_library( cuda_py_test( name = "lstm_test", srcs = ["lstm_test.py"], - additional_deps = [ + xla_enable_strict_auto_jit = False, + deps = [ ":lstm", ":xla_test", "//tensorflow/python:array_ops", @@ -1312,7 +1404,6 @@ cuda_py_test( "//tensorflow/python:platform", "//tensorflow/python:variables", ], - xla_enable_strict_auto_jit = False, ) # An example of ahead-of-time compilation using tfcompile. The @@ -1342,6 +1433,7 @@ tf_xla_py_test( name = "fake_quant_ops_test", size = "medium", srcs = ["fake_quant_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:framework", @@ -1353,6 +1445,7 @@ tf_xla_py_test( name = "placeholder_test", size = "small", srcs = ["placeholder_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/python:array_ops", @@ -1365,6 +1458,7 @@ tf_xla_py_test( name = "quantized_ops_test", size = "medium", srcs = ["quantized_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/compiler/tf2xla/python:xla", @@ -1381,6 +1475,7 @@ tf_xla_py_test( name = "xla_ops_test", size = "medium", srcs = ["xla_ops_test.py"], + python_version = "PY3", deps = [ ":xla_test", "//tensorflow/compiler/tf2xla/python:xla", @@ -1396,6 +1491,7 @@ tf_xla_py_test( name = "conv_node_name_test", size = "medium", srcs = ["conv_node_name_test.py"], + python_version = "PY3", shard_count = 5, deps = [ ":xla_test", @@ -1418,6 +1514,7 @@ tf_xla_py_test( "cpu", "gpu", ], + python_version = "PY3", tags = [ "optonly", ], @@ -1439,6 +1536,7 @@ tf_xla_py_test( "cpu", "gpu", ], + python_version = "PY3", tags = [ "optonly", ], diff --git a/tensorflow/compiler/tests/binary_ops_test.py b/tensorflow/compiler/tests/binary_ops_test.py index 4d85ca67777..65a95c01723 100644 --- a/tensorflow/compiler/tests/binary_ops_test.py +++ b/tensorflow/compiler/tests/binary_ops_test.py @@ -95,29 +95,6 @@ class BinaryOpsTest(xla_test.XLATestCase): rtol=1e-6, atol=1e-8) - self._testBinary(math_ops.pow, dtype(3), dtype(4), expected=dtype(81)) - - self._testBinary( - math_ops.pow, - np.array([1, 2], dtype=dtype), - np.zeros(shape=[0, 2], dtype=dtype), - expected=np.zeros(shape=[0, 2], dtype=dtype)) - self._testBinary( - math_ops.pow, - np.array([10, 4], dtype=dtype), - np.array([2, 3], dtype=dtype), - expected=np.array([100, 64], dtype=dtype)) - self._testBinary( - math_ops.pow, - dtype(2), - np.array([3, 4], dtype=dtype), - expected=np.array([8, 16], dtype=dtype)) - self._testBinary( - math_ops.pow, - np.array([[2], [3]], dtype=dtype), - dtype(4), - expected=np.array([[16], [81]], dtype=dtype)) - self._testBinary( math_ops.atan2, np.array([0, np.sqrt(2), 1, np.sqrt(2), 0], dtype), @@ -310,7 +287,7 @@ class BinaryOpsTest(xla_test.XLATestCase): self._testBinary(bitwise_ops.right_shift, lhs, rhs, expected=expected) def testAdd(self): - for dtype in self.numeric_types | {np.float64}: + for dtype in self.numeric_types: self._testBinary( math_ops.add, np.array([1, 2, 0], dtype=dtype), @@ -336,7 +313,7 @@ class BinaryOpsTest(xla_test.XLATestCase): dtype=dtype)) def testMultiply(self): - for dtype in self.numeric_types | {np.float64}: + for dtype in self.numeric_types: self._testBinary( math_ops.multiply, np.array([1, 20], dtype=dtype), @@ -362,6 +339,42 @@ class BinaryOpsTest(xla_test.XLATestCase): dtype=dtype), rtol=1e-14) + def testPow(self): + for dtype in self.float_types: + rtol = 1e-14 if dtype == np.float64 else None + + self._testBinary( + math_ops.pow, + dtype(3), + dtype(4), + expected=dtype(81), + rtol=rtol) + + self._testBinary( + math_ops.pow, + np.array([1, 2], dtype=dtype), + np.zeros(shape=[0, 2], dtype=dtype), + expected=np.zeros(shape=[0, 2], dtype=dtype), + rtol=rtol) + self._testBinary( + math_ops.pow, + np.array([10, 4], dtype=dtype), + np.array([2, 3], dtype=dtype), + expected=np.array([100, 64], dtype=dtype), + rtol=rtol) + self._testBinary( + math_ops.pow, + dtype(2), + np.array([3, 4], dtype=dtype), + expected=np.array([8, 16], dtype=dtype), + rtol=rtol) + self._testBinary( + math_ops.pow, + np.array([[2], [3]], dtype=dtype), + dtype(4), + expected=np.array([[16], [81]], dtype=dtype), + rtol=rtol) + def testNumericOps(self): for dtype in self.numeric_types: self._testBinary( diff --git a/tensorflow/compiler/tests/matrix_diag_ops_test.py b/tensorflow/compiler/tests/matrix_diag_ops_test.py index 69ae03a06cf..1ca9b157fa1 100644 --- a/tensorflow/compiler/tests/matrix_diag_ops_test.py +++ b/tensorflow/compiler/tests/matrix_diag_ops_test.py @@ -114,7 +114,7 @@ def square_cases(align=None): [6, 7, 8, 9, 1], [2, 3, 4, 5, 6]]]) tests = dict() - # tests[d_lower, d_upper] = (compact_diagonals, padded_diagnals) + # tests[d_lower, d_upper] = (compact_diagonals, padded_diagonals) tests[-1, -1] = (np.array([[6, 4, 1, 7], [5, 2, 8, 5]]), np.array([[[0, 0, 0, 0, 0], @@ -192,7 +192,7 @@ def tall_cases(align=None): [7, 8, 9], [9, 8, 7]]]) tests = dict() - # tests[d_lower, d_upper] = (compact_diagonals, padded_diagnals) + # tests[d_lower, d_upper] = (compact_diagonals, padded_diagonals) tests[0, 0] = (np.array([[1, 5, 9], [3, 2, 6]]), np.array([[[1, 0, 0], @@ -276,7 +276,7 @@ def fat_cases(align=None): [8, 9, 1, 2], [3, 4, 5, 6]]]) tests = dict() - # tests[d_lower, d_upper] = (compact_diagonals, padded_diagnals) + # tests[d_lower, d_upper] = (compact_diagonals, padded_diagonals) tests[0, 0] = (np.array([[1, 6, 2], [4, 9, 5]]), np.array([[[1, 0, 0, 0], diff --git a/tensorflow/compiler/tests/quantized_ops_test.py b/tensorflow/compiler/tests/quantized_ops_test.py index 9a1d29c0092..100be3b9aa5 100644 --- a/tensorflow/compiler/tests/quantized_ops_test.py +++ b/tensorflow/compiler/tests/quantized_ops_test.py @@ -49,7 +49,7 @@ class QuantizedOpsTest(xla_test.XLATestCase): self.assertAllEqual(value, expected) -class DeuantizedOpsTest(xla_test.XLATestCase): +class DequantizedOpsTest(xla_test.XLATestCase): def pack_uint8_r2_to_uint32(self, test_input): num_rows, num_columns = test_input.get_shape().as_list() diff --git a/tensorflow/compiler/tests/randomized_tests.cc b/tensorflow/compiler/tests/randomized_tests.cc index 5a2bda93942..dfa5bc106ed 100644 --- a/tensorflow/compiler/tests/randomized_tests.cc +++ b/tensorflow/compiler/tests/randomized_tests.cc @@ -3423,7 +3423,7 @@ int main(int argc, char** argv) { tensorflow::Flag( "tf_xla_random_seed", &tensorflow::tf_xla_random_seed, "Random seed to use for XLA tests. <= 0 means choose a seed " - "nondetermistically."), + "nondeterministically."), // TODO(phawkins): it might make more sense to run each test up to a // configurable time bound. tensorflow::Flag("tf_xla_test_repetitions", diff --git a/tensorflow/compiler/tests/unstack_test.py b/tensorflow/compiler/tests/unstack_test.py new file mode 100644 index 00000000000..93c3982cf3b --- /dev/null +++ b/tensorflow/compiler/tests/unstack_test.py @@ -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. +# ============================================================================== +"""Tests for tensorflow.compiler.tests.unstack.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized +import numpy as np + +from tensorflow.compiler.tests import xla_test +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import test + + +class UnstackOpTest(xla_test.XLATestCase, parameterized.TestCase): + + def _test(self, size): + with self.session() as sess: + x_tf = array_ops.placeholder(np.float32, shape=[size, 512]) + with self.test_scope(): + ret = array_ops.unstack(x_tf) + ret_vals = sess.run([ret], feed_dict={x_tf: np.zeros([size, 512])}) + self.assertLen(ret_vals[0], size) + for ret_val in ret_vals[0]: + self.assertTrue(np.all(ret_val == 0.)) + + def testLarge2000(self): + self._test(2000) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc index 20804af5229..ba293af7bd9 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.cc @@ -161,7 +161,7 @@ Status GetEngineInfo(const Graph* g, const int node_id = node->id(); const string& node_name = node->name(); - // Create input connections. Sort edges first to make determnistic since + // Create input connections. Sort edges first to make deterministic since // in_edges is a set of pointers. std::vector in_edges(node->in_edges().begin(), node->in_edges().end()); @@ -186,7 +186,7 @@ Status GetEngineInfo(const Graph* g, // If it doesn't have any edges, TF will prune it out. // // Note that the segmenter already ensure that the constant data input - // is valid and suppported by the engine. + // is valid and supported by the engine. if (!added_const_nodes.insert(input_node).second) { // Already added before. continue; @@ -209,7 +209,7 @@ Status GetEngineInfo(const Graph* g, node_id, edge->dst_input(), /*input_edge=*/true, port); } } - // Create output connections. Sort edges first to make determnistic since + // Create output connections. Sort edges first to make deterministic since // out_edges is a set of pointers. std::vector out_edges(node->out_edges().begin(), node->out_edges().end()); @@ -374,7 +374,8 @@ Status CreateTRTNode(const ConversionParams& params, input_shapes.at(conn.port_number) = conn.outside_shape; // Shape must be fully defined (excluding batch dimension) for static // mode. - if (info.engine_type == EngineInfo::EngineType::TRTStatic) { + if (params.use_implicit_batch && + info.engine_type == EngineInfo::EngineType::TRTStatic) { for (int i = 1; i < conn.outside_shape.dims(); i++) { if (conn.outside_shape.dim_size(i) <= 0) { return errors::Internal( @@ -427,7 +428,7 @@ Status CreateTRTNode(const ConversionParams& params, calibrate_int8 ? TrtPrecisionMode::FP32 : info.precision_mode, max_batch_size, info.max_workspace_size_bytes, input_shapes, trt_logger, alloc, /*calibrator=*/nullptr, &engine, info.use_calibration, - /*convert_successfully=*/nullptr)); + params.use_implicit_batch, /*convert_successfully=*/nullptr)); TrtUniquePtrType engine_data(engine->serialize()); segment_string = string(static_cast(engine_data->data()), engine_data->size()); @@ -463,6 +464,7 @@ Status CreateTRTNode(const ConversionParams& params, .Attr("workspace_size_bytes", info.max_workspace_size_bytes) .Attr("precision_mode", prec_string) .Attr("use_calibration", info.use_calibration) + .Attr("_use_implicit_batch", params.use_implicit_batch) .Attr("OutT", out_types) .Finalize(&trt_node); if (!status.ok()) { @@ -624,7 +626,7 @@ Status ConvertAfterShapes(const ConversionParams& params) { segment_options.minimum_segment_size = params.minimum_segment_size; segment::SegmentNodesVector initial_segments; TrtNodeValidator validator(*params.graph_properties, params.precision_mode, - params.use_calibration); + params.use_calibration, params.use_implicit_batch); TF_RETURN_IF_ERROR(segment::SegmentGraph( &graph, std::bind(&TrtNodeValidator::IsTensorRTCandidate, &validator, diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h index 9b2c6237330..00dc4c72f43 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_graph.h @@ -48,6 +48,7 @@ struct ConversionParams { // maximum number of cached engines int max_cached_engines = 1; bool use_calibration = true; + bool use_implicit_batch = true; }; // Method to call from optimization pass diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc index 90c28e03d4d..46ed2d48c52 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.cc @@ -262,6 +262,7 @@ void GetInputProperties(const grappler::GraphProperties& graph_properties, Status ValidateTensorProperties(const string& producer_node_type, const DataType dtype, const PartialTensorShape& shape, + const bool use_implicit_batch, bool validation_only, nvinfer1::DataType* trt_dtype, nvinfer1::Dims* trt_dims, int* batch_size) { @@ -272,20 +273,27 @@ Status ValidateTensorProperties(const string& producer_node_type, if (shape.dims() < 0) { return errors::InvalidArgument("Input tensor rank is unknown."); } - if (shape.dims() > nvinfer1::Dims::MAX_DIMS + 1) { // +1 for batch dim - return errors::OutOfRange("Input tensor rank is greater than ", - nvinfer1::Dims::MAX_DIMS + 1); + // Add 1 to maximum rank for implicit batch dim. + const int max_rank = nvinfer1::Dims::MAX_DIMS + (use_implicit_batch ? 1 : 0); + if (shape.dims() > max_rank) { + return errors::OutOfRange("Input tensor rank is greater than ", max_rank); } - if (producer_node_type != "Const" && shape.dims() < 1) { + if (use_implicit_batch && (producer_node_type != "Const") && + (shape.dims() < 1)) { return errors::InvalidArgument( "Scalar input tensor is not supported since the first dimension " "is treated as batch dimension by TRT"); } - *trt_dims = TensorShapeToTrtDims(shape, /*ignore_first_dim=*/true); - *batch_size = shape.dim_size(0); + *trt_dims = TensorShapeToTrtDims(shape, + /*ignore_first_dim=*/use_implicit_batch); + // Get batch size for tensor if it will not be included the shape. + if (use_implicit_batch) { + *batch_size = shape.dim_size(0); + } // Don't convert empty tensors (dim value of 0). - for (int d = 1; d < shape.dims(); ++d) { + const int first_trt_dim = use_implicit_batch ? 1 : 0; + for (int d = first_trt_dim; d < shape.dims(); ++d) { if (shape.dim_size(d) == 0) { return errors::Unimplemented( "Input tensor with shape ", shape.DebugString(), @@ -296,7 +304,7 @@ Status ValidateTensorProperties(const string& producer_node_type, if (validation_only) return Status::OK(); // Following are validations at runtime. - for (int d = 1; d < shape.dims(); ++d) { + for (int d = first_trt_dim; d < shape.dims(); ++d) { if (shape.dim_size(d) < 0) { return errors::InvalidArgument( "Input tensor with shape ", shape.DebugString(), @@ -369,6 +377,7 @@ string DebugString(const nvinfer1::ITensor& tensor) { Status GetTrtBroadcastShape(const TRT_TensorOrWeights& operand_l, const TRT_TensorOrWeights& operand_r, const bool check_feasibility, + const bool use_implicit_batch, nvinfer1::Dims* operand_l_new_dims, nvinfer1::Dims* operand_r_new_dims) { // TensorRT Elementwise op supports broadcast but requires both tensor to be @@ -400,14 +409,15 @@ Status GetTrtBroadcastShape(const TRT_TensorOrWeights& operand_l, } const int max_nb_dims = nvinfer1::Dims::MAX_DIMS + 1; - auto compute_output_dims = [](const TRT_TensorOrWeights& input, - int broadcast_num_dims, int* output_dims_array, - nvinfer1::Dims* output_dims) { + auto compute_output_dims = [use_implicit_batch]( + const TRT_TensorOrWeights& input, + int broadcast_num_dims, int* output_dims_array, + nvinfer1::Dims* output_dims) { const nvinfer1::Dims input_dims = input.GetTrtDims(); std::fill(output_dims_array, output_dims_array + max_nb_dims, 1); std::copy(input_dims.d, input_dims.d + input_dims.nbDims, output_dims_array + broadcast_num_dims - input_dims.nbDims); - if (input.is_tensor()) { + if (use_implicit_batch && input.is_tensor()) { const int true_input_dims = input_dims.nbDims + 1; if (true_input_dims < broadcast_num_dims) { return errors::InvalidArgument( @@ -419,17 +429,27 @@ Status GetTrtBroadcastShape(const TRT_TensorOrWeights& operand_l, // be broadcasted. output_dims_array[0] = -1; } - // Copy to output dimensions (stripping the batch dimension). - output_dims->nbDims = broadcast_num_dims - 1; - std::copy(output_dims_array + 1, output_dims_array + broadcast_num_dims, - output_dims->d); + // Copy to output dimensions + if (use_implicit_batch) { + // Strip batch dimension while copying + output_dims->nbDims = broadcast_num_dims - 1; + std::copy(output_dims_array + 1, output_dims_array + broadcast_num_dims, + output_dims->d); + } else { + output_dims->nbDims = broadcast_num_dims; + std::copy(output_dims_array, output_dims_array + broadcast_num_dims, + output_dims->d); + } + return Status::OK(); }; // Compute the output dimensions. const int broadcast_num_dims = - std::max(operand_l.GetTrtDims().nbDims + (operand_l.is_tensor() ? 1 : 0), - operand_r.GetTrtDims().nbDims + (operand_r.is_tensor() ? 1 : 0)); + std::max(operand_l.GetTrtDims().nbDims + + (use_implicit_batch && operand_l.is_tensor()), + operand_r.GetTrtDims().nbDims + + (use_implicit_batch && operand_r.is_tensor())); int output_l[max_nb_dims], output_r[max_nb_dims]; TF_RETURN_IF_ERROR(compute_output_dims(operand_l, broadcast_num_dims, output_l, operand_l_new_dims)); @@ -1047,14 +1067,16 @@ TRT_ShapedWeights TrtWeightStore::GetTempWeights(nvinfer1::DataType trt_dtype, OpConverterParams::OpConverterParams( const NodeDef& node_def, const std::vector& inputs, std::vector* outputs, TrtWeightStore* weight_store, - TrtPrecisionMode precision_mode, bool use_calibration) + TrtPrecisionMode precision_mode, bool use_calibration, + bool use_implicit_batch) : node_def(node_def), inputs(inputs), outputs(outputs), validation_only(true), weight_store(weight_store), precision_mode(precision_mode), - use_calibration(use_calibration) {} + use_calibration(use_calibration), + use_implicit_batch(use_implicit_batch) {} OpConverterParams::OpConverterParams( Converter* converter, const NodeDef& node_def, @@ -1067,7 +1089,8 @@ OpConverterParams::OpConverterParams( validation_only(false), weight_store(weight_store), precision_mode(converter->precision_mode()), - use_calibration(converter->use_calibration()) {} + use_calibration(converter->use_calibration()), + use_implicit_batch(converter->use_implicit_batch()) {} const std::set* TrtNodeValidator::quantize_ops = new std::set{ "QuantizeAndDequantizeV2", @@ -1078,10 +1101,12 @@ const std::set* TrtNodeValidator::quantize_ops = new std::set{ TrtNodeValidator::TrtNodeValidator( const grappler::GraphProperties& graph_properties, - TrtPrecisionMode precision_mode, bool use_calibration) + TrtPrecisionMode precision_mode, bool use_calibration, + bool use_implicit_batch) : graph_properties_(graph_properties), precision_mode_(precision_mode), - use_calibration_(use_calibration) { + use_calibration_(use_calibration), + use_implicit_batch_(use_implicit_batch) { RegisterOpValidators(); } @@ -1116,8 +1141,8 @@ Status TrtNodeValidator::ConvertToTensorOrWeights( nvinfer1::Dims trt_dims; int batch_size = -1; TF_RETURN_IF_ERROR(ValidateTensorProperties( - node_def.op(), dtype, shape, /*validation_only_=*/true, &trt_dtype, - &trt_dims, &batch_size)); + node_def.op(), dtype, shape, use_implicit_batch_, + /*validation_only_=*/true, &trt_dtype, &trt_dims, &batch_size)); // Adds a fake ITensor. This is fine since op converter operates in // validation-only mode and it won't (and shouldn't) use the tensor to do @@ -1161,7 +1186,8 @@ Status TrtNodeValidator::IsTensorRTCandidate(const Node* node) { OpConverter validator = op_validators_[op]; OpConverterParams params(node->def(), inputs, /*arg_outputs=*/nullptr, - &weight_store_, precision_mode_, use_calibration_); + &weight_store_, precision_mode_, use_calibration_, + use_implicit_batch_); return validator(¶ms); } @@ -1171,7 +1197,8 @@ Status TrtNodeValidator::ConvertConstToWeights( TRT_TensorOrWeights* output) { std::vector outputs; OpConverterParams params(const_node_def, inputs, &outputs, &weight_store_, - precision_mode_, use_calibration_); + precision_mode_, use_calibration_, + use_implicit_batch_); Status status = op_validators_["Const"](¶ms); if (status.ok() && output) *output = outputs[0]; return status; @@ -1218,16 +1245,19 @@ static void InitializeTrtPlugins(nvinfer1::ILogger* trt_logger) { // static StatusOr> Converter::Create( TrtPrecisionMode precision_mode, bool use_calibration, - nvinfer1::ILogger* trt_logger) { - std::unique_ptr converter = absl::WrapUnique( - new Converter(precision_mode, use_calibration, trt_logger)); + nvinfer1::ILogger* trt_logger, const bool use_implicit_batch) { + std::unique_ptr converter = absl::WrapUnique(new Converter( + precision_mode, use_calibration, trt_logger, use_implicit_batch)); TF_RETURN_IF_ERROR(converter->Init(trt_logger)); return converter; } Converter::Converter(TrtPrecisionMode precision_mode, bool use_calibration, - nvinfer1::ILogger* trt_logger) - : precision_mode_(precision_mode), use_calibration_(use_calibration) { + nvinfer1::ILogger* trt_logger, + const bool use_implicit_batch) + : precision_mode_(precision_mode), + use_calibration_(use_calibration), + use_implicit_batch_(use_implicit_batch) { InitializeTrtPlugins(trt_logger); this->RegisterOpConverters(); } @@ -1237,7 +1267,16 @@ Status Converter::Init(nvinfer1::ILogger* trt_logger) { trt_builder_.reset(nvinfer1::createInferBuilder(*trt_logger)); VLOG(1) << "Creating TensorRT network"; +#if IS_TRT_VERSION_GE(6, 0, 0, 0) + const uint32_t flags = + use_implicit_batch_ + ? 0U + : (1U << static_cast( + nvinfer1::NetworkDefinitionCreationFlag::kEXPLICIT_BATCH)); + trt_network_.reset(trt_builder_->createNetworkV2(flags)); +#else trt_network_.reset(trt_builder_->createNetwork()); +#endif if (!trt_network_) { return errors::Internal("Failed to create TensorRT network object"); } @@ -1374,8 +1413,29 @@ Status Converter::BuildCudaEngine( TRTInt8Calibrator* calibrator) { VLOG(1) << "Configuring TensorRT builder"; trt_builder_->setMaxBatchSize(max_batch_size); - trt_builder_->setMaxWorkspaceSize(max_workspace_size_bytes); trt_builder_->setGpuAllocator(allocator); +#if IS_TRT_VERSION_GE(6, 0, 0, 0) + // Create a network configuration and use it to build a TRT engine. + TrtUniquePtrType builder_config( + trt_builder_->createBuilderConfig()); + builder_config->setMaxWorkspaceSize(max_workspace_size_bytes); + if (precision_mode_ == TrtPrecisionMode::FP16) { + builder_config->setFlag(nvinfer1::BuilderFlag::kFP16); + } else if (precision_mode_ == TrtPrecisionMode::INT8) { + builder_config->setFlag(nvinfer1::BuilderFlag::kFP16); + builder_config->setFlag(nvinfer1::BuilderFlag::kINT8); + if (use_calibration_) { + builder_config->setInt8Calibrator(calibrator); + } else { + builder_config->setInt8Calibrator(nullptr); + } + } + + VLOG(1) << "Building TensorRT engine"; + engine->reset( + trt_builder_->buildEngineWithConfig(*network(), *builder_config)); +#else + trt_builder_->setMaxWorkspaceSize(max_workspace_size_bytes); if (precision_mode_ == TrtPrecisionMode::FP16) { trt_builder_->setFp16Mode(true); } else if (precision_mode_ == TrtPrecisionMode::INT8) { @@ -1393,6 +1453,7 @@ Status Converter::BuildCudaEngine( VLOG(1) << "Building TensorRT engine"; engine->reset(trt_builder_->buildCudaEngine(*network())); +#endif if (engine->get() == nullptr) { return errors::Internal("Failed to build TensorRT engine"); } @@ -1420,7 +1481,9 @@ Status Converter::AddTensorOrWeights(const string& name, // We rely on the individual op converter to understand the semantics of the // TF node, and make sure it doesn't change the batch size nor introduce // intra-element dependency inside the batch. - if (input.is_tensor()) input.set_batch_size(batch_size_); + if (use_implicit_batch_ && input.is_tensor()) { + input.set_batch_size(batch_size_); + } if (trt_tensors_.insert({name, std::move(input)}).second) return Status::OK(); return errors::AlreadyExists("tensor/weights ", name, " already exist."); } @@ -2511,7 +2574,7 @@ Status ConvertStridedSliceHelper(OpConverterParams* params, return Status::OK(); } else if (pad_dims.size() == 1) { // Only one dim is modified but we have to have 2, mark a second dim which - // will have padding of 0. The dim we add is chosen to avoid an unecessary + // will have padding of 0. The dim we add is chosen to avoid an unnecessary // transpose. if (pad_dims[0] != 2) { pad_dims.push_back(2); @@ -3746,6 +3809,7 @@ Status ConvertBiasAdd(OpConverterParams* params) { // Next, broadcast the bias across the input. TF_RETURN_IF_ERROR(GetTrtBroadcastShape(inputs.at(0), inputs.at(1), /*check_feasibility=*/true, + params->use_implicit_batch, &input_shape, &bias_shape)); } @@ -3983,9 +4047,9 @@ Status ConvertBinary(OpConverterParams* params) { } nvinfer1::Dims broadcasted_dims_l, broadcasted_dims_r; - TF_RETURN_IF_ERROR( - GetTrtBroadcastShape(operand_l, operand_r, /*check_feasibility=*/true, - &broadcasted_dims_l, &broadcasted_dims_r)); + TF_RETURN_IF_ERROR(GetTrtBroadcastShape( + operand_l, operand_r, /*check_feasibility=*/true, + params->use_implicit_batch, &broadcasted_dims_l, &broadcasted_dims_r)); nvinfer1::ITensor* tensor_l = nullptr; nvinfer1::ITensor* tensor_r = nullptr; // This will also convert constants to tensors, and set quantization ranges. @@ -4981,7 +5045,7 @@ Status ConvertBatchMatMul(OpConverterParams* params) { nvinfer1::Dims broadcasted_dims_l, broadcasted_dims_r; TF_RETURN_IF_ERROR(GetTrtBroadcastShape( inputs.at(0), inputs.at(1), /*check_feasibility=*/false, - &broadcasted_dims_l, &broadcasted_dims_r)); + params->use_implicit_batch, &broadcasted_dims_l, &broadcasted_dims_r)); nvinfer1::ITensor* tensor_l = nullptr; nvinfer1::ITensor* tensor_r = nullptr; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( @@ -5081,6 +5145,17 @@ Status ConvertTopK(OpConverterParams* params) { CheckInputsWeights(*params, {{"input", false}, {"k", true}})); TF_RETURN_IF_ERROR( AllowDataTypes(*params, {DataType::DT_FLOAT, DataType::DT_HALF})); + TFAttrs attrs(node_def); + const bool sorted = attrs.get("sorted"); + if (!sorted) { + // TensorRT only supports sorted output. Although TensorFlow API + // doesn't specify the order of output elements in case sorted=false, + // but it's safer to not convert because the output of TensorRT might + // be different with TensorFlow which can cause confusion. + return errors::InvalidArgument("Only sorted=True is supported, at", + node_def.name()); + } + nvinfer1::ITensor* tensor = inputs.at(0).tensor(); const int num_dims = tensor->getDimensions().nbDims; if (num_dims == 0) { @@ -5210,7 +5285,7 @@ Status ConvertSquaredDifference(OpConverterParams* params) { nvinfer1::Dims broadcasted_dims_l, broadcasted_dims_r; TF_RETURN_IF_ERROR(GetTrtBroadcastShape( inputs.at(0), inputs.at(1), /*check_feasibility=*/true, - &broadcasted_dims_l, &broadcasted_dims_r)); + params->use_implicit_batch, &broadcasted_dims_l, &broadcasted_dims_r)); nvinfer1::ITensor* tensor_l = nullptr; nvinfer1::ITensor* tensor_r = nullptr; TF_RETURN_IF_ERROR(params->converter->PrepareTensorForShape( @@ -5636,13 +5711,13 @@ Status ConvertGraphDefToEngine( nvinfer1::ILogger* trt_logger, nvinfer1::IGpuAllocator* allocator, TRTInt8Calibrator* calibrator, TrtUniquePtrType* engine, bool use_calibration, - bool* convert_successfully) { + const bool use_implicit_batch, bool* convert_successfully) { engine->reset(); if (convert_successfully) *convert_successfully = false; // Creating converter, TensorRT builder and network - auto statusor = - Converter::Create(precision_mode, use_calibration, trt_logger); + auto statusor = Converter::Create(precision_mode, use_calibration, trt_logger, + use_implicit_batch); TF_RETURN_IF_ERROR(statusor.status()); auto converter = std::move(statusor.ValueOrDie()); @@ -5679,7 +5754,8 @@ Status ConvertGraphDefToEngine( auto shape = input_shapes.at(slot_number); auto status = ValidateTensorProperties( node_def.op(), node_def.attr().at(type_key).type(), shape, - /*validation_only=*/false, &trt_dtype, &trt_dims, &batch_size); + use_implicit_batch, /*validation_only=*/false, &trt_dtype, &trt_dims, + &batch_size); if (!status.ok()) { const string error_message = StrCat("Validation failed for ", node_name, " and input slot ", diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h index eb51ec1b3f6..6090296d8df 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes.h @@ -141,9 +141,9 @@ Status ConvertSegmentToGraphDef( // Converts given subgraph to a TRT engine saved in 'engine'. Returns ok iff // 'builder' successfully build the engine. If the result is not ok, 'engine' // will be set to nullptr -// Once returned, 'builder' is not needed any more and can be safely detroyed. +// Once returned, 'builder' is not needed any more and can be safely destroyed. // -// - convert_successfully: indicates whether the converson to TensorRT network +// - convert_successfully: indicates whether the conversion to TensorRT network // is successful. This is different than successfully building the engine: // building can still fail afterwards. Status ConvertGraphDefToEngine( @@ -153,7 +153,7 @@ Status ConvertGraphDefToEngine( nvinfer1::ILogger* logger, nvinfer1::IGpuAllocator* allocator, TRTInt8Calibrator* calibrator, TrtUniquePtrType* engine, bool use_calibration, - bool* convert_successfully); + const bool use_implicit_batch, bool* convert_successfully); // Helper class for the segmenter to determine whether an output edge from the // TRT segment is valid. @@ -340,6 +340,9 @@ class TRT_TensorOrWeights { // is that currently it cannot convert a graph that doesn't have the batch // size represented in the shapes or the batch sizes are different. See // b/118387490 for more details. + // + // If use_implicit_batch is false, batch_size_ is unused and + // tensor_->getDimensions() will contain the entire shape (A,B,C). int batch_size_ = -1; TRT_ShapedWeights weights_; @@ -358,7 +361,8 @@ struct OpConverterParams { const std::vector& inputs, std::vector* outputs, TrtWeightStore* weight_store, - TrtPrecisionMode precision_mode, bool use_calibration); + TrtPrecisionMode precision_mode, bool use_calibration, + bool use_implicit_batch); // Constructor used for conversion. OpConverterParams(Converter* converter, const NodeDef& node_def, @@ -374,6 +378,7 @@ struct OpConverterParams { TrtWeightStore* weight_store; const TrtPrecisionMode precision_mode; const bool use_calibration; + const bool use_implicit_batch; }; using OpConverter = std::function; @@ -385,7 +390,8 @@ class TrtNodeValidator { // checked by IsTensorRTCandidate() later. It is used to get the shape and // data type information of a tensor for validation purpose. TrtNodeValidator(const grappler::GraphProperties& graph_properties, - TrtPrecisionMode precision_mode, bool use_calibration); + TrtPrecisionMode precision_mode, bool use_calibration, + bool use_implicit_batch); // Returns OK iff 'node' is a TF-TRT conversion candidate, which will be added // to TRT subgraph and later converted into TRT engine. @@ -425,6 +431,8 @@ class TrtNodeValidator { const bool use_calibration_; + const bool use_implicit_batch_; + friend class ValidatorTest; friend class OpConverterTest; }; @@ -447,7 +455,7 @@ class Converter { static StatusOr> Create( TrtPrecisionMode precision_mode, bool use_calibration, - nvinfer1::ILogger* trt_logger); + nvinfer1::ILogger* trt_logger, const bool use_implicit_batch); ////////////////////////////////////////////////////////////////////////////// // Methods used by the TRT engine builder to build a TRT network from a TF @@ -486,6 +494,9 @@ class Converter { // Calibration will be or was previously performed on this network? bool use_calibration() const { return use_calibration_; } + // Whether implicit batch mode is enabled + bool use_implicit_batch() const { return use_implicit_batch_; } + // This should be called on the inputs and outputs of any layer we create // where we know that the quantization range does not change during that // operation. (e.g. Reshape, Transpose, Identity, MaxPool). @@ -530,7 +541,7 @@ class Converter { private: Converter(TrtPrecisionMode precision_mode, bool use_calibration, - nvinfer1::ILogger* trt_logger); + nvinfer1::ILogger* trt_logger, const bool use_implicit_batch); Status Init(nvinfer1::ILogger* trt_logger); @@ -591,6 +602,10 @@ class Converter { const bool use_calibration_; + // If this is false, all dimensions including the batch dimension are + // set explicitely. + const bool use_implicit_batch_; + // Batch size of inputs to trt_network_ added by AddInputTensor(). During // network construction it will update this, use it to verify the batch // size of all inputs are compatible, and make sure individual TF node is @@ -607,6 +622,7 @@ class Converter { Status GetTrtBroadcastShape(const TRT_TensorOrWeights& operand_l, const TRT_TensorOrWeights& operand_r, const bool check_feasibility, + const bool use_implicit_batch, nvinfer1::Dims* operand_l_new_dims, nvinfer1::Dims* operand_r_new_dims); diff --git a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc index ef03ab91714..358004abac7 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/convert_nodes_test.cc @@ -463,7 +463,8 @@ class ValidatorTest : public ::testing::Test { TF_EXPECT_OK(graph_properties.InferStatically(true)); TrtNodeValidator validator(graph_properties, TrtPrecisionMode::FP32, - /*use_calibration=*/false); + /*use_calibration=*/false, + /*use_implicit_batch=*/true); return validator.ConvertToTensorOrWeights(node->def(), output_port, tensor_or_weights); } @@ -477,7 +478,8 @@ TEST_F(ValidatorTest, QuantizeOpsAreRegistered) { grappler::GrapplerItem item; grappler::GraphProperties graph_properties(item); TrtNodeValidator validator(graph_properties, TrtPrecisionMode::FP32, - /*use_calibration=*/false); + /*use_calibration=*/false, + /*use_implicit_batch=*/true); for (const string& quantize_op : *GetQuantizeOps(&validator)) { QCHECK(op_validators(&validator).count(quantize_op)); } @@ -521,7 +523,7 @@ TEST_F(ValidatorTest, ConvertToTensorOrWeights) { "Scalar input tensor is not supported since the first dimension " "is treated as batch dimension by TRT"); } - // Convert non-Const. We test the case where the non-batch dimemsion is + // Convert non-Const. We test the case where the non-batch dimension is // unknown as well, to make sure the validator allows that. for (const int32 non_batch_dim : {-1, 2}) { const int32 batch_size = 12; @@ -547,7 +549,8 @@ TEST_F(ValidatorTest, IsTensorRTCandidate_Basics) { grappler::GraphProperties graph_properties(item); TF_EXPECT_OK(graph_properties.InferStatically(true)); TrtNodeValidator validator(graph_properties, TrtPrecisionMode::FP32, - /*use_calibration=*/false); + /*use_calibration=*/false, + /*use_implicit_batch=*/true); bool start_conversion = false; bool should_fail = false; @@ -626,7 +629,8 @@ TEST(TrtNodeValidator, IsTensorRTCandidate) { for (const TrtPrecisionMode precision_mode : {TrtPrecisionMode::FP32, TrtPrecisionMode::INT8}) { TrtNodeValidator validator(graph_properties, precision_mode, - /*use_calibration=*/false); + /*use_calibration=*/false, + /*use_implicit_batch=*/true); TF_EXPECT_OK(validator.IsTensorRTCandidate(matmul.operation.node())); ExpectStatus( validator.IsTensorRTCandidate(incompatible_matmul.operation.node()), @@ -656,7 +660,8 @@ class ConverterTest : public ::testing::Test { void Reset() { converter_ = std::move(Converter::Create(TrtPrecisionMode::FP32, - /*use_calibration=*/false, &logger_) + /*use_calibration=*/false, &logger_, + /*use_implicit_batch=*/true) .ValueOrDie()); weight_store_ = &converter_->weight_store_; } @@ -973,7 +978,7 @@ TEST_F(ConverterTest, GetWeightRange) { TEST_F(ConverterTest, ProvideQuantizationRange) { FakeITensor fake_tensor; - // Assymetric range + // Asymmetric range converter_->ProvideQuantizationRange(&fake_tensor, 0.0f, 6.0f); EXPECT_EQ(6.0f, quantization_ranges()[&fake_tensor]); converter_->ProvideQuantizationRange(&fake_tensor, 1.0f, 6.0f); @@ -993,7 +998,8 @@ TEST_F(ConverterTest, MaybeApplyQuantizationRanges) { FakeITensor not_infer; Logger logger; auto int8_converter = Converter::Create(TrtPrecisionMode::INT8, - /*use_calibration=*/true, &logger) + /*use_calibration=*/true, &logger, + /*use_implicit_batch=*/true) .ValueOrDie(); int8_converter->ProvideQuantizationRange(&input, -5.0f, 5.0f); int8_converter->ProvideQuantizationRange(¬_infer, -100.0f, 100.0f); @@ -1067,7 +1073,8 @@ TEST_F(ConverterTest, GetTrtBroadcastShape) { // operand_1 broadcast operand_2 ExpectStatus( GetTrtBroadcastShape(operand_1, operand_2, /*check_feasibility=*/true, - &operand_1_new_dims, &operand_2_new_dims), + /*use_implicit_batch=*/true, &operand_1_new_dims, + &operand_2_new_dims), expected_code, expected_error_msg_substr); if (expected_code == error::OK) { ExpectTrtDimsEqualsArray(expected_operand_1_shape, operand_1_new_dims); @@ -1076,7 +1083,8 @@ TEST_F(ConverterTest, GetTrtBroadcastShape) { // operand_2 broadcast operand_1 ExpectStatus( GetTrtBroadcastShape(operand_2, operand_1, /*check_feasibility=*/true, - &operand_2_new_dims, &operand_1_new_dims), + /*use_implicit_batch=*/true, &operand_2_new_dims, + &operand_1_new_dims), expected_code, expected_error_msg_substr); if (expected_code == error::OK) { ExpectTrtDimsEqualsArray(expected_operand_1_shape, operand_1_new_dims); @@ -1176,7 +1184,8 @@ class ConvertGraphDefToEngineTest : public ::testing::Test { gdef, TrtPrecisionMode::FP32, /*max_batch_size=*/1, /*max_workspace_size_bytes=*/64 << 20, input_shapes, &logger_, /*allocator=*/nullptr, /*calibrator=*/nullptr, &engine_, - /*use_calibration=*/false, /*convert_successfully=*/nullptr); + /*use_calibration=*/false, /*use_implicit_batch=*/true, + /*convert_successfully=*/nullptr); } protected: @@ -1251,7 +1260,8 @@ class OpConverterTest : public ::testing::Test { // Re-create them in proper order. converter_ = std::move(Converter::Create(precision_mode_to_test_, - /*use_calibration=*/false, &logger_) + /*use_calibration=*/false, &logger_, + /*use_implicit_batch=*/true) .ValueOrDie()); // Reset other related artifacts. @@ -1403,7 +1413,8 @@ class OpConverterTest : public ::testing::Test { TF_EXPECT_OK(graph_properties.InferStatically(true)); TrtNodeValidator validator(graph_properties, precision_mode_to_test_, - /*use_calibration=*/false); + /*use_calibration=*/false, + /*use_implicit_batch=*/true); ExpectStatus(validator.IsTensorRTCandidate(node), expected_code, expected_msg_substr); } diff --git a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc index bbe7d5f5191..757ddd159c9 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc +++ b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.cc @@ -70,6 +70,9 @@ Status TRTOptimizationPass::Init( if (params.count("trt_logger")) { trt_logger_name_ = params.at("trt_logger").s(); } + if (params.count("use_implicit_batch")) { + use_implicit_batch_ = params.at("use_implicit_batch").b(); + } return Status::OK(); } @@ -261,6 +264,7 @@ Status TRTOptimizationPass::Optimize(grappler::Cluster* cluster, cp.is_dyn_op = is_dynamic_op_; cp.max_cached_engines = max_cached_batches_; cp.use_calibration = use_calibration_; + cp.use_implicit_batch = use_implicit_batch_; auto status = ConvertAfterShapes(cp); VLOG(1) << "Returning from " << name_; return status; diff --git a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h index 93232635376..3ce0d09b7c0 100644 --- a/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h +++ b/tensorflow/compiler/tf2tensorrt/convert/trt_optimization_pass.h @@ -41,7 +41,8 @@ class TRTOptimizationPass : public grappler::CustomGraphOptimizer { is_dynamic_op_(false), max_cached_batches_(1), max_workspace_size_bytes_(256LL << 20), - use_calibration_(true) { + use_calibration_(true), + use_implicit_batch_(true) { VLOG(1) << "Constructing " << name_; } @@ -73,6 +74,7 @@ class TRTOptimizationPass : public grappler::CustomGraphOptimizer { int max_cached_batches_; int64_t max_workspace_size_bytes_; bool use_calibration_; + bool use_implicit_batch_; }; } // namespace convert diff --git a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc index f707cf75417..9adeed7faca 100644 --- a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc +++ b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op.cc @@ -125,7 +125,7 @@ class TRTEngineOp : public AsyncOpKernel { // Verify that the input shapes are consistent and can be handled by this op. Status VerifyInputShapes(const std::vector& shapes); - // Return engine batch in cached_engne_batch_sizes_ which is closest to input + // Return engine batch in cached_engine_batch_sizes_ which is closest to input // batch. Status GetEngineInputShapes( const CacheType& cache, @@ -154,6 +154,9 @@ class TRTEngineOp : public AsyncOpKernel { // Whether to calibrate INT8 engine. bool calibration_mode_; + // Whether to use implicit batch dimension for TensorRT + bool use_implicit_batch_; + // Maximum number of cached engines int max_cached_engines_; @@ -289,6 +292,13 @@ TRTEngineOp::TRTEngineOp(OpKernelConstruction* context) } OP_REQUIRES_OK(context, context->GetAttr("max_cached_engines_count", &max_cached_engines_)); + + auto status = context->GetAttr("_use_implicit_batch", &use_implicit_batch_); + if (status.code() == tensorflow::error::NOT_FOUND) { + VLOG(2) << "Not found _use_implicit_batch in " << context->device()->name() + << ", thus setting _use_implicit_batch=true"; + use_implicit_batch_ = true; + } } void TRTEngineOp::ExecuteNativeSegment(OpKernelContext* ctx, @@ -684,7 +694,10 @@ StatusOr TRTEngineOp::GetEngine( // single element containing the only engine. if (static_engine_) { if (cache.size()) { - if (AreShapesCompatible(input_shapes, cache.begin()->first)) { + // TODO(laigd): need a better shape compatibility check for the case where + // implicit batch is disabled. + if (!use_implicit_batch_ || + AreShapesCompatible(input_shapes, cache.begin()->first)) { return cache.begin()->second.get(); } return &empty_context; @@ -703,9 +716,10 @@ StatusOr TRTEngineOp::GetEngine( // Static engine will have max_batch_size for batch size so that all inputs // will map to this single engine. std::vector engine_input_shapes(input_shapes); - for (int i = 0; i < engine_input_shapes.size(); i++) { - // TODO(tmorris): will all inputs have batch size as first dimension?? - engine_input_shapes[i].set_dim(0, max_batch_size); + if (use_implicit_batch_) { + for (int i = 0; i < engine_input_shapes.size(); i++) { + engine_input_shapes[i].set_dim(0, max_batch_size); + } } // TODO(laigd): here we assume engine_input_shapes matches the actual input // shapes of the engine, we should verify that. @@ -720,7 +734,7 @@ StatusOr TRTEngineOp::GetEngine( string tmp; // Swap with temporary empty string to deallocate the CPU memory. serialized_segment_.swap(tmp); - if (max_batch_size < batch_size) { + if (use_implicit_batch_ && (max_batch_size < batch_size)) { return &empty_context; } return cache.at(engine_input_shapes).get(); @@ -749,7 +763,7 @@ StatusOr TRTEngineOp::GetEngine( auto status = convert::ConvertGraphDefToEngine( segment_graph_, precision_mode_, batch_size, workspace_size_, partial_shapes, &logger, allocator, calibrator_.get(), &engine, - use_calibration_, &convert_successfully); + use_calibration_, use_implicit_batch_, &convert_successfully); if (!status.ok()) { LOG(WARNING) << "Engine creation for " << name() << " failed. " << "The native segment will be used instead. " @@ -838,7 +852,7 @@ Status TRTEngineOp::AllocateCalibrationResources( cres->calibrator_->getBatchSize(), this->workspace_size_, partial_shapes, &cache_res->GetLogger(), cache_res->allocator_.get(), cres->calibrator_.get(), &cres->engine_, - /*use_calibration=*/true, + /*use_calibration=*/true, this->use_implicit_batch_, /*convert_successfully=*/nullptr); if (!s.ok()) { LOG(ERROR) << "Calibration failed: " << s; diff --git a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op_test.cc b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op_test.cc index 25de5fed705..86de5b70b06 100644 --- a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op_test.cc +++ b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_op_test.cc @@ -87,6 +87,7 @@ class TRTEngineOpTestBase : public OpsTestBase { .Attr("workspace_size_bytes", 1 << 20) .Attr("precision_mode", "FP32") .Attr("use_calibration", false) + .Attr("_use_implicit_batch", true) .Attr("OutT", {dtype}) .Finalize(OpsTestBase::node_def())); TF_ASSERT_OK(InitOpWithFunctionLibrary()); diff --git a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_resource_ops_test.cc b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_resource_ops_test.cc index 7a9b9f65fd8..c868416d048 100644 --- a/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_resource_ops_test.cc +++ b/tensorflow/compiler/tf2tensorrt/kernels/trt_engine_resource_ops_test.cc @@ -112,7 +112,7 @@ TEST_F(TRTEngineResourceOpsTest, Basic) { EXPECT_TRUE( errors::IsNotFound(rm->Lookup(container, resource_name, &resource))); - // Create the resouce using an empty file with InitializeTRTResource. + // Create the resource using an empty file with InitializeTRTResource. Reset(); Env* env = Env::Default(); const string filename = io::JoinPath(testing::TmpDir(), "trt_engine_file"); diff --git a/tensorflow/compiler/tf2tensorrt/segment/segment.cc b/tensorflow/compiler/tf2tensorrt/segment/segment.cc index 6d3920874aa..4d9dd42a53a 100644 --- a/tensorflow/compiler/tf2tensorrt/segment/segment.cc +++ b/tensorflow/compiler/tf2tensorrt/segment/segment.cc @@ -466,7 +466,7 @@ Status SegmentGraph(const Graph* tf_graph, // grow from the output-side of the network towards the inputs. // // In general this is not guaranteed to produce a globally optimal - // segmentation. For exaample, consider graph with node {A, B, C, D} and edges + // segmentation. For example, consider graph with node {A, B, C, D} and edges // {A->B, A->C, B->D, C->D), where A, B, D are trt compatible but C is not, so // in theory we can choose to contract either A, B or B, D but not both, but // here it always choose to contract B, D. diff --git a/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h b/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h index d2ea8ad38cf..06b39716490 100644 --- a/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h +++ b/tensorflow/compiler/tf2tensorrt/utils/trt_int8_calibrator.h @@ -34,7 +34,7 @@ namespace tensorrt { // TRTs pull model for calibration. When TRT implements a means for // a push calibration This class should be updated accordingly -// IInt8EntropyCalibrator2 is prefferred for TRT 5.1+. +// IInt8EntropyCalibrator2 is preferred for TRT 5.1+. #if NV_TENSORRT_MAJOR > 5 || (NV_TENSORRT_MAJOR == 5 && NV_TENSORRT_MINOR >= 1) struct TRTInt8Calibrator : public nvinfer1::IInt8EntropyCalibrator2 { #else diff --git a/tensorflow/compiler/tf2xla/BUILD b/tensorflow/compiler/tf2xla/BUILD index a2a0459bb4a..c509afbc33a 100644 --- a/tensorflow/compiler/tf2xla/BUILD +++ b/tensorflow/compiler/tf2xla/BUILD @@ -1,6 +1,6 @@ load("//tensorflow:tensorflow.bzl", "tf_cc_binary", "tf_cc_test", "tf_cuda_cc_test") load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load( diff --git a/tensorflow/compiler/tf2xla/functionalize_cond.cc b/tensorflow/compiler/tf2xla/functionalize_cond.cc index 0e614ca7ace..f9af5581a67 100644 --- a/tensorflow/compiler/tf2xla/functionalize_cond.cc +++ b/tensorflow/compiler/tf2xla/functionalize_cond.cc @@ -572,7 +572,7 @@ Status Conditional::ExtractBodies(Graph* graph) { if (visited.at(n->id())) continue; visited[n->id()] = true; - // Verify output edges and record control edges exitting scope. + // Verify output edges and record control edges exiting scope. for (const Edge* e : n->out_edges()) { Node* dst = e->dst(); if (IsMerge(dst)) continue; @@ -602,7 +602,7 @@ Status Conditional::ExtractBodies(Graph* graph) { } } - // Copying incomming edges to dst node. Iterate over a copy of the edges + // Copying incoming edges to dst node. Iterate over a copy of the edges // as they could be mutated during iteration. std::vector in_edges(n->in_edges().begin(), n->in_edges().end()); @@ -719,7 +719,7 @@ Status Conditional::ExtractBodies(Graph* graph) { ++index; // Connect the input to the merge_ with the retval, except if it is a - // Swich node, which is handled separately. + // Switch node, which is handled separately. for (auto e : m->in_edges()) { if (e->IsControlEdge()) continue; int branch_index = static_cast(find_branch(e)); @@ -1139,7 +1139,7 @@ StateMap::CondId FunctionalizeCond::StateAlongEdge(const Edge* e) { // node. If we don't record this into CondState, branches might have // incorrect CondState (e.g. if the branch only has a Const data node). // We set it to kNeither because there is no way to tell whether it's - // for true branch or false branch. This node's desendents might have + // for true branch or false branch. This node's descendents might have // other incoming edges with defined BranchType, and we correctly handle // merging kNeither with other defined BranchType in StateAlongEdge(). state[predicate] = BranchType::kNeither; diff --git a/tensorflow/compiler/tf2xla/functionalize_cond.h b/tensorflow/compiler/tf2xla/functionalize_cond.h index d85800fb8ee..7940732a11d 100644 --- a/tensorflow/compiler/tf2xla/functionalize_cond.h +++ b/tensorflow/compiler/tf2xla/functionalize_cond.h @@ -213,7 +213,7 @@ class FunctionalizeCond { // This populates the state_map_. Status DetermineStates(std::vector rev_topo_order); - // Determine the CondState for a given node using the incomming edges + // Determine the CondState for a given node using the incoming edges // to the node. Note: it is expected that this node's CondState is only // determined once its input's CondState is. Status DetermineCondState(Node* dst) { diff --git a/tensorflow/compiler/tf2xla/kernels/assert_op.cc b/tensorflow/compiler/tf2xla/kernels/assert_op.cc index 94543686b47..c40caa8fa10 100644 --- a/tensorflow/compiler/tf2xla/kernels/assert_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/assert_op.cc @@ -22,7 +22,7 @@ namespace tensorflow { namespace { -// This TensorFlow op supports the Assert primitve. +// This TensorFlow op supports the Assert primitive. class AssertOp : public XlaOpKernel { public: explicit AssertOp(OpKernelConstruction* ctx) : XlaOpKernel(ctx) {} diff --git a/tensorflow/compiler/tf2xla/kernels/gather_op.cc b/tensorflow/compiler/tf2xla/kernels/gather_op.cc index 320793e0e12..e8a3dab4bed 100644 --- a/tensorflow/compiler/tf2xla/kernels/gather_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/gather_op.cc @@ -257,7 +257,7 @@ class GatherOp : public XlaOpKernel { TF_DISALLOW_COPY_AND_ASSIGN(GatherOp); // The number of batch dimensions, as passed in the batch_dims attribute. - // It must be less than rank(indices). + // It must be less than or equal to rank(indices). int32 batch_dims_ = 0; }; diff --git a/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc b/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc index 507bc8d7a3b..67d49eafcde 100644 --- a/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc @@ -329,7 +329,7 @@ class MaxPoolGradOp : public XlaOpKernel { (padding_ == VALID) ? xla::Padding::kValid : xla::Padding::kSame; // Create a MaxPool operation to check the expected resulting shape, and - // then throw away the operation because we don't actually neeed it here. + // then throw away the operation because we don't actually need it here. TensorShape expected_out_shape; auto pooling = xla::MaxPool(ctx->Input(0), ksize_, stride_, xla_padding, diff --git a/tensorflow/compiler/tf2xla/kernels/training_ops.cc b/tensorflow/compiler/tf2xla/kernels/training_ops.cc index 3816cabc282..c288d613e29 100644 --- a/tensorflow/compiler/tf2xla/kernels/training_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/training_ops.cc @@ -530,9 +530,9 @@ class ResourceApplyAdam : public XlaOpKernel { // alpha <- learning_rate * sqrt(1 - beta2^t) / (1 - beta1^t) // m_t <- beta1 * m_{t-1} + (1 - beta1) * g_t // v_t <- beta2 * v_{t-1} + (1 - beta2) * g_t * g_t - // if use_nesterov: - // variable <- variable - alpha * m_t / (sqrt(v_t) + epsilon) // if not use_nesterov: + // variable <- variable - alpha * m_t / (sqrt(v_t) + epsilon) + // if use_nesterov: // variable <- variable - alpha * (m_t * beta1 + (1 - beta1) * g_t) / // (sqrt(v_t) + epsilon) diff --git a/tensorflow/compiler/tf2xla/kernels/unary_ops.cc b/tensorflow/compiler/tf2xla/kernels/unary_ops.cc index e6076907980..83a894e91fe 100644 --- a/tensorflow/compiler/tf2xla/kernels/unary_ops.cc +++ b/tensorflow/compiler/tf2xla/kernels/unary_ops.cc @@ -111,6 +111,11 @@ XLAJIT_MAKE_UNARY(Real, xla::Real(x)); XLAJIT_MAKE_UNARY(Imag, xla::Imag(x)); XLAJIT_MAKE_UNARY(Erf, xla::Erf(x)); XLAJIT_MAKE_UNARY(Erfc, xla::Erfc(x)); +XLAJIT_MAKE_UNARY(Erfinv, xla::ErfInv(x)); +// ndtri = sqrt(2) * erfinv(2 * x - 1) +XLAJIT_MAKE_UNARY(Ndtri, xla::ScalarLike(x, std::sqrt(2.0)) * + xla::ErfInv(xla::ScalarLike(x, 2.0) * x - + xla::ScalarLike(x, 1.0))); XLAJIT_MAKE_UNARY(Lgamma, xla::Lgamma(x)); XLAJIT_MAKE_UNARY(Digamma, xla::Digamma(x)); XLAJIT_MAKE_UNARY(BesselI0e, xla::BesselI0e(x)); diff --git a/tensorflow/compiler/tf2xla/kernels/xla_conv_op.cc b/tensorflow/compiler/tf2xla/kernels/xla_conv_op.cc index 0b5b66ae52f..7a8aec295a6 100644 --- a/tensorflow/compiler/tf2xla/kernels/xla_conv_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/xla_conv_op.cc @@ -37,7 +37,7 @@ class XlaConvOp : public XlaOpKernel { context, context->GetAttr("precision_config", &precision_config_attr)); OP_REQUIRES(context, precision_config_.ParsePartialFromString(precision_config_attr), - errors::InvalidArgument("Error parsing precison config.")); + errors::InvalidArgument("Error parsing precision config.")); } void Compile(XlaOpKernelContext* context) override { diff --git a/tensorflow/compiler/tf2xla/kernels/xla_svd_op.cc b/tensorflow/compiler/tf2xla/kernels/xla_svd_op.cc index a28ecd660ab..8e9ed35783f 100644 --- a/tensorflow/compiler/tf2xla/kernels/xla_svd_op.cc +++ b/tensorflow/compiler/tf2xla/kernels/xla_svd_op.cc @@ -32,7 +32,7 @@ class XlaSvdOp : public XlaOpKernel { ctx->GetAttr("precision_config", &precision_config_attr)); OP_REQUIRES(ctx, precision_config_.ParsePartialFromString(precision_config_attr), - errors::InvalidArgument("Error parsing precison config.")); + errors::InvalidArgument("Error parsing precision config.")); if (precision_config_.operand_precision_size() == 0) { precision_config_.mutable_operand_precision()->Add( xla::PrecisionConfig::HIGHEST); diff --git a/tensorflow/compiler/tf2xla/ops/xla_ops.cc b/tensorflow/compiler/tf2xla/ops/xla_ops.cc index dab051b39a8..33b740a706c 100644 --- a/tensorflow/compiler/tf2xla/ops/xla_ops.cc +++ b/tensorflow/compiler/tf2xla/ops/xla_ops.cc @@ -84,7 +84,7 @@ lower: a boolean specifies whether the calculation is done with the lower max_iter: maximum number of sweep update, i.e., the whole lower triangular part or upper triangular part based on parameter lower. Heuristically, it has - been argued that approximatly logN sweeps are needed in practice (Ref: Golub & + been argued that approximately logN sweeps are needed in practice (Ref: Golub & van Loan "Matrix Computation"). epsilon: the tolerance ratio. @@ -116,7 +116,7 @@ a: the input tensor. max_iter: maximum number of sweep update, i.e., the whole lower triangular part or upper triangular part based on parameter lower. Heuristically, it has - been argued that approximatly log(min (M, N)) sweeps are needed in practice + been argued that approximately log(min (M, N)) sweeps are needed in practice (Ref: Golub & van Loan "Matrix Computation"). epsilon: the tolerance ratio. @@ -610,7 +610,7 @@ REGISTER_OP("XlaDequantize") .SetShapeFn(shape_inference::UnknownShape) .Doc(R"doc( Takes the packed uint32 input and unpacks the input to uint8 to do -Dequantization on deivce. +Dequantization on device. input: Input tensors whose types is uint32, shape is [d0, ..., dn]. output: Output tensors whose types is bloat16. If transpose_output is true, @@ -644,7 +644,7 @@ REGISTER_OP("XlaEinsum") .Doc(R"doc( An op which supports basic einsum op with 2 inputs and 1 output. -This op has better TPU performnce since it doesn't have explicitly reshape and +This op has better TPU performance since it doesn't have explicitly reshape and transpose operations as tf.einsum does. )doc"); diff --git a/tensorflow/compiler/tf2xla/python/xla.py b/tensorflow/compiler/tf2xla/python/xla.py index 24f1e7b41ec..eff6f828aa2 100644 --- a/tensorflow/compiler/tf2xla/python/xla.py +++ b/tensorflow/compiler/tf2xla/python/xla.py @@ -81,7 +81,8 @@ ceil = _unary_op(math_ops.ceil) digamma = _unary_op(math_ops.digamma) erf = _unary_op(math_ops.erf) erfc = _unary_op(math_ops.erfc) -# TODO(phawkins): implement erfinv +erfinv = _unary_op(math_ops.erfinv) +ndtri = _unary_op(math_ops.ndtri) exp = _unary_op(math_ops.exp) expm1 = _unary_op(math_ops.expm1) floor = _unary_op(math_ops.floor) diff --git a/tensorflow/compiler/tf2xla/shape_util.h b/tensorflow/compiler/tf2xla/shape_util.h index e775c4462c3..331cfa38c1d 100644 --- a/tensorflow/compiler/tf2xla/shape_util.h +++ b/tensorflow/compiler/tf2xla/shape_util.h @@ -51,7 +51,7 @@ xla::Shape TensorShapeToXLAShape(xla::PrimitiveType type, // In case the input shape is a tuple, the minor-to-major values will be in the // order of the tuple elements within the tuple shape. // If a shape (or a subshape of a tuple shape) has missing layout, a rank long -// sequence of -1 values will be emittted. +// sequence of -1 values will be emitted. xla::StatusOr> GetShapeLayoutVector(const xla::Shape& shape); // Given the input shape and a linearized sequence of the minor-to-major values diff --git a/tensorflow/compiler/tf2xla/tf2xla.proto b/tensorflow/compiler/tf2xla/tf2xla.proto index 3093a0b1d8d..557f5bc3470 100644 --- a/tensorflow/compiler/tf2xla/tf2xla.proto +++ b/tensorflow/compiler/tf2xla/tf2xla.proto @@ -52,7 +52,7 @@ message Variable { TensorShapeProto shape = 3; DataType type = 4; - // Flag for variables that are never assigned. Assigments to a read-only + // Flag for variables that are never assigned. Assignments to a read-only // variable or unassigned variables that are not read-only are invalid. bool readonly = 5; } diff --git a/tensorflow/compiler/tf2xla/xla_compiler.cc b/tensorflow/compiler/tf2xla/xla_compiler.cc index defd96b570c..d69e40e93e2 100644 --- a/tensorflow/compiler/tf2xla/xla_compiler.cc +++ b/tensorflow/compiler/tf2xla/xla_compiler.cc @@ -758,9 +758,51 @@ Status XlaCompiler::CompileFunction( } // Computes the XLA shape for argument 'arg'. -Status XlaCompiler::XLAShapeForArgument(const XlaCompiler::Argument& arg, - bool is_entry_computation, - xla::Shape* xla_shape) const { +Status XlaCompiler::XLAShapeForArgument( + const XlaCompiler::Argument& arg, bool is_entry_computation, + const absl::optional& arg_sharding, + xla::Shape* xla_shape) const { + auto rewrite_layout_with_sharded_shape = + [](const absl::optional& arg_sharding, + bool use_fast_memory, + XlaCompiler::ShapeRepresentationFn shape_representation_fn, + xla::Shape* xla_shape) { + if (arg_sharding && !arg_sharding->IsTileMaximal()) { + // After parameter sharding, per core parameter might have different + // layout. For example, before sharding, a parameter of shape [128, + // 128] will be assigned default minor-to-major {1, 0}. But after we + // shard this parameter to [128, 64] * 2, the sharded parameters + // will have minor-to-major {0, 1}. + // + // As a result, for sharded parameters, we set their layout to per + // core parameter's layout. + // + // TODO(endlessroad): for variable input & update, we might have + // different layouts which will prevent input output aliasing and + // increase memory usage. Investigate such cases. + int64 device = *arg_sharding->tile_assignment().begin(); + std::vector offset = + arg_sharding->TileOffsetForDevice(*xla_shape, device); + std::vector limit = + arg_sharding->TileLimitForDevice(*xla_shape, device); + std::vector dimensions(xla_shape->rank()); + for (int64 i = 0; i < xla_shape->rank(); ++i) { + dimensions[i] = limit[i] - offset[i]; + } + xla::Shape per_device_xla_shape = + xla::ShapeUtil::MakeShape(xla_shape->element_type(), dimensions); + TensorShape per_device_tensor_shape; + TF_RETURN_IF_ERROR(XLAShapeToTensorShape(per_device_xla_shape, + &per_device_tensor_shape)); + TF_ASSIGN_OR_RETURN(DataType dtype, EncodePrimitiveTypeAsDataType( + xla_shape->element_type())); + TF_ASSIGN_OR_RETURN(per_device_xla_shape, + shape_representation_fn(per_device_tensor_shape, + dtype, use_fast_memory)); + *xla_shape->mutable_layout() = per_device_xla_shape.layout(); + } + return Status::OK(); + }; switch (arg.kind) { case XlaCompiler::Argument::kConstant: LOG(FATAL) << "Unreachable case"; @@ -776,6 +818,9 @@ Status XlaCompiler::XLAShapeForArgument(const XlaCompiler::Argument& arg, TF_ASSIGN_OR_RETURN(*xla_shape, options_.shape_representation_fn( shape, arg.type, /*use_fast_memory=*/false)); + TF_RETURN_IF_ERROR(rewrite_layout_with_sharded_shape( + arg_sharding, /*use_fast_memory=*/false, + options_.shape_representation_fn, xla_shape)); } else { if (absl::holds_alternative(arg.shape)) { *xla_shape = absl::get(arg.shape); @@ -801,6 +846,9 @@ Status XlaCompiler::XLAShapeForArgument(const XlaCompiler::Argument& arg, options_.shape_representation_fn( absl::get(arg.shape), arg.type, /*use_fast_memory=*/arg.fast_mem)); + TF_RETURN_IF_ERROR(rewrite_layout_with_sharded_shape( + arg_sharding, arg.fast_mem, options_.shape_representation_fn, + xla_shape)); return Status::OK(); } case XlaResource::kTensorArray: { @@ -939,8 +987,16 @@ Status XlaCompiler::BuildArguments( std::vector arg_shapes(input_to_args->size()); for (std::vector::size_type i = 0; i < input_to_args->size(); ++i) { // Computes the shapes of non-constant arguments. - TF_RETURN_IF_ERROR(XLAShapeForArgument( - args[(*input_to_args)[i]], is_entry_computation, &arg_shapes[i])); + auto arg_sharding = arg_shardings.find((*input_to_args)[i]); + absl::optional sharding; + if (arg_sharding != arg_shardings.end()) { + TF_ASSIGN_OR_RETURN(auto hlo_sharding, + xla::HloSharding::FromProto(arg_sharding->second)); + sharding = hlo_sharding; + } + TF_RETURN_IF_ERROR(XLAShapeForArgument(args[(*input_to_args)[i]], + is_entry_computation, sharding, + &arg_shapes[i])); } if (use_tuple_arg) { diff --git a/tensorflow/compiler/tf2xla/xla_compiler.h b/tensorflow/compiler/tf2xla/xla_compiler.h index 670da043c1a..1ae82c3ea54 100644 --- a/tensorflow/compiler/tf2xla/xla_compiler.h +++ b/tensorflow/compiler/tf2xla/xla_compiler.h @@ -381,8 +381,10 @@ class XlaCompiler { // Returns the shape of the XLA parameter for an argument 'arg'. // See the class comment for more details about the argument passing // convention. - Status XLAShapeForArgument(const Argument& arg, bool is_entry_computation, - xla::Shape* xla_shape) const; + Status XLAShapeForArgument( + const Argument& arg, bool is_entry_computation, + const absl::optional& arg_sharding, + xla::Shape* xla_shape) const; // Retrieves the channel handle associated with `key`. Allocates // a new channel handle if none exists. diff --git a/tensorflow/compiler/tf2xla/xla_op_kernel.h b/tensorflow/compiler/tf2xla/xla_op_kernel.h index 3e75cf7fa58..27b198f8bee 100644 --- a/tensorflow/compiler/tf2xla/xla_op_kernel.h +++ b/tensorflow/compiler/tf2xla/xla_op_kernel.h @@ -213,13 +213,13 @@ class XlaOpKernelContext { return dynamic_dimension_is_minus_one_; } - // Reads the current value of the resouce variable referred to by input + // Reads the current value of the resource variable referred to by input // `index`. If `shape` is not nullptr, sets `*shape` to the shape of the // variable. Returns an error if the variable has not been initialized, or if // its type does not match `type`. Status ReadVariableInput(int index, DataType type, TensorShape* shape, xla::XlaOp* value); - // Reads the current value of the resouce variable referred to by input + // Reads the current value of the resource variable referred to by input // `name`. Status ReadVariableInput(absl::string_view name, DataType type, TensorShape* shape, xla::XlaOp* value); diff --git a/tensorflow/compiler/xla/BUILD b/tensorflow/compiler/xla/BUILD index 095daf70498..4e2866865a2 100644 --- a/tensorflow/compiler/xla/BUILD +++ b/tensorflow/compiler/xla/BUILD @@ -522,6 +522,7 @@ cc_library( cc_library( name = "array", + srcs = ["array.cc"], hdrs = ["array.h"], deps = [ ":status", diff --git a/tensorflow/compiler/xla/array.cc b/tensorflow/compiler/xla/array.cc new file mode 100644 index 00000000000..bd958fc0d18 --- /dev/null +++ b/tensorflow/compiler/xla/array.cc @@ -0,0 +1,32 @@ +/* Copyright 2019 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/array.h" + +namespace xla { + +// Specialization of FillRandom() method for complex64 type. Uses real part of +// the stddev parameter as the standard deviation value. +template <> +void Array::FillRandom(const complex64& stddev, const double mean, + const int seed) { + std::mt19937 g(seed); + std::normal_distribution distribution(mean, std::real(stddev)); + for (int64 i = 0; i < num_elements(); ++i) { + values_[i] = complex64(distribution(g), distribution(g)); + } +} + +} // namespace xla diff --git a/tensorflow/compiler/xla/array.h b/tensorflow/compiler/xla/array.h index 2ab45c75049..67bad0f8af7 100644 --- a/tensorflow/compiler/xla/array.h +++ b/tensorflow/compiler/xla/array.h @@ -575,6 +575,12 @@ class Array { std::unique_ptr values_; }; +// Specialization of FillRandom() method for complex64 type. Uses real part of +// the stddev parameter as the standard deviation value. +template <> +void Array::FillRandom(const complex64& stddev, const double mean, + const int seed); + } // namespace xla #endif // TENSORFLOW_COMPILER_XLA_ARRAY_H_ diff --git a/tensorflow/compiler/xla/client/lib/comparators_test.cc b/tensorflow/compiler/xla/client/lib/comparators_test.cc index 598956803b3..d6e5d80b85f 100644 --- a/tensorflow/compiler/xla/client/lib/comparators_test.cc +++ b/tensorflow/compiler/xla/client/lib/comparators_test.cc @@ -73,7 +73,7 @@ void BuildComparatorAndComparisons(ComparatorsTest* test, } } - // Concantenate the comparison results. + // Concatenate the comparison results. ConcatInDim(test->builder(), all_comparisons, 0); // If we use less-than comparisons, we expect the comparison to result in true diff --git a/tensorflow/compiler/xla/client/lib/matrix.cc b/tensorflow/compiler/xla/client/lib/matrix.cc index d2275af5ca4..3f4a63c31be 100644 --- a/tensorflow/compiler/xla/client/lib/matrix.cc +++ b/tensorflow/compiler/xla/client/lib/matrix.cc @@ -316,7 +316,7 @@ Status ValidateEinsumNumericDimensions(absl::Span x_config, namespace { // Helper method to remove dimensions from a shape and dot dimension numbers -// used to implment implicit broadcasting. +// used to implement implicit broadcasting. template void DeleteDimsFromContainer(absl::Span to_delete, Shape* shape, C* batch_dims, C* contracting_dims) { @@ -473,7 +473,7 @@ xla::XlaOp Einsum(xla::XlaOp x, absl::Span x_config, xla::XlaOp y, transpose_dims[output_transpose_dims[i]] = i; } - // Remove ones that where broadcated from the x and the y shape and adjust + // Remove ones that where broadcasted from the x and the y shape and adjust // the dimension numbers that are more minor than those dimensions. DeleteDimsFromContainer(lhs_delete_dims, &x_shape, dnums.mutable_lhs_batch_dimensions(), diff --git a/tensorflow/compiler/xla/client/lib/matrix.h b/tensorflow/compiler/xla/client/lib/matrix.h index 6377704c58c..46f70ed27b9 100644 --- a/tensorflow/compiler/xla/client/lib/matrix.h +++ b/tensorflow/compiler/xla/client/lib/matrix.h @@ -132,7 +132,7 @@ xla::XlaOp Einsum( // the input. xla::XlaOp EinsumDiagonal(XlaOp x, absl::Span config); -// Same as above but supporting numeric labels on dimensins. So "ab,cb->ac" +// Same as above but supporting numeric labels on dimensions. So "ab,cb->ac" // becomes: // x_config = {0, 1} // y_config = {2, 1} diff --git a/tensorflow/compiler/xla/client/lib/pooling.cc b/tensorflow/compiler/xla/client/lib/pooling.cc index 1979c867a4c..45033ec07e7 100644 --- a/tensorflow/compiler/xla/client/lib/pooling.cc +++ b/tensorflow/compiler/xla/client/lib/pooling.cc @@ -39,7 +39,7 @@ XlaOp AvgPoolDivideByCountWithGeneralPadding( std::vector window_ksize(num_spatial_dims); std::vector window_stride(num_spatial_dims); CHECK_EQ(data_format.num_spatial_dims(), num_spatial_dims) - << "Invalid number of spatial dimentions in data format specification"; + << "Invalid number of spatial dimensions in data format specification"; for (int i = 0; i < num_spatial_dims; ++i) { int dim = data_format.spatial_dimension(i); input_dim_sizes[i] = input_shape[dim]; @@ -95,7 +95,7 @@ PaddingConfig MakeSpatialPaddingConfig( padding_config.add_dimensions(); } CHECK_EQ(data_format.num_spatial_dims(), num_spatial_dims) - << "Invalid number of spatial dimentions in data format specification"; + << "Invalid number of spatial dimensions in data format specification"; for (int i = 0; i < num_spatial_dims; ++i) { int dim = data_format.spatial_dimension(i); auto padding_dimension = padding_config.mutable_dimensions(dim); @@ -178,7 +178,7 @@ std::vector> MakeSpatialPadding( std::vector kernel_size_spatial_dimensions; std::vector stride_spatial_dimensions; CHECK_EQ(data_format.num_spatial_dims(), num_spatial_dims) - << "Invalid number of spatial dimentions in data format specification"; + << "Invalid number of spatial dimensions in data format specification"; for (int i = 0; i < num_spatial_dims; ++i) { int dim = data_format.spatial_dimension(i); input_spatial_dimensions.push_back(input_size[dim]); diff --git a/tensorflow/compiler/xla/client/lib/slicing.cc b/tensorflow/compiler/xla/client/lib/slicing.cc index b47ddb7919f..7d8f433bac8 100644 --- a/tensorflow/compiler/xla/client/lib/slicing.cc +++ b/tensorflow/compiler/xla/client/lib/slicing.cc @@ -154,29 +154,29 @@ XlaOp TorchGather(XlaOp input, XlaOp index, int64 dim, bool sparse) { return TorchIndexSelect(input, index, 0); } if (!sparse) { - std::vector index_broacast_dims; - std::vector input_broacast_dims; + std::vector index_broadcast_dims; + std::vector input_broadcast_dims; std::vector sizes; for (int64 i = 0; i < index_shape.rank(); ++i) { if (i < dim) { - input_broacast_dims.push_back(i); - index_broacast_dims.push_back(i); + input_broadcast_dims.push_back(i); + index_broadcast_dims.push_back(i); } else if (i == dim) { sizes.push_back(input_shape.dimensions(i)); - input_broacast_dims.push_back(i); - index_broacast_dims.push_back(i + 1); + input_broadcast_dims.push_back(i); + index_broadcast_dims.push_back(i + 1); } else { - input_broacast_dims.push_back(i + 1); - index_broacast_dims.push_back(i + 1); + input_broadcast_dims.push_back(i + 1); + index_broadcast_dims.push_back(i + 1); } sizes.push_back(index_shape.dimensions(i)); } auto mask = Eq( - BroadcastInDim(index, sizes, index_broacast_dims), + BroadcastInDim(index, sizes, index_broadcast_dims), Iota(builder, ShapeUtil::MakeShape(index_shape.element_type(), sizes), dim)); auto masked_input = Select( - mask, BroadcastInDim(input, sizes, input_broacast_dims), + mask, BroadcastInDim(input, sizes, input_broadcast_dims), Zeros(builder, ShapeUtil::MakeShape(input_shape.element_type(), sizes))); return Reduce(masked_input, Zero(builder, input_shape.element_type()), @@ -214,25 +214,25 @@ XlaOp TorchScatterDense(XlaOp input, XlaOp index, XlaOp src, int64 dim, return builder->ReportErrorOrReturn([&]() -> StatusOr { TF_ASSIGN_OR_RETURN(Shape index_shape, builder->GetShape(index)); TF_ASSIGN_OR_RETURN(Shape input_shape, builder->GetShape(input)); - std::vector index_broacast_dims; + std::vector index_broadcast_dims; std::vector sizes; for (int64 i = 0; i < index_shape.rank(); ++i) { if (i < dim) { - index_broacast_dims.push_back(i); + index_broadcast_dims.push_back(i); } else { if (i == dim) { sizes.push_back(input_shape.dimensions(i)); } - index_broacast_dims.push_back(i + 1); + index_broadcast_dims.push_back(i + 1); } sizes.push_back(index_shape.dimensions(i)); } auto mask = - Eq(BroadcastInDim(index, sizes, index_broacast_dims), + Eq(BroadcastInDim(index, sizes, index_broadcast_dims), Iota(builder, ShapeUtil::MakeShape(index_shape.element_type(), sizes), dim)); auto masked_src = - Select(mask, BroadcastInDim(src, sizes, index_broacast_dims), + Select(mask, BroadcastInDim(src, sizes, index_broadcast_dims), Zeros(builder, ShapeUtil::MakeShape(input_shape.element_type(), sizes))); diff --git a/tensorflow/compiler/xla/client/lib/testing.cc b/tensorflow/compiler/xla/client/lib/testing.cc index 9f520bcdadf..5e177cd391e 100644 --- a/tensorflow/compiler/xla/client/lib/testing.cc +++ b/tensorflow/compiler/xla/client/lib/testing.cc @@ -98,7 +98,7 @@ std::vector> MakeFakeArgumentsOrDie( const XlaComputation& computation, Client* client, DebugOptions* debug_opts /*=nullptr*/) { CHECK(computation.proto().has_host_program_shape()) - << "Computation should have progran shape."; + << "Computation should have program shape."; auto program_shape = computation.proto().host_program_shape(); std::vector> results; diff --git a/tensorflow/compiler/xla/client/local_client.cc b/tensorflow/compiler/xla/client/local_client.cc index 153cb9f5212..a72c59ea255 100644 --- a/tensorflow/compiler/xla/client/local_client.cc +++ b/tensorflow/compiler/xla/client/local_client.cc @@ -329,15 +329,14 @@ StatusOr LocalClient::ReplicaNumberToDeviceOrdinal(int replica_number) { } StatusOr LocalClient::TransferToLocalServer( - const ::xla::BorrowingLiteral& literal, int device_oridinal) { + const ::xla::BorrowingLiteral& literal, int device_ordinal) { const ::xla::Shape& shape = literal.shape(); - TF_ASSIGN_OR_RETURN( - ::xla::ScopedShapedBuffer shaped_buffer, - backend().transfer_manager()->AllocateScopedShapedBuffer( - shape, backend().memory_allocator(), device_oridinal)); + TF_ASSIGN_OR_RETURN(::xla::ScopedShapedBuffer shaped_buffer, + backend().transfer_manager()->AllocateScopedShapedBuffer( + shape, backend().memory_allocator(), device_ordinal)); TF_ASSIGN_OR_RETURN(auto stream, - mutable_backend()->BorrowStream(device_oridinal)); + mutable_backend()->BorrowStream(device_ordinal)); TF_RETURN_IF_ERROR(backend().transfer_manager()->TransferLiteralToDevice( stream.get(), literal, shaped_buffer)); std::vector<::xla::ScopedShapedBuffer> replicated_buffer; diff --git a/tensorflow/compiler/xla/client/local_client.h b/tensorflow/compiler/xla/client/local_client.h index f5e66c6d586..221a911567c 100644 --- a/tensorflow/compiler/xla/client/local_client.h +++ b/tensorflow/compiler/xla/client/local_client.h @@ -122,7 +122,7 @@ class LocalClient : public Client { // Transfer the BorrowingLiteral to the device with the given ordinal. StatusOr TransferToLocalServer( - const ::xla::BorrowingLiteral& literal, int device_oridinal); + const ::xla::BorrowingLiteral& literal, int device_ordinal); // Copy the data from the device contained in the given ShapedBuffer and // return as a Literal. diff --git a/tensorflow/compiler/xla/client/xla_builder.h b/tensorflow/compiler/xla/client/xla_builder.h index 5e93bb2b3ba..42126306996 100644 --- a/tensorflow/compiler/xla/client/xla_builder.h +++ b/tensorflow/compiler/xla/client/xla_builder.h @@ -232,17 +232,17 @@ class XlaBuilder { // added operation. // // `remove_dynamic_dimensions` tells the builder whether to remove the - // dyanmic dimensions information in all ops. + // dynamic dimensions information in all ops. // // TODO(b/121223198): Delete `remove_dynamic_dimensions` and keeps the // dynamic dimensions information when XLA backend can handle dynamic // dimensions. - StatusOr Build(bool remove_dynamic_dimensions = true); + StatusOr Build(bool remove_dynamic_dimensions = false); // Overload of Build which specifies a particular root instruction for the // computation. StatusOr Build(XlaOp root, - bool remove_dynamic_dimensions = true); + bool remove_dynamic_dimensions = false); // Builds the computation with the requested operations, or notes an error in // the parent XlaBuilder and returns an empty computation if building failed. @@ -1194,7 +1194,7 @@ XlaOp Broadcast(XlaOp operand, absl::Span broadcast_sizes); // // For example, say operand = {1, 2}, i.e., a 1D tensor in shape s32[2]; the // output shape is s32[2,2]: -// - Specifying {1} as brodcast_dimension will generate output +// - Specifying {1} as broadcast_dimension will generate output // {{1, 2}, // {1, 2}} // - On the other hand, specifying {0} as broadcast_dimension @@ -1469,7 +1469,7 @@ XlaOp TriangularSolve(XlaOp a, XlaOp b, bool left_side, bool lower, // two minor dimensions equal. // If `lower` is true, the data from the lower triangle is used; if false, the // upper triangle is used. The input data in the other triangle of the input -// does not affect the output. Returns the output in the same lower/uppper +// does not affect the output. Returns the output in the same lower/upper // triangle. The data returned in the other output triangle is arbitrary and // implementation-defined. // diff --git a/tensorflow/compiler/xla/client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_builder_test.cc index f76ea38e08e..fd227ea47f2 100644 --- a/tensorflow/compiler/xla/client/xla_builder_test.cc +++ b/tensorflow/compiler/xla/client/xla_builder_test.cc @@ -292,7 +292,7 @@ TEST_F(XlaBuilderTest, BinopHasInDimAndDegenerateBroadcast) { TF_ASSERT_OK_AND_ASSIGN(auto module, BuildHloModule(&b)); // The binary operation has in-dim broadcast and degenerate broadcast, should - // first do the in-dim broadcast then convert the degnerate broadcast into a + // first do the in-dim broadcast then convert the degenerate broadcast into a // reshape and a broadcast. // // Expected: diff --git a/tensorflow/compiler/xla/debug_options_flags.cc b/tensorflow/compiler/xla/debug_options_flags.cc index 064d8cd8d8b..16c83ab9b2c 100644 --- a/tensorflow/compiler/xla/debug_options_flags.cc +++ b/tensorflow/compiler/xla/debug_options_flags.cc @@ -328,7 +328,7 @@ static void AllocateFlags() { "use multi-threaded Eigen mode."), tensorflow::Flag("xla_gpu_cuda_data_dir", flag_values->mutable_xla_gpu_cuda_data_dir(), - "If non-empty, speficies a local directory containing " + "If non-empty, specifies a local directory containing " "ptxas and nvvm libdevice files; otherwise we use " "those from runfile directories."), tensorflow::Flag("xla_gpu_ftz", @@ -347,7 +347,7 @@ static void AllocateFlags() { flag_values->xla_gpu_max_kernel_unroll_factor(), "Specify the maximum kernel unroll factor for the GPU backend."), tensorflow::Flag("xla_gpu_ptx_file", setter_for_xla_gpu_ptx_file, "", - "If non-empty, speficies a file containing ptx to use. " + "If non-empty, specifies a file containing ptx to use. " "The filename prefix must have the same pattern as PTX " "dumped by XLA. This allows to match one specific " "module. General workflow. Get the generated module " diff --git a/tensorflow/compiler/xla/debug_options_flags.h b/tensorflow/compiler/xla/debug_options_flags.h index 1675b377edf..069e36dc52a 100644 --- a/tensorflow/compiler/xla/debug_options_flags.h +++ b/tensorflow/compiler/xla/debug_options_flags.h @@ -52,7 +52,7 @@ DebugOptions DefaultDebugOptionsIgnoringFlags(); // By default all passes have infinite fuel. You can restrict how much fuel a // pass has by specifying XLA_FLAGS=--xla_fuel=PASS1=NUM1,PASS2=NUM2,... // -// If a user specifes --xla_fuel=PASS=NUM but ConsumeFuel(PASS) is not called +// If a user specifies --xla_fuel=PASS=NUM but ConsumeFuel(PASS) is not called // before the program exits, we'll print a warning. // // We recommend as a convention you use a pass's name for the `pass` argument, @@ -91,7 +91,7 @@ bool ConsumeFuel(absl::string_view pass, // startup. // // You may call this function twice in the same thread to reset its fuel pool -// back to the intitial state. +// back to the initial state. void ResetThreadLocalFuel(); } // namespace xla diff --git a/tensorflow/compiler/xla/execution_options_util.h b/tensorflow/compiler/xla/execution_options_util.h index a8ca27ec8df..7bb817b8f1d 100644 --- a/tensorflow/compiler/xla/execution_options_util.h +++ b/tensorflow/compiler/xla/execution_options_util.h @@ -21,7 +21,7 @@ limitations under the License. namespace xla { // Create a default ExecutionOptions proto; this proto has its debug options -// popupated to the default values taken from flags. +// populated to the default values taken from flags. ExecutionOptions CreateDefaultExecutionOptions(); } // namespace xla diff --git a/tensorflow/compiler/xla/g3doc/operation_semantics.md b/tensorflow/compiler/xla/g3doc/operation_semantics.md index 8cf8022340a..ee7b2b20928 100644 --- a/tensorflow/compiler/xla/g3doc/operation_semantics.md +++ b/tensorflow/compiler/xla/g3doc/operation_semantics.md @@ -94,7 +94,7 @@ The participating cores can be configured by: in the same order of 1, 2, 3. Then, another AllToAll will be applied within replicas 4, 5, 0, and the concatenation order is also 4, 5, 0. If `replica_groups` is empty, all replicas belong to one group, in the - concatenation order of their appearence. + concatenation order of their appearance. Prerequisites: diff --git a/tensorflow/compiler/xla/literal.cc b/tensorflow/compiler/xla/literal.cc index bbd640f6064..3d6310c1e17 100644 --- a/tensorflow/compiler/xla/literal.cc +++ b/tensorflow/compiler/xla/literal.cc @@ -248,7 +248,7 @@ Status MutableLiteralBase::CopySliceFromInternal( TF_RET_CHECK(src_base.size() == copy_size.size()); // Scan the source from minor, stepping in copy size blocks, then within - // the index enumaration functor, do a strided copy advancing source index + // the index enumeration functor, do a strided copy advancing source index // by one (walking through the minor dimension), and destination index by // proper stride size at the matching dimension. DimensionVector src_indexes(src_base.size(), 0); diff --git a/tensorflow/compiler/xla/literal.h b/tensorflow/compiler/xla/literal.h index 227717188ab..2d27f8eb7f6 100644 --- a/tensorflow/compiler/xla/literal.h +++ b/tensorflow/compiler/xla/literal.h @@ -810,7 +810,7 @@ class Literal : public MutableLiteralBase { Literal(const Shape& shape, bool allocate_arrays); Literal& operator=(Literal&& other); - // Similar to CopyFrom, but with move semantincs. The subshape of this literal + // Similar to CopyFrom, but with move semantics. The subshape of this literal // rooted at 'dest_shape_index' must be *equal* to the shape 'src_literal' // (layouts and shapes must match), but need not be arrays. The memory // allocated in this literal for the subshape at dest_shape_index is @@ -883,7 +883,7 @@ class BorrowingLiteral : public LiteralBase { BorrowingLiteral() : LiteralBase() {} // 'src_buf_ptr' is not owned by this class and must outlive the - // lifetime of this class. It points to an appropirately sized buffer with + // lifetime of this class. It points to an appropriately sized buffer with // data interpretered as indicated by 'shape'. // This constructor is only used for array shapes. BorrowingLiteral(const char* src_buf_ptr, const Shape& shape); diff --git a/tensorflow/compiler/xla/literal_comparison.cc b/tensorflow/compiler/xla/literal_comparison.cc index 662aeead14e..e1f52f72e5d 100644 --- a/tensorflow/compiler/xla/literal_comparison.cc +++ b/tensorflow/compiler/xla/literal_comparison.cc @@ -433,7 +433,7 @@ class NearComparator { } } else if (IsInf(expected) || IsInf(actual)) { // If either the expected or actual value is infinity but not both, - // then both absolute and relative error are regarded as inifity. + // then both absolute and relative error are regarded as infinity. CHECK(!CompareEqual(expected, actual, {linear_index})); abs_error = std::numeric_limits::infinity(); rel_error = std::numeric_limits::infinity(); diff --git a/tensorflow/compiler/xla/literal_test.cc b/tensorflow/compiler/xla/literal_test.cc index d1dd6b8fd77..9b17cb762c8 100644 --- a/tensorflow/compiler/xla/literal_test.cc +++ b/tensorflow/compiler/xla/literal_test.cc @@ -1134,7 +1134,7 @@ TEST_F(LiteralUtilTest, CopyFromDifferentShapes) { TEST_F(LiteralUtilTest, F16) { // Verify that the internal data views are consistent and that they // are in little endian format - // TODO - modify if we make the data format machine endianess dependent + // TODO - modify if we make the data format machine endianness dependent Literal m1 = Literal::CreateFromShape(ShapeUtil::MakeShape(F16, {2, 2})); const char* d1 = reinterpret_cast(m1.data().data()); EXPECT_EQ(d1[0], 0); diff --git a/tensorflow/compiler/xla/literal_util.cc b/tensorflow/compiler/xla/literal_util.cc index 70dc386eb14..e342e7a9bdb 100644 --- a/tensorflow/compiler/xla/literal_util.cc +++ b/tensorflow/compiler/xla/literal_util.cc @@ -98,6 +98,11 @@ Literal ConvertType(LiteralSlice literal) { return ConvertType(f32_literal); } +/* static */ Literal LiteralUtil::ConvertF64ToBF16( + const LiteralSlice& f64_literal) { + return ConvertType(f64_literal); +} + /* static */ Literal LiteralUtil::CreateToken() { return Literal(ShapeUtil::MakeTokenShape()); } diff --git a/tensorflow/compiler/xla/literal_util.h b/tensorflow/compiler/xla/literal_util.h index 1f1e378ff9c..c4535badafa 100644 --- a/tensorflow/compiler/xla/literal_util.h +++ b/tensorflow/compiler/xla/literal_util.h @@ -264,6 +264,11 @@ class LiteralUtil { // recursively converts its elements. static Literal ConvertF32ToBF16(const LiteralSlice& f32_literal); + // If the given literal's data type is double, converts it to a bfloat16 + // literal; otherwise, returns a copy of it. If the literal is a tuple, + // recursively converts its elements. + static Literal ConvertF64ToBF16(const LiteralSlice& f64_literal); + // Creates a literal with a new shape with the given new dimensions using the // data in the given input literal. For reshaping purposes the (flat) data // buffer of the input literal is assumed to have the given minor_to_major diff --git a/tensorflow/compiler/xla/parse_flags_from_env.h b/tensorflow/compiler/xla/parse_flags_from_env.h index 76940a4299a..18d9788cde4 100644 --- a/tensorflow/compiler/xla/parse_flags_from_env.h +++ b/tensorflow/compiler/xla/parse_flags_from_env.h @@ -30,7 +30,7 @@ limitations under the License. // - in which case the effective value is the // string with the single-quotes removed -// - in which case the effective value if the // string with the double-quotes removed, and escaped sequences of // replaced by . diff --git a/tensorflow/compiler/xla/parse_flags_from_env_test.cc b/tensorflow/compiler/xla/parse_flags_from_env_test.cc index 3465552ebbf..32f27449b22 100644 --- a/tensorflow/compiler/xla/parse_flags_from_env_test.cc +++ b/tensorflow/compiler/xla/parse_flags_from_env_test.cc @@ -73,14 +73,14 @@ static const char kTestFlagString[] = "--single_quoted='single quoted \\\\ \n \"' " "--double_quoted=\"double quoted \\\\ \n '\\\"\" "; -// Test that the environent variable is parsed correctly. +// Test that the environment variable is parsed correctly. TEST(ParseFlagsFromEnv, Basic) { // Prepare environment. setenv("TF_XLA_FLAGS", kTestFlagString, true /*overwrite*/); TestParseFlagsFromEnv("(flags in environment variable)"); } -// Test that a file named by the environent variable is parsed correctly. +// Test that a file named by the environment variable is parsed correctly. TEST(ParseFlagsFromEnv, File) { // environment variables where tmp dir may be specified. static const char* kTempVars[] = {"TEST_TMPDIR", "TMP"}; @@ -154,7 +154,7 @@ int main(int argc, char* argv[]) { xla::int32 int_flag = 1; const std::vector flag_list = { tensorflow::Flag("recursing", &recursing, - "Whether the binary is being invoked recusively."), + "Whether the binary is being invoked recursively."), tensorflow::Flag("int_flag", &int_flag, "An integer flag to test with"), }; xla::string usage = tensorflow::Flags::Usage(argv[0], flag_list); diff --git a/tensorflow/compiler/xla/python/local_client.cc b/tensorflow/compiler/xla/python/local_client.cc index ef8ff4275a6..d0bb1eb8015 100644 --- a/tensorflow/compiler/xla/python/local_client.cc +++ b/tensorflow/compiler/xla/python/local_client.cc @@ -551,7 +551,7 @@ PyLocalBuffer::DestructureTuple() { absl::MutexLock lock(&mu_); if (!on_host_shape_.IsTuple()) { return InvalidArgument( - "Attemped to destructure a PyLocalBuffer that did not have a tuple " + "Attempted to destructure a PyLocalBuffer that did not have a tuple " "shape; shape: %s", ShapeUtil::HumanString(on_host_shape_)); } diff --git a/tensorflow/compiler/xla/python/tpu_driver/client/tpu_client.cc b/tensorflow/compiler/xla/python/tpu_driver/client/tpu_client.cc index b9ca2a7e1a7..2b69239bb7a 100644 --- a/tensorflow/compiler/xla/python/tpu_driver/client/tpu_client.cc +++ b/tensorflow/compiler/xla/python/tpu_driver/client/tpu_client.cc @@ -345,7 +345,7 @@ PyTpuBuffer::DestructureTuple() { tensorflow::profiler::TraceMe traceme("PyTpuBuffer::DestructureTuple"); if (!on_host_shape_.IsTuple()) { return InvalidArgument( - "Attemped to destructure a PyTpuBuffer that did not have a tuple " + "Attempted to destructure a PyTpuBuffer that did not have a tuple " "shape; shape: %s", ShapeUtil::HumanString(on_host_shape_)); } diff --git a/tensorflow/compiler/xla/python/tpu_driver/tpu_driver.h b/tensorflow/compiler/xla/python/tpu_driver/tpu_driver.h index 36b7fa0d801..dc28ad1f0b4 100644 --- a/tensorflow/compiler/xla/python/tpu_driver/tpu_driver.h +++ b/tensorflow/compiler/xla/python/tpu_driver/tpu_driver.h @@ -37,7 +37,7 @@ #include "tensorflow/compiler/xla/xla_data.pb.h" #include "tensorflow/core/platform/logging.h" -// This API is EXPERIMENTAL and under active developement. It is subject to +// This API is EXPERIMENTAL and under active development. It is subject to // change without notice. namespace tpu_driver { diff --git a/tensorflow/compiler/xla/python/xla.cc b/tensorflow/compiler/xla/python/xla.cc index 13968154188..f1776763796 100644 --- a/tensorflow/compiler/xla/python/xla.cc +++ b/tensorflow/compiler/xla/python/xla.cc @@ -442,30 +442,6 @@ PYBIND11_MODULE(xla_extension, m) { std::move(leaves), tree.shape, std::move(py_buffer_ref), std::move(client), device->local_device_ordinal()); }) - // TODO(skyewm): get rid of this overload once everyone passes Device - .def_static( - "from_python", - [](const pybind11::object& argument, - std::shared_ptr client, - int device_ordinal) -> StatusOr> { - GlobalPyRefManager()->CollectGarbage(); - TF_ASSIGN_OR_RETURN(PythonBufferTree tree, - GetPythonBufferTree(argument)); - std::shared_ptr py_buffer_ref = - GlobalPyRefManager()->ManageReferences( - absl::MakeSpan(tree.arrays)); - tree.arrays.clear(); - - std::vector leaves; - leaves.insert(leaves.end(), - std::make_move_iterator(tree.leaves.begin()), - std::make_move_iterator(tree.leaves.end())); - - py::gil_scoped_release gil_release; - return PyLocalBuffer::FromLiterals( - std::move(leaves), tree.shape, std::move(py_buffer_ref), - std::move(client), device_ordinal); - }) .def_static("make_tuple", [](const std::vector buffers, std::shared_ptr client, @@ -481,8 +457,6 @@ PYBIND11_MODULE(xla_extension, m) { return PyLocalBuffer::MakeTuple( buffers, client, device->local_device_ordinal()); }) - // TODO(skyewm): get rid of this overload once everyone passes Device - .def_static("make_tuple", &PyLocalBuffer::MakeTuple) .def("copy_to_device", [](PyLocalBuffer* buffer, std::shared_ptr dst_device) { CHECK(dst_device != nullptr); @@ -490,13 +464,6 @@ PYBIND11_MODULE(xla_extension, m) { py::gil_scoped_release gil_release; return buffer->CopyToDevice(dst_device->local_device_ordinal()); }) - // TODO(skyewm): get rid of this overload once everyone passes Device - .def("copy_to_device", - [](PyLocalBuffer* buffer, int dst_device_ordinal) { - GlobalPyRefManager()->CollectGarbage(); - py::gil_scoped_release gil_release; - return buffer->CopyToDevice(dst_device_ordinal); - }) .def("delete", &PyLocalBuffer::Delete) .def("destructure", &PyLocalBuffer::DestructureTuple) .def("block_host_until_ready", @@ -522,8 +489,6 @@ PYBIND11_MODULE(xla_extension, m) { [](PyLocalBuffer* buffer) -> std::shared_ptr { return buffer->client()->local_devices()[buffer->device_ordinal()]; }) - // TODO(skyewm): get rid of `device_ordinal` once everything uses `device` - .def("device_ordinal", &PyLocalBuffer::device_ordinal) .def("platform", &PyLocalBuffer::platform_name) .def("is_deleted", [](const PyLocalBuffer& buffer) { @@ -546,15 +511,6 @@ PYBIND11_MODULE(xla_extension, m) { .def_static("Compile", &PyLocalExecutable::Compile, py::call_guard()) .def("local_devices", &PyLocalExecutable::local_devices) - // TODO(skyewm): get rid of this once everything uses `local_devices` - .def("DeviceOrdinals", - [](const PyLocalExecutable& executable) { - std::vector device_ordinals; - for (std::shared_ptr device : executable.local_devices()) { - device_ordinals.push_back(device->local_device_ordinal()); - } - return device_ordinals; - }) .def("SizeOfGeneratedCodeInBytes", &PyLocalExecutable::SizeOfGeneratedCodeInBytes) .def("Delete", &PyLocalExecutable::Delete) @@ -742,6 +698,7 @@ PYBIND11_MODULE(xla_extension, m) { ops.def("ReducePrecision", &ReducePrecision, py::arg("operand"), py::arg("exponent_bits"), py::arg("mantissa_bits")); ops.def("ReduceWindowWithGeneralPadding", &ReduceWindowWithGeneralPadding); + ops.def("RegularizedIncompleteBeta", &RegularizedIncompleteBeta); ops.def("ReplicaId", &ReplicaId); ops.def("Reshape", static_cast, absl::Span)>(&Reshape)); diff --git a/tensorflow/compiler/xla/python/xla_client.py b/tensorflow/compiler/xla/python/xla_client.py index a7e35a8a81f..ec5ca9a4a75 100644 --- a/tensorflow/compiler/xla/python/xla_client.py +++ b/tensorflow/compiler/xla/python/xla_client.py @@ -76,13 +76,6 @@ class Backend(object): def buffer_from_pyval(self, pyval, device=None): """Allocates a fresh buffer and populates it with `pyval`.""" - def buffers_from_pyvals(self, pyvals_and_devices): - """Allocates buffers and populates them with `pyvals`.""" - return [ - self.buffer_from_pyval(pyval, device) - for pyval, device in pyvals_and_devices - ] - @abc.abstractmethod def make_tuple(self, c_buffers, device): """Makes a tuple from a sequence of backend buffer objects.""" @@ -404,22 +397,6 @@ class Buffer(object): backend = backend or get_local_backend() return backend.buffer_from_pyval(pyval, device) - @staticmethod - def from_pyvals(pyvals_and_devices, backend=None): - """Copies multiple Python values to freshly allocated on-device buffers. - - Arguments: - pyvals_and_devices: a list of `(pyval, device)` pairs, where `pyval` is a - Python value to copy (e.g., a NumPy array), and `device` is an integer - device ordinal. - backend: a Backend object, or `None` to use the default local backend. - - Returns: - A list of `Buffer` objects corresponding to `pyvals_and_devices`. - """ - backend = backend or get_local_backend() - return backend.buffers_from_pyvals(pyvals_and_devices) - @staticmethod def make_tuple(buffers, device, backend=None): backend = backend or get_local_backend() @@ -620,7 +597,7 @@ class Computation(object): # An Executable is a C++ class that duck types with the following API: # class Executable(object): -# def DeviceOrdinals(self) -> [int]: +# def local_devices(self) -> [Device]: # def Execute(self, arguments : [Buffer]) -> Buffer: # """Execute on one replica with Buffer arguments and return value.""" # @@ -650,7 +627,7 @@ def execute_with_python_values(executable, arguments=(), backend=None): def put(arg): return Buffer.from_pyval( - arg, device=executable.DeviceOrdinals()[0], backend=backend) + arg, device=executable.local_devices()[0], backend=backend) arguments = [put(arg) for arg in arguments] return executable.Execute(arguments).to_py() @@ -669,12 +646,14 @@ def execute_with_python_values_replicated(executable, arguments, backend=None): A list of python values, one per replica. """ backend = backend or get_local_backend() - device_ordinals = executable.DeviceOrdinals() + devices = executable.local_devices() # pylint: disable=g-complex-comprehension - flat_args = [(arg, device_ordinals[replica]) + flat_args = [(arg, devices[replica]) for replica, replica_args in enumerate(arguments) for arg in replica_args] - flat_arg_buffers = Buffer.from_pyvals(flat_args, backend=backend) + flat_arg_buffers = [ + backend.buffer_from_pyval(pyval, device) for pyval, device in flat_args + ] arg_buffers = [] for replica_args in arguments: arg_buffers.append(flat_arg_buffers[:len(replica_args)]) @@ -1526,7 +1505,7 @@ class ComputationBuilder(object): ConvWithGeneralPadding. feature_group_count: number of feature groups for grouped convolution. batch_group_count: number of batch groups for grouped convolution. - Returns: a XlaOp representing the ConvGenralDilated operation. + Returns: a XlaOp representing the ConvGeneralDilated operation. """ if dimension_numbers is None: dimension_numbers = self._GetConvDimensionNumbers(len(window_strides)) @@ -1729,6 +1708,7 @@ _OTHER_OPS = [ 'Dot', 'GetTupleElement', 'ReducePrecision', + 'RegularizedIncompleteBeta', 'Rev', 'Select', 'SliceInDim', diff --git a/tensorflow/compiler/xla/python/xla_client_test.py b/tensorflow/compiler/xla/python/xla_client_test.py index f490a05e25d..0fd0813bdcb 100644 --- a/tensorflow/compiler/xla/python/xla_client_test.py +++ b/tensorflow/compiler/xla/python/xla_client_test.py @@ -530,7 +530,8 @@ class BufferTest(ComputationTest): ) b0 = xla_client.Buffer.from_pyval(t[0]) b1 = xla_client.Buffer.from_pyval(t[1]) - btup = xla_client.Buffer.make_tuple([b0, b1], device=0) + device = xla_client.get_local_backend().local_devices()[0] + btup = xla_client.Buffer.make_tuple([b0, b1], device=device) pieces = btup.destructure() self.assertLen(pieces, 2) array0, array1 = pieces @@ -576,15 +577,6 @@ class BufferTest(ComputationTest): self.assertEqual(buf.device(), device) np.testing.assert_equal(x, buf.to_py()) - def testInvalidDevice(self): - t = np.array(1.) - with self.assertRaisesRegexp( - RuntimeError, - r"PyLocalBuffer::FromLiterals got bad device_ordinal: 100 " - r"\(num_local_devices=\d+\)"): - # TODO(skyewm): figure out how to test this with a Device - xla_client.Buffer.from_pyval(t, device=100) - class SingleOpTest(ComputationTest): """Tests for single ops. @@ -1456,6 +1448,16 @@ class SingleOpTest(ComputationTest): eps = np.finfo(np.float32).eps np.testing.assert_equal(np.array([eps + 1, 2 - eps], dtype=np.float32), out) + def testRegularizedIncompleteBeta(self): + x = np.array([0.53787335, 0.24015466, 0.47494545, 0.13567594, 0.95114538]) + a = np.array([0.00753073, 0.34813385, 0.30485708, 1.29298632, 0.51472606]) + b = np.array([0.55688389, 0.59794214, 0.42661022, 1.59748339, 0.95047677]) + c = self._NewComputation() + c.RegularizedIncompleteBeta(c.Constant(a), c.Constant(b), c.Constant(x)) + expected = np.array([0.98923271, 0.48575411, 0.57952568, 0.12579775, + 0.96989155]) + self._ExecuteAndCompareClose(c, expected=expected, rtol=1e-4) + class EmbeddedComputationsTest(ComputationTest): """Tests for XLA graphs with embedded computations (such as maps).""" diff --git a/tensorflow/compiler/xla/python_api/types.py b/tensorflow/compiler/xla/python_api/types.py index 57dfce3971b..fffe5adab1d 100644 --- a/tensorflow/compiler/xla/python_api/types.py +++ b/tensorflow/compiler/xla/python_api/types.py @@ -25,10 +25,10 @@ import numpy as _np # Avoids becoming a part of public Tensorflow API. from tensorflow.compiler.xla import xla_data_pb2 from tensorflow.python.framework import dtypes -# Records corresponsence between a XLA primitive type and Python/Numpy types. +# Records correspondence between a XLA primitive type and Python/Numpy types. # # primitive_type: value of type xla_data_pb2.PrimitiveType -# numpy_dtype: corresponsing Numpy "dtype" (like np.float32) +# numpy_dtype: corresponding Numpy "dtype" (like np.float32) # literal_field_name: name of the field in the LiteralProto message elements # of this type go into. # literal_field_type: type of the field named 'literal_field_name'. diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD index 14e6f66741e..b4ea4d9e263 100755 --- a/tensorflow/compiler/xla/service/BUILD +++ b/tensorflow/compiler/xla/service/BUILD @@ -9,7 +9,7 @@ load( ) load("//tensorflow:tensorflow.bzl", "tf_cc_test") load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load( @@ -1564,6 +1564,7 @@ cc_library( "//tensorflow/compiler/xla:debug_options_flags", "//tensorflow/compiler/xla:shape_util", "//tensorflow/compiler/xla:statusor", + "//tensorflow/compiler/xla:util", "//tensorflow/core:lib", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc index 2fe8c309cb0..f145b447bef 100755 --- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc @@ -673,7 +673,7 @@ Status AlgebraicSimplifierVisitor::HandleBitcast(HloInstruction* bitcast) { bitcast, HloInstruction::CreateBitcast(bitcast->shape(), op)); } // All bitcasts can be eliminated (assuming layout constraints are - // satisified). + // satisfied). ReplaceInstructionIfSameShape(bitcast, bitcast->mutable_operand(0)); return Status::OK(); } @@ -692,7 +692,7 @@ Status AlgebraicSimplifierVisitor::HandleCopy(HloInstruction* copy) { return ReplaceWithNewInstruction( copy, HloInstruction::CreateUnary(copy->shape(), HloOpcode::kCopy, op)); } - // All copies can be eliminated (assuming layout constraints are satisified). + // All copies can be eliminated (assuming layout constraints are satisfied). if (ReplaceInstructionIfSameShape(copy, copy->mutable_operand(0))) { return Status::OK(); } @@ -2735,7 +2735,7 @@ Status AlgebraicSimplifierVisitor::HandlePower(HloInstruction* power) { // Don't perform this optimization if either of the exponents is complex; this // identity is true only for real-valued exponents. In addition, we cowardly - // refuse to do this transformation if the two expontents have different + // refuse to do this transformation if the two exponents have different // element types. if (lhs->opcode() == HloOpcode::kPower && !ShapeUtil::ElementIsComplex(lhs->operand(1)->shape()) && diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc index 88282986560..f37ff5387ee 100755 --- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc @@ -4756,7 +4756,7 @@ TEST_P(DotStrengthReductionTest, DotStrengthReduction) { const bool computation_should_be_modified = dot_should_be_transformed || (transpose_lhs && transpose_rhs); EXPECT_EQ(changed, computation_should_be_modified); - // The second pass of algebriac simplifer will remove dots without + // The second pass of algebraic simplifier will remove dots without // non-contracting dimensions or contracting dimensions. TF_ASSERT_OK_AND_ASSIGN(changed, simplifier.Run(module.get())); EXPECT_EQ(changed, computation_should_be_modified); diff --git a/tensorflow/compiler/xla/service/batchnorm_expander_test.cc b/tensorflow/compiler/xla/service/batchnorm_expander_test.cc index 34b516184fa..d7b0dc8b29d 100644 --- a/tensorflow/compiler/xla/service/batchnorm_expander_test.cc +++ b/tensorflow/compiler/xla/service/batchnorm_expander_test.cc @@ -38,7 +38,7 @@ namespace { class BatchNormExpanderTest : public HloTestBase { protected: - // BatchNorm should have a dynamic sized dividor for mean operations. + // BatchNorm should have a dynamic sized divider for mean operations. int64 CountGetDimensionSize(const HloModule& module) { int64 count = 0; for (HloComputation* comp : module.computations()) { diff --git a/tensorflow/compiler/xla/service/buffer_assignment.cc b/tensorflow/compiler/xla/service/buffer_assignment.cc index 0d96ffd4568..7fe4913b8e8 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment.cc +++ b/tensorflow/compiler/xla/service/buffer_assignment.cc @@ -298,6 +298,38 @@ static bool CompareHloValuesById(const HloValue* a, const HloValue* b) { return a->id() < b->id(); } +// Returns parameter instruction corresponding to the allocation or nullptr. +static const HloInstruction* GetEntryParameterInstruction( + const BufferAllocation& alloc) { + for (const auto& p : alloc.assigned_buffers()) { + const HloValue* value = p.first; + const HloInstruction* instr = value->instruction(); + if (instr->opcode() == HloOpcode::kParameter && + instr->parent() == instr->parent()->parent()->entry_computation()) { + return instr; + } + } + return nullptr; +} + +// Returns root module output instruction corresponding to the allocation or +// nullptr. +static const HloInstruction* GetOutputInstruction( + const BufferAllocation& alloc) { + for (const auto& p : alloc.assigned_buffers()) { + const HloValue* value = p.first; + for (const HloPosition& position : value->positions()) { + const HloInstruction* instr = position.instruction; + if (position.index.empty() && + instr->parent()->root_instruction() == instr && + instr->parent()->IsEntryComputation()) { + return instr; + } + } + } + return nullptr; +} + string BufferAllocation::ToString() const { string output; StrAppendFormat(&output, "allocation %d: %p, size %d", index_, this, size()); @@ -305,8 +337,15 @@ string BufferAllocation::ToString() const { StrAppend(&output, ", color ", color().value()); } if (is_entry_computation_parameter()) { - StrAppend(&output, ", parameter ", parameter_number(), " at ShapeIndex ", - param_shape_index().ToString()); + const HloInstruction* param = GetEntryParameterInstruction(*this); + CHECK(param); + StrAppend(&output, ", parameter ", parameter_number(), ", shape |", + param->shape().ToString(/*print_layout=*/false), + "| at ShapeIndex ", param_shape_index().ToString()); + } + if (const HloInstruction* instr = GetOutputInstruction(*this)) { + StrAppend(&output, ", output shape is |", + instr->shape().ToString(/*print_layout=*/false), "|"); } if (is_constant()) { StrAppend(&output, ", constant"); @@ -1608,7 +1647,7 @@ StatusOr> BufferAssigner::CreateAssignment( /*is_thread_local=*/false, &buffers_to_assign_sequentially, assignment.get())); // Assign buffers with sequential ordering, if any. If all global - // computations are sequential, we can run heap simuation on the whole + // computations are sequential, we can run heap simulation on the whole // module, which reduces memory usage. const bool run_whole_module_heap_simulation = buffers_to_assign_sequentially.size() == global_computations.size(); diff --git a/tensorflow/compiler/xla/service/buffer_assignment_test.cc b/tensorflow/compiler/xla/service/buffer_assignment_test.cc index e54ad852d44..912c98b5001 100644 --- a/tensorflow/compiler/xla/service/buffer_assignment_test.cc +++ b/tensorflow/compiler/xla/service/buffer_assignment_test.cc @@ -770,7 +770,7 @@ TEST_F(BufferAssignmentTest, PresetAssignments) { } TEST_F(BufferAssignmentTest, PresetAssignmentsWhile) { - // Tests preset assignments when there is no 1-to-1 corrspondance between + // Tests preset assignments when there is no 1-to-1 correspondence between // HloValue and HloBuffer (i.e., a while loop). auto module = CreateNewVerifiedModule(); Shape f32vec10_color1 = diff --git a/tensorflow/compiler/xla/service/buffer_value.h b/tensorflow/compiler/xla/service/buffer_value.h index 11d8abc5bad..44cd7b5ebbd 100644 --- a/tensorflow/compiler/xla/service/buffer_value.h +++ b/tensorflow/compiler/xla/service/buffer_value.h @@ -160,7 +160,7 @@ class BufferValue { BufferValue(HloInstruction* instruction, const ShapeIndex& index, Id id); private: - // The definining instruction and index are not stored here; they can be found + // The defining instruction and index are not stored here; they can be found // in the LogicalBuffer and HloValue subclasses. This class exists only to // support migrations from TuplePointsToAnalysis to HloDataflowAnalysis, by // allowing abstract use of LogicalBuffer or HloValue. After those migrations diff --git a/tensorflow/compiler/xla/service/call_inliner.cc b/tensorflow/compiler/xla/service/call_inliner.cc index 1718b151e48..4f2436de4fa 100644 --- a/tensorflow/compiler/xla/service/call_inliner.cc +++ b/tensorflow/compiler/xla/service/call_inliner.cc @@ -27,7 +27,7 @@ namespace { // Traverses the callee computation, inlining cloned nodes into the caller // computation and connecting them to producers/consumers appropriately. -// When the traversal has completed, the provided call instruction is entriely +// When the traversal has completed, the provided call instruction is entirely // replaced in the caller's graph. class SubcomputationInsertionVisitor : public DfsHloVisitorWithDefault { public: diff --git a/tensorflow/compiler/xla/service/cholesky_expander.cc b/tensorflow/compiler/xla/service/cholesky_expander.cc index 74fc15a3eed..20576cdc52d 100644 --- a/tensorflow/compiler/xla/service/cholesky_expander.cc +++ b/tensorflow/compiler/xla/service/cholesky_expander.cc @@ -93,7 +93,7 @@ std::pair CholeskyUnblocked( Zeros(body_builder, ShapeUtil::MakeShape(a_shape.element_type(), matrix_dims)); // L * L.T, This matrix has of a lot of multiplying with zero - // (namely, L[:, j:] = 0) and redudant computation, but it is faster + // (namely, L[:, j:] = 0) and redundant computation, but it is faster // than slice. auto l_square = BatchDot(body_l, false, body_l, true, precision); diff --git a/tensorflow/compiler/xla/service/collective_ops_utils.h b/tensorflow/compiler/xla/service/collective_ops_utils.h index 2c5f2d64d1f..8b3c60f76de 100644 --- a/tensorflow/compiler/xla/service/collective_ops_utils.h +++ b/tensorflow/compiler/xla/service/collective_ops_utils.h @@ -32,7 +32,7 @@ namespace xla { enum class ReductionKind { SUM, PRODUCT, MIN, MAX }; -// Atempts to match computation to one of the possible cases in ReductionKind. +// Attempts to match computation to one of the possible cases in ReductionKind. absl::optional MatchReductionComputation( const HloComputation* computation); diff --git a/tensorflow/compiler/xla/service/compiler.h b/tensorflow/compiler/xla/service/compiler.h index a0248839fdd..b2e1231e315 100644 --- a/tensorflow/compiler/xla/service/compiler.h +++ b/tensorflow/compiler/xla/service/compiler.h @@ -47,7 +47,7 @@ namespace xla { // The following types are used for ahead of time compilation. // Contains the object file data created as a result of ahead-of-time -// compuation. +// computation. using ObjectFileData = std::vector; // Abstract superclass describing the result of an ahead-of-time compilation. diff --git a/tensorflow/compiler/xla/service/computation_placer.h b/tensorflow/compiler/xla/service/computation_placer.h index 8df2a50cf8f..03b65fd76a5 100644 --- a/tensorflow/compiler/xla/service/computation_placer.h +++ b/tensorflow/compiler/xla/service/computation_placer.h @@ -71,7 +71,7 @@ class ComputationPlacer { // Returns the device id assigned to the given replica and computation // instance for [replica_count x computation_count] setup. The returned device - // id must match the assignement from PlaceReplicatedComputation(). + // id must match the assignment from PlaceReplicatedComputation(). virtual StatusOr DeviceId(int replica, int computation, int replica_count, int computation_count); diff --git a/tensorflow/compiler/xla/service/conditional_simplifier.cc b/tensorflow/compiler/xla/service/conditional_simplifier.cc index 86f6a9295e6..f60742a8c23 100644 --- a/tensorflow/compiler/xla/service/conditional_simplifier.cc +++ b/tensorflow/compiler/xla/service/conditional_simplifier.cc @@ -189,7 +189,7 @@ StatusOr TryRemoveUnusedConditionalOperands( } for (HloInstruction* user : param->users()) { // If the user is not a get tuple element, assume it is unsafe to remove - // elemnts from the tuple. + // elements from the tuple. if (user->opcode() != HloOpcode::kGetTupleElement) { return false; } diff --git a/tensorflow/compiler/xla/service/convolution_group_converter.cc b/tensorflow/compiler/xla/service/convolution_group_converter.cc index cfcf059ba5f..f942d6768df 100644 --- a/tensorflow/compiler/xla/service/convolution_group_converter.cc +++ b/tensorflow/compiler/xla/service/convolution_group_converter.cc @@ -393,7 +393,7 @@ Status ConvolutionVisitor::HandleConvolution(HloInstruction* convolution) { const int64 depthwise_multiplier = filter->shape().dimensions(kernel_output_feature_dim) / group_count; // Split the kernel output feature dimension into group count and - // depthwise mutlipler. + // depthwise mutilipler. for (int64 i = 0; i < filter->shape().rank(); ++i) { if (i == kernel_output_feature_dim) { new_filter_dimension.push_back(group_count); @@ -439,7 +439,7 @@ Status ConvolutionVisitor::HandleConvolution(HloInstruction* convolution) { new_dim->set_window_dilation(1); new_dim->set_base_dilation(1); - // Split the output feature dimension into and output featrue of group + // Split the output feature dimension into and output feature of group // count and depthwise multipler as an output spatial dimension. std::vector new_output_dimension; new_output_dimension.reserve(convolution->shape().rank() + 1); diff --git a/tensorflow/compiler/xla/service/copy_insertion_test.cc b/tensorflow/compiler/xla/service/copy_insertion_test.cc index cde75d0c16c..9ac5e1c8b92 100644 --- a/tensorflow/compiler/xla/service/copy_insertion_test.cc +++ b/tensorflow/compiler/xla/service/copy_insertion_test.cc @@ -1177,8 +1177,8 @@ TEST_F(WhileCopyInsertionTest, InitPointsToNonDistinct) { InsertCopies(module_.get()); - // The entry computation requires two copies to resolve the non-disinctness of - // two init elements and the constant passed in as one of the init + // The entry computation requires two copies to resolve the non-distinctness + // of two init elements and the constant passed in as one of the init // elements. Either element can be copied for the distinctness issue. EXPECT_EQ(CountCopies(*module_->entry_computation()), 2); if (while_hlo->operand(0)->operand(1)->operand(0)->opcode() == @@ -1996,7 +1996,7 @@ void BM_ParallelWhiles(int num_iters, int num_whiles) { tensorflow::testing::StopTiming(); // Each body receives of copy of two of the parameters (the corresponding - // elements in the body are modifed), and there is one copy in each body. + // elements in the body are modified), and there is one copy in each body. ASSERT_EQ(CountCopies(module), 3 * num_whiles); } } diff --git a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc index 1270cd7a1bc..6a331ba4f19 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc @@ -350,7 +350,7 @@ Status CpuCompiler::RunHloPassesAfterLayoutAssn( // duplicate or NOPs, so remove them with algebraic simplification and CSE. { auto& pass = pipeline.AddPass>( - "simplification after layout assignement"); + "simplification after layout assignment"); pass.AddInvariantChecker( /*layout_sensitive=*/true, /*allow_mixed_precision=*/false, diff --git a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc index d19cf4fb015..a950f1f3d0f 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc @@ -298,10 +298,12 @@ StatusOr CpuExecutable::ExecuteAsyncOnStream( const Shape& expected_shape = entry_comp->parameter_instruction(i)->shape(); const Shape& actual_shape = arguments[i].shape(); - CHECK(expected_shape == actual_shape) << absl::StreamFormat( - "Shape mismatch on argument %d. Expected %s, but was %s.", i, - expected_shape.ToString(/*print_layout=*/true), - actual_shape.ToString(/*print_layout=*/true)); + CHECK( + Shape::Equal().IgnoreDynamicDimension()(expected_shape, actual_shape)) + << absl::StreamFormat( + "Shape mismatch on argument %d. Expected %s, but was %s.", i, + expected_shape.ToString(/*print_layout=*/true), + actual_shape.ToString(/*print_layout=*/true)); } } @@ -327,7 +329,7 @@ StatusOr CpuExecutable::ExecuteAsyncOnStream( // // Logically we want this lambda to capture `buffers` by move, ultimately our // functor needs to be wrapped in an std::function, and that requires its - // functor to be copyable. Thus we perpitrate the hack of capturing buffers + // functor to be copyable. Thus we perpetrate the hack of capturing buffers // "by shared pointer". // // We also need to change the types of some of the variables we capture: diff --git a/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc b/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc index 95b8025f873..4e0715ea7af 100644 --- a/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc +++ b/tensorflow/compiler/xla/service/cpu/cpu_layout_assignment.cc @@ -28,7 +28,7 @@ namespace cpu { // We want to change the layout of constant arrays to be column major when all // of their users are dot operations that can be made faster with the flipped -// layout. To avoid going quadriatic over the # of instructions, we cache this +// layout. To avoid going quadratic over the # of instructions, we cache this // property in should_make_rhs_col_major -- it maps a constant to true if all of // the users of said constant are dot operations that can be sped up. This // cache is populated lazily as we encounter dot operations traversing the diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc index a50c0dafba6..7dba826b65c 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc @@ -84,7 +84,7 @@ enum class DotImplementationStrategy { // supported. kTiledLlvmIrGemv, - // The dot operation is lowered into LLVM IR that implemetns a tiled + // The dot operation is lowered into LLVM IR that implements a tiled // Matrix*Matrix operation. No fusions are supported. The two inputs // and the output have to be row major. kTiledLlvmIrGemm, @@ -237,7 +237,7 @@ void DotOpEmitter::EmitTiledLlvmIrGemm() { int64 size_bytes = m * n * ShapeUtil::ByteSizeOfPrimitiveType(primitive_type); b_->CreateMemSet(target, b_->getInt8(0), /*Size=*/size_bytes, - /*Align=*/1); + /*Align=*/llvm::MaybeAlign(1)); int64 max_target_vector_width = target_machine_features_.vector_register_num_elements( diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h b/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h index cc28918ed60..0c75eaec858 100644 --- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h +++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter_internal.h @@ -63,7 +63,7 @@ enum class DotImplementationStrategy { // supported. kTiledLlvmIrGemv, - // The dot operation is lowered into LLVM IR that implemetns a tiled + // The dot operation is lowered into LLVM IR that implements a tiled // Matrix*Matrix operation. No fusions are supported. The two inputs // and the output have to be row major. kTiledLlvmIrGemm, diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc index cf167a57087..394d1fc979d 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc @@ -1159,7 +1159,7 @@ Status IrEmitter::HandleConvolution(HloInstruction* convolution) { /*instruction=*/*convolution, /*operands=*/{lhs, rhs}, /*supported_types=*/{F16, F32, F64, C64, C128})); - // TODO(tonywy): Add PotentiallyImplementedAsMKLCovolution to support + // TODO(tonywy): Add PotentiallyImplementedAsMKLConvolution to support // different data layouts. if (PotentiallyImplementedAsEigenConvolution(*convolution, target_machine_features_)) { diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.h b/tensorflow/compiler/xla/service/cpu/ir_emitter.h index 453676bd7c7..95458ba05a4 100644 --- a/tensorflow/compiler/xla/service/cpu/ir_emitter.h +++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.h @@ -294,7 +294,7 @@ class IrEmitter : public DfsHloVisitorWithDefault, absl::string_view name); // Emits a call to a "global" function (e.g. to the computation nested within - // a kWhile or a kCall). Buffer assignment unabiguously assignes buffers to + // a kWhile or a kCall). Buffer assignment unabiguously assigns buffers to // the parameters and return values for these computations so there is no need // to explicitly pass parameters or return results. void EmitGlobalCall(const HloComputation& callee, absl::string_view name); @@ -366,7 +366,7 @@ class IrEmitter : public DfsHloVisitorWithDefault, // without generating IR with illegal (e.g. excessively large or // non-power-of-two) vector types. We do this by introducing a layer of // abstraction: we introduce a high level vector-like concept called a - // "sharded vector" that models data paralleism, and is mapped to a sequence + // "sharded vector" that models data parallelism, and is mapped to a sequence // scalar and vector llvm::Value s. // // For example, we can represent 29 f32 elements by a sharded vector mapped to diff --git a/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc b/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc index 0b4e3ecd99b..78da1cfff0a 100644 --- a/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc +++ b/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc @@ -71,6 +71,10 @@ void RewriteCalls( fn = new_fn; } + // Other libraries using tfcompile could also have generated a function with + // the same name and body. Tell the linker to discard all but one instance. + fn->setLinkage(llvm::GlobalVariable::LinkOnceODRLinkage); + llvm::LLVMContext* context = &module->getContext(); llvm::BasicBlock* fn_body = llvm::BasicBlock::Create(*context, "body", fn); @@ -102,13 +106,18 @@ void RewriteCalls( // TODO(b/73081976): Should we avoid inlining these in some cases? std::vector calls_to_inline; for (auto* user : fn->users()) { - calls_to_inline.push_back(llvm::cast(user)); + if (auto* call = llvm::dyn_cast(user)) { + calls_to_inline.push_back(call); + } } for (auto* call_to_inline : calls_to_inline) { llvm::InlineFunctionInfo inline_function_info; CHECK(llvm::InlineFunction(call_to_inline, inline_function_info)); } - fn->eraseFromParent(); + // Delete the function if all uses have been inlined. + if (fn->use_empty()) { + fn->eraseFromParent(); + } } llvm::Value* GenerateVF32Tanh(llvm::IRBuilder<>* b, llvm::Value* input, @@ -185,7 +194,7 @@ llvm::Value* GenerateVF32Exp(llvm::IRBuilder<>* b, llvm::Value* input, // value of n clamped to [-127, 127]. In the case where n' = 127, `a` can grow // up to as large as 88.8 - 127 * log(2) which is about 0.7703. Even though // this value of `a` is outside our previously specified range, e^a will still - // only have a relative error of approximetely 2^-16 at worse. In practice + // only have a relative error of approximately 2^-16 at worse. In practice // this seems to work well enough; it passes our exhaustive tests, breaking // only one result, and by one ulp (we return exp(88.7228394) = max-float but // we should return inf). diff --git a/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc b/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc index 4a8d963bedf..14afe770ede 100644 --- a/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc +++ b/tensorflow/compiler/xla/service/cpu/parallel_task_assignment.cc @@ -193,7 +193,7 @@ bool ParallelTaskAssigner::AssignParallelTasksHelper( computation->instructions().end()); for (auto* instruction : instructions) { // Assign parallel tasks to sub-computations for While and Call HLOs. - // TODO(b/27458679) Evaluate alternative intra-op parallelsim placement, + // TODO(b/27458679) Evaluate alternative intra-op parallelism placement, // and support other callable computations like reduce. if (instruction->opcode() == HloOpcode::kWhile) { changed |= AssignParallelTasksHelper(module, instruction->while_body(), diff --git a/tensorflow/compiler/xla/service/cpu/runtime_fork_join.cc b/tensorflow/compiler/xla/service/cpu/runtime_fork_join.cc index 6f72ddadf94..bf1a1e016af 100644 --- a/tensorflow/compiler/xla/service/cpu/runtime_fork_join.cc +++ b/tensorflow/compiler/xla/service/cpu/runtime_fork_join.cc @@ -33,7 +33,7 @@ using ComputeFunctionType = void (*)(void*, const void*, const void**, void**, // Dispatches 'num_partitions - 1' calls to 'function_ptr' in parallel. // Calls 'function_ptr' for first partition inline. -// Uses blocking counter to synchonize threads after parallel calls complete. +// Uses blocking counter to synchronize threads after parallel calls complete. // // The 'partitions' array has a total number of elements equal to // 'num_partitions * num_partitioned_dims * 2' (the '2' is necessary to specify diff --git a/tensorflow/compiler/xla/service/cpu/shape_partition.cc b/tensorflow/compiler/xla/service/cpu/shape_partition.cc index d12c5396148..e95afbbb131 100644 --- a/tensorflow/compiler/xla/service/cpu/shape_partition.cc +++ b/tensorflow/compiler/xla/service/cpu/shape_partition.cc @@ -146,7 +146,7 @@ std::vector> ShapePartitionIterator::GetPartition( partition[i].second = dimension_partition_sizes_[i]; } CHECK_GT(partition[i].second, 0); - // Update index to remove conribution from current dimension. + // Update index to remove contribution from current dimension. index -= partition_index * dimension_partition_strides_[i]; } return partition; diff --git a/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc b/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc index 7668f364bad..c4626462b66 100644 --- a/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc +++ b/tensorflow/compiler/xla/service/cpu/tiled_dot_emitter.cc @@ -173,7 +173,7 @@ class GemvConfig { // | C | D | // +----------------------+---+ // -// where A is the largest submatrix of the LHS that can be evenly dividied into +// where A is the largest submatrix of the LHS that can be evenly divided into // tiles. For each tile in A, assuming tile_rows_ == tile_cols_ == 4, we have: // // +---+---+---+---+ +--+--+--+--+ @@ -212,7 +212,7 @@ class GemvConfig { // Where R is the starting row for the tile. // // We have an inner epilogue loop to deal with the "C" submatrix and an outer -// epilogue loop to deal with the B,D submarix. +// epilogue loop to deal with the B,D submatrix. // // TODO(sanjoy): We should investigate if using gather loads and scatter stores // can be used here have the same inner loop for both column-major and row-major @@ -410,7 +410,7 @@ void ColumnMajorMatrixVectorProductEmitter::EmitInnerLoopEpilogue( // | C | D | // +----------------------+---+ // -// where A is the largest submatrix of the LHS that can be evenly dividied into +// where A is the largest submatrix of the LHS that can be evenly divided into // tiles. For each tile in A, assuming tile_rows_ == tile_cols_ == 4, we have: // // +---+---+---+---+ diff --git a/tensorflow/compiler/xla/service/dump.cc b/tensorflow/compiler/xla/service/dump.cc index beea561dad6..85884d4af68 100644 --- a/tensorflow/compiler/xla/service/dump.cc +++ b/tensorflow/compiler/xla/service/dump.cc @@ -49,7 +49,7 @@ struct CanonicalDebugOptions { // function we treat this struct's members as write-only, and read only from // `opts`. - // Did the user specifiy an explicit format for dumping? + // Did the user specify an explicit format for dumping? bool output_format_other_than_url_specified = opts.xla_dump_hlo_as_text() || opts.xla_dump_hlo_as_proto() || opts.xla_dump_hlo_as_dot() || opts.xla_dump_hlo_as_html() || diff --git a/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc b/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc index 14ea6f988cb..333626ef3b9 100644 --- a/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc +++ b/tensorflow/compiler/xla/service/dynamic_dimension_inference.cc @@ -178,15 +178,32 @@ Status DynamicDimensionInferenceVisitor::HandleBroadcast(HloInstruction* hlo) { } Status DynamicDimensionInferenceVisitor::HandleCustomCall(HloInstruction* hlo) { + if (hlo->custom_call_target() == "PadToStatic") { + for (int64 i = 0; i < hlo->operand(0)->shape().rank(); ++i) { + if (hlo->operand(0)->shape().is_dynamic_dimension(i)) { + HloInstruction* dynamic_size = + hlo->parent()->AddInstruction(HloInstruction::CreateGetTupleElement( + ShapeUtil::MakeScalarShape(S32), hlo, i + 1)); + // PadToStatic converts a dynamic dimension to static dimension. It then + // returns the padded data output and the dynamic sizes of input + // dimensions. + ShapeIndex data_output = {0}; + parent_->SetDynamicSize(hlo, data_output, i, dynamic_size, + {.stride = 1, .multiple_of = 1}); + } + } + return Status::OK(); + } return ForEachOperandDynamicDimension( hlo, [&](HloInstruction* operand, ShapeIndex index, int64 dimension, int64 operand_index, HloInstruction* dynamic_size, DimensionConstraint constraint) { - if (hlo->custom_call_target() != "Unpad" || + if (hlo->custom_call_target() != "SliceToDynamic" || absl::StartsWith(hlo->custom_call_target(), "Resize")) { return Unimplemented( "CustomCall is not supported to have a dynamic dimension"); } + parent_->SetDynamicSize(hlo, {}, dimension, dynamic_size, constraint); return Status::OK(); }); @@ -298,7 +315,7 @@ Status DynamicDimensionInferenceVisitor::HandleDot(HloInstruction* hlo) { // A. batch dims // B. contracting dims // C. non-batch non-contracting dims. - // The output dimemsions of a dot has three parts with the following + // The output dimensions of a dot has three parts with the following // order: // [(type A), (lhs type C), (rhs type C)] // @@ -317,7 +334,7 @@ Status DynamicDimensionInferenceVisitor::HandleDot(HloInstruction* hlo) { bool lhs = operand_index == 0; // The first loop keep tracks of batch dimension. RHS and LHS could have - // diffrent batch dimension numbers. + // different batch dimension numbers. if (lhs) { for (int64 i : dimension_numbers.lhs_batch_dimensions()) { result_dim_mapping[i] = current_result_dims++; @@ -1039,13 +1056,13 @@ Status DynamicDimensionInferenceVisitor::HandleGather(HloInstruction* hlo) { if (operand_index != 1) { if (hlo->gather_slice_sizes()[input_dynamic_dimension] == 1) { // Gathering a size 1 dimension out of a dynamic dimension removes - // the dynamisity. + // the dynamicity. return Status::OK(); } if (hlo->gather_slice_sizes()[input_dynamic_dimension] == operand->shape().dimensions(input_dynamic_dimension)) { // Gathering a full-sized dimension out of a dynamic dimension - // propagates the dynamisity to output. + // propagates the dynamicity to output. int64 output_dimension = input_dynamic_dimension; for (int64 collapsed_dim : gather_dims.collapsed_slice_dims()) { if (collapsed_dim < input_dynamic_dimension) { diff --git a/tensorflow/compiler/xla/service/dynamic_padder.cc b/tensorflow/compiler/xla/service/dynamic_padder.cc index c94a2594f3b..f41a965825d 100644 --- a/tensorflow/compiler/xla/service/dynamic_padder.cc +++ b/tensorflow/compiler/xla/service/dynamic_padder.cc @@ -23,7 +23,9 @@ limitations under the License. #include "absl/strings/str_format.h" #include "tensorflow/compiler/xla/literal.h" #include "tensorflow/compiler/xla/literal_util.h" +#include "tensorflow/compiler/xla/service/dfs_hlo_visitor_with_default.h" #include "tensorflow/compiler/xla/service/dynamic_dimension_inference.h" +#include "tensorflow/compiler/xla/service/hlo_computation.h" #include "tensorflow/compiler/xla/service/hlo_dce.h" #include "tensorflow/compiler/xla/service/hlo_instruction.h" #include "tensorflow/compiler/xla/shape_util.h" @@ -169,7 +171,7 @@ HloInstruction* PadWithScalar(HloInstruction* inst, int64 dim, return padded; } -// In a reshape if a dynamci dimension is splitted into multiple output +// In a reshape if a dynamic dimension is splitted into multiple output // dimensions, we need to rewrite the input of the reshape. // // The reason for this is that a continuous input may not be evenly reshaped @@ -290,7 +292,7 @@ Status RewriteDynamicReshapeSplitInput( // Step 4. Sort iota. // Use binary mark to sort iota mask, then use iota mask to reshape input. - HloComputation::Builder comp_builder("compare_bianry_iota"); + HloComputation::Builder comp_builder("compare_binary_iota"); { HloInstruction* lhs_key = comp_builder.AddInstruction(HloInstruction::CreateParameter( @@ -322,7 +324,7 @@ Status RewriteDynamicReshapeSplitInput( mask_input_shape, sorted_binary_iota, 1)); // Step 5. Sort original input using iota mask as key. - HloComputation::Builder comp_builder_iota("compare_bianry_iota"); + HloComputation::Builder comp_builder_iota("compare_binary_iota"); { HloInstruction* lhs_key = comp_builder_iota.AddInstruction(HloInstruction::CreateParameter( @@ -641,9 +643,77 @@ StatusOr RewriteDynamicReshape( return changed; } -// For all dynamic outputs that live out of the computation, add unpad -// operations. -Status InsertUnpadsForModuleOutputs( +// Insert pad-to-static after `inst` if `inst` has dynamic dimensions in it. +// Recurse into tuple instructions. +StatusOr InsertPadToStaticOnInstruction(HloInstruction* inst) { + if (inst->shape().is_static()) { + return inst; + } + HloComputation* comp = inst->parent(); + if (!inst->shape().IsTuple()) { + // The output shape of pad static is a tuple. The 0th element is the data + // output, which is the same as input shape, but without dynamic dimensions; + // i-th element is the dynamic dimension size for i-1th input dimension. + Shape data_output_shape = inst->shape(); // 0th element. + data_output_shape.clear_dynamic_dimensions(); + Shape output_shape = ShapeUtil::MakeTupleShape({data_output_shape}); + for (int64 i = 0; i < inst->shape().rank(); ++i) { + ShapeUtil::AppendShapeToTuple(ShapeUtil::MakeScalarShape(S32), + &output_shape); + } + HloInstruction* pad_to_static = + comp->AddInstruction(HloInstruction::CreateCustomCall( + output_shape, {inst}, "PadToStatic", "")); + HloInstruction* data_output = + comp->AddInstruction(HloInstruction::CreateGetTupleElement( + data_output_shape, pad_to_static, 0)); + return data_output; + } + + TF_RET_CHECK(inst->shape().IsTuple()); + std::vector static_tuple_elements; + for (int64 i = 0; i < inst->shape().tuple_shapes_size(); ++i) { + // For each tuple element, if it is static, pass it through. If it is + // dynamic, recursively call this function again. + HloInstruction* gte = + comp->AddInstruction(HloInstruction::CreateGetTupleElement( + inst->shape().tuple_shapes(i), inst, i)); + + if (gte->shape().is_static()) { + static_tuple_elements.push_back(gte); + } else { + TF_ASSIGN_OR_RETURN(HloInstruction * static_gte, + InsertPadToStaticOnInstruction(gte)); + static_tuple_elements.push_back(static_gte); + } + } + + return comp->AddInstruction( + HloInstruction::CreateTuple(static_tuple_elements)); +} + +Status InsertPadToStaticAfterModuleInputs(HloModule* module) { + std::vector params; + HloComputation* entry = module->entry_computation(); + for (int64 i = 0; i < entry->num_parameters(); ++i) { + HloInstruction* param = + module->entry_computation()->parameter_instruction(i); + auto users = param->users(); + TF_ASSIGN_OR_RETURN(HloInstruction * static_param, + InsertPadToStaticOnInstruction(param)); + for (auto* user : users) { + TF_RETURN_IF_ERROR(param->ReplaceUseWith(user, static_param)); + } + if (param == entry->root_instruction()) { + module->entry_computation()->set_root_instruction(static_param); + } + } + return Status::OK(); +} + +// For all dynamic outputs that live out of the computation, add +// slice-to-dynamic operations. +Status InsertSliceToDynamicBeforeModuleOutputs( const DynamicDimensionInference& dynamic_dimension_inference, HloModule* module) { auto root = module->entry_computation()->root_instruction(); @@ -656,7 +726,7 @@ Status InsertUnpadsForModuleOutputs( if (dynamic_dimension_inference.GetDynamicSize(root, index, dim) != nullptr) { CHECK_LE(index.size(), 1) << "XLA doesn't support nested output " - "dimensions that has dynamic size"; + "dimension that has dynamic size"; has_dynamic_output = true; } } @@ -674,30 +744,36 @@ Status InsertUnpadsForModuleOutputs( if (!subshape.IsArray()) { return; } + auto gte = module->entry_computation()->AddInstruction( - HloInstruction::CreateGetTupleElement(subshape, root, index[0])); + HloInstruction::CreateGetTupleElement( + ShapeUtil::MakeShapeWithStaticDimensions(subshape), root, + index[0])); if (dynamic_outputs.contains(index)) { CHECK_EQ(index.size(), 1) << "XLA only support 1 layer nested output tuple"; - // For dynamic outputs, creates an unpad operation. - std::vector unpad_operands; + // For dynamic outputs, creates an slice operation. + std::vector slice_operands; // First operand is the original input. Rest are dimension values. - unpad_operands.push_back(gte); + slice_operands.push_back(gte); + // Keep a dynamic version of the subshape as we are removing the + // dynamic dimension in the original root and gte. + Shape dynamic_subshape = subshape; for (int64 dim = 0; dim < subshape.rank(); ++dim) { HloInstruction* dynamic_size = dynamic_dimension_inference.GetDynamicSize(root, index, dim); if (dynamic_size != nullptr) { - unpad_operands.push_back(dynamic_size); + slice_operands.push_back(dynamic_size); } else { auto const_size = HloInstruction::CreateConstant( LiteralUtil::CreateR0(subshape.dimensions(dim))); - unpad_operands.push_back( + slice_operands.push_back( module->entry_computation()->AddInstruction( std::move(const_size))); } } - // This is a dynamic output, add unpad operation. + // This is a dynamic output, add slice operation. // // Write the backend config in the format of // 'dynamic_index'-'output_index'. @@ -707,11 +783,11 @@ Status InsertUnpadsForModuleOutputs( // // output_index indicates the position of this output in all outputs // (including static inputs). - auto unpad = HloInstruction::CreateCustomCall( - subshape, unpad_operands, "Unpad", + auto slice = HloInstruction::CreateCustomCall( + dynamic_subshape, slice_operands, "SliceToDynamic", absl::StrFormat("%d-%d", dynamic_index++, index[0])); new_root_operands.push_back( - module->entry_computation()->AddInstruction(std::move(unpad))); + module->entry_computation()->AddInstruction(std::move(slice))); } else { new_root_operands.push_back(gte); } @@ -721,37 +797,125 @@ Status InsertUnpadsForModuleOutputs( HloInstruction::CreateTuple(new_root_operands)); module->entry_computation()->set_root_instruction(new_root); } else { - std::vector unpad_operands; + std::vector slice_operands; // First operand is the original input. Rest are dimension values. - unpad_operands.push_back(root); + slice_operands.push_back(root); for (int64 dim = 0; dim < root->shape().rank(); ++dim) { HloInstruction* dynamic_size = dynamic_dimension_inference.GetDynamicSize(root, {}, dim); if (dynamic_size != nullptr) { - unpad_operands.push_back(dynamic_size); + slice_operands.push_back(dynamic_size); } else { auto const_size = HloInstruction::CreateConstant( LiteralUtil::CreateR0(root->shape().dimensions(dim))); - unpad_operands.push_back(module->entry_computation()->AddInstruction( + slice_operands.push_back(module->entry_computation()->AddInstruction( std::move(const_size))); } - // This is a dynamic output, add unpad operation. - auto unpad = module->entry_computation()->AddInstruction( - HloInstruction::CreateCustomCall(root->shape(), unpad_operands, - "Unpad", "0-0")); - module->entry_computation()->set_root_instruction(unpad); + // This is a dynamic output, add slice operation. + auto slice = module->entry_computation()->AddInstruction( + HloInstruction::CreateCustomCall(root->shape(), slice_operands, + "SliceToDynamic", "0-0")); + module->entry_computation()->set_root_instruction(slice); } } } return Status::OK(); } +// Remove all dynamic shapes between pad-to-static and slice-to-dynamic. +// +// After this visitor the entry computation then looks like: +// Param(dynamic) +// | +// GTE (dynamic) +// | +// PadToStatic(static) +// | +// .... regular computation with static shapes. +// | +// SliceToDynamic(dynamic) +// | +// ROOT tuple (dynamic) +class DynamicShapeRemovingVisitor : public DfsHloVisitorWithDefault { + public: + Status DefaultAction(HloInstruction* hlo) override; + + Status HandleCustomCall(HloInstruction* hlo) override; + + Status HandleParameter(HloInstruction* hlo) override; + + static Status Run(HloComputation* computation) { + DynamicShapeRemovingVisitor visitor; + return computation->Accept(&visitor); + } +}; + +Status DynamicShapeRemovingVisitor::DefaultAction(HloInstruction* hlo) { + // Default rule: If input to an op is static, remove dynamism in output. + bool input_is_dynamic = false; + // Default rule: + for (int64 i = 0; i < hlo->operand_count(); ++i) { + if (!hlo->operand(i)->shape().is_static()) { + input_is_dynamic = true; + } + } + + if (!input_is_dynamic) { + hlo->mutable_shape()->clear_dynamic_dimensions(); + } + return Status::OK(); +} + +Status DynamicShapeRemovingVisitor::HandleCustomCall(HloInstruction* hlo) { + if (hlo->custom_call_target() == "SliceToDynamic") { + // Don't remove slice-to-dynamic instruction. + return Status::OK(); + } + return DefaultAction(hlo); +} + +Status DynamicShapeRemovingVisitor::HandleParameter(HloInstruction* hlo) { + return Status::OK(); +} + } // namespace StatusOr DynamicPadder::Run(HloModule* module) { bool changed = false; VLOG(2) << "Pre DynamicPadder HLO:"; - XLA_VLOG_LINES(2, module->ToString()); + + // Removes dynamic dimensions on parameters if there is already a binding for + // it. We do this because we have two different APIs to express a dynamic + // dimension: + // + // 1. Dynamic dimension as specificed directly in the shape -- Needed for + // Pytorch. + // + // 2. Dynamic dimension using dynamic parameter binding object. This + // is needed for tensorflow. + // + // For case 1, we will insert "pad-to-static" instruction in the + // beginning of xla execution, to make it into a static layout. + // + // For case 2, since it already has a static layout, we remove the + // dynamic dimension. + // + // TODO(b/145140571): Convert all API invocations to case 1. + // + TF_RETURN_IF_ERROR(module->dynamic_parameter_binding().ForEachBinding( + [&](const DynamicParameterBinding::DynamicParameter& dynamic_parameter, + const DynamicParameterBinding::DynamicDimension& dynamic_dimension) + -> Status { + HloInstruction* parameter = + module->entry_computation()->parameter_instruction( + dynamic_dimension.parameter_num); + ShapeUtil::UpdateDynamicDimension(parameter->mutable_shape(), + dynamic_dimension.parameter_index, + dynamic_dimension.dimension, false); + return Status::OK(); + })); + + TF_RETURN_IF_ERROR(InsertPadToStaticAfterModuleInputs(module)); TF_ASSIGN_OR_RETURN(DynamicDimensionInference dynamic_dimension_inference, DynamicDimensionInference::Run(module)); @@ -806,8 +970,28 @@ StatusOr DynamicPadder::Run(HloModule* module) { } } - TF_RETURN_IF_ERROR( - InsertUnpadsForModuleOutputs(dynamic_dimension_inference, module)); + TF_RETURN_IF_ERROR(InsertSliceToDynamicBeforeModuleOutputs( + dynamic_dimension_inference, module)); + + // Remove all dynamic dimensions after entry parameter and root instruction -- + // Dynamic padder will produce an equivalent static shaped graph. + for (HloComputation* computation : module->computations()) { + if (computation == module->entry_computation()) { + TF_RETURN_IF_ERROR(DynamicShapeRemovingVisitor::Run(computation)); + } else { + for (HloInstruction* inst : computation->MakeInstructionPostOrder()) { + bool operand_is_dynamic = false; + for (auto* operand : inst->operands()) { + if (!operand->shape().is_static()) { + operand_is_dynamic = true; + } + } + if (!operand_is_dynamic) { + inst->mutable_shape()->clear_dynamic_dimensions(); + } + } + } + } HloDCE dce; TF_ASSIGN_OR_RETURN(changed, dce.Run(module)); diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc index 199e14a2164..66801d28f16 100644 --- a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc +++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc @@ -691,7 +691,7 @@ StatusOr ElementalIrEmitter::EmitComplexUnaryOp( llvm::Value* imag_numerator = FMul(four, FMul(cos_b, sin_b)); // Expm1(x) is about x for small values of x, but exp_sum_m2 is about x^2 - // for small value of x. As a result, due to floating point precission + // for small value of x. As a result, due to floating point precision // issues, x^2 is a better approximation than Expm1(x) + Expm1(x) for // small values of x. llvm::Value* a_sqr = FMul(a, a); @@ -1376,7 +1376,7 @@ StatusOr ElementalIrEmitter::EmitExpm1(PrimitiveType prim_type, auto for_small_x = FAdd(x, x_squared_over_two); // At this point, the relative errors due to floating point precision loss of // calculating exp(x) - 1 and the polynomial exp(x)-1 = x + x^2/2 are about - // equal, with a value of approximetely 2^-16. + // equal, with a value of approximately 2^-16. const auto kExponentIsSmallThreshold = 0.009; auto abs_x = llvm_ir::EmitCallToIntrinsic(llvm::Intrinsic::fabs, {value}, {type}, b_); diff --git a/tensorflow/compiler/xla/service/generic_transfer_manager.h b/tensorflow/compiler/xla/service/generic_transfer_manager.h index 9f415c8fbae..9cc344be06c 100644 --- a/tensorflow/compiler/xla/service/generic_transfer_manager.h +++ b/tensorflow/compiler/xla/service/generic_transfer_manager.h @@ -60,7 +60,6 @@ class GenericTransferManager : public TransferManager { int64 GetByteSizeRequirement(const Shape& shape) const override; - protected: Status WriteSingleTupleIndexTable( se::Stream* stream, absl::Span elements, const Shape& shape, se::DeviceMemoryBase* region) override; diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD index eb8b848fc3f..13e8a3f4409 100755 --- a/tensorflow/compiler/xla/service/gpu/BUILD +++ b/tensorflow/compiler/xla/service/gpu/BUILD @@ -17,7 +17,7 @@ load( ) load("@local_config_cuda//cuda:build_defs.bzl", "if_cuda") load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load( @@ -1250,6 +1250,7 @@ cc_library( "amdgpu_compiler.h", ], deps = [ + ":gemm_rewriter", ":gpu_compiler", ":gpu_conv_algorithm_picker", ":gpu_conv_padding_legalization", diff --git a/tensorflow/compiler/xla/service/gpu/amdgpu_compiler.cc b/tensorflow/compiler/xla/service/gpu/amdgpu_compiler.cc index 10dd4542612..2074ed66766 100644 --- a/tensorflow/compiler/xla/service/gpu/amdgpu_compiler.cc +++ b/tensorflow/compiler/xla/service/gpu/amdgpu_compiler.cc @@ -16,6 +16,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/gpu/amdgpu_compiler.h" #include "tensorflow/compiler/xla/service/algebraic_simplifier.h" +#include "tensorflow/compiler/xla/service/gpu/gemm_rewriter.h" #include "tensorflow/compiler/xla/service/gpu/gpu_conv_algorithm_picker.h" #include "tensorflow/compiler/xla/service/gpu/gpu_conv_padding_legalization.h" #include "tensorflow/compiler/xla/service/gpu/gpu_conv_rewriter.h" @@ -97,6 +98,9 @@ Status AMDGPUCompiler::OptimizeHloPostLayoutAssignment( options.set_is_layout_sensitive(true); pipeline.AddPass>(options); + // Rewrite GEMMs into custom calls. + pipeline.AddPass(); + pipeline.AddPass(stream_exec, device_allocator); // Clean up new_tuple described above. diff --git a/tensorflow/compiler/xla/service/gpu/backend_configs.proto b/tensorflow/compiler/xla/service/gpu/backend_configs.proto index 602e61ac0e8..0724a83180e 100644 --- a/tensorflow/compiler/xla/service/gpu/backend_configs.proto +++ b/tensorflow/compiler/xla/service/gpu/backend_configs.proto @@ -6,7 +6,7 @@ import "tensorflow/compiler/xla/xla_data.proto"; // Backend configs for XLA:GPU. // -// These are metadata that the GPU backend attaches to HloInstrucitons and later +// These are metadata that the GPU backend attaches to HloInstructions and later // uses during e.g. codegen. // // Remember that proto3 doesn't give clients a way to tell the difference diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc b/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc index a8528de96f5..b00dba3e9da 100755 --- a/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_rewriter.cc @@ -193,7 +193,7 @@ Status Visitor::HandleBatchNormTraining(HloInstruction* batch_norm) { if (is_batchnorm_with_fp16_inputs) { new_gte = AddConvert(new_gte, F32); } - // Repackage the results. Athough this tuple is redundant when convert is not + // Repackage the results. Although this tuple is redundant when convert is not // inserted, TupleSimplifier eliminates the Tuple eventually std::unique_ptr replacing_tuple = HloInstruction::CreateTuple( {new_gte, @@ -282,7 +282,7 @@ Status Visitor::HandleBatchNormGrad(HloInstruction* batch_norm) { if (is_batchnorm_with_fp16_inputs) { new_gte = AddConvert(new_gte, F32); } - // Repackage the results. Athough this tuple is redundant when convert is not + // Repackage the results. Although this tuple is redundant when convert is not // inserted, TupleSimplifier eliminates the Tuple eventually std::unique_ptr replacing_tuple = HloInstruction::CreateTuple( {new_gte, diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_thunk.cc b/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_thunk.cc index 4a4198f2fc9..f3fdc6b04e6 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_thunk.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_batchnorm_thunk.cc @@ -72,7 +72,7 @@ void CheckInputOutputPrimitivetypeAreValid(const HloInstruction* hlo) { // The last operand is the feature index which must be int64. CHECK_EQ(hlo->operand(num_operands - 1)->shape().element_type(), S64) - << "Not yet impelemented"; + << "Not yet implemented"; // Check Outputs. if (hlo->shape().IsTuple()) { diff --git a/tensorflow/compiler/xla/service/gpu/cudnn_pad_for_convolutions.cc b/tensorflow/compiler/xla/service/gpu/cudnn_pad_for_convolutions.cc index 17c02b64db5..6a5eb226be0 100644 --- a/tensorflow/compiler/xla/service/gpu/cudnn_pad_for_convolutions.cc +++ b/tensorflow/compiler/xla/service/gpu/cudnn_pad_for_convolutions.cc @@ -143,7 +143,7 @@ static std::vector GetRelevantConvs( // instruction to cuDNN convolution that may need padding to figure out the // desired padded input and output tensor shapes and store the desired // shapes in new_input_shapes and new_input_shapes. Notice that -// new_input_shapes is a vector for multiple input tesnsors. This function +// new_input_shapes is a vector for multiple input tensors. This function // shall return true, if padding is necessary or false otherwise in addition to // status. static StatusOr ResolveAndPad( @@ -175,7 +175,7 @@ static StatusOr ResolveAndPad( // Don't run this pass on GPUs without tensor cores -- it will make them slower! // // TODO(jlebar): Also pad dots. -static StatusOr TryResolvePadedShapesForTensorCore( +static StatusOr TryResolvePaddedShapesForTensorCore( HloCustomCallInstruction* conv, std::vector* new_input_shapes_ptr, Shape* new_result_shape_ptr) { TF_ASSIGN_OR_RETURN(auto kind, GetCudnnConvKind(conv)); @@ -278,7 +278,7 @@ static StatusOr TryResolvePadedShapesForTensorCore( // Adds padding to cudnn integer convolutions to make input and output feature // maps multiple of 4 -static StatusOr TryResolvePadedShapesForIntegerConvolution( +static StatusOr TryResolvePaddedShapesForIntegerConvolution( HloCustomCallInstruction* conv, std::vector* new_input_shapes_ptr, Shape* new_result_shape_ptr) { TF_ASSIGN_OR_RETURN(auto kind, GetCudnnConvKind(conv)); @@ -390,14 +390,14 @@ StatusOr CudnnPadForConvolutions::Run(HloModule* module) { for (HloCustomCallInstruction* conv : GetRelevantConvs(comp)) { TF_ASSIGN_OR_RETURN( bool local_changed, - ResolveAndPad(conv, TryResolvePadedShapesForIntegerConvolution)); + ResolveAndPad(conv, TryResolvePaddedShapesForIntegerConvolution)); changed |= local_changed; } for (HloCustomCallInstruction* conv : GetRelevantConvs(comp)) { if (is_volta_or_later_) { TF_ASSIGN_OR_RETURN( bool local_changed, - ResolveAndPad(conv, TryResolvePadedShapesForTensorCore)); + ResolveAndPad(conv, TryResolvePaddedShapesForTensorCore)); changed |= local_changed; } } diff --git a/tensorflow/compiler/xla/service/gpu/fusion_merger.cc b/tensorflow/compiler/xla/service/gpu/fusion_merger.cc index f707a87d79e..b4ccf758e94 100644 --- a/tensorflow/compiler/xla/service/gpu/fusion_merger.cc +++ b/tensorflow/compiler/xla/service/gpu/fusion_merger.cc @@ -215,7 +215,7 @@ Status FusionInstructionMerger::HandleFusion(HloInstruction* fusion) { // would occur if 'fusion' were merged into multiple users. // // If 'fusion' has just one user, then an earlier fusion pass chose not to - // fuse this producer/comsumer pair (likely because of expensive instruction + // fuse this producer/consumer pair (likely because of expensive instruction // re-use by the consumer), and so we honor that choice here as well. if (absl::c_any_of(fusion->fused_instructions(), [](const HloInstruction* instruction) { @@ -230,7 +230,7 @@ Status FusionInstructionMerger::HandleFusion(HloInstruction* fusion) { // Skip 'fusion' instruction if merging it into all users would result in a // net increase in bytes transferred (currently allowing the net bytes - // transferred to be exceeded up to ~10% in exhange for eliminating the + // transferred to be exceeded up to ~10% in exchange for eliminating the // overhead from a GPU kernel launch). const double current_bytes_transferred = GetCurrentBytesTransferred(fusion); const double merged_bytes_transferred = GetMergedBytesTransferred(fusion); diff --git a/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc b/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc index 50ed7448790..47fd9bbfb09 100644 --- a/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc +++ b/tensorflow/compiler/xla/service/gpu/fusion_merger_test.cc @@ -109,9 +109,9 @@ ENTRY MergeSharedFusionInstruction.Computation0 { // This is because the bytes read by Fusion2 (when replicated if the instruction // is merged into Fusion0 and Fusion1) would exceed the bytes transferred // threshold. -TEST_F(FusionMergerTest, BytesTransferredThresholdExeceeded) { +TEST_F(FusionMergerTest, BytesTransferredThresholdExceeded) { auto module = ParseAndReturnVerifiedModule(R"( -HloModule BytesTransferredThresholdExeceeded +HloModule BytesTransferredThresholdExceeded comp.2 { state.param_1.1 = (f32[4]{0}, f32[4]{0}, f32[4]{0}, f32[4]{0}) parameter(0) @@ -138,7 +138,7 @@ comp { ROOT add.5 = f32[4]{0} add(multiply.2, constant.param_1.1) } -ENTRY BytesTransferredThresholdExeceeded.Computation2 { +ENTRY BytesTransferredThresholdExceeded.Computation2 { constant = f32[4]{0} constant({1, 1, 1, 1}) state = (f32[4]{0}, f32[4]{0}, f32[4]{0}, f32[4]{0}) parameter(0) fusion.2 = f32[4]{0} fusion(state), kind=kLoop, calls=comp.2 @@ -157,9 +157,9 @@ ENTRY BytesTransferredThresholdExeceeded.Computation2 { // Fusion2 is merged into Fusion0 and Fusion1, because bytes read from Param by // Fusion2 is reduced for this test which makes the merge operation into its // operand below the bytes transferred threshold. -TEST_F(FusionMergerTest, BytesTransferredThresholdNotExeceeded) { +TEST_F(FusionMergerTest, BytesTransferredThresholdNotExceeded) { auto module = ParseAndReturnVerifiedModule(R"( -HloModule BytesTransferredThresholdNotExeceeded +HloModule BytesTransferredThresholdNotExceeded comp.2 { state.param_1.1 = (f32[4]{0}, f32[4]{0}, f32[4]{0}) parameter(0) @@ -184,7 +184,7 @@ comp { ROOT add.4 = f32[4]{0} add(multiply.2, constant.param_1.1) } -ENTRY BytesTransferredThresholdNotExeceeded.Computation2 { +ENTRY BytesTransferredThresholdNotExceeded.Computation2 { constant = f32[4]{0} constant({1, 1, 1, 1}) state = (f32[4]{0}, f32[4]{0}, f32[4]{0}) parameter(0) fusion.2 = f32[4]{0} fusion(state), kind=kLoop, calls=comp.2 diff --git a/tensorflow/compiler/xla/service/gpu/gpu_conv_algorithm_picker.cc b/tensorflow/compiler/xla/service/gpu/gpu_conv_algorithm_picker.cc index bf175999c55..71a86207987 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_conv_algorithm_picker.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_conv_algorithm_picker.cc @@ -294,7 +294,7 @@ StatusOr GpuConvAlgorithmPicker::PickBestAlgorithm( allocator->GetStream(stream_exec_->device_ordinal())); StatusOr result_or(InternalError("Unknown platform.")); // Check StreamExecutor on which platform it is. ROCm and Cuda implementation - // have diverged. Secifically, we need to make sure redzone allocator related + // have diverged. Specifically, we need to make sure redzone allocator related // utilities are not used in ROCm routine if (stream_exec_->platform_kind() == se::PlatformKind::kROCm) { result_or = PickBestAlgorithmNoCacheRocm(instr, allocator, stream); @@ -349,7 +349,7 @@ GpuConvAlgorithmPicker::PickBestAlgorithmNoCacheCuda( optional comparator; // Use the first algorithm that's supported as reference. There isn't a - // particular reason to use it, as any algorithm sufficies. It doesn't make + // particular reason to use it, as any algorithm suffices. It doesn't make // this algorithm considered correct, though. se::DeviceMemoryBase reference_result_buffer; AlgorithmDesc first_algorithm; @@ -462,7 +462,7 @@ GpuConvAlgorithmPicker::PickBestAlgorithmNoCacheCuda( << instr->ToString() << ": " << compare_result.status(); if (compare_result.status().code() == tensorflow::error::RESOURCE_EXHAUSTED) { - // Possibly OOM. Propatate the error. + // Possibly OOM. Propagate the error. return compare_result.status(); } CHECK(!crash_on_checking_failure); diff --git a/tensorflow/compiler/xla/service/gpu/gpu_conv_padding_legalization.cc b/tensorflow/compiler/xla/service/gpu/gpu_conv_padding_legalization.cc index da090f2e5e9..5fa102ac785 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_conv_padding_legalization.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_conv_padding_legalization.cc @@ -213,7 +213,7 @@ bool GpuConvPaddingLegalization::CanonicalizeBackwardFilterConvolution( // BackwardFilterConv(ABCD, xyz, padding_low=1, padding_high=2) // is equivalent to // ABCD0 = Pad(ABCD, padding_high=1) - // BackwardFilterConv(ABCD0, xyz, padding_low=pading_high=1) + // BackwardFilterConv(ABCD0, xyz, padding_low=padding_high=1) // We choose the lesser of padding_low and padding_high as the new padding. HloInstruction* input = backward_conv->mutable_operand(0); Window new_backward_conv_window = backward_conv->window(); diff --git a/tensorflow/compiler/xla/service/gpu/gpu_debug_info_manager.h b/tensorflow/compiler/xla/service/gpu/gpu_debug_info_manager.h index f12d47980f3..41825a33174 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_debug_info_manager.h +++ b/tensorflow/compiler/xla/service/gpu/gpu_debug_info_manager.h @@ -53,7 +53,7 @@ class GpuDebugInfoManager { // Register an active module to GpuDebugInfoManager. We will keep track all // existing HloModules within the process. - // Modules with same module id can be registered and tracked seperately. + // Modules with same module id can be registered and tracked separately. void RegisterModule( const ModuleIdentifier& module_id, std::shared_ptr hlo_module, std::shared_ptr buffer_assignment); diff --git a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc index 93af1cd995e..a879e6faf32 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc @@ -121,8 +121,8 @@ Status GpuExecutable::CheckCompatibilityWithServiceExecutableRunOptions( main_stream->parent()->GetDeviceDescription().cuda_compute_capability( &stream_compute_compatibility.first, &stream_compute_compatibility.second); - GpuVersion nvdia_compute_compatibility = stream_compute_compatibility; - TF_RET_CHECK(nvdia_compute_compatibility == gpu_version_) + GpuVersion nvidia_compute_compatibility = stream_compute_compatibility; + TF_RET_CHECK(nvidia_compute_compatibility == gpu_version_) << "Compute capability mismatch; expected {" << absl::get>(gpu_version_).first << ", " << absl::get>(gpu_version_).second << "}, but was {" diff --git a/tensorflow/compiler/xla/service/gpu/gpu_executable.h b/tensorflow/compiler/xla/service/gpu/gpu_executable.h index 51e86a9f8ee..ca1d11b7b7d 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_executable.h +++ b/tensorflow/compiler/xla/service/gpu/gpu_executable.h @@ -118,7 +118,7 @@ class GpuExecutable : public Executable { // Computes annotations for each thunk and store them in thunk_annotations_. void ComputeThunkAnnotations(); - // GpuExecutable check with either AMD's ISA version, or Nvdia's major minor + // GpuExecutable check with either AMD's ISA version, or Nvidia's major minor // version for compute capability, depending on the hardware. Status CheckCompatibilityWithServiceExecutableRunOptions( const ServiceExecutableRunOptions* run_options); diff --git a/tensorflow/compiler/xla/service/gpu/gpu_fusible.cc b/tensorflow/compiler/xla/service/gpu/gpu_fusible.cc index 24738683a19..86faa807cb7 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_fusible.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_fusible.cc @@ -138,7 +138,7 @@ bool ShapesCompatibleForMultiOutputFusion(const HloInstruction& instr1, }; // Multi-output fusion kernels share a common parallel loop. The loop - // dimenstions are determined by instruction shapes. + // dimensions are determined by instruction shapes. auto get_loop_shape = [&](const HloInstruction* element_instr) { // Special-case reduction-to-vector ops: The loop dimensions are determined // by the shape of the first operand. diff --git a/tensorflow/compiler/xla/service/gpu/gpu_fusible.h b/tensorflow/compiler/xla/service/gpu/gpu_fusible.h index 145975e6f49..9d5246c3600 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_fusible.h +++ b/tensorflow/compiler/xla/service/gpu/gpu_fusible.h @@ -37,7 +37,7 @@ bool IsLoopFusible(const HloInstruction& instr); // The code emitted for reduce-rooted input fusions (EmitReductionToVector) // suffers from poor data locality if the layouts of input parameters differ. In -// such situtations it is better not to fuse. Only input params with +// such situations it is better not to fuse. Only input params with // maximum rank are considered. Params with smaller ranks will be broadcasted // and have not been observed to cause data locality issues. // TODO(b/111977086): Improve reduce emitters to remove this limitation. diff --git a/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc b/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc index 75c9d93c63b..f4644c1765b 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc @@ -87,7 +87,7 @@ HeuristicLayoutAssignment(const HloInstruction* instr, // 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. + // evidence. // * 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. diff --git a/tensorflow/compiler/xla/service/gpu/gpu_transfer_manager.cc b/tensorflow/compiler/xla/service/gpu/gpu_transfer_manager.cc index 11a829a12b4..05fa798dc39 100644 --- a/tensorflow/compiler/xla/service/gpu/gpu_transfer_manager.cc +++ b/tensorflow/compiler/xla/service/gpu/gpu_transfer_manager.cc @@ -165,7 +165,7 @@ Status GpuTransferManager::TransferLiteralFromOutfeed( absl::make_unique(literal, index)); }); - // Give the tree of buffers to the outfeed mananger. The device will fill it + // Give the tree of buffers to the outfeed manager. The device will fill it // while we're waiting for it below. gpu::OutfeedManager* outfeed_manager = gpu::GetOrCreateOutfeedManager(); outfeed_manager->EnqueueDestination(&outfeed_buffers); diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc index 2f8fd5e01cf..b65c5c7461d 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc @@ -250,7 +250,7 @@ int ComputeMaxUnrollFactor(const HloInstruction* hlo) { // Otherwise, the return type is i64. llvm::Type* GetIndexTypeForKernel(const HloInstruction* hlo, int64 launch_size, llvm::IRBuilder<>* b) { - // Find the unnested hlo instructon for which the kernel is generated for. + // Find the unnested hlo instruction for which the kernel is generated for. const HloInstruction* unnested_hlo = hlo; const HloComputation* computation = hlo->parent(); if (computation->IsFusionComputation()) { @@ -2888,7 +2888,7 @@ ReductionCodegenInfo IrEmitterUnnested::ComputeReductionCodegenInfo( int64 tile_size_y = reduction_tiling[1]; int64 block_size_z = reduction_tiling[0]; bool dilated_x = - !reduction_dimensions.is_row_reduction && + reduction_dimensions.is_row_reduction || !IsUnrollingColumnReductionBeneficial(unnested_hlo, input_shape, reduction_dimensions.dimensions[2]); diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h index 8df348bc5c0..fb64da6b43e 100644 --- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h +++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h @@ -45,7 +45,7 @@ namespace gpu { // Examples of things that are not unnested computations: // // - The reducer of a kReduce HLO. This is emitted using IrEmitterNested. -// - The body of a fusion node. IrEmitterUnenested emits the relevant code +// - The body of a fusion node. IrEmitterUnnested emits the relevant code // within a kernel function using FusedIrEmitter. (FusedIrEmitter is not // really an IrEmitter, but is more an "IR generator generator".) // diff --git a/tensorflow/compiler/xla/service/gpu/kernel_mapping_scheme.h b/tensorflow/compiler/xla/service/gpu/kernel_mapping_scheme.h index 2eede7036cf..218f45631f5 100644 --- a/tensorflow/compiler/xla/service/gpu/kernel_mapping_scheme.h +++ b/tensorflow/compiler/xla/service/gpu/kernel_mapping_scheme.h @@ -37,7 +37,7 @@ namespace gpu { // Currently, there are two main use cases for a tiling scheme. First, we // implement kernels with 0-2-1 memory transpose using shared memory to improve // memory access pattern. Second, we implement reduction to contiguous -// dimensions in layout, with or without memory tranpsose, to achieve better +// dimensions in layout, with or without memory transpose, to achieve better // memory access pattern as well as to reduce the need numbers of executed // expensive instructions, such as thread synchronization related instructions // and atomic operations. For both use cases, we can apply a normalization to diff --git a/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc b/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc index d7ca14ad273..b4d9750e464 100644 --- a/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc +++ b/tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/gpu_backend_lib.cc @@ -522,7 +522,7 @@ StatusOr CompileToPtx(llvm::Module* module, GpuVersion gpu_version, std::unique_ptr target_machine = NVPTXGetTargetMachine( default_target_triple, *compute_capability, hlo_module_config); - // Link with libdeivce, and optimize the LLVM module. + // Link with libdevice, and optimize the LLVM module. TF_RETURN_IF_ERROR(LinkAndOptimizeModule( module, gpu_version, hlo_module_config, libdevice_dir_path, NVPTXTargetModuleLinker, default_target_triple, target_machine.get(), @@ -546,7 +546,7 @@ static std::vector GetROCDLPaths(int amdgpu_version, {"hc.amdgcn.bc", "opencl.amdgcn.bc", "ocml.amdgcn.bc", "ockl.amdgcn.bc", "oclc_finite_only_off.amdgcn.bc", "oclc_daz_opt_off.amdgcn.bc", "oclc_correctly_rounded_sqrt_on.amdgcn.bc", - "oclc_unsafe_math_off.amdgcn.bc"}); + "oclc_unsafe_math_off.amdgcn.bc", "oclc_wavefrontsize64_on.amdgcn.bc"}); // Construct full path to ROCDL bitcode libraries. std::vector result; diff --git a/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc b/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc index 594a423bda9..ccb1b7311b7 100644 --- a/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc +++ b/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc @@ -136,7 +136,7 @@ std::vector GetProducerConsumerMultiOutputFusionCandidates( // Do not fuse a producer if the other operands of the fusion are // reachable from the producer, this would create a cycle. auto operand_reachable_from_producer = [&](const HloInstruction* operand) { - // If a get-tuple-elment instruction is not in the reachability + // If a get-tuple-element instruction is not in the reachability // map, it has been created by fusion in this pass. Simply move // on to its operand, which is in the reachability map. if (!reachability.IsPresent(operand) && diff --git a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc index 6635b68899d..fa01d75d35a 100755 --- a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc +++ b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc @@ -99,7 +99,7 @@ string GetLibdeviceDir(const HloModuleConfig& hlo_module_config) { "uses routines from libdevice.", hlo_module_config); - // GetCudaRootCandidates always inclues ".", but but if everything fails, we + // GetCudaRootCandidates always includes ".", but but if everything fails, we // return it anyway. Better than returning the empty string. return "."; } diff --git a/tensorflow/compiler/xla/service/gpu/stream_executor_util.h b/tensorflow/compiler/xla/service/gpu/stream_executor_util.h index 3e2ae241a03..684143b2d04 100644 --- a/tensorflow/compiler/xla/service/gpu/stream_executor_util.h +++ b/tensorflow/compiler/xla/service/gpu/stream_executor_util.h @@ -82,7 +82,7 @@ se::GpuAsmOpts PtxOptsFromConfig(const HloModuleConfig& hlo_module_config); // `buffer_type` determines what buffer would be filled out with. // // Precondition: `buffer_type` is a floating point type, `rng_state` needs to be -// initalized to zero on the first use. +// initialized to zero on the first use. void InitializeBuffer(se::Stream* stream, PrimitiveType buffer_type, int64* rng_state, se::DeviceMemoryBase buffer); diff --git a/tensorflow/compiler/xla/service/gpu/thunk_emitter.h b/tensorflow/compiler/xla/service/gpu/thunk_emitter.h index 55d92c74794..49d71192e77 100644 --- a/tensorflow/compiler/xla/service/gpu/thunk_emitter.h +++ b/tensorflow/compiler/xla/service/gpu/thunk_emitter.h @@ -25,7 +25,7 @@ namespace xla { namespace gpu { // Implements handling of GPU execution for HLO operations that are handed off -// to specialzied thunks that do not require code generation. Intended to be +// to specialized thunks that do not require code generation. Intended to be // mixed into GPU emitters. class ThunkEmitter { public: diff --git a/tensorflow/compiler/xla/service/heap_simulator.h b/tensorflow/compiler/xla/service/heap_simulator.h index 7e9ccd5866b..ac047de3ec7 100644 --- a/tensorflow/compiler/xla/service/heap_simulator.h +++ b/tensorflow/compiler/xla/service/heap_simulator.h @@ -254,8 +254,8 @@ class HeapAlgorithm { Alloc(buffer, size); } - // Finish collects the buffer offset assignment results. Free may only be - // called once, after the Alloc and Free calls. + // Finish collects the buffer offset assignment results. Finish may only be + // called once, after all Alloc and Free calls. virtual Result Finish() = 0; }; diff --git a/tensorflow/compiler/xla/service/hlo_alias_analysis.cc b/tensorflow/compiler/xla/service/hlo_alias_analysis.cc index 1810accebfc..384ae272dc1 100644 --- a/tensorflow/compiler/xla/service/hlo_alias_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_alias_analysis.cc @@ -185,7 +185,7 @@ class BufferValueMap { }; // If the value shows up in a root instruction, alias it with parameter - // intruction. + // instruction. for (const HloPosition& pos : value.positions()) { if (pos.instruction == module_->entry_computation()->root_instruction()) { ShapeIndex output_index = pos.index; @@ -404,7 +404,7 @@ bool HloAliasAnalysis::InstructionBuffersAreDistinct( } } else { // It's possible for multiple values at this index to have the same - // HloBuffer. This does not result in non-distictness. To account for + // HloBuffer. This does not result in non-distinctness. To account for // this case, add all of the buffers at this index after checking // whether each buffer exists at an earlier index. This is a corner // case, however, as the number of values at an index is almost always diff --git a/tensorflow/compiler/xla/service/hlo_buffer.h b/tensorflow/compiler/xla/service/hlo_buffer.h index 91597d6f705..870a1a78994 100644 --- a/tensorflow/compiler/xla/service/hlo_buffer.h +++ b/tensorflow/compiler/xla/service/hlo_buffer.h @@ -54,7 +54,7 @@ namespace xla { // HloValue{%cond_param}. // // HloBuffers may appear at different HloPositions in the module mirroring the -// same propery of HloValues. For example: +// same property of HloValues. For example: // // %sub = Sub(...) // %add = Add(...) diff --git a/tensorflow/compiler/xla/service/hlo_casting_utils.h b/tensorflow/compiler/xla/service/hlo_casting_utils.h index 7f73bba0365..4cae37add73 100644 --- a/tensorflow/compiler/xla/service/hlo_casting_utils.h +++ b/tensorflow/compiler/xla/service/hlo_casting_utils.h @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -// Casting utilitiy functions for HLO instructions. +// Casting utility functions for HLO instructions. #ifndef TENSORFLOW_COMPILER_XLA_SERVICE_HLO_CASTING_UTILS_H_ #define TENSORFLOW_COMPILER_XLA_SERVICE_HLO_CASTING_UTILS_H_ diff --git a/tensorflow/compiler/xla/service/hlo_computation.cc b/tensorflow/compiler/xla/service/hlo_computation.cc index c2bbe3ccd71..fa116ae9da1 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.cc +++ b/tensorflow/compiler/xla/service/hlo_computation.cc @@ -838,7 +838,7 @@ bool HloComputation::Equal(const HloComputation& other, continue; } visited.emplace(pair); - // TODO(b/123082518): Avoid recursively invoking == becasue it may + // TODO(b/123082518): Avoid recursively invoking == because it may // cause a stack overflow with deeply nested subcomputations. bool identical_ignoring_operands = pair.first->Identical( *pair.second, diff --git a/tensorflow/compiler/xla/service/hlo_computation.h b/tensorflow/compiler/xla/service/hlo_computation.h index 81c6bfc3ecf..9ca60403929 100644 --- a/tensorflow/compiler/xla/service/hlo_computation.h +++ b/tensorflow/compiler/xla/service/hlo_computation.h @@ -419,7 +419,7 @@ class HloComputation { // the HLO computation with the exception of fusion computation. A parameter // instruction is removable for a fusion computation. // - // Note that IsSafelyRemovable() is a necassarily condition to remove an + // Note that IsSafelyRemovable() is a necessary condition to remove an // instruction rather than a sufficient condition. For example, instructions // with side-effect (e.g., Send, Infeed) may be removed from a computation, // but the transformation must guarantee the invariants relevant to the diff --git a/tensorflow/compiler/xla/service/hlo_cost_analysis.cc b/tensorflow/compiler/xla/service/hlo_cost_analysis.cc index 10ec9ea3757..38231df1f1d 100644 --- a/tensorflow/compiler/xla/service/hlo_cost_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_cost_analysis.cc @@ -324,7 +324,7 @@ Status HloCostAnalysis::HandleDot(const HloInstruction* dot) { for (auto dim : dnums.lhs_contracting_dimensions()) { reduction_width *= lhs_shape.dimensions(dim); } - // Each output elment requires reduction_width FMA operations. + // Each output element requires reduction_width FMA operations. current_properties_[kFlopsKey] = kFmaFlops * ShapeUtil::ElementsIn(dot_shape) * reduction_width; return Status::OK(); diff --git a/tensorflow/compiler/xla/service/hlo_cost_analysis.h b/tensorflow/compiler/xla/service/hlo_cost_analysis.h index 8df700802b6..6da93d28079 100644 --- a/tensorflow/compiler/xla/service/hlo_cost_analysis.h +++ b/tensorflow/compiler/xla/service/hlo_cost_analysis.h @@ -149,7 +149,7 @@ class HloCostAnalysis : public ConstDfsHloVisitor { // if the HLO was not found to have a cost in the analysis. // // Note that the cost for sub HLO instructions are also returned if asked. For - // example, body and condidition of a while, fused instructions within a + // example, body and condition of a while, fused instructions within a // fusion, or the add instruction of a reduce. int64 flop_count(const HloInstruction& hlo) const; int64 transcendental_count(const HloInstruction& hlo) const; diff --git a/tensorflow/compiler/xla/service/hlo_cse_test.cc b/tensorflow/compiler/xla/service/hlo_cse_test.cc index 1eb0260468c..ba27611c6b0 100644 --- a/tensorflow/compiler/xla/service/hlo_cse_test.cc +++ b/tensorflow/compiler/xla/service/hlo_cse_test.cc @@ -382,9 +382,9 @@ condition=%condition.1, body=%body // Test two while loops with identical bodies and same inputs, but different // conditions -TEST_F(HloCseTest, WhileLoopsIdenticalBodiesAndInputDifferntConditions) { +TEST_F(HloCseTest, WhileLoopsIdenticalBodiesAndInputDifferentConditions) { const char* const hlo_string = R"( - HloModule WhileLoopsIdenticalBodiesAndInputDifferntConditions + HloModule WhileLoopsIdenticalBodiesAndInputDifferentConditions %body (param: (f32[], f32[])) -> (f32[], f32[]) { %param = (f32[], f32[]) parameter(0) @@ -404,7 +404,7 @@ index=1 %add = f32[] add(f32[] %get-tuple-element, f32[] %get-tuple-element.1) ROOT %constant.1 = pred[] constant(true) } - ENTRY %WhileLoopsIdenticalBodiesAndInputDifferntConditions () -> (f32[], + ENTRY %WhileLoopsIdenticalBodiesAndInputDifferentConditions () -> (f32[], f32[]) { %constant.2 = f32[] constant(1) %constant.3 = f32[] constant(2) %tuple.1 = (f32[], f32[]) tuple(f32[] %constant.2, f32[] %constant.3) %while = (f32[], f32[]) while((f32[], f32[]) %tuple.1), diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc index ecfa6703f00..11d3c5fdbd0 100644 --- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc @@ -1092,7 +1092,7 @@ bool HloDataflowAnalysis::CanShareOperandBufferWithUser( // TODO(b/80315712): This code is in a bit of a weird intermediate state // at the moment. The in-place DUS check really needs to be common to all // backends, so it runs first. Then we run the backend-specific check if - // provided, or go through the target-indepdendent check if not. + // provided, or go through the target-independent check if not. // Unfortunately, the notionally "target-independent" path actually contains // some target-specific code, so we can't run all of it *in addition* to the // target-specific function, like the interface documentation says. diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc index d6617dea1c4..330779b5ebd 100644 --- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc +++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc @@ -1857,7 +1857,7 @@ TEST_P(HloDataflowAnalysisTest, NestedConditionals) { // inner_conditional((PRED, F32[], F32[]) %param_cond): // %pred_cond = GetTupleElement(%param_cond, 0) // %true_operand_cond = GetTupleElement(%param_cond, 1) - // %false_opearnd_cond = GetTupleElement(%param_cond, 2) + // %false_operand_cond = GetTupleElement(%param_cond, 2) // return Conditional(%pred_cond, %true_operand_cond, computation1, // %false_operand_cond, computation2) // diff --git a/tensorflow/compiler/xla/service/hlo_domain_isolator.h b/tensorflow/compiler/xla/service/hlo_domain_isolator.h index 2274c3a96c2..1fa996dd683 100644 --- a/tensorflow/compiler/xla/service/hlo_domain_isolator.h +++ b/tensorflow/compiler/xla/service/hlo_domain_isolator.h @@ -35,7 +35,7 @@ class HloDomainIsolator : public HloModulePass { // Creates a new kDomain instruction for the edge between the use instruction // (the first HloInstruction argument), and the operand instruction (the // third HloInstruction argument) if the interesting attribute of the - // instruction differes from the attribute of the root (the second + // instruction differences from the attribute of the root (the second // HloInstruction argument). // Returns nullptr in case no domain separation is necessary. using DomainCreator = std::function HloDomainRemover::RunContext::Run() { VLOG(4) << "Processing metadata domain: '" << remover_->kind_ << "'"; int64 removed_domains = 0; for (HloComputation* computation : module_->computations()) { - // First create the domain instruciton sets. A domain instruction set is + // First create the domain instruction sets. A domain instruction set is // the set of instructions whose edges never cross a kDomain instruction. TF_ASSIGN_OR_RETURN(std::unique_ptr domain_map, HloDomainMap::Create(computation, remover_->kind_)); diff --git a/tensorflow/compiler/xla/service/hlo_domain_test.cc b/tensorflow/compiler/xla/service/hlo_domain_test.cc index fd4fb0246d8..c2e0c907a24 100644 --- a/tensorflow/compiler/xla/service/hlo_domain_test.cc +++ b/tensorflow/compiler/xla/service/hlo_domain_test.cc @@ -617,7 +617,7 @@ ENTRY entry { auto tuple0 = FindInstruction(module.get(), "tuple.0"); tuple0->clear_sharding(); - // Insert the following instructons above and below tuple.0, to emulate other + // Insert the following instructions above and below tuple.0, to emulate other // passes effects: // COPY.0 // \ / diff --git a/tensorflow/compiler/xla/service/hlo_domain_verifier.cc b/tensorflow/compiler/xla/service/hlo_domain_verifier.cc index dc514ae3e5c..f8e1973d5b9 100644 --- a/tensorflow/compiler/xla/service/hlo_domain_verifier.cc +++ b/tensorflow/compiler/xla/service/hlo_domain_verifier.cc @@ -35,7 +35,7 @@ class HloDomainVerifier::RunContext { private: // If the verifier caller passed an empty vector for kinds, we collect all the - // avalable domain types. + // available domain types. Status PopulateDomainKinds(); HloModule* module_; @@ -67,7 +67,7 @@ Status HloDomainVerifier::RunContext::Run() { TF_RETURN_IF_ERROR(PopulateDomainKinds()); for (HloComputation* computation : module_->computations()) { for (auto& kind : verifier_->kinds_) { - // First create the domain instruciton sets. A domain instruction set is + // First create the domain instruction sets. A domain instruction set is // the set of instructions whose edges never cross a kDomain instruction. TF_ASSIGN_OR_RETURN(std::unique_ptr domain_map, HloDomainMap::Create(computation, kind)); diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.cc b/tensorflow/compiler/xla/service/hlo_evaluator.cc index 2145be59aca..b2435d3fdf3 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.cc +++ b/tensorflow/compiler/xla/service/hlo_evaluator.cc @@ -1133,7 +1133,7 @@ bool CopyDataFromInput(const Literal& input_literal, int64 input_start, auto base_case = [&](int64 axis, int64 dst_index, int64 src_index, bool within_src_bounds) { if (axis == 0) { - // For IRFFT, the negavie frequencies are only needed for the sweep along + // For IRFFT, the negative frequencies are only needed for the sweep along // the X axis, which is performed last. Leave this part of the working set // uninitialized until then. const int64 length = fft_lengths[axis]; @@ -1684,7 +1684,7 @@ class OutputOffsetIndexToInputIndex { std::vector input_index_; }; -// Rehapes the gather indices input to have a trailing degenerate `1` dimension +// Reshapes the gather indices input to have a trailing degenerate `1` dimension // if necessary. Hands over the ownership of the newly created literal (if // there is one) to `reshaped_start_indices`. static StatusOr> ReshapedGatherIndices( diff --git a/tensorflow/compiler/xla/service/hlo_evaluator.h b/tensorflow/compiler/xla/service/hlo_evaluator.h index de5a9aa4c2c..fc9d42c1b17 100644 --- a/tensorflow/compiler/xla/service/hlo_evaluator.h +++ b/tensorflow/compiler/xla/service/hlo_evaluator.h @@ -253,7 +253,7 @@ class HloEvaluator : public DfsHloVisitorWithDefault { Status HandleCustomCall(HloInstruction* custom_call) override; // Unsupported HLOs, note some of them (such as BatchNorm*) are typically - // expanded in a semantic-preserving way into other HLOs by adding exanpsion + // expanded in a semantic-preserving way into other HLOs by adding expansion // HLO pass to the HLO optimization pass during compilation, which can then be // handled by the evaluator. Status HandleBatchNormGrad(HloInstruction* batch_norm_grad) override { @@ -304,7 +304,7 @@ class HloEvaluator : public DfsHloVisitorWithDefault { // // TODO(b/35950897): have better memory management here to free instructions // that are no longer a parent for any other subsequent instruction in - // post-orderring. + // post-ordering. // // Must be cleared for each evaluation. // diff --git a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc index 507867c013d..516a4283448 100644 --- a/tensorflow/compiler/xla/service/hlo_graph_dumper.cc +++ b/tensorflow/compiler/xla/service/hlo_graph_dumper.cc @@ -387,7 +387,7 @@ class HloDotDumper { const HloExecutionProfile* profile_; // may be null const NodeFilter filter_; - // Each HloInstruction dumped gets a monotically-increasing node ID. This + // Each HloInstruction dumped gets a monotonically-increasing node ID. This // must start at 1, because that's where graphviz's accounting starts. int64 next_node_id_ = 1; absl::flat_hash_map node_ids_; diff --git a/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h b/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h index 6bd34f8a127..689007ff9ab 100644 --- a/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h +++ b/tensorflow/compiler/xla/service/hlo_input_output_alias_config.h @@ -81,8 +81,8 @@ class HloInputOutputAliasConfig { // Checks whether the provided output index has already been aliased. bool OutputHasAlias(const ShapeIndex& output_index) const; - // (De)Serializes an HloInputOutoutAliasConfig to/from an - // HloInputOutoutAliasProto. + // (De)Serializes an HloInputOutputAliasConfig to/from an + // HloInputOutputAliasProto. HloInputOutputAliasProto ToProto() const; static StatusOr CreateFromProto( diff --git a/tensorflow/compiler/xla/service/hlo_instruction.cc b/tensorflow/compiler/xla/service/hlo_instruction.cc index bc099371d08..f8fbaf19c5c 100755 --- a/tensorflow/compiler/xla/service/hlo_instruction.cc +++ b/tensorflow/compiler/xla/service/hlo_instruction.cc @@ -1637,7 +1637,7 @@ HloInstruction::~HloInstruction() { operands_[operand_num] = nullptr; } - // Update users. Set `nullptr` to the correpsonding operand slot for users. + // Update users. Set `nullptr` to the corresponding operand slot for users. for (auto& user : this->users()) { for (int i = 0; i < user->operand_count(); ++i) { if (user->operands_[i] == this) { @@ -1820,6 +1820,12 @@ void HloInstruction::AddUser(HloInstruction* user) { } } +int64 HloInstruction::UserId(HloInstruction* user) { + auto result = user_map_.find(user); + CHECK(result != user_map_.end()); + return result->second; +} + bool HloInstruction::HasConstantOperand() const { for (const HloInstruction* operand : operands_) { if (operand->IsConstant()) { @@ -2693,7 +2699,7 @@ bool HloInstruction::IsFusible() const { case HloOpcode::kReduce: case HloOpcode::kReduceWindow: return true; - // Side effecting instrutions cannot be fused. + // Side effecting instructions cannot be fused. default: return !HasSideEffect(); } diff --git a/tensorflow/compiler/xla/service/hlo_instruction.h b/tensorflow/compiler/xla/service/hlo_instruction.h index 238a96e52a0..2ab606d7100 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction.h +++ b/tensorflow/compiler/xla/service/hlo_instruction.h @@ -643,7 +643,7 @@ class HloInstruction { const std::vector& replica_groups, const absl::optional& channel_id); - // Creates a communitation instructions that permutes data cross replicas. + // Creates a communication instructions that permutes data cross replicas. // Data is sent/received according to the (source_replica_id, // target_replica_id) pairs in `source_target_pairs`. If a replica id is not a // target_replica_id in any pair, the output on that replica is a tensor @@ -1001,6 +1001,11 @@ class HloInstruction { // Returns the users of this instruction. const std::vector& users() const { return users_; } + // Returns the index of the user in the users() vector. + // + // Precondition: `user` is a user of the instruction. + int64 UserId(HloInstruction* user); + // Returns true if this instruction is a user of 'instruction'. bool IsUserOf(const HloInstruction* instruction) const { return ContainsKey(instruction->user_map_, this); diff --git a/tensorflow/compiler/xla/service/hlo_instruction_test.cc b/tensorflow/compiler/xla/service/hlo_instruction_test.cc index a9d9eb9cfa4..e5735bea843 100644 --- a/tensorflow/compiler/xla/service/hlo_instruction_test.cc +++ b/tensorflow/compiler/xla/service/hlo_instruction_test.cc @@ -1545,7 +1545,7 @@ TEST_F(HloInstructionTest, StringifyScatter) { "to_apply=%Scatter.update"); } -TEST_F(HloInstructionTest, CanonnicalStringificationFusion) { +TEST_F(HloInstructionTest, CanonicalStringificationFusion) { // Tests stringification of a simple op, fusion, while, and conditional. const Shape s1 = ShapeUtil::MakeShape(F32, {5, 10}); const Shape s2 = ShapeUtil::MakeShape(F32, {20, 10}); @@ -1587,7 +1587,7 @@ TEST_F(HloInstructionTest, CanonnicalStringificationFusion) { EXPECT_EQ(fusion->ToString(options), expected_fusion); } -TEST_F(HloInstructionTest, CanonnicalStringificationWhile) { +TEST_F(HloInstructionTest, CanonicalStringificationWhile) { // Tests stringification of a simple op, fusion, while, and conditional. const Shape s1 = ShapeUtil::MakeShape(F32, {5, 10}); const Shape s2 = ShapeUtil::MakeShape(F32, {20, 10}); @@ -1643,7 +1643,7 @@ TEST_F(HloInstructionTest, CanonnicalStringificationWhile) { EXPECT_EQ(loop->ToString(options), expected_loop); } -TEST_F(HloInstructionTest, CanonnicalStringificationConditional) { +TEST_F(HloInstructionTest, CanonicalStringificationConditional) { // Tests stringification of a simple op, fusion, while, and conditional. const Shape s1 = ShapeUtil::MakeShape(F32, {5, 10}); const Shape s2 = ShapeUtil::MakeShape(F32, {20, 10}); diff --git a/tensorflow/compiler/xla/service/hlo_instructions.cc b/tensorflow/compiler/xla/service/hlo_instructions.cc index a150efd8c83..94b5926d876 100644 --- a/tensorflow/compiler/xla/service/hlo_instructions.cc +++ b/tensorflow/compiler/xla/service/hlo_instructions.cc @@ -1356,7 +1356,7 @@ void HloFusionInstruction::MergeFusionInstructionIntoMultiOutput( HloFusionInstruction* instruction_to_merge) { // Add all non-parameter fused instructions to 'unfused_instructions' to be // merged into 'this'. `old_to_new' maps the instructions in the fused node - // to the disaseembled fusion instructions. + // to the disassembled fusion instructions. // Note that we add the unfused instructions to this->parent_ computation. // This is necessary because the unique_id needs for an instruction and // it's only added when inserting to the computation. diff --git a/tensorflow/compiler/xla/service/hlo_instructions.h b/tensorflow/compiler/xla/service/hlo_instructions.h index 1863c78e7e1..d2a67eb6979 100755 --- a/tensorflow/compiler/xla/service/hlo_instructions.h +++ b/tensorflow/compiler/xla/service/hlo_instructions.h @@ -768,7 +768,7 @@ class HloFusionInstruction : public HloInstruction { // Merges the fused instructions from 'instruction_to_merge' into the // fused instruction set of 'this', updating operands as necessary. // - // Predondition: 'instruction_to_merge' must be an operand of 'this'. + // Precondition: 'instruction_to_merge' must be an operand of 'this'. void MergeFusionInstruction(HloFusionInstruction* instruction_to_merge); // Merges the fused instructions from instruction_to_merge into the fused @@ -1103,8 +1103,7 @@ class HloConvolutionInstruction : public HloInstruction { void set_feature_group_count(int64 num_feature_groups) { feature_group_count_ = num_feature_groups; } - // The number of feature groups. Must be a divisor of the input batch - // dimension. + // The number of batch groups. Must be a divisor of the input batch dimension. int64 batch_group_count() const { return batch_group_count_; } void set_batch_group_count(int64 num_batch_groups) { batch_group_count_ = num_batch_groups; @@ -1138,8 +1137,7 @@ class HloConvolutionInstruction : public HloInstruction { // The number of feature groups. Must be a divisor of the input feature // dimension and output feature dimension. int64 feature_group_count_; - // The number of feature groups. Must be a divisor of the input batch - // dimension. + // The number of batch groups. Must be a divisor of the input batch dimension. int64 batch_group_count_; // Describes the window used for a convolution. Window window_; diff --git a/tensorflow/compiler/xla/service/hlo_live_range_test.cc b/tensorflow/compiler/xla/service/hlo_live_range_test.cc index 232c6b95e88..e2d320beffd 100644 --- a/tensorflow/compiler/xla/service/hlo_live_range_test.cc +++ b/tensorflow/compiler/xla/service/hlo_live_range_test.cc @@ -144,7 +144,7 @@ TEST_F(HloLiveRangeTest, MultiplyAdd) { } TEST_F(HloLiveRangeTest, LiveOutBuffers) { - // If a buffer is live out, its life range is extened to the end of + // If a buffer is live out, its life range is extended to the end of // computation. auto builder = HloComputation::Builder(TestName()); auto paramA = builder.AddInstruction( @@ -181,7 +181,7 @@ TEST_F(HloLiveRangeTest, LiveOutBuffers) { } TEST_F(HloLiveRangeTest, InstructionScheduledAfterRoot) { - // If a buffer is live out, its life range is extened to the end of + // If a buffer is live out, its life range is extended to the end of // computation. auto builder = HloComputation::Builder(TestName()); auto paramA = builder.AddInstruction( diff --git a/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc b/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc index e14bcfa7f67..994c6628f43 100644 --- a/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc +++ b/tensorflow/compiler/xla/service/hlo_liveness_analysis.cc @@ -113,7 +113,7 @@ void MarkLiveAtAllIndices(const HloInstruction* instruction, // Propagates liveness through Tuple instructions. // *) For each tuple operand: // *) For tuple output shape index associated with operand: -// *) Propgate live shape indices to tuple operand at the associated +// *) Propagate live shape indices to tuple operand at the associated // shape index in the operands output, and add to worklist. void PropagateLivenessThroughTuple( const HloInstruction* instruction, @@ -260,7 +260,7 @@ HloLivenessAnalysis::HloLivenessAnalysis(const HloModule& module) void HloLivenessAnalysis::RunAnalysis() { Worklist worklist; Workset workset; - // Add entry compuation root instruction. + // Add entry computation root instruction. MarkLiveAtAllIndices(module_.entry_computation()->root_instruction(), &live_index_map_, &worklist, &workset); for (auto* computation : module_.computations()) { diff --git a/tensorflow/compiler/xla/service/hlo_liveness_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_liveness_analysis_test.cc index 35db6aa0635..03d353aa1e0 100644 --- a/tensorflow/compiler/xla/service/hlo_liveness_analysis_test.cc +++ b/tensorflow/compiler/xla/service/hlo_liveness_analysis_test.cc @@ -136,7 +136,7 @@ TEST_F(HloLivenessAnalysisTest, NestedTupleAtEntryRoot) { EXPECT_TRUE(liveness.IsLive(GetInstruction(module.get(), "constant.3"), {})); } -// Tests that GTE at entry root of Tuple instruction only propgates liveness +// Tests that GTE at entry root of Tuple instruction only propagates liveness // to the live elements in tuple. TEST_F(HloLivenessAnalysisTest, GteOfTuple) { auto module = ParseAndReturnVerifiedModule(R"( @@ -158,7 +158,7 @@ TEST_F(HloLivenessAnalysisTest, GteOfTuple) { EXPECT_FALSE(liveness.IsLive(GetInstruction(module.get(), "constant.2"), {})); } -// Tests that GTE at entry root of nested Tuple instruction only propgates +// Tests that GTE at entry root of nested Tuple instruction only propagates // liveness to the live elements in tuple. TEST_F(HloLivenessAnalysisTest, GteOfNestedTuple) { auto module = ParseAndReturnVerifiedModule(R"( @@ -196,7 +196,7 @@ TEST_F(HloLivenessAnalysisTest, GteOfNestedTuple) { } // Tests that GTE of GTE (at entry root) of nested Tuple instruction only -// propgates liveness to the live elements in tuple. +// propagates liveness to the live elements in tuple. TEST_F(HloLivenessAnalysisTest, GteOfGteOfNestedTuple) { auto module = ParseAndReturnVerifiedModule(R"( HloModule SimpleModule diff --git a/tensorflow/compiler/xla/service/hlo_memory_scheduler.cc b/tensorflow/compiler/xla/service/hlo_memory_scheduler.cc index 50eaee95455..bda297540ff 100644 --- a/tensorflow/compiler/xla/service/hlo_memory_scheduler.cc +++ b/tensorflow/compiler/xla/service/hlo_memory_scheduler.cc @@ -68,7 +68,7 @@ using ::tensorflow::strings::HumanReadableNumBytes; // A D E F B C G // , which has a maximum memory usage of 6 (B is alive while F is executing). // -// An optimal way to shedule the previous graph is: +// An optimal way to schedule the previous graph is: // A B C D E F G // , which has a maximum memory usage of 5 (when F is executing). // diff --git a/tensorflow/compiler/xla/service/hlo_module.h b/tensorflow/compiler/xla/service/hlo_module.h index 3e9630a13c4..5e662e0bebc 100644 --- a/tensorflow/compiler/xla/service/hlo_module.h +++ b/tensorflow/compiler/xla/service/hlo_module.h @@ -286,7 +286,7 @@ class HloModule { // Returns true if the module has a schedule set. bool has_schedule() const { return schedule_.has_value(); } - // Returns the schedue of the module. CHECK fails if no schedule is set. + // Returns the schedule of the module. CHECK fails if no schedule is set. const HloSchedule& schedule() const { return *schedule_; } HloSchedule& schedule() { return *schedule_; } diff --git a/tensorflow/compiler/xla/service/hlo_module_dce_test.cc b/tensorflow/compiler/xla/service/hlo_module_dce_test.cc index dba699dd8c5..301faa75f0a 100644 --- a/tensorflow/compiler/xla/service/hlo_module_dce_test.cc +++ b/tensorflow/compiler/xla/service/hlo_module_dce_test.cc @@ -187,7 +187,7 @@ TEST_F(HloModuleDceTest, OneWhileWithDeadTupleElement) { } // Tests that a tuple element {1} used by condition computation (which appears -// dead in while.body{1} and at while.result{1}) propgates liveness of this +// dead in while.body{1} and at while.result{1}) propagates liveness of this // tuple element to while.body{1} and at while.result{1}. TEST_F(HloModuleDceTest, OneWhileWithTupleElementUsedByCond) { auto module = ParseAndReturnVerifiedModule(R"( diff --git a/tensorflow/compiler/xla/service/hlo_module_group_util.h b/tensorflow/compiler/xla/service/hlo_module_group_util.h index d388fe51d0d..12a4614412a 100644 --- a/tensorflow/compiler/xla/service/hlo_module_group_util.h +++ b/tensorflow/compiler/xla/service/hlo_module_group_util.h @@ -103,7 +103,7 @@ class HloModuleGroupUtil { absl::Span computations); // Updates the reachability of the given instruction, taking the global - // predeccessorss and successors into account. + // predecessors and successors into account. void UpdateReachabilityThroughInstruction( HloInstruction* instruction, HloReachabilityMap* reachability_map); diff --git a/tensorflow/compiler/xla/service/hlo_ordering_test.cc b/tensorflow/compiler/xla/service/hlo_ordering_test.cc index 2b77619f89b..f8295d579fb 100644 --- a/tensorflow/compiler/xla/service/hlo_ordering_test.cc +++ b/tensorflow/compiler/xla/service/hlo_ordering_test.cc @@ -506,7 +506,7 @@ TEST_F(HloOrderingTest, InterferenceWithOuterRoot) { absl::string_view hlo_string = R"( HloModule InterferenceWithOuterRoot, is_scheduled=true -Emmbedded (embedded_param: f32[4096,4096]) -> f32[4096,4096] { +Embedded (embedded_param: f32[4096,4096]) -> f32[4096,4096] { embedded_param = f32[4096,4096]{1,0} parameter(0) multiply = f32[4096,4096]{1,0} multiply(embedded_param, embedded_param) ROOT log = f32[4096,4096]{1,0} log(multiply) @@ -515,7 +515,7 @@ Emmbedded (embedded_param: f32[4096,4096]) -> f32[4096,4096] { ENTRY InterferenceWithOuterRoot { param = f32[4096,4096]{1,0} parameter(0) ROOT add = f32[4096,4096]{1,0} add(param, param) - call = f32[4096,4096]{1,0} call(param), to_apply=Emmbedded + call = f32[4096,4096]{1,0} call(param), to_apply=Embedded } )"; diff --git a/tensorflow/compiler/xla/service/hlo_parser.cc b/tensorflow/compiler/xla/service/hlo_parser.cc index 3ecd0af3480..4c1ef5b5686 100644 --- a/tensorflow/compiler/xla/service/hlo_parser.cc +++ b/tensorflow/compiler/xla/service/hlo_parser.cc @@ -294,7 +294,7 @@ class HloParserImpl : public HloParser { // Parses a sub-attribute of the window attribute, e.g.,size=1x2x3. bool ParseDxD(const std::string& name, std::vector* result); - // Parses window's pad sub-attriute, e.g., pad=0_0x3x3. + // Parses window's pad sub-attribute, e.g., pad=0_0x3x3. bool ParseWindowPad(std::vector>* pad); bool ParseSliceRanges(SliceRanges* result); @@ -2297,7 +2297,7 @@ bool HloParserImpl::ParseTupleLiteral(Literal* literal, const Shape& shape) { // literal, (',' literal)* for (int i = 0; i < elements.size(); i++) { if (i > 0) { - ParseToken(TokKind::kComma, "exepcts ',' to separate tuple elements"); + ParseToken(TokKind::kComma, "expects ',' to separate tuple elements"); } if (!ParseLiteral(&elements[i], ShapeUtil::GetTupleElementShape(shape, i))) { @@ -4386,6 +4386,7 @@ bool HloParserImpl::ParseSingleInstruction(HloModule* module) { for (auto& comp : computations_) { module->AddEmbeddedComputation(std::move(comp)); } + TF_CHECK_OK(module->set_schedule(ScheduleFromInstructionOrder(module))); return true; } diff --git a/tensorflow/compiler/xla/service/hlo_parser_test.cc b/tensorflow/compiler/xla/service/hlo_parser_test.cc index 29a6a5e4297..d65613fc4b8 100644 --- a/tensorflow/compiler/xla/service/hlo_parser_test.cc +++ b/tensorflow/compiler/xla/service/hlo_parser_test.cc @@ -1203,7 +1203,7 @@ ENTRY Rng { }, // Reduce precision { -"ReducePrevison", +"ReducePrecision", R"(HloModule reduce_precision ENTRY ReducePrecision { @@ -2095,7 +2095,7 @@ ENTRY %ShortConstant.v4 () -> f32[67,89] { EXPECT_EQ(result.ValueOrDie()->ToString(HloPrintOptions()), original); } -TEST_F(HloParserTest, AttibutesAnyOrder) { +TEST_F(HloParserTest, AttributesAnyOrder) { const string original = R"(HloModule any_order_module ENTRY %Convolve1D1Window_0.v3 (input: f32[1,2,1], filter: f32[1,1,1]) -> f32[1,4,1] { diff --git a/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc b/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc index e18521811c0..166ba1b0d99 100644 --- a/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc +++ b/tensorflow/compiler/xla/service/hlo_rematerialization_test.cc @@ -457,7 +457,7 @@ TEST_P(IndirectUseTest, IndirectUseNotRematerialized) { // F32[1024] %call = call(Subcomputation, {%add_1}) // F32[1024] %add_2 = add(%bcast, call) // {F32[1024], F32[1024]} %tuple = tuple(%bcast, %add_2) - // F32[1024] %gte = GetTupleElememt(%tuple, 0) + // F32[1024] %gte = GetTupleElement(%tuple, 0) // F32[1024] %negate = negate(%gte) // // Subcomputation: diff --git a/tensorflow/compiler/xla/service/hlo_runner.h b/tensorflow/compiler/xla/service/hlo_runner.h index c077ccd95fe..3b5a80ce33b 100644 --- a/tensorflow/compiler/xla/service/hlo_runner.h +++ b/tensorflow/compiler/xla/service/hlo_runner.h @@ -80,7 +80,7 @@ class HloRunner { bool run_hlo_passes = false; // If true, executes on multiple threads using se::Stream::ExecuteOnStream. - // Othewise, executes using xla::Executable::ExecuteOnStreams. + // Otherwise, executes using xla::Executable::ExecuteOnStreams. bool use_threads = false; }; diff --git a/tensorflow/compiler/xla/service/hlo_sharding.h b/tensorflow/compiler/xla/service/hlo_sharding.h index 90a80a4421b..56479add95f 100644 --- a/tensorflow/compiler/xla/service/hlo_sharding.h +++ b/tensorflow/compiler/xla/service/hlo_sharding.h @@ -120,7 +120,7 @@ class HloSharding { // Retrieves a histogram of the devices used by the sharding. The returned // map has the device number as key, and the occurrence count as value. - // If a sharding does not have a device, it will not be incuded in the + // If a sharding does not have a device, it will not be included in the // histogram. The count argument, if not nullptr, will receive the total // number of elements this sharding is made of (one for array, N leaves for // tuples). diff --git a/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc b/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc index 094d98bc6e5..837483268f3 100644 --- a/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc +++ b/tensorflow/compiler/xla/service/hlo_sharding_metadata.cc @@ -310,7 +310,7 @@ StatusOr ApplyShardingFromUsers(HloInstruction* instruction, } // Tries to propagate the sharding information into the instructions that are -// part of the domain, in a reverse post order manner (users propoagate to +// part of the domain, in a reverse post order manner (users propagate to // instruction). StatusOr ApplyDomainShardingPass(const DomainMetadata::Domain& domain, const HloSharding& domain_sharding) { diff --git a/tensorflow/compiler/xla/service/indexed_array_analysis.cc b/tensorflow/compiler/xla/service/indexed_array_analysis.cc index 4a325e5cb5b..015246c8cae 100644 --- a/tensorflow/compiler/xla/service/indexed_array_analysis.cc +++ b/tensorflow/compiler/xla/service/indexed_array_analysis.cc @@ -886,7 +886,7 @@ IndexedArrayAnalysis::ComputeArrayForElementwiseBinaryOp(HloOpcode opcode, // To figure out the broadcast dimensions for the (constant) source for the // scalar-indexed node, we "simulate" the index transformation done by the - // existing broadcsat: + // existing broadcast: enum class IndexComponent { Broadcasted, NotBroadcasted }; std::vector simulated_index( broadcast_instr->shape().dimensions_size(), IndexComponent::Broadcasted); diff --git a/tensorflow/compiler/xla/service/indexed_array_analysis_test.cc b/tensorflow/compiler/xla/service/indexed_array_analysis_test.cc index 5478c4a9291..d64d64eb5ee 100644 --- a/tensorflow/compiler/xla/service/indexed_array_analysis_test.cc +++ b/tensorflow/compiler/xla/service/indexed_array_analysis_test.cc @@ -35,7 +35,7 @@ class IndexedArrayAnalysisTest : public HloTestBase { } private: - // Replaces seqences of whitespace with a single space. This makes the + // Replaces sequences of whitespace with a single space. This makes the // strings being matched against "whitespace insensitive" which lets us indent // them for readability. string CanonicalizeWhitespace(const string& text) { diff --git a/tensorflow/compiler/xla/service/layout_assignment.cc b/tensorflow/compiler/xla/service/layout_assignment.cc index defaf4cd7ab..d8609a15d77 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.cc +++ b/tensorflow/compiler/xla/service/layout_assignment.cc @@ -432,7 +432,7 @@ bool IsLayoutConstrainedCustomCall(HloInstruction* instruction) { return custom_call != nullptr && custom_call->layout_constrained(); } -bool IsLayoutConstrainedAllReduce(HloInstruction* instruction) { +bool IsLayoutConstrainedAllReduce(const HloInstruction* instruction) { const HloAllReduceInstruction* all_reduce = DynCast(instruction); return all_reduce != nullptr && all_reduce->constrain_layout(); @@ -607,7 +607,7 @@ Status LayoutAssignment::AddMandatoryConstraints( body_layout.result_shape(), instruction)); } else if (instruction->opcode() == HloOpcode::kConditional) { // Find the conditional branch with the most instructions and force all - // other computations to match that layout. A potentially better decison + // other computations to match that layout. A potentially better decision // could count the number FLOPs or how constrained the layouts are. int64 largest_branch = 0; int64 largest_instruction_count = @@ -942,8 +942,10 @@ Status LayoutAssignment::CheckLayouts(HloModule* module) { const Shape& instruction_subshape = ShapeUtil::GetSubshape(instruction->shape(), index); for (const LogicalBuffer* buffer : buffers) { - if (!Shape::Equal().MinorToMajorOnlyInLayout()( - instruction_subshape, buffer->shape())) { + if (!Shape::Equal() + .IgnoreDynamicDimension() + .MinorToMajorOnlyInLayout()(instruction_subshape, + buffer->shape())) { return InternalError( "Layout of instruction %s at index {%s} does not match " "source LogicalBuffer %s: %s vs %s", @@ -1005,8 +1007,9 @@ Status LayoutAssignment::CheckLayouts(HloModule* module) { FindOrDie(computation_layouts_, module->entry_computation()) .result_layout(); if (result_layout.LayoutIsSet()) { - TF_RET_CHECK(Shape::Equal().MinorToMajorOnlyInLayout()( - module->result_shape(), result_layout.shape())); + TF_RET_CHECK( + Shape::Equal().IgnoreDynamicDimension().MinorToMajorOnlyInLayout()( + module->result_shape(), result_layout.shape())); } return Status::OK(); } @@ -1312,6 +1315,23 @@ Status LayoutAssignment::PropagateOperandConstraint( if (!operand->shape().IsArray()) { return Status::OK(); } + + if (user->opcode() == HloOpcode::kAllReduce) { + const auto shape_index = + user->operand_count() == 1 + ? ShapeIndex() + : ShapeIndex({operand_constraint.operand_no()}); + TF_ASSIGN_OR_RETURN(const LogicalBuffer* buffer, + constraints->points_to_analysis().GetBufferDefinedAt( + user, shape_index)); + const BufferLayoutConstraint* constraint = + constraints->GetBufferLayoutConstraint(*buffer); + if (constraint == nullptr) { + TF_RETURN_IF_ERROR(constraints->SetBufferLayout( + operand_constraint.shape_layout().layout(), *buffer, + /*mandatory=*/false)); + } + } if (instruction_can_change_layout_func_(user) && !user->shape().IsArray()) { return Status::OK(); } @@ -1443,6 +1463,13 @@ Status LayoutAssignment::PropagateBufferConstraintToOperands( return Status::OK(); } + if (instruction->opcode() == HloOpcode::kAllReduce) { + TF_RETURN_IF_ERROR(constraints->SetArrayOperandLayout( + buffer_constraint.layout(), instruction, + instruction->operand_count() == 1 ? 0 : buffer.index()[0], + /*mandatory=*/true)); + return Status::OK(); + } for (int64 operand_no = 0; operand_no < instruction->operand_count(); ++operand_no) { const HloInstruction* operand = instruction->operand(operand_no); @@ -1993,9 +2020,10 @@ Status LayoutAssignment::PropagateComputationLayouts( << ": " << computed_computation_layout.result_layout().ToString(); *result_layout = computed_computation_layout.result_layout(); } else { - TF_RET_CHECK(Shape::Equal().MinorToMajorOnlyInLayout()( - computed_computation_layout.result_layout().shape(), - result_layout->shape())); + TF_RET_CHECK( + Shape::Equal().IgnoreDynamicDimension().MinorToMajorOnlyInLayout()( + computed_computation_layout.result_layout().shape(), + result_layout->shape())); } return Status::OK(); } @@ -2126,7 +2154,6 @@ bool LayoutAssignment::InstructionCanChangeLayout( case HloOpcode::kConditional: case HloOpcode::kConvert: case HloOpcode::kCos: - case HloOpcode::kAllReduce: case HloOpcode::kAllToAll: case HloOpcode::kCollectivePermute: case HloOpcode::kDivide: @@ -2176,6 +2203,9 @@ bool LayoutAssignment::InstructionCanChangeLayout( case HloOpcode::kTupleSelect: case HloOpcode::kWhile: case HloOpcode::kSetDimensionSize: + // AllReduce is variadic so it needs to be careful to assign the same layout + // to the corresponding input argument and Tuple index. + case HloOpcode::kAllReduce: return false; case HloOpcode::kBatchNormGrad: case HloOpcode::kBatchNormInference: diff --git a/tensorflow/compiler/xla/service/layout_assignment.h b/tensorflow/compiler/xla/service/layout_assignment.h index a0f61fc416d..ef30ec3088b 100644 --- a/tensorflow/compiler/xla/service/layout_assignment.h +++ b/tensorflow/compiler/xla/service/layout_assignment.h @@ -456,7 +456,7 @@ class LayoutAssignment : public HloModulePass { // when the instruction is a tuple, and in such case the index represents // the location from where the copy instruction was created from. // If the index is empty, the whole sharding will be propagated, even in case - // the intruction has a tuple sharding. + // the instruction has a tuple sharding. static void SetupCopiedInstruction(const HloInstruction& instruction, HloInstruction* copy, const ShapeIndex& index); @@ -508,7 +508,7 @@ class LayoutAssignment : public HloModulePass { // instructions can be set to match the computation. std::map computation_layouts_; - // Map from branch computations to the result layout they shuould apply. + // Map from branch computations to the result layout they should apply. std::map conditional_mismatch_; // Every copy added to the module by the layout assignment pass is registered diff --git a/tensorflow/compiler/xla/service/layout_assignment_test.cc b/tensorflow/compiler/xla/service/layout_assignment_test.cc index f9d0adb3fba..42245ca73df 100644 --- a/tensorflow/compiler/xla/service/layout_assignment_test.cc +++ b/tensorflow/compiler/xla/service/layout_assignment_test.cc @@ -1347,5 +1347,43 @@ TEST_F(LayoutAssignmentTest, OverwriteDiamondShapedConstraintsX) { EXPECT_THAT(transpose->shape().layout().minor_to_major(), ElementsAre(0, 1)); } +// Tests that the layout assignment supports layout-constrained all-reduce with +// different operand layouts (b/146056839). +TEST_F(LayoutAssignmentTest, LayoutConstrainedAllReduce) { + const char* module_str = R"( +HloModule test_module + +add { + lhs = f32[] parameter(0) + rhs = f32[] parameter(1) + ROOT add = f32[] add(lhs, rhs) +} + +ENTRY entry_computation { + param = (f32[8,4]{0,1}, f32[16,2]{0,1}) parameter(0) + gte0 = f32[8,4] get-tuple-element(param), index=0 + gte1 = f32[16,2] get-tuple-element(param), index=1 + crs = (f32[8,4]{0,1}, f32[16,2]{1,0}) all-reduce(gte0, gte1), + replica_groups={}, constrain_layout=true, to_apply=add + gte2 = f32[8,4] get-tuple-element(crs), index=0 + gte3 = f32[16,2] get-tuple-element(crs), index=1 + ROOT result = (f32[8,4]{1,0}, f32[16,2]{1,0}) tuple(gte2, gte3) +} +)"; + + TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr m, + ParseAndReturnVerifiedModule(module_str)); + ComputationLayout computation_layout( + m->entry_computation()->ComputeProgramShape(), /*ignore_layouts=*/false); + + ChannelLayoutConstraints channel_constraints; + AssignLayouts(m.get(), &computation_layout, &channel_constraints); + + const HloInstruction* crs = FindInstruction(m.get(), "crs"); + ExpectTupleLayoutIs(crs->shape(), {{0, 1}, {1, 0}}); + ExpectLayoutIs(crs->operand(0)->shape(), {0, 1}); + ExpectLayoutIs(crs->operand(1)->shape(), {1, 0}); +} + } // namespace } // namespace xla 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 ba199f35712..77ce26c7e84 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 @@ -24,7 +24,7 @@ namespace xla { namespace llvm_ir { bool MayBeImplementedAsInPlaceDynamicUpdateSlice(const HloInstruction* instr) { - // Today we can't emit a dynamic-update-slice if the DUS node is parallized; + // Today we can't emit a dynamic-update-slice if the DUS node is parallelized; // the emitter will not emit correct code. It's possible to change this, but // then ParallelTaskAssigner would have to somehow know whether a node *will* // be emitted as an in-place DUS, and it can't, because it doesn't have a diff --git a/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc b/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc index c4d527b6cbf..aa37b9e7be9 100644 --- a/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc +++ b/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc @@ -612,7 +612,7 @@ llvm::Function* CreateCpuFunction(llvm::FunctionType* function_type, // set. function->addFnAttr("denormal-fp-math", "preserve-sign"); - // Add the optize attribute to the function if optimizing for size. This + // Add the optimize attribute to the function if optimizing for size. This // controls internal behavior of some optimization passes (e.g. loop // unrolling). if (cpu::options::OptimizeForSizeRequested(module_config)) { diff --git a/tensorflow/compiler/xla/service/memory_space_assignment.cc b/tensorflow/compiler/xla/service/memory_space_assignment.cc index c1dc635eb81..caf8fce0f2e 100644 --- a/tensorflow/compiler/xla/service/memory_space_assignment.cc +++ b/tensorflow/compiler/xla/service/memory_space_assignment.cc @@ -108,6 +108,15 @@ bool InstructionCountPrefetchIntervalPicker::Done() const { return end_time_ - current_prefetch_time_ <= min_overlap_count_; } +std::string InstructionCountPrefetchIntervalPicker::ToDebugString() const { + return absl::StrCat("Overlapped HLOs = ", end_time_ - current_prefetch_time_); +} + +std::string InstructionCountPrefetchIntervalPicker::ToNoCopyDebugString( + const Shape& shape, int64 start_time, int64 end_time) const { + return absl::StrCat("Overlapped HLOs = ", end_time - start_time); +} + void CostAnalysisPrefetchIntervalPicker::SetInstructionSchedule( const absl::flat_hash_map& instruction_schedule) { @@ -180,9 +189,8 @@ bool CostAnalysisPrefetchIntervalPicker::Done() const { } float logical_interval_elapsed = GetLogicalIntervalElapsed( current_logical_prefetch_time_, end_logical_time_); - return min_async_copy_to_overlap_ratio_ * async_copy_elapsed_ - - inst_elapsed_reduction_ > - logical_interval_elapsed; + return async_copy_elapsed_ * min_async_copy_to_overlap_ratio_ > + logical_interval_elapsed + inst_elapsed_reduction_; } float CostAnalysisPrefetchIntervalPicker::GetLogicalIntervalElapsed( @@ -190,6 +198,25 @@ float CostAnalysisPrefetchIntervalPicker::GetLogicalIntervalElapsed( return elapsed_time_cumsum_[end_time - 1] - elapsed_time_cumsum_[start_time]; } +std::string CostAnalysisPrefetchIntervalPicker::ToDebugString() const { + float logical_interval_elapsed = GetLogicalIntervalElapsed( + current_logical_prefetch_time_, end_logical_time_); + return absl::StrCat( + "Async copy elapsed (s) = ", async_copy_elapsed_, + ", inst elapsed reduction (s) = ", inst_elapsed_reduction_, + ", logical interval elapsed (s) = ", logical_interval_elapsed); +} + +std::string CostAnalysisPrefetchIntervalPicker::ToNoCopyDebugString( + const Shape& shape, int64 start_time, int64 end_time) const { + float async_copy_elapsed = cost_analysis_.GetAsyncCopyElapsed(shape); + float logical_interval_elapsed = + GetLogicalIntervalElapsed(start_time, end_time); + return absl::StrCat( + "Async copy elapsed (s) = ", async_copy_elapsed, + ", logical interval elapsed (s) = ", logical_interval_elapsed); +} + std::vector AlternateMemoryBestFitHeap::GetSortedColocatedIntervals( const GlobalDecreasingSizeBestFitHeap::BufferInterval& interval) const { @@ -487,11 +514,21 @@ void AlternateMemoryBestFitHeap::CommitPendingChunks() { } pending_chunks_.clear(); // Also add the pending async copies to the interval tree. - if (options_.max_outstanding_async_copies >= 0) { - for (auto interval : pending_async_copies_) { - async_copy_interval_tree_.Add(interval.first, interval.second, + for (const auto& interval : pending_async_copies_) { + if (options_.max_outstanding_async_copies >= 0) { + async_copy_interval_tree_.Add(interval.start_time, interval.end_time, kDummyChunk); } + if (interval.destination == MemorySpace::kAlternate) { + // If there is already an asynchronous copy ending the same time, pick + // the earliest copy start time. + auto range_it = async_copy_range_map_.find(interval.end_time); + if (range_it != async_copy_range_map_.end()) { + range_it->second = std::min(range_it->second, interval.start_time); + } else { + async_copy_range_map_[interval.end_time] = interval.start_time; + } + } } pending_async_copies_.clear(); } @@ -578,12 +615,37 @@ bool AlternateMemoryBestFitHeap::FindAllocation( if (!allocations->empty()) { prev_allocation = allocations->back().get(); } + // Find a previous allocation that is in the default memory space (not + // necessarily the very last allocation). + MemorySpaceAssignment::Allocation* prev_allocation_in_default_mem = nullptr; + for (auto allocation_it = allocations->rbegin(); + allocation_it != allocations->rend(); ++allocation_it) { + if ((*allocation_it)->memory_space() == MemorySpace::kDefault && + (*allocation_it)->defining_position() == defining_position) { + prev_allocation_in_default_mem = allocation_it->get(); + break; + } + } // Since copies couldn't be removed, create an allocation in the default // memory space. - if (prev_allocation != nullptr && - prev_allocation->memory_space() == MemorySpace::kAlternate && - prev_allocation->defining_position() == defining_position) { + if (prev_allocation_in_default_mem != nullptr) { + if (prev_allocation == prev_allocation_in_default_mem) { + // The latest allocation is also in the default memory, simply extend + // that. + prev_allocation->Extend(end_time); + } else { + // The latest allocation is different. Create a new allocation in default + // memory. + allocations->push_back( + absl::make_unique( + non_bitcast_operand, defining_position, MemorySpace::kDefault, + kDummyChunk, prev_allocation_in_default_mem->end_time(), + end_time)); + } + } else if (prev_allocation != nullptr && + prev_allocation->memory_space() == MemorySpace::kAlternate && + prev_allocation->defining_position() == defining_position) { // If there was an allocation for this HloValue that was in the alternate // memory space, we also need to perform an eviction. // TODO(berkin): For now evictions happen relative to the most recent @@ -631,13 +693,6 @@ bool AlternateMemoryBestFitHeap::FindAllocation( return false; } } - } else if (prev_allocation != nullptr && - prev_allocation->memory_space() == MemorySpace::kDefault && - prev_allocation->defining_position() == defining_position) { - // If the previous allocation was in the default memory space and was - // defined by the same instruction, extend that. Otherwise, create a new - // allocation. - prev_allocation->Extend(end_time); } else { allocations->push_back(absl::make_unique( non_bitcast_operand, defining_position, MemorySpace::kDefault, @@ -667,6 +722,8 @@ bool AlternateMemoryBestFitHeap::FindAllocation( // Start Done options_.prefetch_interval_picker->Begin(use, start_time, latest_prefetch_time); + VLOG(4) << "Trying prefetch picker = " + << options_.prefetch_interval_picker->ToDebugString(); while (!options_.prefetch_interval_picker->Done()) { alternate_mem_interval.start = options_.prefetch_interval_picker->Next(); VLOG(4) << "Trying alternate memory allocation (" @@ -679,6 +736,12 @@ bool AlternateMemoryBestFitHeap::FindAllocation( VLOG(4) << "This would violate the outstanding async copy limit."; continue; } + if (ViolatesAsynchronousCopyOrdering(alternate_mem_interval.start, + alternate_mem_interval.end)) { + VLOG(4) << "This would violate asynchronous copy ordering."; + continue; + } + ChunkCandidate chunk_candidate = FindChunkCandidate(alternate_mem_interval); // Check if the new heap size fits within limits. if (chunk_candidate.heap_size < available_heap_size()) { @@ -686,7 +749,9 @@ bool AlternateMemoryBestFitHeap::FindAllocation( << alternate_mem_interval.start << ". Offset = " << chunk_candidate.chunk.offset << ", size = " << chunk_candidate.chunk.size - << ", heap_size = " << chunk_candidate.heap_size; + << ", heap_size = " << chunk_candidate.heap_size + << ", prefetch picker = " + << options_.prefetch_interval_picker->ToDebugString(); AddToPendingChunks(alternate_mem_interval, chunk_candidate); AddAsyncCopy(*allocations->back().get(), MemorySpace::kAlternate, @@ -722,7 +787,7 @@ void AlternateMemoryBestFitHeap::AddAsyncCopy( // Register the additional async copy with the interval tree to keep track of // the limit at any given time. - pending_async_copies_.emplace_back(start_time, end_time); + pending_async_copies_.push_back({start_time, end_time, memory_space}); } bool AlternateMemoryBestFitHeap::ViolatesMaximumOutstandingAsyncCopies( @@ -737,8 +802,8 @@ bool AlternateMemoryBestFitHeap::ViolatesMaximumOutstandingAsyncCopies( async_copy_interval_tree_.ChunksOverlappingInTime(start_time, end_time) .size(); - for (auto interval : pending_async_copies_) { - if (interval.second > start_time && interval.first < end_time) { + for (const auto& interval : pending_async_copies_) { + if (interval.start_time > start_time && interval.end_time < end_time) { num_async_copies++; } } @@ -747,6 +812,13 @@ bool AlternateMemoryBestFitHeap::ViolatesMaximumOutstandingAsyncCopies( return num_async_copies + 1 > options_.max_outstanding_async_copies; } +bool AlternateMemoryBestFitHeap::ViolatesAsynchronousCopyOrdering( + int64 start_time, int64 end_time) const { + auto async_copy_range_it = async_copy_range_map_.lower_bound(end_time); + return async_copy_range_it != async_copy_range_map_.end() && + async_copy_range_it->second < start_time; +} + bool AlternateMemoryBestFitHeap::TryAllocatingInAlternateMemoryNoCopy( int64 start_time, int64 end_time, int64 last_use_time, HloPosition defining_position, HloUse use, @@ -822,7 +894,10 @@ bool AlternateMemoryBestFitHeap::TryAllocatingInAlternateMemoryNoCopy( VLOG(3) << "Keep the buffer in alternate memory. Offset = " << chunk_candidate.chunk.offset << ", size = " << chunk_candidate.chunk.size - << ", heap_size = " << chunk_candidate.heap_size; + << ", heap_size = " << chunk_candidate.heap_size + << ", prefetch picker = " + << options_.prefetch_interval_picker->ToNoCopyDebugString( + non_bitcast_operand->shape(), start_time, end_time); AddToPendingChunks(alternate_mem_interval, chunk_candidate); // If there was a previous allocation, the buffer location is the @@ -933,8 +1008,7 @@ MemorySpaceAssignment::Run(HloModule* module, const Options& options) { HloLiveRange::Run(module->schedule(), *alias_analysis, entry_computation)); MemorySpaceAssignment memory_space_assignment( - module, options.alternate_memory_space, - hlo_live_range->flattened_instruction_sequence().instructions()); + module, options.alternate_memory_space, *hlo_live_range); auto algorithm = absl::make_unique( &memory_space_assignment.allocation_map_, options, *alias_analysis, *hlo_live_range); @@ -1177,9 +1251,13 @@ void PresetAssignments::RemoveAssignmentForInstruction( Status MemorySpaceAssignment::SimplifyGraph() { for (HloComputation* computation : module_->MakeNonfusionComputations()) { - // FixSchedule can miss unused parameters. Just remove unused parameters - // here so that FixSchedule doesn't have to deal with them. - TF_RETURN_IF_ERROR(computation->RemoveUnusedParametersFromAnyComputation()); + // Parallel computations aren't in the schedule and don't need to be + // modified. + if (!computations_in_schedule_.contains(computation)) { + VLOG(4) << "Not simplifying " << computation->name() + << " because it's not in the schedule."; + continue; + } // We perform limited DCE and forward the tuple operand in patterns like // GetTupleElement(Tuple(a, b), 0). This is mostly because memory space // assignment is ran late in compilation (after DCE and arithmetic @@ -1195,7 +1273,7 @@ Status MemorySpaceAssignment::SimplifyGraph() { instruction->user_count() == 0 && !instruction->HasSideEffect() && instruction != computation->root_instruction()) { VLOG(4) << "Instruction removed: " << instruction->ToString(); - // Ensure the exported preset assignments don't contain a refence to + // Ensure the exported preset assignments don't contain a reference to // the removed instruction. preset_assignments_->RemoveAssignmentForInstruction(instruction); // Instead of deleting the instruction from the schedule, replace it @@ -1244,28 +1322,6 @@ void MemorySpaceAssignment::EnsureInstructionAndOperandsInserted( } void MemorySpaceAssignment::ScheduleAsynchronousCopies() { - // For asynchronous copies of both directions (default to alternate and vice - // versa), sort them by their completion time. Then, if in the sorted order we - // see that the start time is earlier than the start time of an asynchronous - // copy that ends earlier, we delay the start of this. As a result, given - // asynchronous copies that might look like: - // - // CS CD - // a +-----------+ - // b +-----------+ - // c +---------+ - // - // We'll first sort by completion time: - // - // c +---------+ - // a +-----------+ - // b +-----------+ - // - // Then, delay a because c starts later than a despite also ending earlier: - // - // c +---------+ - // a +---------+ - // b +-----------+ for (MemorySpace memory_space : {MemorySpace::kDefault, MemorySpace::kAlternate}) { std::vector copy_allocations; @@ -1290,18 +1346,6 @@ void MemorySpaceAssignment::ScheduleAsynchronousCopies() { CopyAllocation* prev_copy_allocation = nullptr; for (CopyAllocation* copy_allocation : copy_allocations) { - if (prev_copy_allocation && - prev_copy_allocation->copy_start_schedule_after() > - copy_allocation->copy_start_schedule_after()) { - VLOG(4) << "Delaying CopyStart (" - << copy_allocation->copy_start_schedule_after() << " to " - << prev_copy_allocation->copy_start_schedule_after() << ") for " - << copy_allocation->copy_start()->ToString() << " because of " - << prev_copy_allocation->copy_start()->ToString(); - copy_allocation->set_copy_start_schedule_after( - prev_copy_allocation->copy_start_schedule_after()); - } - // If the copy start doesn't happen to be scheduled at the correct // computation, delay it until the correct computation starts. int64 copy_start_schedule_after = @@ -1332,6 +1376,13 @@ Status MemorySpaceAssignment::FixSchedule() { HloSchedule& schedule = module_->schedule(); for (const HloComputation* computation : module_->MakeNonfusionComputations()) { + // Parallel computations aren't in the schedule and don't need to be + // modified. + if (!computations_in_schedule_.contains(computation)) { + VLOG(4) << "Not scheduling " << computation->name() + << " because it's not in the schedule."; + continue; + } CHECK(schedule.is_computation_scheduled(computation)); HloInstructionSequence new_sequence; diff --git a/tensorflow/compiler/xla/service/memory_space_assignment.h b/tensorflow/compiler/xla/service/memory_space_assignment.h index 20551feb715..67ced4c4909 100644 --- a/tensorflow/compiler/xla/service/memory_space_assignment.h +++ b/tensorflow/compiler/xla/service/memory_space_assignment.h @@ -132,6 +132,14 @@ class PrefetchIntervalPicker { // Returns true if the available prefetch intervals have been exhausted. virtual bool Done() const = 0; + // Returns a debug string for the current state of the prefetch interval + // picker. + virtual std::string ToDebugString() const = 0; + + // Returns a debug string for no-copy allocation. + virtual std::string ToNoCopyDebugString(const Shape& shape, int64 start_time, + int64 end_time) const = 0; + protected: const absl::flat_hash_map* instruction_schedule_ = nullptr; @@ -163,6 +171,10 @@ class InstructionCountPrefetchIntervalPicker : public PrefetchIntervalPicker { int64 Next() override; bool Done() const override; + std::string ToDebugString() const override; + std::string ToNoCopyDebugString(const Shape& shape, int64 start_time, + int64 end_time) const override; + private: int64 min_overlap_count_; int64 max_overlap_count_; @@ -199,6 +211,10 @@ class CostAnalysisPrefetchIntervalPicker : public PrefetchIntervalPicker { int64 Next() override; bool Done() const override; + std::string ToDebugString() const override; + std::string ToNoCopyDebugString(const Shape& shape, int64 start_time, + int64 end_time) const override; + private: // Returns the elapsed time in seconds between the logical interval that // corresponds to the instruction schedule. @@ -445,14 +461,23 @@ class MemorySpaceAssignment { const MemorySpaceAssignmentCostAnalysis& cost_analysis); private: - MemorySpaceAssignment( - HloModule* module, int64 alternate_memory_space, - absl::Span flattened_instructions) + MemorySpaceAssignment(HloModule* module, int64 alternate_memory_space, + const HloLiveRange& hlo_live_range) : module_(module), alternate_memory_space_(alternate_memory_space), - flattened_instructions_(flattened_instructions.begin(), - flattened_instructions.end()), - preset_assignments_(absl::make_unique()) {} + flattened_instructions_(hlo_live_range.flattened_instruction_sequence() + .instructions() + .begin(), + hlo_live_range.flattened_instruction_sequence() + .instructions() + .end()), + computations_in_schedule_(), + preset_assignments_(absl::make_unique()) { + for (const auto& computation_and_bound : + hlo_live_range.computation_span_times()) { + computations_in_schedule_.insert(computation_and_bound.first); + } + } // Process calls Process methods of the allocations after the allocations have // been finalized. @@ -481,6 +506,7 @@ class MemorySpaceAssignment { HloModule* module_; int64 alternate_memory_space_; std::vector flattened_instructions_; + absl::flat_hash_set computations_in_schedule_; AllocationMap allocation_map_; std::unique_ptr preset_assignments_; @@ -493,7 +519,7 @@ class MemorySpaceAssignment { // This struct contains mandatory memory assignments at a given time. E.g., an // input's required memory assignment time would correspond to the definition -// time of the parameter instruction, and an output's time would correspnd to +// time of the parameter instruction, and an output's time would correspond to // the time of last use. struct RequiredMemoryAssignment { MemorySpaceAssignment::MemorySpace memory_space; @@ -525,6 +551,14 @@ class AlternateMemoryBestFitHeap : public GlobalDecreasingSizeBestFitHeap { HeapSimulator::Result Finish() override; private: + // A struct representing an asynchronous copy with its logical start and end + // time and its destination memory space. + struct AsynchronousCopy { + int64 start_time; + int64 end_time; + MemorySpace destination; + }; + // Finds an allocation for the given interval. Internally, it will attempt to // find a suitable chunk candidate within the heap size and prefetch interval // limits, and append the new allocation(s) to allocations. The new @@ -569,6 +603,18 @@ class AlternateMemoryBestFitHeap : public GlobalDecreasingSizeBestFitHeap { bool ViolatesMaximumOutstandingAsyncCopies(int64 start_time, int64 end_time) const; + // Returns true if the addition of an asynchronous copy in the the given time + // interval would violate the asynchronous copy ordering. E.g., consider the + // following scenario: + // CS CD + // already committed async copy: +-----------+ + // new async copy: +--------+ + // + // The new asynchronous copy would violate the ordering guarantee because the + // copy start is after an already committed asynchronous copy while its copy + // done is before the committed copy. + bool ViolatesAsynchronousCopyOrdering(int64 start_time, int64 end_time) const; + // Adds an asynchronous copy to the allocations. void AddAsyncCopy(const MemorySpaceAssignment::Allocation& prev_allocation, MemorySpace memory_space, Chunk chunk, int64 start_time, @@ -593,8 +639,11 @@ class AlternateMemoryBestFitHeap : public GlobalDecreasingSizeBestFitHeap { // We use a interval tree to keep track of the number of outstanding // asynchronous copies. BufferIntervalTree async_copy_interval_tree_; + // Given the logical time for CopyDone in key, stores the earliest time for + // the corresponding CopyStart. + std::map async_copy_range_map_; std::vector> pending_chunks_; - std::vector> pending_async_copies_; + std::vector pending_async_copies_; // This map contains required memory assignments for HloValues (e.g., input // and outputs). absl::flat_hash_map> diff --git a/tensorflow/compiler/xla/service/memory_space_assignment_test.cc b/tensorflow/compiler/xla/service/memory_space_assignment_test.cc index 068834e5701..238bbed37c4 100644 --- a/tensorflow/compiler/xla/service/memory_space_assignment_test.cc +++ b/tensorflow/compiler/xla/service/memory_space_assignment_test.cc @@ -361,6 +361,75 @@ TEST_P(MemorySpaceAssignmentTest, EvictAndPrefetchLimitAsyncCopies2) { 2); } +TEST_P(MemorySpaceAssignmentTest, DontEvictWhenThereIsDefaultMemAllocation) { + // This test is the same as EvictAndPrefetchLimitAsyncCopies1, except we check + // that there is no eviction if not necessary (due to an existing allocation + // in default memory). + HloComputation::Builder builder(TestName()); + Shape shape = ShapeUtil::MakeShape(F32, {2, 3}); + HloInstruction* p0 = + builder.AddInstruction(HloInstruction::CreateParameter(0, shape, "p0")); + HloInstruction* p1 = + builder.AddInstruction(HloInstruction::CreateParameter(1, shape, "p1")); + HloInstruction* tanh = builder.AddInstruction( + HloInstruction::CreateUnary(shape, HloOpcode::kTanh, p0)); + // tanh should be placed in the alternate memory since there isn't much + // contention in the beginning. However, tanh has another consumer at the end. + // So it should be kicked out to default memory and prefetched back in. The + // graph below is meant to increase the contention to force eviction/prefetch + // behavior. + HloInstruction* a = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kAdd, p0, tanh)); + HloInstruction* b = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kSubtract, p0, p1)); + HloInstruction* c = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, p0, p1)); + HloInstruction* d = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kSubtract, p0, p1)); + HloInstruction* e = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, a, b)); + HloInstruction* f = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, a, c)); + HloInstruction* g = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, a, d)); + HloInstruction* h = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, b, c)); + HloInstruction* i = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, b, d)); + HloInstruction* j = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, c, d)); + HloInstruction* k = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kAdd, e, f)); + HloInstruction* l = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kAdd, g, h)); + HloInstruction* m = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kAdd, i, j)); + HloInstruction* n = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kAdd, k, l)); + HloInstruction* o = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kAdd, n, m)); + // tanh is being used at the root instruction, and this should be + // prefetched. + HloInstruction* add = builder.AddInstruction( + HloInstruction::CreateBinary(shape, HloOpcode::kAdd, o, tanh)); + + auto module = CreateNewVerifiedModule(); + HloComputation* computation = module->AddEntryComputation(builder.Build()); + + HloSchedule schedule(module.get()); + schedule.set_sequence(computation, {p0, p1, tanh, a, b, c, d, e, f, g, h, i, + j, k, l, m, n, o, add}); + TF_CHECK_OK(module->set_schedule(schedule)); + + AssignMemorySpace(module.get(), /*max_outstanding_async_copies=*/1); + + // We expect the second argument to multiply is prefetched c. + EXPECT_THAT(f, op::Multiply(op::Add(), op::CopyDone())); + // We make sure that the second argument to this multiply is not evicted + // CopyDone but is the original c. + EXPECT_THAT(h, op::Multiply(op::Subtract(), op::Multiply())); +} + TEST_P(MemorySpaceAssignmentTest, While) { auto module = CreateNewVerifiedModule(); Shape shape = ShapeUtil::MakeShape(xla::F32, {2, 3}); @@ -1171,7 +1240,7 @@ TEST_P(MemorySpaceAssignmentTest, NonEntryComputationSchedule5) { // // If a copy to alternate memory is inserted before foo, and if the size of // the while body is less than max prefetch interval so that the copy-done is - // kept in the alternate memory, then we end up refering to the copy-done in + // kept in the alternate memory, then we end up referring to the copy-done in // the root instruction of the while loop body. I.e., // // cs = copy-start(a) @@ -1860,12 +1929,12 @@ TEST_P(MemorySpaceAssignmentTest, MemoryBoundednessBufferIntervalCompare) { // \ / \ / // +--------+ +---------+ // - // The alternate memory is sized to fit only one f32[4,6] tensor at a time. + // The alternate memory is sized to fit only two f32[4,3] tensors at a time. // Also, transcendentals are made to be lower bandwidth than FLOPs. So, the // MemoryBoundednessBufferIntervalCompare should prioritize the negates, which // are more memory bound. HloComputation::Builder builder(TestName()); - Shape shape = ShapeUtil::MakeShape(F32, {4, 6}); + Shape shape = ShapeUtil::MakeShape(F32, {4, 3}); HloInstruction* p0 = builder.AddInstruction(HloInstruction::CreateParameter(0, shape, "p0")); HloInstruction* p1 = @@ -1890,8 +1959,8 @@ TEST_P(MemorySpaceAssignmentTest, MemoryBoundednessBufferIntervalCompare) { HloInstruction::CreateUnary(shape, HloOpcode::kTanh, tanh3)); HloInstruction* negate4 = builder.AddInstruction( HloInstruction::CreateUnary(shape, HloOpcode::kNegate, negate3)); - HloInstruction* add = builder.AddInstruction( - HloInstruction::CreateBinary(shape, HloOpcode::kAdd, tanh4, negate4)); + HloInstruction* tuple = + builder.AddInstruction(HloInstruction::CreateTuple({tanh4, negate4})); auto module = CreateNewVerifiedModule(); HloComputation* computation = module->AddEntryComputation(builder.Build()); @@ -1899,7 +1968,7 @@ TEST_P(MemorySpaceAssignmentTest, MemoryBoundednessBufferIntervalCompare) { HloSchedule schedule(module.get()); schedule.set_sequence(computation, {p0, p1, tanh0, negate0, tanh1, negate1, tanh2, negate2, - tanh3, negate3, tanh4, negate4, add}); + tanh3, negate3, tanh4, negate4, tuple}); TF_CHECK_OK(module->set_schedule(schedule)); AssignMemorySpaceUsingCostAnalysis(module.get()); @@ -1907,7 +1976,7 @@ TEST_P(MemorySpaceAssignmentTest, MemoryBoundednessBufferIntervalCompare) { EXPECT_THAT(p0, op::ShapeWithLayout(shape)); EXPECT_THAT(p1, op::ShapeWithLayout(shape)); Shape shape_in_default_mem = ShapeUtil::MakeShapeWithLayout( - F32, {4, 6}, + F32, {4, 3}, /*minor_to_major=*/{1, 0}, /*tiles=*/{}, /*element_size_in_bits=*/0, kDefaultMemorySpace); // Expect only negates to be in alternate memory space. Not all might fit but diff --git a/tensorflow/compiler/xla/service/mlir_gpu/BUILD b/tensorflow/compiler/xla/service/mlir_gpu/BUILD index f7d0aa6b669..b687d72d3d9 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/BUILD +++ b/tensorflow/compiler/xla/service/mlir_gpu/BUILD @@ -143,13 +143,16 @@ cc_library( "//tensorflow/compiler/mlir/xla:lhlo", "//tensorflow/compiler/mlir/xla:lhlo_fuse_linalg", "//tensorflow/compiler/mlir/xla:lhlo_legalize_to_affine", + "//tensorflow/compiler/mlir/xla:lhlo_legalize_to_gpu", "//tensorflow/compiler/mlir/xla:lhlo_legalize_to_linalg", "//tensorflow/compiler/mlir/xla:xla_dialect_registration", "//tensorflow/compiler/xla:status", "//tensorflow/compiler/xla:statusor", "//tensorflow/compiler/xla:util", + "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/memory", "@local_config_mlir//:AffineDialectRegistration", + "@local_config_mlir//:CFGTransforms", "@local_config_mlir//:GPUDialect", "@local_config_mlir//:GPUDialectRegistration", "@local_config_mlir//:GPUToNVVMTransforms", @@ -159,6 +162,7 @@ cc_library( "@local_config_mlir//:LLVMTransforms", "@local_config_mlir//:Linalg", "@local_config_mlir//:LinalgDialectRegistration", + "@local_config_mlir//:LinalgToLLVM", "@local_config_mlir//:LoopDialectRegistration", "@local_config_mlir//:LoopOps", "@local_config_mlir//:LoopsToGPUPass", diff --git a/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.cc b/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.cc index d7300f58364..84e239ae196 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.cc +++ b/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.cc @@ -285,7 +285,7 @@ mlir::AffineForOp TileLoop(mlir::AffineForOp loop, int64_t size, // operations of their parent loop, and `where` must be an ancestor of that // parent loop. // -// It always preseves the semantics of the program, therefore it may modify the +// It always preserves the semantics of the program, therefore it may modify the // hoisted operations or add extra loops at the hoisted place. mlir::Operation* HoistAndFix(llvm::iplist::iterator begin_op, llvm::iplist::iterator end_op, @@ -618,7 +618,7 @@ StatusOr TransformMlirConv( output_acc = llvm::cast( HoistAndFix(output_acc, tiled_cartesian_loops.front())); - // Hoist everyting before reduction loops (aka zero initializations of + // Hoist everything before reduction loops (aka zero initializations of // output_acc): // for (cartesian loops...) { // %output_acc = alloc() : memref(..., f32) @@ -752,7 +752,7 @@ StatusOr EmitConvolutionForwardAsMlir( // TODO(timshen): Implement a transformation that collects loads to a given // buffer, create a local alloc() for the accessed part, redirects all loads - // and stores to that local alloc(), and create code to ininitialize / + // and stores to that local alloc(), and create code to initialize / // writeback the local alloc() if needed. // TODO(timshen): Implement CUDA-specific lowering. diff --git a/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.h b/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.h index c8bc0a15acd..f0b95876775 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.h +++ b/tensorflow/compiler/xla/service/mlir_gpu/experimental/conv_emitter/conv_emitter.h @@ -24,7 +24,7 @@ namespace mlir_gpu { // Builds MLIR using custom_call that represents a foward convolution. // -// The generated function has the following signautre: +// The generated function has the following signature: // func @(%output: memref, // %input: memref, // %filter: memref) { ... } diff --git a/tensorflow/compiler/xla/service/mlir_gpu/failover_compiler.cc b/tensorflow/compiler/xla/service/mlir_gpu/failover_compiler.cc index 4107d92da7e..7855f1da1cf 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/failover_compiler.cc +++ b/tensorflow/compiler/xla/service/mlir_gpu/failover_compiler.cc @@ -104,7 +104,7 @@ FailoverCompiler::CompileAheadOfTime( const AotCompilationOptions& options) { // This is not supported by GPU compiler anyway. return Unimplemented( - "CompileAheadOfTime not implemeneted in failover compiler!"); + "CompileAheadOfTime not implemented in failover compiler!"); } HloCostAnalysis::ShapeSizeFunction FailoverCompiler::ShapeSizeBytesFunction() diff --git a/tensorflow/compiler/xla/service/mlir_gpu/hlo_dialect_emitter.cc b/tensorflow/compiler/xla/service/mlir_gpu/hlo_dialect_emitter.cc index 342cf211820..60b5d086d15 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/hlo_dialect_emitter.cc +++ b/tensorflow/compiler/xla/service/mlir_gpu/hlo_dialect_emitter.cc @@ -118,6 +118,10 @@ Status HloDialectEmitter::HandleParameter(HloInstruction* param) { } Status HloDialectEmitter::HandleConstant(HloInstruction* constant) { + auto shape = constant->shape(); + if (!shape.IsArray() || shape.rank() != 0) { + return Unimplemented("non-scalar constants are not supported yet"); + } TF_ASSIGN_OR_RETURN(auto type, ConvertTensorShapeToType( constant->shape(), builder_)); diff --git a/tensorflow/compiler/xla/service/mlir_gpu/kernel_lowering.cc b/tensorflow/compiler/xla/service/mlir_gpu/kernel_lowering.cc index c749af3a1c3..186dacc06e6 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/kernel_lowering.cc +++ b/tensorflow/compiler/xla/service/mlir_gpu/kernel_lowering.cc @@ -17,8 +17,11 @@ limitations under the License. #include +#include "absl/container/flat_hash_map.h" #include "absl/memory/memory.h" #include "mlir/Conversion/GPUToNVVM/GPUToNVVMPass.h" // TF:local_config_mlir +#include "mlir/Conversion/LinalgToLLVM/LinalgToLLVM.h" // TF:local_config_mlir +#include "mlir/Conversion/LoopToStandard/ConvertLoopToStandard.h" // TF:local_config_mlir #include "mlir/Conversion/LoopsToGPU/LoopsToGPUPass.h" // TF:local_config_mlir #include "mlir/Conversion/StandardToLLVM/ConvertStandardToLLVMPass.h" // TF:local_config_mlir #include "mlir/Dialect/GPU/GPUDialect.h" // TF:local_config_mlir @@ -72,6 +75,11 @@ struct FusionToLhloConverter signalPassFailure(); } }); + getFunction().walk([&](mlir::xla_lhlo::ReduceOp op) { + if (failed(applyPartialConversion(op, target, patterns, nullptr))) { + signalPassFailure(); + } + }); } }; @@ -137,27 +145,64 @@ struct SingleTripLoopRemoval // same address with the stored value. This needs generalization. struct StoreForwardingPass : mlir::FunctionPass { void runOnFunction() override { + absl::flat_hash_map memrefToAllocOp; + getFunction().walk([&](mlir::LoadOp loadOp) { - auto block = loadOp.getOperation()->getBlock(); - auto iterator = std::find_if(block->rbegin(), block->rend(), + auto* block = loadOp.getOperation()->getBlock(); + auto loadOpIt = std::find_if(block->rbegin(), block->rend(), [&loadOp](mlir::Operation& other) { return &other == loadOp.getOperation(); }); - if (++iterator == block->rend()) return; - mlir::StoreOp storeOp = llvm::dyn_cast(&*(iterator)); - if (!storeOp) return; - // Check both store to the same value. - if (storeOp.memref() != loadOp.memref()) return; - auto storeIndices = storeOp.getIndices(); - auto loadIndices = loadOp.getIndices(); - if (!std::equal(storeIndices.begin(), storeIndices.end(), - loadIndices.begin(), loadIndices.end())) { + for (auto storeOpIt = loadOpIt; storeOpIt != block->rend(); ++storeOpIt) { + auto storeOp = llvm::dyn_cast(&*(storeOpIt)); + if (!storeOp) { + continue; + } + mlir::Operation* storeOpAlloc = + GetAllocOp(storeOp.memref(), &memrefToAllocOp); + mlir::Operation* loadOpAlloc = + GetAllocOp(loadOp.memref(), &memrefToAllocOp); + if (!storeOpAlloc || !loadOpAlloc || storeOpAlloc != loadOpAlloc) { + continue; + } + auto storeIndices = storeOp.getIndices(); + auto loadIndices = loadOp.getIndices(); + if (!std::equal(storeIndices.begin(), storeIndices.end(), + loadIndices.begin(), loadIndices.end())) { + return; + } + loadOp.replaceAllUsesWith(storeOp.getValueToStore()); + loadOp.erase(); return; } - loadOp.replaceAllUsesWith(storeOp.getValueToStore()); - loadOp.erase(); }); }; + + // Recursively checks defining ops until finds AllocOp. Return either AllocOp + // if it is found or nullptr. + mlir::Operation* SearchAllocOp(mlir::Value* memref) { + mlir::Operation* defOp = memref->getDefiningOp(); + while (auto subviewOp = mlir::dyn_cast_or_null(defOp)) { + defOp = subviewOp.source()->getDefiningOp(); + } + if (auto allocOp = mlir::dyn_cast_or_null(defOp)) { + return allocOp.getOperation(); + } + return nullptr; + } + + // Retrieves AllocOp from the cache or actually looks for it. + mlir::Operation* GetAllocOp( + mlir::Value* memref, + absl::flat_hash_map* memrefToAllocOp) { + auto allocOpIt = memrefToAllocOp->find(memref); + if (allocOpIt != memrefToAllocOp->end()) { + return allocOpIt->second; + } + auto allocOp = SearchAllocOp(memref); + memrefToAllocOp->insert({memref, allocOp}); + return allocOp; + } }; // Simple pass that removes temporary buffers that are only written to but @@ -210,19 +255,22 @@ struct DeadTempBufferRemoval : mlir::FunctionPass { } }; -// Neat little helper pass to dump the IR inbetween passes. -struct DumpPass : public mlir::ModulePass { - void runOnModule() override { -#if DEBUG - getModule().dump(); -#endif - } -}; +void EnableIRPrinting(mlir::PassManager* passManager) { + auto enable_if_vlog_is_on = [](mlir::Pass* pass, mlir::Operation* op) { + return VLOG_IS_ON(1); + }; + passManager->enableIRPrinting(/*shouldPrintBeforePass=*/{}, + /*shouldPrintAfterPass=*/enable_if_vlog_is_on, + /*printModuleScope=*/false, + /*printAfterOnlyOnChange=*/true, llvm::dbgs()); + passManager->disableMultithreading(); +} } // namespace Status LowerLHLOToGPU(mlir::ModuleOp module) { mlir::PassManager pm(module.getContext()); + EnableIRPrinting(&pm); // First, lower bodies of fusion operations from hlo to lhlo. pm.addPass(absl::make_unique()); @@ -233,26 +281,22 @@ Status LowerLHLOToGPU(mlir::ModuleOp module) { // Fuse linalg operations. This will yield a single tiled loop nest where // the inner loops are single trip. pm.addPass(::mlir::xla_lhlo::createLhloFuseLinalg()); - pm.addPass(absl::make_unique()); + // Legalize reduce operations directly to GPU dialect. + pm.addPass(::mlir::xla_lhlo::createLegalizeToGpuPass()); + // Fuse linalg operations. This will yield a single tiled loop nest where // Go from linalg to normal loops. pm.addPass(::mlir::linalg::createConvertLinalgToLoopsPass()); - pm.addPass(absl::make_unique()); // Canonicalize the code to simplify index computations. pm.addNestedPass<::mlir::FuncOp>(::mlir::createCanonicalizerPass()); - pm.addPass(absl::make_unique()); // The innermost loops will be single-trip. pm.addPass(absl::make_unique()); - pm.addPass(absl::make_unique()); // Run CSE to ensure that loads and stores to the same subview get // recognized as such. pm.addNestedPass<::mlir::FuncOp>(::mlir::createCSEPass()); - pm.addPass(absl::make_unique()); // Forward stores to buffers to loads. pm.addPass(absl::make_unique()); - pm.addPass(absl::make_unique()); // Remove now unused temporary buffers. pm.addPass(absl::make_unique()); - pm.addPass(absl::make_unique()); // Coalesce generated loops to have 1d loops. pm.addPass(::mlir::createLoopCoalescingPass()); // Transform the now 1d loops to gpu launches. @@ -267,17 +311,54 @@ Status LowerLHLOToGPU(mlir::ModuleOp module) { if (failed(pm.run(module))) { return InternalError("Lowering to GPU kernels failed."); } - return Status::OK(); } +namespace { + +/// A pass that does the final lowering to NVVM. It collects all the patterns +/// that are currently required, currently mixing std, linalg and gpu. +class LowerToNVVMPass : public ::mlir::ModulePass { + public: + void runOnModule() override { + ::mlir::ModuleOp m = getModule(); + if (!m.getAttrOfType<::mlir::UnitAttr>( + ::mlir::gpu::GPUDialect::getKernelModuleAttrName())) { + return; + } + + ::mlir::OwningRewritePatternList patterns; + ::mlir::LinalgTypeConverter converter(m.getContext()); + ::mlir::populateStdToLLVMConversionPatterns(converter, patterns); + // TODO(b/145824979) Remove linalg once sliceop is in std. + ::mlir::populateLinalgToLLVMConversionPatterns(converter, patterns, + &getContext()); + ::mlir::populateGpuToNVVMConversionPatterns(converter, patterns); + + ::mlir::ConversionTarget target(getContext()); + target.addIllegalDialect<::mlir::gpu::GPUDialect>(); + target.addIllegalOp<::mlir::LLVM::ExpOp>(); + target.addLegalDialect<::mlir::LLVM::LLVMDialect>(); + target.addLegalDialect<::mlir::NVVM::NVVMDialect>(); + // TODO(csigg): Remove once we support replacing non-root ops. + target.addLegalOp<::mlir::gpu::YieldOp>(); + if (failed(applyPartialConversion(m, target, patterns, &converter))) { + signalPassFailure(); + } + } +}; + +} // anonymous namespace + Status LowerKernelBodiesToNVVM(mlir::ModuleOp module) { // We cannot verify as the signature of the kernel is rewritten. ::mlir::PassManager pm(module.getContext(), /*verifyPasses=*/false); + EnableIRPrinting(&pm); // Rewrite kernel functions to LLVM IR. auto& kernelPm = pm.nest<::mlir::ModuleOp>(); - kernelPm.addPass(::mlir::createLowerGpuOpsToNVVMOpsPass()); + kernelPm.addPass(::mlir::createLowerToCFGPass()); + kernelPm.addPass(absl::make_unique()); // Some basic cleanup. kernelPm.addNestedPass<::mlir::FuncOp>(::mlir::createCanonicalizerPass()); kernelPm.addNestedPass<::mlir::FuncOp>(::mlir::createCSEPass()); diff --git a/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.cc b/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.cc index 03f753e543e..fd38cd3bf5e 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.cc +++ b/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.cc @@ -287,6 +287,21 @@ Status LhloDialectEmitter::HandleCompare(HloInstruction* compare) { return Status::OK(); } +Status LhloDialectEmitter::HandleConstant(HloInstruction* constant) { + auto shape = constant->shape(); + if (!shape.IsArray() || shape.rank() != 0) { + return Unimplemented("non-scalar constants are not supported yet"); + } + TF_ASSIGN_OR_RETURN(auto function, CreateFunction(*constant)); + OpBuilder func_builder(function.getBody()); + + TF_ASSIGN_OR_RETURN(auto value, CreateDenseElementsAttrFromLiteral( + constant->literal(), func_builder)); + func_builder.create(getLocation(constant), value, + *function.args_begin()); + return Status::OK(); +} + Status LhloDialectEmitter::HandleIota(HloInstruction* iota) { mlir::IntegerAttr iota_dim = builder_.getI64IntegerAttr( static_cast(iota)->iota_dimension()); @@ -298,6 +313,18 @@ Status LhloDialectEmitter::HandleIota(HloInstruction* iota) { return Status::OK(); } +Status LhloDialectEmitter::HandleTuple(HloInstruction* tuple) { + // For the root node of the entry computation we can elide writing the tuple + // buffer. We can always figure out the contents of the tuples from buffer + // assignment because we insert copies to ensure non-ambiguous output buffers. + // GpuExecutable never reads the tuple buffer. + if (tuple == + tuple->parent()->parent()->entry_computation()->root_instruction()) { + return Status::OK(); + } + return Unimplemented("handling of typles not yet implemented"); +} + Status LhloDialectEmitter::FinishVisit(HloInstruction* root) { return Status::OK(); } diff --git a/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.h b/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.h index ceea3e28be2..09d6fc3a5bb 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.h +++ b/tensorflow/compiler/xla/service/mlir_gpu/lhlo_dialect_emitter.h @@ -55,11 +55,13 @@ class LhloDialectEmitter : public DfsHloVisitorWithDefault, Status DefaultAction(HloInstruction* instr) override; Status HandleBroadcast(HloInstruction* broadcast) override; Status HandleCompare(HloInstruction* compare) override; + Status HandleConstant(HloInstruction* constant) override; Status HandleCustomCall(HloInstruction* custom_call) override; Status HandleFusion(HloInstruction* fusion) override; Status HandleIota(HloInstruction* iota) override; Status HandleParameter(HloInstruction* parameter) override; Status HandleReduce(HloInstruction* reduce) override; + Status HandleTuple(HloInstruction* tuple) override; Status FinishVisit(HloInstruction* root) override; diff --git a/tensorflow/compiler/xla/service/mlir_gpu/mlir_compiler.cc b/tensorflow/compiler/xla/service/mlir_gpu/mlir_compiler.cc index 92f7e5a08ac..d332392ab2f 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/mlir_compiler.cc +++ b/tensorflow/compiler/xla/service/mlir_gpu/mlir_compiler.cc @@ -230,7 +230,7 @@ static StatusOr> ComputeOperandToValueMap( has_failed = true; continue; } - // host_index is the argument positon to the surrounding function that + // host_index is the argument position to the surrounding function that // contains the launch. This index corresponds to HLO operand indices // by construction. auto host_index = launchop_operand->getArgNumber(); @@ -330,40 +330,43 @@ Status InsertBufferLoadPreduleIntoKernel( builder.create(loc, offset, structOffsetAddr); // Fill the shape. auto shape = operand->shape(); - auto entry_type = - struct_type.getStructElementType(3).getArrayElementType(); - // TODO(b/137624192) Pass in the descriptor to allow for dynamic shapes. - assert(shape.IsArray() && shape.is_static()); - for (auto extent : llvm::enumerate(shape.dimensions())) { - auto index = builder.create( - loc, offset_type, builder.getI64IntegerAttr(extent.index())); - auto shapeEntryPtr = builder.create( - loc, entry_type, descPtr, - llvm::ArrayRef{zero, shapeIndex, index}); - auto extentValue = builder.create( - loc, entry_type, builder.getI64IntegerAttr(extent.value())); - builder.create(loc, extentValue, shapeEntryPtr); - } - // Finally, fill the strides. - // TODO(b/137624192): Take assigned layout into account. - entry_type = struct_type.getStructElementType(4).getArrayElementType(); - Value* accumulator = nullptr; - for (int64 idx = shape.rank() - 1; idx >= 0; --idx) { - auto indexValue = builder.create( - loc, offset_type, builder.getI64IntegerAttr(idx)); - auto strideEntryPtr = builder.create( - loc, entry_type, descPtr, - llvm::ArrayRef{zero, strideIndex, indexValue}); - if (accumulator) { - auto strideValue = builder.create( - loc, entry_type, - builder.getI64IntegerAttr(shape.dimensions(idx + 1))); - accumulator = builder.create( - loc, entry_type, accumulator, strideValue); - } else { - accumulator = one; + // Unless the operand is a scalar pointer, also fill shape and strides. + if (!shape.dimensions().empty()) { + auto entry_type = + struct_type.getStructElementType(3).getArrayElementType(); + // TODO(b/137624192) Pass in the descriptor to allow for dynamic shapes. + assert(shape.IsArray() && shape.is_static()); + for (auto extent : llvm::enumerate(shape.dimensions())) { + auto index = builder.create( + loc, offset_type, builder.getI64IntegerAttr(extent.index())); + auto shapeEntryPtr = builder.create( + loc, entry_type, descPtr, + llvm::ArrayRef{zero, shapeIndex, index}); + auto extentValue = builder.create( + loc, entry_type, builder.getI64IntegerAttr(extent.value())); + builder.create(loc, extentValue, shapeEntryPtr); + } + // Finally, fill the strides. + // TODO(b/137624192): Take assigned layout into account. + entry_type = struct_type.getStructElementType(4).getArrayElementType(); + Value* accumulator = nullptr; + for (int64 idx = shape.rank() - 1; idx >= 0; --idx) { + auto indexValue = builder.create( + loc, offset_type, builder.getI64IntegerAttr(idx)); + auto strideEntryPtr = builder.create( + loc, entry_type, descPtr, + llvm::ArrayRef{zero, strideIndex, indexValue}); + if (accumulator) { + auto strideValue = builder.create( + loc, entry_type, + builder.getI64IntegerAttr(shape.dimensions(idx + 1))); + accumulator = builder.create( + loc, entry_type, accumulator, strideValue); + } else { + accumulator = one; + } + builder.create(loc, accumulator, strideEntryPtr); } - builder.create(loc, accumulator, strideEntryPtr); } // Now we can use the descriptor instead of the original argument. value->replaceAllUsesWith(descPtr); diff --git a/tensorflow/compiler/xla/service/mlir_gpu/tests/mlir_gpu_lhlo_gen_test.cc b/tensorflow/compiler/xla/service/mlir_gpu/tests/mlir_gpu_lhlo_gen_test.cc index 1d37aa1ba75..505d16d11cc 100644 --- a/tensorflow/compiler/xla/service/mlir_gpu/tests/mlir_gpu_lhlo_gen_test.cc +++ b/tensorflow/compiler/xla/service/mlir_gpu/tests/mlir_gpu_lhlo_gen_test.cc @@ -20,6 +20,23 @@ namespace mlir_gpu { class LhloGenTest : public MlirIrGenTestBase {}; +TEST_F(LhloGenTest, Const) { + CompileAndVerifyIr(R"( +HloModule Const + +ENTRY %Const () -> s32[100] { + %const.0 = s32[] constant(10) + ROOT %broadcast.0 = s32[100]{0} broadcast(s32[] %const.0), dimensions={} +})", + R"( +;CHECK: func @constant(%[[ARG0:.*]]: memref) +;CHECK: "xla_lhlo.constant"(%[[ARG0]]) {value = dense<10> : tensor} +;CHECK: func @broadcast(%[[ARG1:.*]]: memref, %[[ARG2:.*]]: memref<100xi32>) +;CHECK: "xla_lhlo.broadcast_in_dim"(%[[ARG1]], %[[ARG2]]) {broadcast_dimensions = dense<[]> : tensor<0xi64>} +)", + LoweringStage::LHLO); +} + TEST_F(LhloGenTest, BrokenAdd) { CompileAndVerifyErrors( R"( @@ -227,6 +244,27 @@ ENTRY %AddMultiply (x: f32[2,2], y: f32[2,2], z: f32[2,2]) -> f32[2,2] { )"); } +TEST_F(LhloGenTest, IotaAddMultiply) { + CompileAndVerifyIr(R"( +HloModule AddMultiply + +ENTRY %AddMultiply (x: s32[2,2], y: s32[2,2]) -> s32[2,2] { + %x = s32[2,2]{1,0} parameter(0) + %y = s32[2,2]{1,0} parameter(1) + + %add = s32[2,2]{1,0} add(s32[2,2]{1,0} %x, s32[2,2]{1,0} %y) + %iota = s32[2, 2]{1,0} iota(), iota_dimension=0 + + ROOT %mul = s32[2,2]{1,0} multiply(s32[2,2]{1,0} %add, s32[2,2]{1,0} %iota) +})", + R"( +;CHECK-NOT: store +;CHECK: %[[RESULT:.*]] = muli %{{.*}}, %{{.*}} +;CHECK: store %[[RESULT]] +)", + LoweringStage::GPU); +} + TEST_F(LhloGenTest, AddMultiplyGPU) { CompileAndVerifyIr(R"( HloModule AddMultiply @@ -255,44 +293,45 @@ ENTRY %AddMultiply (x: f32[2,2], y: f32[2,2], z: f32[2,2]) -> f32[2,2] { LoweringStage::GPU); } -TEST_F(LhloGenTest, FusedReduce) { - CompileAndVerifyIr(R"( -HloModule FusedReduce - -%add (x: f32[], y: f32[]) -> f32[] { - %x = f32[] parameter(0) - %y = f32[] parameter(1) - ROOT %add = f32[] add(f32[] %x, f32[] %y) -} - -%fused_computation (param: f32[100,10]) -> f32[10] { - %param = f32[100,10] parameter(0) - %constant = f32[] constant(0) - ROOT %reduce = f32[10]{0} reduce(f32[100,10]{1,0} %param, f32[] %constant), - dimensions={0}, to_apply=%add -} - -ENTRY %FusedReduce (x: f32[100,10]) -> f32[10] { - %x = f32[100,10] parameter(0) - ROOT %fusion = f32[10]{0} fusion(f32[100,10]{1,0} %x), kind=kInput, - calls=%fused_computation -} -)", - R"( -;CHECK: func @fusion(%[[ARG0:.*]]: [[TYPE:.*]], %[[RESULT:.*]]: [[RTYPE:.*]]) -;CHECK: "xla_lhlo.fusion"() ( { -;CHECK: %[[REF0:.*]] = tensor_load %arg0 : [[TYPE]] -;CHECK: %[[CT0:.*]] = xla_hlo.constant dense<0.000000e+00> -;CHECK: %[[RED:.*]] = "xla_hlo.reduce"(%0, %1) ( { -;CHECK: ^bb0(%[[BARG0:.*]]: [[ETYPE:.*]], %[[BARG1:.*]]: [[ETYPE]]) -;CHECK: %[[ADD:.*]] = xla_hlo.add %[[BARG0]], %[[BARG1]] : [[ETYPE]] -;CHECK: "xla_hlo.return"(%[[ADD]]) -;CHECK: }) -;CHECK: tensor_store %[[RED]], %[[RESULT]] : [[RTYPE]] -;CHECK: "xla_lhlo.terminator"() -;CHECK-NEXT: }) - )"); -} +// TODO(b/137624192): Reenable once we can fuse reductions. +// TEST_F(LhloGenTest, FusedReduce) { +// CompileAndVerifyIr(R"( +// HloModule FusedReduce +// +// %add (x: f32[], y: f32[]) -> f32[] { +// %x = f32[] parameter(0) +// %y = f32[] parameter(1) +// ROOT %add = f32[] add(f32[] %x, f32[] %y) +// } +// +// %fused_computation (param: f32[100,10]) -> f32[10] { +// %param = f32[100,10] parameter(0) +// %constant = f32[] constant(0) +// ROOT %reduce = f32[10]{0} reduce(f32[100,10]{1,0} %param, f32[] %constant), +// dimensions={0}, to_apply=%add +// } +// +// ENTRY %FusedReduce (x: f32[100,10]) -> f32[10] { +// %x = f32[100,10] parameter(0) +// ROOT %fusion = f32[10]{0} fusion(f32[100,10]{1,0} %x), kind=kInput, +// calls=%fused_computation +// } +// )", +// R"( +// ;CHECK: func @fusion(%[[ARG0:.*]]: [[TYPE:.*]], %[[RESULT:.*]]: [[RTYPE:.*]]) +// ;CHECK: "xla_lhlo.fusion"() ( { +// ;CHECK: %[[REF0:.*]] = tensor_load %arg0 : [[TYPE]] +// ;CHECK: %[[CT0:.*]] = xla_hlo.constant dense<0.000000e+00> +// ;CHECK: %[[RED:.*]] = "xla_hlo.reduce"(%0, %1) ( { +// ;CHECK: ^bb0(%[[BARG0:.*]]: [[ETYPE:.*]], %[[BARG1:.*]]: [[ETYPE]]) +// ;CHECK: %[[ADD:.*]] = xla_hlo.add %[[BARG0]], %[[BARG1]] : [[ETYPE]] +// ;CHECK: "xla_hlo.return"(%[[ADD]]) +// ;CHECK: }) +// ;CHECK: tensor_store %[[RED]], %[[RESULT]] : [[RTYPE]] +// ;CHECK: "xla_lhlo.terminator"() +// ;CHECK-NEXT: }) +// )"); +// } TEST_F(LhloGenTest, Broadcast) { CompileAndVerifyIr(R"( diff --git a/tensorflow/compiler/xla/service/multi_output_fusion.cc b/tensorflow/compiler/xla/service/multi_output_fusion.cc index 07b6fb5bf85..41e2b0e9cb1 100644 --- a/tensorflow/compiler/xla/service/multi_output_fusion.cc +++ b/tensorflow/compiler/xla/service/multi_output_fusion.cc @@ -21,6 +21,7 @@ limitations under the License. #include "tensorflow/compiler/xla/service/hlo_opcode.h" #include "tensorflow/compiler/xla/service/hlo_reachability.h" #include "tensorflow/compiler/xla/shape_util.h" +#include "tensorflow/compiler/xla/util.h" #include "tensorflow/core/platform/types.h" namespace xla { @@ -62,7 +63,19 @@ StatusOr MultiOutputFusion::Run(HloModule* module) { continue; } VLOG(10) << "Operand profitable: " << operand->name(); - for (auto user : operand->users()) { + // We don't look at all users of operands as it's quadratic. Only look + // at one slice of users. + const int64 kUserSliceSize = 128; + + const int64 user_slice_begin = + RoundDownToNearest(operand->UserId(instruction), kUserSliceSize); + + const int64 user_slice_end = + std::min(static_cast(operand->users().size()), + user_slice_begin + kUserSliceSize); + + for (int64 i = user_slice_begin; i < user_slice_end; ++i) { + HloInstruction* user = operand->users()[i]; VLOG(10) << "User: " << user->name(); if (user == instruction || !IsFusible(user)) { VLOG(10) << "User is not fusible, or is the instruction itself: " diff --git a/tensorflow/compiler/xla/service/multi_output_fusion.h b/tensorflow/compiler/xla/service/multi_output_fusion.h index 403b5dfaff9..9be69f808c4 100644 --- a/tensorflow/compiler/xla/service/multi_output_fusion.h +++ b/tensorflow/compiler/xla/service/multi_output_fusion.h @@ -79,7 +79,7 @@ class MultiOutputFusion : public HloModulePass { // Test if it's legal to fuse instr1 and instr2 into one fusion instruction. virtual bool LegalToFuse(HloInstruction* instr1, HloInstruction* instr2); - // Fuse HloInstrctuion instr1 and instr2 and return the fused instruction. + // Fuse HloInstruction instr1 and instr2 and return the fused instruction. // The other instruction is removed from its parent computation. virtual HloInstruction* Fuse(HloInstruction* instr1, HloInstruction* instr2); diff --git a/tensorflow/compiler/xla/service/op_expander_pass.h b/tensorflow/compiler/xla/service/op_expander_pass.h index 276e3d70b8e..49b3ba07031 100644 --- a/tensorflow/compiler/xla/service/op_expander_pass.h +++ b/tensorflow/compiler/xla/service/op_expander_pass.h @@ -34,7 +34,7 @@ class OpExpanderPass : public HloModulePass { virtual bool InstructionMatchesPattern(HloInstruction* instruction) = 0; // Returns a replacement for `instruction`, or nullptr if no replacement is - // neeeded (e.g. only the to_apply subcomputation of the instruction was + // needed (e.g. only the to_apply subcomputation of the instruction was // modified). virtual StatusOr ExpandInstruction( HloInstruction* instruction) = 0; diff --git a/tensorflow/compiler/xla/service/reshape_mover.cc b/tensorflow/compiler/xla/service/reshape_mover.cc index 9e2d7406940..cd11b211747 100644 --- a/tensorflow/compiler/xla/service/reshape_mover.cc +++ b/tensorflow/compiler/xla/service/reshape_mover.cc @@ -80,7 +80,7 @@ bool CanTriviallyChangeShape(const HloInstruction* instruction) { return true; } - // A broadcase of scalar can trivially change its shape. + // A broadcast of scalar can trivially change its shape. if (instruction->opcode() == HloOpcode::kBroadcast && ShapeUtil::IsScalar(instruction->operand(0)->shape())) { return true; diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc index 345a077e321..e12e1577211 100644 --- a/tensorflow/compiler/xla/service/service.cc +++ b/tensorflow/compiler/xla/service/service.cc @@ -660,7 +660,7 @@ Status Service::ExecuteGraphParallel(const ExecuteGraphParallelRequest* arg, const ExecuteGraphRequest& request = arg->requests(i); TF_RET_CHECK(request.has_computation()) << "computations may not be empty"; TF_RET_CHECK(request.computation().has_host_program_shape()) - << "programe shape may not be empty"; + << "program shape may not be empty"; // Get the executors. TF_ASSIGN_OR_RETURN(auto executors, GetExecutors(execution_options, @@ -837,7 +837,7 @@ Status Service::Compile(const CompileRequest* arg, CompileResponse* result) { return InvalidArgument("computations may not be empty"); } if (!arg->computation().has_host_program_shape()) { - return InvalidArgument("programe shape may not be empty"); + return InvalidArgument("program shape may not be empty"); } if (arg->execution_options().device_handles_size() > 1) { @@ -887,7 +887,7 @@ Status Service::Execute(const ExecuteRequest* arg, ExecuteResponse* result) { ResolveAndValidateArguments(arg->arguments(), replicas)); // Check that the replicated_arguments has the same shape and layout as the - // module config used when creating the exectuable. + // module config used when creating the executable. const int64 num_module_args = executable->module_config().entry_computation_layout().parameter_count(); if (num_module_args != arg->arguments_size()) { @@ -902,7 +902,7 @@ Status Service::Execute(const ExecuteRequest* arg, ExecuteResponse* result) { const Shape& shape_arg = replicated_arguments.front()[i]->on_host_shape(); if (!ShapeUtil::Equal(shape_module, shape_arg)) { return InvalidArgumentStrCat( - "The executable exepcts the ", i, "th argument in shape ", + "The executable expects the ", i, "th argument in shape ", ShapeUtil::HumanStringWithLayout(shape_module), " but sees ", ShapeUtil::HumanStringWithLayout(shape_arg)); } diff --git a/tensorflow/compiler/xla/service/shape_inference_test.cc b/tensorflow/compiler/xla/service/shape_inference_test.cc index 3e345448a47..b189e047254 100644 --- a/tensorflow/compiler/xla/service/shape_inference_test.cc +++ b/tensorflow/compiler/xla/service/shape_inference_test.cc @@ -1354,7 +1354,7 @@ TEST_F(ShapeInferenceTest, DotWithTwoContractingDimsPasses) { } // BatchMatMul with different batch dimension sizes fails. -TEST_F(ShapeInferenceTest, DotWithMisatchedBatchDimSizesFails) { +TEST_F(ShapeInferenceTest, DotWithMismatchedBatchDimSizesFails) { Shape lhs_shape = ShapeUtil::MakeShape(F32, {2, 11, 3}); Shape rhs_shape = ShapeUtil::MakeShape(F32, {3, 3, 14}); @@ -1373,7 +1373,7 @@ TEST_F(ShapeInferenceTest, DotWithMisatchedBatchDimSizesFails) { } // BatchMatMul with different batch dimension numbers passes -TEST_F(ShapeInferenceTest, DotWithMisatchedBatchDimNumbersPasses) { +TEST_F(ShapeInferenceTest, DotWithMismatchedBatchDimNumbersPasses) { Shape lhs_shape = ShapeUtil::MakeShape(F32, {2, 11, 3}); Shape rhs_shape = ShapeUtil::MakeShape(F32, {3, 2, 14}); diff --git a/tensorflow/compiler/xla/service/transfer_manager.h b/tensorflow/compiler/xla/service/transfer_manager.h index f08862bff26..40fda188fe3 100644 --- a/tensorflow/compiler/xla/service/transfer_manager.h +++ b/tensorflow/compiler/xla/service/transfer_manager.h @@ -270,6 +270,13 @@ class TransferManager { static StatusOr GetForPlatform( const se::Platform* platform); + // Writes the given device-memory pointers in 'elements' to the given region + // to construct a tuple index table in the platform-specific tuple + // representation. + virtual Status WriteSingleTupleIndexTable( + se::Stream* stream, absl::Span elements, + const Shape& shape, se::DeviceMemoryBase* region) = 0; + protected: // Transfer a memory block of the given size from the device source into the // 'destination' buffer. @@ -287,13 +294,6 @@ class TransferManager { const void* source, se::DeviceMemoryBase* destination); - // Writes the given device-memory pointers in 'elements' to the given region - // to construct a tuple index table in the platform-specific tuple - // representation. - virtual Status WriteSingleTupleIndexTable( - se::Stream* stream, absl::Span elements, - const Shape& shape, se::DeviceMemoryBase* region) = 0; - private: // The mutex that guards the platform-to-transfer manager map. static tensorflow::mutex platform_transfer_manager_mutex_; diff --git a/tensorflow/compiler/xla/service/tree_reduction_rewriter.h b/tensorflow/compiler/xla/service/tree_reduction_rewriter.h index a9852d88a6e..d6e1d4200e9 100644 --- a/tensorflow/compiler/xla/service/tree_reduction_rewriter.h +++ b/tensorflow/compiler/xla/service/tree_reduction_rewriter.h @@ -35,7 +35,7 @@ namespace xla { // // Applying this pass until a fixed point performs a variant of pairwise // summation (https://en.wikipedia.org/wiki/Pairwise_summation), which is -// guaranteed to have an assymptotically smaller error bound provided that +// guaranteed to have an asymptotically smaller error bound provided that // intermediate roundoff errors are random and have random sign. // // If this pass lowers the performance too much, the window size can always be diff --git a/tensorflow/compiler/xla/service/tuple_points_to_analysis.h b/tensorflow/compiler/xla/service/tuple_points_to_analysis.h index cb589326ba7..c223378b332 100644 --- a/tensorflow/compiler/xla/service/tuple_points_to_analysis.h +++ b/tensorflow/compiler/xla/service/tuple_points_to_analysis.h @@ -302,7 +302,7 @@ class TuplePointsToAnalysis : public DfsHloVisitorWithDefault { // Information kept per instruction struct PerInstruction { std::unique_ptr points_to_set; - // Empircally, ~92% of instructions have 1 + // Empirically, ~92% of instructions have 1 // instruction_defined_buffer, and 99% have 0 or 1 BufferDefinitionVector instruction_defined_buffers; }; diff --git a/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc b/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc index 8b381dec073..1f2dcda288a 100644 --- a/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc +++ b/tensorflow/compiler/xla/service/while_loop_constant_sinking.cc @@ -112,7 +112,7 @@ StatusOr WhileLoopConstantSinking::Run(HloModule* module) { bool changed = false; std::vector while_instrs; for (auto* comp : module->MakeNonfusionComputations()) { - // Right now we don't particulary care about optimizing while-of-while + // Right now we don't particularly care about optimizing while-of-while // patterns. If/When we do, we'll want to visit the outer while (while_0) // before we visit the inner while (while_1): // diff --git a/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc b/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc index 351feec6bb7..2d33184b7d0 100644 --- a/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc +++ b/tensorflow/compiler/xla/service/while_loop_invariant_code_motion.cc @@ -317,7 +317,7 @@ StatusOr WhileLoopInvariantCodeMotion::Run(HloModule* module) { // TryHoistingInvariantInstructionsFromWhileBody can be generalized to // optimize the condition computation too, if needed. // - // The transform we do here is a pessmization for while loops that execute + // The transform we do here is a pessimization for while loops that execute // zero times*, but at this time we expect those to be rare. If this // becomes a problem we can consider using the conditional HLO to avoid // doing extra work for while loops with zero trip count. diff --git a/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc b/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc index 8ec6e40044c..cff0fd458e5 100644 --- a/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc +++ b/tensorflow/compiler/xla/service/while_loop_simplifier_test.cc @@ -126,7 +126,7 @@ WhileLoopSimplifierTest::MakeModuleWithSimpleLoopTupleElementLoopBound( return ParseAndReturnVerifiedModule(hlo_string).ValueOrDie(); } -TEST_F(WhileLoopSimplifierTest, LoopWithZeroIterationSimiplified) { +TEST_F(WhileLoopSimplifierTest, LoopWithZeroIterationSimplified) { auto m = MakeModuleWithSimpleLoop(/*num_iters=*/0); ASSERT_TRUE(WhileLoopSimplifier().Run(m.get()).ValueOrDie()); EXPECT_THAT(m->entry_computation()->root_instruction(), diff --git a/tensorflow/compiler/xla/shape_layout.cc b/tensorflow/compiler/xla/shape_layout.cc index 44ed3181162..05401156270 100644 --- a/tensorflow/compiler/xla/shape_layout.cc +++ b/tensorflow/compiler/xla/shape_layout.cc @@ -48,7 +48,7 @@ void ShapeLayout::SetToDefaultLayout() { bool ShapeLayout::MatchesLayoutInShape(const Shape& shape, bool minor_to_major_only) const { - auto equal = Shape::Equal(); + auto equal = Shape::Equal().IgnoreDynamicDimension(); if (minor_to_major_only) { equal.MinorToMajorOnlyInLayout(); } @@ -81,11 +81,11 @@ void ShapeLayout::ResetLayout(const Layout& layout, } bool ShapeLayout::operator==(const ShapeLayout& other) const { - return ShapeUtil::Equal(shape_, other.shape_); + return Shape::Equal().IgnoreDynamicDimension()(shape_, other.shape_); } bool ShapeLayout::operator!=(const ShapeLayout& other) const { - return !ShapeUtil::Equal(shape_, other.shape_); + return !Shape::Equal().IgnoreDynamicDimension()(shape_, other.shape_); } } // namespace xla diff --git a/tensorflow/compiler/xla/shape_test.cc b/tensorflow/compiler/xla/shape_test.cc index aa6c7d10989..47680a6ba32 100644 --- a/tensorflow/compiler/xla/shape_test.cc +++ b/tensorflow/compiler/xla/shape_test.cc @@ -45,13 +45,13 @@ class ShapeTest : public ::testing::Test { ShapeUtil::MakeTupleShape({opaque_, scalar_, matrix_, matrix2_}); const Shape nested_tuple_ = ShapeUtil::MakeTupleShape({tuple_, matrix_, token_}); - const Shape dyanmic_matrix_ = + const Shape dynamic_matrix_ = ShapeUtil::MakeShape(S32, {5, 2}, {true, false}); }; TEST_F(ShapeTest, ShapeToFromProto) { for (const Shape& shape : {opaque_, token_, scalar_, matrix_, matrix2_, - tuple_, nested_tuple_, dyanmic_matrix_}) { + tuple_, nested_tuple_, dynamic_matrix_}) { Shape shape_copy(shape.ToProto()); EXPECT_TRUE(ShapeUtil::Equal(shape, shape_copy)) << shape << " != " << shape_copy; @@ -215,7 +215,7 @@ TEST_F(ShapeTest, ProgramShapeToString) { TEST_F(ShapeTest, SupportsAbslHash) { EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( {opaque_, token_, scalar_, scalar_with_tile_, matrix_, matrix2_, tuple_, - nested_tuple_, dyanmic_matrix_})); + nested_tuple_, dynamic_matrix_})); } } // namespace diff --git a/tensorflow/compiler/xla/shape_util.h b/tensorflow/compiler/xla/shape_util.h index 668274ae714..769094b1f0b 100644 --- a/tensorflow/compiler/xla/shape_util.h +++ b/tensorflow/compiler/xla/shape_util.h @@ -388,6 +388,9 @@ class ShapeUtil { static Shape MakeShape(PrimitiveType element_type, absl::Span dimensions); + // Make a scalar shape with given primitive type. + static Shape MakeScalarShape(PrimitiveType element_type); + // Constructs a new shape with the given element type and sequence of // potentially dynamic dimensions. The argument 'dynamic_dimensions' indicates // with a true value that the respective dimension is dynamic. If the @@ -398,9 +401,6 @@ class ShapeUtil { absl::Span dimensions, const std::vector& dynamic_dimensions); - // Make a scalar shape with given primitive type. - static Shape MakeScalarShape(PrimitiveType element_type); - // Constructs a new shape with the given element type and sequence of // dimensions. Method checks if the element type is valid and the shape's // size fits in std::numeric_limits::max(). @@ -430,7 +430,6 @@ class ShapeUtil { static Shape MakeShapeWithSparseLayout(PrimitiveType element_type, absl::Span dimensions, int64 max_sparse_elements); - // Returns the same shape except with all dimensions set to be static. static Shape MakeShapeWithStaticDimensions(const Shape& shape); diff --git a/tensorflow/compiler/xla/status_macros_test.cc b/tensorflow/compiler/xla/status_macros_test.cc index 4b0740dad72..d1ed11c227e 100644 --- a/tensorflow/compiler/xla/status_macros_test.cc +++ b/tensorflow/compiler/xla/status_macros_test.cc @@ -90,7 +90,7 @@ TEST(StatusMacros, ReturnIfErrorOnError) { EXPECT_EQ(rc.status().code(), tensorflow::error::INTERNAL); } -TEST(StatusMacros, AssignOrReturnSuccessufully) { +TEST(StatusMacros, AssignOrReturnSuccessfully) { Status status = []() { TF_ASSIGN_OR_RETURN(int value, CreateIntSuccessfully()); EXPECT_EQ(value, 42); diff --git a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc index 881d9c5879e..3bb2f619499 100644 --- a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc +++ b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc @@ -3009,7 +3009,7 @@ XLA_TEST_F(ArrayElementwiseOpTest, NonIdentityBroadcastOfSameRankIsDisallowed) { // Regression test for b/31927799. "slice - y" is fused and requires implicit // broadcast. -XLA_TEST_F(ArrayElementwiseOpTest, ImplictBroadcastInFusedExpressions) { +XLA_TEST_F(ArrayElementwiseOpTest, ImplicitBroadcastInFusedExpressions) { XlaBuilder builder(TestName()); auto x_literal = LiteralUtil::CreateR1({1, 2, 3}); auto y_literal = LiteralUtil::CreateR1({4, 5}); diff --git a/tensorflow/compiler/xla/tests/bfloat16_test.cc b/tensorflow/compiler/xla/tests/bfloat16_test.cc index 63e48117056..a1b11fc87b2 100644 --- a/tensorflow/compiler/xla/tests/bfloat16_test.cc +++ b/tensorflow/compiler/xla/tests/bfloat16_test.cc @@ -76,8 +76,8 @@ XLA_TEST_F(Bfloat16Test, NegateScalarF16) { error_spec_); } -// Disabled on interpreter since BatchNormExanper is not run by default on the -// intepreter backend. +// Disabled on interpreter since BatchNormExpander is not run by default on the +// interpreter backend. XLA_TEST_F(Bfloat16Test, DISABLED_ON_INTERPRETER(BatchNormTraining)) { const int kFeatureIndex = 2; XlaBuilder builder(TestName()); @@ -112,8 +112,8 @@ XLA_TEST_F(Bfloat16Test, DISABLED_ON_INTERPRETER(BatchNormTraining)) { ComputeAndCompareTuple(&builder, expected, {}, ErrorSpec(0.01, 0.02)); } -// Disabled on interpreter since BatchNormExanper is not run by default on the -// intepreter backend. +// Disabled on interpreter since BatchNormExpander is not run by default on the +// interpreter backend. XLA_TEST_F(Bfloat16Test, DISABLED_ON_INTERPRETER(BatchNormGrad)) { const int kFeatureIndex = 2; XlaBuilder builder(TestName()); diff --git a/tensorflow/compiler/xla/tests/collective_ops_test.cc b/tensorflow/compiler/xla/tests/collective_ops_test.cc index 8de508e876e..56c5f688312 100644 --- a/tensorflow/compiler/xla/tests/collective_ops_test.cc +++ b/tensorflow/compiler/xla/tests/collective_ops_test.cc @@ -27,7 +27,7 @@ limitations under the License. #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/lib/core/threadpool.h" -// Tests cross-GPU operatons. +// Tests cross-GPU operations. // // This test requires at least four GPUs. For instructions on running this // within Google, see go/multi-gpu-unit-test. diff --git a/tensorflow/compiler/xla/tests/convolution_test.cc b/tensorflow/compiler/xla/tests/convolution_test.cc index f91847e0010..097265f3bb1 100644 --- a/tensorflow/compiler/xla/tests/convolution_test.cc +++ b/tensorflow/compiler/xla/tests/convolution_test.cc @@ -1148,7 +1148,7 @@ TYPED_TEST(Convolve2D_1x4x4x160_3x3x1x160_Depthwise_Input_Batch_In_Lanes, } template -class Convolve2D_1x4x4x160_3x3x1x160_Dephtwise_Both_Batch_In_Lanes +class Convolve2D_1x4x4x160_3x3x1x160_Depthwise_Both_Batch_In_Lanes : public ConvolutionTest { public: void RunTest() { @@ -1210,9 +1210,9 @@ class Convolve2D_1x4x4x160_3x3x1x160_Dephtwise_Both_Batch_In_Lanes } }; -TYPED_TEST_CASE(Convolve2D_1x4x4x160_3x3x1x160_Dephtwise_Both_Batch_In_Lanes, +TYPED_TEST_CASE(Convolve2D_1x4x4x160_3x3x1x160_Depthwise_Both_Batch_In_Lanes, TestTypes); -TYPED_TEST(Convolve2D_1x4x4x160_3x3x1x160_Dephtwise_Both_Batch_In_Lanes, +TYPED_TEST(Convolve2D_1x4x4x160_3x3x1x160_Depthwise_Both_Batch_In_Lanes, Types) { this->RunTest(); } diff --git a/tensorflow/compiler/xla/tests/dynamic_ops_test.cc b/tensorflow/compiler/xla/tests/dynamic_ops_test.cc index 1ea72af5f5f..9ea27585e61 100644 --- a/tensorflow/compiler/xla/tests/dynamic_ops_test.cc +++ b/tensorflow/compiler/xla/tests/dynamic_ops_test.cc @@ -775,7 +775,7 @@ void BM_DynamicSlice(int num_iters) { stream.get(), start_index_literal, shaped_buffers[i])); } - // Add DynamicSlice op to the computatation. + // Add DynamicSlice op to the computation. DynamicSlice(input, start_indices, {1, 1, 1, 1}); auto computation = builder.Build().ConsumeValueOrDie(); diff --git a/tensorflow/compiler/xla/tests/exhaustive_binary_test.cc b/tensorflow/compiler/xla/tests/exhaustive_binary_test.cc index 64372788be4..3c14f78429a 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_binary_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_binary_test.cc @@ -131,7 +131,7 @@ template ::value || std::is_same::value>::type* = nullptr> T ReferenceMax(T x, T y) { - // We need to propagate NAN here becasue std::max may not propagate NAN. + // We need to propagate NAN here because std::max may not propagate NAN. if (std::fpclassify(x) == FP_NAN) { return x; } @@ -146,7 +146,7 @@ template ::value || std::is_same::value>::type* = nullptr> T ReferenceMin(T x, T y) { - // We need to propagate NAN here becasue std::max may not propagate NAN. + // We need to propagate NAN here because std::max may not propagate NAN. if (std::fpclassify(x) == FP_NAN) { return x; } @@ -319,7 +319,7 @@ INSTANTIATE_TEST_SUITE_P( // for each sub-test to avoid timeout because the implementation of ExpectNear // more than 2x slower for binary test. INSTANTIATE_TEST_SUITE_P( - LargeAndSmallMagnituedNormalValues, ExhaustiveF32BinaryTest, + LargeAndSmallMagnitudeNormalValues, ExhaustiveF32BinaryTest, ::testing::Combine( ::testing::ValuesIn(GetFpValuesForMagnitudeExtremeNormals(40000, 2000)), @@ -402,7 +402,7 @@ INSTANTIATE_TEST_SUITE_P( // Similar to ExhaustiveF64BinaryTest, we use a smaller set of inputs for each // for each sub-test comparing with the unary test to avoid timeout. INSTANTIATE_TEST_SUITE_P( - LargeAndSmallMagnituedNormalValues, ExhaustiveF64BinaryTest, + LargeAndSmallMagnitudeNormalValues, ExhaustiveF64BinaryTest, ::testing::Combine( ::testing::ValuesIn( GetFpValuesForMagnitudeExtremeNormals(40000, 2000)), diff --git a/tensorflow/compiler/xla/tests/exhaustive_op_test_utils.h b/tensorflow/compiler/xla/tests/exhaustive_op_test_utils.h index 3d77b44b53a..1aa06a0aa63 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_op_test_utils.h +++ b/tensorflow/compiler/xla/tests/exhaustive_op_test_utils.h @@ -84,7 +84,7 @@ class ExhaustiveOpTestBase : public ClientLibraryTestBase { : (T == F16 || T == BF16) ? U16 : PRIMITIVE_TYPE_INVALID; }; - // Native types that correspond to the primtive types above. + // Native types that correspond to the primitive types above. using NativeT = typename primitive_util::PrimitiveTypeToNative::type; using NativeRefT = typename primitive_util::PrimitiveTypeToNative::type; @@ -746,7 +746,7 @@ class ExhaustiveOpTestBase : public ClientLibraryTestBase { // The platform under test. const string platform_; - // Testing will ignore inputs for which known_incorect_fn_ returns true. The + // Testing will ignore inputs for which known_incorrect_fn_ returns true. The // argument to the function is the raw bits for the data being test, zero // extended to 64 bits if the data type is less than 64 bits. std::function known_incorrect_fn_; diff --git a/tensorflow/compiler/xla/tests/exhaustive_unary_test.cc b/tensorflow/compiler/xla/tests/exhaustive_unary_test.cc index a19f7eea3bd..0ab27554a0c 100644 --- a/tensorflow/compiler/xla/tests/exhaustive_unary_test.cc +++ b/tensorflow/compiler/xla/tests/exhaustive_unary_test.cc @@ -165,7 +165,7 @@ using ExhaustiveUnaryTest = ExhaustiveOpTestBase; // Test parameter is a tuple containing // - primitive type under test, // - (begin, end) range under test, as zero-extended int64s bitcast to the -// primtive type under test. +// primitive type under test. template class Exhaustive32BitOrLessUnaryTest : public ExhaustiveUnaryTest, @@ -727,7 +727,7 @@ INSTANTIATE_TEST_SUITE_P(NormalValues, ExhaustiveF64UnaryTest, // Tests a total of 4000000000 inputs, with 16000000 inputs in each sub-test, to // keep the peak memory usage low. INSTANTIATE_TEST_SUITE_P( - LargeAndSmallMagnituedNormalValues, ExhaustiveF64UnaryTest, + LargeAndSmallMagnitudeNormalValues, ExhaustiveF64UnaryTest, ::testing::ValuesIn(GetFpValuesForMagnitudeExtremeNormals( 4000000000ull, 16000000))); @@ -873,7 +873,7 @@ INSTANTIATE_TEST_SUITE_P( // Tests a total of 40000 ^ 2 inputs, with 4000 ^ 2 inputs in each sub-test, to // keep the peak memory usage low. INSTANTIATE_TEST_SUITE_P( - F32LargeAndSmallMagnituedNormalValues, ExhaustiveC64UnaryTest, + F32LargeAndSmallMagnitudeNormalValues, ExhaustiveC64UnaryTest, ::testing::Combine( ::testing::ValuesIn(GetFpValuesForMagnitudeExtremeNormals(40000, 4000)), @@ -960,7 +960,7 @@ INSTANTIATE_TEST_SUITE_P( // Tests a total of 40000 ^ 2 inputs, with 2000 ^ 2 inputs in each sub-test, to // keep the peak memory usage low. INSTANTIATE_TEST_SUITE_P( - LargeAndSmallMagnituedNormalValues, ExhaustiveC128UnaryTest, + LargeAndSmallMagnitudeNormalValues, ExhaustiveC128UnaryTest, ::testing::Combine( ::testing::ValuesIn( GetFpValuesForMagnitudeExtremeNormals(40000, 2000)), diff --git a/tensorflow/compiler/xla/tests/gather_operation_test.cc b/tensorflow/compiler/xla/tests/gather_operation_test.cc index 47d3546fc41..71090077ae8 100644 --- a/tensorflow/compiler/xla/tests/gather_operation_test.cc +++ b/tensorflow/compiler/xla/tests/gather_operation_test.cc @@ -619,7 +619,7 @@ ENTRY main { class GatherClientLibraryTest : public ClientLibraryTestBase {}; -// Disabled on interpreter since ExectuteAsyncOnStream is not supported. +// Disabled on interpreter since ExecuteAsyncOnStream is not supported. XLA_TEST_F(GatherClientLibraryTest, DISABLED_ON_INTERPRETER(DISABLED_ON_GPU(Basic))) { // We create this HLO, but using the XlaBuilder API. diff --git a/tensorflow/compiler/xla/tests/map_test.cc b/tensorflow/compiler/xla/tests/map_test.cc index 4d327a6fe9c..58ff070671d 100644 --- a/tensorflow/compiler/xla/tests/map_test.cc +++ b/tensorflow/compiler/xla/tests/map_test.cc @@ -463,7 +463,7 @@ TEST_F(MapTest, NestedBinaryMap) { ComputeAndCompareR1(&b, {0.1f, 0.5f, 0.25f, 1.0f, 4.0f}, {}); } -TEST_F(MapTest, MapOperantionWithBuildError) { +TEST_F(MapTest, MapOperationWithBuildError) { // Maps (lambda (x y) (+ x y)) onto two R1F32 vectors but uses an unsupported // type combination (F32 + U16) to test that the error is reported to the // outermost XlaBuilder. diff --git a/tensorflow/compiler/xla/tests/multioutput_fusion_test.cc b/tensorflow/compiler/xla/tests/multioutput_fusion_test.cc index 0dcc0c278ae..ef182ba0f0b 100644 --- a/tensorflow/compiler/xla/tests/multioutput_fusion_test.cc +++ b/tensorflow/compiler/xla/tests/multioutput_fusion_test.cc @@ -187,8 +187,10 @@ class MultiOutputFusionTest : public HloTestBase { XLA_TEST_F(MultiOutputFusionTest, 2DNofusion) { RunTest2D(false, 5); } XLA_TEST_F(MultiOutputFusionTest, 2DFusion) { RunTest2D(true, 5); } XLA_TEST_F(MultiOutputFusionTest, 2DFusionSize129) { RunTest2D(true, 129); } -XLA_TEST_F(MultiOutputFusionTest, DiffentTypesNoFusion) { RunTest1D(false, 8); } -XLA_TEST_F(MultiOutputFusionTest, DiffentTypesFusion) { RunTest1D(true, 8); } +XLA_TEST_F(MultiOutputFusionTest, DifferentTypesNoFusion) { + RunTest1D(false, 8); +} +XLA_TEST_F(MultiOutputFusionTest, DifferentTypesFusion) { RunTest1D(true, 8); } XLA_TEST_F(MultiOutputFusionTest, FusionNodeIsRoot) { const char* testcase = R"( diff --git a/tensorflow/compiler/xla/tests/reduce_hlo_test.cc b/tensorflow/compiler/xla/tests/reduce_hlo_test.cc index d0d6a91e84b..2dc2fce10f9 100644 --- a/tensorflow/compiler/xla/tests/reduce_hlo_test.cc +++ b/tensorflow/compiler/xla/tests/reduce_hlo_test.cc @@ -106,7 +106,10 @@ XLA_TEST_P(ReduceWithLayoutTest, Reduce) { {-0.241772294, -0.245131493, -0.160247207}, {-0.179881215, -0.23383224, -0.121976733}}}}); - EXPECT_TRUE(RunAndCompareNoHloPasses(std::move(module), ErrorSpec(1e-5))); + auto reduce_input_relaid = + reduce_input.Relayout(reduce_input_shape->layout()); + EXPECT_TRUE(RunAndCompareNoHloPasses( + std::move(module), {&reduce_input_relaid}, ErrorSpec(1e-5))); } INSTANTIATE_TEST_CASE_P(ReduceWithLayoutTest_Instantiation, diff --git a/tensorflow/compiler/xla/tools/BUILD b/tensorflow/compiler/xla/tools/BUILD index 8e6e9b46100..603e94ca938 100644 --- a/tensorflow/compiler/xla/tools/BUILD +++ b/tensorflow/compiler/xla/tools/BUILD @@ -363,3 +363,12 @@ tf_cc_binary( "@com_google_absl//absl/strings", ], ) + +# This target is used to reproduce miscompiles in OSS outside of TF, and it can +# not have any dependencies apart from the standard library. +cc_library( + name = "driver", + srcs = ["driver.cc"], + tags = ["nofixdeps"], + deps = [], +) diff --git a/tensorflow/compiler/xla/tools/driver.cc b/tensorflow/compiler/xla/tools/driver.cc new file mode 100644 index 00000000000..4b3ed2b58b7 --- /dev/null +++ b/tensorflow/compiler/xla/tools/driver.cc @@ -0,0 +1,470 @@ +/* Copyright 2019 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. +==============================================================================*/ + +// This file should not have any dependencies apart from the standard library, +// as it will be used in OSS outside of this repository. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // NOLINT +#include +#include +#include +#include + +static constexpr int kSeed = 42; +static constexpr int kUpperBound = 100; +static constexpr int kLowerBound = -100; +static const char* const kUsageString = R"( +Driver for executing an HLO reproducer in object form in order to let OSS +users reproduce the miscompiles. + +Expected workflow: + +1) In the .hlo file, rename the root computation to `EntryModule`. +2) Run the .hlo file with XLA_FLAGS=--xla_dump_to set, to obtain the .ll file. +3) Compile and link this file with the object file from step (2). +4) Run the resulting file with the buffer assignment table as an argument, +taken from step 2. The driver will print the output to stderr. +5) Compare the output with optimized and non-optimized .ll file from step (2). +If the outputs differ, there is a miscompile. + +Run with an environment variable VERBOSE set to see logging. +)"; + +// Function to be linked with. +extern "C" { +extern void EntryModule(char* result_buffer, char* run_opts, char** params, + char** buffer_table, int* prof_counters); +} + +namespace { + +[[noreturn]] void ExitWithMsg(std::string msg) { + std::cerr << msg << std::endl; + exit(1); +} + +void Check(bool cond, std::string msg = "Precondition failed") { + if (!cond) { + ExitWithMsg(msg); + } +} + +bool IsVerbose() { return getenv("VERBOSE") != nullptr; } + +void Log(const std::string& msg) { + if (IsVerbose()) { + std::cerr << msg << std::endl; + } +} + +// Needs to be kept in sync with PrimitiveType in xla_data.proto. +enum PrimitiveType { + S16 = 0, + S32, + S64, + U8, + U16, + U32, + U64, + F16, + BF16, + F32, + F64, + C64, + C128 +}; + +const std::vector& primitive_strings() { + static auto vec = new std::vector( + {"s16", "s32", "s64", "u8", "u16", "u32", "u64", "f16", "bf16", "f32", + "f64", "c64", "c128"}); + return *vec; +} + +std::string ToString(PrimitiveType type) { return primitive_strings()[type]; } + +PrimitiveType PrimitiveTypeFromString(std::string s) { + const auto& vec = primitive_strings(); + return static_cast( + std::distance(vec.begin(), std::find(vec.begin(), vec.end(), s))); +} + +int ByteSize(PrimitiveType type) { + std::string s = ToString(type); + s = s.substr(1, s.size()); + return std::stoi(s) / 8; +} + +struct ArrayShape { + PrimitiveType type; + std::vector dimensions; +}; + +// We support tuples only for output, and we do not support nested tuples. +struct TupleShape { + std::vector elements; +}; + +std::string ArrayShapeToString(ArrayShape shape) { + std::ostringstream out; + out << ToString(shape.type) << "["; + for (int i = 0; i < shape.dimensions.size(); i++) { + out << std::to_string(shape.dimensions[i]); + if (i != shape.dimensions.size() - 1) { + out << ","; + } + } + out << "]"; + return out.str(); +} + +// Input: TYPE[D1,D2,...DN] +ArrayShape ArrayShapeFromString(std::string s) { + Log("Array shape from string: " + s); + Check(s.find('(') == std::string::npos, "Tuple shape is not supported"); + std::regex shape_r("([^\\[]+)\\[(.*)\\]"); + std::smatch match; + Check(std::regex_match(s, match, shape_r), "Shape not found"); + std::string type = match[1]; + std::string dims = match[2]; + PrimitiveType ptype = PrimitiveTypeFromString(type); + std::istringstream dims_stream(dims); + std::string dim; + std::vector dimensions; + while (std::getline(dims_stream, dim, ',')) { + dimensions.push_back(std::stoi(dim)); + } + return {ptype, dimensions}; +} + +// E.g. (f32[10,20], u32[]) +TupleShape TupleShapeFromString(std::string s) { + Log("Tuple shape from string: " + s); + if (s[0] != '(') { + return {{ArrayShapeFromString(s)}}; + } + s = s.substr(1, s.size() - 2); + std::istringstream sstream(s); + std::string subshape; + std::vector out; + while (std::getline(sstream, subshape, ' ')) { + if (subshape[subshape.size() - 1] == ',') { + subshape = subshape.substr(0, subshape.size() - 1); + } + out.push_back(ArrayShapeFromString(subshape)); + } + return {out}; +} + +std::string TupleShapeToString(TupleShape shape) { + std::ostringstream out; + if (shape.elements.size() == 1) { + return ArrayShapeToString(shape.elements[0]); + } + out << "("; + for (int idx = 0; idx < shape.elements.size(); idx++) { + out << ArrayShapeToString(shape.elements[idx]); + if (idx != shape.elements.size() - 1) { + out << ", "; + } + } + out << ")"; + return out.str(); +} + +// Information about the buffer assignment. +struct BufferAssignment { + // Mapping from buffer indexes (0-based) to buffer size (in bytes). + std::vector buffers_size; + + // Sparse mapping for shapes. + std::map buffers_shape; + + // Indexes of buffers which are input parameters. + std::vector params_idx; + + // Index of the output parameter. + int output_idx = -1; +}; + +// RAII table for the given assignment. +class BufferTable { + public: + explicit BufferTable(BufferAssignment assignment) : assignment_(assignment) { + int num_buffers = assignment.buffers_size.size(); + ptr_ = new char*[num_buffers]; + for (int buffer_idx = 0; buffer_idx < num_buffers; buffer_idx++) { + // Call malloc to ensure alignment up to std::max_align_t. + ptr_[buffer_idx] = + static_cast(malloc(assignment.buffers_size[buffer_idx])); + } + } + + char** AsPtr() { return ptr_; } + + ~BufferTable() { + int num_buffers = assignment_.buffers_size.size(); + for (int buffer_idx = 0; buffer_idx < num_buffers; buffer_idx++) { + free(ptr_[buffer_idx]); + } + delete[] ptr_; + } + + private: + BufferAssignment assignment_; + char** ptr_; +}; + +// Parse and populate the buffer table; +// +// Example of input: +// +// BufferAssignment: +// allocation 0: 0x27017c46b600, size 32768, parameter 0, shape f32[256,32] at +// ShapeIndex {}: +// value: <3 parameter @0> (size=32768,offset=0): f32[256,32]{1,0} +// allocation 1: 0x27017c46b6b0, size 128, output shape is f32[32], +// maybe-live-out: +// value: <5 reduce @0> (size=128,offset=0): f32[32]{0} +// allocation 2: 0x27017c46b760, size 4, constant: +// value: <4 init_value @0> (size=4,offset=0): f32[] +// allocation 3: 0x27017c46b810, size 4, thread-local: +// value: <0 x.1 @0> (size=4,offset=0): f32[] +// allocation 4: 0x27017c46b8c0, size 4, thread-local: +// value: <1 y.1 @0> (size=4,offset=0): f32[] +// allocation 5: 0x27017c46b970, size 4, output shape is f32[], thread-local: +// value: <2 add.1 @0> (size=4,offset=0): f32[] +BufferAssignment ParseBufferAssignment(std::string fname) { + BufferAssignment assignment; + std::ifstream infile(fname); + std::string line; + while (std::getline(infile, line)) { + std::regex allocation_line_r("allocation ([0-9]): .+, size ([0-9]+), (.+)"); + std::smatch match; + if (std::regex_search(line, match, allocation_line_r)) { + Log("Matched allocation description: " + line); + int allocation_idx = std::stoi(match[1]); + int size = std::stoi(match[2]); + Log("Allocation size = " + std::to_string(size)); + const std::string& postfix = match[3]; + Check(allocation_idx == assignment.buffers_size.size(), + "Unordered allocations in input"); + assignment.buffers_size.push_back(size); + + std::regex output_r("output shape is \\|([^\\|]+)\\|,"); + std::smatch output_match; + if (std::regex_search(postfix, output_match, output_r)) { + Log("Matched out parameter: " + postfix); + Check(assignment.output_idx == -1, "Multiple out-parameters"); + assignment.output_idx = allocation_idx; + std::string output_shape = output_match[1]; + Log("output shape = " + output_shape); + TupleShape shape = TupleShapeFromString(output_shape); + assignment.buffers_shape[assignment.output_idx] = shape; + Log("parsed output shape = " + TupleShapeToString(shape)); + } + + std::regex parameter_r("parameter ([0-9]+), shape \\|([^\\|]+)\\|"); + std::smatch param_match; + if (std::regex_search(postfix, param_match, parameter_r)) { + Log("Matched parameter description: " + postfix); + int param_idx = std::stoi(param_match[1]); + assignment.params_idx.push_back(param_idx); + + std::string param_shape = param_match[2]; + TupleShape shape = TupleShapeFromString(param_shape); + assignment.buffers_shape[allocation_idx] = shape; + Log("parsed parameter shape = " + TupleShapeToString(shape)); + } + } + } + Check(assignment.output_idx != -1, "Output not set"); + return assignment; +} + +int GetNumElements(ArrayShape shape) { + int num_elements = 1; + for (int dim : shape.dimensions) { + num_elements *= dim; + } + return num_elements; +} + +template ::value>> +void FillIntT(void* buffer, int num_elements) { + std::mt19937 generator(kSeed); + T* casted = static_cast(buffer); + std::uniform_int_distribution distr(kLowerBound, kUpperBound); + for (int i = 0; i < num_elements; i++) { + casted[i] = distr(generator); + } +} + +template ::value>> +void FillFloatT(void* buffer, int num_elements) { + std::mt19937 generator(kSeed); + T* casted = static_cast(buffer); + std::uniform_real_distribution distr(kLowerBound, kUpperBound); + for (int i = 0; i < num_elements; i++) { + casted[i] = distr(generator); + } +} + +void Fill(void* buffer, ArrayShape shape) { + int num_elements = GetNumElements(shape); + Log("Number of elements = " + std::to_string(num_elements)); + Log("Shape type = " + ToString(shape.type)); + switch (shape.type) { + case S16: + return FillIntT(buffer, num_elements); // NOLINT + case S32: + return FillIntT(buffer, num_elements); + case S64: + return FillIntT(buffer, num_elements); // NOLINT + case U8: + return FillIntT(buffer, num_elements); + case U16: + return FillIntT(buffer, num_elements); // NOLINT + case U32: + return FillIntT(buffer, num_elements); + case U64: + return FillIntT(buffer, num_elements); // NOLINT + case F32: + return FillFloatT(buffer, num_elements); + case F64: + return FillFloatT(buffer, num_elements); + + case F16: + case BF16: + case C64: + case C128: + ExitWithMsg("Unsupported type: " + ToString(shape.type)); + } +} + +template +void DisplayT(void* buffer, int num_elements) { + T* casted = static_cast(buffer); + for (int i = 0; i < num_elements; i++) { + std::cout << casted[i]; + if (i != num_elements - 1) { + std::cout << ", "; + } + } + std::cout << std::endl; +} + +void Display(void* buffer, ArrayShape shape) { + int num_elements = GetNumElements(shape); + switch (shape.type) { + case S16: + return DisplayT(buffer, num_elements); // NOLINT + case S32: + return DisplayT(buffer, num_elements); + case S64: + return DisplayT(buffer, num_elements); // NOLINT + case U8: + return DisplayT(buffer, num_elements); + case U16: + return DisplayT(buffer, num_elements); // NOLINT + case U32: + return DisplayT(buffer, num_elements); + case U64: + return DisplayT(buffer, num_elements); // NOLINT + case F32: + return DisplayT(buffer, num_elements); + case F64: + return DisplayT(buffer, num_elements); + + case F16: + case BF16: + case C64: + case C128: + ExitWithMsg("Unsupported type: " + ToString(shape.type)); + } +} + +void Display(void* buffer, TupleShape shape) { + if (shape.elements.size() == 1) { + return Display(buffer, shape.elements[0]); + } + std::cout << "(" << std::endl; + void** casted = static_cast(buffer); + for (int tuple_idx = 0; tuple_idx < shape.elements.size(); tuple_idx++) { + ArrayShape array_shape = shape.elements[tuple_idx]; + Display(casted[tuple_idx], array_shape); + if (tuple_idx != shape.elements.size() - 1) { + std::cout << ", " << std::endl; + } + } + std::cout << ")" << std::endl; +} + +} // end namespace + +int main(int argc, char** argv) { + if (argc < 2) { + ExitWithMsg( + "Please provide buffer table filename as an argument, " + "or invoke with --help for usage instructions."); + } + std::string arg = argv[1]; + if (arg == "--help") { + std::cout << kUsageString << std::endl; + return 0; + } + + BufferAssignment assignment = ParseBufferAssignment(arg); + BufferTable table(assignment); + + // Fill out input parameters. + for (int param_idx : assignment.params_idx) { + TupleShape tuple_shape = assignment.buffers_shape[param_idx]; + Check(tuple_shape.elements.size() == 1, "Parameters can not be tuples"); + ArrayShape shape = tuple_shape.elements[0]; + Check(GetNumElements(shape) == + assignment.buffers_size[param_idx] / ByteSize(shape.type), + "Unexpected number of elements"); + Fill(table.AsPtr()[param_idx], shape); + + if (IsVerbose()) { + std::cout << "Filled parameter buffer for param " << param_idx << ": " + << std::endl; + Display(table.AsPtr()[param_idx], shape); + } + } + + Log("Launching module"); + EntryModule(/*result_buffer=*/nullptr, + /*run_opts=*/nullptr, + /*params=*/nullptr, table.AsPtr(), + /*prof_counters=*/nullptr); + + std::cout << "Output:" << std::endl; + Log("Output shape: " + + TupleShapeToString(assignment.buffers_shape[assignment.output_idx])); + Display(table.AsPtr()[assignment.output_idx], + assignment.buffers_shape[assignment.output_idx]); +} diff --git a/tensorflow/compiler/xla/tools/hlo_module_loader.cc b/tensorflow/compiler/xla/tools/hlo_module_loader.cc index 8eb170b25e5..0b16c877964 100644 --- a/tensorflow/compiler/xla/tools/hlo_module_loader.cc +++ b/tensorflow/compiler/xla/tools/hlo_module_loader.cc @@ -86,8 +86,8 @@ StatusOr> LoadModuleFromData( return InvalidArgument("Failed to parse input as HLO protobuf binary"); } } else if (format == "pbtxt") { - if (!proto2::TextFormat::ParseFromString(data, &proto) && - !proto2::TextFormat::ParseFromString(data, proto.mutable_hlo())) { + if (!google::protobuf::TextFormat::ParseFromString(data, &proto) && + !google::protobuf::TextFormat::ParseFromString(data, proto.mutable_hlo())) { return InvalidArgument("Failed to parse input as HLO protobuf text"); } } else { diff --git a/tensorflow/compiler/xla/tools/hlo_proto_to_json.cc b/tensorflow/compiler/xla/tools/hlo_proto_to_json.cc index 88f3a8bdde2..068ef744c33 100644 --- a/tensorflow/compiler/xla/tools/hlo_proto_to_json.cc +++ b/tensorflow/compiler/xla/tools/hlo_proto_to_json.cc @@ -17,8 +17,8 @@ limitations under the License. // hlo_proto_to_json --input_file=some_binary_proto // --output_file=path_to_dump_output // -// Reads one serilized Hlo module, convert it into JSON format and dump into -// some output directory. some_binaray_proto is obtained by serializing Hlo +// Reads one serialized Hlo module, convert it into JSON format and dump into +// some output directory. some_binary_proto is obtained by serializing Hlo // module to disk using the debug options // // --xla_dump_to=DIR --xla_dump_hlo_as_proto diff --git a/tensorflow/compiler/xla/tools/interactive_graphviz_test.sh b/tensorflow/compiler/xla/tools/interactive_graphviz_test.sh index b3e43aa7da0..a1614c443fe 100755 --- a/tensorflow/compiler/xla/tools/interactive_graphviz_test.sh +++ b/tensorflow/compiler/xla/tools/interactive_graphviz_test.sh @@ -14,6 +14,6 @@ # limitations under the License. # ==============================================================================*/ -# This is a placeholder for a compile-only test for intractive_graphviz tool. +# This is a placeholder for a compile-only test for interactive_graphviz tool. exit 0 diff --git a/tensorflow/compiler/xla/tools/run_hlo_module_main.cc b/tensorflow/compiler/xla/tools/run_hlo_module_main.cc index 7079f413eeb..39d7826e162 100644 --- a/tensorflow/compiler/xla/tools/run_hlo_module_main.cc +++ b/tensorflow/compiler/xla/tools/run_hlo_module_main.cc @@ -104,7 +104,7 @@ int main(int argc, char** argv) { tensorflow::Flag( "use_large_float_range", &opts.use_large_float_range, "Generate floating point values using a large uniform-log " - "distribtion as opposed to a small uniform distribution."), + "distribution as opposed to a small uniform distribution."), tensorflow::Flag( "abs_error_bound", &opts.abs_error_bound, "The absolute error bound used when comparing the test and " diff --git a/tensorflow/compiler/xla/util.cc b/tensorflow/compiler/xla/util.cc index 7b17db12595..6711779cd2b 100644 --- a/tensorflow/compiler/xla/util.cc +++ b/tensorflow/compiler/xla/util.cc @@ -341,7 +341,7 @@ std::pair SplitF64ToF32(double x) { CHECK(std::isfinite(x_f32)) << x; // The high float is simply the double rounded to the nearest float. Because - // we are roundinng to nearest with ties to even, the error introduced in + // we are rounding to nearest with ties to even, the error introduced in // rounding is less than half an ULP in the high ULP. const float hi = x_f32; // We can compute the low term using Sterbenz' lemma: If a and b are two diff --git a/tensorflow/compiler/xla/xla_data.proto b/tensorflow/compiler/xla/xla_data.proto index f5218ad4d8c..b0b97f1eb45 100644 --- a/tensorflow/compiler/xla/xla_data.proto +++ b/tensorflow/compiler/xla/xla_data.proto @@ -22,6 +22,8 @@ option cc_enable_arenas = true; // Primitive types are the individual values that can be held in rectangular // multidimensional arrays. A description of the rectangular multidimensional // array dimensions / primitive type is given by Shape, below. +// +// LINT.IfChange enum PrimitiveType { // Invalid primitive type to serve as default. PRIMITIVE_TYPE_INVALID = 0; @@ -82,6 +84,9 @@ enum PrimitiveType { // Next = 19 } +// LINT.ThenChange( +// https://www.tensorflow.org/code/tensorflow/compiler/xla/tools/driver.cc +// ) // Describes the padding configuration for Pad operation. The padding amount on // both edges as well as between the elements are specified for each dimension. @@ -573,7 +578,7 @@ message TriangularSolveOptions { NO_TRANSPOSE = 1; // Don't transpose 'a'. TRANSPOSE = 2; // Transpose 'a'. ADJOINT = 3; // Complex conjugate and transpose 'a'. - }; + } Transpose transpose_a = 4; } diff --git a/tensorflow/compiler/xrt/tests/raw_api_test.cc b/tensorflow/compiler/xrt/tests/raw_api_test.cc index 427a631f82d..68f56a52d0e 100644 --- a/tensorflow/compiler/xrt/tests/raw_api_test.cc +++ b/tensorflow/compiler/xrt/tests/raw_api_test.cc @@ -1527,7 +1527,7 @@ TEST(RawApiTest, CompileAndExecuteWithS64Argument) { xla::Shape(program_shape.result()), xla::S64)); } -// Tests the XRT device memory compation API (XRTCompactAllocations). +// Tests the XRT device memory compaction API (XRTCompactAllocations). TEST(RawApiTest, TestDeviceMemoryCompaction) { static const int kNumAllocs = 32; Scope root = Scope::NewRootScope().WithDevice(DeviceFromFlag()); diff --git a/tensorflow/compiler/xrt/xrt_memory_manager.cc b/tensorflow/compiler/xrt/xrt_memory_manager.cc index 3a304764800..14986be3d1e 100644 --- a/tensorflow/compiler/xrt/xrt_memory_manager.cc +++ b/tensorflow/compiler/xrt/xrt_memory_manager.cc @@ -319,7 +319,7 @@ Status XRTMemoryManager::TryFreeMemoryStep(MemoryReclaimContext* mrctx, } if (!mrctx->done_freeing) { // If the caller passed us a zero requested_free_size, we try to free chunks - // of kMaxFreeSize memory, until either the run function suceeds, or we run + // of kMaxFreeSize memory, until either the run function succeeds, or we run // out of freeable memory. const size_t kMaxFreeSize = 1000000000; size_t free_size = diff --git a/tensorflow/compiler/xrt/xrt_memory_manager.h b/tensorflow/compiler/xrt/xrt_memory_manager.h index 445be45cf57..0dcd07f9faa 100644 --- a/tensorflow/compiler/xrt/xrt_memory_manager.h +++ b/tensorflow/compiler/xrt/xrt_memory_manager.h @@ -87,7 +87,7 @@ class XRTMemoryManager : public ResourceBase { return Status::OK(); } - // Releases an handle by dropping the refences count held on the + // Releases an handle by dropping the references count held on the // XRTTupleAllocation by the XRTMemoryManager. Existing XRTTupleAllocation // references will continue to be valid. Status Release(int64 handle); diff --git a/tensorflow/compiler/xrt/xrt_state.cc b/tensorflow/compiler/xrt/xrt_state.cc index 4558a7d9f80..cb8d9a1d4da 100644 --- a/tensorflow/compiler/xrt/xrt_state.cc +++ b/tensorflow/compiler/xrt/xrt_state.cc @@ -648,7 +648,8 @@ Status XRTTupleAllocation::AliasBufferFrom(const XRTTupleAllocation& source, xla::StatusOr> XRTTupleAllocation::ToDeviceMemoryTree( - const std::function& release_checker) { + const std::function(const xla::ShapeIndex&)>& + release_checker) { xla::ShapeTree shaped_tree(on_device_shape()); for (const auto& index_buffer : buffers_) { if (index_buffer.second == nullptr || @@ -657,7 +658,9 @@ XRTTupleAllocation::ToDeviceMemoryTree( index_buffer.first.ToString(), " has been released"); } - if (!release_checker(index_buffer.first)) { + TF_ASSIGN_OR_RETURN(bool should_release, + release_checker(index_buffer.first)); + if (!should_release) { *shaped_tree.mutable_element(index_buffer.first) = index_buffer.second->allocation(); } else { diff --git a/tensorflow/compiler/xrt/xrt_state.h b/tensorflow/compiler/xrt/xrt_state.h index 810c6128cad..8b87a12cfd6 100644 --- a/tensorflow/compiler/xrt/xrt_state.h +++ b/tensorflow/compiler/xrt/xrt_state.h @@ -229,7 +229,8 @@ class XRTTupleAllocation : public core::RefCounted { // ScopedShapedBuffer, which wants ownership and does not allow sharing. xla::StatusOr> ToDeviceMemoryTree( - const std::function& release_checker); + const std::function(const xla::ShapeIndex&)>& + release_checker); private: // Creates a new handle with (tuple) shape. diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD index 29cfe52a196..935fd2da732 100644 --- a/tensorflow/core/BUILD +++ b/tensorflow/core/BUILD @@ -79,7 +79,6 @@ load( "tf_cc_tests", "tf_copts", "tf_cuda_library", - "tf_cuda_only_cc_test", "tf_features_nomodules_if_android", "tf_features_nomodules_if_emscripten", "tf_gen_op_libs", @@ -99,7 +98,6 @@ load("//tensorflow:tensorflow.bzl", "tf_cuda_cc_test") # Placeholder: load("//tensorflow:tensorflow.bzl", "tf_portable_proto_lib") load("//tensorflow:tensorflow.bzl", "tf_portable_proto_library") -load("//tensorflow:tensorflow.bzl", "tf_version_info_genrule") # For platform specific build config load( @@ -188,9 +186,12 @@ COMMON_PROTO_SRCS = [ "protobuf/saver.proto", "protobuf/verifier_config.proto", "protobuf/trace_events.proto", - "util/event.proto", - "util/memmapped_file_system.proto", - "util/saved_tensor_slice.proto", +] + +UTIL_PROTO_SRCS = [ + "//tensorflow/core/util:event.proto", + "//tensorflow/core/util:memmapped_file_system.proto", + "//tensorflow/core/util:saved_tensor_slice.proto", ] FRAMEWORK_PROTO_SRCS = [ @@ -230,7 +231,7 @@ ERROR_CODES_PROTO_SRCS = [ ] # LINT.ThenChange(//tensorflow/core/android_proto_config.asciipb) -CORE_PROTO_SRCS = COMMON_PROTO_SRCS + FRAMEWORK_PROTO_SRCS + PROFILER_PROTO_SRCS + ERROR_CODES_PROTO_SRCS +CORE_PROTO_SRCS = COMMON_PROTO_SRCS + FRAMEWORK_PROTO_SRCS + UTIL_PROTO_SRCS + PROFILER_PROTO_SRCS + ERROR_CODES_PROTO_SRCS tf_proto_library( name = "protos_all", @@ -238,11 +239,12 @@ tf_proto_library( cc_api_version = 2, make_default_target_header_only = True, protodeps = [ - ":error_codes_proto_impl", - ":test_log_proto_impl", ":core_protos", + ":error_codes_proto_impl", "//tensorflow/core/framework:protos_all", "//tensorflow/core/lib/core:error_codes_proto", + "//tensorflow/core/util:protos_all", + "//tensorflow/core/util:test_log_proto_impl", ], visibility = ["//visibility:public"], ) @@ -274,14 +276,6 @@ closure_proto_library( deps = [":example_protos"], ) -tf_proto_library( - name = "protos_test", - srcs = ["util/example_proto_fast_parsing_test.proto"], - cc_api_version = 2, - protodeps = tf_additional_all_protos(), - visibility = ["//visibility:public"], -) - filegroup( name = "platform_base_hdrs", srcs = [ @@ -332,17 +326,6 @@ cc_library( ], ) -filegroup( - name = "util_port_hdrs", - srcs = [ - "util/port.h", - ], - visibility = [ - "//tensorflow/core:__pkg__", - "//tensorflow/python:__pkg__", - ], -) - filegroup( name = "quantize_training_hdrs", srcs = [ @@ -354,18 +337,6 @@ filegroup( ], ) -cc_library( - name = "util_port", - srcs = ["util/port.cc"], - hdrs = ["util/port.h"], - copts = tf_copts(), - visibility = [ - "//tensorflow/core:__pkg__", - "//tensorflow/python:__pkg__", - ], - alwayslink = 1, -) - filegroup( name = "platform_port_hdrs", srcs = [ @@ -489,7 +460,7 @@ cc_library( "//tensorflow/core/platform:logging", "//tensorflow/core/platform:macros", "//tensorflow/core/platform:protobuf", - "//tensorflow/core/platform:platform", + "//tensorflow/core/platform", "//tensorflow/core/platform:status", "//tensorflow/core/platform:stringpiece", "//tensorflow/core/platform:tstring", @@ -520,6 +491,7 @@ cc_library( "//tensorflow/core/lib/monitoring:legacy_lib_monitoring_lib_headers", "//tensorflow/core/lib/random:legacy_lib_random_headers", "//tensorflow/core/lib/strings:legacy_lib_string_headers", + "//tensorflow/core/util:lib_hdrs", ], visibility = ["//visibility:public"], deps = [ @@ -569,27 +541,10 @@ cc_library( ], ) -# TODO(gunan): Move this to core/util/BUILD once the file is created -cc_library( - name = "util_reporter", - srcs = ["util/reporter.cc"], - hdrs = ["util/reporter.h"], - # This should only be used in tensorflow/core/platform:test_benchmark - visibility = ["//tensorflow/core/platform:__subpackages__"], - deps = [ - ":test_log_proto_impl_cc", - "//tensorflow/core/platform:env", - "//tensorflow/core/platform:errors", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:mutex", - "//tensorflow/core/platform:str_util", - "//tensorflow/core/platform:types", - ], -) - # Test support library needed for all tests # This is currently public, but may be made internal in the # future. Try to avoid depending on it. + cc_library( name = "test", testonly = 1, @@ -597,10 +552,10 @@ cc_library( "//tensorflow/core/platform:legacy_test_srcs", ], hdrs = [ - "util/reporter.h", "//tensorflow/core/lib/core:legacy_lib_core_status_test_util_header", "//tensorflow/core/platform:test.h", "//tensorflow/core/platform:test_benchmark.h", + "//tensorflow/core/util:test_hdrs", ], copts = tf_copts(), linkopts = select({ @@ -629,15 +584,10 @@ tf_cuda_library( hdrs = [ "example/feature_util.h", "//tensorflow/core/framework:allocator.h", - "//tensorflow/core/framework:bounds_check.h", - "//tensorflow/core/framework:variant.h", - "//tensorflow/core/framework:variant_encode_decode.h", - "//tensorflow/core/framework:variant_op_registry.h", - "//tensorflow/core/framework:variant_tensor_data.h", - "//tensorflow/core/framework:shared_ptr_variant.h", "//tensorflow/core/framework:allocator_registry.h", "//tensorflow/core/framework:attr_value_util.h", "//tensorflow/core/framework:bfloat16.h", + "//tensorflow/core/framework:bounds_check.h", "//tensorflow/core/framework:cancellation.h", "//tensorflow/core/framework:collective.h", "//tensorflow/core/framework:common_shape_fns.h", @@ -675,6 +625,7 @@ tf_cuda_library( "//tensorflow/core/framework:selective_registration.h", "//tensorflow/core/framework:session_state.h", "//tensorflow/core/framework:shape_inference.h", + "//tensorflow/core/framework:shared_ptr_variant.h", "//tensorflow/core/framework:stats_aggregator.h", "//tensorflow/core/framework:tensor.h", "//tensorflow/core/framework:tensor_shape.h", @@ -687,48 +638,20 @@ tf_cuda_library( "//tensorflow/core/framework:type_traits.h", "//tensorflow/core/framework:typed_allocator.h", "//tensorflow/core/framework:types.h", - "public/version.h", - "util/activation_mode.h", - "util/batch_util.h", - "util/bcast.h", - "util/matmul_bcast.h", - "util/debug_events_writer.h", - "util/device_name_utils.h", - "util/dump_graph.h", - "util/events_writer.h", - "util/example_proto_fast_parsing.h", - "util/example_proto_helper.h", - "util/gpu_kernel_helper.h", - "util/guarded_philox_random.h", - "util/mirror_pad_mode.h", - "util/padding.h", - "util/einsum_op_util.h", - "util/port.h", - "util/ptr_util.h", - "util/reffed_status_callback.h", - "util/saved_tensor_slice_util.h", - "util/stat_summarizer.h", - "util/stat_summarizer_options.h", - "util/stream_executor_util.h", - "util/strided_slice_op.h", - "util/tensor_format.h", - "util/tensor_ops_util.h", - "util/tensor_slice_reader.h", - "util/tensor_slice_reader_cache.h", - "util/tensor_slice_writer.h", - "util/use_cudnn.h", - "util/matmul_autotune.h", - "util/util.h", - "util/work_sharder.h", + "//tensorflow/core/framework:variant.h", + "//tensorflow/core/framework:variant_encode_decode.h", + "//tensorflow/core/framework:variant_op_registry.h", + "//tensorflow/core/framework:variant_tensor_data.h", "//tensorflow/core/util/sparse:framework_group", + "//tensorflow/core/util:framework_srcs", + "public/version.h", ] + select({ "//tensorflow:windows": [], "//conditions:default": [ - "util/memmapped_file_system.h", - "util/memmapped_file_system_writer.h", + "//tensorflow/core/util:memmapped_file_system_hdrs", ], }) + if_mkl([ - "util/mkl_util.h", + "//tensorflow/core/util:mkl_util_hdrs", ]), visibility = ["//visibility:public"], deps = [ @@ -737,122 +660,47 @@ tf_cuda_library( ], ) -# This is redundant with the "framework" target above. It's useful for -# applications that want to depend on a minimal subset of TensorFlow (e.g. XLA). -cc_library( +# TODO(gonnet): Remove this alias once all users have been moved to the actual target. +alias( name = "allocator", - srcs = [ - "//tensorflow/core/framework:allocator.cc", - "//tensorflow/core/framework:allocator_registry.h", - "//tensorflow/core/framework:numeric_types.h", - "//tensorflow/core/framework:tracking_allocator.cc", - "//tensorflow/core/framework:tracking_allocator.h", - "//tensorflow/core/framework:type_traits.h", - ], - hdrs = [ - "//tensorflow/core/framework:allocator.h", - ], - features = ["parse_headers"], + actual = "//tensorflow/core/framework:allocator", visibility = ["//visibility:public"], - deps = [ - ":lib", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:optional", - "//third_party/eigen3", - ] + if_static(extra_deps = [":allocator_registry_impl"]), - alwayslink = 1, ) -# This target will be included in libtensorflow_framework.so via the -# framework_internal_impl target. -# All other dependencies on this target need to go through if_static guard, -# as otherwise duplicate registration in the registry will cause crashes. -cc_library( +# TODO(gonnet): Remove this alias once all users have been moved to the actual target. +alias( name = "allocator_registry_impl", - srcs = [ - "//tensorflow/core/framework:allocator.h", - "//tensorflow/core/framework:allocator_registry.cc", - "//tensorflow/core/framework:allocator_registry.h", - "//tensorflow/core/framework:cpu_allocator_impl.cc", - "//tensorflow/core/framework:numeric_types.h", - "//tensorflow/core/framework:tracking_allocator.h", - "//tensorflow/core/framework:type_traits.h", - ], - deps = [ - ":lib", - "//third_party/eigen3", - "@com_google_absl//absl/strings", - "@com_google_absl//absl/types:optional", - ], - alwayslink = 1, + actual = "//tensorflow/core/framework:allocator_registry_impl", + visibility = ["//visibility:public"], ) -cc_library( - name = "stats_calculator_portable", - srcs = [ - "util/stat_summarizer_options.h", - "util/stats_calculator.cc", - ], - hdrs = [ - "util/stats_calculator.h", - ], - copts = tf_copts(), -) - -tf_cc_test( - name = "stats_calculator_test", - srcs = ["util/stats_calculator_test.cc"], - deps = [ - ":stats_calculator_portable", - ":test", - ":test_main", - ], -) - -cc_library( +alias( name = "overflow", - hdrs = ["util/overflow.h"], - deps = [ - ":framework_lite", - ":lib", - ], + actual = "//tensorflow/core/util:overflow", ) -cc_library( +alias( name = "exec_on_stall", - hdrs = ["util/exec_on_stall.h"], - deps = [":framework_lite"], + actual = "//tensorflow/core/util:exec_on_stall", ) -cc_library( +alias( name = "ptr_util", - hdrs = ["util/ptr_util.h"], + actual = "//tensorflow/core/util:ptr_util", ) -cc_library( +# TODO(gonnet): Remove this alias once all users have been moved to the actual target. +alias( name = "reader_base", - srcs = ["//tensorflow/core/framework:reader_base.cc"], - hdrs = ["//tensorflow/core/framework:reader_base.h"], + actual = "//tensorflow/core/framework:reader_base", visibility = ["//visibility:public"], - deps = [ - ":framework", - ":lib", - ":protos_all_cc", - ], ) -cc_library( +# TODO(gonnet): Remove this alias once all users have been moved to the actual target. +alias( name = "op_gen_lib", - srcs = ["//tensorflow/core/framework:op_gen_lib.cc"], - hdrs = ["//tensorflow/core/framework:op_gen_lib.h"], + actual = "//tensorflow/core/framework:op_gen_lib", visibility = ["//visibility:public"], - deps = [ - ":lib", - ":lib_internal", - ":protos_all_cc", - "//tensorflow/core/util/proto:proto_utils", - "@com_google_absl//absl/strings", - ], ) cc_library( @@ -874,8 +722,6 @@ cc_library( "//tensorflow/core/lib/bfloat16:bfloat16.h", "//tensorflow/core/platform:byte_order.h", "//tensorflow/core/platform:cpu_info.h", - "//tensorflow/core/platform:default/integral_types.h", - "//tensorflow/core/platform:default/logging.h", "//tensorflow/core/platform:dynamic_annotations.h", "//tensorflow/core/platform:macros.h", "//tensorflow/core/platform:mutex.h", @@ -885,12 +731,14 @@ cc_library( "//tensorflow/core/platform:thread_annotations.h", "//tensorflow/core/platform:tstring.h", "//tensorflow/core/platform:types.h", + "//tensorflow/core/platform/default:integral_types.h", + "//tensorflow/core/platform/default:logging.h", ], visibility = ["//visibility:public"], deps = - if_mobile([ + [ "@nsync//:nsync_cpp", - ]) + [ + ] + [ "//third_party/eigen3", "//tensorflow/core/lib/bfloat16", "//tensorflow/core/platform:dynamic_annotations", @@ -1105,8 +953,8 @@ cc_library( "ops/ragged_to_dense_util.h", ], deps = [ + ":framework", ":protos_all_cc", - "//tensorflow/core:framework", ], ) @@ -1116,11 +964,11 @@ tf_cc_test( "ops/ragged_to_dense_util_test.cc", ], deps = [ + ":framework", ":protos_all_cc", ":ragged_to_dense_util", ":test", ":testlib", - "//tensorflow/core:framework", "@com_google_googletest//:gtest_main", ], ) @@ -1319,6 +1167,7 @@ cc_library( "//tensorflow/core/kernels:checkpoint_ops", "//tensorflow/core/kernels:clustering_ops", "//tensorflow/core/kernels:collective_ops", + "//tensorflow/core/kernels:constant_op", "//tensorflow/core/kernels:control_flow_ops", "//tensorflow/core/kernels:ctc_ops", "//tensorflow/core/kernels:cudnn_rnn_kernels", @@ -1406,13 +1255,22 @@ cc_library( ]), ) +cc_library( + name = "dynamic_kernels_impl", + visibility = [":__subpackages__"], + deps = [], +) + cc_library( name = "all_kernels", visibility = ["//visibility:public"], deps = if_dynamic_kernels( [], - otherwise = [":all_kernels_impl"], + otherwise = [":dynamic_kernels_impl"], ) + [ + # TODO(gunan): Split this up and load the resulting kernels dynamically once + # the dependency issues are resolved. + ":all_kernels_impl", # TODO(gunan): Work on the API between these and rest of TF and make # these also dynamically loading. "//tensorflow/core/kernels:dataset_ops", # Depends on grappler @@ -1444,6 +1302,20 @@ cc_library( ], ) +cc_library( + name = "testlib_kernels_impl", + deps = [ + "//tensorflow/core/kernels:aggregate_ops", + "//tensorflow/core/kernels:bcast_ops", + "//tensorflow/core/kernels:cast_op", + "//tensorflow/core/kernels:constant_op", + "//tensorflow/core/kernels:identity_op", + "//tensorflow/core/kernels:random_ops", + "//tensorflow/core/kernels:reduction_ops", + "//tensorflow/core/kernels:reshape_op", + ], +) + # Test support library needed for higher-level (TensorFlow-specific) tests cc_library( name = "testlib", @@ -1452,22 +1324,22 @@ cc_library( "common_runtime/function_testlib.cc", "common_runtime/kernel_benchmark_testlib.cc", "graph/testlib.cc", - "//tensorflow/core/framework:fake_input.cc", - "//tensorflow/core/framework:function_testlib.cc", ], hdrs = [ "common_runtime/function_testlib.h", "common_runtime/kernel_benchmark_testlib.h", "common_runtime/test_collective_executor_mgr.h", - "//tensorflow/core/framework:fake_input.h", - "//tensorflow/core/framework:function_testlib.h", - "//tensorflow/core/framework:shape_inference_testutil.h", - "//tensorflow/core/framework:tensor_testutil.h", "graph/benchmark_testlib.h", "graph/testlib.h", # TODO(josh11b): Drop this once users are depending on # kernels:ops_testutil instead. "//tensorflow/core/kernels:ops_testutil.h", + # TODO(gonnet): Stop exporting these headers once users depend on + # the tensorflow/core/framework:* targets directly. + "//tensorflow/core/framework:shape_inference_testutil.h", + "//tensorflow/core/framework:tensor_testutil.h", + "//tensorflow/core/framework:fake_input.h", + "//tensorflow/core/framework:function_testlib.h", ], copts = tf_copts(), visibility = ["//visibility:public"], @@ -1481,26 +1353,18 @@ cc_library( ":lib_internal", ":ops", ":protos_all_cc", - ":shape_inference_testutil", - ":tensor_testutil", ":test", ":testlib_ops", + # TODO(gunan): resolve dependency issues and load these kernels dynamically. + ":testlib_kernels_impl", "//tensorflow/cc:scope", + "//tensorflow/core/framework:fake_input", + "//tensorflow/core/framework:function_testlib", + "//tensorflow/core/framework:shape_inference_testutil", + "//tensorflow/core/framework:tensor_testutil", "//tensorflow/core/kernels:ops_testutil", "//tensorflow/core/kernels:ops_util", - ] + if_dynamic_kernels( - [], - otherwise = [ - "//tensorflow/core/kernels:aggregate_ops", - "//tensorflow/core/kernels:bcast_ops", - "//tensorflow/core/kernels:cast_op", - "//tensorflow/core/kernels:constant_op", - "//tensorflow/core/kernels:identity_op", - "//tensorflow/core/kernels:random_ops", - "//tensorflow/core/kernels:reduction_ops", - "//tensorflow/core/kernels:reshape_op", - ], - ), + ], ) cc_library( @@ -1549,6 +1413,7 @@ filegroup( filegroup( name = "mobile_srcs_no_runtime", srcs = [ + "//tensorflow/compiler/jit:mobile_srcs_no_runtime", "//tensorflow/core/framework:attr_value_proto_text_srcs", "//tensorflow/core/framework:mobile_srcs_no_runtime", "//tensorflow/core/lib/bfloat16:bfloat16.cc", @@ -1556,10 +1421,10 @@ filegroup( "//tensorflow/core/lib/core:legacy_lib_core_all_headers", "//tensorflow/core/lib/core:legacy_lib_core_all_srcs", "//tensorflow/core/lib/gtl:legacy_lib_gtl_all_headers", - "//tensorflow/core/lib/histogram:legacy_lib_histogram_all_headers", - "//tensorflow/core/lib/histogram:legacy_lib_histogram_all_srcs", "//tensorflow/core/lib/hash:legacy_lib_hash_all_headers", "//tensorflow/core/lib/hash:legacy_lib_hash_all_srcs", + "//tensorflow/core/lib/histogram:legacy_lib_histogram_all_headers", + "//tensorflow/core/lib/histogram:legacy_lib_histogram_all_srcs", "//tensorflow/core/lib/io:legacy_lib_io_all_headers", "//tensorflow/core/lib/io:legacy_lib_io_all_srcs", "//tensorflow/core/lib/math:math_util.h", @@ -1573,14 +1438,13 @@ filegroup( "//tensorflow/core/profiler:mobile_srcs", "//tensorflow/core/util/ctc:android_srcs", "//tensorflow/core/util/sparse:mobile_srcs_no_runtime_group", + "//tensorflow/core/util:mobile_srcs_no_runtime", ] + glob( [ "client/**/*.cc", "lib/**/*.h", "lib/**/*.cc", "public/**/*.h", - "util/**/*.h", - "util/**/*.cc", ], exclude = [ "**/*test.*", @@ -1591,15 +1455,7 @@ filegroup( "lib/jpeg/**/*", "lib/png/**/*", "lib/gif/**/*", - "util/debug_events_writer.*", - "util/events_writer.*", - "util/stats_calculator.*", - "util/reporter.*", "user_ops/**/*.cu.cc", - "util/ctc/*.h", - "util/ctc/*.cc", - "util/tensor_bundle/*.h", - "util/tensor_bundle/*.cc", "common_runtime/gpu/**/*", "common_runtime/eager/*", "common_runtime/gpu_device_factory.*", @@ -1614,13 +1470,13 @@ filegroup( filegroup( name = "mobile_srcs_only_runtime", srcs = [ - "//tensorflow/core/framework:mobile_srcs_only_runtime", + "//tensorflow/c/eager:srcs", + "//tensorflow/c:srcs", "//tensorflow/core/common_runtime/eager:srcs", + "//tensorflow/core/framework:mobile_srcs_only_runtime", "//tensorflow/core/kernels:android_srcs", "//tensorflow/core/util/ctc:android_srcs", "//tensorflow/core/util/tensor_bundle:android_srcs", - "//tensorflow/c:srcs", - "//tensorflow/c/eager:srcs", ] + glob( [ "common_runtime/**/*.h", @@ -1679,7 +1535,7 @@ cc_library( deps = [ ":mobile_additional_lib_deps", ":protos_all_cc_impl", - ":stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//third_party/eigen3", "@com_google_protobuf//:protobuf", "@double_conversion//:double-conversion", @@ -1704,7 +1560,7 @@ cc_library( deps = [ ":mobile_additional_lib_deps", ":protos_all_cc_impl", - ":stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//third_party/eigen3", "@com_google_protobuf//:protobuf", "@double_conversion//:double-conversion", @@ -1737,7 +1593,7 @@ cc_library( deps = [ ":emscripten_proto_lib_no_rtti_lite_runtime", ":mobile_additional_lib_deps", - ":stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//third_party/eigen3", "@double_conversion//:double-conversion", "@farmhash_archive//:farmhash", @@ -1780,7 +1636,7 @@ cc_library( deps = [ ":mobile_additional_lib_deps", ":protos_all_cc_impl", - ":stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//third_party/eigen3", "@com_google_protobuf//:protobuf", "@double_conversion//:double-conversion", @@ -1852,15 +1708,10 @@ filegroup( # TODO(andrewharp/nhua): # make more test-related sources portable e.g. "//tensorflow/core/platform:test.cc", srcs = [ - ":util/reporter.cc", - ":util/reporter.h", - "//tensorflow/core/framework:fake_input.cc", - "//tensorflow/core/framework:fake_input.h", - "//tensorflow/core/framework:shape_inference_testutil.cc", - "//tensorflow/core/framework:shape_inference_testutil.h", - "//tensorflow/core/framework:tensor_testutil.cc", - "//tensorflow/core/framework:tensor_testutil.h", + "//tensorflow/core/framework:android_test_hdrs", + "//tensorflow/core/framework:android_test_srcs", "//tensorflow/core/platform:test.h", + "//tensorflow/core/util:android_test_srcs", ], visibility = ["//visibility:public"], ) @@ -1869,13 +1720,10 @@ filegroup( filegroup( name = "android_test_srcs_no_core", srcs = [ - ":util/reporter.cc", - ":util/reporter.h", - "//tensorflow/core/framework:shape_inference_testutil.cc", - "//tensorflow/core/framework:shape_inference_testutil.h", - "//tensorflow/core/framework:tensor_testutil.cc", - "//tensorflow/core/framework:tensor_testutil.h", + "//tensorflow/core/framework:android_test_hdrs", + "//tensorflow/core/framework:android_test_srcs_no_core", "//tensorflow/core/platform:test.h", + "//tensorflow/core/util:android_test_srcs", ], visibility = ["//visibility:public"], ) @@ -1886,10 +1734,8 @@ cc_library( testonly = 1, srcs = if_android([":android_test_srcs"]), hdrs = [ - "util/reporter.h", - "//tensorflow/core/framework:fake_input.h", - "//tensorflow/core/framework:shape_inference_testutil.h", - "//tensorflow/core/framework:tensor_testutil.h", + "//tensorflow/core/framework:android_test_hdrs", + "//tensorflow/core/util:android_test_hdrs", ], copts = tf_copts(android_optimization_level_override = None), tags = [ @@ -2128,7 +1974,7 @@ LIB_INTERNAL_PUBLIC_HEADERS = [ "//tensorflow/core/platform:tracing.h", "//tensorflow/core/platform:unbounded_work_queue.h", "//tensorflow/core/platform:legacy_platform_lib_hdrs", - "util/env_var.h", + "//tensorflow/core/util:lib_internal_public_hdrs", ] cc_library( @@ -2159,7 +2005,6 @@ cc_library( srcs = LIB_INTERNAL_PRIVATE_HEADERS + glob( [ "lib/**/*.cc", - "util/env_var.cc", ], exclude = [ "**/*test*", @@ -2170,13 +2015,13 @@ cc_library( ], ) + [ "//tensorflow/core/platform:legacy_lib_internal_srcs", + "//tensorflow/core/util:lib_internal_impl_srcs", ], hdrs = LIB_INTERNAL_PUBLIC_HEADERS, copts = tf_copts(), deps = tf_additional_lib_deps() + [ ":core_stringpiece", ":lib_proto_parsing", - ":util_reporter", # TODO(gunan): REMOVE as soon as cc_shared_library is supported. "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", "//third_party/eigen3", @@ -2211,7 +2056,7 @@ cc_library( "//tensorflow/core/lib/gtl:priority_queue_util", "//tensorflow/core/lib/gtl:top_n", "//tensorflow/core/lib/hash:crc32c", - "//tensorflow/core/lib/hash:hash", + "//tensorflow/core/lib/hash", "//tensorflow/core/lib/histogram", "//tensorflow/core/lib/io:block", "//tensorflow/core/lib/io:buffered_inputstream", @@ -2244,7 +2089,7 @@ cc_library( "//tensorflow/core/lib/random:exact_uniform_int", "//tensorflow/core/lib/random:philox", "//tensorflow/core/lib/random:philox_random", - "//tensorflow/core/lib/random:random", + "//tensorflow/core/lib/random", "//tensorflow/core/lib/random:weighted_picker", "//tensorflow/core/lib/strings:base64", "//tensorflow/core/lib/strings:numbers", @@ -2303,6 +2148,7 @@ cc_library( "//tensorflow/core/platform:tstring", "//tensorflow/core/platform:unbounded_work_queue", "//tensorflow/core/platform/default/build_config:platformlib", + "//tensorflow/core/util:reporter", # TODO(gunan): REMOVE as soon as cc_shared_library is supported. "@snappy", "@zlib_archive//:zlib", "@double_conversion//:double-conversion", @@ -2379,16 +2225,17 @@ cc_library( name = "tflite_portable_logging", hdrs = [ "//tensorflow/core/lib/bfloat16:bfloat16.h", - "//tensorflow/core/platform:default/integral_types.h", - "//tensorflow/core/platform:default/logging.h", "//tensorflow/core/platform:logging.h", "//tensorflow/core/platform:macros.h", "//tensorflow/core/platform:platform.h", "//tensorflow/core/platform:tstring.h", "//tensorflow/core/platform:types.h", + "//tensorflow/core/platform/default:integral_types.h", + "//tensorflow/core/platform/default:logging.h", ], copts = tf_copts(), linkopts = ["-ldl"], + visibility = ["//visibility:public"], deps = [ ":platform_base", "//tensorflow/core/platform/default/build_config:logging", @@ -2407,8 +2254,6 @@ cc_library( "lib/jpeg/jpeg_mem.h", "//tensorflow/core/lib/bfloat16:bfloat16.h", "//tensorflow/core/lib/core:legacy_lib_core_stringpiece_header", - "//tensorflow/core/platform:default/integral_types.h", - "//tensorflow/core/platform:default/logging.h", "//tensorflow/core/platform:dynamic_annotations.h", "//tensorflow/core/platform:logging.h", "//tensorflow/core/platform:macros.h", @@ -2417,6 +2262,8 @@ cc_library( "//tensorflow/core/platform:stringpiece.h", "//tensorflow/core/platform:tstring.h", "//tensorflow/core/platform:types.h", + "//tensorflow/core/platform/default:integral_types.h", + "//tensorflow/core/platform/default:logging.h", ], copts = tf_copts(), linkopts = ["-ldl"], @@ -2442,8 +2289,6 @@ cc_library( "//tensorflow/core/lib/bfloat16:bfloat16.h", "//tensorflow/core/lib/core:legacy_lib_core_stringpiece_header", "//tensorflow/core/lib/gtl:legacy_android_gif_internal_headers", - "//tensorflow/core/platform:default/integral_types.h", - "//tensorflow/core/platform:default/logging.h", "//tensorflow/core/platform:dynamic_annotations.h", "//tensorflow/core/platform:logging.h", "//tensorflow/core/platform:macros.h", @@ -2451,6 +2296,8 @@ cc_library( "//tensorflow/core/platform:platform.h", "//tensorflow/core/platform:tstring.h", "//tensorflow/core/platform:types.h", + "//tensorflow/core/platform/default:integral_types.h", + "//tensorflow/core/platform/default:logging.h", ], copts = tf_copts(), linkopts = ["-ldl"], @@ -2475,15 +2322,6 @@ tf_proto_library( make_default_target_header_only = True, ) -tf_proto_library( - name = "test_log_proto_impl", - srcs = ["util/test_log.proto"], - cc_api_version = 2, - make_default_target_header_only = True, - # Not to be used outside this file. - visibility = ["//visibility:private"], -) - tf_proto_library( name = "core_protos", srcs = COMMON_PROTO_SRCS + [ @@ -2512,10 +2350,11 @@ tf_proto_library( make_default_target_header_only = True, protodeps = [ ":error_codes_proto_impl", - ":test_log_proto_impl", "//tensorflow/core/framework:protos_all", "//tensorflow/core/lib/core:error_codes_proto", "//tensorflow/core/profiler/protobuf:xplane_proto", + "//tensorflow/core/util:protos_all", + "//tensorflow/core/util:test_log_proto_impl", ], visibility = ["//visibility:private"], ) @@ -2525,16 +2364,9 @@ alias( actual = "//tensorflow/core/lib/core:error_codes_proto_cc", ) -tf_version_info_genrule( - name = "version_info_gen", - out = "util/version_info.cc", -) - -cc_library( +alias( name = "version_lib", - srcs = ["util/version_info.cc"], - hdrs = ["public/version.h"], - copts = tf_copts(), + actual = "//tensorflow/core/util:version_info", ) FRAMEWORK_INTERNAL_PRIVATE_HEADERS = [ @@ -2545,28 +2377,20 @@ FRAMEWORK_INTERNAL_PRIVATE_HEADERS = [ "graph/tensor_id.h", "//tensorflow/core/util/sparse:framework_internal_private_headers_group", "//tensorflow/core/framework:framework_internal_private_hdrs", + "//tensorflow/core/util:framework_internal_private_hdrs", ] + glob( [ "example/**/*.h", - "util/**/*.h", ], exclude = [ "**/*test*", "**/*main.cc", "example/example_parser_configuration.*", - "util/reporter.h", - "util/reporter.cc", - "util/port.h", - "util/memmapped_file_system.*", - "util/memmapped_file_system_writer.*", - "util/session_message.*", - "util/version_info.cc", ], ) + select({ "//tensorflow:windows": [], "//conditions:default": [ - "util/memmapped_file_system.h", - "util/memmapped_file_system_writer.h", + "//tensorflow/core/util:memmapped_file_system_hdrs", ], }) @@ -2582,12 +2406,7 @@ FRAMEWORK_INTERNAL_PUBLIC_HEADERS = [ "//tensorflow/core/framework:tracking_allocator.h", # only needed for tests "//tensorflow/core/framework:unique_tensor_references.h", "//tensorflow/core/framework:variant.h", - "util/command_line_flags.h", - "util/equal_graph_def.h", - "util/presized_cuckoo_map.h", - "util/tensor_slice_set.h", - "util/tensor_slice_util.h", - "util/xla_config_registry.h", + "//tensorflow/core/util:framework_internal_public_hdrs", ] tf_cuda_library( @@ -2640,10 +2459,10 @@ tf_cuda_library( srcs = FRAMEWORK_INTERNAL_PRIVATE_HEADERS + [ "//tensorflow/core/util/sparse:framework_internal_impl_group", "//tensorflow/core/framework:framework_internal_impl_srcs", + "//tensorflow/core/util:framework_internal_impl_srcs", ] + glob( [ "example/**/*.cc", - "util/**/*.cc", "graph/edgeset.cc", "graph/graph.cc", "graph/graph_def_builder.cc", @@ -2657,19 +2476,11 @@ tf_cuda_library( "**/*main.cc", "example/example_parser_configuration.*", "example/feature_util.cc", - "util/reporter.cc", - "util/memmapped_file_system.*", - "util/memmapped_file_system_writer.*", - "util/stats_calculator.*", - "util/version_info.cc", - "util/env_var.cc", - "util/port.cc", ], ) + select({ "//tensorflow:windows": [], "//conditions:default": [ - "util/memmapped_file_system.cc", - "util/memmapped_file_system_writer.cc", + "//tensorflow/core/util:memmapped_file_system_srcs", ], }), hdrs = FRAMEWORK_INTERNAL_PUBLIC_HEADERS, @@ -2683,15 +2494,11 @@ tf_cuda_library( ], }), deps = [ - ":allocator_registry_impl", - ":allocator", ":feature_util", ":lib", ":lib_internal", ":protos_all_cc", - ":stats_calculator_portable", ":version_lib", - ":util_port", "@com_google_absl//absl/base", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", @@ -2699,14 +2506,18 @@ tf_cuda_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/time", "//third_party/eigen3", + "//tensorflow/core/framework:allocator", + "//tensorflow/core/framework:allocator_registry_impl", "//tensorflow/core/framework:attr_value_proto_text", "//tensorflow/core/framework:bfloat16", "//tensorflow/core/framework:numeric_types", "//tensorflow/core/kernels:bounds_check", "//tensorflow/core/platform/default/build_config:platformlib", - "//tensorflow/core/profiler/lib:traceme", - "//tensorflow/core/profiler/internal:traceme_recorder_impl", "//tensorflow/core/profiler/internal:annotation_stack_impl", + "//tensorflow/core/profiler/internal:traceme_recorder_impl", + "//tensorflow/core/profiler/lib:traceme", + "//tensorflow/core/util:port", + "//tensorflow/core/util:stats_calculator_portable", ] + if_static( extra_deps = ["@com_google_protobuf//:protobuf"], otherwise = ["@com_google_protobuf//:protobuf_headers"], @@ -2725,7 +2536,7 @@ cc_header_only_library( visibility = ["//visibility:public"], deps = [ ":framework", - ":reader_base", + "//tensorflow/core/framework:reader_base", ], ) @@ -2770,13 +2581,10 @@ cc_library( ], ) -tf_cuda_library( +alias( name = "cuda_device_functions", - hdrs = [ - "util/gpu_device_functions.h", - ], + actual = "//tensorflow/core/util:gpu_device_functions", visibility = ["//visibility:public"], - deps = [":framework_lite"], ) # TODO(josh11b): Is this needed, or can we just use ":protos_all_cc"? @@ -3117,11 +2925,11 @@ cc_library( features = ["parse_headers"], visibility = ["//visibility:public"], deps = [ - ":allocator", ":lib", ":lib_internal", ":protos_all_cc", ":shared_counter", + "//tensorflow/core/framework:allocator", "@com_google_absl//absl/container:flat_hash_set", ], ) @@ -3155,7 +2963,7 @@ tf_cuda_library( srcs = ["common_runtime/direct_session.cc"], hdrs = [ "common_runtime/direct_session.h", - "util/env_var.h", + "//tensorflow/core/util:lib_internal_public_hdrs", ], copts = tf_copts(), deps = [ @@ -3335,10 +3143,10 @@ tf_cuda_library( features = ["parse_headers"], visibility = ["//visibility:public"], deps = [ - ":allocator", ":lib", ":lib_internal", ":stream_executor", + "//tensorflow/core/framework:allocator", ], ) @@ -3424,31 +3232,16 @@ cc_library( ], ) -cc_library( +# TODO(gonnet): Remove this alias once all users have been moved to the actual target. +alias( name = "tensor_testutil", - testonly = 1, - srcs = ["//tensorflow/core/framework:tensor_testutil.cc"], - hdrs = ["//tensorflow/core/framework:tensor_testutil.h"], - copts = tf_copts(), - deps = [ - ":framework", - ":lib", - ":test", - ], + actual = "//tensorflow/core/framework:tensor_testutil", ) -cc_library( +# TODO(gonnet): Remove this alias once all users have been moved to the actual target. +alias( name = "shape_inference_testutil", - testonly = 1, - srcs = ["//tensorflow/core/framework:shape_inference_testutil.cc"], - hdrs = ["//tensorflow/core/framework:shape_inference_testutil.h"], - copts = tf_copts(), - deps = [ - ":framework", - ":lib", - ":lib_internal", - ":protos_all_cc", - ], + actual = "//tensorflow/core/framework:shape_inference_testutil", ) # Main program for tests @@ -3677,30 +3470,6 @@ tf_cc_test( ], ) -tf_cc_test( - name = "util_overflow_test", - size = "small", - srcs = ["util/overflow_test.cc"], - deps = [ - ":framework_lite", - ":overflow", - ":test", - ":test_main", - ], -) - -tf_cc_test( - name = "exec_on_stall_test", - size = "small", - srcs = ["util/exec_on_stall_test.cc"], - deps = [ - ":exec_on_stall", - ":framework_lite", - ":test", - ":test_main", - ], -) - tf_cc_test( name = "lib_jpeg_jpeg_mem_unittest", srcs = ["lib/jpeg/jpeg_mem_unittest.cc"], @@ -3758,10 +3527,10 @@ tf_cc_test( size = "small", srcs = ["//tensorflow/core/framework:op_gen_lib_test.cc"], deps = [ - ":op_gen_lib", ":protos_all_cc", ":test", ":test_main", + "//tensorflow/core/framework:op_gen_lib", ], ) @@ -3780,15 +3549,24 @@ tf_cc_test( ":lib_internal", ":ops", ":protos_all_cc", - ":protos_test_cc", ":test", ":test_main", ":testlib", + "//tensorflow/core/util:protos_test_cc", + ], +) + +test_suite( + name = "higher_level_tests", + tests = [ + ":core_higher_level_tests", + "//tensorflow/core/framework:higher_level_tests", + "//tensorflow/core/util:higher_level_tests", ], ) tf_cc_tests( - name = "higher_level_tests", + name = "core_higher_level_tests", size = "small", srcs = [ "common_runtime/buf_rendezvous_test.cc", @@ -3816,68 +3594,9 @@ tf_cc_tests( "graph/subgraph_test.cc", "graph/tensor_id_test.cc", "graph/validate_test.cc", - "util/bcast_test.cc", - "util/command_line_flags_test.cc", - "util/debug_events_writer_test.cc", - "util/device_name_utils_test.cc", - "util/dump_graph_test.cc", - "util/equal_graph_def_test.cc", - "util/events_writer_test.cc", - "util/example_proto_fast_parsing_test.cc", - "util/example_proto_helper_test.cc", - "util/matmul_bcast_test.cc", - "util/memmapped_file_system_test.cc", - "util/presized_cuckoo_map_test.cc", - "util/reffed_status_callback_test.cc", - "util/reporter_test.cc", - "util/saved_tensor_slice_util_test.cc", - "util/semver_test.cc", - "util/stat_summarizer_test.cc", - "util/tensor_format_test.cc", - "util/tensor_slice_reader_test.cc", - "util/tensor_slice_set_test.cc", - "util/tensor_slice_util_test.cc", - "util/tensor_slice_writer_test.cc", - "util/work_sharder_test.cc", - "//tensorflow/core/framework:allocator_test.cc", - "//tensorflow/core/framework:attr_value_util_test.cc", - "//tensorflow/core/framework:bfloat16_test.cc", - "//tensorflow/core/framework:cancellation_test.cc", - "//tensorflow/core/framework:common_shape_fns_test.cc", - "//tensorflow/core/framework:dataset_test.cc", - "//tensorflow/core/framework:device_base_test.cc", - "//tensorflow/core/framework:function_test.cc", - "//tensorflow/core/framework:graph_def_util_test.cc", - "//tensorflow/core/framework:graph_to_functiondef_test.cc", - "//tensorflow/core/framework:kernel_def_builder_test.cc", - "//tensorflow/core/framework:kernel_def_util_test.cc", - "//tensorflow/core/framework:memory_types_test.cc", - "//tensorflow/core/framework:model_test.cc", - "//tensorflow/core/framework:node_def_builder_test.cc", - "//tensorflow/core/framework:node_def_util_test.cc", - "//tensorflow/core/framework:op_compatibility_test.cc", - "//tensorflow/core/framework:op_def_builder_test.cc", - "//tensorflow/core/framework:op_def_util_test.cc", - "//tensorflow/core/framework:op_kernel_test.cc", - "//tensorflow/core/framework:op_registration_test.cc", - "//tensorflow/core/framework:partial_tensor_shape_test.cc", - "//tensorflow/core/framework:rendezvous_test.cc", - "//tensorflow/core/framework:resource_mgr_test.cc", - "//tensorflow/core/framework:resource_op_kernel_test.cc", - "//tensorflow/core/framework:shape_inference_test.cc", - "//tensorflow/core/framework:shape_inference_testutil_test.cc", - "//tensorflow/core/framework:tensor_shape_test.cc", - "//tensorflow/core/framework:tensor_slice_test.cc", - "//tensorflow/core/framework:tensor_test.cc", - "//tensorflow/core/framework:tensor_testutil_test.cc", - "//tensorflow/core/framework:tensor_util_test.cc", - "//tensorflow/core/framework:tracking_allocator_test.cc", - "//tensorflow/core/framework:types_test.cc", - "//tensorflow/core/framework:unique_tensor_references_test.cc", - "//tensorflow/core/framework:variant_op_registry_test.cc", - "//tensorflow/core/framework:variant_test.cc", "//tensorflow/core/util/sparse:higher_level_tests_group", ], + create_named_test_suite = True, linkopts = select({ "//tensorflow:macos": ["-headerpad_max_install_names"], "//conditions:default": [], @@ -3894,7 +3613,6 @@ tf_cc_tests( ":lib_internal", ":ops", ":protos_all_cc", - ":protos_test_cc", ":test", ":test_main", ":testlib", @@ -3906,6 +3624,8 @@ tf_cc_tests( "//tensorflow/cc:sendrecv_ops", "//tensorflow/cc:while_loop", "//tensorflow/core/kernels:ops_util", + "//tensorflow/core/platform:regexp", + "//tensorflow/core/util:protos_test_cc", "//third_party/eigen3", "@com_google_absl//absl/base", "@com_google_absl//absl/memory", @@ -3937,7 +3657,6 @@ tf_cc_tests( ":lib_internal", ":ops", ":protos_all_cc", - ":protos_test_cc", ":test", ":test_main", ":testlib", @@ -3946,6 +3665,7 @@ tf_cc_tests( "//tensorflow/cc:scope", "//tensorflow/cc:sendrecv_ops", "//tensorflow/core/kernels:ops_util", + "//tensorflow/core/util:protos_test_cc", "//third_party/eigen3", ], ) @@ -3966,7 +3686,7 @@ tf_cc_test( ], ) -tf_cc_tests( +tf_cc_test( name = "collective_order_test", size = "small", srcs = [ @@ -4008,10 +3728,10 @@ tf_cc_tests_gpu( ":lib_internal", ":ops", ":protos_all_cc", - ":protos_test_cc", ":test", ":test_main", ":testlib", + "//tensorflow/core/util:protos_test_cc", "@com_google_absl//absl/memory", ], ) @@ -4037,10 +3757,10 @@ tf_cc_tests_gpu( ":lib_internal", ":ops", ":protos_all_cc", - ":protos_test_cc", ":test", ":test_main", ":testlib", + "//tensorflow/core/util:protos_test_cc", "@com_google_absl//absl/memory", ], ) @@ -4066,10 +3786,10 @@ tf_cc_tests_gpu( ":lib_internal", ":ops", ":protos_all_cc", - ":protos_test_cc", ":test", ":test_main", ":testlib", + "//tensorflow/core/util:protos_test_cc", "@com_google_absl//absl/memory", ], ) @@ -4101,7 +3821,7 @@ tf_cc_test_mkl( srcs = [ "graph/mkl_layout_pass_test.cc", "graph/mkl_tfconversion_pass_test.cc", - "util/mkl_util_test.cc", + "//tensorflow/core/util:mkl_util_test_srcs", ], linkstatic = 1, deps = [ @@ -4254,18 +3974,6 @@ tf_cc_test_gpu( ], ) -tf_cuda_only_cc_test( - name = "util_gpu_kernel_helper_test", - srcs = [ - "util/gpu_kernel_helper_test.cu.cc", - ], - deps = [ - ":test", - ":test_main", - "//third_party/eigen3", - ] + mkl_deps(), -) - tf_cc_test_gpu( name = "memory_types_test", size = "small", @@ -4461,10 +4169,10 @@ tf_cc_test( ":lib", ":lib_internal", ":protos_all_cc", - ":tensor_testutil", ":test", ":test_main", ":testlib", + "//tensorflow/core/framework:tensor_testutil", "//tensorflow/core/kernels:cwise_op", "//tensorflow/core/kernels:matmul_op", "//third_party/eigen3", @@ -4632,11 +4340,11 @@ tf_cc_test( ":test", ":test_main", ":testlib", + "//third_party/eigen3", "//tensorflow/c/kernels:bitcast_op_lib", "//tensorflow/cc:cc_ops", "//tensorflow/cc:scope", "//tensorflow/core/kernels:cwise_op", - "//third_party/eigen3", ] + if_mkl([":mkl_array_ops_op_lib"]), ) @@ -4934,7 +4642,7 @@ tf_cc_test( ], ) -tf_cc_tests( +tf_cc_test( name = "ops_tests", size = "small", srcs = [ @@ -5004,7 +4712,7 @@ tf_cc_test( ], ) -tf_cc_tests( +tf_cc_test( name = "common_runtime_input_colocation_exemption_registry_test", size = "small", srcs = ["common_runtime/input_colocation_exemption_registry_test.cc"], @@ -5017,7 +4725,7 @@ tf_cc_tests( ], ) -tf_cc_tests( +tf_cc_test( name = "common_runtime_lower_function_call_test", size = "small", srcs = ["common_runtime/lower_function_call_op_test.cc"], @@ -5041,7 +4749,7 @@ tf_cc_tests( ], ) -tf_cc_tests( +tf_cc_test( name = "common_runtime_lower_if_op_test", size = "small", srcs = ["common_runtime/lower_if_op_test.cc"], @@ -5065,7 +4773,7 @@ tf_cc_tests( ], ) -tf_cc_tests( +tf_cc_test( name = "common_runtime_lower_case_op_test", size = "small", srcs = ["common_runtime/lower_case_op_test.cc"], @@ -5089,7 +4797,7 @@ tf_cc_tests( ], ) -tf_cc_tests( +tf_cc_test( name = "common_runtime_lower_while_op_test", size = "small", srcs = ["common_runtime/lower_while_op_test.cc"], @@ -5113,7 +4821,7 @@ tf_cc_tests( ], ) -tf_cc_tests( +tf_cc_test( name = "common_runtime_lower_functional_ops_test", size = "small", srcs = ["common_runtime/lower_functional_ops_test.cc"], @@ -5245,6 +4953,7 @@ tf_portable_proto_library( proto_deps = [ ":core_protos", "//tensorflow/core/framework:protos_all", + "//tensorflow/core/util:protos_all", ], visibility = ["//visibility:public"], deps = ["@com_google_protobuf//:protobuf"], diff --git a/tensorflow/core/api_def/base_api/api_def_ApplyMomentum.pbtxt b/tensorflow/core/api_def/base_api/api_def_ApplyMomentum.pbtxt index 55326fd35cf..cde9a3c51e7 100644 --- a/tensorflow/core/api_def/base_api/api_def_ApplyMomentum.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ApplyMomentum.pbtxt @@ -52,9 +52,9 @@ var - lr * momentum * accum, so in the end, the var you get is actually var - lr * momentum * accum. END } - summary: "Update \'*var\' according to the momentum scheme. Set use_nesterov = True if you" + summary: "Update \'*var\' according to the momentum scheme." description: <bik`), the contracted axis label is `j`. - (e) Expand Diagonal: If the output subcripts contain repeated (explicit) axis + (e) Expand Diagonal: If the output subscripts contain repeated (explicit) axis labels, the opposite operation of (a) is applied. For example, in the equation `i->iii`, and input shape `[3]`, the output of shape `[3, 3, 3]` are all zeros, except for the (generalized) diagonal which is populated @@ -70,7 +70,7 @@ Operations are applied to the input(s) according to the following rules: Note: This operation is not supported by `np.einsum` or `tf.einsum`; it is provided to enable computing the symbolic gradient of `tf.einsum`. -The output subcripts must contain only labels appearing in at least one of the +The output subscripts must contain only labels appearing in at least one of the input subscripts. Furthermore, all dimensions mapping to the same axis label must be equal. @@ -82,7 +82,7 @@ according to standard NumPy broadcasting The broadcasted dimensions are placed in the corresponding location of the ellipsis in the output subscript. If the broadcasted dimensions are non-empty -and the output subcripts do not contain ellipsis, then an InvalidArgument error +and the output subscripts do not contain ellipsis, then an InvalidArgument error is raised. @compatibility(numpy) diff --git a/tensorflow/core/api_def/base_api/api_def_LeftShift.pbtxt b/tensorflow/core/api_def/base_api/api_def_LeftShift.pbtxt index 3855c5095a7..b7bf38535a2 100644 --- a/tensorflow/core/api_def/base_api/api_def_LeftShift.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_LeftShift.pbtxt @@ -16,9 +16,9 @@ dtype_list = [tf.int8, tf.int16, tf.int32, tf.int64] for dtype in dtype_list: lhs = tf.constant([-1, -5, -3, -14], dtype=dtype) rhs = tf.constant([5, 0, 7, 11], dtype=dtype) - + left_shift_result = bitwise_ops.left_shift(lhs, rhs) - + print(left_shift_result) # This will print: diff --git a/tensorflow/core/api_def/base_api/api_def_LowerBound.pbtxt b/tensorflow/core/api_def/base_api/api_def_LowerBound.pbtxt index 5ce825ae043..c2b0405c93d 100644 --- a/tensorflow/core/api_def/base_api/api_def_LowerBound.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_LowerBound.pbtxt @@ -28,7 +28,7 @@ Each set of rows with the same index in (sorted_inputs, values) is treated independently. The resulting row is the equivalent of calling `np.searchsorted(sorted_inputs, values, side='left')`. -The result is not a global index to the entire +The result is not a global index to the entire `Tensor`, but rather just the index in the last dimension. A 2-D example: diff --git a/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt b/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt index e667c328ae5..4fc86807200 100644 --- a/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_MatrixSolveLs.pbtxt @@ -49,7 +49,7 @@ in the batch: If `fast` is `True`, then the solution is computed by solving the normal equations using Cholesky decomposition. Specifically, if \\(m \ge n\\) then \\(X = (A^H A + \lambda I)^{-1} A^H B\\), which solves the least-squares -problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + \lambda ||Z||_F^2\\). +problem \\(X = \mathrm{argmin}_{Z \in \Re^{n \times k} } ||A Z - B||_F^2 + \lambda ||Z||_F^2\\). If \\(m \lt n\\) then `output` is computed as \\(X = A^H (A A^H + \lambda I)^{-1} B\\), which (for \\(\lambda = 0\\)) is the minimum-norm solution to the under-determined linear system, i.e. diff --git a/tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt b/tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt index a9f1e593ccb..1e1a80e7648 100644 --- a/tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_MatrixSquareRoot.pbtxt @@ -24,10 +24,10 @@ The input matrix should be invertible. If the input matrix is real, it should have no eigenvalues which are real and negative (pairs of complex conjugate eigenvalues are allowed). -The matrix square root is computed by first reducing the matrix to -quasi-triangular form with the real Schur decomposition. The square root -of the quasi-triangular matrix is then computed directly. Details of -the algorithm can be found in: Nicholas J. Higham, "Computing real +The matrix square root is computed by first reducing the matrix to +quasi-triangular form with the real Schur decomposition. The square root +of the quasi-triangular matrix is then computed directly. Details of +the algorithm can be found in: Nicholas J. Higham, "Computing real square roots of a real matrix", Linear Algebra Appl., 1987. The input is a tensor of shape `[..., M, M]` whose inner-most 2 dimensions diff --git a/tensorflow/core/api_def/base_api/api_def_ParallelInterleaveDataset.pbtxt b/tensorflow/core/api_def/base_api/api_def_ParallelInterleaveDataset.pbtxt index 939c64fe925..e30395cbfd3 100644 --- a/tensorflow/core/api_def/base_api/api_def_ParallelInterleaveDataset.pbtxt +++ b/tensorflow/core/api_def/base_api/api_def_ParallelInterleaveDataset.pbtxt @@ -11,14 +11,14 @@ END name: "other_arguments" description: <ListDevices()), + host_cpu_device_(device_mgr->ListDevices()[0]), rendezvous_(rendezvous), thread_pool_(NewThreadPoolFromSessionOptions(opts)), custom_kernel_creator_(custom_kernel_creator), @@ -102,7 +102,7 @@ EagerContext::EagerContext( // currently a no-op. eager_context_created->GetCell()->Set(true); monitoring::StartExporter(); - InitDeviceMapAndAsync(); + InitPrioritizedDeviceTypeList(); runner_ = [this](std::function closure) { this->thread_pool_->Schedule(std::move(closure)); }; @@ -140,24 +140,17 @@ void EagerContext::ResetPFLR(const DeviceMgr* device_mgr, Env* env, } } -void EagerContext::InitDeviceMapAndAsync() { - for (auto* device : devices_) { - devices_map_[device->name()] = device; - } - - if (remote_device_mgr() != nullptr) { - for (auto* device : remote_device_mgr()->ListDevices()) { - if (devices_map_.find(device->name()) == devices_map_.end()) { - devices_map_[device->name()] = device; - devices_.push_back(device); - } - } - } - +void EagerContext::InitPrioritizedDeviceTypeList() { DeviceSet ds; - for (Device* d : devices_) { + for (Device* d : local_device_mgr()->ListDevices()) { ds.AddDevice(d); } + auto remote_device_manager = remote_device_mgr(); + if (remote_device_manager != nullptr) { + for (Device* d : remote_device_manager->ListDevices()) { + ds.AddDevice(d); + } + } prioritized_device_type_list_ = ds.PrioritizedDeviceTypeList(); } @@ -391,17 +384,6 @@ std::vector EagerContext::ListRegisteredFunctions() { return result; } -// TODO(gjn): Delete in favour of FindDeviceFromName -Status EagerContext::FindDeviceByName(const string& name, - Device** result) const { - auto it = devices_map_.find(name); - if (it == devices_map_.end()) { - return errors::InvalidArgument(name, " unknown device."); - } - *result = it->second; - return Status::OK(); -} - void EagerContext::ClearRunMetadata() { run_metadata_.Clear(); } void EagerContext::StartStep() { @@ -634,7 +616,7 @@ Status EagerContext::CPUDeviceOnTask(const Device* device, TF_RETURN_IF_ERROR(DeviceNameUtils::DeviceNameToCpuDeviceName( device->name(), &cpu_device_name)); - return FindDeviceByName(cpu_device_name, cpu_device); + return FindDeviceFromName(cpu_device_name.c_str(), cpu_device); } namespace { @@ -718,11 +700,9 @@ Status EagerContext::StoreCollectiveOpsServer( collective_executor_mgr_.Reset(rpc_collective_executor_mgr); local_device_manager_.Reset(device_mgr); + host_cpu_device_ = local_device_manager_.Get()->ListDevices()[0]; - devices_ = local_device_manager_.Get()->ListDevices(); - devices_map_.clear(); - - InitDeviceMapAndAsync(); + InitPrioritizedDeviceTypeList(); ClearCaches(); default_executor_.ClearError(); { @@ -860,9 +840,7 @@ Status EagerContext::SetMasterContextState( ReadBoolFromEnvVar("TF_EAGER_REMOTE_USE_SEND_TENSOR_RPC", true); local_device_manager_.Reset(local_device_mgr); - - devices_ = local_device_manager_.Get()->ListDevices(); - devices_map_.clear(); + host_cpu_device_ = local_device_manager_.Get()->ListDevices()[0]; if (rendezvous_ != nullptr) rendezvous_->Unref(); rendezvous_ = r; @@ -893,7 +871,7 @@ Status EagerContext::SetMasterContextState( DCHECK(remote_device_manager_.Owned()); ResetClusterFLR(cluster_flr); - InitDeviceMapAndAsync(); + InitPrioritizedDeviceTypeList(); ClearCaches(); default_executor_.ClearError(); @@ -1009,7 +987,7 @@ Status EagerContext::InitializeRemoteWorker( ResetPFLR(local_device_manager_.Get(), env_, config, TF_GRAPH_DEF_VERSION, &func_lib_def_, config->graph_options().optimizer_options(), thread_pool_.get(), cluster_flr_.Get(), custom_kernel_creator_); - InitDeviceMapAndAsync(); + InitPrioritizedDeviceTypeList(); ClearCaches(); default_executor_.ClearError(); @@ -1048,9 +1026,7 @@ Status EagerContext::UpdateRemoteWorker( ResetClusterFLR(cluster_flr); remote_device_manager_.Reset(remote_device_mgr); - devices_ = worker_session_device_mgr->ListDevices(); - devices_map_.clear(); - InitDeviceMapAndAsync(); + InitPrioritizedDeviceTypeList(); ClearCaches(); default_executor_.ClearError(); diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h index 93fbd8947fe..6807e0a9d5a 100644 --- a/tensorflow/core/common_runtime/eager/context.h +++ b/tensorflow/core/common_runtime/eager/context.h @@ -141,13 +141,6 @@ class EagerContext : public core::RefCounted { // Specify a executor for this thread. void SetExecutorForThread(EagerExecutor* executor); - // TODO(apassos) make this return a constant reference - gtl::FlatMap* device_map() { - return &devices_map_; - } - - // TODO(apassos) make this return a constant reference - std::vector* devices() { return &devices_; } const std::vector& prioritized_device_type_list() { return prioritized_device_type_list_; } @@ -178,9 +171,7 @@ class EagerContext : public core::RefCounted { const FunctionDef* FindFunctionDef(const string& name); - Status FindDeviceByName(const string& name, Device** result) const; - - Device* HostCPU() const { return devices_[0]; } + Device* HostCPU() const { return host_cpu_device_; } Device* CanonicalDevice(Device* d) const { return HostCPU() == d ? nullptr : d; } @@ -386,7 +377,7 @@ class EagerContext : public core::RefCounted { Status CPUDeviceOnTask(const Device* device, Device** cpu_device) const; private: - void InitDeviceMapAndAsync(); + void InitPrioritizedDeviceTypeList(); Status MaybeRegisterFunctionRemotely(const FunctionDef& fdef); Status RegisterExistingFunctionsOnRemoteWorkers( const std::vector& function_defs, @@ -453,11 +444,8 @@ class EagerContext : public core::RefCounted { // multi-device function on remote worker. OwnedOrUnownedHelper remote_device_manager_; - // Devices owned by device_manager - std::vector devices_; + Device* host_cpu_device_; // Owned by device_manager std::vector prioritized_device_type_list_; - // All devices are not owned. - gtl::FlatMap devices_map_; Rendezvous* rendezvous_; std::function rendezvous_creator_; diff --git a/tensorflow/core/common_runtime/eager/eager_operation.cc b/tensorflow/core/common_runtime/eager/eager_operation.cc index 26515f7ec37..975be6efde0 100644 --- a/tensorflow/core/common_runtime/eager/eager_operation.cc +++ b/tensorflow/core/common_runtime/eager/eager_operation.cc @@ -16,16 +16,25 @@ limitations under the License. namespace tensorflow { -tensorflow::Status EagerOperation::SetDeviceName(const char* device) { +tensorflow::Status EagerOperation::SetDeviceName(const char* device, + const bool reset) { if (device != nullptr && strlen(device) > 0) { - if (!DeviceNameUtils::ParseFullName(device, &device_parsed_name_)) { - return errors::InvalidArgument("Malformed device specification '", device, - "' in eager op: ", DebugString()); + if (device != raw_device_name_) { + if (!DeviceNameUtils::ParseFullName(device, &device_parsed_name_)) { + return errors::InvalidArgument("Malformed device specification '", + device, + "' in eager op: ", DebugString()); + } + raw_device_name_ = device; + device_name_ = + DeviceNameUtils::HasSomeDetails(device_parsed_name_) + ? DeviceNameUtils::ParsedNameToString(device_parsed_name_) + : ""; } - device_name_ = - DeviceNameUtils::HasSomeDetails(device_parsed_name_) - ? DeviceNameUtils::ParsedNameToString(device_parsed_name_) - : ""; + } else if (reset) { + raw_device_name_.clear(); + device_name_.clear(); + device_parsed_name_.Clear(); } return Status::OK(); } diff --git a/tensorflow/core/common_runtime/eager/eager_operation.h b/tensorflow/core/common_runtime/eager/eager_operation.h index 14f71c27529..87da5bf8245 100644 --- a/tensorflow/core/common_runtime/eager/eager_operation.h +++ b/tensorflow/core/common_runtime/eager/eager_operation.h @@ -33,7 +33,9 @@ class EagerOperation { const absl::optional remote_func_params = absl::nullopt) : ctx_(nullptr) { - Reset(ctx, op, is_function, t, executor, remote_func_params); + tensorflow::Status status = + Reset(ctx, op, is_function, t, nullptr, executor, remote_func_params); + DCHECK(status.ok()); } ~EagerOperation() { @@ -53,10 +55,11 @@ class EagerOperation { inputs_.clear(); } - void Reset(tensorflow::EagerContext* ctx, const char* op, bool is_function, - const tensorflow::AttrTypeMap* t, EagerExecutor* executor, - const absl::optional - remote_func_params = absl::nullopt) { + tensorflow::Status Reset(tensorflow::EagerContext* ctx, const char* op, + bool is_function, const tensorflow::AttrTypeMap* t, + const char* raw_device_name, EagerExecutor* executor, + const absl::optional + remote_func_params = absl::nullopt) { DCHECK(ctx_ == nullptr) << "Calling Reset without first calling Release"; DCHECK(inputs_.empty()); ctx_ = ctx; @@ -67,8 +70,6 @@ class EagerOperation { } attr_types_ = t; device_ = nullptr; - device_name_ = ""; - device_parsed_name_.Clear(); use_xla_ = false; is_function_ = is_function; cancellation_manager_ = nullptr; @@ -77,6 +78,7 @@ class EagerOperation { #ifdef TENSORFLOW_MEM_DEBUG op_name_ = op; #endif + return SetDeviceName(raw_device_name, true); } bool is_function() const { return is_function_; } @@ -105,6 +107,7 @@ class EagerOperation { tensorflow::Device* Device() const { return device_; } void SetDevice(tensorflow::Device* device) { device_ = device; + raw_device_name_.clear(); device_name_ = device->name(); device_parsed_name_ = device->parsed_name(); } @@ -113,7 +116,8 @@ class EagerOperation { const DeviceNameUtils::ParsedName& GetDeviceParsedName() const { return device_parsed_name_; } - tensorflow::Status SetDeviceName(const char* device); + tensorflow::Status SetDeviceName(const char* device, + const bool reset = false); // Indicates whether the op is assigned to a device that is local to the // current host. @@ -147,6 +151,7 @@ class EagerOperation { const tensorflow::AttrTypeMap* attr_types_; tensorflow::gtl::InlinedVector inputs_; tensorflow::Device* device_; + string raw_device_name_; string device_name_; DeviceNameUtils::ParsedName device_parsed_name_; bool use_xla_ = false; diff --git a/tensorflow/core/common_runtime/eager/execute.cc b/tensorflow/core/common_runtime/eager/execute.cc index 32937bfdfc4..9584056295c 100644 --- a/tensorflow/core/common_runtime/eager/execute.cc +++ b/tensorflow/core/common_runtime/eager/execute.cc @@ -32,6 +32,7 @@ limitations under the License. #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/types/optional.h" +#include "tensorflow/compiler/jit/defs.h" #include "tensorflow/core/common_runtime/device.h" #include "tensorflow/core/common_runtime/device_set.h" #include "tensorflow/core/common_runtime/eager/context.h" @@ -74,11 +75,6 @@ namespace tensorflow { namespace { -// Copy of the definition in third_party/tensorflow/compiler/jit/defs.h -// Copied here because we don't currently compile XLA on windows. So, can't -// depend on it directly. -const char* const kXlaCompileAttr = "_XlaCompile"; - // Using absl::StrJoin with lambda does not work in tf-lite builds. std::vector DevicesToString(const std::vector devices) { std::vector v; @@ -89,30 +85,6 @@ std::vector DevicesToString(const std::vector devices) { return v; } -// Initializes the step stats if needed. -void MaybeInitializeStepStats(StepStats* step_stats, EagerContext* ctx) { - // Lazily initialize the RunMetadata with information about all devices if - // this is the first call. - while (step_stats->dev_stats_size() < ctx->devices()->size()) { - int device_idx = step_stats->dev_stats_size(); - auto* dev_stats = step_stats->add_dev_stats(); - dev_stats->set_device(ctx->devices()->at(device_idx)->name()); - } -} - -int StepStatsDeviceIndex(StepStats* step_stats, EagerContext* ctx, - Device* device) { - // Find the current device's index. - for (int i = 0; i < ctx->devices()->size(); ++i) { - if (ctx->devices()->at(i) == device || - ctx->devices()->at(i)->name() == device->name()) { - return i; - } - } - // TODO(apassos) do not fall back to host CPU if device is unknown. - return 0; -} - const string& DeviceNameOrUnspecified(Device* device) { static string* unspecified_string = new string(""); return (device == nullptr) ? *unspecified_string : device->name(); @@ -537,7 +509,6 @@ Status EagerLocalExecute(EagerOperation* op, TensorHandle** retvals, DVLOG(2) << "Creating new kernel for " << op->Name() << " on device " << DeviceNameOrUnspecified(op->Device()); bool run_function_with_flr = false; - bool compile_with_xla = false; if (op->is_function()) { bool compile_with_xla; TF_RETURN_IF_ERROR(ShouldCompileWithXLA(op, ctx, &compile_with_xla)); @@ -586,8 +557,7 @@ Status EagerLocalExecute(EagerOperation* op, TensorHandle** retvals, // that we don't support legitimate sending/receiving across function // boundary. DVLOG(2) << "Running " << ndef.op() << " using multi-device function. " - << "compile_with_xla=" << compile_with_xla - << ". Full node_def=" << ndef.DebugString(); + << "Full node_def=" << ndef.DebugString(); std::function get_op_id = nullptr; #if !defined(IS_MOBILE_PLATFORM) if (ctx->LazyCopyFunctionRemoteInputs()) { @@ -602,12 +572,10 @@ Status EagerLocalExecute(EagerOperation* op, TensorHandle** retvals, get_op_id)); } else { DVLOG(2) << "Running " << ndef.op() << " using op kernel. " - << "compile_with_xla=" << compile_with_xla << ". Full node_def=" << ndef.DebugString(); - kernel.reset(new KernelAndDeviceOp(ctx->GetRendezvous(), ctx->LogMemory(), - flr, runner, - ctx->GetCollectiveExecutorHandle(), - ctx->HostCPU(), compile_with_xla)); + kernel.reset(new KernelAndDeviceOp( + ctx->GetRendezvous(), ctx->LogMemory(), flr, runner, + ctx->GetCollectiveExecutorHandle(), ctx->HostCPU())); } TF_RETURN_IF_ERROR(kernel->Init(ndef, graph_collector)); @@ -724,7 +692,7 @@ Status EagerRemoteExecute(EagerOperation* op, TensorHandle** retvals, if (op->Device() == nullptr) { tensorflow::Device* device = nullptr; string device_name = op->GetDeviceName(); - TF_RETURN_IF_ERROR(ctx->FindDeviceByName(device_name, &device)); + TF_RETURN_IF_ERROR(ctx->FindDeviceFromName(device_name.c_str(), &device)); op->SetDevice(device); } diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.cc b/tensorflow/core/common_runtime/eager/kernel_and_device.cc index f9cddab6883..2f39e9ba2da 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.cc +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.cc @@ -98,19 +98,20 @@ Status KernelAndDeviceOp::Init(const NodeDef& ndef, "A valid FunctionLibraryRuntime must be provided when running ops " "based on OpKernel."); } - if (compile_with_xla_) { -#if defined(IS_MOBILE_PLATFORM) || defined(PLATFORM_WINDOWS) - return errors::Unimplemented( - "Compile with XLA is not available on mobile devices and windows."); -#else // !IS_MOBILE_PLATFORM && !PLATFORM_WINDOWS - std::unique_ptr kernel; - TF_RETURN_IF_ERROR(CreateXlaKernel(flr_, ndef, &kernel)); - k = kernel.release(); -#endif // !IS_MOBILE_PLATFORM && !PLATFORM_WINDOWS - } else { - TF_RETURN_IF_ERROR(flr_->CreateKernel(ndef, &k)); - } + TF_RETURN_IF_ERROR(flr_->CreateKernel(ndef, &k)); kernel_.reset(k); + + input_alloc_attrs_.resize(kernel_->num_inputs()); + for (size_t i = 0; i < input_alloc_attrs_.size(); ++i) { + input_alloc_attrs_[i].set_on_host(kernel_->input_memory_types()[i] == + tensorflow::HOST_MEMORY); + } + output_alloc_attrs_.resize(kernel_->num_outputs()); + for (size_t i = 0; i < output_alloc_attrs_.size(); ++i) { + output_alloc_attrs_[i].set_on_host(kernel_->output_memory_types()[i] == + tensorflow::HOST_MEMORY); + } + return Status::OK(); } @@ -237,17 +238,6 @@ Status KernelAndDeviceOp::Run( ScopedStepContainer* step_container, const EagerKernelArgs& inputs, std::vector* outputs, CancellationManager* cancellation_manager, const absl::optional& remote_func_params) { - gtl::InlinedVector in_attrs(kernel_->num_inputs()); - for (size_t i = 0; i < in_attrs.size(); ++i) { - in_attrs[i].set_on_host(kernel_->input_memory_types()[i] == - tensorflow::HOST_MEMORY); - } - std::vector out_attrs(kernel_->num_outputs()); - for (size_t i = 0; i < out_attrs.size(); ++i) { - out_attrs[i].set_on_host(kernel_->output_memory_types()[i] == - tensorflow::HOST_MEMORY); - } - OpKernelContext::Params params; params.is_eager = true; params.device = device_; @@ -255,29 +245,30 @@ Status KernelAndDeviceOp::Run( params.inputs = inputs.GetTensorValues(); params.op_kernel = kernel_.get(); params.resource_manager = device_->resource_manager(); - params.input_alloc_attrs = &in_attrs; - params.output_attr_array = out_attrs.data(); + params.input_alloc_attrs = &input_alloc_attrs_; + params.output_attr_array = output_alloc_attrs_.data(); params.function_library = flr_; params.slice_reader_cache = &slice_reader_cache_; params.rendezvous = rendez_; OpExecutionState* op_execution_state = nullptr; + + CancellationManager default_cancellation_manager; if (cancellation_manager) { params.cancellation_manager = cancellation_manager; - } else { + } else if (kernel_->is_deferred()) { op_execution_state = new OpExecutionState; params.cancellation_manager = &op_execution_state->cancellation_manager; - } - params.log_memory = log_memory_; - params.inc_num_deferred_ops_function = [op_execution_state]() { - if (op_execution_state != nullptr) { + params.inc_num_deferred_ops_function = [op_execution_state]() { op_execution_state->Ref(); - } - }; - params.dec_num_deferred_ops_function = [op_execution_state]() { - if (op_execution_state != nullptr) { + }; + params.dec_num_deferred_ops_function = [op_execution_state]() { op_execution_state->Unref(); - } - }; + }; + } else { + params.cancellation_manager = &default_cancellation_manager; + } + + params.log_memory = log_memory_; params.runner = get_runner(); @@ -287,15 +278,7 @@ Status KernelAndDeviceOp::Run( OpKernelContext context(¶ms); - if (kernel_->def().op() == "_Recv") { - // TODO(apassos) do not special-case _Recv. Currently the GPU device fails - // if trying to run _Recv->Compute(), specifically checking for _Recv. To go - // around this we call _Recv->ComputeAsync, to mimic graph mode behavior. - AsyncOpKernel* async = kernel_->AsAsync(); - Notification done; - device_->ComputeAsync(async, &context, [&done]() { done.Notify(); }); - done.WaitForNotification(); - } else { + { const string& op_name = kernel_->name(); // 'ScopedActivity' will trace the OpKernel scheduling time on host. profiler::TraceMe activity( diff --git a/tensorflow/core/common_runtime/eager/kernel_and_device.h b/tensorflow/core/common_runtime/eager/kernel_and_device.h index 04d97f2b80c..c0da2847fea 100644 --- a/tensorflow/core/common_runtime/eager/kernel_and_device.h +++ b/tensorflow/core/common_runtime/eager/kernel_and_device.h @@ -172,12 +172,11 @@ class KernelAndDeviceOp final : public KernelAndDevice { FunctionLibraryRuntime* flr, std::function)>* runner, std::unique_ptr collective_executor, - Device* host_cpu_device, const bool compile_with_xla = false) + Device* host_cpu_device) : KernelAndDevice(flr, runner, std::move(collective_executor), host_cpu_device), rendez_(rendez), log_memory_(log_memory), - compile_with_xla_(compile_with_xla), step_container_(0, [this](const string& name) { device_->resource_manager()->Cleanup(name).IgnoreError(); }) {} @@ -213,11 +212,11 @@ class KernelAndDeviceOp final : public KernelAndDevice { private: std::unique_ptr kernel_; + gtl::InlinedVector input_alloc_attrs_; + gtl::InlinedVector output_alloc_attrs_; Rendezvous* const rendez_; checkpoint::TensorSliceReaderCacheWrapper slice_reader_cache_; const bool log_memory_; - const bool compile_with_xla_; - ScopedStepContainer step_container_; }; diff --git a/tensorflow/core/common_runtime/eager/tensor_handle.cc b/tensorflow/core/common_runtime/eager/tensor_handle.cc index 717ec586eef..cc3e4a754a9 100644 --- a/tensorflow/core/common_runtime/eager/tensor_handle.cc +++ b/tensorflow/core/common_runtime/eager/tensor_handle.cc @@ -657,13 +657,12 @@ Device* GetResourceDevice(const ResourceHandle& handle, EagerContext* ctx) { if (ctx == nullptr) { return nullptr; } - const auto& map = *ctx->device_map(); - auto it = map.find(handle.device()); - if (it == map.end()) { + Device* device = nullptr; + if (!ctx->FindDeviceFromName(handle.device().c_str(), &device).ok()) { LOG(ERROR) << "Cannot find resource device: " << handle.device() << "."; return nullptr; } - return it->second; + return device; } string TensorHandle::DebugString() const { diff --git a/tensorflow/core/common_runtime/metrics.cc b/tensorflow/core/common_runtime/metrics.cc index cec5d8c1241..2fedffdf211 100644 --- a/tensorflow/core/common_runtime/metrics.cc +++ b/tensorflow/core/common_runtime/metrics.cc @@ -61,6 +61,10 @@ auto* tf_data_bytes_read_counter = monitoring::Counter<1>::New( "/tensorflow/data/bytes_read", "The number of bytes read by tf.data Dataset sources.", "name"); +auto* tf_data_bytes_fetched_counter = monitoring::Counter<0>::New( + "/tensorflow/data/bytes_fetched", + "The number of bytes fetched from tf.data Dataset iterator."); + auto* tf_data_elements_counter = monitoring::Counter<1>::New( "/tensorflow/data/elements", "tf.data elements", "name"); @@ -122,6 +126,10 @@ void RecordTFDataBytesRead(const string& name, int64 num_bytes) { tf_data_bytes_read_counter->GetCell(name)->IncrementBy(num_bytes); } +void RecordTFDataBytesFetched(int64 num_bytes) { + tf_data_bytes_fetched_counter->GetCell()->IncrementBy(num_bytes); +} + void RecordTFDataElements(const string& name, int64 num_elements) { tf_data_elements_counter->GetCell(name)->IncrementBy(num_elements); } diff --git a/tensorflow/core/common_runtime/metrics.h b/tensorflow/core/common_runtime/metrics.h index 87f46aaa400..4b73c315fc6 100644 --- a/tensorflow/core/common_runtime/metrics.h +++ b/tensorflow/core/common_runtime/metrics.h @@ -32,6 +32,9 @@ void RecordTFDataAutotune(const string& name); // The `name` argument identifies the Dataset type (e.g. "TFRecordDataset"). void RecordTFDataBytesRead(const string& name, int64 num_bytes); +// Records the number of bytes fetched from tf.data.Dataset iterator. +void RecordTFDataBytesFetched(int64 num_bytes); + // Records the number of elements produced by a tf.data.Dataset. // // The `name` argument identifies the Dataset type (e.g. "Batch" or "Map"). diff --git a/tensorflow/core/common_runtime/process_function_library_runtime.cc b/tensorflow/core/common_runtime/process_function_library_runtime.cc index 4c01978e6d5..7bd5d09be97 100644 --- a/tensorflow/core/common_runtime/process_function_library_runtime.cc +++ b/tensorflow/core/common_runtime/process_function_library_runtime.cc @@ -410,6 +410,18 @@ Status ProcessFunctionLibraryRuntime::PinArgsAndRets( << " src_device: " << *src_device << " colo group: " << colocation_group; } + // If colocation_group is not set and output producing node is assigned + // to a remote device, colocate the retval node with its input node. + // TODO(yujingzhang): Remove this when we support outputting tensors on + // remote devices. + const bool remote_src_device = + !src_device->empty() && GetFLR(*src_device) == nullptr; + if (colocation_group.empty() && remote_src_device) { + colocation_group = + absl::StrCat(kColocationGroupPrefix, it->src()->name()); + VLOG(3) << "Considering src: " << src_node->name() + << " colo group: " << colocation_group; + } // If resource is produced by a function call node, we can't trust // source node device assignment, because multi-device functions can diff --git a/tensorflow/core/distributed_runtime/eager/eager_service_impl.cc b/tensorflow/core/distributed_runtime/eager/eager_service_impl.cc index 3fe219a0290..7e4e8fed16c 100644 --- a/tensorflow/core/distributed_runtime/eager/eager_service_impl.cc +++ b/tensorflow/core/distributed_runtime/eager/eager_service_impl.cc @@ -277,14 +277,12 @@ Status EagerServiceImpl::UpdateContext(const UpdateContextRequest* request, eager::CreateClusterFLR(request->context_id(), ctx, worker_session.get()); { - // Hold `contexts_mu_` exclusively, wait for all pending nodes to finish - // (implicitly calling WaitForAllPendingNodes inside `ctx->ClearCaches`), - // and update the context state. - // This lock prevents other threads from handling enqueue requests at the - // same time. Each enqueue request will be processed either with context - // state before or after the update, but the exact ordering needs to be - // determined by the client if desired. - mutex_lock lock(contexts_mu_); + // Hold `context_update_mu_` exclusively update the context state. This lock + // prevents other threads from processing an enqueued request at the same + // time. Each enqueue request will be processed either with context state + // before or after the update, but the exact ordering needs to be enforced + // by the client if desired. + mutex_lock l(context_update_mu_); ctx->ClearCaches(); Status s = ctx->UpdateRemoteWorker( device_mgr, std::move(remote_eager_workers), @@ -420,9 +418,6 @@ Status EagerServiceImpl::Enqueue(const EnqueueRequest* request, TF_RETURN_IF_ERROR(GetServerContext(request->context_id(), &context)); core::ScopedUnref context_unref(context); - // Acquire shared lock to prevent handling enqueue requests while updating - // context (see UpdateContext). - tf_shared_lock lock(contexts_mu_); EagerExecutor& executor = stream_id == kInvalidStreamId ? context->Context()->Executor() @@ -431,6 +426,9 @@ Status EagerServiceImpl::Enqueue(const EnqueueRequest* request, Status s; for (const auto& item : request->queue()) { auto* queue_response = response->add_queue_response(); + // Acquire shared lock to prevent handling enqueue requests while updating + // context (see UpdateContext). + tf_shared_lock l(context_update_mu_); if (item.has_operation()) { s = ExecuteOp(item.operation(), context->Context(), &executor, queue_response); @@ -560,7 +558,7 @@ Status EagerServiceImpl::SendTensor(const SendTensorOp& send_tensor, tensorflow::Status EagerServiceImpl::GetServerContext( uint64 context_id, ServerContext** server_context) { - mutex_lock l(contexts_mu_); + tf_shared_lock l(contexts_mu_); auto iter = contexts_.find(context_id); if (iter == contexts_.end()) { *server_context = nullptr; diff --git a/tensorflow/core/distributed_runtime/eager/eager_service_impl.h b/tensorflow/core/distributed_runtime/eager/eager_service_impl.h index 3223f7cdca6..16ca9bbcc3d 100644 --- a/tensorflow/core/distributed_runtime/eager/eager_service_impl.h +++ b/tensorflow/core/distributed_runtime/eager/eager_service_impl.h @@ -213,6 +213,12 @@ class EagerServiceImpl { mutex contexts_mu_; std::unordered_map contexts_ GUARDED_BY(contexts_mu_); + // Mutex to guard access to EagerContext in `contexts_`. Different from + // `contexts_mu_` which guards adding / removing item from the map, this mutex + // is supposed to be used to avoid concurrent reading/updating the state of an + // EagerContext inside the map. + mutex context_update_mu_; + std::unique_ptr gc_thread_; mutex gc_thread_shutdown_mu_; condition_variable gc_thread_cv_; diff --git a/tensorflow/core/framework/BUILD b/tensorflow/core/framework/BUILD index 2f975195391..67f6f44485a 100644 --- a/tensorflow/core/framework/BUILD +++ b/tensorflow/core/framework/BUILD @@ -1,12 +1,19 @@ load( "//tensorflow/core/platform:build_config.bzl", + "tf_kernel_tests_linkstatic", "tf_proto_library", "tf_pyclif_proto_library", ) load( "//tensorflow:tensorflow.bzl", + "tf_cc_tests", + "tf_copts", "tf_generate_proto_text_sources", ) +load( + "//tensorflow/core/platform:build_config_root.bzl", + "if_static", +) package( default_visibility = [ @@ -15,136 +22,75 @@ package( licenses = ["notice"], # Apache 2.0 ) -# Export all files for which we do not yet provide a dedicated build rule. -# This avoids breading all the rules in tensorflow/core/BUILD. +# Export all header files for which we do not yet provide a dedicated build +# rule. This avoids breading all the rules in tensorflow/core/BUILD. exports_files( srcs = [ - "allocator.cc", - "allocator.h", - "allocator_registry.cc", "allocator_registry.h", - "attr_value_util.cc", "attr_value_util.h", "bounds_check.h", - "cancellation.cc", "cancellation.h", - "collective.cc", "collective.h", - "common_shape_fns.cc", "common_shape_fns.h", "control_flow.h", - "cpu_allocator_impl.cc", - "dataset.cc", "dataset.h", "dataset_stateful_op_whitelist.h", - "device_base.cc", "device_base.h", - "fake_input.cc", - "fake_input.h", - "function.cc", "function.h", - "function_handle_cache.cc", "function_handle_cache.h", - "function_testlib.cc", - "function_testlib.h", - "graph_def_util.cc", "graph_def_util.h", - "graph_to_functiondef.cc", "graph_to_functiondef.h", - "kernel_def_builder.cc", "kernel_def_builder.h", - "kernel_def_util.cc", "kernel_def_util.h", - "load_library.cc", - "log_memory.cc", "log_memory.h", - "logging.cc", "logging.h", - "lookup_interface.cc", "lookup_interface.h", - "memory_types.cc", "memory_types.h", - "model.cc", "model.h", - "node_def_builder.cc", "node_def_builder.h", - "node_def_util.cc", "node_def_util.h", "numeric_op.h", - "op.cc", "op.h", - "op_def_builder.cc", "op_def_builder.h", - "op_def_util.cc", "op_def_util.h", - "op_gen_lib.cc", - "op_gen_lib.h", - "op_kernel.cc", "op_kernel.h", - "op_segment.cc", "op_segment.h", - "ops_util.cc", "ops_util.h", "partial_tensor_shape.h", "queue_interface.h", - "reader_base.cc", - "reader_base.h", "reader_interface.h", "reader_op_kernel.h", "register_types.h", "register_types_traits.h", - "rendezvous.cc", "rendezvous.h", - "resource_handle.cc", "resource_handle.h", - "resource_mgr.cc", "resource_mgr.h", "resource_op_kernel.h", "resource_var.h", - "run_handler.cc", "run_handler.h", - "run_handler_util.cc", "run_handler_util.h", "selective_registration.h", "session_state.h", - "shape_inference.cc", "shape_inference.h", - "shape_inference_testutil.cc", - "shape_inference_testutil.h", "shared_ptr_variant.h", "stats_aggregator.h", - "tensor.cc", "tensor.h", - "tensor_reference.cc", "tensor_reference.h", - "tensor_shape.cc", "tensor_shape.h", - "tensor_slice.cc", "tensor_slice.h", - "tensor_testutil.cc", - "tensor_testutil.h", "tensor_types.h", - "tensor_util.cc", "tensor_util.h", "thread_factory.h", - "tracking_allocator.cc", "tracking_allocator.h", "type_index.h", "type_traits.h", - "typed_allocator.cc", "typed_allocator.h", - "types.cc", "types.h", - "unique_tensor_references.cc", "unique_tensor_references.h", - "variant.cc", "variant.h", "variant_encode_decode.h", - "variant_op_registry.cc", "variant_op_registry.h", - "variant_tensor_data.cc", "variant_tensor_data.h", - "versions.cc", "versions.h", ], visibility = ["//tensorflow/core:__pkg__"], @@ -153,48 +99,11 @@ exports_files( # List of exported test source files that do not yet have local build rules. exports_files( srcs = [ - "allocator_test.cc", - "attr_value_util_test.cc", - "bfloat16_test.cc", - "cancellation_test.cc", - "common_shape_fns_test.cc", - "dataset_test.cc", - "device_base_test.cc", - "function_test.cc", - "graph_def_util_test.cc", - "graph_to_functiondef_test.cc", - "kernel_def_builder_test.cc", - "kernel_def_util_test.cc", - "memory_types_test.cc", - "model_test.cc", - "node_def_builder_test.cc", - "node_def_util_test.cc", - "op_compatibility_test.cc", - "op_def_builder_test.cc", - "op_def_util_test.cc", "op_gen_lib_test.cc", - "op_kernel_test.cc", - "op_registration_test.cc", "op_segment_test.cc", - "partial_tensor_shape_test.cc", - "rendezvous_test.cc", - "resource_mgr_test.cc", - "resource_op_kernel_test.cc", "run_handler_test.cc", "run_handler_util_test.cc", - "shape_inference_test.cc", - "shape_inference_testutil_test.cc", - "tensor_shape_test.cc", - "tensor_slice_test.cc", - "tensor_test.cc", - "tensor_testutil_test.cc", - "tensor_util_test.cc", - "tracking_allocator_test.cc", - "types_test.cc", - "unique_tensor_references_test.cc", "variant_op_copy_test.cc", - "variant_op_registry_test.cc", - "variant_test.cc", ], visibility = ["//tensorflow/core:__pkg__"], ) @@ -315,8 +224,172 @@ filegroup( ], ) +filegroup( + name = "android_test_hdrs", + srcs = [ + ":fake_input.h", + ":shape_inference_testutil.h", + ":tensor_testutil.h", + ], +) + +filegroup( + name = "android_test_srcs", + srcs = [ + ":android_test_srcs_no_core", + ":fake_input.cc", + ], +) + +filegroup( + name = "android_test_srcs_no_core", + srcs = [ + ":shape_inference_testutil.cc", + ":tensor_testutil.cc", + ], +) + # Individual targets. These should be prefered over tensorflow/core:framework # whenever possible. + +# This is redundant with the "tensorflow/core:framework" target. It's useful for +# applications that want to depend on a minimal subset of TensorFlow (e.g. XLA). +cc_library( + name = "allocator", + srcs = [ + "allocator.cc", + "allocator_registry.h", + "numeric_types.h", + "tracking_allocator.cc", + "tracking_allocator.h", + "type_traits.h", + ], + hdrs = [ + "allocator.h", + ], + features = ["parse_headers"], + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "//third_party/eigen3", + "//tensorflow/core:lib", + ] + if_static(extra_deps = [":allocator_registry_impl"]), + alwayslink = 1, +) + +# This target will be included in libtensorflow_framework.so via the +# framework_internal_impl target. +# All other dependencies on this target need to go through if_static guard, +# as otherwise duplicate registration in the registry will cause crashes. +cc_library( + name = "allocator_registry_impl", + srcs = [ + "allocator.h", + "allocator_registry.cc", + "allocator_registry.h", + "cpu_allocator_impl.cc", + "numeric_types.h", + "tracking_allocator.h", + "type_traits.h", + ], + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + "//tensorflow/core:lib", + "//third_party/eigen3", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + ], + alwayslink = 1, +) + +cc_library( + name = "tensor_testutil", + testonly = 1, + srcs = ["tensor_testutil.cc"], + hdrs = ["tensor_testutil.h"], + copts = tf_copts(), + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:test", + ], +) + +cc_library( + name = "shape_inference_testutil", + testonly = 1, + srcs = ["shape_inference_testutil.cc"], + hdrs = ["shape_inference_testutil.h"], + copts = tf_copts(), + deps = [ + ":node_def_proto_cc", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + ], +) + +cc_library( + name = "reader_base", + srcs = ["reader_base.cc"], + hdrs = ["reader_base.h"], + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + ":reader_base_proto_cc", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + ], +) + +cc_library( + name = "op_gen_lib", + srcs = ["op_gen_lib.cc"], + hdrs = ["op_gen_lib.h"], + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + ":api_def_proto_cc", + ":attr_value_proto_cc", + ":op_def_proto_cc", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core/util/proto:proto_utils", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "fake_input", + testonly = 1, + srcs = ["fake_input.cc"], + hdrs = ["fake_input.h"], + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + ":attr_value_proto_cc", + ":op_def_proto_cc", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + ], +) + +cc_library( + name = "function_testlib", + testonly = 1, + srcs = ["function_testlib.cc"], + hdrs = ["function_testlib.h"], + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + ":function_proto_cc", + ":graph_proto_cc", + ":node_def_proto_cc", + ":tensor_testutil", + ":versions_proto_cc", + "//tensorflow/core:framework", + "//tensorflow/core:lib", + ], +) + cc_library( name = "bfloat16", srcs = ["bfloat16.cc"], @@ -346,8 +419,99 @@ cc_library( # been cleaned up. exports_files( srcs = [ + "allocator.h", "bfloat16.h", + "fake_input.h", + "function_testlib.h", "numeric_types.h", + "op_gen_lib.h", + "reader_base.h", + "shape_inference_testutil.h", + "tensor_testutil.h", + ], +) + +# Framework tests. +tf_cc_tests( + name = "higher_level_tests", + size = "small", + srcs = [ + "allocator_test.cc", + "attr_value_util_test.cc", + "bfloat16_test.cc", + "cancellation_test.cc", + "common_shape_fns_test.cc", + "dataset_test.cc", + "device_base_test.cc", + "function_test.cc", + "graph_def_util_test.cc", + "graph_to_functiondef_test.cc", + "kernel_def_builder_test.cc", + "kernel_def_util_test.cc", + "memory_types_test.cc", + "model_test.cc", + "node_def_builder_test.cc", + "node_def_util_test.cc", + "op_compatibility_test.cc", + "op_def_builder_test.cc", + "op_def_util_test.cc", + "op_kernel_test.cc", + "op_registration_test.cc", + "partial_tensor_shape_test.cc", + "rendezvous_test.cc", + "resource_mgr_test.cc", + "resource_op_kernel_test.cc", + "shape_inference_test.cc", + "shape_inference_testutil_test.cc", + "tensor_shape_test.cc", + "tensor_slice_test.cc", + "tensor_test.cc", + "tensor_testutil_test.cc", + "tensor_util_test.cc", + "tracking_allocator_test.cc", + "types_test.cc", + "unique_tensor_references_test.cc", + "variant_op_registry_test.cc", + "variant_test.cc", + ], + create_named_test_suite = True, + linkopts = select({ + "//tensorflow:macos": ["-headerpad_max_install_names"], + "//conditions:default": [], + }), + linkstatic = tf_kernel_tests_linkstatic(), + visibility = [ + "//tensorflow:internal", + "//tensorflow/core:__pkg__", + ], + deps = [ + "//tensorflow/cc:cc_ops", + "//tensorflow/cc:cc_ops_internal", + "//tensorflow/cc:function_ops", + "//tensorflow/cc:ops", + "//tensorflow/cc:scope", + "//tensorflow/cc:sendrecv_ops", + "//tensorflow/cc:while_loop", + "//tensorflow/core", + "//tensorflow/core:core_cpu", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:direct_session_internal", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:ops", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + "//tensorflow/core/kernels:ops_util", + "//tensorflow/core/platform:regexp", + "//tensorflow/core/util:protos_test_cc", + "//third_party/eigen3", + "@com_google_absl//absl/base", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/core/framework/allocator.cc b/tensorflow/core/framework/allocator.cc index 1695aed7f89..6757a9b593e 100644 --- a/tensorflow/core/framework/allocator.cc +++ b/tensorflow/core/framework/allocator.cc @@ -39,8 +39,11 @@ string AllocatorStats::DebugString() const { "MaxInUse: %20lld\n" "NumAllocs: %20lld\n" "MaxAllocSize: %20lld\n", - this->bytes_limit ? *this->bytes_limit : 0, this->bytes_in_use, - this->peak_bytes_in_use, this->num_allocs, this->largest_alloc_size); + static_cast(this->bytes_limit ? *this->bytes_limit : 0), + static_cast(this->bytes_in_use), + static_cast(this->peak_bytes_in_use), + static_cast(this->num_allocs), + static_cast(this->largest_alloc_size)); } constexpr size_t Allocator::kAllocatorAlignment; diff --git a/tensorflow/core/framework/cancellation.cc b/tensorflow/core/framework/cancellation.cc index a91442fcbad..99ac9a70ac1 100644 --- a/tensorflow/core/framework/cancellation.cc +++ b/tensorflow/core/framework/cancellation.cc @@ -201,4 +201,9 @@ CancellationManager::~CancellationManager() { } } +bool CancellationManager::IsCancelling() { + mutex_lock lock(mu_); + return is_cancelling_; +} + } // end namespace tensorflow diff --git a/tensorflow/core/framework/cancellation.h b/tensorflow/core/framework/cancellation.h index 3e1727ae54a..7e60eb54065 100644 --- a/tensorflow/core/framework/cancellation.h +++ b/tensorflow/core/framework/cancellation.h @@ -143,6 +143,9 @@ class CancellationManager { // called. bool TryDeregisterCallback(CancellationToken token); + // Returns true iff cancellation is in progress. + bool IsCancelling(); + private: struct State { Notification cancelled_notification; diff --git a/tensorflow/core/framework/cancellation_test.cc b/tensorflow/core/framework/cancellation_test.cc index e4994350ddd..743b3905b83 100644 --- a/tensorflow/core/framework/cancellation_test.cc +++ b/tensorflow/core/framework/cancellation_test.cc @@ -108,6 +108,7 @@ TEST(Cancellation, IsCancelled) { w.Schedule([n, cm]() { while (!cm->IsCancelled()) { } + ASSERT_FALSE(cm->IsCancelling()); n->Notify(); }); } @@ -119,6 +120,30 @@ TEST(Cancellation, IsCancelled) { delete cm; } +TEST(Cancellation, IsCancelling) { + CancellationManager cm; + Notification started_cancelling; + Notification can_finish_cancel; + Notification cancel_done; + thread::ThreadPool w(Env::Default(), "test", 1); + auto token = cm.get_cancellation_token(); + ASSERT_TRUE( + cm.RegisterCallback(token, [&started_cancelling, &can_finish_cancel]() { + started_cancelling.Notify(); + can_finish_cancel.WaitForNotification(); + })); + w.Schedule([&cm, &cancel_done]() { + cm.StartCancel(); + cancel_done.Notify(); + }); + started_cancelling.WaitForNotification(); + ASSERT_TRUE(cm.IsCancelling()); + can_finish_cancel.Notify(); + cancel_done.WaitForNotification(); + ASSERT_FALSE(cm.IsCancelling()); + ASSERT_TRUE(cm.IsCancelled()); +} + TEST(Cancellation, TryDeregisterWithoutCancel) { bool is_cancelled = false; CancellationManager* manager = new CancellationManager(); diff --git a/tensorflow/core/framework/dataset.cc b/tensorflow/core/framework/dataset.cc index a1625b48408..71538648537 100644 --- a/tensorflow/core/framework/dataset.cc +++ b/tensorflow/core/framework/dataset.cc @@ -348,6 +348,20 @@ int64 GetAllocatedBytes(const std::vector& element) { return allocated_bytes; } +int64 GetTotalBytes(const std::vector& element) { + int64 total_bytes = 0; + DatasetBase* dataset; + for (auto& tensor : element) { + if (tensor.dtype() == DT_VARIANT && + GetDatasetFromVariantTensor(tensor, &dataset).ok()) { + total_bytes += dataset->TotalBytes(); + } else { + total_bytes += tensor.TotalBytes(); + } + } + return total_bytes; +} + Status GetDatasetFromVariantTensor(const Tensor& tensor, DatasetBase** out_dataset) { if (!(tensor.dtype() == DT_VARIANT && diff --git a/tensorflow/core/framework/dataset.h b/tensorflow/core/framework/dataset.h index 496544f2beb..0e7a34cd26a 100644 --- a/tensorflow/core/framework/dataset.h +++ b/tensorflow/core/framework/dataset.h @@ -60,6 +60,12 @@ namespace data { constexpr int kInfiniteCardinality = -1; constexpr int kUnknownCardinality = -2; +// This constant is a magic number that is used (as a prefix) to identify keys +// used for serialization of iterator state. +constexpr char kFullNameRandomHex[] = "60d899aa0d8ce4351e7c3b419e92d25b"; +constexpr char kPipe[] = "|"; +constexpr char kColon[] = ":"; + class DatasetBase; class SerializationContext; @@ -653,6 +659,9 @@ class DatasetContext { // Returns the number of bytes allocated for the given tensor. int64 GetAllocatedBytes(const std::vector& element); +// Returns the estimated memory usage in bytes of the given tensor. +int64 GetTotalBytes(const std::vector& element); + // Validates and extracts a `DatasetBase` object from `tensor`. // // `tensor` must have been written by a call to SetVariantTensorToDataset(). @@ -755,6 +764,9 @@ class DatasetBase : public core::RefCounted { // Returns the number of bytes allocated for tensors of this dataset. virtual int64 AllocatedBytes() const { return 0; } + // Returns the estimated number of bytes used for tensors of this dataset. + virtual int64 TotalBytes() const { return 0; } + // Returns the cardinality of this dataset. virtual int64 Cardinality() const { return kUnknownCardinality; } @@ -886,7 +898,12 @@ class DatasetBaseIterator : public IteratorBase { bool* end_of_sequence) = 0; string full_name(const string& name) const { - return strings::StrCat(params_.prefix, ":", name); + if (str_util::StrContains(name, kColon)) { + LOG(ERROR) << name << " should not contain " << kColon; + } + + return strings::StrCat(kFullNameRandomHex, kPipe, params_.prefix, kColon, + name); } // By default we model iterators using an unknown node, which acts as diff --git a/tensorflow/core/framework/dataset_test.cc b/tensorflow/core/framework/dataset_test.cc index 6062362329a..5d5582f3f59 100644 --- a/tensorflow/core/framework/dataset_test.cc +++ b/tensorflow/core/framework/dataset_test.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow/core/framework/dataset.h" +#include "tensorflow/core/framework/tensor_testutil.h" #include "tensorflow/core/platform/test.h" namespace tensorflow { @@ -26,4 +27,52 @@ TEST(DatasetTest, RegisterDatasetOp) { EXPECT_FALSE(data::DatasetOpRegistry::IsRegistered("InvalidDatasetOp")); } +enum DataTypeTest { _int32, _int64, _float, _double, _string }; + +struct DatasetTestParam { + const DataTypeTest type; + const std::vector tensor; + const int64 expected_bytes; +}; + +class DatasetTestTotalBytes + : public ::testing::TestWithParam {}; + +TEST_P(DatasetTestTotalBytes, TestTotalBytes) { + const DatasetTestParam& test_case = GetParam(); + if (test_case.type == _string) { + // TotalBytes() is approximate and gives an upper bound for strings + EXPECT_LE(data::GetTotalBytes(test_case.tensor), test_case.expected_bytes); + } else { + EXPECT_EQ(data::GetTotalBytes(test_case.tensor), test_case.expected_bytes); + } +} + +std::vector tensor_int32s{test::AsTensor({1, 2, 3, 4, 5}), + test::AsTensor({1, 2, 3, 4})}; + +std::vector tensor_int64s{test::AsTensor({1, 2, 3, 4, 5}), + test::AsTensor({10, 12})}; + +std::vector tensor_floats{test::AsTensor({1.0, 2.0, 3.0, 4.0})}; + +std::vector tensor_doubles{ + test::AsTensor({100.0}), test::AsTensor({200.0}), + test::AsTensor({400.0}), test::AsTensor({800.0})}; + +const string str = "test string"; // NOLINT +std::vector tensor_strs{test::AsTensor({str})}; + +const DatasetTestParam test_cases[] = { + {_int32, tensor_int32s, 4 /*bytes*/ * 9 /*elements*/}, + {_int64, tensor_int64s, 8 /*bytes*/ * 7 /*elements*/}, + {_float, tensor_floats, 4 /*bytes*/ * 4 /*elements*/}, + {_double, tensor_doubles, 8 /*bytes*/ * 4 /*elements*/}, + {_string, tensor_strs, + static_cast(sizeof(str) + str.size()) /*bytes*/}, +}; + +INSTANTIATE_TEST_SUITE_P(DatasetTestTotalBytes, DatasetTestTotalBytes, + ::testing::ValuesIn(test_cases)); + } // namespace tensorflow diff --git a/tensorflow/core/framework/function.cc b/tensorflow/core/framework/function.cc index 413fdbcc3ae..c06f2d148ea 100644 --- a/tensorflow/core/framework/function.cc +++ b/tensorflow/core/framework/function.cc @@ -167,9 +167,11 @@ class FunctionInstantiationHelper { } // Builds index for nodes that can be used as node's input arguments. + // `resource_arg_unique_id`: if non-negative, will be populated to the + // "_resource_arg_unique_id" attribute of the arg node. Status BuildInputArgIndex(const OpDef::ArgDef& arg_def, AttrSlice attr_values, const FunctionDef::ArgAttrs* arg_attrs, - bool ints_on_device) { + bool ints_on_device, int64 resource_arg_unique_id) { bool is_type_list; DataTypeVector dtypes; TF_RETURN_IF_ERROR( @@ -196,6 +198,9 @@ class FunctionInstantiationHelper { DataType dtype = arg_def.is_ref() ? MakeRefType(dtypes[i]) : dtypes[i]; AddAttr("T", dtype, gnode); AddAttr("index", arg_index, gnode); + if (resource_arg_unique_id >= 0) { + AddAttr("_resource_arg_unique_id", resource_arg_unique_id, gnode); + } if (arg_attrs) { for (const auto& arg_attr : arg_attrs->attr()) { AddAttr(arg_attr.first, arg_attr.second, gnode->mutable_attr()); @@ -729,8 +734,14 @@ Status InstantiateFunction(const FunctionDef& fdef, AttrSlice attr_values, auto it = fdef.arg_attr().find(i); const FunctionDef::ArgAttrs* arg_attrs = it != fdef.arg_attr().end() ? &it->second : nullptr; + auto resource_id_it = fdef.resource_arg_unique_id().find(i); + int64 resource_arg_unique_id = + resource_id_it != fdef.resource_arg_unique_id().end() + ? resource_id_it->second + : -1LL; s = helper.BuildInputArgIndex(arg_def, attr_values, arg_attrs, - ints_on_device); + ints_on_device, resource_arg_unique_id); + if (!s.ok()) { errors::AppendToMessage(&s, "In ", Print(arg_def)); return s; diff --git a/tensorflow/core/framework/function.proto b/tensorflow/core/framework/function.proto index 7b5756ed8c9..71423f9168f 100644 --- a/tensorflow/core/framework/function.proto +++ b/tensorflow/core/framework/function.proto @@ -37,6 +37,17 @@ message FunctionDef { } map arg_attr = 7; + // Unique IDs for each resource argument, used to track aliasing resources. If + // Argument A and Argument B alias each other, then + // resource_arg_unique_ids[A.index] == resource_arg_unique_ids[B.index]. + // + // If this field is empty, none of the arguments could alias; otherwise, every + // resource argument should have an entry in this field. + // + // When instantiated, the unique IDs will be attached to the _Arg nodes' + // "_resource_arg_unique_id" attribute. + map resource_arg_unique_id = 8; + // NOTE: field id 2 deleted on Jan 11, 2017, GraphDef version 21. reserved 2; diff --git a/tensorflow/core/framework/function_handle_cache.cc b/tensorflow/core/framework/function_handle_cache.cc index 2b93b6b2f87..b64161d50b3 100644 --- a/tensorflow/core/framework/function_handle_cache.cc +++ b/tensorflow/core/framework/function_handle_cache.cc @@ -22,7 +22,9 @@ namespace tensorflow { namespace data { FunctionHandleCache::FunctionHandleCache(FunctionLibraryRuntime* lib) - : lib_(lib), state_handle_(strings::Printf("%lld", random::New64())) {} + : lib_(lib), + state_handle_( + strings::Printf("%lld", static_cast(random::New64()))) {} FunctionHandleCache::~FunctionHandleCache() { Status s = Clear(); diff --git a/tensorflow/core/framework/graph_to_functiondef.cc b/tensorflow/core/framework/graph_to_functiondef.cc index 5baf560801b..50aa4a81926 100644 --- a/tensorflow/core/framework/graph_to_functiondef.cc +++ b/tensorflow/core/framework/graph_to_functiondef.cc @@ -432,6 +432,7 @@ Status GraphToFunctionDef(const Graph& fn_body, const string& fn_name, const string& input_name = node_names.GetInputName(node->name()); argdef->set_name(input_name); FunctionDef::ArgAttrs arg_attrs; + int64 resource_arg_unique_id = -1; for (const auto& attr : node->attrs()) { // Only copy internal attributes. These attributes will be applied to // _Arg/Placeholder nodes when this FunctionDef is converted to graph, @@ -440,10 +441,16 @@ Status GraphToFunctionDef(const Graph& fn_body, const string& fn_name, if (absl::StartsWith(attr.first, "_")) { arg_attrs.mutable_attr()->insert(attr); } + if (attr.first == "_resource_arg_unique_id") { + resource_arg_unique_id = attr.second.i(); + } } if (arg_attrs.attr_size() > 0) { (*fdef->mutable_arg_attr())[i] = std::move(arg_attrs); } + if (resource_arg_unique_id >= 0) { + (*fdef->mutable_resource_arg_unique_id())[idx] = resource_arg_unique_id; + } tensor_renaming[strings::StrCat(node->name(), ":", idx)] = input_name; } diff --git a/tensorflow/core/framework/op_kernel.cc b/tensorflow/core/framework/op_kernel.cc index 959be0781be..c097c2fd044 100644 --- a/tensorflow/core/framework/op_kernel.cc +++ b/tensorflow/core/framework/op_kernel.cc @@ -92,8 +92,12 @@ Status MatchSignatureHelper(const DataTypeSlice expected_inputs, OpKernel::OpKernel(OpKernelConstruction* context) : OpKernel(context, MakeUnique(context->def())) {} +OpKernel::OpKernel(OpKernelConstruction* context, bool is_deferred) + : OpKernel(context, MakeUnique(context->def()), + is_deferred) {} + OpKernel::OpKernel(OpKernelConstruction* context, - std::unique_ptr node_def) + std::unique_ptr node_def, bool is_deferred) : def_(std::move(node_def)), input_types_(context->input_types().begin(), context->input_types().end()), @@ -106,6 +110,7 @@ OpKernel::OpKernel(OpKernelConstruction* context, input_name_map_(context->num_inputs()), output_name_map_(context->num_outputs()), graph_def_version_(context->graph_def_version()), + is_deferred_(is_deferred), cost_estimate_(OpKernel::kInitialCostEstimateCycles) { OP_REQUIRES_OK(context, NameRangesForNode(*def_, *context->op_def_, &input_name_map_, @@ -1135,8 +1140,11 @@ void LoadDynamicKernelsInternal() { // Override to allow loading unsafe packages for development. // DO NOT USE UNLESS YOU KNOW WHAT ABI ISSUES YOU CAN ENCOUNTER. - bool override_abi_check = - strcmp(getenv("TF_REALLY_LOAD_UNSAFE_PACKAGES"), "1") == 0; + char* _abi_check_env_var = getenv("TF_REALLY_LOAD_UNSAFE_PACKAGES"); + bool override_abi_check = false; + if (_abi_check_env_var != nullptr) { + override_abi_check = strcmp(_abi_check_env_var, "1") == 0; + } string bazel_kernel_dir = io::JoinPath(env->GetRunfilesDir(), "tensorflow", "core", "kernels"); diff --git a/tensorflow/core/framework/op_kernel.h b/tensorflow/core/framework/op_kernel.h index 7f9895f7771..5bcb774a31a 100644 --- a/tensorflow/core/framework/op_kernel.h +++ b/tensorflow/core/framework/op_kernel.h @@ -90,7 +90,14 @@ class OpKernel { // stripped-down `NodeDef` that does not contain the full set of attrs (such // as tensor values) if the descendant stores them in a different form. explicit OpKernel(OpKernelConstruction* context, - std::unique_ptr node_def); + std::unique_ptr node_def, + bool is_deferred = false); + + // Specialized constructor that allows a kernel implementation to mark itself + // as a "deferred" op. If true, the executor will provide access to the + // `OpKernelContext::inc_num_deferred_ops_function()` and + // `OpKernelContext::dec_num_deferred_ops_function()` methods at run-time. + explicit OpKernel(OpKernelConstruction* context, bool is_deferred); virtual ~OpKernel(); @@ -210,6 +217,9 @@ class OpKernel { static int DeviceNumaNode(const DeviceBase* device); + // Returns `true` if and only if this kernel uses deferred execution. + bool is_deferred() const { return is_deferred_; } + private: const std::unique_ptr def_; const DataTypeVector input_types_; @@ -219,6 +229,7 @@ class OpKernel { NameRangeMap input_name_map_; NameRangeMap output_name_map_; const int graph_def_version_; + const bool is_deferred_; bool expensive_; std::atomic_uint_fast64_t cost_estimate_; @@ -234,12 +245,12 @@ class AsyncOpKernel : public OpKernel { // Implementations of ComputeAsync() must ensure that `done` is (eventually) // called exactly once to signal the completion of the computation. The // implementation of ComputeAsync() must not block on the execution of another - // OpKernel. `done` may be called by the current thread, or by another thread + // OpKernel. `done` may be called by the current thread, or by another thread. // `context` is guaranteed to stay alive until the `done` callback starts. // // Since it is possible that the unblocking kernel may never run (due to an // error or cancellation), in most cases the AsyncOpKernel should implement - // cancellation support via `ctx->cancellation_manager()`. + // cancellation support via `context->cancellation_manager()`. // // WARNING: As soon as the `done` callback starts, `context` and `this` may be // deleted. No code depending on these objects should execute after the call @@ -253,12 +264,11 @@ class AsyncOpKernel : public OpKernel { void Compute(OpKernelContext* context) final; }; -// Wraps a tensor that is held by an Op across calls to Compute(). For -// memory safety when using asynchronous devices like GPUs, the system -// must be notified when a Tensor is used inside an Op execution. The -// wrapper ensures that all uses of the Tensor are tracked, because in -// order to retrieve the Tensor the caller must use AccessTensor which -// notifies the context. +// Wraps a tensor that is held by an Op across calls to Compute(). For memory +// safety when using asynchronous devices like GPUs, the system must be notified +// when a Tensor is used inside an Op execution. The wrapper ensures that all +// uses of the Tensor are tracked, because in order to retrieve the Tensor the +// caller must use AccessTensor which notifies the context. class PersistentTensor { public: PersistentTensor() {} @@ -1271,11 +1281,13 @@ class OpKernelContext { // functions. It then must call these two functions in pairs, before and after // device execution, respectively. TF_MUST_USE_RESULT std::function inc_num_deferred_ops_function() { + DCHECK(params_->op_kernel->is_deferred()); return params_->inc_num_deferred_ops_function ? params_->inc_num_deferred_ops_function : []() {}; } TF_MUST_USE_RESULT std::function dec_num_deferred_ops_function() { + DCHECK(params_->op_kernel->is_deferred()); return params_->dec_num_deferred_ops_function ? params_->dec_num_deferred_ops_function : []() {}; diff --git a/tensorflow/core/framework/resource_mgr_test.cc b/tensorflow/core/framework/resource_mgr_test.cc index 84ecd79efdf..e42ae38c741 100644 --- a/tensorflow/core/framework/resource_mgr_test.cc +++ b/tensorflow/core/framework/resource_mgr_test.cc @@ -24,6 +24,7 @@ limitations under the License. #include "tensorflow/core/lib/core/threadpool.h" #include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/lib/strings/strcat.h" +#include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/platform/test.h" namespace tensorflow { @@ -190,11 +191,13 @@ string Policy(const string& attr_container, const string& attr_shared_name, TEST(ContainerInfo, Basic) { // Correct cases. - EXPECT_EQ(Policy("", "", false), "[localhost,_0_foo,private]"); + EXPECT_TRUE(RE2::FullMatch(Policy("", "", false), + "\\[localhost,_\\d+_foo,private\\]")); EXPECT_EQ(Policy("", "", true), "[localhost,foo,public]"); EXPECT_EQ(Policy("", "bar", false), "[localhost,bar,public]"); EXPECT_EQ(Policy("", "bar", true), "[localhost,bar,public]"); - EXPECT_EQ(Policy("cat", "", false), "[cat,_1_foo,private]"); + EXPECT_TRUE( + RE2::FullMatch(Policy("cat", "", false), "\\[cat,_\\d+_foo,private\\]")); EXPECT_EQ(Policy("cat", "", true), "[cat,foo,public]"); EXPECT_EQ(Policy("cat", "bar", false), "[cat,bar,public]"); EXPECT_EQ(Policy("cat", "bar", true), "[cat,bar,public]"); diff --git a/tensorflow/core/framework/resource_op_kernel_test.cc b/tensorflow/core/framework/resource_op_kernel_test.cc index 7a2a87045bf..c373e541c9d 100644 --- a/tensorflow/core/framework/resource_op_kernel_test.cc +++ b/tensorflow/core/framework/resource_op_kernel_test.cc @@ -89,12 +89,13 @@ class ResourceOpKernelTest : public ::testing::Test { protected: std::unique_ptr CreateOp(int code, const string& shared_name) { + static std::atomic count(0); NodeDef node_def; - TF_CHECK_OK( - NodeDefBuilder(strings::StrCat("test-node", count_++), "StubResourceOp") - .Attr("code", code) - .Attr("shared_name", shared_name) - .Finalize(&node_def)); + TF_CHECK_OK(NodeDefBuilder(strings::StrCat("test-node", count.fetch_add(1)), + "StubResourceOp") + .Attr("code", code) + .Attr("shared_name", shared_name) + .Finalize(&node_def)); Status status; std::unique_ptr op(CreateOpKernel( DEVICE_CPU, &device_, device_.GetAllocator(AllocatorAttributes()), @@ -126,7 +127,6 @@ class ResourceOpKernelTest : public ::testing::Test { StubDevice device_; ResourceMgr mgr_; - int count_ = 0; }; TEST_F(ResourceOpKernelTest, PrivateResource) { @@ -137,6 +137,9 @@ TEST_F(ResourceOpKernelTest, PrivateResource) { TF_EXPECT_OK(RunOpKernel(op.get())); // Default non-shared name provided from ContainerInfo. + // TODO(gonnet): This test is brittle since it assumes that the + // ResourceManager is untouched and thus the private resource name starts + // with "_0_". const string key = "_0_" + op->name(); StubResource* resource; diff --git a/tensorflow/core/framework/tensor.h b/tensorflow/core/framework/tensor.h index 5c7913d36c7..3c465491426 100644 --- a/tensorflow/core/framework/tensor.h +++ b/tensorflow/core/framework/tensor.h @@ -50,7 +50,6 @@ class Var; namespace batch_util { Status CopyElementToSlice(Tensor element, Tensor* parent, int64 index); -Status MaybeMoveSliceToElement(Tensor* parent, Tensor* element, int64 index); } // namespace batch_util /// @ingroup core @@ -341,7 +340,7 @@ class Tensor { /// methods that have alignment requirement (e.g., `flat()`, `tensor()`). /// /// REQUIRES: `dims()` >= 1 - /// REQUIRES: `0 <= dim0_start < dim_size(0)` + /// REQUIRES: `0 <= index < dim_size(0)` Tensor SubSlice(int64 index) const; /// \brief Parse `other` and construct the tensor. @@ -633,10 +632,11 @@ class Tensor { TF_CHECK_OK(BitcastFrom(other, dtype, shape)); } - private: // Returns true if the refcount on buf_ and any possible underlying root // buffer is one. bool RefCountIsOne() const; + + private: void CheckType(DataType expected_dtype) const; void CheckTypeAndIsAligned(DataType expected_dtype) const; void CheckIsAlignedAndSingleElement() const; @@ -659,24 +659,10 @@ class Tensor { friend class AutoReloadVariableOp; // For access to set_shape. friend class TensorTestHelper; // For access to set_shape. friend class CastOpBase; // For access to set_dtype. - friend class OpKernelContext; // For access to RefCountIsOne(). friend class ScopedAllocator; // For access to buf_. - friend class XlaTensor; // For access to RefCountIsOne(). - template - friend class AssignVariableOp; // For access to RefCountIsOne(). - template - friend Status PrepareToUpdateVariable( - OpKernelContext* ctx, Tensor* tensor, - bool copy_on_read_mode); // For access to RefCountIsOne(). - template - friend Status EnsureSparseVariableAccess( - OpKernelContext* ctx, Var* var); // For access to RefCountIsOne(). friend Status batch_util::CopyElementToSlice( Tensor element, Tensor* parent, - int64 index); // For access to RefCountIsOne(). - friend Status batch_util::MaybeMoveSliceToElement( - Tensor* parent, Tensor* element, - int64 index); // For access to RefCountIsOne(). + int64 index); // For access to base(). bool CanUseDMA() const; diff --git a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc index f7df31a07bf..4a179751982 100644 --- a/tensorflow/core/grappler/costs/op_level_cost_estimator.cc +++ b/tensorflow/core/grappler/costs/op_level_cost_estimator.cc @@ -385,6 +385,8 @@ OpLevelCostEstimator::OpLevelCostEstimator() { const int quantize_v2_cost = EIGEN_COST(scalar_product_op) + EIGEN_COST(scalar_max_op) + EIGEN_COST(scalar_min_op) + EIGEN_COST(scalar_round_op); + const int quantize_and_dequantize_v2_cost = + quantize_v2_cost + EIGEN_COST(scalar_product_op); // Unary ops alphabetically sorted elementwise_ops_.emplace("Acos", EIGEN_COST(scalar_acos_op)); @@ -413,6 +415,8 @@ OpLevelCostEstimator::OpLevelCostEstimator() { elementwise_ops_.emplace("Log", EIGEN_COST(scalar_log_op)); elementwise_ops_.emplace("Log1p", EIGEN_COST(scalar_log1p_op)); elementwise_ops_.emplace("Neg", EIGEN_COST(scalar_opposite_op)); + elementwise_ops_.emplace("QuantizeAndDequantizeV2", + quantize_and_dequantize_v2_cost); elementwise_ops_.emplace("QuantizeV2", quantize_v2_cost); elementwise_ops_.emplace("Reciprocal", EIGEN_COST(scalar_inverse_op)); elementwise_ops_.emplace("Rint", 1); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc index 7f6940fb31d..9a088702e19 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc @@ -1631,6 +1631,17 @@ class HoistCWiseUnaryChainsStage : public ArithmeticOptimizerStage { // TODO(rmlarsen): Allow outgoing control edges. return false; } + // Do not hoist Relu if it can be fused with its predecessors. This is + // important because remapping runs after arithmetic. + if (IsRelu(*op) || IsRelu6(*op)) { + NodeDef* operand = nullptr; + if (!GetInputNode(op->input(0), &operand).ok()) { + return false; + } + if (IsFusedBatchNorm(*operand) || IsBiasAdd(*operand)) { + return false; + } + } } return true; } @@ -2845,14 +2856,23 @@ class OptimizeMaxOrMinOfMonotonicStage : public ArithmeticOptimizerStage { // 2. inner_function's output is not being consumed elsewhere. // 3. is monotonic increasing if reduction_node is a pooling operation // since we don't have MinPool operations. - // 4. inner_functions is not a Relu node with an input from FusedBatchNorm. - // This pattern will be fused later by remapper. + // 4. inner_functions is not a Relu node with an input from FusedBatchNorm + // or BiasAdd. This pattern will be fused later by remapper. + auto can_be_fused_by_remapper = [](const NodeDef& consumer, + const NodeDef& producer) -> bool { + if (IsRelu(consumer) || IsRelu6(consumer)) { + if (IsFusedBatchNorm(producer) || IsBiasAdd(producer)) { + return true; + } + } + return false; + }; bool is_non_decreasing = false; if (!IsInPreserveSet(*inner_function) && IsElementWiseMonotonic(*inner_function, &is_non_decreasing) && ctx().node_map->GetOutputs(inner_function->name()).size() == 1 && (is_non_decreasing || !IsAnyMaxPool(*reduction_node)) && - !(IsRelu(*inner_function) && IsFusedBatchNorm(*inner_function_input))) { + !can_be_fused_by_remapper(*inner_function, *inner_function_input)) { // Swap the first inputs of the inner function Op & the reduction Op. NodeDef* inner_input; TF_RETURN_IF_ERROR(GetInputNode(inner_function->input(0), &inner_input)); diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc index cd20396a38a..4c703439420 100644 --- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc @@ -3077,6 +3077,47 @@ TEST_F(ArithmeticOptimizerTest, MinimizeBroadcasts_BuildTreeUp) { test::ExpectTensorNear(tensors[0], tensors_expected[0], 1e-6); } +TEST_F(ArithmeticOptimizerTest, DoNotHoistReluFromConcat) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output weights1 = ops::Const(s.WithOpName("weights1"), + Input::Initializer(1.0f, {5, 5, 3, 4})); + Output weights2 = ops::Const(s.WithOpName("weights2"), + Input::Initializer(2.0f, {5, 5, 3, 4})); + Output biases = + ops::Const(s.WithOpName("biases"), Input::Initializer(2.0f, {4})); + Output axis = ops::Const(s.WithOpName("axis"), 3, {}); + Output input = ops::Const(s.WithOpName("input"), + Input::Initializer(1.0f, {1, 28, 28, 3})); + Output branch1 = + ops::Conv2D(s.WithOpName("conv1"), input, weights1, {1, 1, 1, 1}, "SAME"); + branch1 = ops::BiasAdd(s.WithOpName("biasadd1"), branch1, biases); + branch1 = ops::Relu(s.WithOpName("relu1"), branch1); + Output branch2 = + ops::Conv2D(s.WithOpName("conv2"), input, weights2, {1, 1, 1, 1}, "SAME"); + branch2 = ops::BiasAdd(s.WithOpName("biasadd2"), branch2, biases); + branch2 = ops::Relu(s.WithOpName("relu2"), branch2); + Output concat = ops::Concat(s.WithOpName("concat"), {branch1, branch2}, axis); + Output output = ops::Identity(s.WithOpName("output"), concat); + + GrapplerItem item; + item.fetch = {"output"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + + auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + + GraphDef new_graph; + ArithmeticOptimizer optimizer; + OptimizeAndPrune(&optimizer, &item, &new_graph); + + // Verify that the two Relus are not hoisted. + EXPECT_EQ(CountOpNodes(new_graph, "Relu"), 2); + + auto tensors = EvaluateNodes(new_graph, item.fetch); + for (int i = 0; i < item.fetch.size(); ++i) { + test::ExpectTensorNear(tensors[i], tensors_expected[i], 1e-6); + } +} + TEST_F(ArithmeticOptimizerTest, HoistCWiseUnaryFromConcat) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); Output a = ops::Const(s.WithOpName("a"), 3.14f, {32}); @@ -3666,6 +3707,41 @@ TEST_F(ArithmeticOptimizerTest, test::ExpectTensorNear(tensors[0], tensors_expected[0], 1e-6); } +TEST_F(ArithmeticOptimizerTest, OptimizeMaxOrMinOfMonotonicBiasAddReluMaxPool) { + tensorflow::Scope s = tensorflow::Scope::NewRootScope(); + Output weights = ops::Const(s.WithOpName("weights"), + Input::Initializer(1.0f, {5, 5, 3, 4})); + Output biases = + ops::Const(s.WithOpName("biases"), Input::Initializer(2.0f, {4})); + Output input = ops::Const(s.WithOpName("input"), + Input::Initializer(1.0f, {1, 28, 28, 3})); + Output output = + ops::Conv2D(s.WithOpName("conv"), input, weights, {1, 1, 1, 1}, "SAME"); + output = ops::BiasAdd(s.WithOpName("biasadd"), output, biases); + output = ops::Relu(s.WithOpName("relu"), output); + output = ops::MaxPool(s.WithOpName("max_pool"), output, {1, 2, 2, 1}, + {1, 2, 2, 1}, "VALID"); + output = ops::Identity(s.WithOpName("output"), output); + + GrapplerItem item; + item.fetch = {"output"}; + TF_CHECK_OK(s.ToGraphDef(&item.graph)); + auto tensors_expected = EvaluateNodes(item.graph, item.fetch); + ASSERT_EQ(tensors_expected.size(), 1); + + GraphDef new_graph; + ArithmeticOptimizer optimizer; + EnableOnlyOptimizeMaxOrMinOfMonotonic(&optimizer); + OptimizeTwice(&optimizer, &item, &new_graph); + + // Should be a NoOp + VerifyGraphsMatch(item.graph, new_graph, __LINE__); + + auto tensors = EvaluateNodes(new_graph, item.fetch); + ASSERT_EQ(tensors.size(), 1); + test::ExpectTensorNear(tensors[0], tensors_expected[0], 1e-6); +} + TEST_F(ArithmeticOptimizerTest, OptimizeMaxOrMinOfMonotonicElementWiseMaxPool) { tensorflow::Scope s = tensorflow::Scope::NewRootScope(); auto x = ops::Const(s.WithOpName("x"), 1.5f, {3, 3, 3, 1}); diff --git a/tensorflow/core/grappler/optimizers/loop_optimizer.cc b/tensorflow/core/grappler/optimizers/loop_optimizer.cc index cf56e00e148..a00dcc0634b 100644 --- a/tensorflow/core/grappler/optimizers/loop_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/loop_optimizer.cc @@ -627,7 +627,7 @@ Status CheckForDeadFanout(const MutableGraphView& view, return Status::OK(); } - VLOG(3) << "Try to find a zero iteration while loop:" + VLOG(4) << "Try to find a zero iteration while loop:" << " switch_node=" << switch_node.name(); // Find the boolean predicate from a LoopCond node (e.g. Greater). @@ -704,7 +704,7 @@ Status CheckForDeadFanout(const MutableGraphView& view, &constant_switch_value)); if (constant_switch_value == false) { - VLOG(4) << "Remove 0 iteration while loop:" + VLOG(3) << "Remove 0 iteration while loop:" << " switch_node=" << switch_node.name(); *has_dead_fanout = true; *dead_fanout = 1; @@ -746,8 +746,6 @@ Status LoopOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item, 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); absl::flat_hash_set feed_nodes; for (const auto& feed : item.feed) { @@ -890,43 +888,55 @@ Status LoopOptimizer::RemoveDeadBranches( // Names of the nodes that were removed from the graph. absl::flat_hash_set dead_node_names; dead_node_names.reserve(dead_nodes.size()); - for (const NodeDef* dead_node : dead_nodes) + for (const NodeDef* dead_node : dead_nodes) { dead_node_names.insert(dead_node->name()); + } - // Remove dead inputs from Merge nodes that were not pruned from the graph. + // Check that the merge nodes are valid. for (const auto& itr : dead_merge_inputs) { - NodeDef* dead_node = itr.first; - if (dead_nodes.find(dead_node) != dead_nodes.end()) { - // The node has been pruned since all its inputs are dead. + NodeDef* merge_node = itr.first; + if (dead_nodes.find(merge_node) != dead_nodes.end()) { + // The node will be pruned since all its inputs are dead. continue; } // Remove dead data input. const std::set& dead_inputs = itr.second; - CHECK_LE(dead_inputs.size(), 1); - // (This loop would delete >1 items possibly in the wrong order.) - for (int index : dead_inputs) { - dead_node->mutable_input()->DeleteSubrange(index, 1); + const int num_data_inputs = merge_node->attr().at("N").i(); + if (merge_node->input_size() != num_data_inputs) { + LOG(WARNING) + << "Skipping loop optimization for Merge node with control input: " + << merge_node->name(); + return Status::OK(); + } else if (dead_inputs.size() != 1 || num_data_inputs != 2) { + LOG(WARNING) << "Skipping loop optimization for Merge node (" + << merge_node->name() + << ") with unexpected dead_inputs.size() (" + << dead_inputs.size() << " or num_data_inputs" + << num_data_inputs; + return Status::OK(); } - // Turn Merge into Identity only if we deleted the other data input. - if (!dead_inputs.empty()) { - const int num_data_inputs = dead_node->attr().at("N").i(); - CHECK_EQ(num_data_inputs, dead_inputs.size() + 1); - dead_node->set_op("Identity"); - dead_node->mutable_attr()->erase("N"); - } - // Remove control inputs from dead nodes. - int pos = 0; - while (pos < dead_node->input_size()) { - TensorId tensor = ParseTensorName(dead_node->input(pos)); - if (tensor.index() == Graph::kControlSlot && - dead_node_names.contains(tensor.node())) { - auto* inputs = dead_node->mutable_input(); - inputs->SwapElements(pos, dead_node->input_size() - 1); - inputs->RemoveLast(); - } else { - ++pos; - } + } + + // Remove dead inputs from Merge nodes that will not be not + // pruned from the graph. + for (const auto& itr : dead_merge_inputs) { + NodeDef* merge_node = itr.first; + if (dead_nodes.find(merge_node) != dead_nodes.end()) { + // The node will be pruned since all its inputs are dead. + continue; } + VLOG(3) << "Merge node before cleanup: " << merge_node->DebugString(); + // Remove dead data input. + const std::set& dead_inputs = itr.second; + int index = *dead_inputs.begin(); + auto* inputs = merge_node->mutable_input(); + inputs->SwapElements(1, index); + inputs->SwapElements(1, merge_node->input_size() - 1); + inputs->RemoveLast(); + merge_node->set_op("Identity"); + merge_node->mutable_attr()->erase("N"); + + VLOG(3) << "Merge node after cleanup: " << merge_node->DebugString(); } EraseNodesFromGraph(std::move(nodes_idx_to_delete), optimized_graph); diff --git a/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc b/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc index a8bedeed663..f48f5b01a79 100644 --- a/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc +++ b/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc @@ -777,10 +777,6 @@ TEST_F(LoopOptimizerTest, RemoveDeadBranchesConstantCondition) { ops::Merge m3(scope.WithOpName("m3"), {v_in, sqrt1}); ops::Merge m4(scope.WithOpName("m4"), {square1, sqrt2}); ops::Merge m5(scope.WithOpName("m5"), {square2, sqrt1}); - ops::Merge m6(scope.WithOpName("m6").WithControlDependencies(sqrt2), - {v_in, square1}); - ops::Merge m7(scope.WithOpName("m7").WithControlDependencies(sqrt1), - {v_in, square1}); ops::Switch s5(scope.WithOpName("switch5"), v_in, ctrl1); Output id1 = ops::Identity(scope.WithOpName("id1"), s5.output_false); @@ -831,19 +827,6 @@ TEST_F(LoopOptimizerTest, RemoveDeadBranchesConstantCondition) { ASSERT_EQ(node.input_size(), 2); EXPECT_EQ(node.input(0), "square1"); EXPECT_EQ(node.input(1), "sqrt2"); - } else if (node.name() == "m6") { - // both inputs are alive and the control dependency can get triggered - EXPECT_EQ(node.op(), "Merge"); - ASSERT_EQ(node.input_size(), 3); - EXPECT_EQ(node.input(0), "v_in"); - EXPECT_EQ(node.input(1), "square1"); - EXPECT_EQ(node.input(2), "^sqrt2"); - } else if (node.name() == "m7") { - // removed control input from dead sqrt1 - EXPECT_EQ(node.op(), "Merge"); - ASSERT_EQ(node.input_size(), 2); - EXPECT_EQ(node.input(0), "v_in"); - EXPECT_EQ(node.input(1), "square1"); } else if (node.name() == "m8") { // The node is to be preserved because of a fetch EXPECT_EQ(node.op(), "Merge"); @@ -859,11 +842,11 @@ TEST_F(LoopOptimizerTest, RemoveDeadBranchesConstantCondition) { } } - auto tensors_expected = EvaluateNodes(item.graph, {"m7", "m8", "m9"}); - ASSERT_EQ(tensors_expected.size(), 3); + auto tensors_expected = EvaluateNodes(item.graph, {"m8", "m9"}); + ASSERT_EQ(tensors_expected.size(), 2); - auto tensors = EvaluateNodes(output, {"m7", "m8", "m9"}); - ASSERT_EQ(tensors.size(), 3); + auto tensors = EvaluateNodes(output, {"m8", "m9"}); + ASSERT_EQ(tensors.size(), 2); test::ExpectTensorNear(tensors_expected[0], tensors[0], 1e-6); test::ExpectTensorNear(tensors_expected[1], tensors[1], 1e-6); @@ -1098,7 +1081,6 @@ node { op: "Merge" input: "EpisodicReplayBuffer/add/assert_equal/Assert/AssertGuard/control_dependency_1" input: "EpisodicReplayBuffer/add/assert_equal/Assert/AssertGuard/control_dependency" - input: "^EpisodicReplayBuffer/add/assert_equal/Assert/AssertGuard/Assert" device: "/job:localhost/replica:0/task:0/device:CPU:0" attr { key: "N" diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer.cc b/tensorflow/core/grappler/optimizers/meta_optimizer.cc index c924f8b52b1..e0d4663f5fb 100644 --- a/tensorflow/core/grappler/optimizers/meta_optimizer.cc +++ b/tensorflow/core/grappler/optimizers/meta_optimizer.cc @@ -101,6 +101,7 @@ FunctionDefLibrary GetFunctionDefLibraryStub( *(fn_stub->mutable_signature()) = fn.signature(); *(fn_stub->mutable_attr()) = fn.attr(); *(fn_stub->mutable_arg_attr()) = fn.arg_attr(); + *(fn_stub->mutable_resource_arg_unique_id()) = fn.resource_arg_unique_id(); } *stub.mutable_gradient() = fdef_lib.gradient(); return stub; @@ -221,7 +222,7 @@ Status MetaOptimizer::InitializeOptimizers( if (cfg_.function_optimization() != RewriterConfig::OFF) { optimizers->push_back(MakeUnique( cfg_.function_optimization(), - /*lower_contorl_flow=*/!IsSingleThreadedExecutor())); + /*lower_control_flow=*/!IsSingleThreadedExecutor())); } if (cfg_.debug_stripper() == RewriterConfig::ON) { optimizers->push_back(MakeUnique()); diff --git a/tensorflow/core/grappler/utils.cc b/tensorflow/core/grappler/utils.cc index 59307a218db..9213b37ff29 100644 --- a/tensorflow/core/grappler/utils.cc +++ b/tensorflow/core/grappler/utils.cc @@ -277,10 +277,22 @@ bool HasRegularInputs(const NodeDef& node) { } int NumNonControlInputs(const NodeDef& node) { - int num_inputs = node.input_size(); - for (const string& input : node.input()) { + int num_inputs = 0; + for (; num_inputs < node.input_size(); ++num_inputs) { + const string& input = node.input(num_inputs); if (IsControlInput(input)) { - --num_inputs; + return num_inputs; + } + } + return num_inputs; +} + +int NumControlInputs(const NodeDef& node) { + int num_inputs = 0; + for (; num_inputs < node.input_size(); ++num_inputs) { + const string& input = node.input(node.input_size() - num_inputs - 1); + if (!IsControlInput(input)) { + return num_inputs; } } return num_inputs; @@ -302,8 +314,9 @@ bool HasRegularOutputs(const NodeDef& node, const NodeMap& node_map) { bool HasControlOutputs(const NodeDef& node, const NodeMap& node_map) { for (const NodeDef* output : node_map.GetOutputs(node.name())) { - for (const string& node_as_input : output->input()) { - if (!IsControlInput(node_as_input)) continue; + for (int idx = output->input_size() - 1; idx >= 0; --idx) { + const string& node_as_input = output->input(idx); + if (!IsControlInput(node_as_input)) break; TensorId tensor = ParseTensorName(node_as_input); if (tensor.node() == node.name()) { @@ -317,8 +330,9 @@ bool HasControlOutputs(const NodeDef& node, const NodeMap& node_map) { int NumControlOutputs(const NodeDef& node, const NodeMap& node_map) { int num_outputs = 0; for (const NodeDef* output : node_map.GetOutputs(node.name())) { - for (const string& node_as_input : output->input()) { - if (!IsControlInput(node_as_input)) continue; + for (int idx = output->input_size() - 1; idx >= 0; --idx) { + const string& node_as_input = output->input(idx); + if (!IsControlInput(node_as_input)) break; TensorId tensor = ParseTensorName(node_as_input); if (tensor.node() == node.name()) { diff --git a/tensorflow/core/grappler/utils.h b/tensorflow/core/grappler/utils.h index 7a1b65e1729..87835245762 100644 --- a/tensorflow/core/grappler/utils.h +++ b/tensorflow/core/grappler/utils.h @@ -221,6 +221,9 @@ bool HasRegularOutputs(const NodeDef& node, const NodeMap& node_map); // Returns true iff the node has at least one control output. bool HasControlOutputs(const NodeDef& node, const NodeMap& node_map); +// Number of connected control inputs. +int NumControlInputs(const NodeDef& node); + // Number of connected non-control inputs. int NumNonControlInputs(const NodeDef& node); diff --git a/tensorflow/core/grappler/utils/topological_sort.cc b/tensorflow/core/grappler/utils/topological_sort.cc index a6d0f5037bb..e24a457593a 100644 --- a/tensorflow/core/grappler/utils/topological_sort.cc +++ b/tensorflow/core/grappler/utils/topological_sort.cc @@ -90,6 +90,16 @@ Status ComputeTopologicalOrder( } if (back != graph_view.num_nodes()) { + if (VLOG_IS_ON(1)) { + VLOG(1) << "The graph couldn't be sorted in topological order. Stalled " + "at node = " + << graph.node(back).DebugString(); + for (int i = 0; i < graph_view.num_nodes(); ++i) { + if (num_ready_inputs[i] != graph_view.GetFanin(i).size()) { + VLOG(1) << "Node not ready: " << graph.node(i).DebugString(); + } + } + } return errors::InvalidArgument( "The graph couldn't be sorted in topological order."); } diff --git a/tensorflow/core/grappler/utils_test.cc b/tensorflow/core/grappler/utils_test.cc index 777630ff98c..7e3d4d90dcd 100644 --- a/tensorflow/core/grappler/utils_test.cc +++ b/tensorflow/core/grappler/utils_test.cc @@ -352,14 +352,17 @@ TEST_F(UtilsTest, NumNonControlOutputs) { NodeMap node_map(&graph); const NodeDef* add_node = node_map.GetNode("add"); + const NodeDef* mul_node = node_map.GetNode("mul"); ASSERT_NE(add_node, nullptr); // [a, b] are only non-control inputs EXPECT_EQ(NumNonControlInputs(*add_node), 2); + EXPECT_EQ(NumControlInputs(*add_node), 1); // [sqrt, shape] are non control outputs EXPECT_EQ(NumNonControlOutputs(*add_node, node_map), 2); // sqrt is the only data output EXPECT_EQ(NumNonControlDataOutputs(*add_node, node_map), 1); + EXPECT_EQ(NumControlInputs(*mul_node), 0); EXPECT_TRUE(HasControlInputs(*add_node)); EXPECT_TRUE(HasRegularInputs(*add_node)); diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD index 50283e79e69..4658230ebfb 100644 --- a/tensorflow/core/kernels/BUILD +++ b/tensorflow/core/kernels/BUILD @@ -21,7 +21,7 @@ load("//tensorflow:tensorflow.bzl", "if_nccl") load("//tensorflow:tensorflow.bzl", "tf_cuda_cc_test") load("//tensorflow:tensorflow.bzl", "tf_cuda_cc_tests") load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load( @@ -65,7 +65,7 @@ package_group( name = "friends", packages = [ "//learning/brain/contrib/...", - "//learning/brain/research/sparse_matrix/...", + "//learning/brain/research/...", "//learning/faster_training/...", "//tensorflow/...", "//tensorflow_text/...", @@ -1097,8 +1097,8 @@ tf_cc_test( name = "fingerprint_op_test", size = "small", srcs = ["fingerprint_op_test.cc"], - kernels = [":fingerprint_op"], deps = [ + ":fingerprint_op", ":ops_testutil", "//tensorflow/core:framework", "//tensorflow/core:lib", @@ -2614,6 +2614,7 @@ LOOKUP_DEPS = [ ":bounds_check", ":initializable_lookup_table", ":lookup_util", + "@com_google_absl//absl/container:flat_hash_map", "//tensorflow/core:core_cpu", "//tensorflow/core:framework", "//tensorflow/core:lib", diff --git a/tensorflow/core/kernels/conv_ops_benchmark_test.cc b/tensorflow/core/kernels/conv_ops_benchmark_test.cc index 2f99414f5df..339c8e2dda6 100644 --- a/tensorflow/core/kernels/conv_ops_benchmark_test.cc +++ b/tensorflow/core/kernels/conv_ops_benchmark_test.cc @@ -574,7 +574,7 @@ using fp32 = float; using fp16 = Eigen::half; // ResNet50-ish convolutions. -#define BENCHMARK_DTYPE(DATA_FORMAT, BATCH, T) \ +#define BENCHMARK_RESNET50(DATA_FORMAT, BATCH, T) \ BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 56, 56, 64, 1, 1, 64, gpu); \ BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 56, 56, 64, 1, 1, 256, gpu); \ BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 56, 56, 256, 1, 1, 64, gpu); \ @@ -588,7 +588,30 @@ using fp16 = Eigen::half; BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 14, 14, 256, 1, 1, 256, gpu); \ BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 14, 14, 256, 1, 1, 1024, gpu); \ BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 14, 14, 1024, 1, 1, 256, gpu); \ - BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 14, 14, 256, 3, 3, 256, gpu); + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 14, 14, 256, 3, 3, 256, gpu) + +// NASnet-ish convolutions (Tensorflow models: slim/nets/nasnet). +#define BENCHMARK_NASNET(DATA_FORMAT, BATCH, T) \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 165, 165, 96, 1, 1, 96, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 83, 83, 84, 1, 1, 84, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 42, 42, 336, 1, 1, 336, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 42, 42, 168, 1, 1, 168, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 21, 21, 1008, 1, 1, 1008, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 21, 21, 336, 1, 1, 336, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 11, 11, 672, 1, 1, 672, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 21, 21, 2016, 1, 1, 2016, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 11, 11, 2688, 1, 1, 2688, gpu); \ + BM_Conv2DFmt(T, DATA_FORMAT, BATCH, 11, 11, 4032, 1, 1, 4032, gpu) + +#define BENCHMARK_DTYPE(DATA_FORMAT, BATCH, T) \ + BENCHMARK_RESNET50(DATA_FORMAT, BATCH, T); \ + BENCHMARK_NASNET(DATA_FORMAT, BATCH, T) + +BENCHMARK_DTYPE(NHWC, 16, fp32); +BENCHMARK_DTYPE(NCHW, 16, fp32); + +BENCHMARK_DTYPE(NHWC, 16, fp16); +BENCHMARK_DTYPE(NCHW, 16, fp16); BENCHMARK_DTYPE(NHWC, 32, fp32); BENCHMARK_DTYPE(NCHW, 32, fp32); diff --git a/tensorflow/core/kernels/data/cache_dataset_ops_test.cc b/tensorflow/core/kernels/data/cache_dataset_ops_test.cc index 72dc0ea8d93..59b82c2fb1f 100644 --- a/tensorflow/core/kernels/data/cache_dataset_ops_test.cc +++ b/tensorflow/core/kernels/data/cache_dataset_ops_test.cc @@ -329,11 +329,11 @@ TEST_P(ParameterizedIteratorSaveAndRestoreTest, SaveAndRestore) { int cur_iteration = 0; auto expected_outputs_it = test_case.expected_outputs.begin(); for (int breakpoint : test_case.breakpoints) { - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_EXPECT_OK(iterator_->Save(serialization_ctx.get(), &writer)); - TF_EXPECT_OK(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, test_case.dataset_params.iterator_prefix(), *dataset_, &iterator_)); diff --git a/tensorflow/core/kernels/data/dataset_test_base.cc b/tensorflow/core/kernels/data/dataset_test_base.cc index 4b67918c1d1..2a7061aa5ef 100644 --- a/tensorflow/core/kernels/data/dataset_test_base.cc +++ b/tensorflow/core/kernels/data/dataset_test_base.cc @@ -593,11 +593,11 @@ Status DatasetOpsTestBase::CheckIteratorSaveAndRestore( int cur_iteration = 0; std::vector out_tensors; for (int breakpoint : breakpoints) { - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_EXPECT_OK(iterator->Save(serialization_ctx.get(), &writer)); - TF_RETURN_IF_ERROR(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, iterator_prefix, *dataset_, &iterator)); diff --git a/tensorflow/core/kernels/data/dataset_utils.cc b/tensorflow/core/kernels/data/dataset_utils.cc index 579be7302cc..3a260f3573b 100644 --- a/tensorflow/core/kernels/data/dataset_utils.cc +++ b/tensorflow/core/kernels/data/dataset_utils.cc @@ -399,14 +399,40 @@ Status VerifyShapesCompatible(const std::vector& expected, return Status::OK(); } +namespace { + +// We assume that all keys are of the form :. We extract +// the iterator name by getting rid of everything post the final colon. +Status GetIteratorName(StringPiece key, string* name) { + if (!str_util::StartsWith(key, data::kFullNameRandomHex)) { + return errors::InvalidArgument("Save key: ", key, + " not generated using full_name."); + } + std::vector split_keys = str_util::Split(key, data::kPipe); + if (split_keys.size() != 2) { + return errors::InvalidArgument("Save key: ", key, + " not generated using full_name."); + } + string real_key = split_keys[1]; + const int pos = real_key.rfind(kColon); + *name = real_key.substr(0, pos); + return Status::OK(); +} + +} // namespace + VariantTensorDataReader::VariantTensorDataReader( - const tensorflow::VariantTensorData* data) - : data_(data) { - string metadata; - data_->get_metadata(&metadata); - auto keys = str_util::Split(metadata, kDelimiter, str_util::SkipEmpty()); - for (size_t i = 0; i < keys.size(); ++i) { - map_[keys[i]] = i; + const std::vector& data) { + for (const auto& d : data) { + string metadata; + d->get_metadata(&metadata); + auto keys = str_util::Split(metadata, kDelimiter, str_util::SkipEmpty()); + const string name = keys[0]; + data_[name] = d; + map_[name] = std::map(); + for (size_t i = 1; i < keys.size(); ++i) { + map_[name][keys[i]] = i - 1; + } } } @@ -423,24 +449,32 @@ Status VariantTensorDataReader::ReadTensor(StringPiece key, Tensor* val) { } bool VariantTensorDataReader::Contains(StringPiece key) { - return map_.find(string(key)) != map_.end(); + string name; + if (!GetIteratorName(key, &name).ok()) { + return false; + } + return map_[name].find(string(key)) != map_[name].end(); } template Status VariantTensorDataReader::ReadScalarInternal(StringPiece key, T* val) { - if (map_.find(string(key)) == map_.end()) { + string name; + TF_RETURN_IF_ERROR(GetIteratorName(key, &name)); + if (map_[name].find(string(key)) == map_[name].end()) { return errors::NotFound(key); } - *val = data_->tensors(map_[string(key)]).scalar()(); + *val = data_[name]->tensors(map_[name][string(key)]).scalar()(); return Status::OK(); } Status VariantTensorDataReader::ReadTensorInternal(StringPiece key, Tensor* val) { - if (map_.find(string(key)) == map_.end()) { + string name; + TF_RETURN_IF_ERROR(GetIteratorName(key, &name)); + if (map_[name].find(string(key)) == map_[name].end()) { return errors::NotFound(key); } - *val = data_->tensors(map_[string(key)]); + *val = data_[name]->tensors(map_[name][string(key)]); return Status::OK(); } @@ -458,18 +492,49 @@ Status VariantTensorDataWriter::WriteTensor(StringPiece key, return WriteTensorInternal(key, val); } -Status VariantTensorDataWriter::Flush() { - string metadata; - for (size_t i = 0; i < keys_.size(); ++i) { - strings::StrAppend(&metadata, kDelimiter, keys_[i]); +void VariantTensorDataWriter::MaybeFlush() { + if (is_flushed_) return; + for (auto& keys : keys_) { + const string name = keys.first; + string metadata = name; + for (size_t i = 0; i < keys_[name].size(); ++i) { + strings::StrAppend(&metadata, kDelimiter, keys_[name][i]); + } + data_[name]->set_metadata(metadata); + } + is_flushed_ = true; +} + +void VariantTensorDataWriter::Reset() { + is_flushed_ = false; + data_.clear(); + keys_.clear(); +} + +void VariantTensorDataWriter::ReleaseData( + std::vector>* variants) { + MaybeFlush(); + for (auto& it : data_) { + variants->push_back(std::move(it.second)); + } + Reset(); +} + +void VariantTensorDataWriter::GetData( + std::vector* variants) { + MaybeFlush(); + for (auto& it : data_) { + variants->push_back(it.second.get()); } - data_->set_metadata(metadata); - return Status::OK(); } template Status VariantTensorDataWriter::WriteScalarInternal(StringPiece key, const T& val) { + if (is_flushed_) { + return errors::FailedPrecondition( + "Cannot call WriteScalar after GetData or ReleaseData is called"); + } Tensor val_t = Tensor(DataTypeToEnum::v(), TensorShape({})); val_t.scalar()() = val; return WriteTensorInternal(key, val_t); @@ -477,9 +542,22 @@ Status VariantTensorDataWriter::WriteScalarInternal(StringPiece key, Status VariantTensorDataWriter::WriteTensorInternal(StringPiece key, const Tensor& val) { + if (is_flushed_) { + return errors::FailedPrecondition( + "Cannot call WriteTensor after GetData or ReleaseData is called"); + } + string name; + TF_RETURN_IF_ERROR(GetIteratorName(key, &name)); DCHECK_EQ(key.find(kDelimiter), string::npos); - keys_.push_back(string(key)); - *(data_->add_tensors()) = val; + if (keys_.count(name) == 0) { + keys_[name] = std::vector(); + } + keys_[name].push_back(string(key)); + if (data_.count(name) == 0) { + data_[name] = absl::make_unique(); + data_[name]->set_type_name("tensorflow::Iterator"); + } + *(data_[name]->add_tensors()) = val; return Status::OK(); } diff --git a/tensorflow/core/kernels/data/dataset_utils.h b/tensorflow/core/kernels/data/dataset_utils.h index a2e7bd81b37..82f05e94af7 100644 --- a/tensorflow/core/kernels/data/dataset_utils.h +++ b/tensorflow/core/kernels/data/dataset_utils.h @@ -162,12 +162,12 @@ Status HashTensor(const Tensor& tensor, uint64* hash); // the same between TensorFlow builds. Status HashGraph(const GraphDef& graph, uint64* hash); -// Helper class for reading data from a VariantTensorData object. +// Helper class for reading data from a vector of VariantTensorData objects. class VariantTensorDataReader : public IteratorStateReader { public: - explicit VariantTensorDataReader(const VariantTensorData* data); + explicit VariantTensorDataReader( + const std::vector& data); - // Returns OK iff the initialization was successful. Status ReadScalar(StringPiece key, int64* val) override; Status ReadScalar(StringPiece key, tstring* val) override; Status ReadTensor(StringPiece key, Tensor* val) override; @@ -178,29 +178,44 @@ class VariantTensorDataReader : public IteratorStateReader { Status ReadScalarInternal(StringPiece key, T* val); Status ReadTensorInternal(StringPiece key, Tensor* val); - std::map map_; - const VariantTensorData* data_; // Not owned. + std::map> map_; + std::map data_; // Not owned. }; -// Helper class for writing data to a VariantTensorData object. +// Helper class used to build a list of VariantTensorData objects, one for each +// iterator which is determined from the key supplied from the Write* calls. +// Sample usage: +// VariantTensorDataWriter writer; +// writer.WriteScalar(full_name("buffer_size"), buffer_.size()); +// writer.WriteScalar(full_name("num_threads"), threadpool_.size()); +// .... +// std::vector> variants; +// writer.ReleaseData(&variants); +// Now the VariantTensorData objects can be used to serialize. class VariantTensorDataWriter : public IteratorStateWriter { public: - // Does not take ownership of data. - explicit VariantTensorDataWriter(VariantTensorData* data) : data_(data) {} Status WriteScalar(StringPiece key, const int64 val) override; Status WriteScalar(StringPiece key, const tstring& val) override; Status WriteTensor(StringPiece key, const Tensor& val) override; - // Writes the metadata to `data_`. - Status Flush(); + // Releases the built VariantTensorData's to `variants`. Clears out all + // class state. + void ReleaseData(std::vector>* variants); + + // Obtains a read-only version of the VariantTensorData's built. + void GetData(std::vector* variants); private: + void MaybeFlush(); + void Reset(); + template Status WriteScalarInternal(StringPiece key, const T& val); Status WriteTensorInternal(StringPiece key, const Tensor& val); - VariantTensorData* data_; - std::vector keys_; + bool is_flushed_ = false; + std::map> data_; + std::map> keys_; }; // Adds the functions in `to_add` to `base`. If a function with a matching diff --git a/tensorflow/core/kernels/data/dataset_utils_test.cc b/tensorflow/core/kernels/data/dataset_utils_test.cc index f016f72be6b..b8de8559e21 100644 --- a/tensorflow/core/kernels/data/dataset_utils_test.cc +++ b/tensorflow/core/kernels/data/dataset_utils_test.cc @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/core/framework/variant.h" #include "tensorflow/core/lib/core/status_test_util.h" #include "tensorflow/core/platform/test.h" +#include "tensorflow/core/protobuf/error_codes.pb.h" #include "tensorflow/core/util/work_sharder.h" namespace tensorflow { @@ -49,21 +50,25 @@ class DatasetHashUtilsTest : public ::testing::Test { } }; +string full_name(string key) { + return strings::StrCat(kFullNameRandomHex, kPipe, "Iterator:", key); +} + TEST(DatasetUtilsTest, VariantTensorDataRoundtrip) { - VariantTensorData data; - VariantTensorDataWriter writer(&data); - TF_ASSERT_OK(writer.WriteScalar("Int64", 24)); + VariantTensorDataWriter writer; + TF_ASSERT_OK(writer.WriteScalar(full_name("Int64"), 24)); Tensor input_tensor(DT_FLOAT, {1}); input_tensor.flat()(0) = 2.0f; - TF_ASSERT_OK(writer.WriteTensor("Tensor", input_tensor)); - TF_ASSERT_OK(writer.Flush()); + TF_ASSERT_OK(writer.WriteTensor(full_name("Tensor"), input_tensor)); + std::vector data; + writer.GetData(&data); - VariantTensorDataReader reader(&data); + VariantTensorDataReader reader(data); int64 val_int64; - TF_ASSERT_OK(reader.ReadScalar("Int64", &val_int64)); + TF_ASSERT_OK(reader.ReadScalar(full_name("Int64"), &val_int64)); EXPECT_EQ(val_int64, 24); Tensor val_tensor; - TF_ASSERT_OK(reader.ReadTensor("Tensor", &val_tensor)); + TF_ASSERT_OK(reader.ReadTensor(full_name("Tensor"), &val_tensor)); EXPECT_EQ(input_tensor.NumElements(), val_tensor.NumElements()); EXPECT_EQ(input_tensor.flat()(0), val_tensor.flat()(0)); } @@ -72,16 +77,29 @@ TEST(DatasetUtilsTest, VariantTensorDataNonExistentKey) { VariantTensorData data; strings::StrAppend(&data.metadata_, "key1", "@@"); data.tensors_.push_back(Tensor(DT_INT64, {1})); - VariantTensorDataReader reader(&data); + std::vector reader_data; + reader_data.push_back(&data); + VariantTensorDataReader reader(reader_data); int64 val_int64; tstring val_string; Tensor val_tensor; EXPECT_EQ(error::NOT_FOUND, - reader.ReadScalar("NonExistentKey", &val_int64).code()); + reader.ReadScalar(full_name("NonExistentKey"), &val_int64).code()); EXPECT_EQ(error::NOT_FOUND, - reader.ReadScalar("NonExistentKey", &val_string).code()); + reader.ReadScalar(full_name("NonExistentKey"), &val_string).code()); EXPECT_EQ(error::NOT_FOUND, - reader.ReadTensor("NonExistentKey", &val_tensor).code()); + reader.ReadTensor(full_name("NonExistentKey"), &val_tensor).code()); +} + +TEST(DatasetUtilsTest, VariantTensorDataWriteAfterFlushing) { + VariantTensorDataWriter writer; + TF_ASSERT_OK(writer.WriteScalar(full_name("Int64"), 24)); + std::vector data; + writer.GetData(&data); + Tensor input_tensor(DT_FLOAT, {1}); + input_tensor.flat()(0) = 2.0f; + EXPECT_EQ(error::FAILED_PRECONDITION, + writer.WriteTensor(full_name("Tensor"), input_tensor).code()); } TEST(DatasetUtilsTest, AddToFunctionLibrary) { diff --git a/tensorflow/core/kernels/data/experimental/parallel_interleave_dataset_op.cc b/tensorflow/core/kernels/data/experimental/parallel_interleave_dataset_op.cc index 5c5d91d4603..3e99a6f5713 100644 --- a/tensorflow/core/kernels/data/experimental/parallel_interleave_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/parallel_interleave_dataset_op.cc @@ -821,7 +821,7 @@ class ParallelInterleaveDatasetOp::Dataset : public DatasetBase { for (int i = 0; i < workers_[index].outputs.size(); ++i) { TF_RETURN_IF_ERROR(WriteOutputElemLocked( writer, workers_[index].outputs[i], - full_name(strings::StrCat(prefix, "_", kOutputs, "_", i)))); + strings::StrCat(prefix, "_", kOutputs, "_", i))); } if (workers_[index].is_producing) { TF_RETURN_IF_ERROR(writer->WriteScalar( @@ -854,7 +854,7 @@ class ParallelInterleaveDatasetOp::Dataset : public DatasetBase { workers_[index].outputs.emplace_back(Status::OK()); TF_RETURN_IF_ERROR(ReadOutputElemLocked( reader, &workers_[index].outputs.back(), - full_name(strings::StrCat(worker_prefix, "_", kOutputs, "_", i)))); + strings::StrCat(worker_prefix, "_", kOutputs, "_", i))); } if (reader->Contains( full_name(strings::StrCat(worker_prefix, "_", kIsProducing)))) { @@ -888,7 +888,7 @@ class ParallelInterleaveDatasetOp::Dataset : public DatasetBase { worker_thread_states_[index].iterator_creation_status)); TF_RETURN_IF_ERROR(WriteOutputElemLocked( writer, worker_thread_states_[index].output_elem, - full_name(strings::StrCat(prefix, "_", kOutput)))); + strings::StrCat(prefix, "_", kOutput))); if (worker_thread_states_[index].end_of_sequence) { TF_RETURN_IF_ERROR(writer->WriteScalar( full_name(strings::StrCat(prefix, "_", kEndOfSequence)), "")); @@ -929,7 +929,7 @@ class ParallelInterleaveDatasetOp::Dataset : public DatasetBase { &worker_thread_states_[index].iterator_creation_status)); TF_RETURN_IF_ERROR(ReadOutputElemLocked( reader, &worker_thread_states_[index].output_elem, - full_name(strings::StrCat(worker_prefix, "_", kOutput)))); + strings::StrCat(worker_prefix, "_", kOutput))); if (reader->Contains( full_name(strings::StrCat(worker_prefix, "_", kEndOfSequence)))) { worker_thread_states_[index].end_of_sequence = true; @@ -945,13 +945,13 @@ class ParallelInterleaveDatasetOp::Dataset : public DatasetBase { EXCLUSIVE_LOCKS_REQUIRED(mu_, ckpt_mu_) { TF_RETURN_IF_ERROR(WriteStatusLocked( writer, strings::StrCat(prefix, "_", kStatus), output_elem.status)); - TF_RETURN_IF_ERROR( - writer->WriteScalar(strings::StrCat(prefix, "_", kOutputSize), - output_elem.output.size())); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(prefix, "_", kOutputSize)), + output_elem.output.size())); for (int i = 0; i < output_elem.output.size(); ++i) { - TF_RETURN_IF_ERROR( - writer->WriteTensor(strings::StrCat(prefix, "_", kOutput, "_", i), - output_elem.output[i])); + TF_RETURN_IF_ERROR(writer->WriteTensor( + full_name(strings::StrCat(prefix, "_", kOutput, "_", i)), + output_elem.output[i])); } return Status::OK(); } @@ -963,13 +963,13 @@ class ParallelInterleaveDatasetOp::Dataset : public DatasetBase { reader, strings::StrCat(prefix, "_", kStatus), &output_elem->status)); int64 output_size; TF_RETURN_IF_ERROR(reader->ReadScalar( - strings::StrCat(prefix, "_", kOutputSize), &output_size)); + full_name(strings::StrCat(prefix, "_", kOutputSize)), &output_size)); output_elem->output.reserve(output_size); for (int i = 0; i < output_size; ++i) { output_elem->output.emplace_back(); - TF_RETURN_IF_ERROR( - reader->ReadTensor(strings::StrCat(prefix, "_", kOutput, "_", i), - &output_elem->output.back())); + TF_RETURN_IF_ERROR(reader->ReadTensor( + full_name(strings::StrCat(prefix, "_", kOutput, "_", i)), + &output_elem->output.back())); } return Status::OK(); } diff --git a/tensorflow/core/kernels/data/experimental/snapshot_dataset_op.cc b/tensorflow/core/kernels/data/experimental/snapshot_dataset_op.cc index de1b1bfd803..05f1b5f3426 100644 --- a/tensorflow/core/kernels/data/experimental/snapshot_dataset_op.cc +++ b/tensorflow/core/kernels/data/experimental/snapshot_dataset_op.cc @@ -81,6 +81,25 @@ constexpr char kSnapshotReadThroughput[] = "snapshot_read_throughput"; constexpr char kSnapshotWrittenElements[] = "snapshot_written_elements"; constexpr char kSnapshotWriteThroughput[] = "snapshot_write_throughput"; +constexpr char kSizeSuffix[] = "_size"; +constexpr char kState[] = "state"; +constexpr char kHashDir[] = "hash_dir"; +constexpr char kRunId[] = "run_id"; +constexpr char kRunDir[] = "run_dir"; +constexpr char kFilenames[] = "filenames"; +constexpr char kCurrentFilenames[] = "current_filenames"; +constexpr char kElementsProduced[] = "elements_produced"; +constexpr char kNextFileIndex[] = "next_file_index"; +constexpr char kNumFilesDone[] = "num_files_done"; +constexpr char kNumElementsRead[] = "num_elements_read"; +constexpr char kStatus[] = "status"; +constexpr char kCode[] = ".code"; +constexpr char kErrorMessage[] = ".error_message"; +constexpr char kEndOfSequence[] = "end_of_sequence"; +constexpr char kBuffer[] = "buffer"; +constexpr char kNumElementsWritten[] = "num_elements_written"; +constexpr char kNextElem[] = "next_elem"; + class SnapshotWriter { public: static constexpr const char* const kClassName = "SnapshotWriter"; @@ -538,12 +557,19 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { class Iterator : public DatasetIterator { public: explicit Iterator(const Params& params) - : DatasetIterator(params) {} - - Status Initialize(IteratorContext* ctx) override { - mutex_lock l(mu_); - // TODO(dero): remove NOLINT after USE_TSTRING is enabled. + : DatasetIterator(params) { hash_dir_ = io::JoinPath(dataset()->dir_, dataset()->graph_hash_); + } + + // We have a somewhat non traditional pattern for iterator initialization + // for Snapshot. The protocol is that we initialize the Reader / Writer + // iterator on the first GetNext call. We also invoke the same + // initialization code when restoring as well. The reason why we don't do + // this during the Initialize call is because during Restore we call + // Initialize at first and at that point we don't know which iterator + // (Reader / Writer / Passthrough) we need to restore as this info is part + // of the checkpoint. + Status Initialize(IteratorContext* ctx) override { return Status::OK(); } @@ -555,50 +581,74 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { experimental::SnapshotMetadataRecord metadata; Status s = ReadMetadataFile(hash_dir_, &metadata); TF_RETURN_IF_ERROR(DetermineOpState( - ReadMetadataFile(hash_dir_, &metadata), metadata, - dataset()->pending_snapshot_expiry_seconds_, &state_)); - - LOG(INFO) << "Op state: " << state_ - << " with metadata: " << metadata.DebugString(); - - switch (state_) { - case WRITER: - iterator_ = absl::make_unique( - SnapshotWriterIterator::Params{ - dataset(), absl::StrCat(prefix(), "WriterImpl")}, - hash_dir_); - break; - case READER: - iterator_ = absl::make_unique( - SnapshotReaderIterator::Params{ - dataset(), absl::StrCat(prefix(), "ReaderImpl")}, - hash_dir_, metadata); - break; - case PASSTHROUGH: - iterator_ = absl::make_unique( - SnapshotPassthroughIterator::Params{ - dataset(), absl::StrCat(prefix(), "PassthroughImpl")}); - break; - } - TF_RETURN_IF_ERROR(iterator_->Initialize(ctx)); + s, metadata, dataset()->pending_snapshot_expiry_seconds_, + &state_)); + TF_RETURN_IF_ERROR(InitializeIterator(ctx, metadata)); } - return iterator_->GetNext(ctx, out_tensors, end_of_sequence); } protected: Status SaveInternal(IteratorStateWriter* writer) override { - // TODO(frankchn): Make save iterators work + mutex_lock l(mu_); + TF_RETURN_IF_ERROR(SaveInput(writer, iterator_)); + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(kState), static_cast(state_))); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kHashDir), hash_dir_)); return Status::OK(); } Status RestoreInternal(IteratorContext* ctx, IteratorStateReader* reader) override { - // TODO(frankchn): Make iterator restores work - return Status::OK(); + mutex_lock l(mu_); + tstring hash_dir; + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kHashDir), &hash_dir)); + if (hash_dir != hash_dir_) { + LOG(ERROR) << "Dataset has changed while restoring from the " + "checkpoint. Old hash: " + << hash_dir << "; new hash: " << hash_dir_; + return Status::OK(); + } + { + int64 temp; + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kState), &temp)); + state_ = SnapshotMode(temp); + } + experimental::SnapshotMetadataRecord metadata; + TF_RETURN_IF_ERROR(ReadMetadataFile(hash_dir_, &metadata)); + TF_RETURN_IF_ERROR(InitializeIterator(ctx, metadata)); + return RestoreInput(ctx, reader, iterator_); } - private: + // This method expects that state_ is populated and it will create the + // correct Reader / Writer / Passthrough iterator and initialize it. + Status InitializeIterator( + IteratorContext* ctx, + const experimental::SnapshotMetadataRecord& metadata) + EXCLUSIVE_LOCKS_REQUIRED(mu_) { + switch (state_) { + case WRITER: + iterator_ = absl::make_unique( + SnapshotWriterIterator::Params{ + dataset(), absl::StrCat(prefix(), "WriterImpl")}, + hash_dir_); + break; + case READER: + iterator_ = absl::make_unique( + SnapshotReaderIterator::Params{ + dataset(), absl::StrCat(prefix(), "ReaderImpl")}, + hash_dir_, metadata); + break; + case PASSTHROUGH: + iterator_ = absl::make_unique( + SnapshotPassthroughIterator::Params{ + dataset(), absl::StrCat(prefix(), "PassthroughImpl")}); + break; + } + return iterator_->Initialize(ctx); + } + + protected: class SnapshotReaderIterator : public DatasetIterator { public: static constexpr const char* const kParse = "Parse"; @@ -626,8 +676,12 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { run_id_ = metadata_.run_id(); run_dir_ = io::JoinPath(hash_dir_, run_id_); // Get all the files in the run_dir. + std::vector filenames_str; TF_RETURN_IF_ERROR(ctx->env()->GetMatchingPaths( - absl::StrCat(run_dir_, "/*"), &filenames_)); + absl::StrCat(run_dir_, "/*"), &filenames_str)); + filenames_.resize(filenames_str.size()); + std::copy(filenames_str.begin(), filenames_str.end(), + filenames_.begin()); if (filenames_.empty()) { return errors::InvalidArgument("Could not find any files in dir: ", run_dir_); @@ -644,6 +698,10 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { } else { std::sort(filenames_.begin(), filenames_.end()); } + + for (auto i = 0; i < dataset()->num_reader_threads_; ++i) { + curr_filenames_.push_back(GetNextFilename()); + } return Status::OK(); } @@ -655,7 +713,7 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { if (!background_threads_started_) { for (int i = 0; i < dataset()->num_reader_threads_; ++i) { ++num_active_threads_; - thread_pool_->Schedule([this]() { ReadingFilesLoop(); }); + thread_pool_->Schedule([this, i]() { ReadingFilesLoop(i); }); } background_threads_started_ = true; } @@ -726,6 +784,100 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { return errors::Internal("Unreachable point in SnapshotReader"); } + protected: + Status SaveInternal(IteratorStateWriter* writer) override { + mutex_lock l(mu_); + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(kHashDir), hash_dir_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kRunId), run_id_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kRunDir), run_dir_)); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(kFilenames, kSizeSuffix)), + filenames_.size())); + for (size_t i = 0; i < filenames_.size(); ++i) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(kFilenames, "[", i, "]")), + filenames_[i])); + } + for (auto i = 0; i < dataset()->num_reader_threads_; ++i) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(kCurrentFilenames, "[", i, "]")), + curr_filenames_[i])); + } + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kElementsProduced), + elements_produced_)); + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(kNextFileIndex), next_file_index_)); + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(kNumFilesDone), num_files_done_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kNumElementsRead), + num_elements_read_)); + return Status::OK(); + } + + Status RestoreInternal(IteratorContext* ctx, + IteratorStateReader* reader) override { + mutex_lock l(mu_); + tstring hash_dir, run_id, run_dir; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name(kHashDir), &hash_dir)); + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kHashDir), &run_id)); + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kHashDir), &run_dir)); + if (run_dir != run_dir_) { + LOG(ERROR) << "Restoring read iterator from ckpt with old " + << "run_dir: " << run_dir + << " but new run_dir is: " << run_dir_ + << ". We'll now restart snapshot creation."; + return Status::OK(); + } + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kRunId), &run_id_)); + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kRunDir), &run_dir_)); + curr_filenames_.clear(); + curr_filenames_.reserve(dataset()->num_reader_threads_); + for (auto i = 0; i < dataset()->num_reader_threads_; ++i) { + curr_filenames_.emplace_back(); + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(kCurrentFilenames, "[", i, "]")), + &curr_filenames_.back())); + } + size_t filenames_size; + { + int64 temp; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(kFilenames, kSizeSuffix)), &temp)); + filenames_size = static_cast(temp); + } + if (filenames_.size() != filenames_size) { + LOG(ERROR) << "Old filenames size: " << filenames_size + << "; new filenames size: " << filenames_.size(); + } + filenames_.clear(); + filenames_.reserve(filenames_size); + for (size_t i = 0; i < filenames_size; ++i) { + filenames_.emplace_back(); + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(kFilenames, "[", i, "]")), + &filenames_.back())); + } + { + int64 temp; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name(kElementsProduced), &temp)); + elements_produced_ = static_cast(temp); + } + { + int64 temp; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name(kNextFileIndex), &temp)); + next_file_index_ = static_cast(temp); + } + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name(kNumFilesDone), &num_files_done_)); + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kNumElementsRead), + &num_elements_read_)); + return Status::OK(); + } + private: // Reads one file end to end. Status ReadFile(const string& filename) { @@ -790,9 +942,19 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { return Status::OK(); } + string GetNextFilename() EXCLUSIVE_LOCKS_REQUIRED(mu_) { + if (next_file_index_ >= filenames_.size()) { + return ""; + } + string filename = io::JoinPath(dataset()->reader_path_prefix_, + filenames_[next_file_index_]); + next_file_index_++; + return filename; + } + // Pulls one file off the filenames_ list and reads it through. When // all files are read, terminates. - void ReadingFilesLoop() { + void ReadingFilesLoop(int i) { auto cleanup = gtl::MakeCleanup([this]() { mutex_lock l(mu_); --num_active_threads_; @@ -802,13 +964,11 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { string filename = ""; { mutex_lock l(mu_); - if (next_file_index_ >= filenames_.size()) { + filename = curr_filenames_[i]; + if (filename.empty()) { return; } - filename = io::JoinPath(dataset()->reader_path_prefix_, - filenames_[next_file_index_]); VLOG(2) << "Starting to read: " << filename; - next_file_index_++; } Status s = ReadFile(filename); // If we get to the end of the file, it's a clean termination and @@ -824,6 +984,7 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { cond_var_.notify_all(); return; } + curr_filenames_[i] = GetNextFilename(); } else { LOG(ERROR) << "Encountered an error: " << s.ToString(); BufferElement elem; @@ -836,6 +997,43 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { } } + Status WriteStatus(IteratorStateWriter* writer, size_t index, + const Status& status) EXCLUSIVE_LOCKS_REQUIRED(mu_) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + CodeKey(index), static_cast(status.code()))); + if (!status.ok()) { + TF_RETURN_IF_ERROR(writer->WriteScalar(ErrorMessageKey(index), + status.error_message())); + } + return Status::OK(); + } + + Status ReadStatus(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(code_int); + + if (code != error::Code::OK) { + tstring 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(kStatus, "[", index, "]", kCode)); + } + + string ErrorMessageKey(size_t index) { + return full_name( + strings::StrCat(kStatus, "[", index, "]", kErrorMessage)); + } + struct BufferElement { Status status; std::vector value; @@ -846,9 +1044,9 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { const string hash_dir_; const experimental::SnapshotMetadataRecord metadata_; - string run_id_ GUARDED_BY(mu_); - string run_dir_ GUARDED_BY(mu_); - std::vector filenames_; + tstring run_id_ GUARDED_BY(mu_); + tstring run_dir_ GUARDED_BY(mu_); + std::vector filenames_; uint64 elements_produced_ GUARDED_BY(mu_) = 0; int64 time_spent_micros_ GUARDED_BY(mu_) = 0; @@ -862,7 +1060,9 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { bool cancelled_ GUARDED_BY(mu_) = false; bool background_threads_started_ GUARDED_BY(mu_) = false; bool background_threads_finished_ GUARDED_BY(mu_) = false; - int64 num_elements_read_ = 0; + int64 num_elements_read_ GUARDED_BY(mu_) = 0; + // curr_filenames_ tracks which file is being read by each thread. + std::vector curr_filenames_ GUARDED_BY(mu_); }; class SnapshotWriterIterator : public DatasetIterator { @@ -891,15 +1091,6 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { strings::Hex(random::New64(), strings::kZeroPad4)); run_dir_ = io::JoinPath(dataset()->writer_path_prefix_, hash_dir_, run_id_); - TF_RETURN_IF_ERROR(Env::Default()->RecursivelyCreateDir(run_dir_)); - - experimental::SnapshotMetadataRecord metadata; - metadata.set_creation_timestamp(EnvTime::NowMicros()); - metadata.set_graph_hash(dataset()->graph_hash_); - metadata.set_run_id(run_id_); - metadata.set_finalized(false); - TF_RETURN_IF_ERROR(WriteMetadataFile(hash_dir_, metadata)); - return dataset()->input_->MakeIterator(ctx, prefix(), &input_impl_); } @@ -909,10 +1100,24 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { absl::Time start = absl::Now(); bool first_call; + bool is_restored; { mutex_lock l(mu_); first_call = first_call_; + is_restored = is_restored_; if (first_call_) { + // If we're restoring then the directory already exists and we + // don't want to overwrite the snapshot metadata file. + if (!is_restored_) { + TF_RETURN_IF_ERROR( + Env::Default()->RecursivelyCreateDir(run_dir_)); + experimental::SnapshotMetadataRecord metadata; + metadata.set_creation_timestamp(EnvTime::NowMicros()); + metadata.set_graph_hash(dataset()->graph_hash_); + metadata.set_run_id(run_id_.data(), run_id_.size()); + metadata.set_finalized(false); + TF_RETURN_IF_ERROR(WriteMetadataFile(hash_dir_, metadata)); + } for (int i = 0; i < dataset()->num_writer_threads_; ++i) { ++num_active_threads_; thread_pool_->Schedule([this]() { WriterThread(); }); @@ -928,7 +1133,7 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { // dataset). So right now we solve this issue by prefetching the next // element in the data stream. Therefore the first call ends up // pulling two elements. - if (first_call) { + if (first_call && !is_restored) { TF_RETURN_IF_ERROR(FillBuffer(ctx)); } @@ -985,6 +1190,151 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { return Status::OK(); } + protected: + Status SaveInternal(IteratorStateWriter* writer) override { + mutex_lock l(mu_); + TF_RETURN_IF_ERROR(SaveInput(writer, input_impl_)); + if (end_of_sequence_) { + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(kEndOfSequence), "")); + } + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(kHashDir), hash_dir_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kRunId), run_id_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kRunDir), run_dir_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kElementsProduced), + elements_produced_)); + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(kBuffer, kSizeSuffix)), + buffer_.size())); + for (size_t i = 0; i < buffer_.size(); ++i) { + auto& buffer_element = buffer_[i]; + if (buffer_element.end_of_sequence) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name( + strings::StrCat(kBuffer, "[", i, "].", kEndOfSequence)), + "")); + } + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(kBuffer, "[", i, "]", kSizeSuffix)), + buffer_element.value.size())); + for (size_t j = 0; j < buffer_element.value.size(); j++) { + TF_RETURN_IF_ERROR(writer->WriteTensor( + full_name(strings::StrCat(kBuffer, "[", i, "][", j, "]")), + buffer_element.value[j])); + } + } + TF_RETURN_IF_ERROR( + writer->WriteScalar(full_name(kNextFileIndex), next_file_index_)); + TF_RETURN_IF_ERROR(writer->WriteScalar(full_name(kNumElementsWritten), + num_elements_written_)); + if (next_elem_.end_of_sequence) { + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(kNextElem, ".", kEndOfSequence)), + "")); + } + TF_RETURN_IF_ERROR(writer->WriteScalar( + full_name(strings::StrCat(kNextElem, kSizeSuffix)), + next_elem_.value.size())); + for (size_t i = 0; i < next_elem_.value.size(); i++) { + TF_RETURN_IF_ERROR(writer->WriteTensor( + full_name(strings::StrCat(kNextElem, "[", i, "]")), + next_elem_.value[i])); + } + return Status::OK(); + } + + Status RestoreInternal(IteratorContext* ctx, + IteratorStateReader* reader) override { + mutex_lock l(mu_); + buffer_.clear(); + TF_RETURN_IF_ERROR(RestoreInput(ctx, reader, input_impl_)); + tstring hash_dir; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name(kHashDir), &hash_dir)); + // If the hash dir has changed then we restart writing. + if (hash_dir != hash_dir_) { + LOG(INFO) << "Old hash dir from ckpt: " << hash_dir + << " is not the same as the new one: " << hash_dir_; + return Status::OK(); + } + is_restored_ = true; + if (reader->Contains(full_name(kEndOfSequence))) { + end_of_sequence_ = true; + } else { + end_of_sequence_ = false; + } + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kRunId), &run_id_)); + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kRunDir), &run_dir_)); + { + int64 temp; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name(kElementsProduced), &temp)); + elements_produced_ = static_cast(temp); + } + size_t buffer_size; + { + int64 temp; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(kBuffer, kSizeSuffix)), &temp)); + buffer_size = static_cast(temp); + } + for (size_t i = 0; i < buffer_size; i++) { + buffer_.emplace_back(); + auto& buffer_element = buffer_.back(); + size_t value_size; + { + int64 temp; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(kBuffer, "[", i, "]", kSizeSuffix)), + &temp)); + value_size = static_cast(temp); + } + if (reader->Contains(full_name( + strings::StrCat(kBuffer, "[", i, "].", kEndOfSequence)))) { + buffer_element.end_of_sequence = true; + } else { + buffer_element.end_of_sequence = false; + } + buffer_element.value.reserve(value_size); + for (size_t j = 0; j < value_size; j++) { + buffer_element.value.emplace_back(); + TF_RETURN_IF_ERROR(reader->ReadTensor( + full_name(strings::StrCat(kBuffer, "[", i, "][", j, "]")), + &buffer_element.value.back())); + } + } + { + int64 temp; + TF_RETURN_IF_ERROR( + reader->ReadScalar(full_name(kNextFileIndex), &temp)); + next_file_index_ = static_cast(temp); + } + TF_RETURN_IF_ERROR(reader->ReadScalar(full_name(kNumElementsWritten), + &num_elements_written_)); + size_t next_elem_size; + { + int64 temp; + TF_RETURN_IF_ERROR(reader->ReadScalar( + full_name(strings::StrCat(kNextElem, kSizeSuffix)), &temp)); + next_elem_size = static_cast(temp); + } + if (reader->Contains( + full_name(strings::StrCat(kNextElem, ".", kEndOfSequence)))) { + next_elem_.end_of_sequence = true; + } else { + next_elem_.end_of_sequence = false; + } + next_elem_.value.reserve(next_elem_size); + for (size_t i = 0; i < next_elem_size; i++) { + next_elem_.value.emplace_back(); + TF_RETURN_IF_ERROR(reader->ReadTensor( + full_name(strings::StrCat(kNextElem, "[", i, "]")), + &next_elem_.value.back())); + } + return Status::OK(); + } + private: struct BufferElement { std::vector value; @@ -1195,8 +1545,9 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { std::unique_ptr input_impl_; const string hash_dir_; - string run_id_ GUARDED_BY(mu_); - string run_dir_ GUARDED_BY(mu_); + tstring run_id_ GUARDED_BY(mu_); + tstring run_dir_ GUARDED_BY(mu_); + bool is_restored_ GUARDED_BY(mu_) = false; uint64 elements_produced_ GUARDED_BY(mu_) = 0; int64 time_spent_micros_ GUARDED_BY(mu_) = 0; @@ -1229,6 +1580,16 @@ class SnapshotDatasetOp : public UnaryDatasetOpKernel { return input_impl_->GetNext(ctx, out_tensors, end_of_sequence); } + protected: + Status SaveInternal(IteratorStateWriter* writer) override { + return SaveInput(writer, input_impl_); + } + + Status RestoreInternal(IteratorContext* ctx, + IteratorStateReader* reader) override { + return RestoreInput(ctx, reader, input_impl_); + } + private: std::unique_ptr input_impl_; }; diff --git a/tensorflow/core/kernels/data/iterator_ops.cc b/tensorflow/core/kernels/data/iterator_ops.cc index 388e94e62f3..88fed8e6d78 100644 --- a/tensorflow/core/kernels/data/iterator_ops.cc +++ b/tensorflow/core/kernels/data/iterator_ops.cc @@ -19,6 +19,7 @@ limitations under the License. #include "absl/memory/memory.h" #include "tensorflow/core/common_runtime/graph_runner.h" #include "tensorflow/core/common_runtime/input_colocation_exemption_registry.h" +#include "tensorflow/core/common_runtime/metrics.h" #include "tensorflow/core/common_runtime/renamed_device.h" #include "tensorflow/core/common_runtime/threadpool_device.h" #include "tensorflow/core/framework/cancellation.h" @@ -29,6 +30,7 @@ limitations under the License. #include "tensorflow/core/framework/stats_aggregator.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/framework/variant_op_registry.h" +#include "tensorflow/core/framework/variant_tensor_data.h" #include "tensorflow/core/graph/graph_constructor.h" #include "tensorflow/core/kernels/data/captured_function.h" #include "tensorflow/core/kernels/data/dataset_utils.h" @@ -82,8 +84,10 @@ Status IteratorResource::GetNext(OpKernelContext* ctx, [cm = params.cancellation_manager]() { cm->StartCancel(); }, &deregister_fn)); auto cleanup = gtl::MakeCleanup(std::move(deregister_fn)); - return captured_state->iterator->GetNext(IteratorContext(std::move(params)), - out_tensors, end_of_sequence); + auto val = captured_state->iterator->GetNext( + IteratorContext(std::move(params)), out_tensors, end_of_sequence); + metrics::RecordTFDataBytesFetched(GetTotalBytes(*out_tensors)); + return val; } return errors::FailedPrecondition( "GetNext() failed because the iterator has not been initialized. Ensure " @@ -194,15 +198,15 @@ Status IteratorResource::SetIteratorFromDataset(OpKernelContext* ctx, namespace { // Wrapper for encoding/decoding the iterator state stored in a Variant tensor. -// The get() method returns an IteratorStateReader which can be used -// to restore iterator state. +// The get() method returns an VariantTensorData object which contains all the +// state needed to restore a single iterator. // // Usage example: // // Encoding: // // Tensor t(DT_VARIANT, TensorShape({})); -// t->scalar()() = IteratorStateVariant(iterator_resource); +// t->scalar()() = IteratorStateVariant(); // // Encode() sets the type_name of the VariantTensorData object to // IteratorStateVariant::TypeName(). @@ -212,7 +216,8 @@ namespace { // Variant v = ; // DecodeUnaryVariant(&v); // IteratorStateVariant* wrapper = v.get(); -// iterator_resource->Restore(ctx, wrapper->get()) +// IteratorStateReader reader({wrapper->GetData()}); +// iterator_resource->Restore(ctx, &reader); // // The type_name of the VariantTensorData object to be decoded must // match IteratorStateVariant::TypeName(). @@ -227,18 +232,12 @@ class IteratorStateVariant { IteratorStateVariant& operator=(IteratorStateVariant&& other) = default; IteratorStateVariant& operator=(const IteratorStateVariant& other) = delete; - // Initializes this object with the current state of the iterator so - // that it can be written on the next call to Encode(). - Status InitializeFromIterator(OpKernelContext* ctx, - IteratorResource* iterator_resource) { - SerializationContext serialization_ctx({}); - data_ = absl::make_unique(); - data_->set_type_name(TypeName()); - VariantTensorDataWriter writer(data_.get()); - TF_RETURN_IF_ERROR(iterator_resource->Save(&serialization_ctx, &writer)); - TF_RETURN_IF_ERROR(writer.Flush()); + // Initializes `this` from a VariantTensorData object. + Status InitializeFromVariantData(std::unique_ptr d) { + data_ = std::move(d); return Status::OK(); } + string TypeName() const { return kIteratorVariantTypeName; } void Encode(VariantTensorData* data) const { *data = *data_; } bool Decode(VariantTensorData data) { @@ -247,12 +246,13 @@ class IteratorStateVariant { } auto tensor_data = absl::make_unique(); std::swap(*tensor_data, data); - auto reader = absl::make_unique(tensor_data.get()); data_ = std::move(tensor_data); - reader_ = std::move(reader); return true; } - IteratorStateReader* get() { return reader_.get(); } + + // Returns a borrowed pointer to the underlying VariantTensorData. + const VariantTensorData* GetData() const { return data_.get(); } + string DebugString() const { if (data_) { return strings::StrCat("IteratorStateVariant<", data_->DebugString(), @@ -263,7 +263,6 @@ class IteratorStateVariant { } private: - std::unique_ptr reader_; std::unique_ptr data_; }; @@ -275,6 +274,86 @@ class IteratorStateVariant { REGISTER_UNARY_VARIANT_DECODE_FUNCTION(IteratorStateVariant, kIteratorVariantTypeName); +// A helper class that uses a list of IteratorStateVariant objects to represent +// the state for an iterator resource. It exposes methods that help with +// saving and restoring of this state. Sample usage +// Saving: +// IteratorVariantSerializer serializer; +// serializer.InitializeFromIterator(iterator_resource); +// Tensor serialized_t; +// serializer.Serialize(&serialized_t); +// +// Restoring: +// IteratorVariantSerializer serializer; +// serializer.InitFromTensor(ctx->input(0)); +// IteratorStateReader* reader = serializer.GetReader(); +// iterator_resource->Restore(ctx, reader); +class IteratorVariantSerializer { + public: + IteratorVariantSerializer() {} + + // Calls `Save` on the iterator_resource to build up the list of + // IteratorStateVariant objects. + Status InitializeFromIterator(IteratorResource* iterator_resource) { + SerializationContext serialization_ctx({}); + VariantTensorDataWriter writer; + TF_RETURN_IF_ERROR(iterator_resource->Save(&serialization_ctx, &writer)); + std::vector> data; + writer.ReleaseData(&data); + variants_.clear(); + variants_.reserve(data.size()); + for (auto& it : data) { + IteratorStateVariant v; + TF_RETURN_IF_ERROR(v.InitializeFromVariantData(std::move(it))); + variants_.push_back(v); + } + num_tensors_ = variants_.size(); + can_serialize_ = true; + return Status::OK(); + } + + // Initializes `this` from `serialized_t` while restoring the iterator state. + Status InitFromTensor(const Tensor* serialized_t) { + int64 num_tensors = serialized_t->dim_size(0); + auto serialized_vec = serialized_t->vec(); + std::vector data; + data.reserve(num_tensors); + for (int i = 0; i < num_tensors; ++i) { + auto* w = serialized_vec(i).get(); + data.push_back(w->GetData()); + } + reader_ = absl::make_unique(data); + num_tensors_ = data.size(); + return Status::OK(); + } + + int64 NumTensors() { return num_tensors_; } + + // Stores the IteratorStateVariant list into a pre-allocated tensor. Expects + // that InitializeFromIterator was called before. + Status Serialize(Tensor* serialized) { + if (!can_serialize_) { + return errors::InvalidArgument( + "Please call InitializeFromIterator before calling Serialize."); + } + int64 size = variants_.size(); + for (int64 i = 0; i < size; ++i) { + serialized->vec()(i) = variants_[i]; + } + return Status::OK(); + } + + // Returns an IteratorStateReader to restore iterator state. Expects that + // InitFromTensor was called before. + IteratorStateReader* GetReader() { return reader_.get(); } + + private: + bool can_serialize_ = false; + int64 num_tensors_; + std::vector variants_; + std::unique_ptr reader_; +}; + } // namespace // Note that IteratorHandleOp holds a reference to the resource it creates. If @@ -1004,11 +1083,13 @@ class SerializeIteratorOp : public OpKernel { OP_REQUIRES_OK( ctx, LookupResource(ctx, HandleFromInput(ctx, 0), &iterator_resource)); core::ScopedUnref unref_iterator(iterator_resource); - Tensor* variant_t; - OP_REQUIRES_OK(ctx, ctx->allocate_output(0, TensorShape({}), &variant_t)); - IteratorStateVariant v; - OP_REQUIRES_OK(ctx, v.InitializeFromIterator(ctx, iterator_resource)); - variant_t->scalar()() = v; + IteratorVariantSerializer serializer; + OP_REQUIRES_OK(ctx, serializer.InitializeFromIterator(iterator_resource)); + Tensor* serialized_t; + OP_REQUIRES_OK( + ctx, ctx->allocate_output(0, TensorShape({serializer.NumTensors()}), + &serialized_t)); + OP_REQUIRES_OK(ctx, serializer.Serialize(serialized_t)); } }; @@ -1023,12 +1104,12 @@ class DeserializeIteratorOp : public OpKernel { OP_REQUIRES_OK( ctx, LookupResource(ctx, HandleFromInput(ctx, 0), &iterator_resource)); core::ScopedUnref unref_iterator(iterator_resource); - Variant variant = ctx->input(1).scalar()(); - auto* wrapper = variant.get(); - OP_REQUIRES(ctx, wrapper != nullptr, - errors::InvalidArgument( - "DeserializeIteratorOp: Unable to parse variant tensor.")); - OP_REQUIRES_OK(ctx, iterator_resource->Restore(ctx, wrapper->get())); + const Tensor* serialized_t; + OP_REQUIRES_OK(ctx, ctx->input("serialized", &serialized_t)); + IteratorVariantSerializer serializer; + OP_REQUIRES_OK(ctx, serializer.InitFromTensor(serialized_t)); + OP_REQUIRES_OK(ctx, + iterator_resource->Restore(ctx, serializer.GetReader())); } }; diff --git a/tensorflow/core/kernels/data/repeat_dataset_op_test.cc b/tensorflow/core/kernels/data/repeat_dataset_op_test.cc index 5834f2675cc..f7c38568309 100644 --- a/tensorflow/core/kernels/data/repeat_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/repeat_dataset_op_test.cc @@ -317,11 +317,11 @@ TEST_P(ParameterizedIteratorSaveAndRestoreTest, Roundtrip) { int cur_iteration = 0; std::vector breakpoints = GetParam().breakpoints; for (int breakpoint : breakpoints) { - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_EXPECT_OK(iterator_->Save(serialization_ctx.get(), &writer)); - TF_EXPECT_OK(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, test_case.dataset_params.iterator_prefix(), *dataset_, &iterator_)); diff --git a/tensorflow/core/kernels/data/shuffle_dataset_op.cc b/tensorflow/core/kernels/data/shuffle_dataset_op.cc index f6ca11beda2..684ab0ba07e 100644 --- a/tensorflow/core/kernels/data/shuffle_dataset_op.cc +++ b/tensorflow/core/kernels/data/shuffle_dataset_op.cc @@ -408,20 +408,40 @@ class ShuffleDatasetOpBase::ShuffleDatasetBase : public DatasetBase { const int64 count_; }; +namespace { +class Seeds { + public: + Seeds(int64 seed, int64 seed2) { + input_seed_ = seed; + input_seed2_ = seed2; + seed_ = seed; + seed2_ = seed2; + // By TensorFlow convention, if both seeds are 0, then shuffling should be + // seeded non-deterministically. + if (seed == 0 && seed2 == 0) { + seed_ = random::New64(); + seed2_ = random::New64(); + } + } + + int64 input_seed_; + int64 input_seed2_; + int64 seed_; + int64 seed2_; +}; +} // namespace + // A dataset that uses a pseudorandom sequence of seeds for the iterators // created from it. Used when `reshuffle_each_iteration` is true. class ShuffleDatasetOp::ReshufflingDataset : public ShuffleDatasetBase { public: ReshufflingDataset(OpKernelContext* ctx, const DatasetBase* input, - int64 buffer_size, int64 seed, int64 seed2, int64 count) - : ShuffleDatasetBase(ctx, input, buffer_size, count), - seed_(seed), - seed2_(seed2) {} - + int64 buffer_size, Seeds seeds, int64 count) + : ShuffleDatasetBase(ctx, input, buffer_size, count), seeds_(seeds) {} string DebugString() const override { name_utils::DatasetDebugStringParams params; params.dataset_prefix = kReshufflingDatasetPrefix; - params.set_args(buffer_size_, seed_, seed2_); + params.set_args(buffer_size_, seeds_.seed_, seeds_.seed2_); return name_utils::DatasetDebugString(kDatasetType, params); } @@ -430,7 +450,7 @@ class ShuffleDatasetOp::ReshufflingDataset : public ShuffleDatasetBase { return absl::make_unique( Iterator::Params{this, name_utils::IteratorPrefix(kDatasetType, prefix)}, - seed_, seed2_); + seeds_.seed_, seeds_.seed2_); } protected: @@ -523,8 +543,8 @@ class ShuffleDatasetOp::ReshufflingDataset : public ShuffleDatasetBase { AttrValue reshuffle_each_iteration; TF_RETURN_IF_ERROR(b->AddScalar(buffer_size_, &buffer_size)); - TF_RETURN_IF_ERROR(b->AddScalar(seed_, &seed)); - TF_RETURN_IF_ERROR(b->AddScalar(seed2_, &seed2)); + TF_RETURN_IF_ERROR(b->AddScalar(seeds_.input_seed_, &seed)); + TF_RETURN_IF_ERROR(b->AddScalar(seeds_.input_seed2_, &seed2)); b->BuildAttrValue(true, &reshuffle_each_iteration); TF_RETURN_IF_ERROR(b->AddDataset( this, {input_graph_node, buffer_size, seed, seed2}, // Inputs @@ -535,8 +555,7 @@ class ShuffleDatasetOp::ReshufflingDataset : public ShuffleDatasetBase { } private: - const int64 seed_; - const int64 seed2_; + const Seeds seeds_; }; // A dataset that uses a pseudorandom sequence of seeds for the iterators @@ -651,15 +670,13 @@ class ShuffleDatasetOp::ReshufflingDatasetV2 : public ShuffleDatasetBase { class ShuffleDatasetOp::FixedSeedDataset : public ShuffleDatasetBase { public: FixedSeedDataset(OpKernelContext* ctx, const DatasetBase* input, - int64 buffer_size, int64 seed, int64 seed2, int64 count) - : ShuffleDatasetBase(ctx, input, buffer_size, count), - seed_(seed), - seed2_(seed2) {} + int64 buffer_size, Seeds seeds, int64 count) + : ShuffleDatasetBase(ctx, input, buffer_size, count), seeds_(seeds) {} string DebugString() const override { name_utils::DatasetDebugStringParams params; params.dataset_prefix = kFixedSeedDatasetPrefix; - params.set_args(buffer_size_, seed_, seed2_); + params.set_args(buffer_size_, seeds_.seed_, seeds_.seed2_); return name_utils::DatasetDebugString(kDatasetType, params); } @@ -668,7 +685,7 @@ class ShuffleDatasetOp::FixedSeedDataset : public ShuffleDatasetBase { return absl::make_unique>( ShuffleDatasetBase::Iterator::Params{ this, name_utils::IteratorPrefix(kDatasetType, prefix)}, - seed_, seed2_); + seeds_.seed_, seeds_.seed2_); } protected: @@ -683,8 +700,8 @@ class ShuffleDatasetOp::FixedSeedDataset : public ShuffleDatasetBase { AttrValue reshuffle_each_iteration; TF_RETURN_IF_ERROR(b->AddScalar(buffer_size_, &buffer_size)); - TF_RETURN_IF_ERROR(b->AddScalar(seed_, &seed)); - TF_RETURN_IF_ERROR(b->AddScalar(seed2_, &seed2)); + TF_RETURN_IF_ERROR(b->AddScalar(seeds_.input_seed_, &seed)); + TF_RETURN_IF_ERROR(b->AddScalar(seeds_.input_seed2_, &seed2)); b->BuildAttrValue(false, &reshuffle_each_iteration); TF_RETURN_IF_ERROR(b->AddDataset( this, {input_graph_node, buffer_size, seed, seed2}, // Inputs @@ -695,8 +712,7 @@ class ShuffleDatasetOp::FixedSeedDataset : public ShuffleDatasetBase { } private: - const int64 seed_; - const int64 seed2_; + const Seeds seeds_; }; ShuffleDatasetOp::ShuffleDatasetOp(OpKernelConstruction* ctx) @@ -742,32 +758,24 @@ void ShuffleDatasetOp::MakeDataset(OpKernelContext* ctx, DatasetBase* input, int64 seed2; OP_REQUIRES_OK(ctx, ParseScalarArgument(ctx, kSeed2, &seed2)); - // By TensorFlow convention, passing 0 for both seeds indicates - // that the shuffling should be seeded non-deterministically. - if (seed == 0 && seed2 == 0) { - seed = random::New64(); - seed2 = random::New64(); - } - if (reshuffle_each_iteration_) { - *output = - new ReshufflingDataset(ctx, input, buffer_size, seed, seed2, count); + *output = new ReshufflingDataset(ctx, input, buffer_size, + Seeds(seed, seed2), count); } else { - *output = new FixedSeedDataset(ctx, input, buffer_size, seed, seed2, count); + *output = new FixedSeedDataset(ctx, input, buffer_size, Seeds(seed, seed2), + count); } } class ShuffleAndRepeatDatasetOp::Dataset : public ShuffleDatasetBase { public: Dataset(OpKernelContext* ctx, const DatasetBase* input, int64 buffer_size, - int64 seed, int64 seed2, int64 count) - : ShuffleDatasetBase(ctx, input, buffer_size, count), - seed_(seed), - seed2_(seed2) {} + Seeds seeds, int64 count) + : ShuffleDatasetBase(ctx, input, buffer_size, count), seeds_(seeds) {} string DebugString() const override { name_utils::DatasetDebugStringParams params; - params.set_args(buffer_size_, seed_, seed2_); + params.set_args(buffer_size_, seeds_.seed_, seeds_.seed2_); return name_utils::DatasetDebugString(kDatasetType, params); } @@ -776,7 +784,7 @@ class ShuffleAndRepeatDatasetOp::Dataset : public ShuffleDatasetBase { return absl::make_unique>( ShuffleDatasetBase::Iterator::Params{ this, name_utils::IteratorPrefix(kDatasetType, prefix)}, - seed_, seed2_); + seeds_.seed_, seeds_.seed2_); } protected: @@ -791,8 +799,8 @@ class ShuffleAndRepeatDatasetOp::Dataset : public ShuffleDatasetBase { Node* count = nullptr; TF_RETURN_IF_ERROR(b->AddScalar(buffer_size_, &buffer_size)); - TF_RETURN_IF_ERROR(b->AddScalar(seed_, &seed)); - TF_RETURN_IF_ERROR(b->AddScalar(seed2_, &seed2)); + TF_RETURN_IF_ERROR(b->AddScalar(seeds_.input_seed_, &seed)); + TF_RETURN_IF_ERROR(b->AddScalar(seeds_.input_seed2_, &seed2)); TF_RETURN_IF_ERROR(b->AddScalar(count_, &count)); TF_RETURN_IF_ERROR(b->AddDataset( this, {input_graph_node, buffer_size, seed, seed2, count}, // Inputs @@ -802,8 +810,7 @@ class ShuffleAndRepeatDatasetOp::Dataset : public ShuffleDatasetBase { } private: - const int64 seed_; - const int64 seed2_; + const Seeds seeds_; }; ShuffleAndRepeatDatasetOp::ShuffleAndRepeatDatasetOp(OpKernelConstruction* ctx) @@ -832,14 +839,7 @@ void ShuffleAndRepeatDatasetOp::MakeDataset(OpKernelContext* ctx, errors::InvalidArgument( "count must be greater than zero or equal to -1.")); - // By TensorFlow convention, if both seeds are 0, then shuffling should be - // seeded non-deterministically. - if (seed == 0 && seed2 == 0) { - seed = random::New64(); - seed2 = random::New64(); - } - - *output = new Dataset(ctx, input, buffer_size, seed, seed2, count); + *output = new Dataset(ctx, input, buffer_size, Seeds(seed, seed2), count); } namespace { diff --git a/tensorflow/core/kernels/data/shuffle_dataset_op_test.cc b/tensorflow/core/kernels/data/shuffle_dataset_op_test.cc index 9002d58d0af..c4014b81063 100644 --- a/tensorflow/core/kernels/data/shuffle_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/shuffle_dataset_op_test.cc @@ -524,11 +524,11 @@ TEST_P(ParameterizedIteratorSaveAndRestoreTest, IteratorSaveAndRestore) { int cur_iteration = 0; const std::vector& breakpoints = test_case.breakpoints; for (int breakpoint : breakpoints) { - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_EXPECT_OK(iterator_->Save(serialization_ctx.get(), &writer)); - TF_EXPECT_OK(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, test_case.dataset_params.iterator_prefix(), *dataset_, &iterator_)); diff --git a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc index 96c40e3823d..d31cb9ee9c6 100644 --- a/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/sparse_tensor_slice_dataset_op_test.cc @@ -397,11 +397,11 @@ TEST_P(ParameterizedIteratorSaveAndRestoreTest, IteratorSaveAndRestore) { EXPECT_TRUE(end_of_sequence); } - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_ASSERT_OK(iterator_->Save(serialization_ctx.get(), &writer)); - TF_ASSERT_OK(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, test_case.dataset_params.iterator_prefix(), *dataset_, &iterator_)); diff --git a/tensorflow/core/kernels/data/tensor_dataset_op_test.cc b/tensorflow/core/kernels/data/tensor_dataset_op_test.cc index 4a1ebaa3be2..8f8657282f9 100644 --- a/tensorflow/core/kernels/data/tensor_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/tensor_dataset_op_test.cc @@ -234,11 +234,11 @@ TEST_P(ParameterizedIteratorSaveAndRestoreTest, SaveAndRestore) { const std::vector& breakpoints = test_case.breakpoints; int cardinality = 1; for (int breakpoint : breakpoints) { - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_EXPECT_OK(iterator_->Save(serialization_ctx.get(), &writer)); - TF_EXPECT_OK(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, test_case.dataset_params.iterator_prefix(), *dataset_, &iterator_)); diff --git a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc index d74057c53d2..546b8b2e1e0 100644 --- a/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/tensor_slice_dataset_op_test.cc @@ -304,11 +304,11 @@ TEST_P(ParameterizedIteratorSaveAndRestoreTest, SaveAndRestore) { EXPECT_TRUE(end_of_sequence); } - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_ASSERT_OK(iterator_->Save(serialization_context.get(), &writer)); - TF_ASSERT_OK(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, "Iterator", *dataset_, &iterator_)); } diff --git a/tensorflow/core/kernels/data/window_dataset.cc b/tensorflow/core/kernels/data/window_dataset.cc index b8a7a8a1474..64365f52268 100644 --- a/tensorflow/core/kernels/data/window_dataset.cc +++ b/tensorflow/core/kernels/data/window_dataset.cc @@ -55,6 +55,14 @@ class WindowDataset : public DatasetBase { return allocated_bytes; } + int64 TotalBytes() const override { + int64 total_bytes = 0; + for (auto& element : elements_) { + total_bytes += GetTotalBytes(element); + } + return total_bytes; + } + int64 Cardinality() const override { return elements_.size(); } string DebugString() const override { return kWindowDataset; } diff --git a/tensorflow/core/kernels/data/window_dataset_op.cc b/tensorflow/core/kernels/data/window_dataset_op.cc index 6fd66aaa0b1..c3a75350394 100644 --- a/tensorflow/core/kernels/data/window_dataset_op.cc +++ b/tensorflow/core/kernels/data/window_dataset_op.cc @@ -261,15 +261,15 @@ class WindowDatasetOp::Dataset : public DatasetBase { } // Save buffer. TF_RETURN_IF_ERROR( - writer->WriteScalar(strings::StrCat(kBufferSize), buffer_.size())); + writer->WriteScalar(full_name(kBufferSize), buffer_.size())); for (int64 i = 0; i < buffer_.size(); i++) { TF_RETURN_IF_ERROR(WriteStatusLocked(writer, i, buffer_[i].status)); TF_RETURN_IF_ERROR(writer->WriteScalar( - strings::StrCat(kBuffer, "[", i, "]", kSizeSuffix), + full_name(strings::StrCat(kBuffer, "[", i, "]", kSizeSuffix)), buffer_[i].result.size())); for (int64 j = 0; j < buffer_[i].result.size(); j++) { TF_RETURN_IF_ERROR(writer->WriteTensor( - strings::StrCat(kBuffer, "[", i, "][", j, "]"), + full_name(strings::StrCat(kBuffer, "[", i, "][", j, "]")), buffer_[i].result[j])); } } @@ -287,18 +287,19 @@ class WindowDatasetOp::Dataset : public DatasetBase { // Restore buffer. int64 buffer_size = 0; TF_RETURN_IF_ERROR( - reader->ReadScalar(strings::StrCat(kBufferSize), &buffer_size)); + reader->ReadScalar(full_name(kBufferSize), &buffer_size)); buffer_.resize(buffer_size); for (int64 i = 0; i < buffer_size; i++) { int64 vector_size; TF_RETURN_IF_ERROR(ReadStatusLocked(reader, i, &buffer_[i].status)); TF_RETURN_IF_ERROR(reader->ReadScalar( - strings::StrCat(kBuffer, "[", i, "]", kSizeSuffix), &vector_size)); + full_name(strings::StrCat(kBuffer, "[", i, "]", kSizeSuffix)), + &vector_size)); buffer_[i].result.resize(vector_size); for (int64 j = 0; j < vector_size; j++) { - TF_RETURN_IF_ERROR( - reader->ReadTensor(strings::StrCat(kBuffer, "[", i, "][", j, "]"), - &buffer_[i].result[j])); + TF_RETURN_IF_ERROR(reader->ReadTensor( + full_name(strings::StrCat(kBuffer, "[", i, "][", j, "]")), + &buffer_[i].result[j])); } } return Status::OK(); diff --git a/tensorflow/core/kernels/data/window_dataset_op_test.cc b/tensorflow/core/kernels/data/window_dataset_op_test.cc index 8c883122a17..cac0f828875 100644 --- a/tensorflow/core/kernels/data/window_dataset_op_test.cc +++ b/tensorflow/core/kernels/data/window_dataset_op_test.cc @@ -471,11 +471,11 @@ TEST_P(ParameterizedIteratorSaveAndRestoreTest, IteratorSaveAndRestore) { auto expected_outputs_it = test_case.expected_outputs.begin(); int cur_iteration = 0; for (int breakpoint : test_case.breakpoints) { - VariantTensorData data; - VariantTensorDataWriter writer(&data); + VariantTensorDataWriter writer; TF_EXPECT_OK(iterator_->Save(serialization_ctx.get(), &writer)); - TF_EXPECT_OK(writer.Flush()); - VariantTensorDataReader reader(&data); + std::vector data; + writer.GetData(&data); + VariantTensorDataReader reader(data); TF_EXPECT_OK(RestoreIterator(iterator_ctx_.get(), &reader, test_case.dataset_params.iterator_prefix(), *dataset_, &iterator_)); diff --git a/tensorflow/core/kernels/gather_op.cc b/tensorflow/core/kernels/gather_op.cc index 4e051c6b186..849a2b4389f 100644 --- a/tensorflow/core/kernels/gather_op.cc +++ b/tensorflow/core/kernels/gather_op.cc @@ -182,7 +182,7 @@ class GatherOp : public OpKernel { private: // The number of batch dimensions, as passed in the batch_dims attribute. - // It must be less than rank(indices). + // It must be less than or equal to rank(indices). int32 batch_dims_ = 0; }; diff --git a/tensorflow/core/kernels/initializable_lookup_table.cc b/tensorflow/core/kernels/initializable_lookup_table.cc index b72874f723b..196c2fe95a3 100644 --- a/tensorflow/core/kernels/initializable_lookup_table.cc +++ b/tensorflow/core/kernels/initializable_lookup_table.cc @@ -68,10 +68,7 @@ Status InitializableLookupTable::Initialize(InitTableIterator& iter) { return iter.status(); } - // Prevent compiler/memory reordering of is_initialized and - // the initialization itself. - std::atomic_thread_fence(std::memory_order_release); - is_initialized_ = true; + is_initialized_.store(true, std::memory_order_release); return Status::OK(); } diff --git a/tensorflow/core/kernels/initializable_lookup_table.h b/tensorflow/core/kernels/initializable_lookup_table.h index 5ebfef01f5a..2ff537df81c 100644 --- a/tensorflow/core/kernels/initializable_lookup_table.h +++ b/tensorflow/core/kernels/initializable_lookup_table.h @@ -16,6 +16,8 @@ limitations under the License. #ifndef TENSORFLOW_CORE_KERNELS_INITIALIZABLE_LOOKUP_TABLE_H_ #define TENSORFLOW_CORE_KERNELS_INITIALIZABLE_LOOKUP_TABLE_H_ +#include + #include "tensorflow/core/framework/lookup_interface.h" #include "tensorflow/core/platform/macros.h" @@ -71,7 +73,9 @@ class InitializableLookupTable : public LookupInterface { TensorShape value_shape() const final { return TensorShape(); } // Returns whether the table was initialized and is ready to serve lookups. - bool is_initialized() const { return is_initialized_; } + bool is_initialized() const { + return is_initialized_.load(std::memory_order_acquire); + } // Initializes the table from the given init table iterator. // @@ -156,7 +160,9 @@ class InitializableLookupTable : public LookupInterface { virtual Status AreEntriesSame(const InitTableIterator& iter, bool* result); mutex mu_; - bool is_initialized_ = false; + + private: + std::atomic is_initialized_{false}; }; // Iterator to initialize tables given 'keys' and 'values' tensors. diff --git a/tensorflow/core/kernels/lookup_table_op.h b/tensorflow/core/kernels/lookup_table_op.h index c633b668646..56818470cda 100644 --- a/tensorflow/core/kernels/lookup_table_op.h +++ b/tensorflow/core/kernels/lookup_table_op.h @@ -16,6 +16,7 @@ limitations under the License. #ifndef TENSORFLOW_CORE_KERNELS_LOOKUP_TABLE_OP_H_ #define TENSORFLOW_CORE_KERNELS_LOOKUP_TABLE_OP_H_ +#include "absl/container/flat_hash_map.h" #include "tensorflow/core/framework/bounds_check.h" #include "tensorflow/core/framework/lookup_interface.h" #include "tensorflow/core/framework/op_kernel.h" @@ -153,7 +154,7 @@ inline const ResourceHandle& SubtleMustCopyIfIntegral( return value; } -// Lookup table that wraps an unordered_map, where the key and value data type +// Lookup table that wraps an flat_hash_map, where the key and value data type // is specified. // // This table is recommended for any variations to key values. @@ -164,13 +165,7 @@ inline const ResourceHandle& SubtleMustCopyIfIntegral( // Sample use case: // // HashTable table; // int64 -> int64. -// table.Prepare(10); // Prepare the underlying data structure, the number of -// // elements is required by interface, but not used. -// // Populate the table, elements could be added in one or multiple calls. -// table.Insert(key_tensor, value_tensor); // Populate the table. -// ... -// table.set_is_initialized(); -// +// table.Initialize(...); // table.Find(in_t, &out_t, default_t) // template @@ -178,21 +173,14 @@ class HashTable : public InitializableLookupTable { public: HashTable(OpKernelContext* ctx, OpKernel* kernel) {} - size_t size() const override { - // return the size of the table only if it's initialized, otherwise 0. - if (!is_initialized_) { - return 0; - } - std::atomic_thread_fence(std::memory_order_acquire); - return table_ ? table_->size() : 0; - } + size_t size() const override { return table_.size(); } Status ExportValues(OpKernelContext* context) override { - if (!is_initialized_) { + if (!is_initialized()) { return errors::Aborted("HashTable is not initialized."); } - const int64 size = table_->size(); + const int64 size = table_.size(); Tensor* keys; Tensor* values; @@ -204,7 +192,7 @@ class HashTable : public InitializableLookupTable { auto keys_data = keys->flat(); auto values_data = values->flat(); int64 i = 0; - for (auto it = table_->begin(); it != table_->end(); ++it, ++i) { + for (auto it = table_.begin(); it != table_.end(); ++it, ++i) { keys_data(i) = it->first; values_data(i) = it->second; } @@ -216,37 +204,29 @@ class HashTable : public InitializableLookupTable { DataType value_dtype() const override { return DataTypeToEnum::v(); } protected: - Status DoPrepare(size_t unused) override { - if (is_initialized_) { + Status DoPrepare(size_t size) override { + if (is_initialized()) { return errors::Aborted("HashTable already initialized."); } - if (!table_) { - table_ = std::unique_ptr>( - new std::unordered_map()); - } + table_.reserve(size); return Status::OK(); }; - Status DoLazyPrepare(std::function unused) override { - constexpr size_t kUnusedSize = 0; - return DoPrepare(kUnusedSize); + Status DoLazyPrepare(std::function size_fn) override { + return DoPrepare(size_fn()); } Status DoInsert(const Tensor& keys, const Tensor& values) override { - if (!table_) { - return errors::FailedPrecondition("HashTable is not prepared."); - } - const auto key_values = keys.flat(); const auto value_values = values.flat(); for (int64 i = 0; i < key_values.size(); ++i) { - const K key = SubtleMustCopyIfIntegral(key_values(i)); - const V value = SubtleMustCopyIfIntegral(value_values(i)); - const V& previous_value = gtl::LookupOrInsert(table_.get(), key, value); - if (previous_value != value) { + auto&& key = SubtleMustCopyIfIntegral(key_values(i)); + auto&& value = SubtleMustCopyIfIntegral(value_values(i)); + auto result = table_.emplace(key, value); + if (!result.second && result.first->second != value) { return errors::FailedPrecondition( "HashTable has different value for same key. Key ", key, " has ", - previous_value, " and trying to add value ", value); + result.first->second, " and trying to add value ", value); } } return Status::OK(); @@ -260,22 +240,18 @@ class HashTable : public InitializableLookupTable { for (int64 i = 0; i < key_values.size(); ++i) { value_values(i) = gtl::FindWithDefault( - *table_, SubtleMustCopyIfIntegral(key_values(i)), default_val); + table_, SubtleMustCopyIfIntegral(key_values(i)), default_val); } return Status::OK(); } int64 MemoryUsed() const override { - if (table_) { - const int64 num_elements = table_->size(); - return num_elements * (sizeof(K) + sizeof(V)); - } else { - return 0; - } + const int64 num_elements = table_.size(); + return num_elements * (sizeof(K) + sizeof(V)); } private: - std::unique_ptr> table_; + absl::flat_hash_map table_; }; } // namespace lookup diff --git a/tensorflow/core/kernels/matmul_op.cc b/tensorflow/core/kernels/matmul_op.cc index f77c182d78f..91ea7a84d78 100644 --- a/tensorflow/core/kernels/matmul_op.cc +++ b/tensorflow/core/kernels/matmul_op.cc @@ -220,10 +220,11 @@ bool ShouldUseGemv(uint64 n) { bool GetCublasAutotuneComputationType(const DataType& dtype, se::blas::ComputationType* compute_type) { using se::blas::ComputationType; - bool use_f32_for_f16_computation = MatmulDoFP32ComputationFP16Input(); switch (dtype) { case DT_HALF: case DT_BFLOAT16: + static bool use_f32_for_f16_computation = + MatmulDoFP32ComputationFP16Input(); if (use_f32_for_f16_computation) { *compute_type = ComputationType::kF32; } else { diff --git a/tensorflow/core/kernels/mirror_pad_op.h b/tensorflow/core/kernels/mirror_pad_op.h index eda3b2b9e2a..7001257ad8b 100644 --- a/tensorflow/core/kernels/mirror_pad_op.h +++ b/tensorflow/core/kernels/mirror_pad_op.h @@ -111,7 +111,7 @@ struct TensorEvaluator, }; //===- Tensor block evaluation strategy (see TensorBlock.h) -------------===// - typedef internal::TensorBlockNotImplemented TensorBlockV2; + typedef internal::TensorBlockNotImplemented TensorBlock; //===--------------------------------------------------------------------===// EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE TensorEvaluator(const XprType& op, diff --git a/tensorflow/core/kernels/reshape_util.h b/tensorflow/core/kernels/reshape_util.h index 6777748b63b..90cd30869c8 100644 --- a/tensorflow/core/kernels/reshape_util.h +++ b/tensorflow/core/kernels/reshape_util.h @@ -21,6 +21,8 @@ limitations under the License. namespace tensorflow { +class OpKernelContext; + // Reshapes the input indices and input shape to the target shape. void Reshape(OpKernelContext *context, const Tensor &input_indices_in, const Tensor &input_shape_in, const Tensor &target_shape_in, diff --git a/tensorflow/core/kernels/rnn/BUILD b/tensorflow/core/kernels/rnn/BUILD index 8096e2150d8..0f0e358d9ed 100644 --- a/tensorflow/core/kernels/rnn/BUILD +++ b/tensorflow/core/kernels/rnn/BUILD @@ -7,7 +7,7 @@ load( "tf_kernel_library", ) load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load( diff --git a/tensorflow/core/lib/gtl/BUILD b/tensorflow/core/lib/gtl/BUILD index 098866517d8..8f03633cba0 100644 --- a/tensorflow/core/lib/gtl/BUILD +++ b/tensorflow/core/lib/gtl/BUILD @@ -8,6 +8,8 @@ package( "//tensorflow/core/lib/strings:__pkg__", # tensorflow/core/lib/histogram uses array_slice "//tensorflow/core/lib/histogram:__pkg__", + # tensorflow/core/framework uses map_util + "//tensorflow/core/framework:__pkg__", ], licenses = ["notice"], # Apache 2.0 ) @@ -47,6 +49,13 @@ cc_library( cc_library( name = "flatmap", hdrs = ["flatmap.h"], + visibility = [ + "//tensorflow/c/eager:__pkg__", + "//tensorflow/core:__pkg__", + "//tensorflow/core/lib/histogram:__pkg__", + "//tensorflow/core/lib/random:__pkg__", + "//tensorflow/core/lib/strings:__pkg__", + ], deps = [ "//tensorflow/core/lib/gtl:flatrep", "//tensorflow/core/lib/hash", diff --git a/tensorflow/core/lib/gtl/map_util.h b/tensorflow/core/lib/gtl/map_util.h index 6a48d5566e0..8f0c92ef598 100644 --- a/tensorflow/core/lib/gtl/map_util.h +++ b/tensorflow/core/lib/gtl/map_util.h @@ -158,6 +158,29 @@ typename Collection::value_type::second_type& LookupOrInsert( typename Collection::value_type(key, value)); } +// Saves the reverse mapping into reverse. Returns true if values could all be +// inserted. +template +bool ReverseMap(const M& m, ReverseM* reverse) { + bool all_unique = true; + for (const auto& kv : m) { + if (!InsertOrUpdate(reverse, kv.second, kv.first)) { + all_unique = false; + } + } + return all_unique; +} + +// Like ReverseMap above, but returns its output m. Return type has to +// be specified explicitly. Example: +// M::M(...) : m_(...), r_(ReverseMap(m_)) {} +template +ReverseM ReverseMap(const M& m) { + typename std::remove_const::type reverse; + ReverseMap(m, &reverse); + return reverse; +} + // Erases the m item identified by the given key, and returns the value // associated with that key. It is assumed that the value (i.e., the // mapped_type) is a pointer. Returns null if the key was not found in the diff --git a/tensorflow/core/lib/monitoring/BUILD b/tensorflow/core/lib/monitoring/BUILD index 35c59079231..add31e54688 100644 --- a/tensorflow/core/lib/monitoring/BUILD +++ b/tensorflow/core/lib/monitoring/BUILD @@ -40,6 +40,11 @@ cc_library( cc_library( name = "counter", hdrs = ["counter.h"], + visibility = [ + "//tensorflow/c/eager:__pkg__", + "//tensorflow/core:__pkg__", + "//tensorflow/core/platform:__subpackages__", + ], deps = [ ":collection_registry", ":metric_def", @@ -56,6 +61,11 @@ cc_library( cc_library( name = "gauge", hdrs = ["gauge.h"], + visibility = [ + "//tensorflow/c/eager:__pkg__", + "//tensorflow/core:__pkg__", + "//tensorflow/core/platform:__subpackages__", + ], deps = [ ":collection_registry", ":metric_def", @@ -115,6 +125,11 @@ cc_library( name = "sampler", srcs = ["sampler.cc"], hdrs = ["sampler.h"], + visibility = [ + "//tensorflow/c/eager:__pkg__", + "//tensorflow/core:__pkg__", + "//tensorflow/core/platform:__subpackages__", + ], deps = [ ":collection_registry", ":metric_def", diff --git a/tensorflow/core/lib/random/BUILD b/tensorflow/core/lib/random/BUILD index 7360e72f233..770d00051e3 100644 --- a/tensorflow/core/lib/random/BUILD +++ b/tensorflow/core/lib/random/BUILD @@ -65,6 +65,15 @@ cc_library( cc_library( name = "random", hdrs = ["random.h"], + visibility = [ + "//tensorflow/c/eager:__pkg__", + "//tensorflow/core:__pkg__", + "//tensorflow/core/lib/core:__pkg__", + "//tensorflow/core/lib/gtl:__pkg__", + "//tensorflow/core/lib/io:__pkg__", + "//tensorflow/core/lib/strings:__pkg__", + "//tensorflow/core/platform:__subpackages__", + ], deps = [ "//tensorflow/core/platform:random", ], diff --git a/tensorflow/core/ops/dataset_ops.cc b/tensorflow/core/ops/dataset_ops.cc index 4608be95bbe..72737d5f757 100644 --- a/tensorflow/core/ops/dataset_ops.cc +++ b/tensorflow/core/ops/dataset_ops.cc @@ -696,7 +696,10 @@ REGISTER_OP("IteratorFromStringHandleV2") REGISTER_OP("SerializeIterator") .Input("resource_handle: resource") .Output("serialized: variant") - .SetShapeFn(shape_inference::ScalarShape); + .SetShapeFn([](shape_inference::InferenceContext* c) { + c->set_output(0, c->Vector(c->UnknownDim())); + return Status::OK(); + }); REGISTER_OP("DeserializeIterator") .Input("resource_handle: resource") diff --git a/tensorflow/core/platform/BUILD b/tensorflow/core/platform/BUILD index 94657280223..2df7477adc8 100644 --- a/tensorflow/core/platform/BUILD +++ b/tensorflow/core/platform/BUILD @@ -14,17 +14,13 @@ load( "tf_additional_tensor_coding_deps", "tf_additional_test_srcs", "tf_fingerprint_deps", - "tf_protobuf_compiler_deps", - "tf_protobuf_deps", -) -load( - "//tensorflow/core/platform:default/build_refactor.bzl", - "tf_instantiate_platform_libraries", "tf_legacy_srcs_no_runtime_google", "tf_logging_deps", - "tf_mobile_aware_deps", "tf_monitoring_deps", - "tf_platform_helper_deps", + "tf_platform_deps", + "tf_protobuf_compiler_deps", + "tf_protobuf_deps", + "tf_windows_aware_platform_deps", ) load( "//tensorflow:tensorflow.bzl", @@ -44,39 +40,6 @@ package( licenses = ["notice"], # Apache 2.0 ) -# Note(bmzhao): These are temporary BUILD rule helpers for -# platform-specific library dependencies. These temporarily -# exist to allow us incrementally add granular targets -# to tf/core/platform/BUILD. We will remove these helpers, -# after we finish refactoring tf/core/platform/BUILD. -tf_instantiate_platform_libraries(names = [ - "context", - "cord", - "cuda_libdevice_path", - "dynamic_annotations", - "env", - "env_time", - "human_readable_json", - "logging", - "load_library", - "monitoring", - "mutex", - "net", - "notification", - "platform_port", - "rocm_rocdl_path", - "stacktrace", - "stacktrace_handler", - "strong_hash", - "subprocess", - "test", - "test_benchmark", - "tracing", - "types", - "unbounded_work_queue", - "wide_char", -]) - cc_library( name = "abi", srcs = ["abi.cc"], @@ -123,8 +86,7 @@ cc_library( cc_library( name = "context", textual_hdrs = ["context.h"], - # See call to tf_instantiate_platform_libraries() above. - deps = tf_mobile_aware_deps("context_impl"), + deps = tf_platform_deps("context"), ) cc_library( @@ -132,7 +94,7 @@ cc_library( hdrs = ["cord.h"], deps = [ ":platform", - ] + tf_mobile_aware_deps("cord_impl"), + ] + tf_platform_deps("cord"), ) cc_library( @@ -158,7 +120,7 @@ cc_library( cc_library( name = "cuda_libdevice_path", textual_hdrs = ["cuda_libdevice_path.h"], - deps = tf_mobile_aware_deps("cuda_libdevice_path_impl"), + deps = tf_platform_deps("cuda_libdevice_path"), ) cc_library( @@ -179,7 +141,7 @@ cc_library( hdrs = ["dynamic_annotations.h"], deps = [ ":platform", - ] + tf_mobile_aware_deps("dynamic_annotations_impl"), + ] + tf_platform_deps("dynamic_annotations"), ) cc_library( @@ -190,13 +152,13 @@ cc_library( "file_system_helper.h", "threadpool.h", ], - deps = tf_platform_helper_deps("env_impl"), + deps = tf_windows_aware_platform_deps("env"), ) cc_library( name = "env_time", textual_hdrs = ["env_time.h"], - deps = tf_platform_helper_deps("env_time_impl"), + deps = tf_windows_aware_platform_deps("env_time"), ) cc_library( @@ -262,7 +224,7 @@ cc_library( cc_library( name = "human_readable_json", textual_hdrs = ["human_readable_json.h"], - deps = tf_mobile_aware_deps("human_readable_json_impl"), + deps = tf_platform_deps("human_readable_json"), ) cc_library( @@ -276,7 +238,7 @@ cc_library( cc_library( name = "load_library", textual_hdrs = ["load_library.h"], - deps = tf_platform_helper_deps("load_library_impl"), + deps = tf_windows_aware_platform_deps("load_library"), ) cc_library( @@ -313,13 +275,13 @@ cc_library( cc_library( name = "mutex", textual_hdrs = ["mutex.h"], - deps = tf_mobile_aware_deps("mutex_impl"), + deps = tf_platform_deps("mutex"), ) cc_library( name = "net", textual_hdrs = ["net.h"], - deps = tf_platform_helper_deps("net_impl"), + deps = tf_windows_aware_platform_deps("net"), ) cc_library( @@ -327,7 +289,7 @@ cc_library( hdrs = ["notification.h"], deps = [ ":platform", - ] + tf_mobile_aware_deps("notification_impl"), + ] + tf_platform_deps("notification"), ) cc_library( @@ -384,7 +346,7 @@ cc_library( "numa.h", "snappy.h", ], - deps = tf_platform_helper_deps("platform_port_impl"), + deps = tf_windows_aware_platform_deps("platform_port"), ) cc_library( @@ -482,7 +444,7 @@ cc_library( cc_library( name = "rocm_rocdl_path", textual_hdrs = ["rocm_rocdl_path.h"], - deps = tf_mobile_aware_deps("rocm_rocdl_path_impl"), + deps = tf_platform_deps("rocm_rocdl_path"), ) cc_library( @@ -520,7 +482,7 @@ cc_library( hdrs = ["stacktrace.h"], deps = [ ":platform", - ] + tf_platform_helper_deps("stacktrace_impl"), + ] + tf_windows_aware_platform_deps("stacktrace"), ) filegroup( @@ -537,7 +499,7 @@ filegroup( cc_library( name = "stacktrace_handler", textual_hdrs = ["stacktrace_handler.h"], - deps = tf_platform_helper_deps("stacktrace_handler_impl"), + deps = tf_windows_aware_platform_deps("stacktrace_handler"), ) cc_library( @@ -610,7 +572,7 @@ cc_library( deps = [ ":platform", ":types", - ] + tf_mobile_aware_deps("strong_hash_impl"), + ] + tf_platform_deps("strong_hash"), ) cc_library( @@ -618,7 +580,7 @@ cc_library( textual_hdrs = [ "subprocess.h", ], - deps = tf_platform_helper_deps("subprocess_impl"), + deps = tf_windows_aware_platform_deps("subprocess"), ) cc_library( @@ -640,7 +602,7 @@ cc_library( name = "test", testonly = True, textual_hdrs = ["test.h"], - deps = tf_mobile_aware_deps("test_impl"), + deps = tf_platform_deps("test"), ) cc_library( @@ -649,8 +611,7 @@ cc_library( hdrs = ["test_benchmark.h"], deps = [ ":platform", - ":test_benchmark_impl", - ], + ] + tf_platform_deps("test_benchmark"), ) cc_library( @@ -679,7 +640,7 @@ cc_library( cc_library( name = "tracing", textual_hdrs = ["tracing.h"], - deps = tf_mobile_aware_deps("tracing_impl"), + deps = tf_platform_deps("tracing"), ) cc_library( @@ -693,7 +654,7 @@ cc_library( deps = [ ":platform", ":tstring", - ] + tf_mobile_aware_deps("types_impl"), + ] + tf_platform_deps("types"), ) cc_library( @@ -701,7 +662,7 @@ cc_library( hdrs = ["unbounded_work_queue.h"], deps = [ ":platform", - ] + tf_mobile_aware_deps("unbounded_work_queue_impl"), + ] + tf_platform_deps("unbounded_work_queue"), ) # This is a hacky, do-nothing, binary that makes it easy to verify ability to @@ -890,31 +851,31 @@ filegroup( name = "legacy_srcs_no_runtime", srcs = [ ":legacy_srcs_common", - "//tensorflow/core/platform:default/context.h", - "//tensorflow/core/platform:default/cord.h", - "//tensorflow/core/platform:default/dynamic_annotations.h", - "//tensorflow/core/platform:default/env.cc", - "//tensorflow/core/platform:default/human_readable_json.cc", - "//tensorflow/core/platform:default/integral_types.h", - "//tensorflow/core/platform:default/load_library.cc", - "//tensorflow/core/platform:default/logging.h", - "//tensorflow/core/platform:default/monitoring.cc", - "//tensorflow/core/platform:default/mutex.h", - "//tensorflow/core/platform:default/mutex_data.h", - "//tensorflow/core/platform:default/net.cc", - "//tensorflow/core/platform:default/notification.h", - "//tensorflow/core/platform:default/port.cc", - "//tensorflow/core/platform:default/posix_file_system.cc", - "//tensorflow/core/platform:default/posix_file_system.h", - "//tensorflow/core/platform:default/stacktrace.h", - "//tensorflow/core/platform:default/stacktrace_handler.cc", - "//tensorflow/core/platform:default/strong_hash.h", - "//tensorflow/core/platform:default/subprocess.cc", - "//tensorflow/core/platform:default/subprocess.h", - "//tensorflow/core/platform:default/tracing.cc", - "//tensorflow/core/platform:default/tracing_impl.h", - "//tensorflow/core/platform:default/unbounded_work_queue.cc", - "//tensorflow/core/platform:default/unbounded_work_queue.h", + "//tensorflow/core/platform/default:context.h", + "//tensorflow/core/platform/default:cord.h", + "//tensorflow/core/platform/default:dynamic_annotations.h", + "//tensorflow/core/platform/default:env.cc", + "//tensorflow/core/platform/default:human_readable_json.cc", + "//tensorflow/core/platform/default:integral_types.h", + "//tensorflow/core/platform/default:load_library.cc", + "//tensorflow/core/platform/default:logging.h", + "//tensorflow/core/platform/default:monitoring.cc", + "//tensorflow/core/platform/default:mutex.h", + "//tensorflow/core/platform/default:mutex_data.h", + "//tensorflow/core/platform/default:net.cc", + "//tensorflow/core/platform/default:notification.h", + "//tensorflow/core/platform/default:port.cc", + "//tensorflow/core/platform/default:posix_file_system.cc", + "//tensorflow/core/platform/default:posix_file_system.h", + "//tensorflow/core/platform/default:stacktrace.h", + "//tensorflow/core/platform/default:stacktrace_handler.cc", + "//tensorflow/core/platform/default:strong_hash.h", + "//tensorflow/core/platform/default:subprocess.cc", + "//tensorflow/core/platform/default:subprocess.h", + "//tensorflow/core/platform/default:tracing.cc", + "//tensorflow/core/platform/default:tracing_impl.h", + "//tensorflow/core/platform/default:unbounded_work_queue.cc", + "//tensorflow/core/platform/default:unbounded_work_queue.h", ], visibility = ["//tensorflow/core:__pkg__"], ) @@ -967,6 +928,14 @@ filegroup( visibility = ["//tensorflow/core:__pkg__"], ) +bzl_library( + name = "build_config_root_bzl", + srcs = [ + "build_config_root.bzl", + "//tensorflow/core/platform/default:build_config_root.bzl", + ], +) + # TODO(gunan): Remove the following once references in core/BUILD is removed. exports_files( glob( @@ -988,29 +957,17 @@ exports_files( ), ) -bzl_library( - name = "build_config_root_bzl", - srcs = [ - "build_config_root.bzl", - "default/build_config_root.bzl", - ], -) - -bzl_library( - name = "cuda_build_defs_bzl", - srcs = ["default/cuda_build_defs.bzl"], -) - exports_files( [ - "stacktrace.h", - "cpu_info.h", - "types.h", - "macros.h", "abi.h", - "logging.h", - "platform.h", "byte_order.h", + "cpu_info.cc", + "cpu_info.h", + "logging.h", + "macros.h", + "platform.h", + "stacktrace.h", + "types.h", ], visibility = ["//tensorflow:__subpackages__"], ) diff --git a/tensorflow/core/platform/build_config.bzl b/tensorflow/core/platform/build_config.bzl index cdd810aeacb..4ca965299e7 100644 --- a/tensorflow/core/platform/build_config.bzl +++ b/tensorflow/core/platform/build_config.bzl @@ -1,12 +1,11 @@ """Provides a redirection point for platform specific implementations of starlark utilities.""" load( - "//tensorflow/core/platform:default/build_config.bzl", + "//tensorflow/core/platform/default:build_config.bzl", _pyx_library = "pyx_library", _tf_additional_all_protos = "tf_additional_all_protos", _tf_additional_binary_deps = "tf_additional_binary_deps", _tf_additional_core_deps = "tf_additional_core_deps", - _tf_additional_cupti_test_flags = "tf_additional_cupti_test_flags", _tf_additional_cupti_utils_cuda_deps = "tf_additional_cupti_utils_cuda_deps", _tf_additional_device_tracer_srcs = "tf_additional_device_tracer_srcs", _tf_additional_env_hdrs = "tf_additional_env_hdrs", @@ -20,7 +19,11 @@ load( _tf_fingerprint_deps = "tf_fingerprint_deps", _tf_jspb_proto_library = "tf_jspb_proto_library", _tf_kernel_tests_linkstatic = "tf_kernel_tests_linkstatic", + _tf_legacy_srcs_no_runtime_google = "tf_legacy_srcs_no_runtime_google", _tf_lib_proto_parsing_deps = "tf_lib_proto_parsing_deps", + _tf_logging_deps = "tf_logging_deps", + _tf_monitoring_deps = "tf_monitoring_deps", + _tf_platform_deps = "tf_platform_deps", _tf_proto_library = "tf_proto_library", _tf_proto_library_cc = "tf_proto_library_cc", _tf_proto_library_py = "tf_proto_library_py", @@ -33,13 +36,13 @@ load( _tf_protos_profiler_impl = "tf_protos_profiler_impl", _tf_py_clif_cc = "tf_py_clif_cc", _tf_pyclif_proto_library = "tf_pyclif_proto_library", + _tf_windows_aware_platform_deps = "tf_windows_aware_platform_deps", ) pyx_library = _pyx_library tf_additional_all_protos = _tf_additional_all_protos tf_additional_binary_deps = _tf_additional_binary_deps tf_additional_core_deps = _tf_additional_core_deps -tf_additional_cupti_test_flags = _tf_additional_cupti_test_flags tf_additional_cupti_utils_cuda_deps = _tf_additional_cupti_utils_cuda_deps tf_additional_device_tracer_srcs = _tf_additional_device_tracer_srcs tf_additional_env_hdrs = _tf_additional_env_hdrs @@ -53,7 +56,11 @@ tf_additional_test_srcs = _tf_additional_test_srcs tf_fingerprint_deps = _tf_fingerprint_deps tf_jspb_proto_library = _tf_jspb_proto_library tf_kernel_tests_linkstatic = _tf_kernel_tests_linkstatic +tf_legacy_srcs_no_runtime_google = _tf_legacy_srcs_no_runtime_google tf_lib_proto_parsing_deps = _tf_lib_proto_parsing_deps +tf_logging_deps = _tf_logging_deps +tf_monitoring_deps = _tf_monitoring_deps +tf_platform_deps = _tf_platform_deps tf_proto_library = _tf_proto_library tf_proto_library_cc = _tf_proto_library_cc tf_proto_library_py = _tf_proto_library_py @@ -66,3 +73,4 @@ tf_protos_grappler_impl = _tf_protos_grappler_impl tf_protos_profiler_impl = _tf_protos_profiler_impl tf_py_clif_cc = _tf_py_clif_cc tf_pyclif_proto_library = _tf_pyclif_proto_library +tf_windows_aware_platform_deps = _tf_windows_aware_platform_deps diff --git a/tensorflow/core/platform/build_config_root.bzl b/tensorflow/core/platform/build_config_root.bzl index 6a09333e4c5..05b38bbeca2 100644 --- a/tensorflow/core/platform/build_config_root.bzl +++ b/tensorflow/core/platform/build_config_root.bzl @@ -1,7 +1,7 @@ """Provides a redirection point for platform specific implementations of starlark utilities.""" load( - "//tensorflow/core/platform:default/build_config_root.bzl", + "//tensorflow/core/platform/default:build_config_root.bzl", _if_dynamic_kernels = "if_dynamic_kernels", _if_static = "if_static", _if_static_and_not_mobile = "if_static_and_not_mobile", diff --git a/tensorflow/core/platform/default/BUILD b/tensorflow/core/platform/default/BUILD new file mode 100644 index 00000000000..04893ec9243 --- /dev/null +++ b/tensorflow/core/platform/default/BUILD @@ -0,0 +1,495 @@ +# Tensorflow default + linux implementations of tensorflow/core/platform libraries. +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("//tensorflow:tensorflow.bzl", "tf_copts") + +package( + default_visibility = [ + "//tensorflow/core/platform:__pkg__", + ], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "context", + hdrs = ["//tensorflow/core/platform:context.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["context.h"], + deps = ["//tensorflow/core/platform"], +) + +cc_library( + name = "cord", + hdrs = ["cord.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], +) + +cc_library( + name = "cuda_libdevice_path", + srcs = ["cuda_libdevice_path.cc"], + hdrs = ["//tensorflow/core/platform:cuda_libdevice_path.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:path", + "//tensorflow/core/platform:types", + "@local_config_cuda//cuda:cuda_headers", + ], +) + +cc_library( + name = "dynamic_annotations", + hdrs = ["dynamic_annotations.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], +) + +cc_library( + name = "env", + srcs = [ + "env.cc", + "posix_file_system.cc", + "posix_file_system.h", + "//tensorflow/core/platform:env.cc", + "//tensorflow/core/platform:file_system.cc", + "//tensorflow/core/platform:file_system_helper.cc", + "//tensorflow/core/platform:threadpool.cc", + ], + hdrs = [ + "//tensorflow/core/platform:env.h", + "//tensorflow/core/platform:file_system.h", + "//tensorflow/core/platform:file_system_helper.h", + "//tensorflow/core/platform:threadpool.h", + ], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/lib/core:error_codes_proto_cc", + "//tensorflow/core/lib/core:stringpiece", + "//tensorflow/core/platform", + "//tensorflow/core/platform:blocking_counter", + "//tensorflow/core/platform:context", + "//tensorflow/core/platform:cord", + "//tensorflow/core/platform:denormal", + "//tensorflow/core/platform:env_time", + "//tensorflow/core/platform:error", + "//tensorflow/core/platform:errors", + "//tensorflow/core/platform:file_statistics", + "//tensorflow/core/platform:load_library", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:mutex", + "//tensorflow/core/platform:path", + "//tensorflow/core/platform:platform_port", + "//tensorflow/core/platform:protobuf", + "//tensorflow/core/platform:setround", + "//tensorflow/core/platform:status", + "//tensorflow/core/platform:str_util", + "//tensorflow/core/platform:strcat", + "//tensorflow/core/platform:stringpiece", + "//tensorflow/core/platform:stringprintf", + "//tensorflow/core/platform:threadpool_interface", + "//tensorflow/core/platform:tracing", + "//tensorflow/core/platform:types", + "//third_party/eigen3", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + ], +) + +cc_library( + name = "env_time", + srcs = ["env_time.cc"], + hdrs = ["//tensorflow/core/platform:env_time.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = ["//tensorflow/core/platform:types"], +) + +cc_library( + name = "human_readable_json", + srcs = ["human_readable_json.cc"], + hdrs = ["//tensorflow/core/platform:human_readable_json.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:errors", + "//tensorflow/core/platform:protobuf", + "//tensorflow/core/platform:status", + "//tensorflow/core/platform:strcat", + ], +) + +cc_library( + name = "load_library", + srcs = ["load_library.cc"], + hdrs = ["//tensorflow/core/platform:load_library.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:errors", + "//tensorflow/core/platform:status", + ], +) + +cc_library( + name = "logging", + srcs = ["logging.cc"], + hdrs = ["//tensorflow/core/platform:logging.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["logging.h"], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:env_time", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:types", + "@com_google_absl//absl/base", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "monitoring", + srcs = ["monitoring.cc"], + hdrs = ["//tensorflow/core/platform:monitoring.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], +) + +cc_library( + name = "mutex", + srcs = [ + "mutex.cc", + "mutex_data.h", + ], + hdrs = ["//tensorflow/core/platform:mutex.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["mutex.h"], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:thread_annotations", + "//tensorflow/core/platform:types", + "@nsync//:nsync_cpp", + ], +) + +cc_library( + name = "net", + srcs = ["net.cc"], + hdrs = ["//tensorflow/core/platform:net.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:strcat", + ], + alwayslink = True, +) + +cc_library( + name = "notification", + hdrs = ["notification.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:mutex", + "//tensorflow/core/platform:types", + ], +) + +cc_library( + name = "platform_port", + srcs = [ + "port.cc", + "//tensorflow/core/platform:cpu_info.cc", + ], + hdrs = [ + "//tensorflow/core/platform:cpu_info.h", + "//tensorflow/core/platform:demangle.h", + "//tensorflow/core/platform:host_info.h", + "//tensorflow/core/platform:init_main.h", + "//tensorflow/core/platform:mem.h", + "//tensorflow/core/platform:numa.h", + "//tensorflow/core/platform:snappy.h", + ], + copts = tf_copts(), + defines = ["TF_USE_SNAPPY"] + select({ + # TF Additional NUMA defines + "//tensorflow:with_numa_support": ["TENSORFLOW_USE_NUMA"], + "//conditions:default": [], + }), + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "@com_google_absl//absl/base", + "//tensorflow/core/platform:byte_order", + "//tensorflow/core/platform:dynamic_annotations", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:types", + "//tensorflow/core/platform", + "@snappy", + ] + select({ + # TF Additional NUMA dependencies + "//tensorflow:android": [], + "//tensorflow:ios": [], + "//tensorflow:macos": [], + "//conditions:default": [ + "@hwloc", + ], + }), +) + +cc_library( + name = "rocm_rocdl_path", + srcs = ["rocm_rocdl_path.cc"], + hdrs = ["//tensorflow/core/platform:rocm_rocdl_path.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:path", + "//tensorflow/core/platform:types", + "@local_config_rocm//rocm:rocm_headers", + ], +) + +cc_library( + name = "stacktrace", + hdrs = ["stacktrace.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:abi", + ], +) + +cc_library( + name = "stacktrace_handler", + srcs = ["stacktrace_handler.cc"], + hdrs = ["//tensorflow/core/platform:stacktrace_handler.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:stacktrace", + ], +) + +cc_library( + name = "strong_hash", + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["strong_hash.h"], + deps = ["@highwayhash//:sip_hash"], +) + +cc_library( + name = "subprocess", + srcs = ["subprocess.cc"], + hdrs = ["//tensorflow/core/platform:subprocess.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["subprocess.h"], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:mutex", + "//tensorflow/core/platform:types", + ], + alwayslink = True, +) + +cc_library( + name = "test_benchmark", + testonly = True, + srcs = ["test_benchmark.cc"], + hdrs = ["test_benchmark.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:env", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:types", + "//tensorflow/core/util:reporter", + ], +) + +cc_library( + name = "test", + testonly = True, + srcs = ["test.cc"], + hdrs = ["//tensorflow/core/platform:test.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:net", + "//tensorflow/core/platform:strcat", + "//tensorflow/core/platform:types", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "tracing", + srcs = [ + "tracing.cc", + "//tensorflow/core/platform:tracing.cc", + ], + hdrs = ["//tensorflow/core/platform:tracing.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["tracing_impl.h"], + deps = [ + "//tensorflow/core/lib/hash", + "//tensorflow/core/platform", + "//tensorflow/core/platform:hash", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:str_util", + "//tensorflow/core/platform:strcat", + "//tensorflow/core/platform:stringpiece", + "//tensorflow/core/platform:types", + ], + alwayslink = True, +) + +cc_library( + name = "types", + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["integral_types.h"], +) + +cc_library( + name = "unbounded_work_queue", + srcs = ["unbounded_work_queue.cc"], + hdrs = ["unbounded_work_queue.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/lib/core:notification", + "//tensorflow/core/platform:env", + "//tensorflow/core/platform:mutex", + "@com_google_absl//absl/memory", + ], +) + +bzl_library( + name = "cuda_build_defs_bzl", + srcs = ["cuda_build_defs.bzl"], + visibility = ["//tensorflow:__subpackages__"], +) + +package_group( + name = "core_and_platform_packages", + packages = [ + "//tensorflow/core", + "//tensorflow/core/platform", + ], +) + +exports_files( + srcs = glob( + ["*"], + exclude = [ + "integral_types.h", + "logging.h", + "test.cc", + "test_benchmark.cc", + ], + ), + visibility = ["//tensorflow/core/platform:__pkg__"], +) + +exports_files( + srcs = [ + "integral_types.h", + "logging.h", + "test.cc", + "test_benchmark.cc", + ], + visibility = [":core_and_platform_packages"], +) diff --git a/tensorflow/core/platform/default/build_config.bzl b/tensorflow/core/platform/default/build_config.bzl index a94114507d2..3c50725aa9e 100644 --- a/tensorflow/core/platform/default/build_config.bzl +++ b/tensorflow/core/platform/default/build_config.bzl @@ -554,30 +554,30 @@ def tf_platform_srcs(files): def tf_additional_lib_hdrs(): return [ - "//tensorflow/core/platform:default/context.h", - "//tensorflow/core/platform:default/cord.h", - "//tensorflow/core/platform:default/dynamic_annotations.h", - "//tensorflow/core/platform:default/integral_types.h", - "//tensorflow/core/platform:default/logging.h", - "//tensorflow/core/platform:default/mutex.h", - "//tensorflow/core/platform:default/mutex_data.h", - "//tensorflow/core/platform:default/notification.h", - "//tensorflow/core/platform:default/stacktrace.h", - "//tensorflow/core/platform:default/strong_hash.h", - "//tensorflow/core/platform:default/test_benchmark.h", - "//tensorflow/core/platform:default/tracing_impl.h", - "//tensorflow/core/platform:default/unbounded_work_queue.h", + "//tensorflow/core/platform/default:context.h", + "//tensorflow/core/platform/default:cord.h", + "//tensorflow/core/platform/default:dynamic_annotations.h", + "//tensorflow/core/platform/default:integral_types.h", + "//tensorflow/core/platform/default:logging.h", + "//tensorflow/core/platform/default:mutex.h", + "//tensorflow/core/platform/default:mutex_data.h", + "//tensorflow/core/platform/default:notification.h", + "//tensorflow/core/platform/default:stacktrace.h", + "//tensorflow/core/platform/default:strong_hash.h", + "//tensorflow/core/platform/default:test_benchmark.h", + "//tensorflow/core/platform/default:tracing_impl.h", + "//tensorflow/core/platform/default:unbounded_work_queue.h", ] + select({ "//tensorflow:windows": [ - "//tensorflow/core/platform:windows/intrinsics_port.h", - "//tensorflow/core/platform:windows/stacktrace.h", - "//tensorflow/core/platform:windows/subprocess.h", - "//tensorflow/core/platform:windows/wide_char.h", - "//tensorflow/core/platform:windows/windows_file_system.h", + "//tensorflow/core/platform/windows:intrinsics_port.h", + "//tensorflow/core/platform/windows:stacktrace.h", + "//tensorflow/core/platform/windows:subprocess.h", + "//tensorflow/core/platform/windows:wide_char.h", + "//tensorflow/core/platform/windows:windows_file_system.h", ], "//conditions:default": [ - "//tensorflow/core/platform:default/posix_file_system.h", - "//tensorflow/core/platform:default/subprocess.h", + "//tensorflow/core/platform/default:posix_file_system.h", + "//tensorflow/core/platform/default:subprocess.h", ], }) @@ -621,16 +621,13 @@ def tf_additional_device_tracer_srcs(): def tf_additional_cupti_utils_cuda_deps(): return [] -def tf_additional_cupti_test_flags(): - return [] - def tf_additional_test_deps(): return [] def tf_additional_test_srcs(): return [ - "default/test.cc", - "default/test_benchmark.cc", + "//tensorflow/core/platform/default:test.cc", + "//tensorflow/core/platform/default:test_benchmark.cc", ] def tf_kernel_tests_linkstatic(): @@ -745,3 +742,25 @@ def tf_protobuf_compiler_deps(): ], otherwise = [clean_dep("@com_google_protobuf//:protobuf_headers")], ) + +def tf_windows_aware_platform_deps(name): + return select({ + "//tensorflow:windows": [ + "//tensorflow/core/platform/windows:" + name, + ], + "//conditions:default": [ + "//tensorflow/core/platform/default:" + name, + ], + }) + +def tf_platform_deps(name): + return ["//tensorflow/core/platform/default:" + name] + +def tf_logging_deps(): + return ["//tensorflow/core/platform/default:logging"] + +def tf_monitoring_deps(): + return ["//tensorflow/core/platform/default:monitoring"] + +def tf_legacy_srcs_no_runtime_google(): + return [] diff --git a/tensorflow/core/platform/default/build_refactor.bzl b/tensorflow/core/platform/default/build_refactor.bzl deleted file mode 100644 index 76033a880ae..00000000000 --- a/tensorflow/core/platform/default/build_refactor.bzl +++ /dev/null @@ -1,661 +0,0 @@ -""" -Build targets for default implementations of tf/core/platform libraries. -""" -# This is a temporary hack to mimic the presence of a BUILD file under -# tensorflow/core/platform/default. This is part of a large refactoring -# of BUILD rules under tensorflow/core/platform. We will remove this file -# and add real BUILD files under tensorflow/core/platform/default and -# tensorflow/core/platform/windows after the refactoring is complete. - -load( - "//tensorflow:tensorflow.bzl", - "tf_copts", -) - -TF_DEFAULT_PLATFORM_LIBRARIES = { - "context": { - "name": "context_impl", - "hdrs": ["//tensorflow/core/platform:context.h"], - "textual_hdrs": ["//tensorflow/core/platform:default/context.h"], - "deps": [ - "//tensorflow/core/platform", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "cord": { - "name": "cord_impl", - "hdrs": ["//tensorflow/core/platform:default/cord.h"], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "cuda_libdevice_path": { - "name": "cuda_libdevice_path_impl", - "hdrs": [ - "//tensorflow/core/platform:cuda_libdevice_path.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/cuda_libdevice_path.cc", - ], - "deps": [ - "@local_config_cuda//cuda:cuda_headers", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:path", - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "dynamic_annotations": { - "name": "dynamic_annotations_impl", - "hdrs": [ - "//tensorflow/core/platform:default/dynamic_annotations.h", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "env": { - "name": "env_impl", - "hdrs": [ - "//tensorflow/core/platform:env.h", - "//tensorflow/core/platform:file_system.h", - "//tensorflow/core/platform:file_system_helper.h", - "//tensorflow/core/platform:threadpool.h", - ], - "srcs": [ - "//tensorflow/core/platform:env.cc", - "//tensorflow/core/platform:file_system.cc", - "//tensorflow/core/platform:file_system_helper.cc", - "//tensorflow/core/platform:threadpool.cc", - "//tensorflow/core/platform:default/env.cc", - "//tensorflow/core/platform:default/posix_file_system.h", - "//tensorflow/core/platform:default/posix_file_system.cc", - ], - "deps": [ - "@com_google_absl//absl/time", - "@com_google_absl//absl/types:optional", - "//third_party/eigen3", - "//tensorflow/core/lib/core:error_codes_proto_cc", - "//tensorflow/core/lib/core:stringpiece", - "//tensorflow/core/platform", - "//tensorflow/core/platform:blocking_counter", - "//tensorflow/core/platform:context", - "//tensorflow/core/platform:cord", - "//tensorflow/core/platform:denormal", - "//tensorflow/core/platform:error", - "//tensorflow/core/platform:errors", - "//tensorflow/core/platform:env_time", - "//tensorflow/core/platform:file_statistics", - "//tensorflow/core/platform:load_library", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:mutex", - "//tensorflow/core/platform:path", - "//tensorflow/core/platform:platform_port", - "//tensorflow/core/platform:protobuf", - "//tensorflow/core/platform:setround", - "//tensorflow/core/platform:status", - "//tensorflow/core/platform:stringpiece", - "//tensorflow/core/platform:stringprintf", - "//tensorflow/core/platform:strcat", - "//tensorflow/core/platform:str_util", - "//tensorflow/core/platform:threadpool_interface", - "//tensorflow/core/platform:tracing", - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "env_time": { - "name": "env_time_impl", - "hdrs": [ - "//tensorflow/core/platform:env_time.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/env_time.cc", - ], - "deps": [ - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "human_readable_json": { - "name": "human_readable_json_impl", - "hdrs": [ - "//tensorflow/core/platform:human_readable_json.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/human_readable_json.cc", - ], - "deps": [ - "//tensorflow/core/platform:errors", - "//tensorflow/core/platform:protobuf", - "//tensorflow/core/platform:status", - "//tensorflow/core/platform:strcat", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "load_library": { - "name": "load_library_impl", - "hdrs": [ - "//tensorflow/core/platform:load_library.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/load_library.cc", - ], - "deps": [ - "//tensorflow/core/platform:errors", - "//tensorflow/core/platform:status", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "logging": { - "name": "logging_impl", - "hdrs": [ - "//tensorflow/core/platform:logging.h", - ], - "textual_hdrs": [ - "//tensorflow/core/platform:default/logging.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/logging.cc", - ], - "deps": [ - "@com_google_absl//absl/base", - "@com_google_absl//absl/strings", - "//tensorflow/core/platform", - "//tensorflow/core/platform:env_time", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "monitoring": { - "name": "monitoring_impl", - "hdrs": [ - "//tensorflow/core/platform:monitoring.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/monitoring.cc", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "mutex": { - "name": "mutex_impl", - "hdrs": [ - "//tensorflow/core/platform:mutex.h", - ], - "textual_hdrs": [ - "//tensorflow/core/platform:default/mutex.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/mutex.cc", - "//tensorflow/core/platform:default/mutex_data.h", - ], - "deps": [ - "@nsync//:nsync_cpp", - "//tensorflow/core/platform", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:thread_annotations", - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "net": { - "name": "net_impl", - "hdrs": [ - "//tensorflow/core/platform:net.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/net.cc", - ], - "deps": [ - "//tensorflow/core/platform:strcat", - "//tensorflow/core/platform:logging", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - "alwayslink": 1, - }, - "notification": { - "name": "notification_impl", - "hdrs": [ - "//tensorflow/core/platform:default/notification.h", - ], - "deps": [ - "//tensorflow/core/platform:mutex", - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "rocm_rocdl_path": { - "name": "rocm_rocdl_path_impl", - "hdrs": [ - "//tensorflow/core/platform:rocm_rocdl_path.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/rocm_rocdl_path.cc", - ], - "deps": [ - "@local_config_rocm//rocm:rocm_headers", - "//tensorflow/core/platform:path", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "stacktrace": { - "name": "stacktrace_impl", - "hdrs": [ - "//tensorflow/core/platform:default/stacktrace.h", - ], - "deps": [ - "//tensorflow/core/platform:abi", - "//tensorflow/core/platform:platform", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - }, - "stacktrace_handler": { - "name": "stacktrace_handler_impl", - "hdrs": [ - "//tensorflow/core/platform:stacktrace_handler.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/stacktrace_handler.cc", - ], - "deps": [ - "//tensorflow/core/platform", - "//tensorflow/core/platform:stacktrace", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - }, - "strong_hash": { - "name": "strong_hash_impl", - "textual_hdrs": [ - "//tensorflow/core/platform:default/strong_hash.h", - ], - "deps": [ - "@highwayhash//:sip_hash", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual"], - }, - "subprocess": { - "name": "subprocess_impl", - "textual_hdrs": [ - "//tensorflow/core/platform:default/subprocess.h", - ], - "hdrs": [ - "//tensorflow/core/platform:subprocess.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/subprocess.cc", - ], - "deps": [ - "//tensorflow/core/platform", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:mutex", - "//tensorflow/core/platform:types", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - "alwayslink": 1, - }, - "test": { - "name": "test_impl", - "testonly": True, - "srcs": [ - "//tensorflow/core/platform:default/test.cc", - ], - "hdrs": [ - "//tensorflow/core/platform:test.h", - ], - "deps": [ - "@com_google_googletest//:gtest", - "//tensorflow/core/platform", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:net", - "//tensorflow/core/platform:strcat", - "//tensorflow/core/platform:types", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - }, - "test_benchmark": { - "name": "test_benchmark_impl", - "testonly": True, - "srcs": [ - "//tensorflow/core/platform:default/test_benchmark.cc", - ], - "hdrs": [ - "//tensorflow/core/platform:default/test_benchmark.h", - ], - "deps": [ - "//tensorflow/core/platform", - "//tensorflow/core/platform:env", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:types", - "//tensorflow/core:util_reporter", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - }, - "tracing": { - "name": "tracing_impl", - "textual_hdrs": [ - "//tensorflow/core/platform:default/tracing_impl.h", - ], - "hdrs": [ - "//tensorflow/core/platform:tracing.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/tracing.cc", - "//tensorflow/core/platform:tracing.cc", - ], - "deps": [ - "//tensorflow/core/lib/hash", - "//tensorflow/core/platform", - "//tensorflow/core/platform:hash", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:strcat", - "//tensorflow/core/platform:str_util", - "//tensorflow/core/platform:stringpiece", - "//tensorflow/core/platform:types", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - "alwayslink": 1, - }, - "types": { - "name": "types_impl", - "textual_hdrs": [ - "//tensorflow/core/platform:default/integral_types.h", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - }, - "unbounded_work_queue": { - "name": "unbounded_work_queue_impl", - "hdrs": [ - "//tensorflow/core/platform:default/unbounded_work_queue.h", - ], - "srcs": [ - "//tensorflow/core/platform:default/unbounded_work_queue.cc", - ], - "deps": [ - "@com_google_absl//absl/memory", - "//tensorflow/core/platform:env", - "//tensorflow/core/platform:mutex", - "//tensorflow/core/lib/core:notification", - ], - "tags": ["no_oss", "manual"], - "visibility": ["//visibility:private"], - }, -} - -TF_WINDOWS_PLATFORM_LIBRARIES = { - "env": { - "name": "windows_env_impl", - "hdrs": [ - "//tensorflow/core/platform:env.h", - "//tensorflow/core/platform:file_system.h", - "//tensorflow/core/platform:file_system_helper.h", - "//tensorflow/core/platform:threadpool.h", - ], - "srcs": [ - "//tensorflow/core/platform:env.cc", - "//tensorflow/core/platform:file_system.cc", - "//tensorflow/core/platform:file_system_helper.cc", - "//tensorflow/core/platform:threadpool.cc", - "//tensorflow/core/platform:windows/env.cc", - "//tensorflow/core/platform:windows/windows_file_system.h", - "//tensorflow/core/platform:windows/windows_file_system.cc", - ], - "deps": [ - "@com_google_absl//absl/time", - "@com_google_absl//absl/types:optional", - "//third_party/eigen3", - "//tensorflow/core/lib/core:blocking_counter", - "//tensorflow/core/lib/core:error_codes_proto_cc", - "//tensorflow/core/lib/core:stringpiece", - "//tensorflow/core/platform", - "//tensorflow/core/platform:context", - "//tensorflow/core/platform:cord", - "//tensorflow/core/platform:denormal", - "//tensorflow/core/platform:error", - "//tensorflow/core/platform:errors", - "//tensorflow/core/platform:env_time", - "//tensorflow/core/platform:file_statistics", - "//tensorflow/core/platform:load_library", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:mutex", - "//tensorflow/core/platform:path", - "//tensorflow/core/platform:platform_port", - "//tensorflow/core/platform:protobuf", - "//tensorflow/core/platform:setround", - "//tensorflow/core/platform:status", - "//tensorflow/core/platform:stringpiece", - "//tensorflow/core/platform:stringprintf", - "//tensorflow/core/platform:strcat", - "//tensorflow/core/platform:str_util", - "//tensorflow/core/platform:threadpool_interface", - "//tensorflow/core/platform:tracing", - "//tensorflow/core/platform:types", - "//tensorflow/core/platform:windows_wide_char_impl", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual", "nobuilder"], - }, - "env_time": { - "name": "windows_env_time_impl", - "hdrs": [ - "//tensorflow/core/platform:env_time.h", - ], - "srcs": [ - "//tensorflow/core/platform:windows/env_time.cc", - ], - "deps": [ - "//tensorflow/core/platform:types", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual", "nobuilder"], - }, - "load_library": { - "name": "windows_load_library_impl", - "hdrs": [ - "//tensorflow/core/platform:load_library.h", - ], - "srcs": [ - "//tensorflow/core/platform:windows/load_library.cc", - ], - "deps": [ - "//tensorflow/core/platform:errors", - "//tensorflow/core/platform:status", - "//tensorflow/core/platform:windows_wide_char_impl", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual", "nobuilder"], - }, - "net": { - "name": "windows_net_impl", - "hdrs": [ - "//tensorflow/core/platform:net.h", - ], - "srcs": [ - "//tensorflow/core/platform:windows/net.cc", - ], - "deps": [ - "//tensorflow/core/platform:error", - "//tensorflow/core/platform:logging", - ], - "visibility": ["//visibility:private"], - "tags": ["no_oss", "manual", "nobuilder"], - }, - "stacktrace": { - "name": "windows_stacktrace_impl", - "hdrs": [ - "//tensorflow/core/platform:windows/stacktrace.h", - ], - "srcs": [ - "//tensorflow/core/platform:windows/stacktrace.cc", - ], - "deps": [ - "//tensorflow/core/platform:mutex", - ], - "tags": ["no_oss", "manual", "nobuilder"], - "visibility": ["//visibility:private"], - }, - "stacktrace_handler": { - "name": "windows_stacktrace_handler_impl", - "hdrs": [ - "//tensorflow/core/platform:stacktrace_handler.h", - ], - "srcs": [ - "//tensorflow/core/platform:windows/stacktrace_handler.cc", - ], - "deps": [ - "//tensorflow/core/platform:mutex", - "//tensorflow/core/platform:stacktrace", - "//tensorflow/core/platform:types", - ], - "tags": ["no_oss", "manual", "nobuilder"], - "visibility": ["//visibility:private"], - }, - "subprocess": { - "name": "windows_subprocess_impl", - "textual_hdrs": [ - "//tensorflow/core/platform:windows/subprocess.h", - ], - "hdrs": [ - "//tensorflow/core/platform:subprocess.h", - ], - "deps": [ - "//tensorflow/core/platform", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:macros", - "//tensorflow/core/platform:types", - ], - "tags": ["no_oss", "manual", "nobuilder"], - "visibility": ["//visibility:private"], - }, - "wide_char": { - "name": "windows_wide_char_impl", - "hdrs": [ - "//tensorflow/core/platform:windows/wide_char.h", - ], - "tags": ["no_oss", "manual", "nobuilder"], - "visibility": ["//visibility:private"], - }, -} - -def tf_instantiate_platform_libraries(names = []): - for name in names: - # Unfortunately, this target cannot be represented as a dictionary - # because it uses "select" - if name == "platform_port": - native.cc_library( - name = "platform_port_impl", - srcs = [ - "//tensorflow/core/platform:cpu_info.cc", - "//tensorflow/core/platform:default/port.cc", - ], - hdrs = [ - "//tensorflow/core/platform:cpu_info.h", - "//tensorflow/core/platform:demangle.h", - "//tensorflow/core/platform:host_info.h", - "//tensorflow/core/platform:init_main.h", - "//tensorflow/core/platform:mem.h", - "//tensorflow/core/platform:numa.h", - "//tensorflow/core/platform:snappy.h", - ], - defines = ["TF_USE_SNAPPY"] + select({ - # TF Additional NUMA defines - "//tensorflow:with_numa_support": ["TENSORFLOW_USE_NUMA"], - "//conditions:default": [], - }), - copts = tf_copts(), - deps = [ - "@com_google_absl//absl/base", - "//tensorflow/core/platform:byte_order", - "//tensorflow/core/platform:dynamic_annotations", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:types", - "//tensorflow/core/platform", - "@snappy", - ] + select({ - # TF Additional NUMA dependencies - "//tensorflow:android": [], - "//tensorflow:ios": [], - "//tensorflow:macos": [], - "//conditions:default": [ - "@hwloc", - ], - }), - visibility = ["//visibility:private"], - tags = ["no_oss", "manual"], - ) - native.cc_library( - name = "windows_platform_port_impl", - srcs = [ - "//tensorflow/core/platform:cpu_info.cc", - "//tensorflow/core/platform:windows/port.cc", - ], - hdrs = [ - "//tensorflow/core/platform:cpu_info.h", - "//tensorflow/core/platform:demangle.h", - "//tensorflow/core/platform:host_info.h", - "//tensorflow/core/platform:init_main.h", - "//tensorflow/core/platform:mem.h", - "//tensorflow/core/platform:numa.h", - "//tensorflow/core/platform:snappy.h", - ], - defines = ["TF_USE_SNAPPY"], - copts = tf_copts(), - deps = [ - "//tensorflow/core/platform", - "//tensorflow/core/platform:byte_order", - "//tensorflow/core/platform:dynamic_annotations", - "//tensorflow/core/platform:logging", - "//tensorflow/core/platform:types", - "@snappy", - ], - visibility = ["//visibility:private"], - tags = ["no_oss", "manual"], - ) - else: - if name in TF_DEFAULT_PLATFORM_LIBRARIES: - native.cc_library(**TF_DEFAULT_PLATFORM_LIBRARIES[name]) - if name in TF_WINDOWS_PLATFORM_LIBRARIES: - native.cc_library(**TF_WINDOWS_PLATFORM_LIBRARIES[name]) - -def tf_mobile_aware_deps(name): - return [":" + name] - -def tf_platform_helper_deps(name): - return select({ - "//tensorflow:windows": [":windows_" + name], - "//conditions:default": [":" + name], - }) - -def tf_logging_deps(): - return [":logging_impl"] - -def tf_monitoring_deps(): - return [":monitoring_impl"] - -def tf_legacy_srcs_no_runtime_google(): - return [] diff --git a/tensorflow/core/platform/default/distribute.bzl b/tensorflow/core/platform/default/distribute.bzl index ea8fa8708e4..0133623a427 100644 --- a/tensorflow/core/platform/default/distribute.bzl +++ b/tensorflow/core/platform/default/distribute.bzl @@ -33,7 +33,7 @@ def distribute_py_test( srcs = srcs, data = data, main = main, - additional_deps = deps, + deps = deps, shard_count = shard_count, tags = tags, args = args, diff --git a/tensorflow/core/platform/env_test.cc b/tensorflow/core/platform/env_test.cc index 1f4bd7c6a79..8da2fc99e08 100644 --- a/tensorflow/core/platform/env_test.cc +++ b/tensorflow/core/platform/env_test.cc @@ -319,13 +319,27 @@ class TmpDirFileSystem : public NullFileSystem { if (host != "testhost") { return errors::FailedPrecondition("host must be testhost"); } - return Env::Default()->CreateDir(io::JoinPath(BaseDir(), path)); + Status status = Env::Default()->CreateDir(io::JoinPath(BaseDir(), path)); + if (status.ok()) { + // Record that we have created this directory so `IsDirectory` works. + created_directories_.push_back(std::string(path)); + } + return status; + } + + Status IsDirectory(const string& dir) override { + StringPiece scheme, host, path; + io::ParseURI(dir, &scheme, &host, &path); + for (const auto& existing_dir : created_directories_) + if (existing_dir == path) return Status::OK(); + return errors::NotFound(dir, " not found"); } void FlushCaches() override { flushed_ = true; } private: bool flushed_ = false; + std::vector created_directories_ = {"/"}; }; REGISTER_FILE_SYSTEM("tmpdirfs", TmpDirFileSystem); diff --git a/tensorflow/core/platform/file_system.cc b/tensorflow/core/platform/file_system.cc index fb013b82570..203076b05f4 100644 --- a/tensorflow/core/platform/file_system.cc +++ b/tensorflow/core/platform/file_system.cc @@ -87,6 +87,14 @@ Status FileSystem::DeleteRecursively(const string& dirname, (*undeleted_dirs)++; return exists_status; } + + // If given path to a single file, we should just delete it. + if (!IsDirectory(dirname).ok()) { + Status delete_root_status = DeleteFile(dirname); + if (!delete_root_status.ok()) (*undeleted_files)++; + return delete_root_status; + } + std::deque dir_q; // Queue for the BFS std::vector dir_list; // List of all dirs discovered dir_q.push_back(dirname); @@ -142,12 +150,23 @@ Status FileSystem::RecursivelyCreateDir(const string& dirname) { io::ParseURI(dirname, &scheme, &host, &remaining_dir); std::vector sub_dirs; while (!remaining_dir.empty()) { - Status status = FileExists(io::CreateURI(scheme, host, remaining_dir)); - if (status.ok()) { - break; + std::string current_entry = io::CreateURI(scheme, host, remaining_dir); + Status exists_status = FileExists(current_entry); + if (exists_status.ok()) { + // FileExists cannot differentiate between existence of a file or a + // directory, hence we need an additional test as we must not assume that + // a path to a file is a path to a parent directory. + Status directory_status = IsDirectory(current_entry); + if (directory_status.ok()) { + break; // We need to start creating directories from here. + } else if (directory_status.code() == tensorflow::error::UNIMPLEMENTED) { + return directory_status; + } else { + return errors::FailedPrecondition(remaining_dir, " is not a directory"); + } } - if (status.code() != error::Code::NOT_FOUND) { - return status; + if (exists_status.code() != error::Code::NOT_FOUND) { + return exists_status; } // Basename returns "" for / ending dirs. if (!str_util::EndsWith(remaining_dir, "/")) { diff --git a/tensorflow/core/platform/file_system_test.cc b/tensorflow/core/platform/file_system_test.cc index 278561f4f0d..a245c8f8769 100644 --- a/tensorflow/core/platform/file_system_test.cc +++ b/tensorflow/core/platform/file_system_test.cc @@ -255,10 +255,10 @@ TEST(InterPlanetaryFileSystemTest, RecursivelyCreateAlreadyExistingDir) { InterPlanetaryFileSystem ipfs; const string dirname = io::JoinPath(kPrefix, "match-00/abc/00"); TF_EXPECT_OK(ipfs.RecursivelyCreateDir(dirname)); - // Ensure that CreateDir throws an error, to sanity check that this test - // actually tests the behavior of RecursivelyCreateDir. - EXPECT_EQ(ipfs.CreateDir(dirname).code(), tensorflow::error::ALREADY_EXISTS); - TF_EXPECT_OK(ipfs.RecursivelyCreateDir(dirname)); + // We no longer check for recursively creating the directory again because + // `ipfs.IsDirectory` is badly implemented, fixing it will break other tests + // in this suite and we already test creating the directory again in + // env_test.cc as well as in the modular filesystem tests. } // A simple file system with a root directory and a single file underneath it. diff --git a/tensorflow/core/platform/rules_cc.bzl b/tensorflow/core/platform/rules_cc.bzl index e664512aa4c..7342d5ea545 100644 --- a/tensorflow/core/platform/rules_cc.bzl +++ b/tensorflow/core/platform/rules_cc.bzl @@ -1,7 +1,7 @@ """Provides an indirection layer to bazel cc_rules""" load( - "//tensorflow/core/platform:default/rules_cc.bzl", + "//tensorflow/core/platform/default:rules_cc.bzl", _cc_binary = "cc_binary", _cc_import = "cc_import", _cc_library = "cc_library", diff --git a/tensorflow/core/platform/tstring.h b/tensorflow/core/platform/tstring.h index e14fdf2826f..68f19d3f767 100644 --- a/tensorflow/core/platform/tstring.h +++ b/tensorflow/core/platform/tstring.h @@ -30,13 +30,11 @@ limitations under the License. // forward declaration and associated templates. namespace absl { class string_view; -} - +class AlphaNum; #ifdef PLATFORM_GOOGLE -// TODO(dero): Move above to 'namespace absl' when absl moves Cord out of global -// namepace. class Cord; #endif // PLATFORM_GOOGLE +} // namespace absl namespace tensorflow { @@ -68,6 +66,9 @@ class tstring { }; public: + typedef char* iterator; + typedef const char* const_iterator; + tstring() = default; tstring(const tstring&) = default; @@ -86,8 +87,9 @@ class tstring { explicit tstring(const T& str) : str_(str.data(), str.size()) {} #ifdef PLATFORM_GOOGLE - template ::value, - T>::type* = nullptr> + template ::value, + T>::type* = nullptr> explicit tstring(const T& cord) : str_(string(cord)) {} #endif // PLATFORM_GOOGLE @@ -113,8 +115,9 @@ class tstring { } #ifdef PLATFORM_GOOGLE - template ::value, - T>::type* = nullptr> + template ::value, + T>::type* = nullptr> tstring& operator=(const T& cord) { str_ = string(cord); @@ -128,6 +131,12 @@ class tstring { return *this; } + tstring& operator=(char ch) { + str_ = ch; + + return *this; + } + tstring& operator=(tstring&&) noexcept = default; bool operator<(const tstring& o) const { return str_ < o.str_; } @@ -151,6 +160,13 @@ class tstring { return T(str_.data(), str_.size()); } + template ::value, + T>::type* = nullptr> + operator T() const { + return T(str_); + } + bool empty() const { return str_.empty(); } size_t length() const { return str_.length(); } @@ -163,18 +179,26 @@ class tstring { const char* data() const { return str_.data(); } + const_iterator begin() const { return data(); } + const_iterator end() const { return data() + size(); } + char back() const { return str_.back(); } const char& operator[](size_t i) const { return str_[i]; } char* data() { return &str_[0]; } + iterator begin() { return data(); } + iterator end() { return data() + size(); } + char& operator[](size_t i) { return str_[i]; } void clear() noexcept { str_.clear(); } void resize(size_t new_size) { str_.resize(new_size); } + void resize(size_t new_size, char c) { str_.resize(new_size, c); } + void resize_uninitialized(size_t new_size) { ResizeUninitialized::Resize(str_, new_size); } @@ -194,7 +218,7 @@ class tstring { } tstring& append(const tstring& str) { - str_.append(str); + str_.append(str.str_); return *this; } @@ -211,6 +235,12 @@ class tstring { return *this; } + tstring& append(size_t n, char c) { + str_.append(n, c); + + return *this; + } + void swap(tstring& str) { str_.swap(str.str_); } tstring& insert(size_t pos, const tstring& str, size_t subpos, @@ -220,6 +250,18 @@ class tstring { return *this; } + tstring& insert(size_t pos, size_t n, char c) { + str_.insert(pos, n, c); + + return *this; + } + + tstring& erase(size_t pos, size_t len) { + str_.erase(pos, len); + + return *this; + } + void push_back(char ch) { str_.push_back(ch); } friend const tstring operator+(const tstring& a, const tstring& b); diff --git a/tensorflow/core/platform/windows/BUILD b/tensorflow/core/platform/windows/BUILD new file mode 100644 index 00000000000..397217ca365 --- /dev/null +++ b/tensorflow/core/platform/windows/BUILD @@ -0,0 +1,215 @@ +# Tensorflow windows-specific implementations of tensorflow/core/platform libraries. +load( + "//tensorflow:tensorflow.bzl", + "tf_copts", +) + +package( + default_visibility = [ + "//tensorflow/core/platform:__pkg__", + ], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "env", + srcs = [ + "env.cc", + "windows_file_system.cc", + "windows_file_system.h", + "//tensorflow/core/platform:env.cc", + "//tensorflow/core/platform:file_system.cc", + "//tensorflow/core/platform:file_system_helper.cc", + "//tensorflow/core/platform:threadpool.cc", + ], + hdrs = [ + "//tensorflow/core/platform:env.h", + "//tensorflow/core/platform:file_system.h", + "//tensorflow/core/platform:file_system_helper.h", + "//tensorflow/core/platform:threadpool.h", + ], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + ":wide_char", + "//tensorflow/core/lib/core:blocking_counter", + "//tensorflow/core/lib/core:error_codes_proto_cc", + "//tensorflow/core/lib/core:stringpiece", + "//tensorflow/core/platform", + "//tensorflow/core/platform:context", + "//tensorflow/core/platform:cord", + "//tensorflow/core/platform:denormal", + "//tensorflow/core/platform:env_time", + "//tensorflow/core/platform:error", + "//tensorflow/core/platform:errors", + "//tensorflow/core/platform:file_statistics", + "//tensorflow/core/platform:load_library", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:mutex", + "//tensorflow/core/platform:path", + "//tensorflow/core/platform:platform_port", + "//tensorflow/core/platform:protobuf", + "//tensorflow/core/platform:setround", + "//tensorflow/core/platform:status", + "//tensorflow/core/platform:str_util", + "//tensorflow/core/platform:strcat", + "//tensorflow/core/platform:stringpiece", + "//tensorflow/core/platform:stringprintf", + "//tensorflow/core/platform:threadpool_interface", + "//tensorflow/core/platform:tracing", + "//tensorflow/core/platform:types", + "//third_party/eigen3", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:optional", + ], +) + +cc_library( + name = "env_time", + srcs = ["env_time.cc"], + hdrs = ["//tensorflow/core/platform:env_time.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = ["//tensorflow/core/platform:types"], +) + +cc_library( + name = "intrinsics_port", + srcs = ["intrinsics_port.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = ["//tensorflow/core/platform:types"], +) + +cc_library( + name = "load_library", + srcs = ["load_library.cc"], + hdrs = ["//tensorflow/core/platform:load_library.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + ":wide_char", + "//tensorflow/core/platform:errors", + "//tensorflow/core/platform:status", + ], +) + +cc_library( + name = "net", + srcs = ["net.cc"], + hdrs = ["//tensorflow/core/platform:net.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:error", + "//tensorflow/core/platform:logging", + ], +) + +cc_library( + name = "platform_port", + srcs = [ + "port.cc", + "//tensorflow/core/platform:cpu_info.cc", + ], + hdrs = [ + "//tensorflow/core/platform:cpu_info.h", + "//tensorflow/core/platform:demangle.h", + "//tensorflow/core/platform:host_info.h", + "//tensorflow/core/platform:init_main.h", + "//tensorflow/core/platform:mem.h", + "//tensorflow/core/platform:numa.h", + "//tensorflow/core/platform:snappy.h", + ], + copts = tf_copts(), + defines = ["TF_USE_SNAPPY"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:byte_order", + "//tensorflow/core/platform:dynamic_annotations", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:types", + "@snappy", + ], +) + +cc_library( + name = "stacktrace", + srcs = ["stacktrace.cc"], + hdrs = ["stacktrace.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = ["//tensorflow/core/platform:mutex"], +) + +cc_library( + name = "stacktrace_handler", + srcs = ["stacktrace_handler.cc"], + hdrs = ["//tensorflow/core/platform:stacktrace_handler.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + deps = [ + "//tensorflow/core/platform:mutex", + "//tensorflow/core/platform:stacktrace", + "//tensorflow/core/platform:types", + ], +) + +cc_library( + name = "subprocess", + hdrs = ["//tensorflow/core/platform:subprocess.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], + textual_hdrs = ["subprocess.h"], + deps = [ + "//tensorflow/core/platform", + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:types", + ], +) + +cc_library( + name = "wide_char", + hdrs = ["wide_char.h"], + tags = [ + "manual", + "no_oss", + "nobuilder", + ], +) + +exports_files( + srcs = ["intrinsics_port.h"], + visibility = ["//tensorflow/core/platform:__pkg__"], +) diff --git a/tensorflow/core/profiler/BUILD b/tensorflow/core/profiler/BUILD index c6eb4e28e10..c9e1b19f24e 100644 --- a/tensorflow/core/profiler/BUILD +++ b/tensorflow/core/profiler/BUILD @@ -103,6 +103,8 @@ cc_library( ":protos_all_cc", "//tensorflow/core:framework_headers_lib", "//tensorflow/core:lib", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) diff --git a/tensorflow/core/profiler/convert/BUILD b/tensorflow/core/profiler/convert/BUILD index cfb50111c1b..4fbeef18058 100644 --- a/tensorflow/core/profiler/convert/BUILD +++ b/tensorflow/core/profiler/convert/BUILD @@ -3,6 +3,18 @@ package( licenses = ["notice"], # Apache 2.0 ) +cc_library( + name = "run_metadata_to_trace_events", + srcs = ["run_metadata_to_trace_events.cc"], + hdrs = ["run_metadata_to_trace_events.h"], + deps = [ + "//tensorflow/core:core_cpu_lib", + "//tensorflow/core:lib", + "//tensorflow/core:protos_all_cc", + "@com_google_absl//absl/strings", + ], +) + cc_library( name = "op_metrics_to_record", srcs = ["op_metrics_to_record.cc"], diff --git a/tensorflow/core/profiler/convert/run_metadata_to_trace_events.cc b/tensorflow/core/profiler/convert/run_metadata_to_trace_events.cc new file mode 100644 index 00000000000..6d2705c3c2b --- /dev/null +++ b/tensorflow/core/profiler/convert/run_metadata_to_trace_events.cc @@ -0,0 +1,161 @@ +/* Copyright 2019 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/profiler/convert/run_metadata_to_trace_events.h" + +#include + +#include + +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "tensorflow/core/common_runtime/step_stats_collector.h" +#include "tensorflow/core/platform/env_time.h" + +namespace tensorflow { +namespace profiler { +namespace { + +// Given a node_name in the format "op_name:op_type", returns the "op_type". +// If the "op_type" is missing, returns the node_name. +// This is done so all ops with the same type appear in the same color in trace +// viewer. +inline string EventName(absl::string_view node_name) { + // NOTE: open source device tracer now append cupti kernel name after + // annotation as node_name, @@ is used as separator. kernel name is + // demangled and possibly contains "::" patterns. + std::vector segments = absl::StrSplit(node_name, "@@"); + if (segments.size() > 1) { // unparsed + // find the last annotation. + std::vector annotation_stack = + absl::StrSplit(segments.front(), "::"); + // strip trace argument. + std::vector annotation_parts = + absl::StrSplit(annotation_stack.back(), '#'); + std::vector parts = + absl::StrSplit(annotation_parts.front(), ':'); + return string(parts.back()); + } else { + std::vector parts = absl::StrSplit(node_name, ':'); + return string(parts.back()); + } +} + +void AssignLanes(RunMetadata* run_metadata) { + for (size_t device_id = 0; + device_id < run_metadata->step_stats().dev_stats_size(); ++device_id) { + auto* device_stats = + run_metadata->mutable_step_stats()->mutable_dev_stats(device_id); + if (device_stats->thread_names_size() > 0 || + device_stats->node_stats_size() == 0) { + continue; + } + std::vector lanes; + for (auto ns = device_stats->mutable_node_stats()->rbegin(); + ns != device_stats->mutable_node_stats()->rend(); ns++) { + uint64 end_micros = ns->all_start_micros() + ns->all_end_rel_micros(); + bool found_lane = false; + for (size_t l = 0; l < lanes.size(); l++) { + if (end_micros <= lanes[l]) { + ns->set_thread_id(l); + found_lane = true; + lanes[l] = ns->all_start_micros(); + break; + } + } + if (!found_lane) { + ns->set_thread_id(lanes.size()); + lanes.push_back(ns->all_start_micros()); + } + } + } +} + +} // namespace + +void ConvertRunMetadataToTraceEvents(uint64 profile_start_time_ns, + uint64 profile_end_time_ns, + RunMetadata* run_metadata, Trace* trace) { + uint64 profile_start_time_micros = + profile_start_time_ns / EnvTime::kMicrosToNanos; + uint64 profile_end_time_micros = + profile_end_time_ns / EnvTime::kMicrosToNanos; + + AssignLanes(run_metadata); + + auto* trace_devices = trace->mutable_devices(); + for (size_t device_id = 0; + device_id < run_metadata->step_stats().dev_stats_size(); ++device_id) { + // Create device + const auto& device_stats = run_metadata->step_stats().dev_stats(device_id); + // Don't generate trace events for "derived or aggregated" devices, the + // events in these devices are duplicated from other streams. + if (absl::EndsWith(device_stats.device(), "stream:all") || + absl::EndsWith(device_stats.device(), "sync") || + absl::EndsWith(device_stats.device(), "memcpy")) { + continue; + } + Device device; + device.set_name(device_stats.device()); + device.set_device_id(device_id); + Resource resource; + resource.set_name("0"); + resource.set_resource_id(0); + (*device.mutable_resources())[0] = resource; + for (const auto& thread_name : device_stats.thread_names()) { + Resource resource; + resource.set_resource_id(thread_name.first); + resource.set_name(thread_name.second); + (*device.mutable_resources())[thread_name.first] = resource; + } + (*trace_devices)[device_id] = device; + + // Emit events. + for (const auto& node : device_stats.node_stats()) { + if (node.all_start_micros() < profile_start_time_micros || + node.all_start_micros() + node.all_end_rel_micros() > + profile_end_time_micros) { + continue; + } + auto* event = trace->add_trace_events(); + auto* args = event->mutable_args(); + event->set_device_id(device_id); + event->set_resource_id(node.thread_id()); + event->set_name(EventName(node.node_name())); + event->set_timestamp_ps( + (node.all_start_micros() - profile_start_time_micros) * + EnvTime::kMicrosToPicos); + event->set_duration_ps(node.all_end_rel_micros() * + EnvTime::kMicrosToPicos); + if (!node.timeline_label().empty()) { + std::vector label_parts = + absl::StrSplit(node.timeline_label(), "@@"); + (*args)["label"] = string(label_parts.front()); + if (label_parts.size() == 2) { + // NOTE: we can further parse annotation here. + (*args)["annotation"] = string(label_parts.back()); + } + } + if (event->name() != node.node_name()) { + (*args)["long name"] = node.node_name(); + } + } + } + + // TODO(fishx): Convert allocation data as well. +} + +} // namespace profiler +} // namespace tensorflow diff --git a/tensorflow/core/profiler/convert/run_metadata_to_trace_events.h b/tensorflow/core/profiler/convert/run_metadata_to_trace_events.h new file mode 100644 index 00000000000..b55fdad4fd9 --- /dev/null +++ b/tensorflow/core/profiler/convert/run_metadata_to_trace_events.h @@ -0,0 +1,33 @@ +/* Copyright 2019 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_PROFILER_CONVERT_RUN_METADATA_TO_TRACE_EVENTS_H_ +#define TENSORFLOW_CORE_PROFILER_CONVERT_RUN_METADATA_TO_TRACE_EVENTS_H_ + +#include "tensorflow/core/platform/types.h" +#include "tensorflow/core/protobuf/config.pb.h" +#include "tensorflow/core/protobuf/trace_events.pb.h" + +namespace tensorflow { +namespace profiler { + +void ConvertRunMetadataToTraceEvents(uint64 profile_start_time_ns, + uint64 profile_end_time_ns, + RunMetadata* run_metadata, Trace* trace); + +} // namespace profiler +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_PROFILER_CONVERT_RUN_METADATA_TO_TRACE_EVENTS_H_ diff --git a/tensorflow/core/profiler/internal/BUILD b/tensorflow/core/profiler/internal/BUILD index a69806ef639..3e9f80807cf 100644 --- a/tensorflow/core/profiler/internal/BUILD +++ b/tensorflow/core/profiler/internal/BUILD @@ -25,6 +25,8 @@ cc_library( "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -37,6 +39,8 @@ cc_library( ":tfprof_utils", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", "@jsoncpp_git//:jsoncpp", ], ) @@ -52,6 +56,7 @@ cc_library( "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings:str_format", ], ) @@ -73,6 +78,7 @@ cc_library( "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings:str_format", ], ) @@ -86,13 +92,14 @@ cc_library( ":tfprof_show_multi", ":tfprof_tensor", ":tfprof_utils", - "//tensorflow/c:c_api", "//tensorflow/c:checkpoint_reader", "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -115,6 +122,8 @@ cc_library( "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -134,6 +143,7 @@ cc_library( "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings:str_format", ], ) @@ -169,6 +179,8 @@ cc_library( "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -191,6 +203,8 @@ cc_library( "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -253,6 +267,8 @@ cc_library( "//tensorflow/core:protos_all_cc", "//tensorflow/core:regexp_internal", "//tensorflow/core/profiler:tfprof_options", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -275,6 +291,7 @@ cc_library( "//tensorflow/core/profiler:protos_all_cc", "//tensorflow/core/profiler:tfprof_options", "//tensorflow/core/profiler/internal/advisor:tfprof_advisor", + "@com_google_absl//absl/strings:str_format", ], alwayslink = 1, ) @@ -314,6 +331,8 @@ cc_library( "//tensorflow/core:framework", "//tensorflow/core:lib", "//tensorflow/core/profiler:protos_all_cc", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -399,6 +418,7 @@ tf_cc_test( deps = [ ":traceme_recorder", "//tensorflow/core:lib", + "@com_google_absl//absl/strings", "@com_google_googletest//:gtest_main", ], ) diff --git a/tensorflow/core/profiler/internal/advisor/BUILD b/tensorflow/core/profiler/internal/advisor/BUILD index 7d72ceee228..f6ab03a0100 100644 --- a/tensorflow/core/profiler/internal/advisor/BUILD +++ b/tensorflow/core/profiler/internal/advisor/BUILD @@ -30,6 +30,7 @@ cc_library( hdrs = ["accelerator_utilization_checker.h"], deps = [ ":checker", + "@com_google_absl//absl/strings:str_format", ], ) @@ -38,6 +39,7 @@ cc_library( hdrs = ["operation_checker.h"], deps = [ ":checker", + "@com_google_absl//absl/strings:str_format", ], ) @@ -46,6 +48,8 @@ cc_library( hdrs = ["expensive_operation_checker.h"], deps = [ ":checker", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", ], ) @@ -59,6 +63,7 @@ cc_library( ":internal_checker_runner_dummy", ":operation_checker", "//tensorflow/core/profiler:protos_all_cc", + "@com_google_absl//absl/strings:str_format", ], ) diff --git a/tensorflow/core/profiler/internal/advisor/accelerator_utilization_checker.h b/tensorflow/core/profiler/internal/advisor/accelerator_utilization_checker.h index 25766668d88..11f75fea3cb 100644 --- a/tensorflow/core/profiler/internal/advisor/accelerator_utilization_checker.h +++ b/tensorflow/core/profiler/internal/advisor/accelerator_utilization_checker.h @@ -16,6 +16,7 @@ limitations under the License. #ifndef TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_ACCELERATOR_UTILIZATION_CHECKER_H_ #define TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_ACCELERATOR_UTILIZATION_CHECKER_H_ +#include "absl/strings/str_format.h" #include "tensorflow/core/profiler/internal/advisor/checker.h" namespace tensorflow { @@ -39,8 +40,8 @@ class AcceleratorUtilizationChecker : public Checker { AdviceProto::Checker Check(const AdvisorOptionsProto::CheckerOption& options, const TFStats* stats) override { if (!stats) { - fprintf(stderr, "Missing profiles (e.g. graph, run_meta). Skip %s\n", - name().c_str()); + absl::FPrintF( + stderr, "Missing profiles (e.g. graph, run_meta). Skip %s\n", name()); return reports_; } for (const auto& n : stats->nodes()) { @@ -56,14 +57,14 @@ class AcceleratorUtilizationChecker : public Checker { if (total_micros <= 0) continue; double utilization = 1.0 * stat.exec_micros / total_micros; if (utilization >= 0.5) { - reports_.add_reports(strings::Printf("device: %s utilization: %.2f", - s.first.c_str(), utilization)); + reports_.add_reports(absl::StrFormat("device: %s utilization: %.2f", + s.first, utilization)); } else if (utilization < 0.5 && utilization > 0.2) { - reports_.add_reports(strings::Printf("device: %s low utilization: %.2f", - s.first.c_str(), utilization)); + reports_.add_reports(absl::StrFormat("device: %s low utilization: %.2f", + s.first, utilization)); } else if (utilization <= 0.2) { - reports_.add_reports(strings::Printf("device: %s low utilization: %.2f", - s.first.c_str(), utilization)); + reports_.add_reports(absl::StrFormat("device: %s low utilization: %.2f", + s.first, utilization)); } } return reports_; diff --git a/tensorflow/core/profiler/internal/advisor/checker.h b/tensorflow/core/profiler/internal/advisor/checker.h index 5d7da39e6b2..89c3533a46e 100644 --- a/tensorflow/core/profiler/internal/advisor/checker.h +++ b/tensorflow/core/profiler/internal/advisor/checker.h @@ -16,7 +16,6 @@ limitations under the License. #ifndef TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_CHECKER_H_ #define TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_CHECKER_H_ -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/profiler/internal/tfprof_stats.h" #include "tensorflow/core/profiler/tfprof_options.pb.h" diff --git a/tensorflow/core/profiler/internal/advisor/expensive_operation_checker.h b/tensorflow/core/profiler/internal/advisor/expensive_operation_checker.h index 0d1c92eb08b..cf6067fb8b5 100644 --- a/tensorflow/core/profiler/internal/advisor/expensive_operation_checker.h +++ b/tensorflow/core/profiler/internal/advisor/expensive_operation_checker.h @@ -16,6 +16,8 @@ limitations under the License. #ifndef TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_EXPENSIVE_OPERATION_CHECKER_H_ #define TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_EXPENSIVE_OPERATION_CHECKER_H_ +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" #include "tensorflow/core/profiler/internal/advisor/checker.h" namespace tensorflow { @@ -29,12 +31,12 @@ class ExpensiveOperationChecker : public Checker { AdviceProto::Checker Check(const AdvisorOptionsProto::CheckerOption& options, const TFStats* stats) override { if (!stats) { - fprintf(stderr, "Missing profiles (e.g. graph, run_meta). Skip %s\n", - name().c_str()); + absl::FPrintF( + stderr, "Missing profiles (e.g. graph, run_meta). Skip %s\n", name()); return reports_; } if (stats->steps().empty()) { - fprintf(stderr, "Missing RunMetadata info. Skip %s\n", name().c_str()); + absl::FPrintF(stderr, "Missing RunMetadata info. Skip %s\n", name()); } CheckOpView(stats); CheckScopeView(stats); @@ -44,7 +46,7 @@ class ExpensiveOperationChecker : public Checker { void CheckOpView(const TFStats* stats) { if (stats->steps().empty()) { - fprintf(stderr, "Missing run_meta for %s\n", name().c_str()); + absl::FPrintF(stderr, "Missing run_meta for %s\n", name()); return; } Options opts(3, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, -1, "micros", {".*"}, {".*"}, @@ -57,21 +59,20 @@ class ExpensiveOperationChecker : public Checker { std::vector outputs; for (int i = 0; i < 3 && node->children_size() > 0; ++i) { node = &node->children(0); - outputs.push_back(strings::Printf( + outputs.push_back(absl::StrFormat( "top %d operation type: %s, " "cpu: %s, accelerator: %s, total: %s (%.2f%%)", - i + 1, node->name().c_str(), - FormatTime(node->cpu_exec_micros()).c_str(), - FormatTime(node->accelerator_exec_micros()).c_str(), - FormatTime(node->exec_micros()).c_str(), + i + 1, node->name(), FormatTime(node->cpu_exec_micros()), + FormatTime(node->accelerator_exec_micros()), + FormatTime(node->exec_micros()), 100.0 * node->exec_micros() / (root.total_exec_micros() + 1e-10))); } - reports_.add_reports(str_util::Join(outputs, "\n")); + reports_.add_reports(absl::StrJoin(outputs, "\n")); } void CheckCodeView(const TFStats* stats) { if (!stats->has_code_traces()) { - fprintf(stderr, "Missing op_log (code traces) for %s\n", name().c_str()); + absl::FPrintF(stderr, "Missing op_log (code traces) for %s\n", name()); return; } Options opts(100, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, -1, "micros", {".*"}, @@ -89,7 +90,7 @@ class ExpensiveOperationChecker : public Checker { std::vector outputs; CodeViewHelper(node, 0, &outputs); - reports_.add_reports(str_util::Join(outputs, "\n")); + reports_.add_reports(absl::StrJoin(outputs, "\n")); } void CheckScopeView(const TFStats* stats) { @@ -102,13 +103,13 @@ class ExpensiveOperationChecker : public Checker { std::vector outputs; for (int i = 0; i < 3 && i < root.children_size(); ++i) { const GraphNodeProto& node = root.children(i); - outputs.push_back(strings::Printf( + outputs.push_back(absl::StrFormat( "top %d graph node: %s, cpu: %s, accelerator: %s, total: %s", i + 1, - node.name().c_str(), FormatTime(node.cpu_exec_micros()).c_str(), - FormatTime(node.accelerator_exec_micros()).c_str(), - FormatTime(node.exec_micros()).c_str())); + node.name(), FormatTime(node.cpu_exec_micros()), + FormatTime(node.accelerator_exec_micros()), + FormatTime(node.exec_micros()))); } - reports_.add_reports(str_util::Join(outputs, "\n")); + reports_.add_reports(absl::StrJoin(outputs, "\n")); } void CodeViewHelper(const MultiGraphNodeProto* node, int depth, @@ -121,12 +122,12 @@ class ExpensiveOperationChecker : public Checker { if (c->total_exec_micros() < 1000) { continue; } - outputs->push_back(strings::Printf( - "%s%s, cpu: %s, accelerator: %s, total: %s", - string(depth * 2, ' ').c_str(), c->name().c_str(), - FormatTime(c->total_cpu_exec_micros()).c_str(), - FormatTime(c->total_accelerator_exec_micros()).c_str(), - FormatTime(c->total_exec_micros()).c_str())); + outputs->push_back( + absl::StrFormat("%s%s, cpu: %s, accelerator: %s, total: %s", + std::string(depth * 2, ' '), c->name(), + FormatTime(c->total_cpu_exec_micros()), + FormatTime(c->total_accelerator_exec_micros()), + FormatTime(c->total_exec_micros()))); CodeViewHelper(c, depth + 1, outputs); } } diff --git a/tensorflow/core/profiler/internal/advisor/operation_checker.h b/tensorflow/core/profiler/internal/advisor/operation_checker.h index 6c1d5cd6704..5142639fea6 100644 --- a/tensorflow/core/profiler/internal/advisor/operation_checker.h +++ b/tensorflow/core/profiler/internal/advisor/operation_checker.h @@ -17,6 +17,7 @@ limitations under the License. #ifndef TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_OPERATION_CHECKER_H_ #define TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_OPERATION_CHECKER_H_ +#include "absl/strings/str_format.h" #include "tensorflow/core/profiler/internal/advisor/checker.h" namespace tensorflow { @@ -30,8 +31,8 @@ class OperationChecker : public Checker { AdviceProto::Checker Check(const AdvisorOptionsProto::CheckerOption& options, const TFStats* stats) override { if (!stats) { - fprintf(stderr, "Missing profiles (e.g. graph, run_meta). Skip %s\n", - name().c_str()); + absl::FPrintF( + stderr, "Missing profiles (e.g. graph, run_meta). Skip %s\n", name()); return reports_; } bool use_batch_norm = false; diff --git a/tensorflow/core/profiler/internal/advisor/tfprof_advisor.h b/tensorflow/core/profiler/internal/advisor/tfprof_advisor.h index e1533f882f8..3fe772e6df1 100644 --- a/tensorflow/core/profiler/internal/advisor/tfprof_advisor.h +++ b/tensorflow/core/profiler/internal/advisor/tfprof_advisor.h @@ -16,6 +16,7 @@ limitations under the License. #ifndef TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_TFPROF_ADVISOR_H_ #define TENSORFLOW_CORE_PROFILER_INTERNAL_ADVISOR_TFPROF_ADVISOR_H_ +#include "absl/strings/str_format.h" #include "tensorflow/core/profiler/internal/advisor/accelerator_utilization_checker.h" #include "tensorflow/core/profiler/internal/advisor/checker.h" #include "tensorflow/core/profiler/internal/advisor/expensive_operation_checker.h" @@ -62,9 +63,9 @@ class Advisor { stats_)); } for (const auto& checker : ret.checkers()) { - fprintf(stdout, "\n%s:\n", checker.first.c_str()); + absl::FPrintF(stdout, "\n%s:\n", checker.first); for (const string& r : checker.second.reports()) { - fprintf(stdout, "%s\n", r.c_str()); + absl::FPrintF(stdout, "%s\n", r); } } fflush(stdout); diff --git a/tensorflow/core/profiler/internal/advisor/tfprof_advisor_test.cc b/tensorflow/core/profiler/internal/advisor/tfprof_advisor_test.cc index 0098b4b2f10..472c3d735eb 100644 --- a/tensorflow/core/profiler/internal/advisor/tfprof_advisor_test.cc +++ b/tensorflow/core/profiler/internal/advisor/tfprof_advisor_test.cc @@ -16,7 +16,6 @@ limitations under the License. #include "tensorflow/core/profiler/internal/advisor/tfprof_advisor.h" #include "tensorflow/core/lib/io/path.h" -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/test.h" diff --git a/tensorflow/core/profiler/internal/cpu/BUILD b/tensorflow/core/profiler/internal/cpu/BUILD index a3048dfb85b..75240b5da88 100644 --- a/tensorflow/core/profiler/internal/cpu/BUILD +++ b/tensorflow/core/profiler/internal/cpu/BUILD @@ -33,6 +33,7 @@ cc_library( "//tensorflow/core/profiler/internal:profiler_interface", "//tensorflow/core/profiler/internal:traceme_recorder", "//tensorflow/core/profiler/protobuf:xplane_proto_cc", + "//tensorflow/core/profiler/utils:xplane_schema", "@com_google_absl//absl/strings", ], alwayslink = True, @@ -50,6 +51,7 @@ tf_cc_test( "//tensorflow/core/profiler/internal:profiler_interface", "//tensorflow/core/profiler/lib:traceme", "//tensorflow/core/profiler/protobuf:xplane_proto_cc", + "//tensorflow/core/profiler/utils:xplane_schema", "@com_google_absl//absl/types:optional", "@com_google_googletest//:gtest_main", ], diff --git a/tensorflow/core/profiler/internal/cpu/host_tracer.cc b/tensorflow/core/profiler/internal/cpu/host_tracer.cc index 79fe7cf3729..9e1d2fb4217 100644 --- a/tensorflow/core/profiler/internal/cpu/host_tracer.cc +++ b/tensorflow/core/profiler/internal/cpu/host_tracer.cc @@ -24,6 +24,7 @@ limitations under the License. #include "tensorflow/core/profiler/internal/profiler_interface.h" #include "tensorflow/core/profiler/internal/traceme_recorder.h" #include "tensorflow/core/profiler/protobuf/xplane.pb.h" +#include "tensorflow/core/profiler/utils/xplane_schema.h" #include "tensorflow/core/protobuf/config.pb.h" #include "tensorflow/core/util/env_var.h" @@ -140,8 +141,9 @@ Status HostTracer::CollectData(XSpace* space) { return errors::Internal("TraceMeRecorder not stopped"); } MakeCompleteEvents(&events_); - ConvertCompleteEventsToXPlane(start_timestamp_ns_, events_, - space->add_planes()); + XPlane* plane = space->add_planes(); + plane->set_name(string(kHostThreads)); + ConvertCompleteEventsToXPlane(start_timestamp_ns_, events_, plane); events_.clear(); return Status::OK(); } diff --git a/tensorflow/core/profiler/internal/cpu/host_tracer_test.cc b/tensorflow/core/profiler/internal/cpu/host_tracer_test.cc index ffe702ad121..f98912a2800 100644 --- a/tensorflow/core/profiler/internal/cpu/host_tracer_test.cc +++ b/tensorflow/core/profiler/internal/cpu/host_tracer_test.cc @@ -24,6 +24,7 @@ limitations under the License. #include "tensorflow/core/profiler/internal/profiler_interface.h" #include "tensorflow/core/profiler/lib/traceme.h" #include "tensorflow/core/profiler/protobuf/xplane.pb.h" +#include "tensorflow/core/profiler/utils/xplane_schema.h" #include "tensorflow/core/protobuf/config.pb.h" namespace tensorflow { @@ -126,6 +127,7 @@ TEST(HostTracerTest, CollectsTraceMeEventsAsXSpace) { ASSERT_EQ(space.planes_size(), 1); const auto& plane = space.planes(0); + ASSERT_EQ(plane.name(), kHostThreads); ASSERT_EQ(plane.lines_size(), 1); ASSERT_EQ(plane.event_metadata_size(), 6); ASSERT_EQ(plane.stat_metadata_size(), 2); diff --git a/tensorflow/core/profiler/internal/gpu/device_tracer.cc b/tensorflow/core/profiler/internal/gpu/device_tracer.cc index 9b3254ed905..b70a6ea6414 100644 --- a/tensorflow/core/profiler/internal/gpu/device_tracer.cc +++ b/tensorflow/core/profiler/internal/gpu/device_tracer.cc @@ -21,12 +21,11 @@ limitations under the License. #include "absl/container/fixed_array.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "tensorflow/core/common_runtime/step_stats_collector.h" #include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/abi.h" #include "tensorflow/core/platform/macros.h" -#include "tensorflow/core/platform/stringprintf.h" #include "tensorflow/core/profiler/internal/annotation_stack.h" #include "tensorflow/core/profiler/internal/gpu/cupti_tracer.h" #include "tensorflow/core/profiler/internal/gpu/cupti_wrapper.h" @@ -165,8 +164,8 @@ class CuptiTraceCollectorImpl : public CuptiTraceCollector { ns->set_node_name(activity_name); switch (event.type) { case CuptiTracerEventType::Kernel: { - const std::string details = strings::Printf( - "regs:%llu shm:%llu grid:%llu,%llu,%llu block:%llu,%llu,%llu", + const std::string details = absl::StrFormat( + "regs:%u shm:%u grid:%u,%u,%u block:%u,%u,%u", event.kernel_info.registers_per_thread, event.kernel_info.static_shared_memory_usage, event.kernel_info.grid_x, event.kernel_info.grid_y, diff --git a/tensorflow/core/profiler/internal/print_model_analysis.cc b/tensorflow/core/profiler/internal/print_model_analysis.cc index 5a31c7d789e..5841a04fe2e 100644 --- a/tensorflow/core/profiler/internal/print_model_analysis.cc +++ b/tensorflow/core/profiler/internal/print_model_analysis.cc @@ -16,9 +16,11 @@ limitations under the License. #include "tensorflow/core/profiler/internal/print_model_analysis.h" #include + #include #include +#include "absl/strings/str_format.h" #include "tensorflow/c/checkpoint_reader.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/lib/core/errors.h" @@ -40,7 +42,7 @@ string RunProfile(const string& command, const string& options, if (command == kCmds[4]) { AdvisorOptionsProto option_pb; if (!option_pb.ParseFromString(options)) { - fprintf(stderr, "Cannot parse AdvisorOptionsProto\n"); + absl::FPrintF(stderr, "Cannot parse AdvisorOptionsProto\n"); return ""; } tf_stats->BuildAllViews(); @@ -52,23 +54,26 @@ string RunProfile(const string& command, const string& options, Options opts; tensorflow::Status s = Options::FromProtoStr(options, &opts); if (!s.ok()) { - fprintf(stderr, "%s\n", s.ToString().c_str()); + absl::FPrintF(stderr, "%s\n", s.ToString()); return ""; } if (opts.output_type == kOutput[1]) { - printf("\n=========================Options=============================\n"); - printf("%s", opts.ToString().c_str()); - printf("\n==================Model Analysis Report======================\n"); + absl::PrintF( + "\n=========================Options=============================\n"); + absl::PrintF("%s", opts.ToString()); + absl::PrintF( + "\n==================Model Analysis Report======================\n"); string ret = ""; if (command == kCmds[2] || command == kCmds[3]) { ret = tf_stats->ShowMultiGraphNode(command, opts).SerializeAsString(); } else if (command == kCmds[0] || command == kCmds[1]) { ret = tf_stats->ShowGraphNode(command, opts).SerializeAsString(); } else { - fprintf(stderr, "Unknown command: %s\n", command.c_str()); + absl::FPrintF(stderr, "Unknown command: %s\n", command); } - printf("\n======================End of Report==========================\n"); + absl::PrintF( + "\n======================End of Report==========================\n"); fflush(stdout); return ret; } @@ -77,7 +82,7 @@ string RunProfile(const string& command, const string& options, } else if (command == kCmds[0] || command == kCmds[1]) { return tf_stats->ShowGraphNode(command, opts).SerializeAsString(); } else { - fprintf(stderr, "Unknown command: %s\n", command.c_str()); + absl::FPrintF(stderr, "Unknown command: %s\n", command); return ""; } } @@ -88,7 +93,7 @@ bool NewProfiler(const string* graph, const string* op_log) { if (graph && !graph->empty()) { if (!graph_ptr->ParseFromString(*graph)) { if (!protobuf::TextFormat::ParseFromString(*graph, graph_ptr.get())) { - fprintf(stderr, "Failed to parse graph\n"); + absl::FPrintF(stderr, "Failed to parse graph\n"); return false; } } @@ -98,7 +103,7 @@ bool NewProfiler(const string* graph, const string* op_log) { if (op_log && !op_log->empty()) { op_log_ptr.reset(new OpLogProto()); if (!op_log_ptr->ParseFromString(*op_log)) { - fprintf(stderr, "Failed to parse OpLogProto.\n"); + absl::FPrintF(stderr, "Failed to parse OpLogProto.\n"); return false; } } @@ -128,7 +133,7 @@ double AddStep(int64 step, const string* graph, const string* run_meta, std::unique_ptr graph_ptr(new GraphDef()); if (!graph_ptr->ParseFromString(*graph)) { if (!protobuf::TextFormat::ParseFromString(*graph, graph_ptr.get())) { - fprintf(stderr, "Failed to parse graph\n"); + absl::FPrintF(stderr, "Failed to parse graph\n"); } } tf_stat->AddGraph(std::move(graph_ptr)); diff --git a/tensorflow/core/profiler/internal/tfprof_code.cc b/tensorflow/core/profiler/internal/tfprof_code.cc index b39d9a99d50..75d1e156cce 100644 --- a/tensorflow/core/profiler/internal/tfprof_code.cc +++ b/tensorflow/core/profiler/internal/tfprof_code.cc @@ -16,16 +16,16 @@ limitations under the License. #include "tensorflow/core/profiler/internal/tfprof_code.h" #include + #include +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "tensorflow/c/c_api.h" #include "tensorflow/core/framework/tensor.h" #include "tensorflow/core/lib/io/path.h" #include "tensorflow/core/lib/io/zlib_compression_options.h" #include "tensorflow/core/lib/io/zlib_outputbuffer.h" -#include "tensorflow/core/lib/strings/str_util.h" -#include "tensorflow/core/lib/strings/strcat.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/profiler/internal/tfprof_constants.h" @@ -36,13 +36,13 @@ namespace { const char* const kGradientSuffix = " (gradient)"; // Convert to Trace proto into a short readable string. -string GetTraceString(const CallStack::Trace& trace) { - string ntrace(io::Basename(trace.file())); - ntrace += strings::StrCat(":", trace.lineno()); +std::string GetTraceString(const CallStack::Trace& trace) { + std::string ntrace = + absl::StrCat(io::Basename(trace.file()), ":", trace.lineno()); if (trace.function().length() < 20) { - ntrace += ":" + trace.function(); + absl::StrAppend(&ntrace, ":", trace.function()); } else { - ntrace += ":" + trace.function().substr(0, 17) + "..."; + absl::StrAppend(&ntrace, ":", trace.function().substr(0, 17), "..."); } return ntrace; } @@ -116,7 +116,7 @@ class FunctionTable { string file_base(io::Basename(file_path)); file_base = file_base.substr(0, file_base.find_last_of(".")); func_pb->set_name( - string_table_->GetIndex(strings::StrCat(file_base, ":", func_name))); + string_table_->GetIndex(absl::StrCat(file_base, ":", func_name))); func_pb->set_filename(string_table_->GetIndex(file_path)); func_pb->set_start_line(func_start_line); return func_pb->id(); @@ -229,7 +229,7 @@ class Samples { } else if (type == kShown[3]) { sample_pb->mutable_value()->Add(gn->float_ops(node->node->step())); } else { - fprintf(stderr, "pprof doesn't support -select=%s\n", type.c_str()); + absl::FPrintF(stderr, "pprof doesn't support -select=%s\n", type); } } } @@ -309,8 +309,9 @@ class PprofProfileImpl : public PprofProfile { delete zlib_output_buffer; return s; } - fprintf(stdout, "\nRun pprof -png --nodecount=100 --sample_index=1 <%s>\n", - filename.c_str()); + absl::FPrintF(stdout, + "\nRun pprof -png --nodecount=100 --sample_index=1 <%s>\n", + filename); delete zlib_output_buffer; return s; } @@ -364,7 +365,7 @@ class PprofProfileImpl : public PprofProfile { profile_pb->mutable_comment()->Add(string_table_.GetIndex( "Model float operations (Only available if defined).")); } else { - fprintf(stderr, "pprof doesn't support selecting: %s\n", type.c_str()); + absl::FPrintF(stderr, "pprof doesn't support selecting: %s\n", type); } for (const string& str : string_table_.strings()) { @@ -459,7 +460,8 @@ void TFCode::Build() { } } if (unaccounted_nodes > 0) { - fprintf(stderr, "%lld gradient nodes not accounted\n", unaccounted_nodes); + absl::FPrintF(stderr, "%d gradient nodes not accounted\n", + unaccounted_nodes); } } @@ -468,19 +470,20 @@ const ShowMultiNode* TFCode::ShowInternal(const Options& opts, root_->ResetTotalStats(); if (opts.output_type == kOutput[3]) { if (opts.select.size() != 1) { - fprintf(stderr, "Can only select 1 attribute for pprof output.\n"); + absl::FPrintF(stderr, "Can only select 1 attribute for pprof output.\n"); return root_.get(); } string select = *opts.select.begin(); if (select != kShown[0] && select != kShown[1] && select != kShown[2] && select != kShown[3] && select != kShown[9] && select != kShown[10] && select != kShown[11] && select != kShown[12] && select != kShown[13]) { - fprintf(stderr, "pprof doesn't support -select=%s\n", select.c_str()); + absl::FPrintF(stderr, "pprof doesn't support -select=%s\n", select); return root_.get(); } } if (opts.account_displayed_op_only) { - fprintf(stderr, "Note: code view ignores account_displayed_op_only\n"); + absl::FPrintF(stderr, + "Note: code view ignores account_displayed_op_only\n"); } std::vector roots = Account(root_->children, opts); @@ -508,7 +511,7 @@ const ShowMultiNode* TFCode::ShowInternal(const Options& opts, Status s = pprof_profile_->WritePprofProfile( opts.output_options.at(kPprofOpts[0])); if (!s.ok()) { - fprintf(stderr, "%s\n", s.ToString().c_str()); + absl::FPrintF(stderr, "%s\n", s.ToString()); } } else { Format(root, root->show_children, opts, &root->formatted_str, @@ -593,7 +596,7 @@ std::vector TFCode::PrintScope(const std::vector roots, node->formatted_str = FormatNode(node, opts, last_ident); if (opts.select.find(kShown[4]) != opts.select.end()) { - fprintf(stderr, "code view has no tensor value to show\n"); + absl::FPrintF(stderr, "code view has no tensor value to show\n"); } show_nodes.push_back(node); } else { @@ -690,15 +693,14 @@ string TFCode::FormatNode(CodeNode* node, const Options& opts, } if (opts.select.find(kShown[7]) != opts.select.end()) { // TODO(xpan): Make op count available in code view? - attrs.push_back(strings::Printf("%s N/A in code view", kShown[7])); + attrs.push_back(absl::StrFormat("%s N/A in code view", kShown[7])); } if (opts.select.find(kShown[8]) != opts.select.end()) { - attrs.push_back(strings::Printf("%s N/A in code view", kShown[8])); + attrs.push_back(absl::StrFormat("%s N/A in code view", kShown[8])); } - return strings::Printf("%s%s (%s)\n", string(indent, ' ').c_str(), - node->name().c_str(), - absl::StrJoin(attrs, ", ").c_str()); + return absl::StrFormat("%s%s (%s)\n", std::string(indent, ' '), node->name(), + absl::StrJoin(attrs, ", ")); } } // namespace tfprof } // namespace tensorflow diff --git a/tensorflow/core/profiler/internal/tfprof_graph.cc b/tensorflow/core/profiler/internal/tfprof_graph.cc index db7ae3b3971..41f653a857e 100644 --- a/tensorflow/core/profiler/internal/tfprof_graph.cc +++ b/tensorflow/core/profiler/internal/tfprof_graph.cc @@ -16,10 +16,10 @@ limitations under the License. #include "tensorflow/core/profiler/internal/tfprof_graph.h" #include + #include -#include "tensorflow/core/lib/strings/strcat.h" -#include "tensorflow/core/lib/strings/stringprintf.h" +#include "absl/strings/str_format.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/profiler/internal/tfprof_constants.h" #include "tensorflow/core/profiler/internal/tfprof_tensor.h" @@ -74,13 +74,14 @@ const ShowNode* TFGraph::ShowInternal(const Options& opts, Timeline* timeline) { root_->show_children.clear(); if (opts.output_type == kOutput[3]) { - fprintf(stderr, "Only 'code' view supports pprof output now.\n"); + absl::FPrintF(stderr, "Only 'code' view supports pprof output now.\n"); return root_; } if (timeline && timeline->step() < 0) { // TODO(xpan): Maybe pick a default step for users. - fprintf(stderr, - "Must specify -step option to generate timeline in graph view.\n"); + absl::FPrintF( + stderr, + "Must specify -step option to generate timeline in graph view.\n"); return root_; } // 1. Account and aggregate the stats based on the graph structure. @@ -188,9 +189,8 @@ std::vector TFGraph::PrintGraph(const std::vector roots, node->AggregateTotalStats(sc); } } - node->formatted_str = - strings::Printf("%s%s\n", string(last_ident, ' ').c_str(), - FormatNode(node, opts).c_str()); + node->formatted_str = absl::StrFormat( + "%s%s\n", std::string(last_ident, ' '), FormatNode(node, opts)); if (opts.select.find(kShown[4]) != opts.select.end()) { std::unique_ptr tfprof_tensor; diff --git a/tensorflow/core/profiler/internal/tfprof_node.cc b/tensorflow/core/profiler/internal/tfprof_node.cc index ef875832fb2..d11b0ce1583 100644 --- a/tensorflow/core/profiler/internal/tfprof_node.cc +++ b/tensorflow/core/profiler/internal/tfprof_node.cc @@ -85,8 +85,8 @@ void ExecStep::AddMemoryStats(const string& dev, exec_mem.set_memory_micros(step_stat.all_start_micros() + step_stat.op_end_rel_micros()); } else { - fprintf(stderr, "%s has no start time, skipping\n", - step_stat.node_name().c_str()); + absl::FPrintF(stderr, "%s has no start time, skipping\n", + step_stat.node_name()); return; } @@ -106,8 +106,8 @@ void ExecStep::AddMemoryStats(const string& dev, } } if (accelerator_allocator_cnt > 1) { - fprintf(stderr, "found %d gpu allocator for 1 node\n", - accelerator_allocator_cnt); + absl::FPrintF(stderr, "found %d gpu allocator for 1 node\n", + accelerator_allocator_cnt); } int64 total_output_bytes = 0; diff --git a/tensorflow/core/profiler/internal/tfprof_node.h b/tensorflow/core/profiler/internal/tfprof_node.h index 0a97b1cb0f2..5b2cd5fc309 100644 --- a/tensorflow/core/profiler/internal/tfprof_node.h +++ b/tensorflow/core/profiler/internal/tfprof_node.h @@ -21,6 +21,7 @@ limitations under the License. #include #include +#include "absl/strings/str_format.h" #include "tensorflow/core/framework/allocation_description.pb.h" #include "tensorflow/core/framework/attr_value.pb.h" #include "tensorflow/core/framework/node_def.pb.h" @@ -28,8 +29,6 @@ limitations under the License. #include "tensorflow/core/framework/tensor_description.pb.h" #include "tensorflow/core/framework/tensor_shape.pb.h" #include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/lib/strings/str_util.h" -#include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/profiler/tfprof_log.pb.h" #include "tensorflow/core/profiler/tfprof_options.h" @@ -326,13 +325,13 @@ class TFGraphNode { (*node_.mutable_attrs())[attr.first].MergeFrom(attr.second); if (attr.first == "shape" && attr.second.has_shape()) { if (!shape_.empty()) { - fprintf(stderr, "Found duplicated shapes!\n"); + absl::FPrintF(stderr, "Found duplicated shapes!\n"); continue; } shape_ = ShapeProtoToVec(attr.second.shape()); } else if (attr.first == "_output_shapes" && attr.second.has_list()) { if (!output_shapes_.empty()) { - fprintf(stderr, "Found duplicated output shapes!\n"); + absl::FPrintF(stderr, "Found duplicated output shapes!\n"); continue; } for (int i = 0; i < attr.second.list().shape_size(); ++i) { @@ -669,7 +668,7 @@ class TFGraphNode { if (complete_shape) { return params; } else { - fprintf(stderr, "Incomplete shape.\n"); + absl::FPrintF(stderr, "Incomplete shape.\n"); } } return 0; diff --git a/tensorflow/core/profiler/internal/tfprof_node_show.cc b/tensorflow/core/profiler/internal/tfprof_node_show.cc index b0f8dcbf3b5..84ddf149909 100644 --- a/tensorflow/core/profiler/internal/tfprof_node_show.cc +++ b/tensorflow/core/profiler/internal/tfprof_node_show.cc @@ -14,9 +14,6 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/core/profiler/internal/tfprof_node_show.h" -#include "tensorflow/core/lib/strings/str_util.h" -#include "tensorflow/core/lib/strings/stringprintf.h" - namespace tensorflow { namespace tfprof { namespace {} diff --git a/tensorflow/core/profiler/internal/tfprof_op.cc b/tensorflow/core/profiler/internal/tfprof_op.cc index 21a1513a06a..adc12882f9e 100644 --- a/tensorflow/core/profiler/internal/tfprof_op.cc +++ b/tensorflow/core/profiler/internal/tfprof_op.cc @@ -16,10 +16,11 @@ limitations under the License. #include "tensorflow/core/profiler/internal/tfprof_op.h" #include + #include -#include "tensorflow/core/lib/strings/strcat.h" -#include "tensorflow/core/lib/strings/stringprintf.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/profiler/internal/tfprof_constants.h" #include "tensorflow/core/profiler/internal/tfprof_tensor.h" @@ -38,11 +39,10 @@ string FormatToalExecTime(const ShowMultiNode* node, 100.0 * node->proto().exec_micros() / root->proto().total_exec_micros(); } - return strings::Printf( - "%30s", strings::Printf("%s (%.2f%%, %.2f%%)", - FormatTime(node->proto().exec_micros()).c_str(), - accu_pct, pct) - .c_str()); + return absl::StrFormat( + "%30s", + absl::StrFormat("%s (%.2f%%, %.2f%%)", + FormatTime(node->proto().exec_micros()), accu_pct, pct)); } string FormatCPUExecTime(const ShowMultiNode* node, const ShowMultiNode* root) { double accu_pct = 0.0; @@ -54,12 +54,10 @@ string FormatCPUExecTime(const ShowMultiNode* node, const ShowMultiNode* root) { root->proto().total_cpu_exec_micros(); } - return strings::Printf( - "%30s", - strings::Printf("%s (%.2f%%, %.2f%%)", - FormatTime(node->proto().cpu_exec_micros()).c_str(), - accu_pct, pct) - .c_str()); + return absl::StrFormat( + "%30s", absl::StrFormat("%s (%.2f%%, %.2f%%)", + FormatTime(node->proto().cpu_exec_micros()), + accu_pct, pct)); } string FormatAcceleratorExecTime(const ShowMultiNode* node, const ShowMultiNode* root) { @@ -72,12 +70,11 @@ string FormatAcceleratorExecTime(const ShowMultiNode* node, root->proto().total_accelerator_exec_micros(); } - return strings::Printf( - "%30s", strings::Printf( - "%s (%.2f%%, %.2f%%)", - FormatTime(node->proto().accelerator_exec_micros()).c_str(), - accu_pct, pct) - .c_str()); + return absl::StrFormat( + "%30s", + absl::StrFormat("%s (%.2f%%, %.2f%%)", + FormatTime(node->proto().accelerator_exec_micros()), + accu_pct, pct)); } } // namespace @@ -106,16 +103,16 @@ const ShowMultiNode* TFOp::ShowInternal(const Options& opts, Timeline* timeline) { root_->ResetTotalStats(); if (opts.output_type == kOutput[3]) { - fprintf(stderr, "Only 'code' view supports pprof output now.\n"); + absl::FPrintF(stderr, "Only 'code' view supports pprof output now.\n"); return root_.get(); } if (opts.output_type == kOutput[1] || opts.output_type == kOutput[2]) { root_->formatted_str = FormatNode(root_.get(), root_.get(), opts); } if (timeline) { - fprintf(stderr, - "op view doesn't support timeline yet. " - "Consider graph/scope/code view.\n"); + absl::FPrintF(stderr, + "op view doesn't support timeline yet. " + "Consider graph/scope/code view.\n"); return root_.get(); } if (cnodes_map_.empty()) { @@ -216,10 +213,9 @@ string TFOp::FormatMemoryNode(int64 node_total_bytes, int64 root_total_bytes, accu_pct = 100.0 * node_total_bytes / root_total_bytes; pct = 100.0 * node_bytes / root_total_bytes; } - return strings::Printf( - "%30s", strings::Printf("%s (%.2f%%, %.2f%%)", - FormatMemory(node_bytes).c_str(), accu_pct, pct) - .c_str()); + return absl::StrFormat( + "%30s", absl::StrFormat("%s (%.2f%%, %.2f%%)", FormatMemory(node_bytes), + accu_pct, pct)); } string TFOp::FormatNode(OpNode* node, OpNode* root, const Options& opts) const { @@ -270,12 +266,10 @@ string TFOp::FormatNode(OpNode* node, OpNode* root, const Options& opts) const { pct = 100.0 * node->proto().parameters() / root->proto().total_parameters(); } - attrs.push_back(strings::Printf( - "%30s", - strings::Printf("%s params (%.2f%%, %.2f%%)", - FormatNumber(node->proto().parameters()).c_str(), - accu_pct, pct) - .c_str())); + attrs.push_back(absl::StrFormat( + "%30s", absl::StrFormat("%s params (%.2f%%, %.2f%%)", + FormatNumber(node->proto().parameters()), + accu_pct, pct))); } if (opts.select.find(kShown[3]) != opts.select.end()) { @@ -287,11 +281,10 @@ string TFOp::FormatNode(OpNode* node, OpNode* root, const Options& opts) const { pct = 100.0 * node->proto().float_ops() / root->proto().total_float_ops(); } - attrs.push_back(strings::Printf( - "%30s", strings::Printf("%s float_ops (%.2f%%, %.2f%%)", - FormatNumber(node->proto().float_ops()).c_str(), - accu_pct, pct) - .c_str())); + attrs.push_back(absl::StrFormat( + "%30s", absl::StrFormat("%s float_ops (%.2f%%, %.2f%%)", + FormatNumber(node->proto().float_ops()), + accu_pct, pct))); } if (opts.select.find(kShown[5]) != opts.select.end()) { @@ -308,20 +301,18 @@ string TFOp::FormatNode(OpNode* node, OpNode* root, const Options& opts) const { for (const auto& gnode : node->proto().graph_nodes()) { total_runs += gnode.run_count(); } - attrs.push_back(strings::Printf( - "%10s", - strings::Printf("%lld|%d", total_runs, node->proto().graph_nodes_size()) - .c_str())); + attrs.push_back(absl::StrFormat( + "%10s", absl::StrFormat("%d|%d", total_runs, + node->proto().graph_nodes_size()))); } - string node_str = strings::Printf("%-25s%s\n", node->name().c_str(), - absl::StrJoin(attrs, ", ").c_str()); + string node_str = + absl::StrFormat("%-25s%s\n", node->name(), absl::StrJoin(attrs, ", ")); if (opts.select.find(kShown[8]) != opts.select.end()) { string input_shape_str = FormatInputShapes(node->proto()); if (!input_shape_str.empty()) { - node_str = strings::Printf("%s\n%s\n\n", node_str.c_str(), - input_shape_str.c_str()); + node_str = absl::StrFormat("%s\n%s\n\n", node_str, input_shape_str); } } return node_str; diff --git a/tensorflow/core/profiler/internal/tfprof_scope.cc b/tensorflow/core/profiler/internal/tfprof_scope.cc index 988bed71cc8..ba0bcd98fc1 100644 --- a/tensorflow/core/profiler/internal/tfprof_scope.cc +++ b/tensorflow/core/profiler/internal/tfprof_scope.cc @@ -16,11 +16,12 @@ limitations under the License. #include "tensorflow/core/profiler/internal/tfprof_scope.h" #include + #include +#include "absl/strings/str_format.h" #include "tensorflow/c/c_api.h" #include "tensorflow/core/framework/tensor.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/profiler/internal/tfprof_constants.h" #include "tensorflow/core/profiler/internal/tfprof_tensor.h" @@ -80,7 +81,7 @@ void TFScope::Build() { const ShowNode* TFScope::ShowInternal(const Options& opts, Timeline* timeline) { root_->ResetTotalStats(); if (opts.output_type == kOutput[3]) { - fprintf(stderr, "Only 'code' view supports pprof output now.\n"); + absl::FPrintF(stderr, "Only 'code' view supports pprof output now.\n"); return root_; } @@ -171,9 +172,8 @@ std::vector TFScope::PrintScope(const std::vector roots, } } - node->formatted_str = - strings::Printf("%s%s\n", string(last_ident, ' ').c_str(), - FormatNode(node, opts).c_str()); + node->formatted_str = absl::StrFormat( + "%s%s\n", std::string(last_ident, ' '), FormatNode(node, opts)); if (opts.select.find(kShown[4]) != opts.select.end()) { std::unique_ptr tfprof_tensor; diff --git a/tensorflow/core/profiler/internal/tfprof_show.cc b/tensorflow/core/profiler/internal/tfprof_show.cc index 07145ddc243..e7a5b03a558 100644 --- a/tensorflow/core/profiler/internal/tfprof_show.cc +++ b/tensorflow/core/profiler/internal/tfprof_show.cc @@ -18,7 +18,8 @@ limitations under the License. #include #include -#include "tensorflow/core/lib/strings/stringprintf.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/regexp.h" @@ -32,19 +33,19 @@ const GraphNodeProto& TFShow::Show(const string& prefix, const Options& opts) { } else { const ShowNode* ret = ShowInternal(opts, nullptr); if (opts.output_type == kOutput[1]) { - printf("%s", (prefix + ret->formatted_str).c_str()); + absl::PrintF("%s", (prefix + ret->formatted_str)); fflush(stdout); } else if (opts.output_type == kOutput[2]) { Status s = WriteStringToFile(Env::Default(), opts.output_options.at(kFileOpts[0]), prefix + ret->formatted_str); if (!s.ok()) { - fprintf(stderr, "%s\n", s.ToString().c_str()); + absl::FPrintF(stderr, "%s\n", s.ToString()); } } else if (opts.output_type == kOutput[3] || opts.output_type == kOutput[4]) { } else { - fprintf(stderr, "Unknown output type: %s\n", opts.output_type.c_str()); + absl::FPrintF(stderr, "Unknown output type: %s\n", opts.output_type); } return ret->proto(); } @@ -59,7 +60,7 @@ bool TFShow::LookUpCheckPoint(const string& name, TF_Status* status = TF_NewStatus(); ckpt_reader_->GetTensor(name, &out_tensor, status); if (TF_GetCode(status) != TF_OK) { - fprintf(stderr, "%s\n", TF_Message(status)); + absl::FPrintF(stderr, "%s\n", TF_Message(status)); TF_DeleteStatus(status); return false; } @@ -227,17 +228,16 @@ string TFShow::FormatNode(ShowNode* node, const Options& opts) const { std::vector shape_vec; for (const auto& s : node->node->input_shapes()) { if (s.second.empty()) { - shape_vec.push_back(strings::Printf("%d:unknown", s.first)); + shape_vec.push_back(absl::StrFormat("%d:unknown", s.first)); } else { - shape_vec.push_back(strings::Printf( - "%d:%s", s.first, absl::StrJoin(s.second, "x").c_str())); + shape_vec.push_back( + absl::StrFormat("%d:%s", s.first, absl::StrJoin(s.second, "x"))); } } info.push_back(absl::StrJoin(shape_vec, "|")); } - return strings::Printf("%s (%s)", node->name().c_str(), - absl::StrJoin(info, ", ").c_str()); + return absl::StrFormat("%s (%s)", node->name(), absl::StrJoin(info, ", ")); } string TFShow::FormatLegend(const Options& opts) const { @@ -285,8 +285,7 @@ string TFShow::FormatLegend(const Options& opts) const { if (opts.select.find(kShown[8]) != opts.select.end()) { legends.push_back("input shapes"); } - return strings::Printf("node name | %s\n", - absl::StrJoin(legends, " | ").c_str()); + return absl::StrFormat("node name | %s\n", absl::StrJoin(legends, " | ")); } } // namespace tfprof diff --git a/tensorflow/core/profiler/internal/tfprof_show.h b/tensorflow/core/profiler/internal/tfprof_show.h index 81b021549a4..dfab5d805ee 100644 --- a/tensorflow/core/profiler/internal/tfprof_show.h +++ b/tensorflow/core/profiler/internal/tfprof_show.h @@ -25,7 +25,6 @@ limitations under the License. #include "tensorflow/c/checkpoint_reader.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/profiler/internal/tfprof_constants.h" #include "tensorflow/core/profiler/internal/tfprof_node.h" #include "tensorflow/core/profiler/internal/tfprof_node_show.h" diff --git a/tensorflow/core/profiler/internal/tfprof_show_multi.cc b/tensorflow/core/profiler/internal/tfprof_show_multi.cc index 1b45c30ad7e..bfdf6ed35a5 100644 --- a/tensorflow/core/profiler/internal/tfprof_show_multi.cc +++ b/tensorflow/core/profiler/internal/tfprof_show_multi.cc @@ -18,8 +18,8 @@ limitations under the License. #include #include -#include "tensorflow/core/lib/strings/str_util.h" -#include "tensorflow/core/lib/strings/stringprintf.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/profiler/internal/tfprof_scope.h" @@ -35,19 +35,19 @@ const MultiGraphNodeProto& TFMultiShow::Show(const string& prefix, } else { const ShowMultiNode* ret = ShowInternal(opts, nullptr); if (opts.output_type == kOutput[1]) { - printf("%s", (prefix + ret->formatted_str).c_str()); + absl::PrintF("%s%s", prefix, ret->formatted_str); fflush(stdout); } else if (opts.output_type == kOutput[2]) { Status s = WriteStringToFile(Env::Default(), opts.output_options.at(kFileOpts[0]), prefix + ret->formatted_str); if (!s.ok()) { - fprintf(stderr, "%s\n", s.ToString().c_str()); + absl::FPrintF(stderr, "%s\n", s.ToString()); } } else if (opts.output_type == kOutput[3] || opts.output_type == kOutput[4]) { } else { - fprintf(stderr, "Unknown output type: %s\n", opts.output_type.c_str()); + absl::FPrintF(stderr, "Unknown output type: %s\n", opts.output_type); } return ret->proto(); } @@ -158,8 +158,7 @@ string TFMultiShow::FormatLegend(const Options& opts) const { if (opts.select.find(kShown[8]) != opts.select.end()) { legends.push_back("input shapes"); } - return strings::Printf("node name | %s\n", - absl::StrJoin(legends, " | ").c_str()); + return absl::StrFormat("node name | %s\n", absl::StrJoin(legends, " | ")); } string TFMultiShow::FormatInputShapes(const MultiGraphNodeProto& proto) const { @@ -176,14 +175,14 @@ string TFMultiShow::FormatInputShapes(const MultiGraphNodeProto& proto) const { std::vector input_vec; for (const auto& s : input_shapes) { if (s.second.empty()) { - input_vec.push_back(strings::Printf("%d:unknown", s.first)); + input_vec.push_back(absl::StrFormat("%d:unknown", s.first)); } else { - input_vec.push_back(strings::Printf( - "%d:%s", s.first, absl::StrJoin(s.second, "x").c_str())); + input_vec.push_back( + absl::StrFormat("%d:%s", s.first, absl::StrJoin(s.second, "x"))); } } - string shape_type_str = strings::Printf( - "input_type: %s", absl::StrJoin(input_vec, ",\t").c_str()); + string shape_type_str = + absl::StrFormat("input_type: %s", absl::StrJoin(input_vec, ",\t")); auto t = input_shapes_attr.find(shape_type_str); if (t == input_shapes_attr.end()) { input_shapes_attr.insert( @@ -211,9 +210,9 @@ string TFMultiShow::FormatInputShapes(const MultiGraphNodeProto& proto) const { input_types.reserve(shape_count_vec.size()); for (const auto& s : shape_count_vec) { std::tuple t = s.second; - input_types.push_back(strings::Printf( - "%s\t(run*%lld|defined*%lld)\texec_time: %s", s.first.c_str(), - std::get<1>(t), std::get<0>(t), FormatTime(std::get<2>(t)).c_str())); + input_types.push_back(absl::StrFormat( + "%s\t(run*%d|defined*%d)\texec_time: %s", s.first, std::get<1>(t), + std::get<0>(t), FormatTime(std::get<2>(t)))); } return absl::StrJoin(input_types, "\n"); } diff --git a/tensorflow/core/profiler/internal/tfprof_show_multi.h b/tensorflow/core/profiler/internal/tfprof_show_multi.h index 711d35f9753..931ecb954a6 100644 --- a/tensorflow/core/profiler/internal/tfprof_show_multi.h +++ b/tensorflow/core/profiler/internal/tfprof_show_multi.h @@ -25,7 +25,6 @@ limitations under the License. #include "tensorflow/c/checkpoint_reader.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/profiler/internal/tfprof_constants.h" #include "tensorflow/core/profiler/internal/tfprof_node.h" #include "tensorflow/core/profiler/internal/tfprof_node_show.h" @@ -45,8 +44,7 @@ class TFMultiShow { virtual ~TFMultiShow() {} virtual void AddNode(TFGraphNode* node) = 0; virtual void Build() = 0; - virtual const MultiGraphNodeProto& Show(const string& prefix, - const Options& opts) final; + const MultiGraphNodeProto& Show(const string& prefix, const Options& opts); protected: virtual const ShowMultiNode* ShowInternal(const Options& opts, diff --git a/tensorflow/core/profiler/internal/tfprof_stats.cc b/tensorflow/core/profiler/internal/tfprof_stats.cc index 3b1c66880df..22b3bdc2042 100644 --- a/tensorflow/core/profiler/internal/tfprof_stats.cc +++ b/tensorflow/core/profiler/internal/tfprof_stats.cc @@ -16,10 +16,13 @@ limitations under the License. #include "tensorflow/core/profiler/internal/tfprof_stats.h" #include + #include +#include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" #include "tensorflow/core/framework/step_stats.pb.h" -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/regexp.h" #include "tensorflow/core/profiler/internal/tfprof_timeline.h" @@ -55,7 +58,7 @@ TFStats::TFStats(std::unique_ptr graph, ckpt_reader_(std::move(ckpt_reader)) { CHECK(graph) << "Must at least have GraphDef"; - printf("Parsing Inputs...\n"); + absl::PrintF("Parsing Inputs...\n"); AddGraph(std::move(graph)); if (run_meta && run_meta->has_step_stats()) { AddRunMeta(0, std::move(run_meta)); @@ -80,13 +83,13 @@ TFStats::TFStats(const string& filename, string str; Status s = ReadFileToString(Env::Default(), filename, &str); if (!s.ok()) { - fprintf(stderr, "Failed to read profile: %s", s.ToString().c_str()); + absl::FPrintF(stderr, "Failed to read profile: %s", s.ToString()); return; } ProfileProto profile; if (!profile.ParseFromString(str)) { - fprintf(stderr, "Failed to parse profile\n"); + absl::FPrintF(stderr, "Failed to parse profile\n"); return; } for (const auto& entry : profile.id_to_string()) { @@ -163,7 +166,7 @@ const GraphNodeProto& TFStats::ShowGraphNode(const string& cmd, } return graph_view_->Show(prefix, opts); } else { - fprintf(stderr, "Unknown command: %s\n", cmd.c_str()); + absl::FPrintF(stderr, "Unknown command: %s\n", cmd); return empty_graph_node_; } } @@ -178,14 +181,14 @@ const MultiGraphNodeProto& TFStats::ShowMultiGraphNode( if (cmd == kCmds[2]) { if (!has_code_traces()) { - fprintf(stderr, "No code trace information\n"); + absl::FPrintF(stderr, "No code trace information\n"); return empty_multi_graph_node_; } return code_view_->Show(prefix, opts); } else if (cmd == kCmds[3]) { return op_view_->Show(prefix, opts); } else { - fprintf(stderr, "Unknown command: %s\n", cmd.c_str()); + absl::FPrintF(stderr, "Unknown command: %s\n", cmd); return empty_multi_graph_node_; } } @@ -212,11 +215,11 @@ void TFStats::AddGraph(std::unique_ptr graph) { // if not :src_output, then it's the first one (further verify?) auto prefix_pos = node_input.find(":"); if (prefix_pos != node_input.npos) { - std::vector input_parts = str_util::Split(node_input, ":"); - CHECK(input_parts.size() == 2) + std::vector input_parts = absl::StrSplit(node_input, ':'); + DCHECK(input_parts.size() == 2) << "Unknown NodeDef.input format: " << node_input; node_input = input_parts[0]; - CHECK(strings::safe_strto32(input_parts[1], &output_idx)) + DCHECK(absl::SimpleAtoi(input_parts[1], &output_idx)) << "Failed to parse integer: " << output_idx; } if (node_input.substr(0, 1) == "^") { @@ -263,7 +266,7 @@ void TFStats::AddOpLogProto(std::unique_ptr op_log) { void TFStats::AddRunMeta(int64 step, std::unique_ptr run_meta) { if (!run_meta || !run_meta->has_step_stats()) { - fprintf(stderr, "Invalid RunMetadata for step %lld\n", step); + absl::FPrintF(stderr, "Invalid RunMetadata for step %d\n", step); return; } if (steps_.find(step) == steps_.end()) { @@ -349,18 +352,18 @@ void TFStats::WriteProfile(const string& filename) { SerializeToString(&content); Status s = WriteStringToFile(Env::Default(), filename, content); if (!s.ok()) { - fprintf(stderr, "%s\n", s.ToString().c_str()); + absl::FPrintF(stderr, "%s\n", s.ToString()); } } bool TFStats::Validate(const Options& opts) const { if (opts.step >= 0 && steps_.find(opts.step) == steps_.end()) { - fprintf(stderr, - "Options -step=%lld not found.\nAvailable steps: ", opts.step); + absl::FPrintF(stderr, + "Options -step=%d not found.\nAvailable steps: ", opts.step); for (int64 s : steps_) { - fprintf(stderr, "%lld ", s); + absl::FPrintF(stderr, "%d ", s); } - fprintf(stderr, "\n"); + absl::FPrintF(stderr, "\n"); return false; } return true; diff --git a/tensorflow/core/profiler/internal/tfprof_stats.h b/tensorflow/core/profiler/internal/tfprof_stats.h index db148c936c9..82e8314cfec 100644 --- a/tensorflow/core/profiler/internal/tfprof_stats.h +++ b/tensorflow/core/profiler/internal/tfprof_stats.h @@ -33,7 +33,6 @@ limitations under the License. #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/step_stats.pb.h" #include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/profiler/internal/tfprof_code.h" #include "tensorflow/core/profiler/internal/tfprof_graph.h" #include "tensorflow/core/profiler/internal/tfprof_node.h" diff --git a/tensorflow/core/profiler/internal/tfprof_tensor.cc b/tensorflow/core/profiler/internal/tfprof_tensor.cc index a610175b72b..722ece4b37c 100644 --- a/tensorflow/core/profiler/internal/tfprof_tensor.cc +++ b/tensorflow/core/profiler/internal/tfprof_tensor.cc @@ -15,6 +15,9 @@ limitations under the License. #include "tensorflow/core/profiler/internal/tfprof_tensor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" + namespace tensorflow { namespace tfprof { void TFProfTensor::Display(string* formatted_str, @@ -22,7 +25,7 @@ void TFProfTensor::Display(string* formatted_str, if (formatted_str) { if (formatted_str_.length() >= kTFProfTenosrMaxDisplayLen) { *formatted_str = - strings::StrCat(formatted_str_, "...omitted from display\n\n"); + absl::StrCat(formatted_str_, "...omitted from display\n\n"); } else { *formatted_str = formatted_str_; } @@ -68,7 +71,7 @@ void TFProfTensor::Build() { break; } default: { - fprintf(stderr, "Not Supported type %d\n", tensor_->dtype()); + absl::FPrintF(stderr, "Not Supported type %d\n", tensor_->dtype()); break; } } diff --git a/tensorflow/core/profiler/internal/tfprof_tensor.h b/tensorflow/core/profiler/internal/tfprof_tensor.h index 7a088577200..9268e080bd3 100644 --- a/tensorflow/core/profiler/internal/tfprof_tensor.h +++ b/tensorflow/core/profiler/internal/tfprof_tensor.h @@ -24,10 +24,10 @@ limitations under the License. #include +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "tensorflow/core/framework/tensor.h" -#include "tensorflow/core/lib/strings/numbers.h" -#include "tensorflow/core/lib/strings/strcat.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/profiler/tfprof_output.pb.h" namespace tensorflow { @@ -56,23 +56,21 @@ class TFProfTensor { std::ostringstream sstream; sstream << value; if (typeid(value) == typeid(double)) { - double double_val; - CHECK(strings::safe_strtod(sstream.str().c_str(), &double_val)); + double double_val = 0.0; + CHECK(absl::SimpleAtod(sstream.str(), &double_val)); // Crash OK dim->add_value_double(double_val); - formatted_str_ += strings::Printf( - "%.2f ", dim->value_double(dim->value_double_size() - 1)); + absl::StrAppendFormat(&formatted_str_, "%.2f ", + dim->value_double(dim->value_double_size() - 1)); } else if (typeid(value) == typeid(int64)) { - int64 int64_val; - CHECK(strings::safe_strto64(sstream.str().c_str(), &int64_val)); + int64 int64_val = 0; + CHECK(absl::SimpleAtoi(sstream.str(), &int64_val)); // Crash OK dim->add_value_int64(int64_val); - formatted_str_ += strings::Printf( - "%lld ", - static_cast(dim->value_int64(dim->value_int64_size() - 1))); + absl::StrAppendFormat(&formatted_str_, "%d ", + dim->value_int64(dim->value_int64_size() - 1)); } else if (typeid(value) == typeid(string)) { dim->add_value_str(sstream.str()); - formatted_str_ = - strings::StrCat(formatted_str_, "'", - dim->value_str(dim->value_str_size() - 1) + "' "); + absl::StrAppend(&formatted_str_, "'", + dim->value_str(dim->value_str_size() - 1), "' "); } else { CHECK(false) << "Unsupported type: " << typeid(value).name(); } @@ -91,23 +89,21 @@ class TFProfTensor { sstream << values[nstart]; if (typeid(values[nstart]) == typeid(double)) { - double double_val; - CHECK(strings::safe_strtod(sstream.str().c_str(), &double_val)); + double double_val = 0.0; + CHECK(absl::SimpleAtod(sstream.str(), &double_val)); // Crash OK dim->add_value_double(double_val); - formatted_str_ += strings::Printf( - "%.2f ", dim->value_double(dim->value_double_size() - 1)); + absl::StrAppendFormat(&formatted_str_, "%.2f ", + dim->value_double(dim->value_double_size() - 1)); } else if (typeid(values[nstart]) == typeid(int64)) { - int64 int64_val; - CHECK(strings::safe_strto64(sstream.str().c_str(), &int64_val)); + int64 int64_val = 0; + CHECK(absl::SimpleAtoi(sstream.str(), &int64_val)); // Crash OK dim->add_value_int64(int64_val); - formatted_str_ += strings::Printf( - "%lld ", - static_cast(dim->value_int64(dim->value_int64_size() - 1))); + absl::StrAppendFormat(&formatted_str_, "%d ", + dim->value_int64(dim->value_int64_size() - 1)); } else if (typeid(values[nstart]) == typeid(string)) { dim->add_value_str(sstream.str()); - formatted_str_ = - strings::StrCat(formatted_str_, "'", - dim->value_str(dim->value_str_size() - 1) + "' "); + absl::StrAppend(&formatted_str_, "'", + dim->value_str(dim->value_str_size() - 1), "' "); } else { CHECK(false) << "Unsupported type: " << typeid(values[nstart]).name(); } @@ -119,23 +115,23 @@ class TFProfTensor { sstream << values[nstart]; if (typeid(values[nstart]) == typeid(double)) { - double double_val; - CHECK(strings::safe_strtod(sstream.str().c_str(), &double_val)); + double double_val = 0.0; + CHECK(absl::SimpleAtod(sstream.str(), &double_val)); // Crash OK dim->add_value_double(double_val); - formatted_str_ += strings::Printf( - "%.2f ", dim->value_double(dim->value_double_size() - 1)); + absl::StrAppendFormat( + &formatted_str_, "%.2f ", + dim->value_double(dim->value_double_size() - 1)); } else if (typeid(values[nstart]) == typeid(int64)) { - int64 int64_val; - CHECK(strings::safe_strto64(sstream.str().c_str(), &int64_val)); + int64 int64_val = 0; + CHECK(absl::SimpleAtoi(sstream.str(), &int64_val)); // Crash OK dim->add_value_int64(int64_val); - formatted_str_ += strings::Printf( - "%lld ", static_cast( - dim->value_int64(dim->value_int64_size() - 1))); + absl::StrAppendFormat( + &formatted_str_, "%d ", + dim->value_int64(dim->value_int64_size() - 1)); } else if (typeid(values[nstart]) == typeid(string)) { dim->add_value_str(sstream.str()); - formatted_str_ = strings::StrCat( - formatted_str_, "'", - dim->value_str(dim->value_str_size() - 1) + "' "); + absl::StrAppend(&formatted_str_, "'", + dim->value_str(dim->value_str_size() - 1), "' "); } else { CHECK(false) << "Unsupported type: " << typeid(values[nstart]).name(); @@ -158,7 +154,7 @@ class TFProfTensor { void GetValueVec(std::vector* value_vec) { // TODO(xpan): Address the huge tensor problem. if (tensor_->NumElements() > kTFProfTensorMaxWarnLen) { - fprintf(stderr, "Showing huge tensor, the tool might halt...\n"); + absl::FPrintF(stderr, "Showing huge tensor, the tool might halt...\n"); } auto values = tensor_->flat(); for (int64 i = 0; i < tensor_->NumElements(); i++) { diff --git a/tensorflow/core/profiler/internal/tfprof_timeline.cc b/tensorflow/core/profiler/internal/tfprof_timeline.cc index 979b4379141..96e880dc999 100644 --- a/tensorflow/core/profiler/internal/tfprof_timeline.cc +++ b/tensorflow/core/profiler/internal/tfprof_timeline.cc @@ -17,9 +17,9 @@ limitations under the License. #include +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "tensorflow/core/lib/core/status.h" -#include "tensorflow/core/lib/strings/str_util.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/profiler/internal/tfprof_utils.h" namespace tensorflow { @@ -27,15 +27,15 @@ namespace tfprof { namespace { int kMaxDisplayedMemNode = 10; -string GetTimeDevName(const string& dev) { +std::string GetTimeDevName(const std::string& dev) { if (dev.find("stream") != dev.npos) { - return strings::StrCat("Op execution threads: ", dev); + return absl::StrCat("Op execution threads: ", dev); } else { - return strings::StrCat("Op scheduling threads: ", dev); + return absl::StrCat("Op scheduling threads: ", dev); } } -string GetMemoryLaneName(const string& dev) { - return strings::StrCat("mem usage on:", dev); +std::string GetMemoryLaneName(const std::string& dev) { + return absl::StrCat("mem usage on:", dev); } } // namespace @@ -104,7 +104,7 @@ void ChromeTraceFormatter::EmitCounter( Json::Value args2(Json::objectValue); // Need to reserve the same args for all locations. for (int i = 1; i < kMaxDisplayedMemNode; ++i) { - args2[strings::Printf("Top Allocation %02d", i)] = Json::Value("N/A"); + args2[absl::StrFormat("Top Allocation %02d", i)] = Json::Value("N/A"); } int count = 0; for (auto it = tensor_mem.rbegin(); it != tensor_mem.rend(); ++it) { @@ -112,14 +112,14 @@ void ChromeTraceFormatter::EmitCounter( if (bytes < it->first || count >= kMaxDisplayedMemNode) { break; } - args2[strings::Printf("Top Allocation %02d", count)] = - Json::Value(strings::StrCat(it->first / 1000000.0, " MB from ", t)); + args2[absl::StrFormat("Top Allocation %02d", count)] = + Json::Value(absl::StrCat(it->first / 1000000.0, " MB from ", t)); ++count; bytes -= it->first; } } - args2[strings::StrCat("Not Displayed")] = - Json::Value(strings::Printf("%.2f MB", bytes / 1000000.0)); + args2[std::string("Not Displayed")] = + Json::Value(absl::StrFormat("%.2f MB", bytes / 1000000.0)); event2["args"] = args2; events_.push_back(event2); } @@ -136,10 +136,10 @@ string ChromeTraceFormatter::Format() { Json::FastWriter writer; string trace_str = writer.write(trace); if (trace_str.length() > 200 * 1024 * 1024) { - fprintf(stderr, - "Trace file is over 200MB. Chrome might not be able to " - "display it. Consider to use filters (e.g. -min_micros " - "> 1000 or -op_type .*gpu:0.* to reduce the size.\n"); + absl::FPrintF(stderr, + "Trace file is over 200MB. Chrome might not be able to " + "display it. Consider to use filters (e.g. -min_micros " + "> 1000 or -op_type .*gpu:0.* to reduce the size.\n"); } return trace_str; } @@ -235,7 +235,7 @@ void Timeline::GenerateGraphTimeline(const std::vector& gnodes) { } AllocateLanes(); - fprintf(stdout, "generating trace file.\n"); + absl::FPrintF(stdout, "generating trace file.\n"); int64 flow_id = 1; for (const auto& process : alloc_nodes_) { for (const auto& lane : process.second) { @@ -301,8 +301,8 @@ void Timeline::GenerateGraphTimeline(const std::vector& gnodes) { dev.first, cur_bytes_in_use, tensor_mem); } if (IsPlacedOnAccelerator(dev.first)) { - fprintf(stdout, "%s peak memory: %.2f MB\n", dev.first.c_str(), - max_bytes_in_use / 1000000.0); + absl::FPrintF(stdout, "%s peak memory: %.2f MB\n", dev.first, + max_bytes_in_use / 1000000.0); } } OutputTimeline(); @@ -321,21 +321,23 @@ void Timeline::GenerateCodeTimeline(const CodeNode* node) { } void Timeline::OutputTimeline() { - string outfile = strings::Printf("%s_%lld", outfile_.c_str(), step()); + std::string outfile = absl::StrFormat("%s_%d", outfile_, step()); Status s = WriteStringToFile(Env::Default(), outfile, chrome_formatter_.Format()); if (!s.ok()) { - fprintf(stderr, "Failed to write timeline file: %s\nError: %s\n", - outfile.c_str(), s.ToString().c_str()); + absl::FPrintF(stderr, "Failed to write timeline file: %s\nError: %s\n", + outfile, s.ToString()); return; } - fprintf(stdout, "\n******************************************************\n"); - fprintf(stdout, - "Timeline file is written to %s.\n" - "Open a Chrome browser, enter URL chrome://tracing and " - "load the timeline file.", - outfile.c_str()); - fprintf(stdout, "\n******************************************************\n"); + absl::FPrintF(stdout, + "\n******************************************************\n"); + absl::FPrintF(stdout, + "Timeline file is written to %s.\n" + "Open a Chrome browser, enter URL chrome://tracing and " + "load the timeline file.", + outfile); + absl::FPrintF(stdout, + "\n******************************************************\n"); fflush(stdout); } diff --git a/tensorflow/core/profiler/internal/tfprof_timeline.h b/tensorflow/core/profiler/internal/tfprof_timeline.h index baf3fb2bedb..834e3c9be91 100644 --- a/tensorflow/core/profiler/internal/tfprof_timeline.h +++ b/tensorflow/core/profiler/internal/tfprof_timeline.h @@ -16,10 +16,10 @@ limitations under the License. #ifndef TENSORFLOW_CORE_PROFILER_INTERNAL_TFPROF_TIMELINE_H_ #define TENSORFLOW_CORE_PROFILER_INTERNAL_TFPROF_TIMELINE_H_ +#include "absl/strings/str_cat.h" #include "include/json/json.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/step_stats.pb.h" -#include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/profiler/internal/tfprof_node_show.h" #include "tensorflow/core/protobuf/config.pb.h" @@ -143,7 +143,7 @@ class Timeline { void EmitTreeNode(const Node* node, int64 start_time, int64 duration, int64 depth, std::set* visited_depth) { if (visited_depth->find(depth) == visited_depth->end()) { - chrome_formatter_.EmitPID(strings::StrCat("Scope:", depth), depth); + chrome_formatter_.EmitPID(absl::StrCat("Scope:", depth), depth); visited_depth->insert(depth); } diff --git a/tensorflow/core/profiler/internal/tfprof_utils.cc b/tensorflow/core/profiler/internal/tfprof_utils.cc index 5ff66cf5925..0f7670f15dc 100644 --- a/tensorflow/core/profiler/internal/tfprof_utils.cc +++ b/tensorflow/core/profiler/internal/tfprof_utils.cc @@ -16,14 +16,16 @@ limitations under the License. #include "tensorflow/core/profiler/internal/tfprof_utils.h" #include + #include #include #include -#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/lib/strings/stringprintf.h" +#include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" #include "tensorflow/core/platform/protobuf.h" #include "tensorflow/core/platform/regexp.h" @@ -31,33 +33,33 @@ namespace tensorflow { namespace tfprof { string FormatNumber(int64 n) { if (n < 1000) { - return strings::Printf("%lld", n); + return absl::StrFormat("%d", n); } else if (n < 1000000) { - return strings::Printf("%.2fk", n / 1000.0); + return absl::StrFormat("%.2fk", n / 1000.0); } else if (n < 1000000000) { - return strings::Printf("%.2fm", n / 1000000.0); + return absl::StrFormat("%.2fm", n / 1000000.0); } else { - return strings::Printf("%.2fb", n / 1000000000.0); + return absl::StrFormat("%.2fb", n / 1000000000.0); } } string FormatTime(int64 micros) { if (micros < 1000) { - return strings::Printf("%lldus", micros); + return absl::StrFormat("%dus", micros); } else if (micros < 1000000) { - return strings::Printf("%.2fms", micros / 1000.0); + return absl::StrFormat("%.2fms", micros / 1000.0); } else { - return strings::Printf("%.2fsec", micros / 1000000.0); + return absl::StrFormat("%.2fsec", micros / 1000000.0); } } string FormatMemory(int64 bytes) { if (bytes < 1000) { - return strings::Printf("%lldB", bytes); + return absl::StrFormat("%dB", bytes); } else if (bytes < 1000000) { - return strings::Printf("%.2fKB", bytes / 1000.0); + return absl::StrFormat("%.2fKB", bytes / 1000.0); } else { - return strings::Printf("%.2fMB", bytes / 1000000.0); + return absl::StrFormat("%.2fMB", bytes / 1000000.0); } } @@ -88,7 +90,7 @@ tensorflow::Status ReturnError(const std::vector& pieces, int idx) { } return tensorflow::Status( tensorflow::error::INVALID_ARGUMENT, - strings::StrCat("Invalid option '", pieces[idx], "' value: '", val, "'")); + absl::StrCat("Invalid option '", pieces[idx], "' value: '", val, "'")); } bool CaseEqual(StringPiece s1, StringPiece s2) { @@ -114,8 +116,7 @@ bool StringToBool(StringPiece str, bool* value) { tensorflow::Status ParseCmdLine(const string& line, string* cmd, tensorflow::tfprof::Options* opts) { - std::vector pieces = - str_util::Split(line, ' ', str_util::SkipEmpty()); + std::vector pieces = absl::StrSplit(line, ' ', absl::SkipEmpty()); std::vector cmds_str(kCmds, kCmds + sizeof(kCmds) / sizeof(*kCmds)); if (std::find(cmds_str.begin(), cmds_str.end(), pieces[0]) == @@ -128,74 +129,73 @@ tensorflow::Status ParseCmdLine(const string& line, string* cmd, for (int i = 1; i < pieces.size(); ++i) { if (pieces[i] == string(tensorflow::tfprof::kOptions[0])) { if (pieces.size() <= i + 1 || - !strings::safe_strto32(pieces[i + 1], &opts->max_depth)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->max_depth)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[1]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_bytes)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_bytes)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[2]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_peak_bytes)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_peak_bytes)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[3]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_residual_bytes)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_residual_bytes)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[4]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_output_bytes)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_output_bytes)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[5]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_micros)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_micros)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[6]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], - &opts->min_accelerator_micros)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_accelerator_micros)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[7]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_cpu_micros)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_cpu_micros)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[8]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_params)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_params)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[9]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_float_ops)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_float_ops)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[10]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->min_occurrence)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->min_occurrence)) { return ReturnError(pieces, i); } ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[11]) { if (pieces.size() <= i + 1 || - !strings::safe_strto64(pieces[i + 1], &opts->step)) { + !absl::SimpleAtoi(pieces[i + 1], &opts->step)) { return ReturnError(pieces, i); } ++i; @@ -215,39 +215,39 @@ tensorflow::Status ParseCmdLine(const string& line, string* cmd, if (pieces.size() <= i + 1) { return ReturnError(pieces, i); } - opts->account_type_regexes = str_util::Split(StripQuote(pieces[i + 1]), - ',', str_util::SkipEmpty()); + opts->account_type_regexes = + absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::SkipEmpty()); ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[14]) { if (pieces.size() <= i + 1) { return ReturnError(pieces, i); } - opts->start_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',', - str_util::SkipEmpty()); + opts->start_name_regexes = + absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::SkipEmpty()); ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[15]) { if (pieces.size() <= i + 1) { return ReturnError(pieces, i); } - opts->trim_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',', - str_util::SkipEmpty()); + opts->trim_name_regexes = + absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::SkipEmpty()); ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[16]) { if (pieces.size() <= i + 1) { return ReturnError(pieces, i); } - opts->show_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',', - str_util::SkipEmpty()); + opts->show_name_regexes = + absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::SkipEmpty()); ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[17]) { if (pieces.size() <= i + 1) { return ReturnError(pieces, i); } - opts->hide_name_regexes = str_util::Split(StripQuote(pieces[i + 1]), ',', - str_util::SkipEmpty()); + opts->hide_name_regexes = + absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::SkipEmpty()); ++i; } else if (pieces[i] == tensorflow::tfprof::kOptions[18]) { - if ((pieces.size() > i + 1 && pieces[i + 1].find("-") == 0) || + if ((pieces.size() > i + 1 && absl::StartsWith(pieces[i + 1], "-")) || pieces.size() == i + 1) { opts->account_displayed_op_only = true; } else if (!StringToBool(pieces[i + 1], @@ -262,8 +262,8 @@ tensorflow::Status ParseCmdLine(const string& line, string* cmd, } std::set shown_set(kShown, kShown + sizeof(kShown) / sizeof(*kShown)); - std::vector requested_vector = str_util::Split( - StripQuote(pieces[i + 1]), ',', str_util::SkipEmpty()); + std::vector requested_vector = + absl::StrSplit(StripQuote(pieces[i + 1]), ',', absl::SkipEmpty()); std::set requested_set(requested_vector.begin(), requested_vector.end()); for (const string& requested : requested_set) { @@ -290,13 +290,13 @@ tensorflow::Status ParseCmdLine(const string& line, string* cmd, } void PrintHelp() { - printf( + absl::PrintF( "See https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/profiler/" "README.md for profiler tutorial.\n"); - printf( + absl::PrintF( "See https://github.com/tensorflow/tensorflow/tree/master/tensorflow/core/profiler/" "g3doc/command_line.md for command line tool tutorial.\n"); - printf( + absl::PrintF( "profiler --profile_path= # required\n" "\nOr:\n\n" "profiler --graph_path= " @@ -305,7 +305,7 @@ void PrintHelp() { "# Contains runtime info. Optional.\n" " --run_log_path= " "# Contains extra source code, flops, custom type info. Optional\n\n"); - printf( + absl::PrintF( "\nTo skip interactive mode, append one of the following commands:\n" " scope: Organize profiles based on name scopes.\n" " graph: Organize profiles based on graph node input/output.\n" @@ -392,8 +392,8 @@ string QueryDoc(const string& cmd, const Options& opts) { if (s == kShown[0]) { helps.push_back(kBytes); } else if (s == kShown[1]) { - helps.push_back(strings::StrCat(kTotalMicrosHelp, "\n", kCPUHelp, "\n", - kAccMicrosHelp)); + helps.push_back( + absl::StrCat(kTotalMicrosHelp, "\n", kCPUHelp, "\n", kAccMicrosHelp)); } else if (s == kShown[2]) { helps.push_back(kParams); } else if (s == kShown[3]) { @@ -422,8 +422,8 @@ string QueryDoc(const string& cmd, const Options& opts) { helps.push_back("Unknown select: " + s); } } - return strings::StrCat("\nDoc:\n", cmd_help, "\n", absl::StrJoin(helps, "\n"), - "\n\n"); + return absl::StrCat("\nDoc:\n", cmd_help, "\n", absl::StrJoin(helps, "\n"), + "\n\n"); } } // namespace tfprof diff --git a/tensorflow/core/profiler/internal/traceme_recorder_test.cc b/tensorflow/core/profiler/internal/traceme_recorder_test.cc index fc723fa5f8c..90478881361 100644 --- a/tensorflow/core/profiler/internal/traceme_recorder_test.cc +++ b/tensorflow/core/profiler/internal/traceme_recorder_test.cc @@ -18,6 +18,7 @@ limitations under the License. #include #include +#include "absl/strings/str_cat.h" #include "tensorflow/core/lib/core/threadpool.h" #include "tensorflow/core/platform/env_time.h" #include "tensorflow/core/platform/notification.h" @@ -81,7 +82,7 @@ TEST(RecorderTest, Multithreaded) { uint64 start_time = Env::Default()->NowNanos(); uint64 end_time = start_time + kNanosInSec; TraceMeRecorder::Record({/*activity_id=*/j++, - /*name=*/strings::StrCat(i), start_time, + /*name=*/absl::StrCat(i), start_time, end_time}); }; thread_count.fetch_add(1, std::memory_order_relaxed); diff --git a/tensorflow/core/profiler/lib/BUILD b/tensorflow/core/profiler/lib/BUILD index 54b85b03045..e64a8e1fcc6 100644 --- a/tensorflow/core/profiler/lib/BUILD +++ b/tensorflow/core/profiler/lib/BUILD @@ -17,14 +17,15 @@ cc_library( ":profiler_utils", "//tensorflow/core/profiler/internal:profiler_interface", "//tensorflow/core/profiler/internal:profiler_factory", - "@com_google_absl//absl/strings", + "//tensorflow/core/profiler/protobuf:xplane_proto_cc", ] + select({ "//tensorflow:android": [], "//conditions:default": [ + "//tensorflow/core/profiler/convert:run_metadata_to_trace_events", "//tensorflow/core/platform", - "//tensorflow/core:core_cpu_lib", "//tensorflow/core:lib", "//tensorflow/core:lib_internal", + "//tensorflow/core:framework", "//tensorflow/core:protos_all_cc", ], }), diff --git a/tensorflow/core/profiler/lib/profiler_session.cc b/tensorflow/core/profiler/lib/profiler_session.cc index 3f69e5ad624..3882a63432e 100644 --- a/tensorflow/core/profiler/lib/profiler_session.cc +++ b/tensorflow/core/profiler/lib/profiler_session.cc @@ -15,18 +15,13 @@ limitations under the License. #include "tensorflow/core/profiler/lib/profiler_session.h" -#include - -#include - -#include "absl/strings/str_split.h" -#include "absl/strings/string_view.h" -#include "tensorflow/core/common_runtime/step_stats_collector.h" -#include "tensorflow/core/platform/env.h" +#include "tensorflow/core/lib/core/errors.h" +#include "tensorflow/core/platform/env_time.h" #include "tensorflow/core/platform/mutex.h" #include "tensorflow/core/platform/platform.h" #include "tensorflow/core/platform/types.h" #if !defined(IS_MOBILE_PLATFORM) +#include "tensorflow/core/profiler/convert/run_metadata_to_trace_events.h" #include "tensorflow/core/profiler/internal/profiler_factory.h" #include "tensorflow/core/profiler/lib/profiler_utils.h" #endif @@ -34,137 +29,13 @@ limitations under the License. #include "tensorflow/core/protobuf/error_codes.pb.h" #include "tensorflow/core/protobuf/trace_events.pb.h" #include "tensorflow/core/util/env_var.h" +#include "tensorflow/core/util/ptr_util.h" namespace tensorflow { -namespace { - -// Given a node_name in the format "op_name:op_type", returns the "op_type". -// If the "op_type" is missing, returns the node_name. -// This is done so all ops with the same type appear in the same color in trace -// viewer. -inline string EventName(absl::string_view node_name) { - // NOTE: open source device tracer now append cupti kernel name after - // annotation as node_name, @@ is used as separator. kernel name is - // demangled and possibly contains "::" patterns. - std::vector segments = absl::StrSplit(node_name, "@@"); - if (segments.size() > 1) { // unparsed - // find the last annotation. - std::vector annotation_stack = - absl::StrSplit(segments.front(), "::"); - // strip trace argument. - std::vector annotation_parts = - absl::StrSplit(annotation_stack.back(), '#'); - std::vector parts = - absl::StrSplit(annotation_parts.front(), ':'); - return string(parts.back()); - } else { - std::vector parts = absl::StrSplit(node_name, ':'); - return string(parts.back()); - } -} - -void AssignLanes(RunMetadata* run_metadata) { - for (size_t device_id = 0; - device_id < run_metadata->step_stats().dev_stats_size(); ++device_id) { - auto* device_stats = - run_metadata->mutable_step_stats()->mutable_dev_stats(device_id); - if (device_stats->thread_names_size() > 0 || - device_stats->node_stats_size() == 0) { - continue; - } - std::vector lanes; - for (auto ns = device_stats->mutable_node_stats()->rbegin(); - ns != device_stats->mutable_node_stats()->rend(); ns++) { - uint64 end_micros = ns->all_start_micros() + ns->all_end_rel_micros(); - bool found_lane = false; - for (size_t l = 0; l < lanes.size(); l++) { - if (end_micros <= lanes[l]) { - ns->set_thread_id(l); - found_lane = true; - lanes[l] = ns->all_start_micros(); - break; - } - } - if (!found_lane) { - ns->set_thread_id(lanes.size()); - lanes.push_back(ns->all_start_micros()); - } - } - } -} - -void ConvertRunMetadataToTraceEvent(RunMetadata* run_metadata, - profiler::Trace* trace, - const uint64 profile_start_time_micros, - const uint64 profile_end_time_micros) { - AssignLanes(run_metadata); - auto trace_devices = trace->mutable_devices(); - - for (size_t device_id = 0; - device_id < run_metadata->step_stats().dev_stats_size(); ++device_id) { - // Create device - auto* device_stats = - run_metadata->mutable_step_stats()->mutable_dev_stats(device_id); - // Don't generate trace events for "derived or aggregated" devices, the - // events in these devices are duplicated from other streams. - if (absl::EndsWith(device_stats->device(), "stream:all") || - absl::EndsWith(device_stats->device(), "sync") || - absl::EndsWith(device_stats->device(), "memcpy")) - continue; - profiler::Device device; - device.set_name(device_stats->device()); - device.set_device_id(device_id); - profiler::Resource resource; - resource.set_name("0"); - resource.set_resource_id(0); - (*device.mutable_resources())[0] = resource; - for (const auto& thread_name : device_stats->thread_names()) { - profiler::Resource resource; - resource.set_resource_id(thread_name.first); - resource.set_name(thread_name.second); - (*device.mutable_resources())[thread_name.first] = resource; - } - (*trace_devices)[device_id] = device; - - // Emit events. - for (auto node : device_stats->node_stats()) { - if (node.all_start_micros() < profile_start_time_micros || - node.all_start_micros() + node.all_end_rel_micros() > - profile_end_time_micros) { - continue; - } - auto* event = trace->add_trace_events(); - auto* args = event->mutable_args(); - event->set_device_id(device_id); - event->set_resource_id(node.thread_id()); - event->set_name(EventName(node.node_name())); - event->set_timestamp_ps( - (node.all_start_micros() - profile_start_time_micros) * - EnvTime::kMicrosToPicos); - event->set_duration_ps(node.all_end_rel_micros() * - EnvTime::kMicrosToPicos); - if (!node.timeline_label().empty()) { - std::vector label_parts = - absl::StrSplit(node.timeline_label(), "@@"); - (*args)["label"] = string(label_parts.front()); - if (label_parts.size() == 2) { - // NOTE: we can further parse annotation here. - (*args)["annotation"] = string(label_parts.back()); - } - } - if (event->name() != node.node_name()) { - (*args)["long name"] = node.node_name(); - } - } - } - - // TODO(fishx): Convert allocation data as well. -} -} // namespace /*static*/ std::unique_ptr ProfilerSession::Create( const profiler::ProfilerOptions& options) { - return absl::WrapUnique(new ProfilerSession(options)); + return WrapUnique(new ProfilerSession(options)); } /*static*/ std::unique_ptr ProfilerSession::Create() { @@ -184,6 +55,28 @@ Status ProfilerSession::Status() { return status_; } +Status ProfilerSession::CollectData(profiler::XSpace* space) { + mutex_lock l(mutex_); + if (!status_.ok()) return status_; + for (auto& profiler : profilers_) { + profiler->Stop().IgnoreError(); + } + + for (auto& profiler : profilers_) { + profiler->CollectData(space).IgnoreError(); + } + + if (active_) { + // Allow another session to start. +#if !defined(IS_MOBILE_PLATFORM) + profiler::ReleaseProfilerLock(); +#endif + active_ = false; + } + + return Status::OK(); +} + Status ProfilerSession::CollectData(RunMetadata* run_metadata) { mutex_lock l(mutex_); if (!status_.ok()) return status_; @@ -209,12 +102,12 @@ Status ProfilerSession::CollectData(RunMetadata* run_metadata) { Status ProfilerSession::SerializeToString(string* content) { RunMetadata run_metadata; TF_RETURN_IF_ERROR(CollectData(&run_metadata)); - profiler::Trace trace; - ConvertRunMetadataToTraceEvent( - &run_metadata, &trace, start_time_micros_, - Env::Default()->NowNanos() / EnvTime::kMicrosToNanos); - +#if !defined(IS_MOBILE_PLATFORM) + uint64 end_time_ns = EnvTime::NowNanos(); + profiler::ConvertRunMetadataToTraceEvents(start_time_ns_, end_time_ns, + &run_metadata, &trace); +#endif trace.SerializeToString(content); return Status::OK(); } @@ -225,10 +118,16 @@ ProfilerSession::ProfilerSession(const profiler::ProfilerOptions& options) #else : active_(false), #endif - start_time_micros_(Env::Default()->NowNanos() / EnvTime::kMicrosToNanos) { + start_time_ns_(EnvTime::NowNanos()) { if (!active_) { +#if !defined(IS_MOBILE_PLATFORM) status_ = tensorflow::Status(error::UNAVAILABLE, "Another profiler session is active."); +#else + status_ = + tensorflow::Status(error::UNIMPLEMENTED, + "Profiler is unimplemented for mobile platforms."); +#endif return; } diff --git a/tensorflow/core/profiler/lib/profiler_session.h b/tensorflow/core/profiler/lib/profiler_session.h index 85b9901d889..409e23e013b 100644 --- a/tensorflow/core/profiler/lib/profiler_session.h +++ b/tensorflow/core/profiler/lib/profiler_session.h @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/core/platform/mutex.h" #include "tensorflow/core/platform/thread_annotations.h" #include "tensorflow/core/profiler/internal/profiler_interface.h" +#include "tensorflow/core/profiler/protobuf/xplane.pb.h" #include "tensorflow/core/protobuf/config.pb.h" namespace tensorflow { @@ -45,6 +46,9 @@ class ProfilerSession { tensorflow::Status Status() LOCKS_EXCLUDED(mutex_); + tensorflow::Status CollectData(profiler::XSpace* space) + LOCKS_EXCLUDED(mutex_); + tensorflow::Status CollectData(RunMetadata* run_metadata) LOCKS_EXCLUDED(mutex_); @@ -65,7 +69,7 @@ class ProfilerSession { bool active_ GUARDED_BY(mutex_); tensorflow::Status status_ GUARDED_BY(mutex_); - const uint64 start_time_micros_; + const uint64 start_time_ns_; mutex mutex_; }; diff --git a/tensorflow/core/profiler/profiler.cc b/tensorflow/core/profiler/profiler.cc index cdcb8dddf6d..5fd3a32a680 100644 --- a/tensorflow/core/profiler/profiler.cc +++ b/tensorflow/core/profiler/profiler.cc @@ -15,19 +15,21 @@ limitations under the License. #include #include + #include #include #include #include #include +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" #include "linenoise.h" #include "tensorflow/c/c_api.h" #include "tensorflow/c/checkpoint_reader.h" #include "tensorflow/core/framework/graph.pb.h" #include "tensorflow/core/framework/types.h" #include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/init_main.h" #include "tensorflow/core/platform/protobuf.h" @@ -93,7 +95,7 @@ int Run(int argc, char** argv) { string FLAGS_select = "micros"; string FLAGS_output = ""; for (int i = 0; i < argc; i++) { - fprintf(stderr, "%s\n", argv[i]); + absl::FPrintF(stderr, "%s\n", argv[i]); } std::vector flag_list = { @@ -135,31 +137,31 @@ int Run(int argc, char** argv) { string usage = Flags::Usage(argv[0], flag_list); bool parse_ok = Flags::Parse(&argc, argv, flag_list); if (!parse_ok) { - printf("%s", usage.c_str()); + absl::PrintF("%s", usage); return (2); } port::InitMain(argv[0], &argc, &argv); if (!FLAGS_profile_path.empty() && (!FLAGS_graph_path.empty() || !FLAGS_run_meta_path.empty())) { - fprintf(stderr, - "--profile_path is set, do not set --graph_path or " - "--run_meta_path\n"); + absl::FPrintF(stderr, + "--profile_path is set, do not set --graph_path or " + "--run_meta_path\n"); return 1; } std::vector account_type_regexes = - str_util::Split(FLAGS_account_type_regexes, ',', str_util::SkipEmpty()); + absl::StrSplit(FLAGS_account_type_regexes, ',', absl::SkipEmpty()); std::vector start_name_regexes = - str_util::Split(FLAGS_start_name_regexes, ',', str_util::SkipEmpty()); + absl::StrSplit(FLAGS_start_name_regexes, ',', absl::SkipEmpty()); std::vector trim_name_regexes = - str_util::Split(FLAGS_trim_name_regexes, ',', str_util::SkipEmpty()); + absl::StrSplit(FLAGS_trim_name_regexes, ',', absl::SkipEmpty()); std::vector show_name_regexes = - str_util::Split(FLAGS_show_name_regexes, ',', str_util::SkipEmpty()); + absl::StrSplit(FLAGS_show_name_regexes, ',', absl::SkipEmpty()); std::vector hide_name_regexes = - str_util::Split(FLAGS_hide_name_regexes, ',', str_util::SkipEmpty()); + absl::StrSplit(FLAGS_hide_name_regexes, ',', absl::SkipEmpty()); std::vector select = - str_util::Split(FLAGS_select, ',', str_util::SkipEmpty()); + absl::StrSplit(FLAGS_select, ',', absl::SkipEmpty()); string output_type; std::map output_options; @@ -183,14 +185,14 @@ int Run(int argc, char** argv) { } } - printf("Reading Files...\n"); + absl::PrintF("Reading Files...\n"); std::unique_ptr ckpt_reader; TF_Status* status = TF_NewStatus(); if (!FLAGS_checkpoint_path.empty()) { ckpt_reader.reset( new checkpoint::CheckpointReader(FLAGS_checkpoint_path, status)); if (TF_GetCode(status) != TF_OK) { - fprintf(stderr, "%s\n", TF_Message(status)); + absl::FPrintF(stderr, "%s\n", TF_Message(status)); TF_DeleteStatus(status); return 1; } @@ -201,15 +203,14 @@ int Run(int argc, char** argv) { if (!FLAGS_profile_path.empty()) { tf_stat.reset(new TFStats(FLAGS_profile_path, std::move(ckpt_reader))); } else { - printf( + absl::PrintF( "Try to use a single --profile_path instead of " "graph_path,op_log_path,run_meta_path\n"); std::unique_ptr graph(new GraphDef()); if (!FLAGS_graph_path.empty()) { s = ReadProtoFile(Env::Default(), FLAGS_graph_path, graph.get(), false); if (!s.ok()) { - fprintf(stderr, "Failed to read graph_path: %s\n", - s.ToString().c_str()); + absl::FPrintF(stderr, "Failed to read graph_path: %s\n", s.ToString()); return 1; } } @@ -219,12 +220,11 @@ int Run(int argc, char** argv) { string op_log_str; s = ReadFileToString(Env::Default(), FLAGS_op_log_path, &op_log_str); if (!s.ok()) { - fprintf(stderr, "Failed to read op_log_path: %s\n", - s.ToString().c_str()); + absl::FPrintF(stderr, "Failed to read op_log_path: %s\n", s.ToString()); return 1; } if (!ParseProtoUnlimited(op_log.get(), op_log_str)) { - fprintf(stderr, "Failed to parse op_log_path\n"); + absl::FPrintF(stderr, "Failed to parse op_log_path\n"); return 1; } } @@ -232,18 +232,19 @@ int Run(int argc, char** argv) { std::move(ckpt_reader))); std::vector run_meta_files = - str_util::Split(FLAGS_run_meta_path, ',', str_util::SkipEmpty()); + absl::StrSplit(FLAGS_run_meta_path, ',', absl::SkipEmpty()); for (int i = 0; i < run_meta_files.size(); ++i) { std::unique_ptr run_meta(new RunMetadata()); s = ReadProtoFile(Env::Default(), run_meta_files[i], run_meta.get(), true); if (!s.ok()) { - fprintf(stderr, "Failed to read run_meta_path %s. Status: %s\n", - run_meta_files[i].c_str(), s.ToString().c_str()); + absl::FPrintF(stderr, "Failed to read run_meta_path %s. Status: %s\n", + run_meta_files[i], s.ToString()); return 1; } tf_stat->AddRunMeta(i, std::move(run_meta)); - fprintf(stdout, "run graph coverage: %.2f\n", tf_stat->run_coverage()); + absl::FPrintF(stdout, "run graph coverage: %.2f\n", + tf_stat->run_coverage()); } } @@ -280,9 +281,9 @@ int Run(int argc, char** argv) { char* line = linenoise("tfprof> "); if (line == nullptr) { if (!looped) { - fprintf(stderr, - "Cannot start interative shell, " - "use 'bazel-bin' instead of 'bazel run'.\n"); + absl::FPrintF(stderr, + "Cannot start interative shell, " + "use 'bazel-bin' instead of 'bazel run'.\n"); } break; } @@ -291,7 +292,7 @@ int Run(int argc, char** argv) { free(line); if (line_s.empty()) { - printf("%s", opts.ToString().c_str()); + absl::PrintF("%s", opts.ToString()); continue; } linenoiseHistoryAdd(line_s.c_str()); @@ -300,7 +301,7 @@ int Run(int argc, char** argv) { Options new_opts = opts; Status s = ParseCmdLine(line_s, &cmd, &new_opts); if (!s.ok()) { - fprintf(stderr, "E: %s\n", s.ToString().c_str()); + absl::FPrintF(stderr, "E: %s\n", s.ToString()); continue; } if (cmd == kCmds[5]) { diff --git a/tensorflow/core/profiler/protobuf/BUILD b/tensorflow/core/profiler/protobuf/BUILD index 97443038576..c9275d92544 100644 --- a/tensorflow/core/profiler/protobuf/BUILD +++ b/tensorflow/core/profiler/protobuf/BUILD @@ -33,12 +33,26 @@ tf_proto_library( visibility = [":friends"], ) +tf_proto_library( + name = "steps_db_proto", + srcs = ["steps_db.proto"], + cc_api_version = 2, + protodeps = [":op_metrics_proto"], + visibility = [ + ":friends", + ], +) + tf_proto_library( name = "op_stats_proto", srcs = ["op_stats.proto"], cc_api_version = 2, protodeps = [ ":op_metrics_proto", + ":steps_db_proto", + ], + visibility = [ + ":friends", ], ) diff --git a/tensorflow/core/profiler/protobuf/op_stats.proto b/tensorflow/core/profiler/protobuf/op_stats.proto index 6391e1f8c90..bcde4ca3678 100644 --- a/tensorflow/core/profiler/protobuf/op_stats.proto +++ b/tensorflow/core/profiler/protobuf/op_stats.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package tensorflow.profiler; import "tensorflow/core/profiler/protobuf/op_metrics.proto"; +import "tensorflow/core/profiler/protobuf/steps_db.proto"; // Performance environment, e.g the peak performance capabilities of the device. message PerfEnv { @@ -25,4 +26,6 @@ message OpStats { OpMetricsDb device_op_metrics_db = 2; // Performance environment of the op metrics collected. PerfEnv perf_env = 3; + // The database of step sequences. + StepDatabaseResult step_db = 4; } diff --git a/tensorflow/core/profiler/protobuf/steps_db.proto b/tensorflow/core/profiler/protobuf/steps_db.proto new file mode 100644 index 00000000000..df58ceeb4ec --- /dev/null +++ b/tensorflow/core/profiler/protobuf/steps_db.proto @@ -0,0 +1,101 @@ +syntax = "proto3"; + +package tensorflow.profiler; + +import "google/protobuf/any.proto"; +import "tensorflow/core/profiler/protobuf/op_metrics.proto"; + +// Breakdown of step-time on generic hardware. Note that these components are +// mutually exclusive so that adding them together is equal to the step time. If +// an execution time interval has multiple types of event happening, we need to +// pick one of the event type to attribute the time interval to. +message GenericStepBreakdown { + // Map event type to the accumulated duration in + // picoseconds of that type. + map type_ps = 1; +} + +// Next ID: 5 +// Result proto for StepInfo. +message StepInfoResult { + // The step number. + uint32 step_num = 1; + // The step duration in picoseconds. + uint64 duration_ps = 2; + // The start time of this step in picoseconds. + uint64 begin_ps = 3; + // Breakdown of the step-time. Can be unpacked into a GenericStepBreakdown. + google.protobuf.Any step_breakdown = 4; +} + +// Result proto for metrics on flow events. +message FlowEventInfo { + // Unique id for each send and recv pair. + uint64 flow_id = 1; + // Channel id generated by the XLA compiler, it is statically unique within an + // HloModule. + int64 channel_id = 2; + // The name of the hlo op. + string name = 3; + // Category of the hlo op. + string category = 4; + // The start time in picoseconds of the op event. + uint64 start_time_ps = 5; + // The end time in picoseconds of the op event. + uint64 end_time_ps = 6; + // The size of the op in bytes. + uint64 byte_size = 7; + // The replica id of the program running the flow event. + uint32 replica_id = 8; +} + +// Result database for core to core flow events. +message FlowDbResult { + repeated FlowEventInfo flow_info = 1; +} + +// Result proto for all -educe ops. +message AllReduceInfo { + // Unique id for all-reduce ops. + uint64 id = 1; + // The name of the hlo op. + string name = 2; + // For all-reduce nodes from different modules, if they have the same + // all_reduce_id, they will be 'Allreduce'd'. If empty, AllReduce will not be + // applied across modules. + uint64 all_reduce_id = 3; + // The start time in picoseconds of the op event. + uint64 start_time_ps = 4; + // The end time in picoseconds of the op event. + uint64 end_time_ps = 5; + // The size of the op in bytes. + uint64 byte_size = 6; +} + +// Result database for all-reduce ops. +message AllReduceDbResult { + repeated AllReduceInfo all_reduce_info = 1; +} + +// Result proto for information in a step across all cores. +message PerCoreStepInfo { + // The step number. + uint32 step_num = 1; + // A map from core_id to StepInfo. + map step_info_per_core = 2; + // The result for the per-step HLO-metric database. + OpMetricsDb hlo_metrics_db = 3; + // The result for send and recv flows. + map flow_db_per_core = 4; + // A map from core ID to program replica id. Replica id map could change + // during a profile session, but should stay stable within a step. + map core_id_to_replica_id_map = 5; + // The result for all-reduce ops.hlo_metrics_db + map all_reduce_db_per_core = 6; +} + +// Result proto for a StepDatabase. +message StepDatabaseResult { + // A sequence of PerCoreStepInfo. + repeated PerCoreStepInfo step_sequence = 1; +} diff --git a/tensorflow/core/profiler/rpc/BUILD b/tensorflow/core/profiler/rpc/BUILD index 50a54b2c189..f22cd23333e 100644 --- a/tensorflow/core/profiler/rpc/BUILD +++ b/tensorflow/core/profiler/rpc/BUILD @@ -31,5 +31,6 @@ cc_library( "//tensorflow/core/profiler:profiler_service_proto_cc", "//tensorflow/core/profiler/lib:profiler_lib", "//tensorflow/core/profiler/lib:profiler_session", + "@com_google_absl//absl/strings", ], ) diff --git a/tensorflow/core/profiler/rpc/client/BUILD b/tensorflow/core/profiler/rpc/client/BUILD index da48c3eaffc..f985a5b47d4 100644 --- a/tensorflow/core/profiler/rpc/client/BUILD +++ b/tensorflow/core/profiler/rpc/client/BUILD @@ -33,6 +33,7 @@ cc_library( "//tensorflow/core:protos_all_cc", "//tensorflow/core/profiler:op_profile_proto_cc", "//tensorflow/core/profiler:profiler_service_proto_cc", + "@com_google_absl//absl/strings", ], ) @@ -43,6 +44,8 @@ cc_library( deps = [ "//tensorflow/core:lib", "//tensorflow/core:protos_all_cc", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", "@jsoncpp_git//:jsoncpp", ], ) diff --git a/tensorflow/core/profiler/rpc/client/capture_profile.cc b/tensorflow/core/profiler/rpc/client/capture_profile.cc index d1e55f4f440..5b2f15694da 100644 --- a/tensorflow/core/profiler/rpc/client/capture_profile.cc +++ b/tensorflow/core/profiler/rpc/client/capture_profile.cc @@ -21,12 +21,12 @@ limitations under the License. #include "grpcpp/grpcpp.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" #include "tensorflow/core/distributed_runtime/rpc/grpc_util.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/core/status.h" #include "tensorflow/core/lib/io/path.h" -#include "tensorflow/core/lib/strings/numbers.h" -#include "tensorflow/core/lib/strings/str_util.h" #include "tensorflow/core/profiler/profiler_analysis.grpc.pb.h" #include "tensorflow/core/profiler/profiler_service.grpc.pb.h" #include "tensorflow/core/profiler/rpc/client/dump_tpu_profile.h" @@ -48,10 +48,10 @@ string GetCurrentTimeStampAsString() { Status ValidateHostPortPair(const string& host_port) { uint32 port; - std::vector parts = str_util::Split(host_port, ':'); + std::vector parts = absl::StrSplit(host_port, ':'); // Must be host:port, port must be a number, host must not contain a '/', // host also must not be empty. - if (parts.size() != 2 || !strings::safe_strtou32(parts[1], &port) || + if (parts.size() != 2 || !absl::SimpleAtoi(parts[1], &port) || parts[0].find("/") != string::npos || parts[0].empty()) { return errors::InvalidArgument("Could not interpret \"", host_port, "\" as a host-port pair."); @@ -180,7 +180,7 @@ Status MaybeCreateEmptyEventFile(const tensorflow::string& logdir) { std::vector children; TF_RETURN_IF_ERROR(Env::Default()->GetChildren(logdir, &children)); for (const string& child : children) { - if (str_util::EndsWith(child, kProfileEmptySuffix)) { + if (absl::EndsWith(child, kProfileEmptySuffix)) { return Status::OK(); } } @@ -201,8 +201,10 @@ Status StartTracing(const tensorflow::string& service_addr, constexpr char kProfilePluginDirectory[] = "plugins/profile/"; tensorflow::string repository_root = io::JoinPath(logdir, kProfilePluginDirectory); - std::vector hostnames = - tensorflow::str_util::Split(workers_list, ","); + std::vector hostnames; + if (!workers_list.empty()) { + hostnames = absl::StrSplit(workers_list, ','); + } TF_RETURN_IF_ERROR(MaybeCreateEmptyEventFile(logdir)); diff --git a/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc index 81b1ca83f8a..37c7af20fc3 100644 --- a/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc +++ b/tensorflow/core/profiler/rpc/client/dump_tpu_profile.cc @@ -19,11 +19,11 @@ limitations under the License. #include #include +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" #include "tensorflow/core/lib/core/errors.h" #include "tensorflow/core/lib/io/compression.h" #include "tensorflow/core/lib/io/path.h" -#include "tensorflow/core/lib/strings/str_util.h" -#include "tensorflow/core/lib/strings/strcat.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/platform/protobuf.h" // Windows.h #defines ERROR, but it is also used in @@ -43,8 +43,6 @@ namespace { using ::tensorflow::io::JoinPath; using ::tensorflow::protobuf::util::JsonOptions; using ::tensorflow::protobuf::util::MessageToJsonString; -using ::tensorflow::str_util::EndsWith; -using ::tensorflow::strings::StrCat; constexpr char kJsonOpProfileFileName[] = "op_profile.json"; constexpr char kJsonTraceFileName[] = "trace.json.gz"; @@ -70,12 +68,13 @@ Status WriteGzippedDataToFile(const string& filename, const string& data) { Status DumpTraceToLogDirectory(StringPiece run_dir, const string& host_prefix, const string& encoded_trace, std::ostream* os) { string proto_path = - JoinPath(run_dir, StrCat(host_prefix, kProtoTraceFileName)); + JoinPath(run_dir, absl::StrCat(host_prefix, kProtoTraceFileName)); TF_RETURN_IF_ERROR( WriteStringToFile(Env::Default(), proto_path, encoded_trace)); LOG(INFO) << "Dumped raw-proto trace data to " << proto_path; - string json_path = JoinPath(run_dir, StrCat(host_prefix, kJsonTraceFileName)); + string json_path = + JoinPath(run_dir, absl::StrCat(host_prefix, kJsonTraceFileName)); Trace trace; trace.ParseFromString(encoded_trace); if (os) { @@ -94,7 +93,8 @@ Status DumpOpProfileToLogDirectory(StringPiece run_dir, const string& host_prefix, const op_profile::Profile& profile, std::ostream* os) { - string path = JoinPath(run_dir, StrCat(host_prefix, kJsonOpProfileFileName)); + string path = + JoinPath(run_dir, absl::StrCat(host_prefix, kJsonOpProfileFileName)); string json; JsonOptions options; options.always_print_primitive_fields = true; @@ -116,10 +116,10 @@ Status DumpToolDataToLogDirectory(StringPiece run_dir, const ProfileToolData& tool, std::ostream* os) { // Don't save the intermediate results for combining the per host tool data. - if (EndsWith(tool.name(), kFlatProfilerFileName) || - EndsWith(tool.name(), kTfStatsHelperSuffix)) + if (absl::EndsWith(tool.name(), kFlatProfilerFileName) || + absl::EndsWith(tool.name(), kTfStatsHelperSuffix)) return Status::OK(); - string path = JoinPath(run_dir, StrCat(host_prefix, tool.name())); + string path = JoinPath(run_dir, absl::StrCat(host_prefix, tool.name())); TF_RETURN_IF_ERROR(WriteStringToFile(Env::Default(), path, tool.data())); if (os) { *os << "Dumped tool data for " << tool.name() << " to " << path @@ -135,7 +135,7 @@ Status WriteTensorboardTPUProfile(const string& logdir, const string& run, const ProfileResponse& response, std::ostream* os) { // Dumps profile data to /plugins/profile//. - string host_prefix = host.empty() ? "" : StrCat(host, "."); + string host_prefix = host.empty() ? "" : absl::StrCat(host, "."); string profile_run_dir = JoinPath(logdir, kProfilePluginDirectory, run); *os << "Creating directory: " << profile_run_dir; TF_RETURN_IF_ERROR(Env::Default()->RecursivelyCreateDir(profile_run_dir)); diff --git a/tensorflow/core/profiler/rpc/client/trace_events_to_json.cc b/tensorflow/core/profiler/rpc/client/trace_events_to_json.cc index e593f696e94..7f122f8b1d2 100644 --- a/tensorflow/core/profiler/rpc/client/trace_events_to_json.cc +++ b/tensorflow/core/profiler/rpc/client/trace_events_to_json.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/core/profiler/rpc/client/trace_events_to_json.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "include/json/json.h" -#include "tensorflow/core/lib/strings/strcat.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/protobuf/trace_events.pb.h" namespace tensorflow { @@ -26,13 +26,10 @@ namespace profiler { namespace client { namespace { -using ::tensorflow::strings::Appendf; -using ::tensorflow::strings::StrAppend; - constexpr double kPicosPerMicro = 1000000.0; inline void AppendEscapedName(string *json, const string &name) { - StrAppend(json, "\"name\":", Json::valueToQuotedString(name.c_str()).c_str()); + absl::StrAppend(json, "\"name\":", Json::valueToQuotedString(name.c_str())); } // Adds resource events for a single device. @@ -43,17 +40,18 @@ void AddResourceMetadata(uint32 device_id, uint32 resource_id = pair.first; const Resource &resource = *pair.second; if (!resource.name().empty()) { - Appendf(json, - R"({"ph":"M","pid":%u,"tid":%u,)" - R"("name":"thread_name","args":{)", - device_id, resource_id); + absl::StrAppendFormat(json, + R"({"ph":"M","pid":%u,"tid":%u,)" + R"("name":"thread_name","args":{)", + device_id, resource_id); AppendEscapedName(json, resource.name()); - Appendf(json, "}},"); + absl::StrAppend(json, "}},"); } - Appendf(json, - R"({"ph":"M","pid":%u,"tid":%u,)" - R"("name":"thread_sort_index","args":{"sort_index":%u}},)", - device_id, resource_id, resource_id); + absl::StrAppendFormat( + json, + R"({"ph":"M","pid":%u,"tid":%u,)" + R"("name":"thread_sort_index","args":{"sort_index":%u}},)", + device_id, resource_id, resource_id); } } @@ -63,17 +61,17 @@ void AddDeviceMetadata(const std::map &devices, uint32 device_id = pair.first; const Device &device = *pair.second; if (!device.name().empty()) { - Appendf(json, - R"({"ph":"M","pid":%u,"name":"process_name",)" - R"("args":{)", - device_id); + absl::StrAppendFormat(json, + R"({"ph":"M","pid":%u,"name":"process_name",)" + R"("args":{)", + device_id); AppendEscapedName(json, device.name()); - StrAppend(json, "}},"); + absl::StrAppend(json, "}},"); } - Appendf(json, - R"({"ph":"M","pid":%u,"name":"process_sort_index",)" - R"("args":{"sort_index":%u}},)", - device_id, device_id); + absl::StrAppendFormat(json, + R"({"ph":"M","pid":%u,"name":"process_sort_index",)" + R"("args":{"sort_index":%u}},)", + device_id, device_id); // Convert to a std::map so that devices are sorted by the device id. std::map sorted_resources; for (const auto &pair : device.resources()) { @@ -84,25 +82,24 @@ void AddDeviceMetadata(const std::map &devices, } inline void AddTraceEvent(const TraceEvent &event, string *json) { - Appendf(json, R"({"pid":%u,"tid":%u,"ts":%.5f,)", event.device_id(), - event.resource_id(), event.timestamp_ps() / kPicosPerMicro); + absl::StrAppendFormat(json, R"({"pid":%u,"tid":%u,"ts":%.5f,)", + event.device_id(), event.resource_id(), + event.timestamp_ps() / kPicosPerMicro); AppendEscapedName(json, event.name()); - StrAppend(json, ","); + absl::StrAppend(json, ","); if (event.duration_ps() > 0) { - Appendf(json, R"("ph":"X","dur":%.5f},)", - event.duration_ps() / kPicosPerMicro); + absl::StrAppendFormat(json, R"("ph":"X","dur":%.5f},)", + event.duration_ps() / kPicosPerMicro); } else { - StrAppend(json, R"("ph":"i","s":"t"},)"); + absl::StrAppend(json, R"("ph":"i","s":"t"},)"); } } } // namespace string TraceEventsToJson(const Trace &trace) { - string json; - Appendf( - &json, R"({"displayTimeUnit":"ns","metadata":{"highres-ticks":true},)"); - Appendf(&json, R"("traceEvents":[)"); + string json = R"({"displayTimeUnit":"ns","metadata":{"highres-ticks":true},)" + R"("traceEvents":[)"; // Convert to a std::map so that devices are sorted by the device id. std::map sorted_devices; for (const auto &pair : trace.devices()) { @@ -113,7 +110,7 @@ string TraceEventsToJson(const Trace &trace) { AddTraceEvent(event, &json); } // Add one fake event to avoid dealing with no-trailing-comma rule. - Appendf(&json, R"({}]})"); + absl::StrAppend(&json, "{}]}"); return json; } diff --git a/tensorflow/core/profiler/rpc/profiler_server.cc b/tensorflow/core/profiler/rpc/profiler_server.cc index 9e4803a6978..d1027308b44 100644 --- a/tensorflow/core/profiler/rpc/profiler_server.cc +++ b/tensorflow/core/profiler/rpc/profiler_server.cc @@ -19,7 +19,7 @@ limitations under the License. #include #include "grpcpp/grpcpp.h" -#include "tensorflow/core/lib/strings/strcat.h" +#include "absl/strings/str_cat.h" #include "tensorflow/core/platform/env.h" #include "tensorflow/core/profiler/lib/profiler_session.h" #include "tensorflow/core/profiler/profiler_service.grpc.pb.h" @@ -31,7 +31,7 @@ namespace tensorflow { std::unique_ptr StartProfilerServer(int32 port) { Env* env = Env::Default(); return WrapUnique(env->StartThread({}, "profiler server", [port]() { - string server_address = strings::StrCat("0.0.0.0:", port); + string server_address = absl::StrCat("0.0.0.0:", port); std::unique_ptr service = CreateProfilerService(); ::grpc::ServerBuilder builder; diff --git a/tensorflow/core/profiler/tfprof_options.cc b/tensorflow/core/profiler/tfprof_options.cc index e940a9911ef..a482df45e58 100644 --- a/tensorflow/core/profiler/tfprof_options.cc +++ b/tensorflow/core/profiler/tfprof_options.cc @@ -15,10 +15,9 @@ limitations under the License. #include "tensorflow/core/profiler/tfprof_options.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" #include "tensorflow/core/lib/core/errors.h" -#include "tensorflow/core/lib/strings/str_util.h" -#include "tensorflow/core/lib/strings/strcat.h" -#include "tensorflow/core/lib/strings/stringprintf.h" #include "tensorflow/core/profiler/tfprof_options.pb.h" namespace tensorflow { @@ -28,7 +27,7 @@ string KeyValueToStr(const std::map& kv_map) { std::vector kv_vec; kv_vec.reserve(kv_map.size()); for (const auto& pair : kv_map) { - kv_vec.push_back(strings::StrCat(pair.first, "=", pair.second)); + kv_vec.push_back(absl::StrCat(pair.first, "=", pair.second)); } return absl::StrJoin(kv_vec, ","); } @@ -50,9 +49,8 @@ tensorflow::Status ParseOutput(const string& output_opt, string* output_type, if (output_types.find(output_opt) == output_types.end()) { return tensorflow::Status( tensorflow::error::INVALID_ARGUMENT, - strings::Printf("E.g. Unknown output type: %s, Valid types: %s\n", - output_opt.c_str(), - absl::StrJoin(output_types, ",").c_str())); + absl::StrFormat("E.g. Unknown output type: %s, Valid types: %s\n", + output_opt, absl::StrJoin(output_types, ","))); } *output_type = output_opt; } else { @@ -60,12 +58,11 @@ tensorflow::Status ParseOutput(const string& output_opt, string* output_type, if (output_types.find(*output_type) == output_types.end()) { return tensorflow::Status( tensorflow::error::INVALID_ARGUMENT, - strings::Printf("E.g. Unknown output type: %s, Valid types: %s\n", - output_type->c_str(), - absl::StrJoin(output_types, ",").c_str())); + absl::StrFormat("E.g. Unknown output type: %s, Valid types: %s\n", + *output_type, absl::StrJoin(output_types, ","))); } - kv_split = str_util::Split(output_opt.substr(opt_split + 1), ",", - str_util::SkipEmpty()); + kv_split = absl::StrSplit(output_opt.substr(opt_split + 1), ",", + absl::SkipEmpty()); } std::set valid_options; @@ -95,7 +92,7 @@ tensorflow::Status ParseOutput(const string& output_opt, string* output_type, for (const string& kv_str : kv_split) { const std::vector kv = - str_util::Split(kv_str, "=", str_util::SkipEmpty()); + absl::StrSplit(kv_str, "=", absl::SkipEmpty()); if (kv.size() < 2) { return tensorflow::Status( tensorflow::error::INVALID_ARGUMENT, @@ -104,8 +101,8 @@ tensorflow::Status ParseOutput(const string& output_opt, string* output_type, if (valid_options.find(kv[0]) == valid_options.end()) { return tensorflow::Status( tensorflow::error::INVALID_ARGUMENT, - strings::Printf("Unrecognized options %s for output_type: %s\n", - kv[0].c_str(), output_type->c_str())); + absl::StrFormat("Unrecognized options %s for output_type: %s\n", + kv[0], *output_type)); } const std::vector kv_without_key(kv.begin() + 1, kv.end()); (*output_options)[kv[0]] = absl::StrJoin(kv_without_key, "="); @@ -115,10 +112,9 @@ tensorflow::Status ParseOutput(const string& output_opt, string* output_type, if (output_options->find(opt) == output_options->end()) { return tensorflow::Status( tensorflow::error::INVALID_ARGUMENT, - strings::Printf("Missing required output_options for %s\n" + absl::StrFormat("Missing required output_options for %s\n" "E.g. -output %s:%s=...\n", - output_type->c_str(), output_type->c_str(), - opt.c_str())); + *output_type, *output_type, opt)); } } return tensorflow::Status::OK(); @@ -130,8 +126,8 @@ tensorflow::Status Options::FromProtoStr(const string& opts_proto_str, if (!opts_pb.ParseFromString(opts_proto_str)) { return tensorflow::Status( tensorflow::error::INTERNAL, - strings::StrCat("Failed to parse option string from Python API: ", - opts_proto_str)); + absl::StrCat("Failed to parse option string from Python API: ", + opts_proto_str)); } string output_type; @@ -141,11 +137,12 @@ tensorflow::Status Options::FromProtoStr(const string& opts_proto_str, if (!s.ok()) return s; if (!opts_pb.dump_to_file().empty()) { - fprintf(stderr, - "-dump_to_file option is deprecated. " - "Please use -output file:outfile=\n"); - fprintf(stderr, "-output %s is overwritten with -output file:outfile=%s\n", - opts_pb.output().c_str(), opts_pb.dump_to_file().c_str()); + absl::FPrintF(stderr, + "-dump_to_file option is deprecated. " + "Please use -output file:outfile=\n"); + absl::FPrintF(stderr, + "-output %s is overwritten with -output file:outfile=%s\n", + opts_pb.output(), opts_pb.dump_to_file()); output_type = kOutput[2]; output_options.clear(); output_options[kFileOpts[0]] = opts_pb.dump_to_file(); @@ -173,20 +170,21 @@ tensorflow::Status Options::FromProtoStr(const string& opts_proto_str, return tensorflow::Status::OK(); } -string Options::ToString() const { - const string s = strings::Printf( +std::string Options::ToString() const { + // clang-format off + const std::string s = absl::StrFormat( + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" + "%-28s%d\n" "%-28s%d\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" - "%-28s%lld\n" "%-28s%s\n" "%-28s%s\n" "%-28s%s\n" @@ -196,20 +194,28 @@ string Options::ToString() const { "%-28s%s\n" "%-28s%s\n" "%-28s%s:%s\n", - kOptions[0], max_depth, kOptions[1], min_bytes, kOptions[2], - min_peak_bytes, kOptions[3], min_residual_bytes, kOptions[4], - min_output_bytes, kOptions[5], min_micros, kOptions[6], - min_accelerator_micros, kOptions[7], min_cpu_micros, kOptions[8], - min_params, kOptions[9], min_float_ops, kOptions[10], min_occurrence, - kOptions[11], step, kOptions[12], order_by.c_str(), kOptions[13], - absl::StrJoin(account_type_regexes, ",").c_str(), kOptions[14], - absl::StrJoin(start_name_regexes, ",").c_str(), kOptions[15], - absl::StrJoin(trim_name_regexes, ",").c_str(), kOptions[16], - absl::StrJoin(show_name_regexes, ",").c_str(), kOptions[17], - absl::StrJoin(hide_name_regexes, ",").c_str(), kOptions[18], - (account_displayed_op_only ? "true" : "false"), kOptions[19], - absl::StrJoin(select, ",").c_str(), kOptions[20], output_type.c_str(), - KeyValueToStr(output_options).c_str()); + kOptions[0], max_depth, + kOptions[1], min_bytes, + kOptions[2], min_peak_bytes, + kOptions[3], min_residual_bytes, + kOptions[4], min_output_bytes, + kOptions[5], min_micros, + kOptions[6], min_accelerator_micros, + kOptions[7], min_cpu_micros, + kOptions[8], min_params, + kOptions[9], min_float_ops, + kOptions[10], min_occurrence, + kOptions[11], step, + kOptions[12], order_by, + kOptions[13], absl::StrJoin(account_type_regexes, ","), + kOptions[14], absl::StrJoin(start_name_regexes, ","), + kOptions[15], absl::StrJoin(trim_name_regexes, ","), + kOptions[16], absl::StrJoin(show_name_regexes, ","), + kOptions[17], absl::StrJoin(hide_name_regexes, ","), + kOptions[18], (account_displayed_op_only ? "true" : "false"), + kOptions[19], absl::StrJoin(select, ","), + kOptions[20], output_type, KeyValueToStr(output_options)); + // clang-format on return s; } diff --git a/tensorflow/core/profiler/utils/BUILD b/tensorflow/core/profiler/utils/BUILD index 73fc56dfd11..8169d56ba6f 100644 --- a/tensorflow/core/profiler/utils/BUILD +++ b/tensorflow/core/profiler/utils/BUILD @@ -117,6 +117,18 @@ cc_library( ], ) +cc_library( + name = "xplane_schema", + srcs = ["xplane_schema.cc"], + hdrs = ["xplane_schema.h"], + visibility = [":friends"], + deps = [ + "//tensorflow/core:lib", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) + cc_library( name = "xplane_visitor", hdrs = ["xplane_visitor.h"], @@ -130,3 +142,32 @@ cc_library( "@com_google_absl//absl/strings", ], ) + +cc_library( + name = "metadata_matcher", + srcs = ["metadata_matcher.cc"], + hdrs = ["metadata_matcher.h"], + deps = [ + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core/profiler/protobuf:xplane_proto_cc", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:optional", + "@com_google_absl//absl/types:span", + ], +) + +tf_cc_test( + name = "metadata_matcher_test", + size = "small", + srcs = ["metadata_matcher_test.cc"], + deps = [ + ":metadata_matcher", + ":xplane_schema", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/types:span", + ], +) diff --git a/tensorflow/core/profiler/utils/metadata_matcher.cc b/tensorflow/core/profiler/utils/metadata_matcher.cc new file mode 100644 index 00000000000..7abdd77941a --- /dev/null +++ b/tensorflow/core/profiler/utils/metadata_matcher.cc @@ -0,0 +1,145 @@ +/* Copyright 2019 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/profiler/utils/metadata_matcher.h" + +#include "absl/strings/string_view.h" + +namespace tensorflow { +namespace profiler { +namespace { + +using ::tensorflow::profiler::XEvent; +using ::tensorflow::profiler::XPlane; +using ::tensorflow::profiler::XStat; + +absl::flat_hash_map CreateEventMetadataMap( + const XPlane& xplane, + const std::vector, + /*first_event_type*/ int>>& + event_type_metadata_maps) { + absl::flat_hash_map id_to_event_type_map; + for (const auto& id_and_event_metadata : xplane.event_metadata()) { + int64 id = id_and_event_metadata.first; + absl::string_view event_name = id_and_event_metadata.second.name(); + for (const auto& event_type_metadata_map_and_first_event_type : + event_type_metadata_maps) { + auto event_type_metadata_map = + event_type_metadata_map_and_first_event_type.first; + int first_event_type = + event_type_metadata_map_and_first_event_type.second; + for (int i = 0; i < event_type_metadata_map.size(); ++i) { + if (event_type_metadata_map[i] == event_name) { + id_to_event_type_map[id] = first_event_type + i; + break; + } + } + } + } + return id_to_event_type_map; +} + +absl::flat_hash_map CreateStatMetadataMap( + const XPlane& xplane, + const absl::Span stat_type_str_map) { + absl::flat_hash_map id_to_stat_type_map; + for (const auto& id_and_stat_metadata : xplane.stat_metadata()) { + int64 id = id_and_stat_metadata.first; + absl::string_view stat_name = id_and_stat_metadata.second.name(); + for (int stat_type = 0; stat_type < stat_type_str_map.size(); ++stat_type) { + if (stat_type_str_map[stat_type] == stat_name) { + id_to_stat_type_map[id] = stat_type; + break; + } + } + } + return id_to_stat_type_map; +} + +} // namespace + +MetadataMatcher::MetadataMatcher( + const XPlane& xplane, + const std::vector, + /*first_event_type*/ int>>& + event_type_metadata_maps, + const absl::Span stat_type_str_map) + : id_to_event_type_map_( + CreateEventMetadataMap(xplane, event_type_metadata_maps)), + id_to_stat_type_map_(CreateStatMetadataMap(xplane, stat_type_str_map)), + event_type_to_id_map_(gtl::ReverseMap( + id_to_event_type_map_)), + stat_type_to_id_map_(gtl::ReverseMap( + id_to_stat_type_map_)) {} + +const XStat* MetadataMatcher::GetStat(const XEvent& event, + int stat_type) const { + for (const auto& stat : event.stats()) { + if (GetStatType(stat) == stat_type) { + return &stat; + } + } + return nullptr; +} + +absl::optional> +MetadataMatcher::GetStats(const XEvent& event, int first_stat_type, + int second_stat_type) const { + const XStat* first_stat = nullptr; + const XStat* second_stat = nullptr; + for (const auto& stat : event.stats()) { + if (GetStatType(stat) == first_stat_type) { + first_stat = &stat; + } else if (GetStatType(stat) == second_stat_type) { + second_stat = &stat; + } + } + if (first_stat && second_stat) { + return std::make_tuple(first_stat, second_stat); + } + return absl::nullopt; +} + +absl::optional> +MetadataMatcher::GetStats(const XEvent& event, int first_stat_type, + int second_stat_type, int third_stat_type) const { + const XStat* first_stat = nullptr; + const XStat* second_stat = nullptr; + const XStat* third_stat = nullptr; + for (const auto& stat : event.stats()) { + if (GetStatType(stat) == first_stat_type) { + first_stat = &stat; + } else if (GetStatType(stat) == second_stat_type) { + second_stat = &stat; + } else if (GetStatType(stat) == third_stat_type) { + third_stat = &stat; + } + } + if (first_stat && second_stat && third_stat) { + return std::make_tuple(first_stat, second_stat, third_stat); + } + return absl::nullopt; +} + +absl::optional MetadataMatcher::GetIntStatValue(const XEvent& event, + int stat_type) const { + if (const XStat* stat = GetStat(event, stat_type)) { + return stat->int64_value(); + } + return absl::nullopt; +} + +} // namespace profiler +} // namespace tensorflow diff --git a/tensorflow/core/profiler/utils/metadata_matcher.h b/tensorflow/core/profiler/utils/metadata_matcher.h new file mode 100644 index 00000000000..beaba5ecd70 --- /dev/null +++ b/tensorflow/core/profiler/utils/metadata_matcher.h @@ -0,0 +1,108 @@ +/* Copyright 2019 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_PROFILER_UTILS_METADATA_MATCHER_H_ +#define TENSORFLOW_CORE_PROFILER_UTILS_METADATA_MATCHER_H_ + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" +#include "tensorflow/core/lib/gtl/map_util.h" +#include "tensorflow/core/platform/types.h" +#include "tensorflow/core/profiler/protobuf/xplane.pb.h" + +namespace tensorflow { +namespace profiler { + +// Builds mapping between metadata ids and interesting event and stat types. +// Event and stat types are represented in integer ids. Multiple spans of event +// types can be passed with offset values (i.e., first_event_type) to be +// used to calculate integer ids for event types. Spans and offset values are +// expected to result in a unique integer id for each event type. +class MetadataMatcher { + public: + explicit MetadataMatcher( + const XPlane& xplane, + const std::vector, + /*first_event_type*/ int>>& + event_type_metadata_maps, + const absl::Span stat_type_str_map); + + // Returns EventType if input is one of interesting event types. + // Otherwise, it returns kUnknownEventType. + int GetEventType(const XEvent& xevent) const { + return gtl::FindWithDefault(id_to_event_type_map_, xevent.metadata_id(), + /*kUnknownEventType*/ 0); + } + + // Overload of GetEventType function. + // Returns EventType if input is one of interesting event types. + // Otherwise, it returns kUnknownEventType. + int GetEventType(int64 metadata_id) const { + return gtl::FindWithDefault(id_to_event_type_map_, metadata_id, + /*kUnknownEventType*/ 0); + } + + // Returns metadata id if xplane has the input event type. + absl::optional GetEventMetadataId(int event_type) const { + if (const int64* id = gtl::FindOrNull(event_type_to_id_map_, event_type)) { + return *id; + } + return absl::nullopt; + } + + // Returns StatType if input is one of interesting stat types. + // Otherwise, it returns kUnknownStatType. + int GetStatType(const XStat& xstat) const { + return gtl::FindWithDefault(id_to_stat_type_map_, xstat.metadata_id(), + /*kUnknownStatType*/ 0); + } + + // Returns metadata id if xplane has the input stat type. + absl::optional GetStatMetadataId(int stat_type) const { + if (const int64* id = gtl::FindOrNull(stat_type_to_id_map_, stat_type)) { + return *id; + } + return absl::nullopt; + } + + const XStat* GetStat(const XEvent& event, int stat_type) const; + + absl::optional> GetStats( + const XEvent& event, int first_stat_type, int second_stat_type) const; + + absl::optional> GetStats( + const XEvent& event, int first_stat_type, int second_stat_type, + int third_stat_type) const; + + absl::optional GetIntStatValue(const XEvent& event, + int stat_type) const; + + private: + // Maps from metada ids to interesting event and stat types. + // Uninteresting event and stat types are not cached in these maps and + // considered to be kUnknown*. + const absl::flat_hash_map id_to_event_type_map_; + const absl::flat_hash_map id_to_stat_type_map_; + // Reverse of the above. + const absl::flat_hash_map event_type_to_id_map_; + const absl::flat_hash_map stat_type_to_id_map_; +}; + +} // namespace profiler +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_PROFILER_UTILS_METADATA_MATCHER_H_ diff --git a/tensorflow/core/profiler/utils/metadata_matcher_test.cc b/tensorflow/core/profiler/utils/metadata_matcher_test.cc new file mode 100644 index 00000000000..d430b44fc64 --- /dev/null +++ b/tensorflow/core/profiler/utils/metadata_matcher_test.cc @@ -0,0 +1,69 @@ +/* Copyright 2019 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/profiler/utils/metadata_matcher.h" + +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "tensorflow/core/platform/test.h" +#include "tensorflow/core/profiler/utils/xplane_schema.h" + +namespace tensorflow { +namespace profiler { +namespace { + +using ::tensorflow::profiler::XEventMetadata; +using ::tensorflow::profiler::XPlane; +using ::tensorflow::profiler::XStatMetadata; + +TEST(MetadataMatcherTest, GetHostEventTypeTest) { + for (int event_type = HostEventType::kFirstHostEventType; + event_type <= HostEventType::kLastHostEventType; ++event_type) { + XPlane xplane; + XEventMetadata& metadata = (*xplane.mutable_event_metadata())[0]; + metadata.set_id(0); + metadata.set_name(std::string( + GetHostEventTypeStr(static_cast(event_type)))); + MetadataMatcher metadata_matcher( + xplane, + {{GetHostEventTypeStrMap(), HostEventType::kFirstHostEventType}}, + GetStatTypeStrMap()); + XEvent event; + event.set_metadata_id(0); + EXPECT_EQ(metadata_matcher.GetEventType(event), event_type); + } +} + +TEST(MetadataMatcherTest, GetStatTypeTest) { + for (int stat_type = StatType::kFirstStatType; + stat_type <= StatType::kLastStatType; ++stat_type) { + XPlane xplane; + XStatMetadata& metadata = (*xplane.mutable_stat_metadata())[0]; + metadata.set_id(0); + metadata.set_name( + std::string(GetStatTypeStr(static_cast(stat_type)))); + MetadataMatcher metadata_matcher( + xplane, + {{GetHostEventTypeStrMap(), HostEventType::kFirstHostEventType}}, + GetStatTypeStrMap()); + XStat stat; + stat.set_metadata_id(0); + EXPECT_EQ(metadata_matcher.GetStatType(stat), stat_type); + } +} + +} // namespace +} // namespace profiler +} // namespace tensorflow diff --git a/tensorflow/core/profiler/utils/tf_op_utils.cc b/tensorflow/core/profiler/utils/tf_op_utils.cc index 47412e1a3f1..8cf39b43b53 100644 --- a/tensorflow/core/profiler/utils/tf_op_utils.cc +++ b/tensorflow/core/profiler/utils/tf_op_utils.cc @@ -19,10 +19,22 @@ limitations under the License. #include "absl/strings/ascii.h" #include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/strings/strip.h" #include "tensorflow/core/platform/regexp.h" namespace tensorflow { namespace profiler { +namespace { + +constexpr absl::string_view kIterator = "Iterator"; +constexpr absl::string_view kSeparator = "::"; + +} // namespace + +const absl::string_view kUnknownOp = ""; // op types are non-empty strings +const absl::string_view kDatasetOp = "Dataset"; TfOp ParseTfOpFullname(absl::string_view tf_op_fullname) { // TF Op names have the format "name:type" where: diff --git a/tensorflow/core/profiler/utils/tf_op_utils.h b/tensorflow/core/profiler/utils/tf_op_utils.h index 708284c0a3c..588dcd4a4a3 100644 --- a/tensorflow/core/profiler/utils/tf_op_utils.h +++ b/tensorflow/core/profiler/utils/tf_op_utils.h @@ -16,22 +16,14 @@ limitations under the License. #ifndef TENSORFLOW_CORE_PROFILER_UTILS_TF_OP_UTILS_H_ #define TENSORFLOW_CORE_PROFILER_UTILS_TF_OP_UTILS_H_ -#include - -#include "absl/strings/match.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/str_split.h" #include "absl/strings/string_view.h" -#include "absl/strings/strip.h" namespace tensorflow { namespace profiler { // Special op types. -constexpr absl::string_view kUnknownOp = ""; // op types are non-empty strings -constexpr absl::string_view kDatasetOp = "Dataset"; -constexpr absl::string_view kIterator = "Iterator"; -constexpr absl::string_view kSeparator = "::"; +ABSL_CONST_INIT extern const absl::string_view kUnknownOp; +ABSL_CONST_INIT extern const absl::string_view kDatasetOp; // Breaks a TensorFlow op fullname into name and type. struct TfOp { @@ -54,6 +46,7 @@ inline bool IsUnknownOp(absl::string_view tf_op_type) { inline bool IsDatasetOp(absl::string_view tf_op_type) { return tf_op_type == kDatasetOp; } + } // namespace profiler } // namespace tensorflow diff --git a/tensorflow/core/profiler/utils/xplane_schema.cc b/tensorflow/core/profiler/utils/xplane_schema.cc new file mode 100644 index 00000000000..095ef2e1f0e --- /dev/null +++ b/tensorflow/core/profiler/utils/xplane_schema.cc @@ -0,0 +1,94 @@ +/* Copyright 2019 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/profiler/utils/xplane_schema.h" + +#include "absl/strings/string_view.h" + +namespace tensorflow { +namespace profiler { + +const absl::string_view kHostThreads = "Host Threads"; + +constexpr int kNumHostEventTypes = + HostEventType::kLastHostEventType - HostEventType::kFirstHostEventType + 1; + +constexpr int kNumStatTypes = + StatType::kLastStatType - StatType::kFirstStatType + 1; + +static const absl::string_view kHostEventTypeMetadataMap[] = { + "UnknownHostEventType", + "TraceContext", + "SessionRun", + "FunctionRun", + "RunGraph", + "ExecutorState::Process", + "ExecutorDoneCallback", + // tf data captured function events. + "InstantiatedCapturedFunction::Run", + "InstantiatedCapturedFunction::RunWithBorrowedArgs", + "InstantiatedCapturedFunction::RunInstantiated", + "InstantiatedCapturedFunction::RunAsync", + // Functional ops. + "CallOp", + "ParallelForOp", + "ForeverOp", + "NumericalGradientOp-EvalRight", + "NumericalGradientOp-EvalLeft", + "SymbolicGradientOp", + "RemoteCallOp", + "IfOp", + "CaseOp", + "WhileOp-EvalCond", + "WhileOp-StartBody", + "ForOp", + "PartitionedCallOp", +}; + +static_assert(sizeof(kHostEventTypeMetadataMap) / sizeof(absl::string_view) == + kNumHostEventTypes, + "Mismatch between enum and string map."); + +static const absl::string_view kStatTypeStrMap[] = { + "UnknownStatType", "id", + "parent_step_id", "function_step_id", + "device_ordinal", "chip_ordinal", + "node_ordinal", "model_id", + "queue_addr", "request_id", + "run_id", "correlation_id", + "graph_type", "step_num", + "iter_num", "index_on_host", + "bytes_reserved", "bytes_allocated", + "bytes_available", "fragmentation", + "kernel_details", "group_id", + "step_name", "level 0", + "tf_op", "hlo_op", + "hlo_module", +}; + +static_assert(sizeof(kStatTypeStrMap) / sizeof(absl::string_view) == + kNumStatTypes, + "Mismatch between enum and string map."); + +absl::Span GetHostEventTypeStrMap() { + return absl::MakeConstSpan(kHostEventTypeMetadataMap, kNumHostEventTypes); +} + +absl::Span GetStatTypeStrMap() { + return absl::MakeConstSpan(kStatTypeStrMap, kNumStatTypes); +} + +} // namespace profiler +} // namespace tensorflow diff --git a/tensorflow/core/profiler/utils/xplane_schema.h b/tensorflow/core/profiler/utils/xplane_schema.h new file mode 100644 index 00000000000..0df9e2b21eb --- /dev/null +++ b/tensorflow/core/profiler/utils/xplane_schema.h @@ -0,0 +1,120 @@ +/* Copyright 2019 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_PROFILER_UTILS_XPLANE_SCHEMA_H_ +#define TENSORFLOW_CORE_PROFILER_UTILS_XPLANE_SCHEMA_H_ + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" +#include "tensorflow/core/platform/logging.h" + +namespace tensorflow { +namespace profiler { + +// Name of XPlane that contains TraceMe events. +ABSL_CONST_INIT extern const absl::string_view kHostThreads; + +// Interesting event types (i.e., TraceMe names). +enum HostEventType { + kFirstHostEventType = 0, + kUnknownHostEventType = kFirstHostEventType, + kTraceContext, + kSessionRun, + kFunctionRun, + kRunGraph, + kExecutorStateProcess, + kExecutorDoneCallback, + // tf.data captured function events. + kTfDataCapturedFunctionRun, + kTfDataCapturedFunctionRunWithBorrowedArgs, + kTfDataCapturedFunctionRunInstantiated, + kTfDataCapturedFunctionRunAsync, + // Functional ops. + kCallOp, + kParallelForOp, + kForeverOp, + kNumericalGradientOpEvalRight, + kNumericalGradientOpEvalLeft, + kSymbolicGradientOp, + kRemoteCallOp, + kIfOp, + kCaseOp, + kWhileOpEvalCond, + kWhileOpStartBody, + kForOp, + kPartitionedCallOp, + kLastHostEventType = kPartitionedCallOp, +}; + +enum StatType { + kFirstStatType = 0, + kUnknownStatType = kFirstStatType, + // TraceMe arguments. + kStepId, + kParentStepId, + kFunctionStepId, + kDeviceOrdinal, + kChipOrdinal, + kNodeOrdinal, + kModelId, + kQueueAddr, + kRequestId, + kRunId, + kCorrelationId, + kGraphType, + kStepNum, + kIterNum, + kIndexOnHost, + kBytesReserved, + kBytesAllocated, + kBytesAvailable, + kFragmentation, + kKernelDetails, + // Stats added when processing traces. + kGroupId, + kStepName, + kLevel0, + kTfOp, + kHloOp, + kHloModule, + kLastStatType = kHloModule, +}; + +absl::Span GetHostEventTypeStrMap(); + +inline absl::string_view GetHostEventTypeStr(HostEventType event_type) { + return GetHostEventTypeStrMap()[event_type]; +} + +inline bool IsHostEventType(HostEventType event_type, + absl::string_view event_name) { + return GetHostEventTypeStrMap()[event_type] == event_name; +} + +absl::Span GetStatTypeStrMap(); + +inline absl::string_view GetStatTypeStr(StatType stat_type) { + return GetStatTypeStrMap()[stat_type]; +} + +inline bool IsStatType(StatType stat_type, absl::string_view stat_name) { + return GetStatTypeStr(stat_type) == stat_name; +} + +} // namespace profiler +} // namespace tensorflow + +#endif // TENSORFLOW_CORE_PROFILER_UTILS_XPLANE_SCHEMA_H_ diff --git a/tensorflow/core/util/BUILD b/tensorflow/core/util/BUILD new file mode 100644 index 00000000000..1c2f20e3622 --- /dev/null +++ b/tensorflow/core/util/BUILD @@ -0,0 +1,564 @@ +load( + "//tensorflow/core/platform:build_config.bzl", + "tf_kernel_tests_linkstatic", + "tf_proto_library", +) +load( + "//tensorflow:tensorflow.bzl", + "tf_cc_test", + "tf_cc_tests", + "tf_copts", + "tf_cuda_library", + "tf_cuda_only_cc_test", +) +load("//tensorflow:tensorflow.bzl", "tf_version_info_genrule") +load( + "//third_party/mkl:build_defs.bzl", + "mkl_deps", +) + +package( + default_visibility = [ + "//tensorflow/core:__subpackages__", + ], + licenses = ["notice"], # Apache 2.0 +) + +# Export all files for which we do not yet provide a dedicated build rule. +# This avoids breading all the rules in tensorflow/core/BUILD. +exports_files( + srcs = [ + "activation_mode.h", + "batch_util.h", + "bcast.h", + "command_line_flags.h", + "debug_events_writer.h", + "device_name_utils.h", + "dump_graph.h", + "einsum_op_util.h", + "env_var.h", + "equal_graph_def.h", + "events_writer.h", + "example_proto_fast_parsing.h", + "example_proto_helper.h", + "gpu_kernel_helper.h", + "gpu_launch_config.h", + "guarded_philox_random.h", + "matmul_autotune.h", + "matmul_bcast.h", + "memmapped_file_system.h", + "memmapped_file_system_writer.h", + "mirror_pad_mode.h", + "mkl_util.h", + "padding.h", + "permutation_input_iterator.h", + "permutation_output_iterator.h", + "port.h", + "reporter.h", + "saved_tensor_slice_util.h", + "stat_summarizer.h", + "stat_summarizer_options.h", + "stats_calculator.h", + "strided_slice_op.h", + "tensor_format.h", + "tensor_slice_reader.h", + "tensor_slice_reader_cache.h", + "tensor_slice_set.h", + "tensor_slice_writer.h", + "use_cudnn.h", + "util.h", + "work_sharder.h", + "xla_config_registry.h", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +# List of exported proto source files. +exports_files( + srcs = [ + "event.proto", + "example_proto_fast_parsing_test.proto", + "memmapped_file_system.proto", + "saved_tensor_slice.proto", + "test_log.proto", + ], + visibility = [ + "//tensorflow:internal", + "//tensorflow/core:__pkg__", + ], +) + +# The following filegroups are needed since globbing across packages boundaries +# will just fail silently (see 3rd caveat at +# https://docs.bazel.build/versions/master/be/functions.html#glob). +# Files needed for core:framework_internal_impl. +filegroup( + name = "mobile_srcs_no_runtime", + srcs = glob( + [ + "*.cc", + "*.h", + ], + exclude = [ + "*_test.*", + "debug_events_writer.*", + "stats_calculator.*", + "events_writer.*", + "reporter.*", + ], + ), +) + +filegroup( + name = "lib_internal_impl_srcs", + srcs = ["env_var.cc"], +) + +filegroup( + name = "framework_internal_private_hdrs", + srcs = glob( + [ + "*.h", + ], + exclude = [ + "reporter.h", + "port.h", + "memmapped_file_system.*", + "memmapped_file_system_writer.*", + "session_message.*", + ], + ), +) + +filegroup( + name = "framework_internal_public_hdrs", + srcs = [ + "command_line_flags.h", + "equal_graph_def.h", + "presized_cuckoo_map.h", + "tensor_slice_set.h", + "tensor_slice_util.h", + "xla_config_registry.h", + ], +) + +filegroup( + name = "framework_internal_impl_srcs", + srcs = glob( + [ + "*.cc", + ], + exclude = [ + "*test*", + "env_var.cc", + "memmapped_file_system.*", + "memmapped_file_system_writer.*", + "port.cc", + "reporter.cc", + "stats_calculator.cc", + "version_info.cc", + ], + ), +) + +filegroup( + name = "lib_internal_public_hdrs", + srcs = [ + "env_var.h", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +filegroup( + name = "lib_hdrs", + srcs = [ + "gpu_cuda_alias.h", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +filegroup( + name = "memmapped_file_system_hdrs", + srcs = [ + "memmapped_file_system.h", + "memmapped_file_system_writer.h", + ], +) + +filegroup( + name = "memmapped_file_system_srcs", + srcs = [ + "memmapped_file_system.cc", + "memmapped_file_system_writer.cc", + ], +) + +filegroup( + name = "port_hdrs", + srcs = [ + "port.h", + ], + visibility = [ + "//tensorflow/core:__pkg__", + "//tensorflow/python:__pkg__", + ], +) + +filegroup( + name = "test_hdrs", + srcs = [ + "reporter.h", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +filegroup( + name = "mkl_util_hdrs", + srcs = [ + "mkl_util.h", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +filegroup( + name = "mkl_util_test_srcs", + srcs = [ + "mkl_util_test.cc", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +filegroup( + name = "android_test_hdrs", + srcs = [ + "reporter.h", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +filegroup( + name = "android_test_srcs", + srcs = [ + "reporter.cc", + ":android_test_hdrs", + ], + visibility = ["//tensorflow/core:__pkg__"], +) + +filegroup( + name = "framework_srcs", + srcs = [ + "activation_mode.h", + "batch_util.h", + "bcast.h", + "debug_events_writer.h", + "device_name_utils.h", + "dump_graph.h", + "einsum_op_util.h", + "events_writer.h", + "example_proto_fast_parsing.h", + "example_proto_helper.h", + "gpu_kernel_helper.h", + "guarded_philox_random.h", + "matmul_autotune.h", + "matmul_bcast.h", + "mirror_pad_mode.h", + "padding.h", + "port.h", + "ptr_util.h", + "reffed_status_callback.h", + "saved_tensor_slice_util.h", + "stat_summarizer.h", + "stat_summarizer_options.h", + "stream_executor_util.h", + "strided_slice_op.h", + "tensor_format.h", + "tensor_ops_util.h", + "tensor_slice_reader.h", + "tensor_slice_reader_cache.h", + "tensor_slice_writer.h", + "use_cudnn.h", + "util.h", + "work_sharder.h", + ], +) + +# Version info generation needs to be generated in the same package where it +# is written. +tf_version_info_genrule( + name = "version_info_gen", + out = "version_info.cc", +) + +cc_library( + name = "stats_calculator_portable", + srcs = [ + "stat_summarizer_options.h", + "stats_calculator.cc", + ], + hdrs = [ + "stats_calculator.h", + ], + copts = tf_copts(), + visibility = [ + "//tensorflow:internal", + ], +) + +tf_cc_test( + name = "stats_calculator_test", + srcs = ["stats_calculator_test.cc"], + deps = [ + ":stats_calculator_portable", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ], +) + +cc_library( + name = "reporter", + srcs = ["reporter.cc"], + hdrs = ["reporter.h"], + visibility = ["//tensorflow/core:__subpackages__"], + deps = [ + ":test_log_proto_impl_cc", + "//tensorflow/core/platform:env", + "//tensorflow/core/platform:errors", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:mutex", + "//tensorflow/core/platform:str_util", + "//tensorflow/core/platform:types", + ], +) + +cc_library( + name = "port", + srcs = ["port.cc"], + hdrs = ["port.h"], + copts = tf_copts(), + visibility = [ + "//tensorflow/core:__pkg__", + "//tensorflow/python:__pkg__", + ], + alwayslink = 1, +) + +tf_cuda_library( + name = "gpu_cuda_alias", + hdrs = ["gpu_cuda_alias.h"], +) + +tf_cuda_library( + name = "gpu_device_functions", + hdrs = ["gpu_device_functions.h"], + deps = [ + ":gpu_cuda_alias", + "//tensorflow/core/platform:types", + "//third_party/eigen3", + ], +) + +cc_library( + name = "overflow", + hdrs = ["overflow.h"], + deps = [ + "//tensorflow/core/platform:logging", + "//tensorflow/core/platform:macros", + "//tensorflow/core/platform:types", + ], +) + +cc_library( + name = "exec_on_stall", + hdrs = ["exec_on_stall.h"], + deps = [ + "//tensorflow/core/platform:env", + "//tensorflow/core/platform:mutex", + ], +) + +cc_library( + name = "ptr_util", + hdrs = ["ptr_util.h"], +) + +cc_library( + name = "version_info", + srcs = ["version_info.cc"], + hdrs = ["//tensorflow/core:public/version.h"], + copts = tf_copts(), +) + +# Tests. + +tf_cc_test( + name = "overflow_test", + size = "small", + srcs = ["overflow_test.cc"], + deps = [ + ":overflow", + "//tensorflow/core:framework_lite", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ], +) + +tf_cc_test( + name = "exec_on_stall_test", + size = "small", + srcs = ["exec_on_stall_test.cc"], + deps = [ + ":exec_on_stall", + "//tensorflow/core:framework_lite", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + ], +) + +tf_cuda_only_cc_test( + name = "gpu_kernel_helper_test", + srcs = [ + "gpu_kernel_helper_test.cu.cc", + ], + deps = [ + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//third_party/eigen3", + ] + mkl_deps(), +) + +tf_cc_tests( + name = "higher_level_tests", + size = "small", + srcs = [ + "bcast_test.cc", + "command_line_flags_test.cc", + "debug_events_writer_test.cc", + "device_name_utils_test.cc", + "dump_graph_test.cc", + "equal_graph_def_test.cc", + "events_writer_test.cc", + "example_proto_fast_parsing_test.cc", + "example_proto_helper_test.cc", + "matmul_bcast_test.cc", + "memmapped_file_system_test.cc", + "presized_cuckoo_map_test.cc", + "reffed_status_callback_test.cc", + "reporter_test.cc", + "saved_tensor_slice_util_test.cc", + "semver_test.cc", + "stat_summarizer_test.cc", + "tensor_format_test.cc", + "tensor_slice_reader_test.cc", + "tensor_slice_set_test.cc", + "tensor_slice_util_test.cc", + "tensor_slice_writer_test.cc", + "work_sharder_test.cc", + ], + create_named_test_suite = True, + linkopts = select({ + "//tensorflow:macos": ["-headerpad_max_install_names"], + "//conditions:default": [], + }), + linkstatic = tf_kernel_tests_linkstatic(), + visibility = [ + "//tensorflow/core:__pkg__", + ], + deps = [ + ":protos_test_cc", + "//tensorflow/cc:cc_ops", + "//tensorflow/cc:cc_ops_internal", + "//tensorflow/cc:function_ops", + "//tensorflow/cc:ops", + "//tensorflow/cc:scope", + "//tensorflow/cc:sendrecv_ops", + "//tensorflow/cc:while_loop", + "//tensorflow/core", + "//tensorflow/core:core_cpu", + "//tensorflow/core:core_cpu_internal", + "//tensorflow/core:direct_session_internal", + "//tensorflow/core:framework", + "//tensorflow/core:framework_internal", + "//tensorflow/core:lib", + "//tensorflow/core:lib_internal", + "//tensorflow/core:ops", + "//tensorflow/core:protos_all_cc", + "//tensorflow/core:test", + "//tensorflow/core:test_main", + "//tensorflow/core:testlib", + "//tensorflow/core/kernels:ops_util", + "//tensorflow/core/platform:regexp", + "//third_party/eigen3", + "@com_google_absl//absl/base", + "@com_google_absl//absl/memory", + "@com_google_absl//absl/strings", + ], +) + +# Proto libraries. +tf_proto_library( + name = "test_log_proto_impl", + srcs = ["test_log.proto"], + cc_api_version = 2, + make_default_target_header_only = True, + # Not to be used outside of tensorflow/core. + visibility = ["//tensorflow/core:__pkg__"], +) + +tf_proto_library( + name = "protos_test", + srcs = ["example_proto_fast_parsing_test.proto"], + cc_api_version = 2, + protodeps = ["//tensorflow/core:protos_all"], + visibility = ["//visibility:public"], +) + +tf_proto_library( + name = "event_proto", + srcs = ["event.proto"], + cc_api_version = 2, + make_default_target_header_only = True, + protodeps = [ + "//tensorflow/core/framework:summary_proto", + "//tensorflow/core/framework:resource_handle_proto", + "//tensorflow/core/framework:tensor_proto", + "//tensorflow/core/framework:tensor_shape_proto", + "//tensorflow/core/framework:types_proto", + ], + visibility = ["//visibility:public"], +) + +tf_proto_library( + name = "saved_tensor_slice_proto", + srcs = ["saved_tensor_slice.proto"], + cc_api_version = 2, + make_default_target_header_only = True, + protodeps = [ + "//tensorflow/core/framework:resource_handle_proto", + "//tensorflow/core/framework:tensor_proto", + "//tensorflow/core/framework:tensor_shape_proto", + "//tensorflow/core/framework:tensor_slice_proto", + "//tensorflow/core/framework:types_proto", + "//tensorflow/core/framework:versions_proto", + ], + visibility = ["//visibility:public"], +) + +tf_proto_library( + name = "memmapped_file_system_proto", + srcs = ["memmapped_file_system.proto"], + cc_api_version = 2, + make_default_target_header_only = True, + visibility = ["//visibility:public"], +) + +tf_proto_library( + name = "protos_all", + cc_api_version = 2, + make_default_target_header_only = True, + protodeps = [ + ":event_proto", + ":saved_tensor_slice_proto", + ":memmapped_file_system_proto", + ], +) diff --git a/tensorflow/core/util/debug_events_writer.cc b/tensorflow/core/util/debug_events_writer.cc index 3ccdf3f720c..58994e7a9fd 100644 --- a/tensorflow/core/util/debug_events_writer.cc +++ b/tensorflow/core/util/debug_events_writer.cc @@ -158,7 +158,7 @@ Status DebugEventsWriter::Init() { int64 time_in_seconds = env_->NowMicros() / 1e6; file_prefix_ = io::JoinPath( dump_root_, strings::Printf("%s.%010lld.%s", kFileNamePrefix, - static_cast(time_in_seconds), + static_cast(time_in_seconds), port::Hostname().c_str())); TF_RETURN_IF_ERROR(InitNonMetadataFile(SOURCE_FILES)); TF_RETURN_IF_ERROR(InitNonMetadataFile(STACK_FRAMES)); diff --git a/tensorflow/core/util/sparse/BUILD b/tensorflow/core/util/sparse/BUILD index 01433f0a25d..1b22b5082ba 100644 --- a/tensorflow/core/util/sparse/BUILD +++ b/tensorflow/core/util/sparse/BUILD @@ -14,6 +14,7 @@ filegroup( "dim_comparator.h", "group_iterator.cc", "group_iterator.h", + "sparse_tensor.cc", "sparse_tensor.h", ], visibility = ["//tensorflow/core:__pkg__"], @@ -42,6 +43,7 @@ filegroup( name = "framework_internal_impl_group", srcs = [ "group_iterator.cc", + "sparse_tensor.cc", ], visibility = ["//tensorflow/core:__pkg__"], ) diff --git a/tensorflow/core/util/sparse/sparse_tensor.cc b/tensorflow/core/util/sparse/sparse_tensor.cc new file mode 100644 index 00000000000..7cbfbda7dea --- /dev/null +++ b/tensorflow/core/util/sparse/sparse_tensor.cc @@ -0,0 +1,165 @@ +/* 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/util/sparse/sparse_tensor.h" + +#include "tensorflow/core/lib/strings/strcat.h" + +namespace tensorflow { +namespace sparse { + +namespace { + +int UnsafeGetDimsFromIx(const Tensor& ix) { + DCHECK(TensorShapeUtils::IsMatrix(ix.shape())); + return ix.dim_size(1); +} + +Status GetDimsFromIx(const Tensor& ix, int* result) { + if (!TensorShapeUtils::IsMatrix(ix.shape())) { + return errors::InvalidArgument("indices must be a matrix, but got: ", + ix.shape().DebugString()); + } + *result = UnsafeGetDimsFromIx(ix); + return Status::OK(); +} + +} // namespace + +/* static */ Status SparseTensor::Create(Tensor ix, Tensor vals, + const VarDimArray shape, + const VarDimArray order, + SparseTensor* result) { + if (ix.dtype() != DT_INT64) { + return errors::InvalidArgument("indices must be type int64 but got: ", + ix.dtype()); + } + if (!TensorShapeUtils::IsVector(vals.shape())) { + return errors::InvalidArgument("vals must be a vec, but got: ", + vals.shape().DebugString()); + } + if (ix.shape().dim_size(0) != vals.shape().dim_size(0)) { + return errors::InvalidArgument( + "indices and values rows (indexing " + "dimension) must match. (indices = ", + ix.shape().dim_size(0), ", values = ", vals.shape().dim_size(0), ")"); + } + int dims = 0; + TF_RETURN_IF_ERROR(GetDimsFromIx(ix, &dims)); + if (order.size() != dims) { + return errors::InvalidArgument("Order length must be SparseTensor rank."); + } + if (shape.size() != dims) { + return errors::InvalidArgument("Shape rank must be SparseTensor rank."); + } + + *result = SparseTensor(std::move(ix), std::move(vals), shape, order); + return Status::OK(); +} + +/* static */ Status SparseTensor::Create(Tensor ix, Tensor vals, + const TensorShape& shape, + SparseTensor* result) { + return Create(std::move(ix), std::move(vals), TensorShapeToVector(shape), + UndefinedOrder(TensorShapeToVector(shape)), result); +} + +/* static */ Status SparseTensor::Create(Tensor ix, Tensor vals, + const VarDimArray shape, + SparseTensor* result) { + return Create(std::move(ix), std::move(vals), shape, UndefinedOrder(shape), + result); +} + +/* static */ Status SparseTensor::Create(Tensor ix, Tensor vals, + const TensorShape& shape, + const VarDimArray order, + SparseTensor* result) { + return Create(std::move(ix), std::move(vals), TensorShapeToVector(shape), + order, result); +} + +SparseTensor::SparseTensor(Tensor ix, Tensor vals, const VarDimArray shape, + const VarDimArray order) + : ix_(std::move(ix)), + vals_(std::move(vals)), + shape_(shape.begin(), shape.end()), + order_(order.begin(), order.end()), + dims_(UnsafeGetDimsFromIx(ix_)) { + DCHECK_EQ(ix_.dtype(), DT_INT64) + << "indices must be type int64 but got: " << ix_.dtype(); + DCHECK(TensorShapeUtils::IsVector(vals_.shape())) + << "vals must be a vec, but got: " << vals_.shape().DebugString(); + DCHECK_EQ(ix_.shape().dim_size(0), vals_.shape().dim_size(0)) + << "indices and values rows (indexing dimension) must match."; + DCHECK_EQ(order.size(), dims_) << "Order length must be SparseTensor rank."; + DCHECK_EQ(shape.size(), dims_) << "Shape rank must be SparseTensor rank."; +} + +Status SparseTensor::IndicesValid() const { + const auto ix_t = ix_.matrix(); + for (int64 ord : order_) { + if (ord < 0) { + return errors::FailedPrecondition( + "Order was not provided. Provide an order at " + "construction time or run ReorderInPlace"); + } + } + + for (std::size_t n = 0; n < num_entries(); ++n) { + bool valid = true; + bool different = false; + bool increasing = true; + if (n == 0) { + for (int di = 0; di < dims_; ++di) { + if (ix_t(n, di) < 0 || ix_t(n, di) >= shape_[di]) valid = false; + } + different = true; + } else { + for (int di = 0; di < dims_; ++di) { + if (ix_t(n, di) < 0 || ix_t(n, di) >= shape_[di]) valid = false; + int64 diff = ix_t(n, order_[di]) - ix_t(n - 1, order_[di]); + if (diff > 0) different = true; + if (!different && diff < 0) increasing = false; + } + } + if (TF_PREDICT_FALSE(!valid || !increasing || !different)) { + string index = strings::StrCat("indices[", n, "] = ["); + for (int di = 0; di < dims_; ++di) { + strings::StrAppend(&index, ix_t(n, di), di < dims_ - 1 ? "," : "]"); + } + if (!valid) { + return errors::InvalidArgument(index, + " is out of bounds: need 0 <= index < [", + str_util::Join(shape_, ","), "]"); + } + if (!increasing) { + return errors::InvalidArgument( + index, + " is out of order. Many sparse ops require sorted indices.\n" + " Use `tf.sparse.reorder` to create a correctly ordered copy." + "\n\n"); + } + if (!different) { + return errors::InvalidArgument(index, " is repeated"); + } + } + } + + return Status::OK(); +} + +} // namespace sparse +} // namespace tensorflow diff --git a/tensorflow/core/util/sparse/sparse_tensor.h b/tensorflow/core/util/sparse/sparse_tensor.h index d33bd03db29..680962d5567 100644 --- a/tensorflow/core/util/sparse/sparse_tensor.h +++ b/tensorflow/core/util/sparse/sparse_tensor.h @@ -30,7 +30,6 @@ limitations under the License. #include "tensorflow/core/lib/core/errors.h" #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/types.h" #include "tensorflow/core/util/sparse/dim_comparator.h" @@ -45,88 +44,38 @@ class SparseTensor { typedef typename gtl::InlinedVector ShapeArray; static Status Create(Tensor ix, Tensor vals, const VarDimArray shape, - const VarDimArray order, SparseTensor* result) { - if (ix.dtype() != DT_INT64) { - return Status( - error::INVALID_ARGUMENT, - strings::StrCat("indices must be type int64 but got: ", ix.dtype())); - } - if (!TensorShapeUtils::IsVector(vals.shape())) { - return Status(error::INVALID_ARGUMENT, - strings::StrCat("vals must be a vec, but got: ", - vals.shape().DebugString())); - } - if (ix.shape().dim_size(0) != vals.shape().dim_size(0)) { - return Status(error::INVALID_ARGUMENT, - strings::StrCat("indices and values rows (indexing " - "dimension) must match. (indices = ", - ix.shape().dim_size(0), ", values = ", - vals.shape().dim_size(0), ")")); - } - int dims = 0; - TF_RETURN_IF_ERROR(GetDimsFromIx(ix, &dims)); - if (order.size() != dims) { - return Status(error::INVALID_ARGUMENT, - "Order length must be SparseTensor rank."); - } - if (shape.size() != dims) { - return Status(error::INVALID_ARGUMENT, - "Shape rank must be SparseTensor rank."); - } - - *result = SparseTensor(ix, vals, shape, order); - return Status(); - } + const VarDimArray order, SparseTensor* result); static Status Create(Tensor ix, Tensor vals, const TensorShape& shape, - SparseTensor* result) { - return Create(ix, vals, TensorShapeToVector(shape), - UndefinedOrder(TensorShapeToVector(shape)), result); - } + SparseTensor* result); static Status Create(Tensor ix, Tensor vals, const VarDimArray shape, - SparseTensor* result) { - return Create(ix, vals, shape, UndefinedOrder(shape), result); - } + SparseTensor* result); static Status Create(Tensor ix, Tensor vals, const TensorShape& shape, - const VarDimArray order, SparseTensor* result) { - return Create(ix, vals, TensorShapeToVector(shape), order, result); - } + const VarDimArray order, SparseTensor* result); SparseTensor() : dims_(0) {} ABSL_DEPRECATED("Use Create() functions instead of constructors directly.") SparseTensor(Tensor ix, Tensor vals, const TensorShape& shape) - : SparseTensor(ix, vals, TensorShapeToVector(shape), + : SparseTensor(std::move(ix), std::move(vals), TensorShapeToVector(shape), UndefinedOrder(TensorShapeToVector(shape))) {} ABSL_DEPRECATED("Use Create() functions instead of constructors directly.") SparseTensor(Tensor ix, Tensor vals, const VarDimArray shape) - : SparseTensor(ix, vals, shape, UndefinedOrder(shape)) {} + : SparseTensor(std::move(ix), std::move(vals), shape, + UndefinedOrder(shape)) {} ABSL_DEPRECATED("use Create() functions instead of constructors directly.") SparseTensor(Tensor ix, Tensor vals, const TensorShape& shape, const VarDimArray order) - : SparseTensor(ix, vals, TensorShapeToVector(shape), order) {} + : SparseTensor(std::move(ix), std::move(vals), TensorShapeToVector(shape), + order) {} ABSL_DEPRECATED("Use Create() functions instead of constructors directly.") SparseTensor(Tensor ix, Tensor vals, const VarDimArray shape, - const VarDimArray order) - : ix_(ix), - vals_(vals), - shape_(shape.begin(), shape.end()), - order_(order.begin(), order.end()), - dims_(UnsafeGetDimsFromIx(ix)) { - DCHECK_EQ(ix.dtype(), DT_INT64) - << "indices must be type int64 but got: " << ix.dtype(); - DCHECK(TensorShapeUtils::IsVector(vals.shape())) - << "vals must be a vec, but got: " << vals.shape().DebugString(); - DCHECK_EQ(ix.shape().dim_size(0), vals.shape().dim_size(0)) - << "indices and values rows (indexing dimension) must match."; - DCHECK_EQ(order.size(), dims_) << "Order length must be SparseTensor rank."; - DCHECK_EQ(shape.size(), dims_) << "Shape rank must be SparseTensor rank."; - } + const VarDimArray order); SparseTensor(const SparseTensor& other) : SparseTensor(other.ix_, other.vals_, other.shape_, other.order_) {} @@ -163,22 +112,7 @@ class SparseTensor { DataType dtype() const { return vals_.dtype(); } - Status IndicesValid() const { - const auto ix_t = ix_.matrix(); - for (int64 ord : order_) { - if (ord < 0) { - return errors::FailedPrecondition( - "Order was not provided. Provide an order at " - "construction time or run ReorderInPlace"); - } - } - - for (std::size_t n = 0; n < num_entries(); ++n) { - TF_RETURN_IF_ERROR(IndexValid(ix_t, n)); - } - - return Status::OK(); - } + Status IndicesValid() const; VarDimArray shape() const { return shape_; } @@ -257,21 +191,6 @@ class SparseTensor { } private: - static Status GetDimsFromIx(const Tensor& ix, int* result) { - if (!TensorShapeUtils::IsMatrix(ix.shape())) { - return Status(error::INVALID_ARGUMENT, - strings::StrCat("indices must be a matrix, but got: ", - ix.shape().DebugString())); - } - *result = UnsafeGetDimsFromIx(ix); - return Status(); - } - - static int UnsafeGetDimsFromIx(const Tensor& ix) { - DCHECK(TensorShapeUtils::IsMatrix(ix.shape())); - return ix.dim_size(1); - } - static inline ShapeArray UndefinedOrder(const VarDimArray shape) { return ShapeArray(shape.size(), -1); } @@ -282,49 +201,6 @@ class SparseTensor { return vec; } - // Helper for IndicesValid() - inline Status IndexValid(const TTypes::ConstMatrix& ix_t, - int n) const { - bool valid = true; - bool different = false; - bool increasing = true; - if (n == 0) { - for (int di = 0; di < dims_; ++di) { - if (ix_t(n, di) < 0 || ix_t(n, di) >= shape_[di]) valid = false; - } - different = true; - } else { - for (int di = 0; di < dims_; ++di) { - if (ix_t(n, di) < 0 || ix_t(n, di) >= shape_[di]) valid = false; - int64 diff = ix_t(n, order_[di]) - ix_t(n - 1, order_[di]); - if (diff > 0) different = true; - if (!different && diff < 0) increasing = false; - } - } - if (TF_PREDICT_FALSE(!valid || !increasing || !different)) { - string index = strings::StrCat("indices[", n, "] = ["); - for (int di = 0; di < dims_; ++di) { - strings::StrAppend(&index, ix_t(n, di), di < dims_ - 1 ? "," : "]"); - } - if (!valid) { - return errors::InvalidArgument(index, - " is out of bounds: need 0 <= index < [", - str_util::Join(shape_, ","), "]"); - } - if (!increasing) { - return errors::InvalidArgument( - index, - " is out of order. Many sparse ops require sorted indices.\n" - " Use `tf.sparse.reorder` to create a correctly ordered copy." - "\n\n"); - } - if (!different) { - return errors::InvalidArgument(index, " is repeated"); - } - } - return Status::OK(); - } - // Helper for ToDense() template bool ValidateAndInitializeToDense(Tensor* out, bool initialize); @@ -381,7 +257,7 @@ class SparseTensor { // an in-place algorithm. It requires O(N log N) time and O(N) // temporary space. template -void SparseTensor::Reorder(const VarDimArray& order) { +inline void SparseTensor::Reorder(const VarDimArray& order) { DCHECK_EQ(DataTypeToEnum::v(), dtype()) << "Reorder requested with the wrong datatype"; DCHECK_EQ(order.size(), dims_) << "Order length must be SparseTensor rank"; @@ -440,7 +316,8 @@ void SparseTensor::Reorder(const VarDimArray& order) { } template -bool SparseTensor::ValidateAndInitializeToDense(Tensor* out, bool initialize) { +inline bool SparseTensor::ValidateAndInitializeToDense(Tensor* out, + bool initialize) { DCHECK_EQ(DataTypeToEnum::v(), dtype()) << "ToDense requested with the wrong datatype"; @@ -468,7 +345,7 @@ bool SparseTensor::ValidateAndInitializeToDense(Tensor* out, bool initialize) { } template -bool SparseTensor::ToDense(Tensor* out, bool initialize) { +inline bool SparseTensor::ToDense(Tensor* out, bool initialize) { if (!ValidateAndInitializeToDense(out, initialize)) return false; auto out_t = out->flat(); @@ -501,7 +378,7 @@ bool SparseTensor::ToDense(Tensor* out, bool initialize) { } template -SparseTensor SparseTensor::Concat( +inline SparseTensor SparseTensor::Concat( const gtl::ArraySlice& tensors) { DCHECK_GE(tensors.size(), size_t{1}) << "Cannot concat 0 SparseTensors"; const int dims = tensors[0].dims_; @@ -573,9 +450,9 @@ SparseTensor SparseTensor::Concat( } template -Status SparseTensor::Split(const SparseTensor& input_tensor, - const int split_dim, const int num_split, - std::vector* result) { +inline Status SparseTensor::Split(const SparseTensor& input_tensor, + const int split_dim, const int num_split, + std::vector* result) { std::vector output_indices; std::vector output_values; std::vector output_shapes; @@ -596,14 +473,12 @@ Status SparseTensor::Split(const SparseTensor& input_tensor, const int split_size = split_dim_size / num_split; if (!(num_split > 0 && num_split <= split_dim_size)) { - return Status(error::INVALID_ARGUMENT, - strings::StrCat("num_split must be in the interval (0, ", - split_dim_size, "]")); + return errors::InvalidArgument("num_split must be in the interval (0, ", + split_dim_size, "]"); } if (!(split_dim >= 0 && split_dim < num_dim)) { - return Status( - error::INVALID_ARGUMENT, - strings::StrCat("num_dim must be in the interval [0, ", num_dim, ")")); + return errors::InvalidArgument("num_dim must be in the interval [0, ", + num_dim, ")"); } const int residual = split_dim_size % num_split; @@ -656,9 +531,9 @@ Status SparseTensor::Split(const SparseTensor& input_tensor, } template -SparseTensor SparseTensor::Slice(const SparseTensor& input_tensor, - const gtl::ArraySlice& start, - const gtl::ArraySlice& size) { +inline SparseTensor SparseTensor::Slice(const SparseTensor& input_tensor, + const gtl::ArraySlice& start, + const gtl::ArraySlice& size) { TensorShape output_shape(input_tensor.shape()); const int dims = input_tensor.dims(); diff --git a/tensorflow/examples/adding_an_op/BUILD b/tensorflow/examples/adding_an_op/BUILD index adf7a959296..3dcf04bb324 100644 --- a/tensorflow/examples/adding_an_op/BUILD +++ b/tensorflow/examples/adding_an_op/BUILD @@ -70,7 +70,7 @@ py_test( name = "zero_out_1_test", size = "small", srcs = ["zero_out_1_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = [ "no_pip", @@ -86,7 +86,7 @@ py_test( name = "zero_out_2_test", size = "small", srcs = ["zero_out_2_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = [ "no_pip", @@ -103,7 +103,7 @@ py_test( name = "zero_out_3_test", size = "small", srcs = ["zero_out_3_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = [ "no_pip", @@ -134,7 +134,7 @@ py_test( size = "small", srcs = ["cuda_op_test.py"], exec_compatible_with = tf_exec_compatible_with({"tags": tf_cuda_tests_tags()}), - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = tf_cuda_tests_tags() + [ "notap", @@ -150,7 +150,7 @@ py_test( name = "fact_test", size = "small", srcs = ["fact_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = ["//tensorflow:tensorflow_py"], ) diff --git a/tensorflow/examples/label_image/BUILD b/tensorflow/examples/label_image/BUILD index efa637db03f..162a44ac109 100644 --- a/tensorflow/examples/label_image/BUILD +++ b/tensorflow/examples/label_image/BUILD @@ -56,7 +56,7 @@ py_binary( name = "label_image_py", srcs = ["label_image.py"], main = "label_image.py", - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ "//tensorflow:tensorflow_py", diff --git a/tensorflow/examples/saved_model/integration_tests/BUILD b/tensorflow/examples/saved_model/integration_tests/BUILD index c967f49d540..0e55a0af437 100644 --- a/tensorflow/examples/saved_model/integration_tests/BUILD +++ b/tensorflow/examples/saved_model/integration_tests/BUILD @@ -53,13 +53,6 @@ cuda_py_test( srcs = [ "saved_model_test.py", ], - additional_deps = [ - ":distribution_strategy_utils", - ":integration_scripts", - "@absl_py//absl/testing:parameterized", - "//tensorflow:tensorflow_py", - "//tensorflow/python/distribute:combinations", - ], shard_count = 4, tags = [ "no_pip", # b/131697937 and b/132196869 @@ -67,6 +60,13 @@ cuda_py_test( "nomsan", # forge input size exceeded "notsan", # forge input size exceeded ], + deps = [ + ":distribution_strategy_utils", + ":integration_scripts", + "//tensorflow:tensorflow_py", + "//tensorflow/python/distribute:combinations", + "@absl_py//absl/testing:parameterized", + ], ) # b/132234211: Target added to support internal test target that runs the test diff --git a/tensorflow/examples/speech_commands/BUILD b/tensorflow/examples/speech_commands/BUILD index 905e251864b..ea8a7d89ed9 100644 --- a/tensorflow/examples/speech_commands/BUILD +++ b/tensorflow/examples/speech_commands/BUILD @@ -15,7 +15,7 @@ py_binary( name = "accuracy_utils_py", srcs = ["accuracy_utils.py"], main = "accuracy_utils.py", - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ "//tensorflow:tensorflow_py", @@ -28,7 +28,7 @@ py_binary( name = "recognize_commands_py", srcs = ["recognize_commands.py"], main = "recognize_commands.py", - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ "//tensorflow:tensorflow_py", @@ -41,7 +41,7 @@ py_binary( name = "test_streaming_accuracy_py", srcs = ["test_streaming_accuracy.py"], main = "test_streaming_accuracy.py", - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":accuracy_utils_py", @@ -69,13 +69,13 @@ tf_py_test( name = "models_test", size = "small", srcs = ["models_test.py"], - additional_deps = [ - ":models", - "//tensorflow/python:client_testlib", - ], tags = [ "no_pip", # b/131330719 ], + deps = [ + ":models", + "//tensorflow/python:client_testlib", + ], ) py_library( @@ -96,21 +96,21 @@ tf_py_test( name = "input_data_test", size = "small", srcs = ["input_data_test.py"], - additional_deps = [ - ":input_data", - ":models", - "//tensorflow/python:client_testlib", - ], tags = [ "no_pip", # b/131330719 "v1only", # uses contrib ], + deps = [ + ":input_data", + ":models", + "//tensorflow/python:client_testlib", + ], ) py_binary( name = "train", srcs = ["train.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":train_main_lib"], ) @@ -134,20 +134,20 @@ tf_py_test( name = "train_test", size = "small", srcs = ["train_test.py"], - additional_deps = [ - ":train_main_lib", - "//tensorflow/python:client_testlib", - ], tags = [ "no_pip", # b/131330719 "v1only", # uses contrib ], + deps = [ + ":train_main_lib", + "//tensorflow/python:client_testlib", + ], ) py_binary( name = "freeze", srcs = ["freeze.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":freeze_main_lib"], ) @@ -181,20 +181,20 @@ tf_py_test( name = "freeze_test", size = "small", srcs = ["freeze_test.py"], - additional_deps = [ - ":freeze_main_lib", - "//tensorflow/python:client_testlib", - ], tags = [ "no_pip", # b/131330719 "v1only", # uses contrib ], + deps = [ + ":freeze_main_lib", + "//tensorflow/python:client_testlib", + ], ) py_binary( name = "wav_to_features", srcs = ["wav_to_features.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":wav_to_features_main_lib"], ) @@ -225,20 +225,20 @@ tf_py_test( name = "wav_to_features_test", size = "small", srcs = ["wav_to_features_test.py"], - additional_deps = [ - ":wav_to_features_main_lib", - "//tensorflow/python:client_testlib", - ], tags = [ "no_pip", # b/131330719 "v1only", # uses contrib ], + deps = [ + ":wav_to_features_main_lib", + "//tensorflow/python:client_testlib", + ], ) py_binary( name = "generate_streaming_test_wav", srcs = ["generate_streaming_test_wav.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":generate_streaming_test_wav_main_lib"], ) @@ -269,14 +269,14 @@ tf_py_test( name = "generate_streaming_test_wav_test", size = "small", srcs = ["generate_streaming_test_wav_test.py"], - additional_deps = [ - ":generate_streaming_test_wav_main_lib", - "//tensorflow/python:client_testlib", - ], tags = [ "no_pip", # b/131330719 "v1only", # uses contrib ], + deps = [ + ":generate_streaming_test_wav_main_lib", + "//tensorflow/python:client_testlib", + ], ) tf_cc_binary( @@ -297,7 +297,7 @@ tf_cc_binary( py_binary( name = "label_wav", srcs = ["label_wav.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":label_wav_main_lib"], ) @@ -324,14 +324,14 @@ tf_py_test( name = "label_wav_test", size = "medium", srcs = ["label_wav_test.py"], - additional_deps = [ - ":label_wav_main_lib", - "//tensorflow/python:client_testlib", - ], tags = [ "no_pip", # b/131330719 "v1only", # uses contrib ], + deps = [ + ":label_wav_main_lib", + "//tensorflow/python:client_testlib", + ], ) cc_library( diff --git a/tensorflow/examples/tf2_showcase/BUILD b/tensorflow/examples/tf2_showcase/BUILD index 5e612ae2f6f..afa32d1be25 100644 --- a/tensorflow/examples/tf2_showcase/BUILD +++ b/tensorflow/examples/tf2_showcase/BUILD @@ -18,7 +18,7 @@ test_suite( py_test( name = "mnist", srcs = ["mnist.py"], - python_version = "PY2", + python_version = "PY3", tags = [ "manual", "no_oss", diff --git a/tensorflow/examples/tutorials/mnist/BUILD b/tensorflow/examples/tutorials/mnist/BUILD index c4f283bb679..be8cdc3a1c2 100644 --- a/tensorflow/examples/tutorials/mnist/BUILD +++ b/tensorflow/examples/tutorials/mnist/BUILD @@ -51,7 +51,7 @@ py_binary( srcs = [ "fully_connected_feed.py", ], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = ["optonly"], deps = [ @@ -66,7 +66,7 @@ py_binary( srcs = [ "mnist_with_summaries.py", ], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":input_data", @@ -85,7 +85,7 @@ py_binary( srcs = [ "mnist_softmax_xla.py", ], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":input_data", @@ -98,17 +98,17 @@ tf_py_test( srcs = [ "fully_connected_feed.py", ], - additional_deps = [ - ":input_data", - ":mnist", - "//tensorflow:tensorflow_py", - ], args = [ "--fake_data", "--max_steps=10", ], main = "fully_connected_feed.py", tags = ["no_pip"], + deps = [ + ":input_data", + ":mnist", + "//tensorflow:tensorflow_py", + ], ) tf_py_test( @@ -117,10 +117,6 @@ tf_py_test( srcs = [ "mnist_with_summaries.py", ], - additional_deps = [ - ":input_data", - "//tensorflow:tensorflow_py", - ], args = [ "--fake_data", "--max_steps=10", @@ -129,6 +125,11 @@ tf_py_test( main = "mnist_with_summaries.py", tags = [ "no_pip", + "noasan", # http://b/146080738 "notsan", # http://b/29184009 ], + deps = [ + ":input_data", + "//tensorflow:tensorflow_py", + ], ) diff --git a/tensorflow/examples/tutorials/word2vec/BUILD b/tensorflow/examples/tutorials/word2vec/BUILD index c2dd1acf8c2..0a885303919 100644 --- a/tensorflow/examples/tutorials/word2vec/BUILD +++ b/tensorflow/examples/tutorials/word2vec/BUILD @@ -13,7 +13,7 @@ py_binary( srcs = [ "word2vec_basic.py", ], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = [ "no-internal-py3", diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go index 98e11497915..a103b438bff 100644 --- a/tensorflow/go/op/wrappers.go +++ b/tensorflow/go/op/wrappers.go @@ -11720,7 +11720,7 @@ func DepthwiseConv2dNativeBackpropFilterDataFormat(value string) DepthwiseConv2d // element on that dimension. The dimension order is determined by the value of // `data_format`, see above for details. Dilations in the batch and depth // dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func DepthwiseConv2dNativeBackpropFilterDilations(value []int64) DepthwiseConv2dNativeBackpropFilterAttr { return func(m optionalAttr) { m["dilations"] = value @@ -11977,7 +11977,7 @@ func SampleDistortedBoundingBoxV2Seed2(value int64) SampleDistortedBoundingBoxV2 // // value: The cropped area of the image must have an aspect ratio = // width / height within this range. -// If not specified, defaults to {f:0.75 f:1.33} +// If not specified, defaults to {f:0.75 f:1.33} func SampleDistortedBoundingBoxV2AspectRatioRange(value []float32) SampleDistortedBoundingBoxV2Attr { return func(m optionalAttr) { m["aspect_ratio_range"] = value @@ -11988,7 +11988,7 @@ func SampleDistortedBoundingBoxV2AspectRatioRange(value []float32) SampleDistort // // value: The cropped area of the image must contain a fraction of the // supplied image within this range. -// If not specified, defaults to {f:0.05 f:1} +// If not specified, defaults to {f:0.05 f:1} func SampleDistortedBoundingBoxV2AreaRange(value []float32) SampleDistortedBoundingBoxV2Attr { return func(m optionalAttr) { m["area_range"] = value @@ -12194,7 +12194,7 @@ func SampleDistortedBoundingBoxMinObjectCovered(value float32) SampleDistortedBo // // value: The cropped area of the image must have an aspect ratio = // width / height within this range. -// If not specified, defaults to {f:0.75 f:1.33} +// If not specified, defaults to {f:0.75 f:1.33} func SampleDistortedBoundingBoxAspectRatioRange(value []float32) SampleDistortedBoundingBoxAttr { return func(m optionalAttr) { m["aspect_ratio_range"] = value @@ -12205,7 +12205,7 @@ func SampleDistortedBoundingBoxAspectRatioRange(value []float32) SampleDistorted // // value: The cropped area of the image must contain a fraction of the // supplied image within this range. -// If not specified, defaults to {f:0.05 f:1} +// If not specified, defaults to {f:0.05 f:1} func SampleDistortedBoundingBoxAreaRange(value []float32) SampleDistortedBoundingBoxAttr { return func(m optionalAttr) { m["area_range"] = value @@ -16898,7 +16898,7 @@ func CompareAndBitpack(scope *Scope, input tf.Output, threshold tf.Output) (outp // Considering the batch matrix multiplication equation again // (`bij,bjk->bik`), the contracted axis label is `j`. // -// (e) Expand Diagonal: If the output subcripts contain repeated (explicit) axis +// (e) Expand Diagonal: If the output subscripts contain repeated (explicit) axis // labels, the opposite operation of (a) is applied. For example, in the // equation `i->iii`, and input shape `[3]`, the output of shape `[3, 3, 3]` // are all zeros, except for the (generalized) diagonal which is populated @@ -16906,7 +16906,7 @@ func CompareAndBitpack(scope *Scope, input tf.Output, threshold tf.Output) (outp // Note: This operation is not supported by `np.einsum` or `tf.einsum`; it is // provided to enable computing the symbolic gradient of `tf.einsum`. // -// The output subcripts must contain only labels appearing in at least one of the +// The output subscripts must contain only labels appearing in at least one of the // input subscripts. Furthermore, all dimensions mapping to the same axis label // must be equal. // @@ -16918,7 +16918,7 @@ func CompareAndBitpack(scope *Scope, input tf.Output, threshold tf.Output) (outp // // The broadcasted dimensions are placed in the corresponding location of the // ellipsis in the output subscript. If the broadcasted dimensions are non-empty -// and the output subcripts do not contain ellipsis, then an InvalidArgument error +// and the output subscripts do not contain ellipsis, then an InvalidArgument error // is raised. // // @compatibility(numpy) @@ -18067,11 +18067,11 @@ func ResourceApplyAdamWithAmsgradUseLocking(value bool) ResourceApplyAdamWithAms // Update '*var' according to the Adam algorithm. // -// $$lr_t := \text{learning\_rate} * \sqrt{1 - beta_2^t} / (1 - beta_1^t)$$ -// $$m_t := beta_1 * m_{t-1} + (1 - beta_1) * g$$ -// $$v_t := beta_2 * v_{t-1} + (1 - beta_2) * g * g$$ -// $$vhat_t := max{vhat_{t-1}, v_t}$$ -// $$variable := variable - lr_t * m_t / (\sqrt{vhat_t} + \epsilon)$$ +// $$\text{lr}_t := \mathrm{learning_rate} * \sqrt{1 - \beta_2^t} / (1 - \beta_1^t)$$ +// $$m_t := \beta_1 * m_{t-1} + (1 - \beta_1) * g$$ +// $$v_t := \beta_2 * v_{t-1} + (1 - \beta_2) * g * g$$ +// $$\hat{v}_t := max{\hat{v}_{t-1}, v_t}$$ +// $$\text{variable} := \text{variable} - \text{lr}_t * m_t / (\sqrt{\hat{v}_t} + \epsilon)$$ // // Arguments: // var_: Should be from a Variable(). @@ -18879,7 +18879,7 @@ func ImageSummaryMaxImages(value int64) ImageSummaryAttr { // ImageSummaryBadColor sets the optional bad_color attribute to value. // // value: Color to use for pixels with non-finite values. -// If not specified, defaults to {dtype:DT_UINT8 tensor_shape:{dim:{size:4}} int_val:255 int_val:0 int_val:0 int_val:255} +// If not specified, defaults to {dtype:DT_UINT8 tensor_shape:{dim:{size:4}} int_val:255 int_val:0 int_val:0 int_val:255} func ImageSummaryBadColor(value tf.Tensor) ImageSummaryAttr { return func(m optionalAttr) { m["bad_color"] = value @@ -19874,7 +19874,7 @@ func Conv3DBackpropFilterV2DataFormat(value string) Conv3DBackpropFilterV2Attr { // filter element on that dimension. The dimension order is determined by the // value of `data_format`, see above for details. Dilations in the batch and // depth dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} func Conv3DBackpropFilterV2Dilations(value []int64) Conv3DBackpropFilterV2Attr { return func(m optionalAttr) { m["dilations"] = value @@ -21171,7 +21171,7 @@ func Conv2DBackpropInputDataFormat(value string) Conv2DBackpropInputAttr { // element on that dimension. The dimension order is determined by the value of // `data_format`, see above for details. Dilations in the batch and depth // dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func Conv2DBackpropInputDilations(value []int64) Conv2DBackpropInputAttr { return func(m optionalAttr) { m["dilations"] = value @@ -21879,7 +21879,7 @@ func Conv2DDataFormat(value string) Conv2DAttr { // filter element on that dimension. The dimension order is determined by the // value of `data_format`, see above for details. Dilations in the batch and // depth dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func Conv2DDilations(value []int64) Conv2DAttr { return func(m optionalAttr) { m["dilations"] = value @@ -22075,7 +22075,7 @@ func QuantizedDepthwiseConv2DWithBiasAndReluAndRequantizeOutType(value tf.DataTy // QuantizedDepthwiseConv2DWithBiasAndReluAndRequantizeDilations sets the optional dilations attribute to value. // // value: List of dilation values. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func QuantizedDepthwiseConv2DWithBiasAndReluAndRequantizeDilations(value []int64) QuantizedDepthwiseConv2DWithBiasAndReluAndRequantizeAttr { return func(m optionalAttr) { m["dilations"] = value @@ -22144,7 +22144,7 @@ func QuantizedDepthwiseConv2DWithBiasAndReluOutType(value tf.DataType) Quantized // QuantizedDepthwiseConv2DWithBiasAndReluDilations sets the optional dilations attribute to value. // // value: List of dilation values. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func QuantizedDepthwiseConv2DWithBiasAndReluDilations(value []int64) QuantizedDepthwiseConv2DWithBiasAndReluAttr { return func(m optionalAttr) { m["dilations"] = value @@ -22259,7 +22259,7 @@ func QuantizedDepthwiseConv2DWithBiasOutType(value tf.DataType) QuantizedDepthwi // QuantizedDepthwiseConv2DWithBiasDilations sets the optional dilations attribute to value. // // value: List of dilation values. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func QuantizedDepthwiseConv2DWithBiasDilations(value []int64) QuantizedDepthwiseConv2DWithBiasAttr { return func(m optionalAttr) { m["dilations"] = value @@ -22318,7 +22318,7 @@ func QuantizedDepthwiseConv2DOutType(value tf.DataType) QuantizedDepthwiseConv2D // QuantizedDepthwiseConv2DDilations sets the optional dilations attribute to value. // // value: List of dilation values. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func QuantizedDepthwiseConv2DDilations(value []int64) QuantizedDepthwiseConv2DAttr { return func(m optionalAttr) { m["dilations"] = value @@ -22492,7 +22492,7 @@ func QuantizedConv2DPerChannelOutType(value tf.DataType) QuantizedConv2DPerChann // QuantizedConv2DPerChannelDilations sets the optional dilations attribute to value. // // value: list of dilation values. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func QuantizedConv2DPerChannelDilations(value []int64) QuantizedConv2DPerChannelAttr { return func(m optionalAttr) { m["dilations"] = value @@ -22683,7 +22683,7 @@ func Conv3DBackpropInputV2DataFormat(value string) Conv3DBackpropInputV2Attr { // filter element on that dimension. The dimension order is determined by the // value of `data_format`, see above for details. Dilations in the batch and // depth dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} func Conv3DBackpropInputV2Dilations(value []int64) Conv3DBackpropInputV2Attr { return func(m optionalAttr) { m["dilations"] = value @@ -25257,7 +25257,7 @@ func DepthwiseConv2dNativeDataFormat(value string) DepthwiseConv2dNativeAttr { // element on that dimension. The dimension order is determined by the value of // `data_format`, see above for details. Dilations in the batch and depth // dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func DepthwiseConv2dNativeDilations(value []int64) DepthwiseConv2dNativeAttr { return func(m optionalAttr) { m["dilations"] = value @@ -25314,7 +25314,7 @@ func DepthwiseConv2dNative(scope *Scope, input tf.Output, filter tf.Output, stri type Conv3DBackpropInputAttr func(optionalAttr) // Conv3DBackpropInputDilations sets the optional dilations attribute to value. -// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} func Conv3DBackpropInputDilations(value []int64) Conv3DBackpropInputAttr { return func(m optionalAttr) { m["dilations"] = value @@ -25646,7 +25646,7 @@ func DepthwiseConv2dNativeBackpropInputDataFormat(value string) DepthwiseConv2dN // element on that dimension. The dimension order is determined by the value of // `data_format`, see above for details. Dilations in the batch and depth // dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func DepthwiseConv2dNativeBackpropInputDilations(value []int64) DepthwiseConv2dNativeBackpropInputAttr { return func(m optionalAttr) { m["dilations"] = value @@ -26269,7 +26269,7 @@ func QuantizedConv2DOutType(value tf.DataType) QuantizedConv2DAttr { // filter element on that dimension. The dimension order is determined by the // value of `data_format`, see above for details. Dilations in the batch and // depth dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func QuantizedConv2DDilations(value []int64) QuantizedConv2DAttr { return func(m optionalAttr) { m["dilations"] = value @@ -27290,7 +27290,7 @@ func Conv3DDataFormat(value string) Conv3DAttr { // filter element on that dimension. The dimension order is determined by the // value of `data_format`, see above for details. Dilations in the batch and // depth dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} func Conv3DDilations(value []int64) Conv3DAttr { return func(m optionalAttr) { m["dilations"] = value @@ -33668,7 +33668,7 @@ func SparseReduceMax(scope *Scope, input_indices tf.Output, input_values tf.Outp type Conv3DBackpropFilterAttr func(optionalAttr) // Conv3DBackpropFilterDilations sets the optional dilations attribute to value. -// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1 i:1} func Conv3DBackpropFilterDilations(value []int64) Conv3DBackpropFilterAttr { return func(m optionalAttr) { m["dilations"] = value @@ -39805,9 +39805,9 @@ func ResourceApplyKerasMomentumUseNesterov(value bool) ResourceApplyKerasMomentu } } -// Update '*var' according to the momentum scheme. Set use_nesterov = True if you +// Update '*var' according to the momentum scheme. // -// want to use Nesterov momentum. +// Set use_nesterov = True if you want to use Nesterov momentum. // // accum = accum * momentum - lr * grad // var += accum @@ -41346,10 +41346,10 @@ func ResourceApplyAdamUseNesterov(value bool) ResourceApplyAdamAttr { // Update '*var' according to the Adam algorithm. // -// $$lr_t := \text{learning\_rate} * \sqrt{1 - beta_2^t} / (1 - beta_1^t)$$ -// $$m_t := beta_1 * m_{t-1} + (1 - beta_1) * g$$ -// $$v_t := beta_2 * v_{t-1} + (1 - beta_2) * g * g$$ -// $$variable := variable - lr_t * m_t / (\sqrt{v_t} + \epsilon)$$ +// $$\text{lr}_t := \mathrm{learning_rate} * \sqrt{1 - \beta_2^t} / (1 - \beta_1^t)$$ +// $$m_t := \beta_1 * m_{t-1} + (1 - \beta_1) * g$$ +// $$v_t := \beta_2 * v_{t-1} + (1 - \beta_2) * g * g$$ +// $$\text{variable} := \text{variable} - \text{lr}_t * m_t / (\sqrt{v_t} + \epsilon)$$ // // Arguments: // var_: Should be from a Variable(). @@ -45095,7 +45095,7 @@ func Conv2DBackpropFilterDataFormat(value string) Conv2DBackpropFilterAttr { // element on that dimension. The dimension order is determined by the value of // `data_format`, see above for details. Dilations in the batch and depth // dimensions must be 1. -// If not specified, defaults to {i:1 i:1 i:1 i:1} +// If not specified, defaults to {i:1 i:1 i:1 i:1} func Conv2DBackpropFilterDilations(value []int64) Conv2DBackpropFilterAttr { return func(m optionalAttr) { m["dilations"] = value diff --git a/tensorflow/lite/BUILD b/tensorflow/lite/BUILD index 84150546353..119903d4150 100644 --- a/tensorflow/lite/BUILD +++ b/tensorflow/lite/BUILD @@ -1,5 +1,6 @@ load("//tensorflow:tensorflow.bzl", "if_not_windows", "tf_cc_test") load("//tensorflow/lite:build_def.bzl", "tflite_cc_shared_object", "tflite_copts") +load("//tensorflow/lite/micro:build_def.bzl", "cc_library") load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite") package( @@ -15,6 +16,13 @@ exports_files(glob([ "models/testdata/*", ])) +config_setting( + name = "gemmlowp_profiling", + values = { + "copt": "-DGEMMLOWP_PROFILING", + }, +) + config_setting( name = "mips", values = { @@ -143,6 +151,7 @@ cc_library( hdrs = [ "string_type.h", ], + build_for_embedded = True, copts = TFLITE_DEFAULT_COPTS, ) @@ -226,6 +235,7 @@ cc_library( name = "string_util", srcs = ["string_util.cc"], hdrs = ["string_util.h"], + build_for_embedded = True, copts = TFLITE_DEFAULT_COPTS, deps = [ ":string", @@ -430,6 +440,7 @@ cc_library( cc_library( name = "type_to_tflitetype", hdrs = ["type_to_tflitetype.h"], + build_for_embedded = True, deps = ["//tensorflow/lite/c:common"], ) diff --git a/tensorflow/lite/build_def.bzl b/tensorflow/lite/build_def.bzl index 35b75cdbd61..5234fb32b76 100644 --- a/tensorflow/lite/build_def.bzl +++ b/tensorflow/lite/build_def.bzl @@ -272,8 +272,7 @@ def generated_test_models(): "exp", "embedding_lookup", "expand_dims", - # TODO(b/145885576): Re-enable. - # "eye", + "eye", "fill", "floor", "floor_div", @@ -302,9 +301,8 @@ def generated_test_models(): "logical_or", "logical_xor", "lstm", - # TODO(b/145885576): Re-enable. - # "matrix_diag", - # "matrix_set_diag", + "matrix_diag", + "matrix_set_diag", "max_pool", "maximum", "mean", @@ -691,7 +689,7 @@ def gen_model_coverage_test(src, model_name, data, failure_type, tags): ] + args, data = data, srcs_version = "PY2AND3", - python_version = "PY2", + python_version = "PY3", tags = [ "no_oss", "no_windows", diff --git a/tensorflow/lite/builtin_ops.h b/tensorflow/lite/builtin_ops.h index 18c9b32bc3d..7cb04c84b2e 100644 --- a/tensorflow/lite/builtin_ops.h +++ b/tensorflow/lite/builtin_ops.h @@ -149,6 +149,7 @@ typedef enum { kTfLiteBuiltinNonMaxSuppressionV4 = 120, kTfLiteBuiltinNonMaxSuppressionV5 = 121, kTfLiteBuiltinScatterNd = 122, + kTfLiteBuiltinSelectV2 = 123, } TfLiteBuiltinOperator; #ifdef __cplusplus diff --git a/tensorflow/lite/c/BUILD b/tensorflow/lite/c/BUILD index b3e231e8cb3..0fe9d974d6e 100644 --- a/tensorflow/lite/c/BUILD +++ b/tensorflow/lite/c/BUILD @@ -3,6 +3,10 @@ load( "tflite_cc_shared_object", "tflite_copts", ) +load( + "//tensorflow/lite/micro:build_def.bzl", + "cc_library", +) package( default_visibility = [":experimental"], @@ -130,6 +134,7 @@ cc_library( "builtin_op_data.h", "common.h", ], + build_for_embedded = True, visibility = [ "//tensorflow/lite:__subpackages__", ], diff --git a/tensorflow/lite/c/c_api.h b/tensorflow/lite/c/c_api.h index 036df27b5d1..6c46e92bc53 100644 --- a/tensorflow/lite/c/c_api.h +++ b/tensorflow/lite/c/c_api.h @@ -56,8 +56,8 @@ limitations under the License. /// TfLiteInterpreterInvoke(interpreter); /// /// // Extract the output tensor data. -/// TfLiteTensor* output_tensor = -// TfLiteInterpreterGetInputTensor(interpreter, 0); +/// const TfLiteTensor* output_tensor = +// TfLiteInterpreterGetOutputTensor(interpreter, 0); /// TfLiteTensorCopyToBuffer(output_tensor, output.data(), /// output.size() * sizeof(float)); /// diff --git a/tensorflow/lite/c/common.h b/tensorflow/lite/c/common.h index 332b9b68881..7d728ab55b7 100644 --- a/tensorflow/lite/c/common.h +++ b/tensorflow/lite/c/common.h @@ -91,9 +91,11 @@ typedef struct { // in bytes. int TfLiteIntArrayGetSizeInBytes(int size); +#ifndef TF_LITE_STATIC_MEMORY // Create a array of a given `size` (uninitialized entries). // This returns a pointer, that you must free using TfLiteIntArrayFree(). TfLiteIntArray* TfLiteIntArrayCreate(int size); +#endif // Check if two intarrays are equal. Returns 1 if they are equal, 0 otherwise. int TfLiteIntArrayEqual(const TfLiteIntArray* a, const TfLiteIntArray* b); @@ -102,12 +104,14 @@ int TfLiteIntArrayEqual(const TfLiteIntArray* a, const TfLiteIntArray* b); int TfLiteIntArrayEqualsArray(const TfLiteIntArray* a, int b_size, const int b_data[]); +#ifndef TF_LITE_STATIC_MEMORY // Create a copy of an array passed as `src`. // You are expected to free memory with TfLiteIntArrayFree TfLiteIntArray* TfLiteIntArrayCopy(const TfLiteIntArray* src); // Free memory of array `a`. void TfLiteIntArrayFree(TfLiteIntArray* a); +#endif // TF_LITE_STATIC_MEMORY // Fixed size list of floats. Used for per-channel quantization. typedef struct { @@ -126,12 +130,14 @@ typedef struct { // in bytes. int TfLiteFloatArrayGetSizeInBytes(int size); +#ifndef TF_LITE_STATIC_MEMORY // Create a array of a given `size` (uninitialized entries). // This returns a pointer, that you must free using TfLiteFloatArrayFree(). TfLiteFloatArray* TfLiteFloatArrayCreate(int size); // Free memory of array `a`. void TfLiteFloatArrayFree(TfLiteFloatArray* a); +#endif // TF_LITE_STATIC_MEMORY // Since we must not depend on any libraries, define a minimal subset of // error macros while avoiding names that have pre-conceived meanings like @@ -387,6 +393,7 @@ typedef struct { TfLiteSparsity* sparsity; } TfLiteTensor; +#ifndef TF_LITE_STATIC_MEMORY // Free data memory of tensor `t`. void TfLiteTensorDataFree(TfLiteTensor* t); @@ -409,6 +416,7 @@ void TfLiteTensorReset(TfLiteType type, const char* name, TfLiteIntArray* dims, // Resize the allocated data of a (dynamic) tensor. Tensors with allocation // types other than kTfLiteDynamic will be ignored. void TfLiteTensorRealloc(size_t num_bytes, TfLiteTensor* tensor); +#endif // TF_LITE_STATIC_MEMORY // A structure representing an instance of a node. // This structure only exhibits the inputs, outputs and user defined data, not diff --git a/tensorflow/lite/core/api/BUILD b/tensorflow/lite/core/api/BUILD index 9615f8ed10b..c4eebf8b5df 100644 --- a/tensorflow/lite/core/api/BUILD +++ b/tensorflow/lite/core/api/BUILD @@ -1,4 +1,5 @@ load("//tensorflow/lite:build_def.bzl", "tflite_copts") +load("//tensorflow/lite/micro:build_def.bzl", "cc_library") package( default_visibility = ["//visibility:public"], @@ -20,6 +21,7 @@ cc_library( "profiler.h", "tensor_utils.h", ], + build_for_embedded = True, copts = tflite_copts(), deps = [ "//tensorflow/lite/c:common", diff --git a/tensorflow/lite/core/api/flatbuffer_conversions.cc b/tensorflow/lite/core/api/flatbuffer_conversions.cc index 7f6266e28b8..f7b8197b068 100644 --- a/tensorflow/lite/core/api/flatbuffer_conversions.cc +++ b/tensorflow/lite/core/api/flatbuffer_conversions.cc @@ -791,6 +791,7 @@ TfLiteStatus ParseOpData(const Operator* op, BuiltinOperator op_type, case BuiltinOperator_ROUND: case BuiltinOperator_RSQRT: case BuiltinOperator_SELECT: + case BuiltinOperator_SELECT_V2: case BuiltinOperator_SIN: case BuiltinOperator_SLICE: case BuiltinOperator_SPACE_TO_BATCH_ND: diff --git a/tensorflow/lite/core/subgraph.cc b/tensorflow/lite/core/subgraph.cc index 69c39769593..e6a37ee0476 100644 --- a/tensorflow/lite/core/subgraph.cc +++ b/tensorflow/lite/core/subgraph.cc @@ -125,6 +125,18 @@ TfLiteQuantizationParams GetLegacyQuantization( return legacy_quantization; } +static constexpr const char kUnknownCustomOpName[] = "UnknownCustomOp"; +const char* GetTFLiteOpName(const TfLiteRegistration& op_reg) { + const char* op_name = nullptr; + if (op_reg.builtin_code == tflite::BuiltinOperator_CUSTOM) { + const char* const custom_name = op_reg.custom_name; + op_name = custom_name ? custom_name : kUnknownCustomOpName; + } else { + op_name = tflite::EnumNamesBuiltinOperator()[op_reg.builtin_code]; + } + return op_name; +} + } // namespace // A trivial implementation of GraphInfo around the Interpreter. @@ -769,14 +781,7 @@ TfLiteStatus Subgraph::Invoke() { nodes_and_registration_[node_index].second; const char* op_name = nullptr; - if (profiler_) { - if (registration.builtin_code == tflite::BuiltinOperator_CUSTOM) { - const char* const custom_name = registration.custom_name; - op_name = custom_name ? custom_name : "UnknownCustomOp"; - } else { - op_name = tflite::EnumNamesBuiltinOperator()[registration.builtin_code]; - } - } + if (profiler_) op_name = GetTFLiteOpName(registration); TFLITE_SCOPED_TAGGED_OPERATOR_PROFILE(profiler_.get(), op_name, node_index); // TODO(ycling): This is an extra loop through inputs to check if the data @@ -1156,6 +1161,9 @@ TfLiteStatus Subgraph::EnsureMemoryAllocations() { } TfLiteStatus Subgraph::ModifyGraphWithDelegate(TfLiteDelegate* delegate) { + TFLITE_SCOPED_TAGGED_DEFAULT_PROFILE(profiler_.get(), + "ModifyGraphWithDelegate"); + // Restore delegation state if applicable. TF_LITE_ENSURE_STATUS(RedoAllDelegates()); diff --git a/tensorflow/lite/core/subgraph.h b/tensorflow/lite/core/subgraph.h index c2572546709..26e195a6c6e 100644 --- a/tensorflow/lite/core/subgraph.h +++ b/tensorflow/lite/core/subgraph.h @@ -532,7 +532,7 @@ class Subgraph { // A pure C data structure used to communicate with the pure C plugin // interface. To avoid copying tensor metadata, this is also the definitive // structure to store tensors. - TfLiteContext context_; + TfLiteContext context_ = {}; // A pointer to the external contexts (kTfLiteMaxExternalContexts) array that // sits inside the associated TFLite interpreter instance. diff --git a/tensorflow/lite/delegates/flex/whitelisted_flex_ops.cc b/tensorflow/lite/delegates/flex/whitelisted_flex_ops.cc index 67b760c4ef3..685c4cf4758 100644 --- a/tensorflow/lite/delegates/flex/whitelisted_flex_ops.cc +++ b/tensorflow/lite/delegates/flex/whitelisted_flex_ops.cc @@ -184,8 +184,10 @@ bool IsWhitelistedFlexOp(const std::string& tensorflow_op_name) { "MatMul", "MatrixDiag", "MatrixDiagV2", + "MatrixDiagV3", "MatrixSetDiag", "MatrixSetDiagV2", + "MatrixSetDiagV3", "Max", "Maximum", "MaxPool", diff --git a/tensorflow/lite/delegates/gpu/cl/kernels/BUILD b/tensorflow/lite/delegates/gpu/cl/kernels/BUILD index c64819b72e4..5b127842d28 100644 --- a/tensorflow/lite/delegates/gpu/cl/kernels/BUILD +++ b/tensorflow/lite/delegates/gpu/cl/kernels/BUILD @@ -439,6 +439,46 @@ cc_test( ], ) +cc_library( + name = "convolution_transposed_4x4", + srcs = ["convolution_transposed_4x4.cc"], + hdrs = ["convolution_transposed_4x4.h"], + deps = [ + ":gpu_operation", + ":util", + "//tensorflow/lite/delegates/gpu/cl:buffer", + "//tensorflow/lite/delegates/gpu/cl:linear_storage", + "//tensorflow/lite/delegates/gpu/cl:precision", + "//tensorflow/lite/delegates/gpu/cl:tensor", + "//tensorflow/lite/delegates/gpu/cl:tensor_type", + "//tensorflow/lite/delegates/gpu/cl:util", + "//tensorflow/lite/delegates/gpu/common:data_type", + "//tensorflow/lite/delegates/gpu/common:operations", + "//tensorflow/lite/delegates/gpu/common:shape", + "//tensorflow/lite/delegates/gpu/common:status", + "//tensorflow/lite/delegates/gpu/common:tensor", + "//tensorflow/lite/delegates/gpu/common:types", + ], +) + +cc_test( + name = "convolution_transposed_4x4_test", + srcs = ["convolution_transposed_4x4_test.cc"], + linkstatic = True, + tags = [ + "linux", + "local", + ], + deps = [ + ":cl_test", + ":convolution_transposed_4x4", + "//tensorflow/lite/delegates/gpu/cl:tensor", + "//tensorflow/lite/delegates/gpu/common:operations", + "//tensorflow/lite/delegates/gpu/common:status", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "convolution_transposed_thin", srcs = ["convolution_transposed_thin.cc"], @@ -1181,6 +1221,7 @@ test_suite( "conv_powervr_test", "conv_texture_test", "convolution_transposed_3x3_thin_test", + "convolution_transposed_4x4_test", "convolution_transposed_test", "convolution_transposed_thin_test", "depth_wise_conv_3x3_test", diff --git a/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.cc b/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.cc new file mode 100644 index 00000000000..6faaaa88105 --- /dev/null +++ b/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.cc @@ -0,0 +1,316 @@ +/* Copyright 2019 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/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.h" + +#include +#include +#include + +#include "tensorflow/lite/delegates/gpu/cl/kernels/util.h" +#include "tensorflow/lite/delegates/gpu/cl/precision.h" +#include "tensorflow/lite/delegates/gpu/cl/tensor_type.h" + +namespace tflite { +namespace gpu { +namespace cl { +namespace { + +std::string GenerateConvolutionTransposedCode( + const OperationDef& op_def, + const std::vector& linked_operations) { + std::string c = GetCommonDefines(op_def.precision); + + TensorCodeGenerator src_tensor("src_data", + {"src_size.x", "src_size.y", "src_size.z"}, + op_def.src_tensors[0]); + TensorCodeGenerator dst_tensor("dst_data", + {"dst_size.x", "dst_size.y", "dst_size.z"}, + op_def.dst_tensors[0]); + + const auto src_tensor_type = op_def.src_tensors[0].storage_type; + const bool manual_clamp = src_tensor_type == TensorStorageType::BUFFER || + src_tensor_type == TensorStorageType::IMAGE_BUFFER; + + switch (op_def.precision) { + case CalculationsPrecision::F32: + case CalculationsPrecision::F16: + c += "#define CONV(R, SRC, F) \\\n"; + c += " R += SRC.x * weights_cache[F]; \\\n"; + c += " R += SRC.y * weights_cache[F + 1]; \\\n"; + c += " R += SRC.z * weights_cache[F + 2]; \\\n"; + c += " R += SRC.w * weights_cache[F + 3]; \n"; + break; + case CalculationsPrecision::F32_F16: + c += "#define CONV(R, SRC, F) \\\n"; + c += " R += convert_float4(SRC.x * weights_cache[F] + SRC.y * " + "weights_cache[F + 1] + SRC.z * weights_cache[F + 2] + SRC.w * " + "weights_cache[F + 3]);\n"; + break; + } + + const std::string pixel_stride = op_def.batch_support ? "dst_size.w" : "1"; + c += "__attribute__((reqd_work_group_size(8, 4, 1)))\n"; + c += "__kernel void main_function(\n"; + c += src_tensor.GetDeclaration(AccessType::READ) + ",\n"; + c += " __global FLT4* filters,\n"; + c += " __read_only image2d_t biases"; + c += GetArgsDeclaration(linked_operations); + c += dst_tensor.GetDeclaration(AccessType::WRITE) + ",\n"; + c += " int4 src_size, \n"; + c += " int4 dst_size, \n"; + c += " int filter_offset \n"; + c += ") {\n"; + if (op_def.batch_support) { + c += " int linear_id = get_global_id(0);\n"; + c += " int X0 = linear_id / dst_size.w;\n"; + c += " int B = linear_id % dst_size.w;\n"; + } + c += " int X = get_global_id(0);\n"; + c += " int Y = get_global_id(1);\n"; + c += " int Z = get_global_id(2);\n"; + c += " ACCUM_FLT4 r0 = (ACCUM_FLT4)(0.0f);\n"; + c += " ACCUM_FLT4 r1 = (ACCUM_FLT4)(0.0f);\n"; + c += " ACCUM_FLT4 r2 = (ACCUM_FLT4)(0.0f);\n"; + c += " ACCUM_FLT4 r3 = (ACCUM_FLT4)(0.0f);\n"; + c += " int f_offset = Z * filter_offset;\n"; + c += " __local FLT4 weights_cache[64];\n"; + if (manual_clamp) { + const std::string prev_x = "X - " + pixel_stride; + c += " bool in_x0 = " + prev_x + " >= 0 && " + prev_x + " < src_size.x;\n"; + c += " bool in_x1 = X >= 0 && X < src_size.x;\n"; + c += " bool in_y0 = Y - 1 >= 0 && Y - 1 < src_size.y;\n"; + c += " bool in_y1 = Y >= 0 && Y < src_size.y;\n"; + if (src_tensor_type == TensorStorageType::IMAGE_BUFFER) { + c += " int addr_0 = select(-1, (Y - 1) * src_size.x + " + prev_x + + ", (in_x0 && in_y0));\n"; + c += " int addr_1 = select(-1, (Y - 1) * src_size.x + X, (in_x1 && " + "in_y0));\n"; + c += " int addr_2 = select(-1, Y * src_size.x + " + prev_x + + ", (in_x0 && in_y1));\n"; + c += " int addr_3 = select(-1, Y * src_size.x + X, (in_x1 && " + "in_y1));\n"; + c += " int dz_0 = select(0, src_size.x * src_size.y, (in_x0 && " + "in_y0));\n"; + c += " int dz_1 = select(0, src_size.x * src_size.y, (in_x1 && " + "in_y0));\n"; + c += " int dz_2 = select(0, src_size.x * src_size.y, (in_x0 && " + "in_y1));\n"; + c += " int dz_3 = select(0, src_size.x * src_size.y, (in_x1 && " + "in_y1));\n"; + } + if (src_tensor_type == TensorStorageType::BUFFER) { + c += " int xc0 = clamp(" + prev_x + ", 0, src_size.x - 1);\n"; + c += " int xc1 = clamp(X, 0, src_size.x - 1);\n"; + c += " int yc0 = clamp(Y - 1, 0, src_size.y - 1);\n"; + c += " int yc1 = clamp(Y, 0, src_size.y - 1);\n"; + c += " int addr_0 = yc0 * src_size.x + xc0;\n"; + c += " int addr_1 = yc0 * src_size.x + xc1;\n"; + c += " int addr_2 = yc1 * src_size.x + xc0;\n"; + c += " int addr_3 = yc1 * src_size.x + xc1;\n"; + c += " int dz = src_size.x * src_size.y;\n"; + } + } + auto read_src = [&](int x, int y) { + if (manual_clamp) { + const std::string id = std::to_string(y * 2 + x); + const std::string addr = "addr_" + std::to_string(y * 2 + x); + if (src_tensor_type == TensorStorageType::IMAGE_BUFFER) { + return src_tensor.Read(addr) + "; " + addr + " += dz_" + id + ";"; + } else { + return src_tensor.Read(addr) + " * (FLT)(in_x" + std::to_string(x) + + " && in_y" + std::to_string(y) + "); " + addr + " += dz;"; + } + } else { + return src_tensor.Read3D( + "X + " + std::to_string(x - 1) + "*" + pixel_stride, + "Y + " + std::to_string(y - 1), "s", TextureAddressMode::ZERO); + } + }; + c += " for (int s = 0; s < src_size.z; ++s) {\n"; + c += " barrier(CLK_LOCAL_MEM_FENCE);\n"; + c += " async_work_group_copy(weights_cache, filters + f_offset, 64, 0);\n"; + c += " FLT4 src0 = " + read_src(0, 0) + ";\n"; + c += " FLT4 src1 = " + read_src(1, 0) + ";\n"; + c += " FLT4 src2 = " + read_src(0, 1) + ";\n"; + c += " FLT4 src3 = " + read_src(1, 1) + ";\n"; + c += " f_offset += 64;\n"; + c += " barrier(CLK_LOCAL_MEM_FENCE);\n"; + c += " CONV(r0, src0, 0);\n"; + c += " CONV(r1, src0, 4);\n"; + c += " CONV(r2, src0, 8);\n"; + c += " CONV(r3, src0, 12);\n"; + c += " CONV(r0, src1, 16);\n"; + c += " CONV(r1, src1, 20);\n"; + c += " CONV(r2, src1, 24);\n"; + c += " CONV(r3, src1, 28);\n"; + c += " CONV(r0, src2, 32);\n"; + c += " CONV(r1, src2, 36);\n"; + c += " CONV(r2, src2, 40);\n"; + c += " CONV(r3, src2, 44);\n"; + c += " CONV(r0, src3, 48);\n"; + c += " CONV(r1, src3, 52);\n"; + c += " CONV(r2, src3, 56);\n"; + c += " CONV(r3, src3, 60);\n"; + c += " }\n"; + c += "\n"; + if (op_def.batch_support) { + c += " if (X0 * 2 * dst_size.w > dst_size.x || Y * 2 > dst_size.y || Z >= " + "dst_size.z) return;\n"; + } else { + c += " if (X * 2 > dst_size.x || Y * 2 > dst_size.y || Z >= dst_size.z) " + "return;\n"; + } + if (op_def.batch_support) { + c += " X = X0 * 2 * dst_size.w + B - dst_size.w;\n"; + } else { + c += " X = X * 2 - 1;\n"; + } + c += " Y = Y * 2 - 1;\n"; + c += "\n"; + c += " FLT4 bias_val = READ_IMAGE(biases, smp_none, (int2)(Z, 0));\n"; + c += " if (X >= 0 && Y >= 0) {\n"; + c += " FLT4 result = TO_FLT4(r0) + bias_val;\n"; + LinkingContext context{"result", "X", "Y", "Z"}; + c += PostProcess(linked_operations, context); + c += " " + dst_tensor.Write3D("result", "X", "Y", "Z") + "\n"; + c += " }\n"; + c += " if (X + " + pixel_stride + " < dst_size.x && Y >= 0) {\n"; + c += " FLT4 result = TO_FLT4(r1) + bias_val;\n"; + context = {"result", "X + " + pixel_stride, "Y", "Z"}; + c += PostProcess(linked_operations, context); + c += " " + dst_tensor.Write3D("result", "X + " + pixel_stride, "Y", "Z") + + "\n"; + c += " }\n"; + c += " if (X >= 0 && Y + 1 < dst_size.y) {\n"; + c += " FLT4 result = TO_FLT4(r2) + bias_val;\n"; + context = {"result", "X", "Y + 1", "Z"}; + c += PostProcess(linked_operations, context); + c += " " + dst_tensor.Write3D("result", "X", "Y + 1", "Z") + "\n"; + c += " }\n"; + c += " if (X + " + pixel_stride + " < dst_size.x && Y + 1 < dst_size.y) {\n"; + c += " FLT4 result = TO_FLT4(r3) + bias_val;\n"; + context = {"result", "X + " + pixel_stride, "Y + 1", "Z"}; + c += PostProcess(linked_operations, context); + c += " " + + dst_tensor.Write3D("result", "X + " + pixel_stride, "Y + 1", "Z") + "\n"; + c += " }\n"; + c += "}\n"; + return c; +} + +} // namespace + +ConvolutionTransposed4x4::ConvolutionTransposed4x4( + const OperationDef& definition) + : GPUOperation(definition) {} + +ConvolutionTransposed4x4::ConvolutionTransposed4x4( + ConvolutionTransposed4x4&& operation) + : GPUOperation(std::move(operation)), + weights_(std::move(operation.weights_)), + biases_(std::move(operation.biases_)), + kernel_(std::move(operation.kernel_)), + work_group_size_(operation.work_group_size_) {} + +ConvolutionTransposed4x4& ConvolutionTransposed4x4::operator=( + ConvolutionTransposed4x4&& operation) { + if (this != &operation) { + weights_ = std::move(operation.weights_); + biases_ = std::move(operation.biases_); + kernel_ = std::move(operation.kernel_); + std::swap(work_group_size_, operation.work_group_size_); + GPUOperation::operator=(std::move(operation)); + } + return *this; +} + +Status ConvolutionTransposed4x4::Compile( + const CreationContext& creation_context) { + const auto code = + GenerateConvolutionTransposedCode(definition_, linked_operations_); + + std::vector options; + if (definition_.precision == CalculationsPrecision::F16 && + creation_context.device->IsPowerVR()) { + options.push_back(CompilerOptions::POWERVR_FP16); + } + RETURN_IF_ERROR(creation_context.cache->GetOrCreateCLKernel( + code, "main_function", options, *creation_context.context, + *creation_context.device, &kernel_)); + + return OkStatus(); +} + +Status ConvolutionTransposed4x4::BindArguments() { + kernel_.ResetBindingCounter(); + RETURN_IF_ERROR(kernel_.SetMemoryAuto(src_[0]->GetMemoryPtr())); + RETURN_IF_ERROR(kernel_.SetMemoryAuto(weights_.GetMemoryPtr())); + RETURN_IF_ERROR(kernel_.SetMemoryAuto(biases_.GetMemoryPtr())); + RETURN_IF_ERROR(BindArgs(&kernel_, linked_operations_)); + RETURN_IF_ERROR(kernel_.SetMemoryAuto(dst_[0]->GetMemoryPtrForWriting())); + RETURN_IF_ERROR(kernel_.SetBytesAuto(src_[0]->GetWBatchedHDB())); + RETURN_IF_ERROR(kernel_.SetBytesAuto(dst_[0]->GetWBatchedHDB())); + const int32_t filters_offset = 4 * 16 * src_[0]->Depth(); + RETURN_IF_ERROR(kernel_.SetBytesAuto(filters_offset)); + + return OkStatus(); +} + +int3 ConvolutionTransposed4x4::GetGridSize() const { + const int grid_x = + IntegralDivideRoundUp(dst_[0]->Width() + 2, 2) * dst_[0]->Batch(); + const int grid_y = IntegralDivideRoundUp(dst_[0]->Height() + 2, 2); + const int grid_z = dst_[0]->Depth(); + return int3(grid_x, grid_y, grid_z); +} + +Status ConvolutionTransposed4x4::AddToQueue(CLCommandQueue* queue) { + RETURN_IF_ERROR(BindArguments()); + return queue->DispatchImplicit(kernel_, GetGridSize(), work_group_size_); +} + +bool IsConvolutionTransposed4x4Supported( + const CLDevice& device, const OperationDef& definition, + const ConvolutionTransposedAttributes& attr) { + return attr.weights.shape.w == 4 && attr.weights.shape.h == 4 && + attr.stride.w == 2 && attr.stride.h == 2 && + attr.padding.prepended.w == 1 && attr.padding.prepended.h == 1; +} + +Status CreateConvolutionTransposed4x4( + const CreationContext& creation_context, const OperationDef& definition, + const ConvolutionTransposedAttributes& attr, + ConvolutionTransposed4x4* result) { + if (!IsConvolutionTransposed4x4Supported(*creation_context.device, definition, + attr)) { + return InvalidArgumentError( + "ConvolutionTransposed4x4 doesn't support this attributes"); + } + *result = ConvolutionTransposed4x4(definition); + RETURN_IF_ERROR( + result->UploadWeights(attr.weights, creation_context.context)); + LinearStorageCreateInfo create_info; + create_info.storage_type = LinearStorageType::TEXTURE_2D; + create_info.data_type = definition.GetDataType(); + create_info.aligned_size = attr.weights.shape.o; + RETURN_IF_ERROR(CreateLinearStorage( + create_info, attr.bias, creation_context.context, &result->biases_)); + return OkStatus(); +} + +} // namespace cl +} // namespace gpu +} // namespace tflite diff --git a/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.h b/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.h new file mode 100644 index 00000000000..dee0b2d2eb3 --- /dev/null +++ b/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.h @@ -0,0 +1,153 @@ +/* Copyright 2019 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_LITE_DELEGATES_GPU_CL_KERNELS_CONVOLUTION_TRANSPOSED_4X4_H_ +#define TENSORFLOW_LITE_DELEGATES_GPU_CL_KERNELS_CONVOLUTION_TRANSPOSED_4X4_H_ + +#include + +#include "tensorflow/lite/delegates/gpu/cl/buffer.h" +#include "tensorflow/lite/delegates/gpu/cl/kernels/gpu_operation.h" +#include "tensorflow/lite/delegates/gpu/cl/linear_storage.h" +#include "tensorflow/lite/delegates/gpu/cl/tensor.h" +#include "tensorflow/lite/delegates/gpu/cl/util.h" +#include "tensorflow/lite/delegates/gpu/common/data_type.h" +#include "tensorflow/lite/delegates/gpu/common/operations.h" +#include "tensorflow/lite/delegates/gpu/common/shape.h" +#include "tensorflow/lite/delegates/gpu/common/status.h" +#include "tensorflow/lite/delegates/gpu/common/tensor.h" +#include "tensorflow/lite/delegates/gpu/common/types.h" + +namespace tflite { +namespace gpu { +namespace cl { + +class ConvolutionTransposed4x4 : public GPUOperation { + public: + ConvolutionTransposed4x4() = default; + Status AddToQueue(CLCommandQueue* queue) override; + Status Compile(const CreationContext& creation_context) override; + + // Move only + ConvolutionTransposed4x4(ConvolutionTransposed4x4&& operation); + ConvolutionTransposed4x4& operator=(ConvolutionTransposed4x4&& operation); + ConvolutionTransposed4x4(const ConvolutionTransposed4x4&) = delete; + ConvolutionTransposed4x4& operator=(const ConvolutionTransposed4x4&) = delete; + + private: + explicit ConvolutionTransposed4x4(const OperationDef& definition); + friend Status CreateConvolutionTransposed4x4( + const CreationContext& creation_context, const OperationDef& definition, + const ConvolutionTransposedAttributes& attr, + ConvolutionTransposed4x4* result); + template + Status UploadWeights(const ::tflite::gpu::Tensor& weights, + CLContext* context); + + template + void RearrangeWeightsData(const ::tflite::gpu::Tensor& weights, + absl::Span dst); + + Status BindArguments(); + int3 GetGridSize() const; + + Buffer weights_; + LinearStorage biases_; + + CLKernel kernel_; + int3 work_group_size_ = int3(8, 4, 1); +}; + +template +Status ConvolutionTransposed4x4::UploadWeights( + const ::tflite::gpu::Tensor& weights, CLContext* context) { + const int src_depth = IntegralDivideRoundUp(weights.shape.i, 4); + const int dst_depth = IntegralDivideRoundUp(weights.shape.o, 4); + const int kernel_x = 4; // This operation support only 4x4 kernel + const int kernel_y = 4; + const int flt4_count = kernel_x * kernel_y * src_depth * dst_depth * 4; + + const bool f32_weights = definition_.precision == CalculationsPrecision::F32; + const int flt4_size = f32_weights ? sizeof(float4) : sizeof(half4); + + if (f32_weights) { + std::vector gpu_data(flt4_count); + RearrangeWeightsData(weights, absl::MakeSpan(gpu_data)); + return CreateReadOnlyBuffer(flt4_size * flt4_count, gpu_data.data(), + context, &weights_); + } else { + std::vector gpu_data(flt4_count); + RearrangeWeightsData(weights, absl::MakeSpan(gpu_data)); + return CreateReadOnlyBuffer(flt4_size * flt4_count, gpu_data.data(), + context, &weights_); + } +} + +template +void ConvolutionTransposed4x4::RearrangeWeightsData( + const ::tflite::gpu::Tensor& weights, absl::Span dst) { + const int src_depth = IntegralDivideRoundUp(weights.shape.i, 4); + const int dst_depth = IntegralDivideRoundUp(weights.shape.o, 4); + const int kernel_x = 4; + const int kernel_y = 4; + + const int remap[16] = {10, 11, 14, 15, 8, 9, 12, 13, 2, 3, 6, 7, 0, 1, 4, 5}; + + int counter = 0; + for (int d = 0; d < dst_depth; ++d) { + for (int s = 0; s < src_depth; ++s) { + for (int y = 0; y < kernel_y; ++y) { + for (int x = 0; x < kernel_x; ++x) { + const int kernel_index = remap[y * kernel_x + x]; + const int kernel_index_x = kernel_index % kernel_x; + const int kernel_index_y = kernel_index / kernel_x; + T filters[4]; + for (int j = 0; j < 4; ++j) { + for (int i = 0; i < 4; ++i) { + const int s_ch = s * 4 + i; + const int d_ch = d * 4 + j; + if (s_ch < weights.shape.i && d_ch < weights.shape.o) { + const int f_index = weights.shape.LinearIndex( + {d_ch, kernel_index_y, kernel_index_x, s_ch}); + filters[i][j] = weights.data[f_index]; + } else { + filters[i][j] = 0.0f; + } + } + } + dst[counter++] = filters[0]; + dst[counter++] = filters[1]; + dst[counter++] = filters[2]; + dst[counter++] = filters[3]; + } + } + } + } +} + +bool IsConvolutionTransposed4x4Supported( + const CLDevice& device, const OperationDef& definition, + const ConvolutionTransposedAttributes& attr); + +Status CreateConvolutionTransposed4x4( + const CreationContext& creation_context, const OperationDef& definition, + const ConvolutionTransposedAttributes& attr, + ConvolutionTransposed4x4* result); + +} // namespace cl +} // namespace gpu +} // namespace tflite + +#endif // TENSORFLOW_LITE_DELEGATES_GPU_CL_KERNELS_CONVOLUTION_TRANSPOSED_4X4_H_ diff --git a/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4_test.cc b/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4_test.cc new file mode 100644 index 00000000000..1f7feafbedf --- /dev/null +++ b/tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4_test.cc @@ -0,0 +1,74 @@ +/* Copyright 2019 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/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.h" + +#include + +#include +#include +#include "tensorflow/lite/delegates/gpu/cl/kernels/cl_test.h" +#include "tensorflow/lite/delegates/gpu/common/operations.h" +#include "tensorflow/lite/delegates/gpu/common/status.h" + +using ::testing::FloatNear; +using ::testing::Pointwise; + +namespace tflite { +namespace gpu { +namespace cl { +namespace { + +TEST_F(OpenCLOperationTest, ConvolutionTransposed4x4) { + TensorFloat32 src_tensor; + src_tensor.shape = BHWC(1, 2, 2, 1); + src_tensor.data = {0.0f, 1.0f, 2.0f, 3.0f}; + + ConvolutionTransposedAttributes attr; + attr.padding.prepended = HW(1, 1); + attr.padding.appended = HW(0, 0); + attr.stride = HW(2, 2); + attr.weights.shape = OHWI(1, 4, 4, 1); + attr.weights.data = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + attr.bias.shape = Linear(1); + attr.bias.data = {0.0f}; + + for (auto storage : env_.GetSupportedStorages()) { + for (auto precision : env_.GetSupportedPrecisions()) { + const float eps = precision == CalculationsPrecision::F32 ? 1e-6f : 1e-3f; + OperationDef op_def; + op_def.precision = precision; + auto data_type = DeduceDataTypeFromPrecision(precision); + op_def.src_tensors.push_back({data_type, storage}); + op_def.dst_tensors.push_back({data_type, storage}); + TensorFloat32 dst_tensor; + ConvolutionTransposed4x4 operation; + ASSERT_OK(CreateConvolutionTransposed4x4(creation_context_, op_def, attr, + &operation)); + ASSERT_OK(ExecuteGPUOperation(src_tensor, creation_context_, &operation, + BHWC(1, 4, 4, 1), &dst_tensor)); + EXPECT_THAT(dst_tensor.data, + Pointwise(FloatNear(eps), + {0.0f, 1.0f, 1.0f, 1.0f, 2.0f, 6.0f, 6.0f, 4.0f, + 2.0f, 6.0f, 6.0f, 4.0f, 2.0f, 5.0f, 5.0f, 3.0f})); + } + } +} + +} // namespace +} // namespace cl +} // namespace gpu +} // namespace tflite diff --git a/tensorflow/lite/delegates/gpu/cl/selectors/BUILD b/tensorflow/lite/delegates/gpu/cl/selectors/BUILD index 012ef0eb55b..378679cc603 100644 --- a/tensorflow/lite/delegates/gpu/cl/selectors/BUILD +++ b/tensorflow/lite/delegates/gpu/cl/selectors/BUILD @@ -33,6 +33,7 @@ cc_library( "//tensorflow/lite/delegates/gpu/cl:tensor_type", "//tensorflow/lite/delegates/gpu/cl/kernels:convolution_transposed", "//tensorflow/lite/delegates/gpu/cl/kernels:convolution_transposed_3x3_thin", + "//tensorflow/lite/delegates/gpu/cl/kernels:convolution_transposed_4x4", "//tensorflow/lite/delegates/gpu/cl/kernels:convolution_transposed_thin", "//tensorflow/lite/delegates/gpu/cl/kernels:gpu_operation", "//tensorflow/lite/delegates/gpu/common:operations", diff --git a/tensorflow/lite/delegates/gpu/cl/selectors/convolution_transposed_selector.cc b/tensorflow/lite/delegates/gpu/cl/selectors/convolution_transposed_selector.cc index b782ab701ea..5f106a528ec 100644 --- a/tensorflow/lite/delegates/gpu/cl/selectors/convolution_transposed_selector.cc +++ b/tensorflow/lite/delegates/gpu/cl/selectors/convolution_transposed_selector.cc @@ -18,6 +18,7 @@ limitations under the License. #include "absl/memory/memory.h" #include "tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed.h" #include "tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_3x3_thin.h" +#include "tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_4x4.h" #include "tensorflow/lite/delegates/gpu/cl/kernels/convolution_transposed_thin.h" #include "tensorflow/lite/delegates/gpu/cl/tensor_type.h" @@ -65,6 +66,12 @@ Status SelectConvolutionTransposedPowerVR( RETURN_IF_ERROR(CreateConvolutionTransposed3x3Thin(creation_context, op_def, attr, &conv)); *ptr = absl::make_unique(std::move(conv)); + } else if (IsConvolutionTransposed4x4Supported(*creation_context.device, + op_def, attr)) { + ConvolutionTransposed4x4 conv; + RETURN_IF_ERROR( + CreateConvolutionTransposed4x4(creation_context, op_def, attr, &conv)); + *ptr = absl::make_unique(std::move(conv)); } else { ConvolutionTransposed conv; RETURN_IF_ERROR( @@ -95,6 +102,7 @@ Status SelectConvolutionTransposed(const ConvolutionTransposedAttributes& attr, return SelectConvolutionTransposedAdreno(attr, creation_context, op_def, ptr); case Vendor::POWERVR: + case Vendor::NVIDIA: return SelectConvolutionTransposedPowerVR(attr, creation_context, op_def, ptr); case Vendor::MALI: diff --git a/tensorflow/lite/delegates/gpu/common/model_builder.cc b/tensorflow/lite/delegates/gpu/common/model_builder.cc index 8e33c4eeb75..4e5ee940841 100644 --- a/tensorflow/lite/delegates/gpu/common/model_builder.cc +++ b/tensorflow/lite/delegates/gpu/common/model_builder.cc @@ -1211,6 +1211,7 @@ class FullyConnectedOperationParser : public TFLiteOperationParser { auto& reshape = node; conv = graph->NewNode(); // reset conv pointer! Value>* reshaped_value = graph->NewValue(); + reshaped_value->tensor.type = DataType::FLOAT32; reshaped_value->tensor.shape = BHWC(1, 1, 1, weights.shape.w); RETURN_IF_ERROR(graph->SetProducer(reshape->id, reshaped_value->id)); reshape->operation.type = ToString(OperationType::RESHAPE); @@ -2350,7 +2351,6 @@ class UnsupportedOperationParser : public TFLiteOperationParser { std::unique_ptr NewOperationParser( const TfLiteRegistration* registration) { const auto builtin_code = registration->builtin_code; - const absl::string_view custom_name = registration->custom_name; switch (builtin_code) { case kTfLiteBuiltinAbs: return absl::make_unique(OperationType::ABS); @@ -2428,6 +2428,7 @@ std::unique_ptr NewOperationParser( return absl::make_unique(); case kTfLiteBuiltinCustom: + const absl::string_view custom_name = registration->custom_name; if (custom_name == "Convolution2DTransposeBias") { return absl::make_unique(); } diff --git a/tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.cc b/tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.cc index 3fe13a14c48..417b51f9e80 100644 --- a/tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.cc +++ b/tensorflow/lite/delegates/gpu/gl/compiler/object_accessor.cc @@ -300,16 +300,23 @@ std::string ToBufferType(DataType data_type) { case DataType::UINT16: case DataType::UINT32: return "uvec4"; + case DataType::UINT64: + return "u64vec4_not_available_in_glsl"; case DataType::INT8: case DataType::INT16: case DataType::INT32: return "ivec4"; + case DataType::INT64: + return "i64vec4_not_available_in_glsl"; case DataType::FLOAT16: return "uvec2"; case DataType::FLOAT32: return "vec4"; - default: - return "unknown"; + case DataType::FLOAT64: + return "dvec4"; + case DataType::UNKNOWN: + return "unknown_buffer_type"; + // Do NOT add `default:'; we want build failure for new enum values. } } @@ -331,7 +338,7 @@ struct TextureImageTypeGetter { case DataType::FLOAT32: return "image2D"; default: - return "unknown"; + return "unknown_image_2d"; } } @@ -347,7 +354,7 @@ struct TextureImageTypeGetter { case DataType::FLOAT32: return "image2DArray"; default: - return "unknown"; + return "unknown_image_2d_array"; } } @@ -418,7 +425,7 @@ std::string ToImageLayoutQualifier(DataType type) { case DataType::FLOAT32: return "rgba32f"; default: - return "unknown"; + return "unknown_image_layout"; } } @@ -433,7 +440,7 @@ std::string ToImagePrecision(DataType type) { case DataType::FLOAT32: return "highp"; default: - return "unknown"; + return "unknown_image_precision"; } } diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/prelu.cc b/tensorflow/lite/delegates/gpu/gl/kernels/prelu.cc index 80df527ffa4..88078935ee2 100644 --- a/tensorflow/lite/delegates/gpu/gl/kernels/prelu.cc +++ b/tensorflow/lite/delegates/gpu/gl/kernels/prelu.cc @@ -102,7 +102,8 @@ class PReLUFull : public NodeShader { auto shape = output->tensor.shape; - ObjectSize obj_size = uint3(shape.h, shape.w, shape.c); + ObjectSize obj_size = + uint3(shape.w, shape.h, IntegralDivideRoundUp(shape.c, 4)); *generated_code = attr.clip diff --git a/tensorflow/lite/delegates/gpu/gl/kernels/prelu_test.cc b/tensorflow/lite/delegates/gpu/gl/kernels/prelu_test.cc index d8ccd410751..dc6a0fad60d 100644 --- a/tensorflow/lite/delegates/gpu/gl/kernels/prelu_test.cc +++ b/tensorflow/lite/delegates/gpu/gl/kernels/prelu_test.cc @@ -80,7 +80,7 @@ TEST(PReluTest, LinearAlphaWithClip) { EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {-2, -4, 1, 1})); } -TEST(PReluTest, 3DAlphaNoClip) { +TEST(PReluTest, 2DAlphaNoClip) { TensorRef input; input.type = DataType::FLOAT32; input.ref = 0; @@ -106,7 +106,7 @@ TEST(PReluTest, 3DAlphaNoClip) { EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {0, -2, 2, -6})); } -TEST(PReluTest, 3DAlphaWithClip) { +TEST(PReluTest, 2DAlphaWithClip) { TensorRef input; input.type = DataType::FLOAT32; input.ref = 0; @@ -132,6 +132,60 @@ TEST(PReluTest, 3DAlphaWithClip) { EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {0, -2, 1, -6})); } +TEST(PReluTest, 2DAlphaWidthNotEqualHeight) { + TensorRef input; + input.type = DataType::FLOAT32; + input.ref = 0; + input.shape = BHWC(1, 2, 1, 1); + + OperationType op_type = OperationType::PRELU; + PReLUAttributes attr; + attr.clip = 0; + Tensor alpha; + alpha.shape = HWC(2, 1, 1); + alpha.id = 1; + alpha.data = {1, 1}; + attr.alpha = std::move(alpha); + + TensorRef output; + output.type = DataType::FLOAT32; + output.ref = 2; + output.shape = BHWC(1, 2, 1, 1); + + SingleOpModel model({ToString(op_type), attr}, {input}, {output}); + ASSERT_TRUE(model.PopulateTensor(0, {-1.0, -1.0})); + ASSERT_OK(model.Invoke(*NewPReLUNodeShader())); + EXPECT_THAT(model.GetOutput(0), Pointwise(FloatNear(1e-6), {-1, -1})); +} + +TEST(PReluTest, 3DAlphaNoClip) { + TensorRef input; + input.type = DataType::FLOAT32; + input.ref = 0; + input.shape = BHWC(1, 2, 2, 2); + + OperationType op_type = OperationType::PRELU; + PReLUAttributes attr; + attr.clip = 0; + Tensor alpha; + alpha.shape = HWC(2, 2, 2); + alpha.id = 1; + alpha.data = {1, 1, 2, 2, 2, 2, 2, 2}; + attr.alpha = std::move(alpha); + + TensorRef output; + output.type = DataType::FLOAT32; + output.ref = 2; + output.shape = BHWC(1, 2, 2, 2); + + SingleOpModel model({ToString(op_type), attr}, {input}, {output}); + ASSERT_TRUE( + model.PopulateTensor(0, {0.0, 0.0, -1.0, -1.0, 2.0, 2.0, -3.0, -3.0})); + ASSERT_OK(model.Invoke(*NewPReLUNodeShader())); + EXPECT_THAT(model.GetOutput(0), + Pointwise(FloatNear(1e-6), {0, 0, -2, -2, 2, 2, -6, -6})); +} + } // namespace } // namespace gl } // namespace gpu diff --git a/tensorflow/lite/delegates/gpu/metal/compiled_model.cc b/tensorflow/lite/delegates/gpu/metal/compiled_model.cc index 501c272e053..ed673233dda 100644 --- a/tensorflow/lite/delegates/gpu/metal/compiled_model.cc +++ b/tensorflow/lite/delegates/gpu/metal/compiled_model.cc @@ -107,22 +107,36 @@ bool Contains( return true; } +uint32_t BufferUseCount(ValueId id, + const std::list& descriptors, + std::list* chains) { + uint32_t use_count = 0; + // Buffer may be read by both processed and not processed operations. + for (auto& desc : descriptors) { + if (Contains(desc->input_buffers, id)) { + use_count++; + } + } + + for (auto& chain : *chains) { + if (Contains(chain.begin()->get()->input_buffers, id)) { + use_count++; + } + } + return use_count; +} + // Examines if the second operation can be linked to the first one. Linking may // be skipped in the situation when conflic may happen: if first operation's // output is used by more than 1 other operation. bool CanFuseOperations(const ComputeTaskDescriptorPtr first, const ComputeTaskDescriptorPtr second, const std::vector& output_ids, - const std::list& descriptors) { - int use_count = 0; - if (second->is_linkable && !Contains(output_ids, first->output_buffer.id)) { - for (auto& desc : descriptors) { - if (Contains(desc->input_buffers, first->output_buffer.id)) { - use_count++; - } - } - } - return (use_count == 1); + const std::list& descriptors, + std::list* chains) { + return second->is_linkable && + !Contains(output_ids, first->output_buffer.id) && + BufferUseCount(first->output_buffer.id, descriptors, chains) == 1; } // Takes an unsorted list of task descriptors, builds a list of chains. Each @@ -166,7 +180,7 @@ void BuildFusableChains(const std::vector& input_ids, if (Contains(task_descriptor->input_buffers, chain.back()->output_buffer.id)) { if (CanFuseOperations(chain.back(), task_descriptor, output_ids, - *descriptors)) { + *descriptors, chains)) { chain.push_back(task_descriptor); } else { // Start new chain. diff --git a/tensorflow/lite/delegates/nnapi/BUILD b/tensorflow/lite/delegates/nnapi/BUILD index 6e48b214d66..94c48f80313 100644 --- a/tensorflow/lite/delegates/nnapi/BUILD +++ b/tensorflow/lite/delegates/nnapi/BUILD @@ -103,6 +103,7 @@ cc_library( }), deps = [ ":nnapi_delegate", + "//tensorflow/lite/nnapi:nnapi_handler", "//tensorflow/lite/nnapi:nnapi_implementation", "@com_google_absl//absl/memory", "@com_google_googletest//:gtest", @@ -121,7 +122,6 @@ cc_test( ], deps = [ ":nnapi_delegate", - ":nnapi_delegate_mock_test", "//tensorflow/lite:framework", "//tensorflow/lite:minimal_logging", "//tensorflow/lite/c:common", diff --git a/tensorflow/lite/delegates/nnapi/nnapi_delegate_mock_test.h b/tensorflow/lite/delegates/nnapi/nnapi_delegate_mock_test.h index 6e5e2098f42..4a48409de1e 100644 --- a/tensorflow/lite/delegates/nnapi/nnapi_delegate_mock_test.h +++ b/tensorflow/lite/delegates/nnapi/nnapi_delegate_mock_test.h @@ -28,134 +28,17 @@ limitations under the License. #include #include "absl/memory/memory.h" #include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h" +#include "tensorflow/lite/nnapi/nnapi_handler.h" #include "tensorflow/lite/nnapi/nnapi_implementation.h" namespace tflite { namespace delegate { namespace nnapi { -class NnApiMock { +class NnApiMock : public ::tflite::nnapi::NnApiHandler { public: - template - void GetDeviceCountReturns() { - nnapi_->ANeuralNetworks_getDeviceCount = [](uint32_t* numDevices) -> int { - *numDevices = 2; - return Value; - }; - } - - template - void ModelCreateReturns() { - nnapi_->ANeuralNetworksModel_create = [](ANeuralNetworksModel** model) { - *model = reinterpret_cast(1); - return Value; - }; - } - - template - void AddOperandReturns() { - nnapi_->ANeuralNetworksModel_addOperand = - [](ANeuralNetworksModel* model, - const ANeuralNetworksOperandType* type) { return Value; }; - } - - template - void SetOperandValueReturns() { - nnapi_->ANeuralNetworksModel_setOperandValue = - [](ANeuralNetworksModel* model, int32_t index, const void* buffer, - size_t length) { return Value; }; - } - - template - void AddOperationReturns() { - nnapi_->ANeuralNetworksModel_addOperation = - [](ANeuralNetworksModel* model, ANeuralNetworksOperationType type, - uint32_t inputCount, const uint32_t* inputs, uint32_t outputCount, - const uint32_t* outputs) { return Value; }; - } - - template - void IdentifyInputAndOutputsReturns() { - nnapi_->ANeuralNetworksModel_identifyInputsAndOutputs = - [](ANeuralNetworksModel* model, uint32_t inputCount, - const uint32_t* inputs, uint32_t outputCount, - const uint32_t* outputs) { return Value; }; - } - - template - void RelaxComputationFloatReturns() { - nnapi_->ANeuralNetworksModel_relaxComputationFloat32toFloat16 = - [](ANeuralNetworksModel* model, bool allow) { return Value; }; - } - - template - void ModelFinishReturns() { - nnapi_->ANeuralNetworksModel_finish = [](ANeuralNetworksModel* model) { - return Value; - }; - } - - template - void MemoryCreateFromFdReturns() { - nnapi_->ANeuralNetworksMemory_createFromFd = - [](size_t size, int protect, int fd, size_t offset, - ANeuralNetworksMemory** memory) { - *memory = reinterpret_cast(2); - return Value; - }; - } - - template - void CompilationCreateReturns() { - nnapi_->ANeuralNetworksCompilation_create = - [](ANeuralNetworksModel* model, - ANeuralNetworksCompilation** compilation) { - *compilation = reinterpret_cast(3); - return Value; - }; - } - - template - void CompilationFinishReturns() { - nnapi_->ANeuralNetworksCompilation_finish = - [](ANeuralNetworksCompilation* compilation) { return Value; }; - } - - template - void ExecutionCreateReturns() { - nnapi_->ANeuralNetworksExecution_create = - [](ANeuralNetworksCompilation* compilation, - ANeuralNetworksExecution** execution) { - if (compilation == nullptr) return 1; - *execution = reinterpret_cast(4); - return Value; - }; - } - template - void ExecutionSetInputFromMemoryReturns() { - nnapi_->ANeuralNetworksExecution_setInputFromMemory = - [](ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, - const ANeuralNetworksMemory* memory, size_t offset, - size_t length) { return Value; }; - } - template - void ExecutionSetOutputFromMemoryReturns() { - nnapi_->ANeuralNetworksExecution_setOutputFromMemory = - [](ANeuralNetworksExecution* execution, int32_t index, - const ANeuralNetworksOperandType* type, - const ANeuralNetworksMemory* memory, size_t offset, - size_t length) { return Value; }; - } - - template - void ExecutionComputeReturns() { - nnapi_->ANeuralNetworksExecution_compute = - [](ANeuralNetworksExecution* execution) { return Value; }; - } - explicit NnApiMock(NnApi* nnapi, int android_sdk_version = 29) - : nnapi_(nnapi), prev_nnapi_(*nnapi) { + : ::tflite::nnapi::NnApiHandler(nnapi) { nnapi_->nnapi_exists = true; nnapi_->android_sdk_version = android_sdk_version; @@ -186,14 +69,7 @@ class NnApiMock { ExecutionComputeReturns<0>(); } - ~NnApiMock() { - // Restores global NNAPI to original value for non mocked tests - *nnapi_ = prev_nnapi_; - } - - private: - NnApi* nnapi_; - NnApi prev_nnapi_; + ~NnApiMock() { Reset(); } }; class NnApiDelegateMockTest : public ::testing::Test { diff --git a/tensorflow/lite/examples/experimental_new_converter/BUILD b/tensorflow/lite/examples/experimental_new_converter/BUILD index f5c225e7718..1c6fc7b52aa 100644 --- a/tensorflow/lite/examples/experimental_new_converter/BUILD +++ b/tensorflow/lite/examples/experimental_new_converter/BUILD @@ -6,6 +6,6 @@ package( py_binary( name = "stack_trace_example", srcs = ["stack_trace_example.py"], - python_version = "PY2", + python_version = "PY3", deps = ["//tensorflow:tensorflow_py"], ) diff --git a/tensorflow/lite/experimental/delegates/hexagon/BUILD b/tensorflow/lite/experimental/delegates/hexagon/BUILD new file mode 100644 index 00000000000..d24c790531c --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/BUILD @@ -0,0 +1,110 @@ +# Copyright 2019 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. +# ============================================================================== + +load("//tensorflow/lite:build_def.bzl", "tflite_copts", "tflite_linkopts") + +package( + default_visibility = [ + "//visibility:public", + ], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "hexagon_implementation", + srcs = ["hexagon_implementation.cc"], + hdrs = [ + "hexagon_implementation.h", + "hexagon_nn_interface.h", + ], + tags = [ + "manual", + "nobuilder", + ], + deps = [ + "//tensorflow/lite:minimal_logging", + "//tensorflow/lite/experimental/delegates/hexagon/hexagon_nn:hexagon_nn_header", + "//tensorflow/lite/kernels/internal:compatibility", + ], +) + +cc_library( + name = "hexagon_delegate_kernel", + srcs = [ + "hexagon_delegate.h", + "hexagon_delegate_kernel.cc", + ], + hdrs = ["hexagon_delegate_kernel.h"], + tags = [ + "manual", + "nobuilder", + ], + deps = [ + ":hexagon_implementation", + ":utils", + "//tensorflow/lite:kernel_api", + "//tensorflow/lite/c:common", + "//tensorflow/lite/experimental/delegates/hexagon/builders:op_builder", + "//tensorflow/lite/experimental/delegates/hexagon/hexagon_nn:hexagon_nn_header", + "//tensorflow/lite/schema:schema_fbs", + "@hexagon_nn//:hexagon_nn_ops", + ], +) + +cc_library( + name = "hexagon_delegate", + srcs = ["hexagon_delegate.cc"], + hdrs = ["hexagon_delegate.h"], + tags = [ + "manual", + "nobuilder", + ], + deps = [ + ":hexagon_delegate_kernel", + ":hexagon_implementation", + ":utils", + "//tensorflow/lite:kernel_api", + "//tensorflow/lite/c:common", + ], +) + +cc_library( + name = "utils", + srcs = ["utils.cc"], + hdrs = ["utils.h"], + copts = tflite_copts(), + tags = [ + "manual", + "nobuilder", + ], + deps = [ + "//tensorflow/lite:kernel_api", + "//tensorflow/lite/c:common", + "//tensorflow/lite/kernels:kernel_util", + ], +) + +cc_test( + name = "utils_test", + srcs = ["utils_test.cc"], + linkopts = tflite_linkopts() + ["-lm"], + deps = [ + ":utils", + "//tensorflow/lite/c:common", + "@com_google_googletest//:gtest_main", + ], +) + +exports_files(["version_script.lds"]) diff --git a/tensorflow/lite/experimental/delegates/hexagon/README.md b/tensorflow/lite/experimental/delegates/hexagon/README.md new file mode 100644 index 00000000000..f2336410e81 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/README.md @@ -0,0 +1,99 @@ +# Hexagon Delegate + +Experimental delegate which uses Hexagon SDK to delegate the processing +to QC DSP. +Note that we only support quantized models, since the DSP is efficient +with quantized versions. So all op support is for quantized versions. + +Usage: + +- Add dependency on hexagon_delegate rule. + +- Code change example: + +``` + #include "tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.h" + + // Assuming shared libraries are under "/data/local/tmp/" + // If files are packaged with native lib in android App then it + // will typically be equivalent to the path provided by + // "getContext().getApplicationInfo().nativeLibraryDir" + const char[] library_directory_path = "/data/local/tmp/"; + TfLiteHexagonInitWithPath(library_directory_path); // Needed once at startup. + ::tflite::TfLiteHexagonDelegateOptions params = {0}; + // 'delegate_ptr' Need to outlive the interpreter. For example, + // If use case will need to resize input or anything that can trigger + // re-applying delegates then 'delegate_ptr' need to outlive the interpreter. + auto* delegate_ptr = ::tflite::TfLiteHexagonDelegateCreate(¶ms); + Interpreter::TfLiteDelegatePtr delegate(delegate_ptr, + [](TfLiteDelegate* delegate) { + ::tflite::TfLiteHexagonDelegateDelete(delegate); + }); + interpreter->ModifyGraphWithDelegate(delegate.get()); + TfLiteHexagonTearDown(); // Needed once at end of app/DSP usage. +``` + +* Shared libraries: + - 'libhexagon_interface.so' which holds the interface that the delegate uses. + It must be available if you linked the hexagon_delegate library to TFLite. + You can load it either from shell by overriding + LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"path to the so", + or add it inside your apk in a way it is available. + - 'libhexagon_nn_skel(_v65/_v66).so' which holds the DSP code. + Use TfLiteHexagonInitWithPath(..) and provide the path to the directory + which holds the shared libraries for the Hexagon NN on device. + If you're using TfLiteHexagonInit() then + You will need to set environment variable "ADSP_LIBRARY_PATH" to + "path_to_the_lib";/system/lib/rfsa/adsp;/system/vendor/lib/rfsa/adsp;/dsp + Note that separator here is ';' not ':' + You can push all 3 files, and the library will pick the one needed based + on the runtime. Or if you are sure of what you will use on the device then + push only one of them. + + + +## Supported Ops + +Hexagon only supports ops that have inputs/outputs of <= 4 dimensions. +The following operations have been implemented, with a few constraints that +are verified in `IsNodeSupportedByHexagon`: + +* Add +* ArgMax +* ArgMin +* AveragePool2D: + * Constraints: + - No Activation +* Concat +* Conv2D: + * Constraints: + - stride width/height <= 3 +* DepthwiseConv2D: + * Constraints: + - Filter width == 3 + - depth_multiplier == 1 + - dilation only supported when stride == 1 + - Otherwise, stride height/width <= 3 +* FullyConnected (without any activation) +* L2Normalization (without any activation) +* Logistic (aka Sigmoid) +* MaxPool2D (without any activation) (b/129276536) +* Mul (without any activation) (b/129276536) +* Neg +* Pad: Only supports 0 padding (b/139277813) +* Relu +* Relu6 +* Reshape +* Resize Bilinear: + * Constraints: + - Requested size <= 65 (b/143105433) +* Resize Nearest Neighbor +* SoftMax +* Split +* Sub +* Tanh +* Transpose +* TransposeConv2D: + * Constraints: + - stride height/width <= 3 + - dilation height/width == 1 diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/BUILD b/tensorflow/lite/experimental/delegates/hexagon/builders/BUILD new file mode 100644 index 00000000000..dfc0b522551 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/BUILD @@ -0,0 +1,78 @@ +package( + default_visibility = [ + "//visibility:public", + ], + licenses = ["notice"], # Apache 2.0 +) + +cc_library( + name = "op_builder", + srcs = [ + "activation_builder.cc", + "arg_min_max_builder.cc", + "arithmetic_builder.cc", + "concat_builder.cc", + "conv_2d_builder.cc", + "l2_normalization_builder.cc", + "matmul_builder.cc", + "neg_op_builder.cc", + "op_builder.cc", + "pad_builder.cc", + "pool_2d_builder.cc", + "reduce_builder.cc", + "reshape_builder.cc", + "resize_bilinear_builder.cc", + "resize_nearest_neighbor_builder.cc", + "softmax_builder.cc", + "split_builder.cc", + "transpose_builder.cc", + "transpose_conv_2d_builder.cc", + ], + hdrs = [ + "activation_builder.h", + "arg_min_max_builder.h", + "arithmetic_builder.h", + "concat_builder.h", + "conv_2d_builder.h", + "l2_normalization_builder.h", + "matmul_builder.h", + "neg_op_builder.h", + "op_builder.h", + "pad_builder.h", + "pool_2d_builder.h", + "reduce_builder.h", + "reshape_builder.h", + "resize_bilinear_builder.h", + "resize_nearest_neighbor_builder.h", + "softmax_builder.h", + "split_builder.h", + "transpose_builder.h", + "transpose_conv_2d_builder.h", + ], + tags = [ + "manual", + "nobuilder", + ], + deps = [ + ":op_factory", + "//tensorflow/lite:kernel_api", + "//tensorflow/lite/c:common", + "//tensorflow/lite/experimental/delegates/hexagon:hexagon_implementation", + "//tensorflow/lite/experimental/delegates/hexagon/hexagon_nn:hexagon_nn_header", + "//tensorflow/lite/kernels:kernel_util", + "//tensorflow/lite/kernels:padding", + "//tensorflow/lite/kernels/internal:optimized_base", + "@hexagon_nn//:hexagon_nn_ops", + ], +) + +cc_library( + name = "op_factory", + hdrs = ["op_factory.h"], + tags = [ + "manual", + "nobuilder", + ], + deps = [ + ], +) diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/activation_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/activation_builder.cc new file mode 100644 index 00000000000..d152f17351c --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/activation_builder.cc @@ -0,0 +1,87 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/activation_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus ActivationOpBuilder::PopulateSubGraph( + const TfLiteIntArray* inputs, const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int scalar_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max()); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + scalar_shape, reinterpret_cast(&input_min_), sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + scalar_shape, reinterpret_cast(&input_max_), sizeof(input_max_)); + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + if (op_node_.op_type == OP_QuantizedReluX_8) { + auto* relu_value_const = graph_builder_->AddConstNodeWithData( + scalar_shape, reinterpret_cast(&relu_value_), + sizeof(relu_value_)); + AddInput(TensorID(relu_value_const->GetID(), 0)); + } + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus ActivationOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + + return kTfLiteOk; +} + +ActivationOpBuilder::~ActivationOpBuilder() {} + +OpBuilder* CreateActivationBuilder(GraphBuilder* graph_builder, int op_type) { + return new ActivationOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/activation_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/activation_builder.h new file mode 100644 index 00000000000..3329a855990 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/activation_builder.h @@ -0,0 +1,52 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ACTIVATION_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ACTIVATION_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ActivationOpBuilder : public OpBuilder { + public: + explicit ActivationOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + explicit ActivationOpBuilder(GraphBuilder* graph_builder, int op_type, + int relu_value) + : OpBuilder(graph_builder, op_type), relu_value_(relu_value) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ActivationOpBuilder() override; + + private: + TensorID node_output_; + float input_min_, input_max_; + float relu_value_ = 6; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ACTIVATION_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/arg_min_max_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/arg_min_max_builder.cc new file mode 100644 index 00000000000..b7923905673 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/arg_min_max_builder.cc @@ -0,0 +1,98 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/arg_min_max_builder.h" + +#include + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus ArgMinMaxOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + + if (inputs->size != 2) { + context->ReportError(context, "Expecting 2 inputs %d != 2\n", inputs->size); + return kTfLiteError; + } + + // Input data tensor. + int input_tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[input_tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(input_tensor_id)); + + // Axis tensor. + const int axis_tensor_id = inputs->data[1]; + const auto& axis = context->tensors[axis_tensor_id]; + if (axis.allocation_type != kTfLiteMmapRo) { + context->ReportError(context, + "Axis tensor doesn't have correct allocation type: %s", + axis.name); + return kTfLiteError; + } + + int axis_value = axis.data.i32[0]; + if (axis_value < 0) { + axis_value += input_tensor.dims->size; + } + auto* input_axis_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&axis_value), sizeof(int32_t)); + AddInput(TensorID(input_axis_const->GetID(), 0)); + + // Compute Min/Max + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Output Node + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + + return kTfLiteOk; +} + +TfLiteStatus ArgMinMaxOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +ArgMinMaxOpBuilder::~ArgMinMaxOpBuilder() {} + +OpBuilder* CreateArgMinMaxOpBuilder(GraphBuilder* graph_builder, int op_type) { + return new ArgMinMaxOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/arg_min_max_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/arg_min_max_builder.h new file mode 100644 index 00000000000..7859e637559 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/arg_min_max_builder.h @@ -0,0 +1,46 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ARG_MIN_MAX_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ARG_MIN_MAX_BUILDER_H_ + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ArgMinMaxOpBuilder : public OpBuilder { + public: + explicit ArgMinMaxOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ArgMinMaxOpBuilder() override; + + private: + TensorID node_output_; + float input_min_, input_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ARG_MIN_MAX_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/arithmetic_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/arithmetic_builder.cc new file mode 100644 index 00000000000..44c262bba80 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/arithmetic_builder.cc @@ -0,0 +1,127 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/arithmetic_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus ArithmeticOpBuilder::PopulateSubGraph( + const TfLiteIntArray* inputs, const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // First input data tensor. + tensor_id = inputs->data[0]; + const auto& input1_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input1_tensor, &input1_min_, &input1_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input1_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input1_min_), + sizeof(input1_min_)); + auto* input1_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input1_max_), + sizeof(input1_max_)); + + // Second input data tensor. + tensor_id = inputs->data[1]; + const auto& input2_tensor = context->tensors[tensor_id]; + // TODO(karimnosseir): Have this as util to generalize to all ops. + if (input2_tensor.allocation_type == kTfLiteMmapRo) { + auto* const_input_node = + graph_builder_->AddConstNodeWithData(tensor_id, input2_tensor); + graph_builder_->AddTensorWithID(tensor_id, const_input_node->GetID(), 0); + } + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input2_tensor, &input2_min_, &input2_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input2_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input2_min_), + sizeof(input2_min_)); + auto* input2_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input2_max_), + sizeof(input2_max_)); + + // Min/max values for input tensors. + AddInput(TensorID(input1_min_const->GetID(), 0)); + AddInput(TensorID(input1_max_const->GetID(), 0)); + AddInput(TensorID(input2_min_const->GetID(), 0)); + AddInput(TensorID(input2_max_const->GetID(), 0)); + + // Output min/max as inputs, only if it's an Add node. + if (op_node_.op_type == OP_QuantizedAdd_8p8to8) { + output_min_ = 0; + output_max_ = 0; + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + context->tensors[outputs->data[0]], &output_min_, &output_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + if (output_max_ != 0) { + auto* output_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&output_min_), + sizeof(output_min_)); + auto* output_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&output_max_), + sizeof(output_max_)); + AddInput(TensorID(output_min_const->GetID(), 0)); + AddInput(TensorID(output_max_const->GetID(), 0)); + } + } + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus ArithmeticOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +ArithmeticOpBuilder::~ArithmeticOpBuilder() {} + +OpBuilder* CreateArithmeticBuilder(GraphBuilder* graph_builder, int op_type) { + return new ArithmeticOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/arithmetic_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/arithmetic_builder.h new file mode 100644 index 00000000000..af9b5dc815d --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/arithmetic_builder.h @@ -0,0 +1,49 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ARITHMETIC_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ARITHMETIC_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ArithmeticOpBuilder : public OpBuilder { + public: + explicit ArithmeticOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ArithmeticOpBuilder() override; + + private: + TensorID node_output_; + float input1_min_, input1_max_, input2_min_, input2_max_, output_min_, + output_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_ARITHMETIC_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/concat_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/concat_builder.cc new file mode 100644 index 00000000000..d8929538fda --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/concat_builder.cc @@ -0,0 +1,133 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/concat_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus ConcatOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + + // Only axis 3 is supported. + const TfLiteConcatenationParams* concat_params = + reinterpret_cast(builtin_data_); + auto* axis_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, (char*)&concat_params->axis, + sizeof(concat_params->axis)); + AddInput(TensorID(axis_const->GetID(), 0)); + + int tensor_id; + + // Input data tensors. + input_minima_.reserve(inputs->size); + input_maxima_.reserve(inputs->size); + for (int i = 0; i < inputs->size; ++i) { + tensor_id = inputs->data[i]; + float data_min, data_max; + const auto& data_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + data_tensor, &data_min, &data_max, std::numeric_limits::min(), + std::numeric_limits::max())); + input_minima_.push_back(data_min); + input_maxima_.push_back(data_max); + } + + // Minima tensors. + for (int i = 0; i < input_minima_.size(); ++i) { + auto* data_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_minima_[i]), + sizeof(input_minima_[i])); + AddInput(TensorID(data_min_const->GetID(), 0)); + } + + // Maxima tensors. + for (int i = 0; i < input_minima_.size(); ++i) { + auto* data_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_maxima_[i]), + sizeof(input_maxima_[i])); + AddInput(TensorID(data_max_const->GetID(), 0)); + } + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + + // We requantize the output from concat to the range expected by TFLite. + // Otherwise, we see accuracy issues for cases where the inputs have different + // min/max bounds. + TensorID concat_out = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + const auto& concat_out_min = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + const auto& concat_out_max = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + // Output min/max for requantization. + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + context->tensors[outputs->data[0]], &output_min_, &output_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* output_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, (char*)&output_min_, sizeof(output_min_)); + auto* output_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, (char*)&output_max_, sizeof(output_max_)); + + auto* requantize_op = graph_builder_->AddNode(); + requantize_op->SetOpType(OP_Requantize_8to8); + requantize_op->AddInput(concat_out); + requantize_op->AddInput(concat_out_min); + requantize_op->AddInput(concat_out_max); + requantize_op->AddInput(TensorID(output_min_const->GetID(), 0)); + requantize_op->AddInput(TensorID(output_max_const->GetID(), 0)); + node_output_ = + requantize_op->AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus ConcatOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +ConcatOpBuilder::~ConcatOpBuilder() {} + +OpBuilder* CreateConcatBuilder(GraphBuilder* graph_builder, int op_type) { + return new ConcatOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/concat_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/concat_builder.h new file mode 100644 index 00000000000..02045e13c15 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/concat_builder.h @@ -0,0 +1,50 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_CONCAT_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_CONCAT_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ConcatOpBuilder : public OpBuilder { + public: + explicit ConcatOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ConcatOpBuilder() override; + + private: + TensorID node_output_; + std::vector input_minima_; + std::vector input_maxima_; + float output_min_, output_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_CONCAT_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/conv_2d_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/conv_2d_builder.cc new file mode 100644 index 00000000000..cc7b715dc71 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/conv_2d_builder.cc @@ -0,0 +1,378 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/conv_2d_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +namespace { + +// Dilated Depthwise Convolution performs SpaceToBatchND & BatchToSpaceND before +// and after the op respectively. +// This helper computes the paddings param for SpaceToBatchND and crops param +// for BatchToSpaceND. +// +// Inspired by tf.nn.with_space_to_batch & tf.required_space_to_batch_paddings. +void ComputeSpaceToBatchParams(int input_height, int input_width, + int weights_height, int weights_width, + const std::vector& dilation_factors_h_w, + const TfLitePadding padding_type, + std::vector* paddings, + std::vector* crops) { + // Base paddings depend on padding applied to the Depthwise Conv op. + // 4-element array: {top, bottom, left, right}. + std::vector base_paddings(4, 0); + if (padding_type == kTfLitePaddingSame) { + const int dilated_weights_h = + dilation_factors_h_w[0] * (weights_height - 1) + 1; + const int dilated_weights_w = + dilation_factors_h_w[1] * (weights_width - 1) + 1; + base_paddings[0] = (dilated_weights_h - 1) / 2; + base_paddings[1] = dilated_weights_h - 1 - (dilated_weights_h - 1) / 2; + base_paddings[2] = (dilated_weights_w - 1) / 2; + base_paddings[3] = dilated_weights_w - 1 - (dilated_weights_w - 1) / 2; + } + + // paddings represents {pad_top, pad_bottom, pad_left, pad_right}. + paddings->resize(4, 0); + // crops represents {crop_top, crop_bottom, crop_left, crop_right}. + crops->resize(4, 0); + + // Logic for computing paddings & crops follows. + // Taken from tf.required_space_to_batch_paddings, but without array + // operations since we only deal with 2 dimensions. + int pad_start_h = base_paddings[0]; + int pad_start_w = base_paddings[2]; + int orig_pad_end_h = base_paddings[1]; + int orig_pad_end_w = base_paddings[3]; + int full_input_h = input_height + pad_start_h + orig_pad_end_h; + int full_input_w = input_width + pad_start_w + orig_pad_end_w; + int pad_end_extra_h = + (dilation_factors_h_w[0] - full_input_h % dilation_factors_h_w[0]) % + dilation_factors_h_w[0]; + int pad_end_extra_w = + (dilation_factors_h_w[1] - full_input_w % dilation_factors_h_w[1]) % + dilation_factors_h_w[1]; + int pad_end_h = orig_pad_end_h + pad_end_extra_h; + int pad_end_w = orig_pad_end_w + pad_end_extra_w; + + // Assign values. + (*paddings)[0] = pad_start_h; + (*paddings)[1] = pad_end_h; + (*paddings)[2] = pad_start_w; + (*paddings)[3] = pad_end_w; + (*crops)[0] = 0; + (*crops)[1] = pad_end_extra_h; + (*crops)[2] = 0; + (*crops)[3] = pad_end_extra_w; +} + +// Computes output dimensions for the SpaceToBatchND op used in the dilated +// Depthwise Conv case. +// space_to_batch_paddings should be in format {top, bottom, left, right}. +// These are computed from the documentation for SpaceToBatchND_8's output. +void PopulateSpaceToBatchOutputDims( + int input_batch_size, int input_height_size, int input_width_size, + int input_depth_size, const std::vector& dilation_factors_h_w, + const std::vector& space_to_batch_paddings, + std::vector* space_to_batch_output_dims) { + // Batches. + space_to_batch_output_dims->push_back( + input_batch_size * dilation_factors_h_w[0] * dilation_factors_h_w[1]); + // Height. + space_to_batch_output_dims->push_back((space_to_batch_paddings[0] + + input_height_size + + space_to_batch_paddings[1]) / + dilation_factors_h_w[0]); + // Width. + space_to_batch_output_dims->push_back((space_to_batch_paddings[2] + + input_width_size + + space_to_batch_paddings[3]) / + dilation_factors_h_w[1]); + // Depth. + space_to_batch_output_dims->push_back(input_depth_size); +} + +} // namespace + +TfLiteStatus Conv2dOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static std::vector quant_bound_shape = {1, 1, 1, 1}; + static std::vector dilation_factors_shape = {1, 1, 1, 2}; + static std::vector paddings_shape = {1, 1, 2, 2}; + + // Input data tensor. + const auto& data_tensor = context->tensors[inputs->data[0]]; + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + data_tensor, &data_min_, &data_max_, std::numeric_limits::min(), + std::numeric_limits::max())); + auto* data_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&data_min_, sizeof(data_min_)); + auto* data_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&data_max_, sizeof(data_max_)); + + // Gather information about the Convolution operations. + TfLitePadding padding_type = kTfLitePaddingUnknown; + int stride_height = 0; + int stride_width = 0; + bool is_dilated_depthwise_conv = false; + if (op_node_.op_type == OP_Supernode_8x8p32to8) { + const TfLiteConvParams* conv_params = + reinterpret_cast(builtin_data_); + stride_height = conv_params->stride_height; + stride_width = conv_params->stride_width; + padding_type = conv_params->padding; + } else if (op_node_.op_type == OP_DepthwiseSupernode_8x8p32to8) { + const TfLiteDepthwiseConvParams* conv_params = + reinterpret_cast(builtin_data_); + stride_height = conv_params->stride_height; + stride_width = conv_params->stride_width; + padding_type = conv_params->padding; + // We only support dilation for DepthwiseConv. + if (conv_params->dilation_height_factor > 1 || + conv_params->dilation_width_factor > 1) { + is_dilated_depthwise_conv = true; + dilation_factors_h_w_.push_back(conv_params->dilation_height_factor); + dilation_factors_h_w_.push_back(conv_params->dilation_width_factor); + } + } + + // Weights tensor + const auto& weights_tensor = context->tensors[inputs->data[1]]; + if (weights_tensor.allocation_type != kTfLiteMmapRo) { + context->ReportError( + context, "Weights tensor doesn't have correct allocation type: %s", + weights_tensor.name); + return kTfLiteError; + } + int weights_batch_size, weights_height_size, weights_width_size, + weights_depth_size; + // Hexagon lib expects the weight tensor in HWCN, TFLite uses NHWC. + // Transpose NHWC -> HWCN + GetDims(&weights_batch_size, &weights_height_size, &weights_width_size, + &weights_depth_size, weights_tensor.dims); + weight_shape_ = {weights_height_size, weights_width_size, weights_depth_size, + weights_batch_size}; + RuntimeShape nhwc_shape({weights_batch_size, weights_height_size, + weights_width_size, weights_depth_size}); + RuntimeShape hwcn_shape({weights_height_size, weights_width_size, + weights_depth_size, weights_batch_size}); + std::vector hwcn(NumElements(&weights_tensor)); + TransposeParams transpose_params; + transpose_params.perm_count = 4; + transpose_params.perm[0] = 1; + transpose_params.perm[1] = 2; + transpose_params.perm[2] = 3; + transpose_params.perm[3] = 0; + optimized_ops::Transpose(transpose_params, nhwc_shape, + weights_tensor.data.uint8, hwcn_shape, + hwcn.data()); + // Quantization params for Weights tensor. + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(weights_tensor, &weights_min_, &weights_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* weights_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&weights_min_, sizeof(weights_min_)); + auto* weights_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&weights_max_, sizeof(weights_max_)); + auto* const_weights_node = graph_builder_->AddConstNodeWithData( + weight_shape_.data(), (char*)hwcn.data(), hwcn.size() * sizeof(hwcn[0])); + graph_builder_->AddTensorWithID(inputs->data[1], const_weights_node->GetID(), + 0); + + // Stride node. + static int dummy = 0; + stride_shape_ = {1, stride_height, stride_width, 1}; + auto* stride_node = graph_builder_->AddConstNodeWithData( + stride_shape_.data(), (char*)&dummy, sizeof(dummy)); + + // Output dimensions. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + // Output min/max. + // TODO(b/129276536): Add support for other activations here. Current + // implementation assumes None/Relu. + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + context->tensors[outputs->data[0]], &output_min_, &output_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* output_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&output_min_, sizeof(output_min_)); + auto* output_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&output_max_, sizeof(output_max_)); + + // Bias node. + const auto& bias_tensor = context->tensors[inputs->data[2]]; + auto* bias_data_node = + graph_builder_->AddConstNodeWithData(inputs->data[2], bias_tensor); + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + bias_tensor, &bias_min_, &bias_max_, std::numeric_limits::min(), + std::numeric_limits::max())); + auto* bias_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&bias_min_, sizeof(bias_min_)); + auto* bias_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&bias_max_, sizeof(bias_max_)); + + if (is_dilated_depthwise_conv) { + // For dilated Depthwise Conv, we convert this node into SpaceToBatchND, and + // then chain Supernode & BatchToSpaceND after it. + int input_batch_size, input_height_size, input_width_size, input_depth_size; + GetDims(&input_batch_size, &input_height_size, &input_width_size, + &input_depth_size, data_tensor.dims); + ComputeSpaceToBatchParams( + input_height_size, input_width_size, weights_height_size, + weights_width_size, dilation_factors_h_w_, padding_type, + &space_to_batch_paddings_, &batch_to_space_crops_); + auto* dilation_factors_const = graph_builder_->AddConstNodeWithData( + dilation_factors_shape.data(), (char*)dilation_factors_h_w_.data(), + dilation_factors_h_w_.size() * sizeof(stride_height)); + auto* paddings_const = graph_builder_->AddConstNodeWithData( + paddings_shape.data(), (char*)space_to_batch_paddings_.data(), + space_to_batch_paddings_.size() * sizeof(stride_height)); + auto* crops_const = graph_builder_->AddConstNodeWithData( + paddings_shape.data(), (char*)batch_to_space_crops_.data(), + batch_to_space_crops_.size() * sizeof(stride_height)); + + // 1. SpaceToBatch. + SetOpType(OP_SpaceToBatchND_8); + AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0])); + AddInput(TensorID(dilation_factors_const->GetID(), 0)); + AddInput(TensorID(paddings_const->GetID(), 0)); + AddInput(TensorID(data_min_const->GetID(), 0)); + AddInput(TensorID(data_max_const->GetID(), 0)); + std::vector space_to_batch_output_dims; + PopulateSpaceToBatchOutputDims( + input_batch_size, input_height_size, input_width_size, input_depth_size, + dilation_factors_h_w_, space_to_batch_paddings_, + &space_to_batch_output_dims); + TensorID space_to_batch_op_out = + AddOutput(sizeof(uint8_t), 4, space_to_batch_output_dims); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + // 2. Depthwise Conv. + auto* conv_op = graph_builder_->AddNode(); + conv_op->SetOpType(OP_DepthwiseSupernode_8x8p32to8); + conv_op->AddInput(space_to_batch_op_out); + conv_op->AddInput(TensorID(const_weights_node->GetID(), 0)); + conv_op->AddInput(TensorID(data_min_const->GetID(), 0)); + conv_op->AddInput(TensorID(data_max_const->GetID(), 0)); + conv_op->AddInput(TensorID(weights_min_const->GetID(), 0)); + conv_op->AddInput(TensorID(weights_max_const->GetID(), 0)); + conv_op->AddInput(TensorID(stride_node->GetID(), 0)); + conv_op->AddInput(TensorID(bias_data_node->GetID(), 0)); + conv_op->AddInput(TensorID(bias_min_const->GetID(), 0)); + conv_op->AddInput(TensorID(bias_max_const->GetID(), 0)); + conv_op->AddInput(TensorID(output_min_const->GetID(), 0)); + conv_op->AddInput(TensorID(output_max_const->GetID(), 0)); + // The padding is handled by the SpaceToBatch/BatchToSpace ops surrounding + // this node. Hence, this op's padding remains VALID only. + // tf.nn.with_space_to_batch's docs state the following pattern: + // """ + // batch_to_space_nd( + // op(space_to_batch_nd(input, adjusted_dilation_rate, adjusted_paddings), + // num_spatial_dims, + // "VALID") + // adjusted_dilation_rate, + // adjusted_crops) + // """ + conv_op->SetPaddingType(NN_PAD_VALID); + // These dimensions are probably a little excessive, but they upper-bound + // the possible output from DepthwiseConv. + // TODO(b/139955809): Find better bounds? + TensorID conv_output = conv_op->AddOutput( + sizeof(uint8_t), 4, + {output_batch_size * dilation_factors_h_w_[0] * + dilation_factors_h_w_[1], + output_height_size, output_width_size, output_depth_size}); + conv_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + conv_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + // 3. BatchToSpace. + auto* batch_to_space_op = graph_builder_->AddNode(); + batch_to_space_op->SetOpType(OP_BatchToSpaceND_8); + batch_to_space_op->AddInput(conv_output); + batch_to_space_op->AddInput(TensorID(dilation_factors_const->GetID(), 0)); + batch_to_space_op->AddInput(TensorID(crops_const->GetID(), 0)); + batch_to_space_op->AddInput(TensorID(output_min_const->GetID(), 0)); + batch_to_space_op->AddInput(TensorID(output_max_const->GetID(), 0)); + node_output_ = + batch_to_space_op->AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + batch_to_space_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + batch_to_space_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + } else { + // Standard case. + // Padding type. + if (padding_type == kTfLitePaddingSame) { + SetPaddingType(NN_PAD_SAME); + } else if (padding_type == kTfLitePaddingValid) { + SetPaddingType(NN_PAD_VALID); + } + // Inputs + AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0])); + AddInput(TensorID(const_weights_node->GetID(), 0)); + AddInput(TensorID(data_min_const->GetID(), 0)); + AddInput(TensorID(data_max_const->GetID(), 0)); + AddInput(TensorID(weights_min_const->GetID(), 0)); + AddInput(TensorID(weights_max_const->GetID(), 0)); + AddInput(TensorID(stride_node->GetID(), 0)); + AddInput(TensorID(bias_data_node->GetID(), 0)); + AddInput(TensorID(bias_min_const->GetID(), 0)); + AddInput(TensorID(bias_max_const->GetID(), 0)); + AddInput(TensorID(output_min_const->GetID(), 0)); + AddInput(TensorID(output_max_const->GetID(), 0)); + // Outputs + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + } + + return kTfLiteOk; +} + +TfLiteStatus Conv2dOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +Conv2dOpBuilder::~Conv2dOpBuilder() {} + +OpBuilder* CreateConv2DBuilder(GraphBuilder* graph_builder, int op_type) { + return new Conv2dOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/conv_2d_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/conv_2d_builder.h new file mode 100644 index 00000000000..b66e410d3bb --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/conv_2d_builder.h @@ -0,0 +1,57 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_CONV_2D_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_CONV_2D_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class Conv2dOpBuilder : public OpBuilder { + public: + explicit Conv2dOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~Conv2dOpBuilder(); + + private: + TensorID node_output_; + std::vector transposed_weights_; + std::vector stride_shape_; + std::vector weight_shape_; + float data_min_, data_max_, weights_min_, weights_max_, bias_min_, bias_max_, + output_min_, output_max_; + + // Only used for dilated Depthwise Conv. + std::vector dilation_factors_h_w_; + std::vector space_to_batch_paddings_; + std::vector batch_to_space_crops_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_CONV_2D_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/l2_normalization_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/l2_normalization_builder.cc new file mode 100644 index 00000000000..ab91f65e18c --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/l2_normalization_builder.cc @@ -0,0 +1,83 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/l2_normalization_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus L2NormalizationOpBuilder::PopulateSubGraph( + const TfLiteIntArray* inputs, const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus L2NormalizationOpBuilder::RegisterOutputs( + const TfLiteIntArray* outputs, TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + + return kTfLiteOk; +} + +L2NormalizationOpBuilder::~L2NormalizationOpBuilder() {} + +OpBuilder* CreateL2NormalizationBuilder(GraphBuilder* graph_builder, + int op_type) { + return new L2NormalizationOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/l2_normalization_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/l2_normalization_builder.h new file mode 100644 index 00000000000..e78fe9580a2 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/l2_normalization_builder.h @@ -0,0 +1,48 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_L2_NORMALIZATION_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_L2_NORMALIZATION_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class L2NormalizationOpBuilder : public OpBuilder { + public: + explicit L2NormalizationOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~L2NormalizationOpBuilder() override; + + private: + TensorID node_output_; + float input_min_, input_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_L2_NORMALIZATION_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/matmul_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/matmul_builder.cc new file mode 100644 index 00000000000..aeae5b68eed --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/matmul_builder.cc @@ -0,0 +1,211 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/matmul_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +// The TFLite 'Fully-connected' quantized op corresponds to the following +// subgraph in Hexagon: +// Data (uint8), Weights (const, uint8) => MatMul => MatMul out (int32) +// Bias (const, int32) => Quantize => Bias (uint8) +// MatMul out (int32) => Quantize => MatMul out (uint8) +// MatMul out (uint8), Bias (uint8) => QuantizedAdd => Output (uint8) +// TODO(b/129276536): Add activation support. +TfLiteStatus MatMulOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + + // Data tensor. + int data_tensor_id = inputs->data[0]; + const auto& data_tensor = context->tensors[data_tensor_id]; + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + data_tensor, &data_min_, &data_max_, std::numeric_limits::min(), + std::numeric_limits::max())); + auto* data_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&data_min_), + sizeof(data_min_)); + auto* data_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&data_max_), + sizeof(data_max_)); + + // Weights vector. + int weights_tensor_id = inputs->data[1]; + const auto& weights_tensor = context->tensors[weights_tensor_id]; + // TODO(srjoglekar): Abstract out. + if (weights_tensor.allocation_type != kTfLiteMmapRo) { + context->ReportError( + context, "Weights tensor doesn't have correct allocation type: %s", + weights_tensor.name); + return kTfLiteError; + } + int batch_size, height_size, width_size, depth_size; + // Hexagon lib expects the weight tensor in NHCW, TFLite uses NHWC. + // Transpose NHWC -> NHCW + GetDims(&batch_size, &height_size, &width_size, &depth_size, + weights_tensor.dims); + weights_shape_ = {batch_size, height_size, depth_size, width_size}; + RuntimeShape nhwc_shape({batch_size, height_size, width_size, depth_size}); + RuntimeShape nhcw_shape({batch_size, height_size, depth_size, width_size}); + std::vector nhcw(NumElements(&weights_tensor)); + TransposeParams transpose_params; + transpose_params.perm_count = 4; + transpose_params.perm[0] = 0; + transpose_params.perm[1] = 1; + transpose_params.perm[2] = 3; + transpose_params.perm[3] = 2; + optimized_ops::Transpose(transpose_params, nhwc_shape, + weights_tensor.data.uint8, nhcw_shape, + nhcw.data()); + auto* const_weights_node = graph_builder_->AddConstNodeWithData( + weights_shape_.data(), reinterpret_cast(nhcw.data()), + weights_tensor.bytes); + graph_builder_->AddTensorWithID(weights_tensor_id, + const_weights_node->GetID(), 0); + ComputeMinAndMaxQuantValues(weights_tensor, &weights_min_, &weights_max_, + std::numeric_limits::min(), + std::numeric_limits::max()); + auto* weights_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&weights_min_), + sizeof(weights_min_)); + auto* weights_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&weights_max_), + sizeof(weights_max_)); + + // Data and weight tensors in required order. + AddInput(graph_builder_->GetHexagonTensorId(data_tensor_id)); + AddInput(graph_builder_->GetHexagonTensorId(weights_tensor_id)); + AddInput(TensorID(data_min_const->GetID(), 0)); + AddInput(TensorID(data_max_const->GetID(), 0)); + AddInput(TensorID(weights_min_const->GetID(), 0)); + AddInput(TensorID(weights_max_const->GetID(), 0)); + + // Outputs for the MatMul node, which are in int32 format. + // Output shape should still be the same. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + const auto& matmul_out = AddOutput(sizeof(int32_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + const auto& matmul_out_min = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + const auto& matmul_out_max = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + // Quantize the MatMul output to quint8. + auto* quantize_matmul_op = graph_builder_->AddNode(); + quantize_matmul_op->SetOpType(OP_QuantizeDownAndShrinkRange_32to8); + quantize_matmul_op->AddInput(matmul_out); + quantize_matmul_op->AddInput(matmul_out_min); + quantize_matmul_op->AddInput(matmul_out_max); + const auto& quantized_matmul_out = + quantize_matmul_op->AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + const auto& quantized_matmul_out_min = + quantize_matmul_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + const auto& quantized_matmul_out_max = + quantize_matmul_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + // Bias tensor. + int bias_tensor_id = inputs->data[2]; + const auto& bias_tensor = context->tensors[bias_tensor_id]; + auto* const_bias_node = + graph_builder_->AddConstNodeWithData(bias_tensor_id, bias_tensor); + graph_builder_->AddTensorWithID(bias_tensor_id, const_bias_node->GetID(), 0); + ComputeMinAndMaxQuantValues(bias_tensor, &bias_min_, &bias_max_, + std::numeric_limits::min(), + std::numeric_limits::max()); + auto* bias_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&bias_min_), + sizeof(bias_min_)); + auto* bias_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&bias_max_), + sizeof(bias_max_)); + // Quantize bias + auto* quantize_bias_op = graph_builder_->AddNode(); + quantize_bias_op->SetOpType(OP_QuantizeDownAndShrinkRange_32to8); + quantize_bias_op->AddInput( + graph_builder_->GetHexagonTensorId(bias_tensor_id)); + quantize_bias_op->AddInput(TensorID(bias_min_const->GetID(), 0)); + quantize_bias_op->AddInput(TensorID(bias_max_const->GetID(), 0)); + const auto& quantized_bias_out = + quantize_bias_op->AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + const auto& quantized_bias_out_min = + quantize_bias_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + const auto& quantized_bias_out_max = + quantize_bias_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + // Output min/max. + ComputeMinAndMaxQuantValues(context->tensors[outputs->data[0]], &output_min_, + &output_max_, std::numeric_limits::min(), + std::numeric_limits::max()); + + auto* output_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&output_min_), + sizeof(output_min_)); + auto* output_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&output_max_), + sizeof(output_max_)); + + // MatMul + Bias. + auto* bias_add_op = graph_builder_->AddNode(); + bias_add_op->SetOpType(OP_QuantizedAdd_8p8to8); + bias_add_op->AddInput(quantized_matmul_out); + bias_add_op->AddInput(quantized_bias_out); + bias_add_op->AddInput(quantized_matmul_out_min); + bias_add_op->AddInput(quantized_matmul_out_max); + bias_add_op->AddInput(quantized_bias_out_min); + bias_add_op->AddInput(quantized_bias_out_max); + bias_add_op->AddInput(TensorID(output_min_const->GetID(), 0)); + bias_add_op->AddInput(TensorID(output_max_const->GetID(), 0)); + node_output_ = bias_add_op->AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + bias_add_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + bias_add_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus MatMulOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +MatMulOpBuilder::~MatMulOpBuilder() {} + +OpBuilder* CreateMatMulBuilder(GraphBuilder* graph_builder, int op_type) { + return new MatMulOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/matmul_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/matmul_builder.h new file mode 100644 index 00000000000..212ea7be7a3 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/matmul_builder.h @@ -0,0 +1,51 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_MATMUL_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_MATMUL_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class MatMulOpBuilder : public OpBuilder { + public: + explicit MatMulOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~MatMulOpBuilder() override; + + private: + TensorID node_output_; + std::vector weights_shape_, bias_shape_; + std::vector transposed_weights_; + float data_min_, data_max_, weights_min_, weights_max_, bias_min_, bias_max_, + output_min_, output_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_MATMUL_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/neg_op_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/neg_op_builder.cc new file mode 100644 index 00000000000..f2561d4fa41 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/neg_op_builder.cc @@ -0,0 +1,69 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/neg_op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus NegOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int scalar_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max()); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + scalar_shape, reinterpret_cast(&input_min_), sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + scalar_shape, reinterpret_cast(&input_max_), sizeof(input_max_)); + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus NegOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + + return kTfLiteOk; +} + +OpBuilder* CreateNegOpBuilder(GraphBuilder* graph_builder, int op_type) { + return new NegOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/neg_op_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/neg_op_builder.h new file mode 100644 index 00000000000..e0b0e430193 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/neg_op_builder.h @@ -0,0 +1,44 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_NEG_OP_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_NEG_OP_BUILDER_H_ + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class NegOpBuilder : public OpBuilder { + public: + explicit NegOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + private: + TensorID node_output_; + float input_min_, input_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_NEG_OP_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.cc new file mode 100644 index 00000000000..0dabdcb8608 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.cc @@ -0,0 +1,207 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/op_builder.h" + +#include "tensorflow/lite/builtin_ops.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_factory.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +OpBuilder* GraphBuilder::CreateOpBuilderFromTfLiteOp(int op_type) { + switch (op_type) { + case kTfLiteBuiltinAdd: + return CreateArithmeticBuilder(this, OP_QuantizedAdd_8p8to8); + case kTfLiteBuiltinArgMax: + return CreateArgMinMaxOpBuilder(this, OP_ArgMax_8toInt32); + case kTfLiteBuiltinArgMin: + return CreateArgMinMaxOpBuilder(this, OP_ArgMin_8); + case kTfLiteBuiltinMul: + return CreateArithmeticBuilder(this, OP_QuantizedMul_8x8to8); + case kTfLiteBuiltinSub: + return CreateArithmeticBuilder(this, OP_QuantizedSub_8p8to8); + case kTfLiteBuiltinMean: + return CreateReduceBuilder(this, OP_QuantizedMean_8); + case kTfLiteBuiltinSum: + return CreateReduceBuilder(this, OP_QuantizedSum_8to32); + case kTfLiteBuiltinPad: + return CreatePadBuilder(this, OP_QuantizedPad_8); + case kTfLiteBuiltinFullyConnected: + return CreateMatMulBuilder(this, OP_QuantizedMatMul_8x8to32); + case kTfLiteBuiltinAveragePool2d: + return CreatePool2DBuilder(this, OP_QuantizedAvgPool_8); + case kTfLiteBuiltinMaxPool2d: + return CreatePool2DBuilder(this, OP_QuantizedMaxPool_8); + case kTfLiteBuiltinConcatenation: + return CreateConcatBuilder(this, OP_QuantizedConcat_8); + case kTfLiteBuiltinConv2d: + return CreateConv2DBuilder(this, OP_Supernode_8x8p32to8); + case kTfLiteBuiltinTransposeConv: + return CreateTransposeConv2DBuilder( + this, OP_QuantizedTransposeConv2d_8x8p32to8); + case kTfLiteBuiltinDepthwiseConv2d: + return CreateConv2DBuilder(this, OP_DepthwiseSupernode_8x8p32to8); + case kTfLiteBuiltinReshape: + return CreateReshapeBuilder(this, OP_Reshape); + case kTfLiteBuiltinSoftmax: + return CreateSoftmaxBuilder(this, OP_QuantizedSoftmax_8); + case kTfLiteBuiltinResizeNearestNeighbor: + return CreateResizeNearestNeighborBuilder(this, + OP_ResizeNearestNeighbor_8); + case kTfLiteBuiltinL2Normalization: + return CreateL2NormalizationBuilder(this, OP_L2Normalize_8); + case kTfLiteBuiltinRelu: + return CreateActivationBuilder(this, OP_QuantizedRelu_8); + case kTfLiteBuiltinRelu6: + return CreateActivationBuilder(this, OP_QuantizedReluX_8); + case kTfLiteBuiltinTanh: + return CreateActivationBuilder(this, OP_QuantizedTanh_8); + case kTfLiteBuiltinLogistic: + return CreateActivationBuilder(this, OP_QuantizedSigmoid_8); + case kTfLiteBuiltinSplit: + return CreateSplitBuilder(this, OP_QuantizedSplit_8); + case kTfLiteBuiltinResizeBilinear: + return CreateResizeBilinearOpBuilder(this, OP_QuantizedResizeBilinear_8); + case kTfLiteBuiltinNeg: + return CreateNegOpBuilder(this, OP_QuantizedNeg_8); + case kTfLiteBuiltinTranspose: + return CreateTransposeBuilder(this, OP_Transpose_8); + default: + context_->ReportError(context_, "Op not supported: %d", op_type); + return nullptr; + } +} + +OpBuilder* GraphBuilder::AddConstNodeWithData(const int shape[], char* data, + int data_size) { + builders_.emplace_back(new OpBuilder(this, OP_Const)); + builders_.back()->SetConstNode(); + builders_.back()->SetNodeId(builders_.size()); + int error = hexagon_nn_->hexagon_nn_append_const_node( + graph_id_, builders_.size(), shape[0], shape[1], shape[2], shape[3], + reinterpret_cast(data), data_size); + if (error != 0) { + context_->ReportError(context_, "Error adding const node with shape id: %d", + (int)builders_.size()); + return nullptr; + } + return builders_.back().get(); +} + +OpBuilder* GraphBuilder::AddConstNodeWithData(int tensor_id, + const TfLiteTensor& tensor) { + builders_.emplace_back(new OpBuilder(this, OP_Const)); + const int node_id = builders_.size(); + builders_.back()->SetConstNode(); + builders_.back()->SetNodeId(node_id); + int batch_size, height_size, width_size, depth_size; + GetDims(&batch_size, &height_size, &width_size, &depth_size, tensor.dims); + int error = hexagon_nn_->hexagon_nn_append_const_node( + graph_id_, node_id, batch_size, height_size, width_size, depth_size, + reinterpret_cast(tensor.data.raw), tensor.bytes); + if (error > 0) { + context_->ReportError( + context_, "Failed to add const node for tensor with id: %d", tensor_id); + return nullptr; + } + AddTensorWithID(tensor_id, node_id, 0); + return builders_.back().get(); +} + +void delegates::hexagon::GraphBuilder::AddInputTensors( + const TfLiteIntArray* input_tensors, TfLiteContext* context) { + builders_.emplace_back(new OpBuilder(this, OP_INPUT)); + builders_.back()->SetNodeId(builders_.size()); + // We need to track num_inputs since not all input_tensors are actual input + // data. Some are constants. + int num_inputs = 0; + for (int i = 0; i < input_tensors->size; ++i) { + const int tensor_id = input_tensors->data[i]; + const auto& tensor = context->tensors[tensor_id]; + if (tensor.allocation_type != kTfLiteMmapRo) { + AddTensorWithID(tensor_id, builders_.size(), num_inputs); + builders_.back()->AddOutput(tensor.dims); + ++num_inputs; + } + } +} + +void delegates::hexagon::GraphBuilder::AddOutputTensors( + const TfLiteIntArray* output_tensors, TfLiteContext* context) { + builders_.emplace_back(new OpBuilder(this, OP_OUTPUT)); + builders_.back()->SetNodeId(builders_.size()); + for (int i = 0; i < output_tensors->size; ++i) { + const int tensor_id = output_tensors->data[i]; + builders_.back()->AddInput(GetHexagonTensorId(tensor_id)); + } +} + +OpBuilder::TensorID OpBuilder::AddOutput(const TfLiteIntArray* dims) { + op_node_.outputs.push_back(hexagon_nn_output()); + op_node_.outputs.back().elementsize = sizeof(float); + op_node_.outputs.back().rank = 4; + // TODO(karimnosseir): What is a good to estimate the max size ? + int batch_size, height_size, width_size, depth_size; + GetDims(&batch_size, &height_size, &width_size, &depth_size, dims); + auto& max_sizes = op_node_.outputs.back().max_sizes; + max_sizes[0] = batch_size; + max_sizes[1] = height_size; + max_sizes[2] = width_size; + max_sizes[3] = depth_size; + return TensorID(GetID(), op_node_.outputs.size() - 1); +} + +OpBuilder::TensorID OpBuilder::AddOutput( + int elementsize, int rank, const std::vector& max_sizes_vect) { + op_node_.outputs.push_back(hexagon_nn_output()); + op_node_.outputs.back().elementsize = elementsize; + op_node_.outputs.back().rank = rank; + auto& max_sizes = op_node_.outputs.back().max_sizes; + for (int i = 0; i < max_sizes_vect.size(); ++i) { + max_sizes[i] = max_sizes_vect[i]; + } + return TensorID(GetID(), op_node_.outputs.size() - 1); +} + +const OpNode* OpBuilder::Build() { + for (const auto& id : input_ids_) { + op_node_.inputs.push_back(hexagon_nn_input()); + op_node_.inputs.back().src_id = id.first; + op_node_.inputs.back().output_idx = id.second; + } + return &op_node_; +} + +OpBuilder* GraphBuilder::AddNode() { + OpBuilder* op = new OpBuilder(this, OP_Nop); + builders_.emplace_back(op); + op->SetNodeId(builders_.size()); + return op; +} + +OpBuilder* GraphBuilder::AddNodeFromTfLiteOp(int op_type, TfLiteNode* node) { + OpBuilder* op = CreateOpBuilderFromTfLiteOp(op_type); + builders_.emplace_back(op); + op->SetNodeId(builders_.size()); + op->SetBuiltinData(node->builtin_data); + op->SetTfLiteNode(node); + return op; +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h new file mode 100644 index 00000000000..31e8e9d4a42 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h @@ -0,0 +1,277 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_OP_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_OP_BUILDER_H_ + +#include +#include +#include +#include + +#include "hexagon/hexagon_nn_ops.h" +#include "tensorflow/lite/builtin_ops.h" +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +struct OpNode { + std::vector inputs; + std::vector outputs; + // Value from the Enum of Ops in hexagon_nn_ops + int op_type; + hexagon_nn_padding_type padding_type = NN_PAD_NA; + // Id of node in the Hexagon graph. + int node_id = -1; +}; + +class GraphBuilder; + +class OpBuilder { + public: + OpBuilder(GraphBuilder* graph_builder, int hexagon_op_type) + : graph_builder_(graph_builder) { + op_node_.op_type = hexagon_op_type; + } + // A tensor is identified in the graph using a pair of IDs + // (Node ID, output Tensor ID) + // Node producing this tensor, and the index of the tensor in this + // node output list. + using TensorID = std::pair; + + virtual ~OpBuilder() {} + + // TODO(karimnosseir): Do we need to have builder pattern, or they are few not + // worth it ? + void SetOpType(int op_type) { op_node_.op_type = op_type; } + + void SetNodeId(int node_id) { op_node_.node_id = node_id; } + + void SetConstNode() { op_node_.op_type = OP_Const; } + + void SetPaddingType(hexagon_nn_padding_type padding_type) { + op_node_.padding_type = padding_type; + } + + void SetBuiltinData(void* builtin_data) { builtin_data_ = builtin_data; } + + bool IsConstNode() const { return op_node_.op_type == OP_Const; } + + void print() {} + + const OpNode* Build(); + + void AddInput(const TensorID& tensor_id) { input_ids_.push_back(tensor_id); } + + TensorID AddOutput(const TfLiteIntArray* dims); + + TensorID AddOutput(int elementsize, int rank, + const std::vector& max_sizes); + + int GetID() const { return op_node_.node_id; } + + int GetOpType() const { return op_node_.op_type; } + + void SetTfLiteNode(const TfLiteNode* node) { tflite_node_ = node; } + + virtual TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + return kTfLiteOk; + } + + virtual TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + return kTfLiteOk; + } + + protected: + // Helper method to fetch dimensions. + // TODO(karimnosseir): Move to a shared place. + void GetDims(int* batch_size, int* height_size, int* width_size, + int* depth_size, const TfLiteIntArray* dims) { + int* dim[] = {batch_size, height_size, width_size, depth_size}; + for (int i = 0; i < 4; ++i) *(dim[i]) = 1; + for (int i = 4 - dims->size; i < 4; ++i) { + *dim[i] = dims->data[i - (4 - dims->size)]; + } + } + + template + TfLiteStatus ComputeMinAndMaxQuantValues(const TfLiteTensor& tensor, + float* min, float* max, T min_value, + T max_value) { + *min = 0; + *max = 0; + const TfLiteQuantization& quant = tensor.quantization; + if (quant.type != TfLiteQuantizationType::kTfLiteAffineQuantization) { + printf("Tensor not quantized: %s\n", tensor.name); + return kTfLiteError; + } + const TfLiteAffineQuantization* params = + static_cast(quant.params); + if (params->quantized_dimension != 0) { + printf("Quantized dimensions not 0 for tensor: %s\n", tensor.name); + return kTfLiteError; + } + float scale = params->scale->data[0]; + float zero_point = static_cast(params->zero_point->data[0]); + *min = scale * (static_cast(min_value) - zero_point); + *max = scale * (static_cast(max_value) - zero_point); + + return kTfLiteOk; + } + + OpNode op_node_; + // inputs to the current op. Each pair identifies a single output from + // another node (node_id, output_id). + std::vector input_ids_; + // Pointer to the graph builder. + GraphBuilder* graph_builder_ = nullptr; + // Data needed by this node. + void* builtin_data_ = nullptr; + // TODO(karimnosseir): Currently we only use it for getting output + // size. Can we avoid passing it ? + const TfLiteNode* tflite_node_ = nullptr; +}; + +class GraphBuilder { + public: + GraphBuilder(const HexagonNN* hexagon_nn, TfLiteContext* context, + int graph_id) + : hexagon_nn_(hexagon_nn), context_(context), graph_id_(graph_id) {} + + // Returns per OP builder. 'op_type' is the TfLite builtinOperator. + OpBuilder* AddNodeFromTfLiteOp(int op_type, TfLiteNode* node); + + // Add node to the graph. The caller responsible for setting correct + // data in the Op. + OpBuilder* AddNode(); + + // Add const node that provides the data held by 'tensor'. + OpBuilder* AddConstNodeWithData(int tensor_id, const TfLiteTensor& tensor); + + // Same as above but takes shape of the tensor that will holds the data. + OpBuilder* AddConstNodeWithData(const int shape[], char* data, int data_size); + + OpBuilder* CreateOpBuilderFromTfLiteOp(int op_type); + + // Construct Input node with 'input_tensors' as output. + void AddInputTensors(const TfLiteIntArray* input_tensors, + TfLiteContext* context); + + // Construct Output node with 'output_tensors' as input. + void AddOutputTensors(const TfLiteIntArray* output_tensors, + TfLiteContext* context); + + // Returns tensor id inside Hexagon graph. + OpBuilder::TensorID GetHexagonTensorId(int tflite_tensor_index) { + if (!HasTensor(tflite_tensor_index)) { + printf("Could not find tensor id: %d\n", tflite_tensor_index); + // Return invalid ID. + return OpBuilder::TensorID(-1, -1); + } + return tensors_[tflite_tensor_index]; + } + + // Return true if this tensor was added before to the graph. + bool HasTensor(int tflite_tensor_index) { + if (tensors_.size() <= tflite_tensor_index) { + return false; + } + // the first field is node ID and id = 0 is reserved + // so anything > 0 is correctly initialized. + return tensors_[tflite_tensor_index].first != 0; + } + + void AddDebugNode() {} + + void Build() { + for (int i = 0; i < builders_.size(); ++i) { + if (builders_[i]->IsConstNode()) { + continue; + } + const OpNode* op_node = builders_[i]->Build(); + int error = hexagon_nn_->hexagon_nn_append_node( + graph_id_, op_node->node_id, op_node->op_type, op_node->padding_type, + op_node->inputs.data(), op_node->inputs.size(), + op_node->outputs.data(), op_node->outputs.size()); + if (error != 0) { + printf("Error adding node: id:%d, op_type:%d\n", op_node->node_id, + op_node->op_type); + } + } + } + + void print() { + printf("------------------------------\n"); + std::vector buf(10000); + hexagon_nn_->hexagon_nn_snpprint(graph_id_, buf.data(), buf.size()); + printf("%s", buf.data()); + printf("------------------------------\n"); + fflush(stdout); + } + + // Add new tensor mapping to the tensor list. + bool AddTensorWithID(int tflite_tensor_id, int hexagon_node_id, + int hexagon_node_output_id) { + if (HasTensor(tflite_tensor_id)) { + return false; + } + if (tensors_.size() <= tflite_tensor_id) { + tensors_.resize(tflite_tensor_id + 1); + } + tensors_[tflite_tensor_id] = + OpBuilder::TensorID(hexagon_node_id, hexagon_node_output_id); + return true; + } + + int GetOpTypeId(int node_id) { + if (node_id > builders_.size()) { + return -1; + } + return builders_[node_id - 1]->GetOpType(); + } + + private: + // Helper method to fetch dimensions. + // TODO(karimnosseir): Move this method to shared place. + void GetDims(int* batch_size, int* height_size, int* width_size, + int* depth_size, const TfLiteIntArray* dims) { + int* dim[] = {batch_size, height_size, width_size, depth_size}; + for (int i = 0; i < 4; ++i) *(dim[i]) = 1; + for (int i = 4 - dims->size; i < 4; ++i) { + *dim[i] = dims->data[i - (4 - dims->size)]; + } + } + + const HexagonNN* hexagon_nn_ = nullptr; + TfLiteContext* context_ = nullptr; + int graph_id_ = -1; + std::vector> builders_; + // Index in the vector is the tflite_tensor_index, the value + // is the ID in the hexgon graph. + std::vector tensors_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_OP_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/op_factory.h b/tensorflow/lite/experimental/delegates/hexagon/builders/op_factory.h new file mode 100644 index 00000000000..277ddf1f3d4 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/op_factory.h @@ -0,0 +1,51 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_OP_FACTORY_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_OP_FACTORY_H_ + +namespace tflite { +namespace delegates { +namespace hexagon { +class GraphBuilder; +class OpBuilder; + +OpBuilder* CreateArgMinMaxOpBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateActivationBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateArithmeticBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateMatMulBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateConcatBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateConv2DBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateTransposeConv2DBuilder(GraphBuilder* graph_builder, + int op_type); +OpBuilder* CreatePool2DBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateReshapeBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateSoftmaxBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateReduceBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreatePadBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateResizeNearestNeighborBuilder(GraphBuilder* graph_builder, + int op_type); +OpBuilder* CreateL2NormalizationBuilder(GraphBuilder* graph_builder, + int op_type); +OpBuilder* CreateSplitBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateResizeBilinearOpBuilder(GraphBuilder* graph_builder, + int op_type); +OpBuilder* CreateNegOpBuilder(GraphBuilder* graph_builder, int op_type); +OpBuilder* CreateTransposeBuilder(GraphBuilder* graph_builder, int op_type); + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_OP_FACTORY_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/pad_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/pad_builder.cc new file mode 100644 index 00000000000..320ff672880 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/pad_builder.cc @@ -0,0 +1,97 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/pad_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus PadOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + + // Min/max values for input tensor. + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Padding tensor. + tensor_id = inputs->data[1]; + const auto& padding_tensor = context->tensors[tensor_id]; + if (padding_tensor.allocation_type == kTfLiteMmapRo) { + // If the padding input is a constant, bake it into the Hexagon graph as a + // Const node. + auto* const_padding_node = + graph_builder_->AddConstNodeWithData(tensor_id, padding_tensor); + AddInput(TensorID(const_padding_node->GetID(), 0)); + } else { + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + } + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus PadOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +PadOpBuilder::~PadOpBuilder() {} + +OpBuilder* CreatePadBuilder(GraphBuilder* graph_builder, int op_type) { + return new PadOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/pad_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/pad_builder.h new file mode 100644 index 00000000000..a7d50d2cbda --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/pad_builder.h @@ -0,0 +1,48 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_PAD_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_PAD_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class PadOpBuilder : public OpBuilder { + public: + explicit PadOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~PadOpBuilder() override; + + private: + TensorID node_output_; + float input_min_, input_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_PAD_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/pool_2d_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/pool_2d_builder.cc new file mode 100644 index 00000000000..37059842256 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/pool_2d_builder.cc @@ -0,0 +1,136 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/pool_2d_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus Pool2dOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static std::vector quant_bound_shape = {1, 1, 1, 1}; + + // Input data tensor. + int tensor_id = inputs->data[0]; + const auto& data_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + data_tensor, &data_min_, &data_max_, std::numeric_limits::min(), + std::numeric_limits::max())); + auto* data_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&data_min_, sizeof(data_min_)); + auto* data_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&data_max_, sizeof(data_max_)); + AddInput(TensorID(data_min_const->GetID(), 0)); + AddInput(TensorID(data_max_const->GetID(), 0)); + + const TfLitePoolParams* pool_params = + reinterpret_cast(builtin_data_); + + // Padding type. + if (pool_params->padding == kTfLitePaddingSame) { + SetPaddingType(NN_PAD_SAME); + } else if (pool_params->padding == kTfLitePaddingValid) { + SetPaddingType(NN_PAD_VALID); + } + + // Pooling window (filter) width/height as inputs. + static int dummy = 0; + filter_shape_ = {1, pool_params->filter_height, pool_params->filter_width, 1}; + auto* filter_node = graph_builder_->AddConstNodeWithData( + filter_shape_.data(), (char*)&dummy, sizeof(dummy)); + AddInput(TensorID(filter_node->GetID(), 0)); + // Stride width/height as inputs. + stride_shape_ = {1, pool_params->stride_height, pool_params->stride_width, 1}; + auto* stride_node = graph_builder_->AddConstNodeWithData( + stride_shape_.data(), (char*)&dummy, sizeof(dummy)); + AddInput(TensorID(stride_node->GetID(), 0)); + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + + if (op_node_.op_type == OP_QuantizedMaxPool_8) { + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + } else { + // Hexagon's AvgPool output has different min/max bounds than what TFLite + // expects. Therefore, we add a Requantize op to correct the ranges. + TensorID pool_out = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + const auto& pool_out_min = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + const auto& pool_out_max = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + // Output min/max for requantization. + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + context->tensors[outputs->data[0]], &output_min_, &output_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* output_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&output_min_, sizeof(output_min_)); + auto* output_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&output_max_, sizeof(output_max_)); + + auto* requantize_op = graph_builder_->AddNode(); + requantize_op->SetOpType(OP_Requantize_8to8); + requantize_op->AddInput(pool_out); + requantize_op->AddInput(pool_out_min); + requantize_op->AddInput(pool_out_max); + requantize_op->AddInput(TensorID(output_min_const->GetID(), 0)); + requantize_op->AddInput(TensorID(output_max_const->GetID(), 0)); + node_output_ = + requantize_op->AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + requantize_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + } + + return kTfLiteOk; +} + +TfLiteStatus Pool2dOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + + return kTfLiteOk; +} + +Pool2dOpBuilder::~Pool2dOpBuilder() {} + +OpBuilder* CreatePool2DBuilder(GraphBuilder* graph_builder, int op_type) { + return new Pool2dOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/pool_2d_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/pool_2d_builder.h new file mode 100644 index 00000000000..2ea80bcd644 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/pool_2d_builder.h @@ -0,0 +1,50 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_POOL_2D_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_POOL_2D_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class Pool2dOpBuilder : public OpBuilder { + public: + explicit Pool2dOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~Pool2dOpBuilder(); + + private: + TensorID node_output_; + std::vector stride_shape_; + std::vector filter_shape_; + float data_min_, data_max_, output_min_, output_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_POOL_2D_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/reduce_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/reduce_builder.cc new file mode 100644 index 00000000000..b3f6b850390 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/reduce_builder.cc @@ -0,0 +1,119 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/reduce_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus ReduceOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max()); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + + // Min/max values for input tensor. + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Axes tensor should be constant. + tensor_id = inputs->data[1]; + const auto& axes_tensor = context->tensors[tensor_id]; + if (axes_tensor.allocation_type == kTfLiteMmapRo) { + // If the axes input is a constant, bake it into the Hexagon graph as a + // Const node. + auto* const_axes_node = + graph_builder_->AddConstNodeWithData(tensor_id, axes_tensor); + AddInput(TensorID(const_axes_node->GetID(), 0)); + } else { + context->ReportError(context, "Reduction op doesn't have constant axis"); + return kTfLiteError; + } + + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + + // Hexagon's sum-reduction outputs int32, so we shrink it down to UInt8. + if (op_node_.op_type == OP_QuantizedSum_8to32) { + const auto& reduce_out = AddOutput(sizeof(int32_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + const auto& reduce_out_min = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + const auto& reduce_out_max = AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + auto* quantize_output_op = graph_builder_->AddNode(); + quantize_output_op->SetOpType(OP_QuantizeDownAndShrinkRange_32to8); + quantize_output_op->AddInput(reduce_out); + quantize_output_op->AddInput(reduce_out_min); + quantize_output_op->AddInput(reduce_out_max); + node_output_ = + quantize_output_op->AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + quantize_output_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + quantize_output_op->AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + } else { + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + } + + return kTfLiteOk; +} + +TfLiteStatus ReduceOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + + return kTfLiteOk; +} + +ReduceOpBuilder::~ReduceOpBuilder() {} + +OpBuilder* CreateReduceBuilder(GraphBuilder* graph_builder, int op_type) { + return new ReduceOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/reduce_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/reduce_builder.h new file mode 100644 index 00000000000..597ad1ce2d4 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/reduce_builder.h @@ -0,0 +1,48 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_REDUCE_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_REDUCE_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ReduceOpBuilder : public OpBuilder { + public: + explicit ReduceOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ReduceOpBuilder() override; + + private: + TensorID node_output_; + float input_min_, input_max_, output_min_, output_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_REDUCE_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/reshape_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/reshape_builder.cc new file mode 100644 index 00000000000..400b9da85c4 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/reshape_builder.cc @@ -0,0 +1,122 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/reshape_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +namespace { + +void PopulateOutputShapeFromTensor(const TfLiteTensor* shape_tensor, + std::vector* output_shape) { + for (int i = 0; i < shape_tensor->dims->data[0]; ++i) { + output_shape->push_back(shape_tensor->data.i32[i]); + } +} + +void PopulateShapeFromParam(const TfLiteReshapeParams* params, + std::vector* output_shape) { + // The function is returned above this line if the shape tensor is usable. + // Now fallback to the shape parameter in `TfLiteReshapeParams`. + 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; + } + for (int i = 0; i < num_dimensions; ++i) { + output_shape->push_back(params->shape[i]); + } +} +} // namespace + +TfLiteStatus ReshapeOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Input data tensor. + AddInput(graph_builder_->GetHexagonTensorId(inputs->data[0])); + + // Output shape. + TfLiteTensor* shape_tensor; + bool output_shape_is_dynamic = false; + if (inputs->size == 2) { + shape_tensor = &context->tensors[inputs->data[1]]; + bool is_shape_tensor = + (shape_tensor->dims->size == 1 && shape_tensor->type == kTfLiteInt32); + // If tensor shape is dynamic, pass it along directly. + if (shape_tensor->allocation_type != kTfLiteMmapRo && is_shape_tensor) { + output_shape_is_dynamic = true; + AddInput(graph_builder_->GetHexagonTensorId(inputs->data[1])); + } + if (!is_shape_tensor) { + shape_tensor = nullptr; + } + } + if (!output_shape_is_dynamic) { + if (shape_tensor) { + PopulateOutputShapeFromTensor(shape_tensor, &output_shape_); + } else { + const TfLiteReshapeParams* reshape_params = + reinterpret_cast(builtin_data_); + PopulateShapeFromParam(reshape_params, &output_shape_); + } + int num_elements_in_shape = static_cast(output_shape_.size()); + output_shape_shape_ = {1, 1, 1, num_elements_in_shape}; + auto* shape_node = graph_builder_->AddConstNodeWithData( + output_shape_shape_.data(), + reinterpret_cast(output_shape_.data()), + sizeof(int) * num_elements_in_shape); + AddInput(TensorID(shape_node->GetID(), 0)); + } + + // Hexagon output for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + + return kTfLiteOk; +} + +TfLiteStatus ReshapeOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +ReshapeOpBuilder::~ReshapeOpBuilder() {} + +OpBuilder* CreateReshapeBuilder(GraphBuilder* graph_builder, int op_type) { + return new ReshapeOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/reshape_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/reshape_builder.h new file mode 100644 index 00000000000..dff417dda34 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/reshape_builder.h @@ -0,0 +1,49 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESHAPE_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESHAPE_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ReshapeOpBuilder : public OpBuilder { + public: + explicit ReshapeOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ReshapeOpBuilder() override; + + private: + TensorID node_output_; + std::vector output_shape_; + std::vector output_shape_shape_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESHAPE_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/resize_bilinear_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_bilinear_builder.cc new file mode 100644 index 00000000000..0c120426918 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_bilinear_builder.cc @@ -0,0 +1,106 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/resize_bilinear_builder.h" + +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus ResizeBilinearOpBuilder::PopulateSubGraph( + const TfLiteIntArray* inputs, const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + + if (inputs->size != 2) { + context->ReportError(context, "Expecting 2 inputs %d != 2\n", inputs->size); + return kTfLiteError; + } + + // Input data tensor. + int input_tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[input_tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(input_tensor_id)); + + const auto& size_tensor = context->tensors[inputs->data[1]]; + if (!IsConstantTensor(&size_tensor)) { + context->ReportError(context, + "Hexagon Delegate doesn't support dynamic shape.\n"); + return kTfLiteError; + } + // dims tensor. + const int dims_shape[] = {1, 1, 1, 2}; + std::vector dims = {size_tensor.data.i32[0], size_tensor.data.i32[1]}; + auto* dims_const = graph_builder_->AddConstNodeWithData( + dims_shape, reinterpret_cast(dims.data()), + sizeof(int) * dims.size()); + AddInput(TensorID(dims_const->GetID(), 0)); + + // Input min/max + TF_LITE_ENSURE_OK(context, ComputeMinAndMaxQuantValues( + input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + // Align Corners + const TfLiteResizeBilinearParams* params = + reinterpret_cast(builtin_data_); + int align_corners = params->align_corners ? 1 : 0; + auto* align_corners_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&align_corners), + sizeof(align_corners)); + AddInput(TensorID(align_corners_const->GetID(), 0)); + + // Output + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + auto resize_bilinear_out = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + node_output_ = resize_bilinear_out; + + return kTfLiteOk; +} + +TfLiteStatus ResizeBilinearOpBuilder::RegisterOutputs( + const TfLiteIntArray* outputs, TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +ResizeBilinearOpBuilder::~ResizeBilinearOpBuilder() {} + +OpBuilder* CreateResizeBilinearOpBuilder(GraphBuilder* graph_builder, + int op_type) { + return new ResizeBilinearOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/resize_bilinear_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_bilinear_builder.h new file mode 100644 index 00000000000..3dca07f1d81 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_bilinear_builder.h @@ -0,0 +1,46 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESIZE_BILINEAR_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESIZE_BILINEAR_BUILDER_H_ + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ResizeBilinearOpBuilder : public OpBuilder { + public: + explicit ResizeBilinearOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ResizeBilinearOpBuilder() override; + + private: + TensorID node_output_; + float input_min_, input_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESIZE_BILINEAR_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/resize_nearest_neighbor_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_nearest_neighbor_builder.cc new file mode 100644 index 00000000000..f659162ee78 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_nearest_neighbor_builder.cc @@ -0,0 +1,107 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/resize_nearest_neighbor_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus ResizeNearestNeighborOpBuilder::PopulateSubGraph( + const TfLiteIntArray* inputs, const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + + // Output dimensions tensor. + tensor_id = inputs->data[1]; + const auto& output_dim_tensor = context->tensors[tensor_id]; + if (output_dim_tensor.allocation_type == kTfLiteMmapRo) { + // If the output dimensions input is a constant, bake it into the Hexagon + // graph as a Const node. + auto* const_output_dim_node = + graph_builder_->AddConstNodeWithData(tensor_id, output_dim_tensor); + AddInput(TensorID(const_output_dim_node->GetID(), 0)); + } else { + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + } + + // Min/max values for input tensor. + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Align corners. + const TfLiteResizeNearestNeighborParams* params = + reinterpret_cast(builtin_data_); + align_corners_ = params->align_corners; + auto* align_corners_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&align_corners_), + sizeof(align_corners_)); + AddInput(TensorID(align_corners_const->GetID(), 0)); + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus ResizeNearestNeighborOpBuilder::RegisterOutputs( + const TfLiteIntArray* outputs, TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +ResizeNearestNeighborOpBuilder::~ResizeNearestNeighborOpBuilder() {} + +OpBuilder* CreateResizeNearestNeighborBuilder(GraphBuilder* graph_builder, + int op_type) { + return new ResizeNearestNeighborOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/resize_nearest_neighbor_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_nearest_neighbor_builder.h new file mode 100644 index 00000000000..0129340682c --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/resize_nearest_neighbor_builder.h @@ -0,0 +1,50 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESIZE_NEAREST_NEIGHBOR_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESIZE_NEAREST_NEIGHBOR_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class ResizeNearestNeighborOpBuilder : public OpBuilder { + public: + explicit ResizeNearestNeighborOpBuilder(GraphBuilder* graph_builder, + int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~ResizeNearestNeighborOpBuilder() override; + + private: + TensorID node_output_; + float input_min_, input_max_; + bool align_corners_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_RESIZE_NEAREST_NEIGHBOR_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/softmax_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/softmax_builder.cc new file mode 100644 index 00000000000..b4a5b9551e9 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/softmax_builder.cc @@ -0,0 +1,89 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/softmax_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus SoftmaxOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static std::vector quant_bound_shape = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&input_min_, sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&input_max_, sizeof(input_max_)); + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // beta value + const TfLiteSoftmaxParams* softmax_params = + reinterpret_cast(builtin_data_); + beta_value_ = softmax_params->beta; + auto* beta_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&beta_value_, sizeof(beta_value_)); + AddInput(TensorID(beta_const->GetID(), 0)); + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus SoftmaxOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + + return kTfLiteOk; +} + +SoftmaxOpBuilder::~SoftmaxOpBuilder() {} + +OpBuilder* CreateSoftmaxBuilder(GraphBuilder* graph_builder, int op_type) { + return new SoftmaxOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/softmax_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/softmax_builder.h new file mode 100644 index 00000000000..c48a621d2a6 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/softmax_builder.h @@ -0,0 +1,49 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_SOFTMAX_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_SOFTMAX_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class SoftmaxOpBuilder : public OpBuilder { + public: + explicit SoftmaxOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~SoftmaxOpBuilder(); + + private: + TensorID node_output_; + float beta_value_ = 1.0f; + float input_min_, input_max_, output_min_, output_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_SOFTMAX_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/split_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/split_builder.cc new file mode 100644 index 00000000000..3347654e5ac --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/split_builder.cc @@ -0,0 +1,107 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/split_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus SplitOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + + const int input_tensor_id = inputs->data[1]; + const auto& input_tensor = context->tensors[input_tensor_id]; + + // Axis tensor. + const int axis_tensor_id = inputs->data[0]; + const auto& axis = context->tensors[axis_tensor_id]; + if (axis.allocation_type != kTfLiteMmapRo) { + context->ReportError(context, + "Axis tensor doesn't have correct allocation type: %s", + axis.name); + return kTfLiteError; + } + // We pad Hexagon tensor dimensions with 1 if dims.size < 4. + // (4 - input_tensor.dims->size) helps maps the input axis value in such + // cases. + int axis_value = axis.data.i32[0] + (4 - input_tensor.dims->size); + if (axis_value < 0) { + axis_value += input_tensor.dims->size; + } + auto* input_axis_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&axis_value), sizeof(int32_t)); + AddInput(TensorID(input_axis_const->GetID(), 0)); + + // Input data tensor & min/max. + AddInput(graph_builder_->GetHexagonTensorId(input_tensor_id)); + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Output data tensors. + for (int i = 0; i < outputs->size; ++i) { + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[i]].dims); + TensorID output = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + node_outputs_.push_back(output); + } + // For Hexagon output min/max. + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus SplitOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + for (int i = 0; i < node_outputs_.size(); ++i) { + graph_builder_->AddTensorWithID(outputs->data[i], node_outputs_[i].first, + node_outputs_[i].second); + } + return kTfLiteOk; +} + +SplitOpBuilder::~SplitOpBuilder() {} + +OpBuilder* CreateSplitBuilder(GraphBuilder* graph_builder, int op_type) { + return new SplitOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/split_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/split_builder.h new file mode 100644 index 00000000000..1ae7cfb04c8 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/split_builder.h @@ -0,0 +1,49 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_SPLIT_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_SPLIT_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class SplitOpBuilder : public OpBuilder { + public: + explicit SplitOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~SplitOpBuilder() override; + + private: + std::vector node_outputs_; + float input_min_; + float input_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_SPLIT_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_builder.cc new file mode 100644 index 00000000000..c01d5c7e5b0 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_builder.cc @@ -0,0 +1,85 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/transpose_builder.h" + +#include + +#include + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus TransposeOpBuilder::PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) { + static int quant_bound_shape[] = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[0]; + const auto& input_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + // permutation tensor. + tensor_id = inputs->data[1]; + const auto& control_tensor = context->tensors[tensor_id]; + if (control_tensor.allocation_type == kTfLiteMmapRo) { + auto* const_control_tensor_node = + graph_builder_->AddConstNodeWithData(tensor_id, control_tensor); + AddInput(TensorID(const_control_tensor_node->GetID(), 0)); + } else { + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + } + + TF_LITE_ENSURE_STATUS( + ComputeMinAndMaxQuantValues(input_tensor, &input_min_, &input_max_, + std::numeric_limits::min(), + std::numeric_limits::max())); + auto* input_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_min_), + sizeof(input_min_)); + auto* input_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape, reinterpret_cast(&input_max_), + sizeof(input_max_)); + // Min/max values for input tensor. + AddInput(TensorID(input_min_const->GetID(), 0)); + AddInput(TensorID(input_max_const->GetID(), 0)); + + // Hexagon outputs for this node. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + return kTfLiteOk; +} + +TfLiteStatus TransposeOpBuilder::RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) { + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +OpBuilder* CreateTransposeBuilder(GraphBuilder* graph_builder, int op_type) { + return new TransposeOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_builder.h new file mode 100644 index 00000000000..b28f4438419 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_builder.h @@ -0,0 +1,43 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_BUILDER_H_ + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class TransposeOpBuilder : public OpBuilder { + public: + explicit TransposeOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + private: + TensorID node_output_; + float input_min_, input_max_; +}; +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_conv_2d_builder.cc b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_conv_2d_builder.cc new file mode 100644 index 00000000000..54d602b8adc --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_conv_2d_builder.cc @@ -0,0 +1,169 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/builders/transpose_conv_2d_builder.h" + +#include + +#include + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/kernels/padding.h" + +namespace tflite { +namespace delegates { +namespace hexagon { +TfLiteStatus TransposeConv2dOpBuilder::PopulateSubGraph( + const TfLiteIntArray* inputs, const TfLiteIntArray* outputs, + TfLiteContext* context) { + static std::vector quant_bound_shape = {1, 1, 1, 1}; + int tensor_id; + + // Input data tensor. + tensor_id = inputs->data[2]; + const auto& data_tensor = context->tensors[tensor_id]; + AddInput(graph_builder_->GetHexagonTensorId(tensor_id)); + TF_LITE_ENSURE_STATUS(ComputeMinAndMaxQuantValues( + data_tensor, &data_min_, &data_max_, std::numeric_limits::min(), + std::numeric_limits::max())); + auto* data_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&data_min_, sizeof(data_min_)); + auto* data_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&data_max_, sizeof(data_max_)); + + // Weights tensor + tensor_id = inputs->data[1]; + const auto& weights_tensor = context->tensors[tensor_id]; + if (weights_tensor.allocation_type != kTfLiteMmapRo) { + context->ReportError( + context, "Weights tensor doesn't have correct allocation type: %s", + weights_tensor.name); + return kTfLiteError; + } + int filter_batch_size, filter_height_size, filter_width_size, + filter_depth_size; + GetDims(&filter_batch_size, &filter_height_size, &filter_width_size, + &filter_depth_size, weights_tensor.dims); + weight_shape_ = {filter_batch_size, filter_height_size, filter_width_size, + filter_depth_size}; + auto* const_weights_node = graph_builder_->AddConstNodeWithData( + weight_shape_.data(), (char*)weights_tensor.data.raw, + weights_tensor.bytes); + graph_builder_->AddTensorWithID(tensor_id, const_weights_node->GetID(), 0); + AddInput(TensorID(const_weights_node->GetID(), 0)); + ComputeMinAndMaxQuantValues(weights_tensor, &weights_min_, &weights_max_, + std::numeric_limits::min(), + std::numeric_limits::max()); + auto* weights_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&weights_min_, sizeof(weights_min_)); + auto* weights_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&weights_max_, sizeof(weights_max_)); + + // Min/max inputs for data & weights tensors. + AddInput(TensorID(data_min_const->GetID(), 0)); + AddInput(TensorID(data_max_const->GetID(), 0)); + AddInput(TensorID(weights_min_const->GetID(), 0)); + AddInput(TensorID(weights_max_const->GetID(), 0)); + + // Output dims are required to compute padding. + int output_batch_size, output_height_size, output_width_size, + output_depth_size; + GetDims(&output_batch_size, &output_height_size, &output_width_size, + &output_depth_size, context->tensors[outputs->data[0]].dims); + + // Hexagon TransposeConv requires an explicit padding tensor. So we compute + // the same using stride, input & output info. + const TfLiteTransposeConvParams* params = + reinterpret_cast(builtin_data_); + int unused_output_height, unused_output_width; + TfLitePaddingValues padding = ComputePaddingHeightWidth( + params->stride_height, params->stride_width, 1, 1, output_height_size, + output_width_size, filter_height_size, filter_width_size, params->padding, + &unused_output_height, &unused_output_width); + std::vector padding_tensor = {padding.height, padding.height, + padding.width, padding.width}; + std::vector padding_tensor_shape = {1, 1, 2, 2}; + auto* padding_const = graph_builder_->AddConstNodeWithData( + padding_tensor_shape.data(), (char*)padding_tensor.data(), + (sizeof(int) * 4)); + AddInput(TensorID(padding_const->GetID(), 0)); + + // Stride shape. + int stride_height = params->stride_height; + int stride_width = params->stride_width; + static int dummy = 0; + stride_shape_ = {1, stride_height, stride_width, 1}; + auto* stride_node = graph_builder_->AddConstNodeWithData( + stride_shape_.data(), (char*)&dummy, sizeof(dummy)); + AddInput(TensorID(stride_node->GetID(), 0)); + + // TFLite's TransposeConv doesn't have a bias input, so we just feed in 0s. + std::vector bias_data(output_depth_size); + // Hexagon's conv ops require bias as a [1, 1, 1, dout] tensor. + bias_shape_ = {1, 1, 1, output_depth_size}; + auto* bias_const = graph_builder_->AddConstNodeWithData( + bias_shape_.data(), (char*)bias_data.data(), + sizeof(bias_data[0]) * bias_data.size()); + bias_min_ = 0; + bias_max_ = 0; + auto* bias_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&bias_min_, sizeof(bias_min_)); + auto* bias_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&bias_max_, sizeof(bias_max_)); + AddInput(TensorID(bias_const->GetID(), 0)); + AddInput(TensorID(bias_min_const->GetID(), 0)); + AddInput(TensorID(bias_max_const->GetID(), 0)); + + // Output min/max. + ComputeMinAndMaxQuantValues(context->tensors[outputs->data[0]], &output_min_, + &output_max_, std::numeric_limits::min(), + std::numeric_limits::max()); + auto* output_min_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&output_min_, sizeof(output_min_)); + auto* output_max_const = graph_builder_->AddConstNodeWithData( + quant_bound_shape.data(), (char*)&output_max_, sizeof(output_max_)); + AddInput(TensorID(output_min_const->GetID(), 0)); + AddInput(TensorID(output_max_const->GetID(), 0)); + + // Hexagon outputs for this node. + node_output_ = AddOutput(sizeof(uint8_t), 4, + {output_batch_size, output_height_size, + output_width_size, output_depth_size}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + AddOutput(sizeof(float), 4, {1, 1, 1, 1}); + + return kTfLiteOk; +} + +TfLiteStatus TransposeConv2dOpBuilder::RegisterOutputs( + const TfLiteIntArray* outputs, TfLiteContext* context) { + // Should be only 1 output. + graph_builder_->AddTensorWithID(outputs->data[0], node_output_.first, + node_output_.second); + return kTfLiteOk; +} + +TransposeConv2dOpBuilder::~TransposeConv2dOpBuilder() {} + +OpBuilder* CreateTransposeConv2DBuilder(GraphBuilder* graph_builder, + int op_type) { + return new TransposeConv2dOpBuilder(graph_builder, op_type); +} + +} // namespace hexagon +} // namespace delegates +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_conv_2d_builder.h b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_conv_2d_builder.h new file mode 100644 index 00000000000..058595ae15a --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/builders/transpose_conv_2d_builder.h @@ -0,0 +1,53 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_CONV_2D_BUILDER_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_CONV_2D_BUILDER_H_ + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" + +namespace tflite { +namespace delegates { +namespace hexagon { + +class TransposeConv2dOpBuilder : public OpBuilder { + public: + explicit TransposeConv2dOpBuilder(GraphBuilder* graph_builder, int op_type) + : OpBuilder(graph_builder, op_type) {} + TfLiteStatus PopulateSubGraph(const TfLiteIntArray* inputs, + const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + TfLiteStatus RegisterOutputs(const TfLiteIntArray* outputs, + TfLiteContext* context) override; + + ~TransposeConv2dOpBuilder(); + + private: + TensorID node_output_; + std::vector transposed_weights_; + std::vector stride_shape_; + std::vector weight_shape_, bias_shape_; + std::vector bias_data_; + float data_min_, data_max_, weights_min_, weights_max_, bias_min_, bias_max_, + output_min_, output_max_; +}; + +} // namespace hexagon +} // namespace delegates +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_BUILDERS_TRANSPOSE_CONV_2D_BUILDER_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.cc b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.cc new file mode 100644 index 00000000000..2cca5b1b59f --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.cc @@ -0,0 +1,184 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/hexagon_delegate.h" + +#include +#include +#include + +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/context_util.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.h" +#include "tensorflow/lite/experimental/delegates/hexagon/utils.h" +#include "tensorflow/lite/minimal_logging.h" + +namespace tflite { +namespace { +// Should be > 0. > 16 causes problems. +constexpr int kMaxHexagonGraphs = 4; + +TfLiteRegistration GetHexagonKernelRegistration() { + // This is the registration for the Delegate Node that gets added to + // the TFLite graph instead of the subGraph it replaces it. + // It is treated as a an OP node. But in our case + // Init will initialize the delegate + // Invoke will run the delegate graph. + // Prepare for prearing the delegate. + // Free for any cleaning needed by the delegate. + TfLiteRegistration kernel_registration; + kernel_registration.builtin_code = kTfLiteBuiltinDelegate; + kernel_registration.custom_name = "TfLiteHexagonDelegate"; + kernel_registration.free = [](TfLiteContext* context, void* buffer) -> void { + delete reinterpret_cast(buffer); + }; + kernel_registration.init = [](TfLiteContext* context, const char* buffer, + size_t length) -> void* { + const TfLiteDelegateParams* params = + reinterpret_cast(buffer); + auto hexagon_kernel = std::make_unique(); + if (hexagon_kernel->Init(context, params) != kTfLiteOk) { + return nullptr; + } + return hexagon_kernel.release(); + }; + kernel_registration.invoke = [](TfLiteContext* context, + TfLiteNode* node) -> TfLiteStatus { + HexagonDelegateKernel* kernel = + reinterpret_cast(node->user_data); + if (!kernel) { + context->ReportError(context, "Hexagon Kernel was not initialized"); + return kTfLiteError; + } + return kernel->Invoke(context, node); + }; + kernel_registration.prepare = [](TfLiteContext* context, + TfLiteNode* node) -> TfLiteStatus { + if (node->user_data == nullptr) { + context->ReportError(context, "Hexagon Kernel was not initialized"); + return kTfLiteError; + } + HexagonDelegateKernel* kernel = + reinterpret_cast(node->user_data); + return kernel->Prepare(context, node); + }; + + return kernel_registration; +} + +TfLiteStatus DelegatePrepare(TfLiteContext* context, TfLiteDelegate* delegate) { + // Reserve 1 element, since we need first element to be size, will be updated + // later. + std::vector supported_nodes(1); + TfLiteIntArray* plan; + TF_LITE_ENSURE_STATUS(context->GetExecutionPlan(context, &plan)); + TfLiteNode* node; + TfLiteRegistration* registration; + + // Rudimentary mechanism to check how many Hexagon graphs we initialize. + int num_components = 1; + int last_index = -1; + for (int node_index : TfLiteIntArrayView(plan)) { + TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration( + context, node_index, &node, ®istration)); + if (IsNodeSupportedByHexagon(registration, node, context)) { + // If there is a 'break' in node indices, a new subgraph (and therefore, a + // new Hexagon graph) will be created. + if (last_index != -1 && node_index != last_index + 1) { + if (num_components == kMaxHexagonGraphs) { + break; + } + ++num_components; + } + supported_nodes.push_back(node_index); + last_index = node_index; + } + } + // Set first element to the number of nodes to replace. + supported_nodes[0] = supported_nodes.size() - 1; + TFLITE_LOG_PROD(tflite::TFLITE_LOG_INFO, + "Hexagon delegate: %d nodes delegated out of %d nodes.\n", + supported_nodes[0], plan->size); + TfLiteRegistration hexagon_kernel_registration = + GetHexagonKernelRegistration(); + + return context->ReplaceNodeSubsetsWithDelegateKernels( + context, hexagon_kernel_registration, + reinterpret_cast(supported_nodes.data()), delegate); +} + +class HexagonDelegate : public TfLiteDelegate { + public: + explicit HexagonDelegate(const TfLiteHexagonDelegateOptions* params) + : params_(params != nullptr ? *params : TfLiteHexagonDelegateOptions()) {} + + TfLiteHexagonDelegateOptions* params() { return ¶ms_; } + + bool VerifyDelegate() { + auto* hexagon_nn = HexagonNNImplementation(); + if (hexagon_nn == nullptr) { + return false; + } + return hexagon_nn->hexagon_nn_is_device_supported && + hexagon_nn->hexagon_nn_is_device_supported(); + } + + private: + TfLiteHexagonDelegateOptions params_; +}; + +TfLiteDelegate* CreateDelegate(const TfLiteHexagonDelegateOptions* params) { + TfLiteDelegate* delegate = new HexagonDelegate(params); + if (!static_cast(delegate)->VerifyDelegate()) { + delete delegate; + TFLITE_LOG_PROD_ONCE(tflite::TFLITE_LOG_INFO, + "Hexagon Delegate is not supported.\n"); + return nullptr; + } + + delegate->data_ = static_cast(delegate)->params(); + delegate->flags = kTfLiteDelegateFlagsNone; + delegate->Prepare = &DelegatePrepare; + delegate->CopyFromBufferHandle = nullptr; + delegate->CopyToBufferHandle = nullptr; + delegate->FreeBufferHandle = nullptr; + + TFLITE_LOG_PROD_ONCE(tflite::TFLITE_LOG_INFO, + "Created TensorFlow Lite delegate for Hexagon."); + + return delegate; +} + +} // namespace +} // namespace tflite + +TfLiteDelegate* TfLiteHexagonDelegateCreate( + const TfLiteHexagonDelegateOptions* options) { + return tflite::CreateDelegate(options); +} + +void TfLiteHexagonDelegateDelete(TfLiteDelegate* delegate) { delete delegate; } + +void TfLiteHexagonInit() { tflite::HexagonDelegateKernel::InitState(); } + +void TfLiteHexagonInitWithPath(const char* lib_directory_path) { + if (lib_directory_path != nullptr) { + std::string env_var_value = lib_directory_path; + env_var_value += ";/system/lib/rfsa/adsp;/system/vendor/lib/rfsa/adsp;/dsp"; + setenv("ADSP_LIBRARY_PATH", env_var_value.c_str(), 1 /* overwrite */); + } + tflite::HexagonDelegateKernel::InitState(); +} +void TfLiteHexagonTearDown() { tflite::HexagonDelegateKernel::Teardown(); } diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.h b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.h new file mode 100644 index 00000000000..449184f4ff3 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.h @@ -0,0 +1,81 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_DELEGATE_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_DELEGATE_H_ + +#include "tensorflow/lite/c/common.h" + +#ifdef SWIG +#define TFL_CAPI_EXPORT +#else +#if defined(_WIN32) +#ifdef TFL_COMPILE_LIBRARY +#define TFL_CAPI_EXPORT __declspec(dllexport) +#else +#define TFL_CAPI_EXPORT __declspec(dllimport) +#endif // TFL_COMPILE_LIBRARY +#else +#define TFL_CAPI_EXPORT __attribute__((visibility("default"))) +#endif // _WIN32 +#endif // SWIG + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus +struct TFL_CAPI_EXPORT TfLiteHexagonDelegateOptions { + // This corresponds to the debug level in the hexagon SDK. 0 (default) + // means no debug. + int debug_level; + // This corresponds to powersave_level in the hexagon SDK. + // where 0 (default) means high performance which means more power + // consumption. + int powersave_level; + // If set to true, performance information about the graph will be dumped + // to Standard output, this includes cpu cycles. + // WARNING: Experimental and subject to change anytime. + bool print_graph_profile; + // If set to true, graph structure will be dumped to Standard output. + // This is usually beneficial to see what actual nodes executed on + // the DSP. Combining with 'debug_level' more information will be printed. + // WARNING: Experimental and subject to change anytime. + bool print_graph_debug; +}; + +// Return a delegate that uses Hexagon SDK for ops execution. +// Must outlive the interpreter. +TfLiteDelegate* TFL_CAPI_EXPORT +TfLiteHexagonDelegateCreate(const TfLiteHexagonDelegateOptions* options); + +// Do any needed cleanup and delete 'delegate'. +void TFL_CAPI_EXPORT TfLiteHexagonDelegateDelete(TfLiteDelegate* delegate); + +// Initializes the DSP connection. +// This should be called before doing any usage of the delegate. +// "lib_directory_path": Path to the directory which holds the +// shared libraries for the Hexagon NN libraries on the device. +void TFL_CAPI_EXPORT TfLiteHexagonInitWithPath(const char* lib_directory_path); + +// Same as above method but doesn't accept the path params. +// Assumes the environment setup is already done. Only initialize Hexagon. +void TFL_CAPI_EXPORT TfLiteHexagonInit(); + +// Clean up and switch off the DSP connection. +// This should be called after all processing is done and delegate is deleted. +void TFL_CAPI_EXPORT TfLiteHexagonTearDown(); +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_DELEGATE_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.cc b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.cc new file mode 100644 index 00000000000..e65a60fa00b --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.cc @@ -0,0 +1,357 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.h" + +#include +#include + +#include "tensorflow/lite/builtin_ops.h" +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/context_util.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.h" +#include "tensorflow/lite/experimental/delegates/hexagon/utils.h" + +namespace tflite { + +namespace { +inline const char* StateToString( + HexagonDelegateKernel::HexagonKernelState state) { + switch (state) { + case HexagonDelegateKernel::HexagonKernelState::HEALTHY: + return "HEALTHY"; + case HexagonDelegateKernel::HexagonKernelState::FAST_RPC_SETUP_FAILED: + return "FAST_RPC_SETUP_FAILED"; + case HexagonDelegateKernel::HexagonKernelState::FAILED_TO_INIT_GRAPH: + return "FAILED_TO_INIT_GRAPH"; + case HexagonDelegateKernel::HexagonKernelState::FAILED_TO_PREPARE_GRAPH: + return "FAILED_TO_PREPARE_GRAPH"; + case HexagonDelegateKernel::HexagonKernelState::MULTIPLE_INPUTS: + return "MULTIPLE_INPUTS"; + case HexagonDelegateKernel::HexagonKernelState::INPUT_RANK_NOT_SUPPORTED: + return "INPUT_RANK_NOT_SUPPORTED"; + case HexagonDelegateKernel::HexagonKernelState::MULTIPLE_OUTPUTS: + return "MULTIPLE_OUTPUTS"; + case HexagonDelegateKernel::HexagonKernelState::FAILED_TO_EXECUTE_GRAPH: + return "FAILED_TO_EXECUTE_GRAPH"; + } +} + +// Returns uint64 representing total cycles in 'perf_info' by +// combining lo and hi counters. +inline uint64_t GetCycles(const hexagon_nn_perfinfo& perf_info) { + uint64_t res = perf_info.counter_hi; + res <<= 32; + res |= perf_info.counter_lo; + return res; +} + +// Comparator for hexagon_nn_perfinfo in descending order based on +// total cycles consumed. +struct PerfInfoCmp { + bool operator()(const hexagon_nn_perfinfo& a, + const hexagon_nn_perfinfo& b) const { + return GetCycles(a) > GetCycles(b); + } +}; +} // namespace + +void HexagonDelegateKernel::ReportError(TfLiteContext* context, + HexagonKernelState state, + const std::string& msg) { + PrintLog(); + context->ReportError(context, "Failed: %s. STATE: %s", msg.c_str(), + StateToString(state)); +} + +TfLiteStatus HexagonDelegateKernel::Init(TfLiteContext* context, + const TfLiteDelegateParams* params) { + hexagon_nn_ = HexagonNNImplementation(); + if (hexagon_nn_ == nullptr) { + context->ReportError(context, "Hexagon interface not available."); + return kTfLiteError; + } + if (params != nullptr && params->delegate != nullptr) { + const ::TfLiteHexagonDelegateOptions* options_ptr = + reinterpret_cast( + params->delegate->data_); + params_ = (options_ptr == nullptr ? ::TfLiteHexagonDelegateOptions() + : *options_ptr); + } + + // Ensure Hexagon NNLib is ready to start working. + int error = hexagon_nn_->hexagon_nn_config(); + if (error != 0) { + context->ReportError(context, "hexagon_nn_config failed. Error: %d", error); + return kTfLiteError; + } + + // Initialize an empty graph. + error = hexagon_nn_->hexagon_nn_init(&graph_id_); + if (error != 0) { + state_ = HexagonKernelState::FAILED_TO_INIT_GRAPH; + ReportError(context, state_, "failed to init"); + return kTfLiteError; + } + error = + hexagon_nn_->hexagon_nn_set_debug_level(graph_id_, params_.debug_level); + if (error != 0) { + context->ReportError(context, "Failed to set debug level, error: %d", + error); + return kTfLiteError; + } + error = hexagon_nn_->hexagon_nn_set_powersave_level(params_.powersave_level); + if (error != 0) { + context->ReportError(context, "Failed to set powersave level, error %d", + error); + return kTfLiteError; + } + + for (auto node_index : TfLiteIntArrayView(params->nodes_to_replace)) { + nodes_.push_back(node_index); + } + + TF_LITE_ENSURE_STATUS( + BuildGraph(context, params->input_tensors, params->output_tensors)); + return kTfLiteOk; +} + +TfLiteStatus HexagonDelegateKernel::Invoke(TfLiteContext* context, + TfLiteNode* node) { + if (hexagon_nn_ == nullptr) { + context->ReportError(context, "Hexagon interface not available."); + return kTfLiteError; + } + // Allocate inputs. + std::vector input_tensors; + for (auto tensor_index : TfLiteIntArrayView(node->inputs)) { + if (tensor_index == kTfLiteOptionalTensor) { + continue; + } + TfLiteTensor* tensor = &context->tensors[tensor_index]; + // Const tensors should be added as const nodes during graph construction. + if (tensor->allocation_type != kTfLiteMmapRo) { + if (tensor->dims->size > 4) { + ReportError(context, HexagonKernelState::INPUT_RANK_NOT_SUPPORTED, + "Only up to 4d tensor are supported."); + return kTfLiteError; + } + input_tensors.emplace_back(); + auto& input_tensor = input_tensors.back(); + input_tensor.data = reinterpret_cast(tensor->data.raw); + input_tensor.dataLen = tensor->bytes; + input_tensor.data_valid_len = tensor->bytes; + TF_LITE_ENSURE_STATUS( + Get4DShape(&input_tensor.batches, &input_tensor.height, + &input_tensor.width, &input_tensor.depth, tensor->dims)); + } + } + + // Allocate outputs. + std::vector output_tensors; + for (auto tensor_index : TfLiteIntArrayView(node->outputs)) { + if (tensor_index == kTfLiteOptionalTensor) { + continue; + } + TfLiteTensor* tensor = &context->tensors[tensor_index]; + if (tensor->allocation_type != kTfLiteMmapRo) { + if (tensor->dims->size > 4) { + ReportError(context, HexagonKernelState::INPUT_RANK_NOT_SUPPORTED, + "Only up to 4d tensor are supported."); + return kTfLiteError; + } + output_tensors.emplace_back(); + auto& output_tensor = output_tensors.back(); + output_tensor.data = reinterpret_cast(tensor->data.raw); + output_tensor.dataLen = tensor->bytes; + } + } + + if (params_.print_graph_profile) { + hexagon_nn_->hexagon_nn_reset_perfinfo(graph_id_, 0); + } + + // Execute. + int error = hexagon_nn_->hexagon_nn_execute_new( + graph_id_, input_tensors.data(), input_tensors.size(), + output_tensors.data(), output_tensors.size()); + if (error != 0) { + ReportError(context, HexagonKernelState::FAILED_TO_EXECUTE_GRAPH, + "Failed to execute graph."); + return kTfLiteError; + } + if (params_.print_graph_profile) { + PrintPerformanceData(); + } + return kTfLiteOk; +} + +TfLiteStatus HexagonDelegateKernel::Prepare(TfLiteContext* context, + TfLiteNode* node) { + if (hexagon_nn_ == nullptr) { + context->ReportError(context, "Hexagon interface not available. prepare"); + return kTfLiteError; + } + int status = hexagon_nn_->hexagon_nn_prepare(graph_id_); + if (status != 0) { + state_ = HexagonKernelState::FAILED_TO_PREPARE_GRAPH; + ReportError(context, state_, "Failed to prepare graph.\n"); + return kTfLiteError; + } + + // Check input/output tensors. + std::vector tensors; + for (auto tensor_index : TfLiteIntArrayView(node->inputs)) { + tensors.push_back(tensor_index); + } + for (auto tensor_index : TfLiteIntArrayView(node->outputs)) { + tensors.push_back(tensor_index); + } + for (auto tensor_index : tensors) { + if (tensor_index == kTfLiteOptionalTensor) { + continue; + } + TfLiteTensor* tensor = &context->tensors[tensor_index]; + // Const tensors should be added as const nodes during graph construction. + if (tensor->allocation_type != kTfLiteMmapRo && tensor->dims->size > 4) { + ReportError(context, HexagonKernelState::INPUT_RANK_NOT_SUPPORTED, + "Only up to 4d tensor are supported."); + return kTfLiteError; + } + } + + if (params_.print_graph_debug) { + PrintDebuggingGraph(); + } + + return kTfLiteOk; +} + +TfLiteStatus HexagonDelegateKernel::BuildGraph( + TfLiteContext* context, const TfLiteIntArray* input_tensors, + const TfLiteIntArray* output_tensors) { + builder_.reset( + new delegates::hexagon::GraphBuilder(hexagon_nn_, context, graph_id_)); + // Add inputs to the graph. + builder_->AddInputTensors(input_tensors, context); + + // Add all ops. + TfLiteNode* node; + TfLiteRegistration* reg; + for (int node_index : nodes_) { + TF_LITE_ENSURE_STATUS( + context->GetNodeAndRegistration(context, node_index, &node, ®)); + auto* op_builder = builder_->AddNodeFromTfLiteOp(reg->builtin_code, node); + TF_LITE_ENSURE_STATUS( + op_builder->PopulateSubGraph(node->inputs, node->outputs, context)); + TF_LITE_ENSURE_STATUS(op_builder->RegisterOutputs(node->outputs, context)); + } + + // Add Outputs. + builder_->AddOutputTensors(output_tensors, context); + + builder_->Build(); + + return kTfLiteOk; +} + +HexagonDelegateKernel::~HexagonDelegateKernel() { + if (graph_id_ != -1) { + hexagon_nn_->hexagon_nn_teardown(graph_id_); + } +} + +void HexagonDelegateKernel::PrintLog() { + std::vector buf(3000000); + time_t my_time = time(nullptr); + hexagon_nn_->hexagon_nn_getlog(graph_id_, buf.data(), buf.size()); + printf("----------------\n"); + printf("Timestamp: %s\n\n", ctime(&my_time)); + printf("Log\n%s\n", buf.data()); + printf("----------------\n"); + fflush(stdout); +} + +void HexagonDelegateKernel::PrintPerformanceData() { + const int kMaxNodes = 2048; + const int kMaxNameLen = 100; + std::vector perf_data(kMaxNodes); + std::vector op_name(kMaxNameLen); + uint64_t total_cycles = 0; + uint64_t cum_cycles = 0; + uint64_t counter = 0; + unsigned int num_nodes; + printf("------- Performance Debug Data Start -------\n"); + if (hexagon_nn_->hexagon_nn_get_perfinfo(graph_id_, perf_data.data(), + kMaxNodes, &num_nodes) != 0) { + printf("Failed fetching perf data.\n"); + return; + } + printf("Total %d nodes.\n", num_nodes); + std::sort(perf_data.begin(), perf_data.begin() + num_nodes, PerfInfoCmp()); + for (int i = 0; i < num_nodes; i++) { + total_cycles += GetCycles(perf_data[i]); + } + printf("Total %lu cycles\n", static_cast(total_cycles)); + printf( + "Node ID,\tOP Name,\tExecutions,\tCycles,\t%% of total,\tCummulative " + "cycles,\tCummulative %%\n"); + for (int i = 0; i < num_nodes; i++) { + counter = GetCycles(perf_data[i]); + cum_cycles += counter; + int op_type_id = builder_->GetOpTypeId(perf_data[i].node_id); + if (op_type_id >= 0 && hexagon_nn_->hexagon_nn_op_id_to_name( + op_type_id, op_name.data(), kMaxNameLen) != 0) { + printf("Failed to fetch name for %u with type %d\n", perf_data[i].node_id, + op_type_id); + continue; + } + printf("0x%x,\t%s,\t%d,\t%lu,\t%f %%,\t%lu,\t%f %%\n", perf_data[i].node_id, + (op_type_id < 0 ? "" : op_name.data()), perf_data[i].executions, + static_cast(counter), + 100.0 * (1.0 * counter / total_cycles), + static_cast(cum_cycles), + 100.0 * (1.0 * cum_cycles / total_cycles)); + } + printf("------- Performance Debug Data End -------\n"); +} + +void HexagonDelegateKernel::PrintDebuggingGraph() { + const int kMaxBufLen = 100000; + std::vector buf(kMaxBufLen); + if (hexagon_nn_->hexagon_nn_snpprint(graph_id_, buf.data(), kMaxBufLen) != + 0) { + printf("Error fetching graph debug details.\n"); + return; + } + printf("------- Graph Debugging Start -------\n"); + printf("%s\n", buf.data()); + printf("------- Graph Debugging End -------\n"); +} + +void HexagonDelegateKernel::Teardown() { + auto* hexagon_nn = HexagonNNImplementation(); + if (hexagon_nn != nullptr) { + hexagon_nn->hexagon_nn_global_teardown(); + } +} + +void HexagonDelegateKernel::InitState() { + auto* hexagon_nn = HexagonNNImplementation(); + if (hexagon_nn != nullptr) { + hexagon_nn->hexagon_nn_global_init(); + } +} +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.h b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.h new file mode 100644 index 00000000000..a360b827e22 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate_kernel.h @@ -0,0 +1,100 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_DELEGATE_KERNEL_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_DELEGATE_KERNEL_H_ + +#include + +#include +#include +#include +#include + +#include "hexagon/hexagon_nn_ops.h" +#include "tensorflow/lite/builtin_ops.h" +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/experimental/delegates/hexagon/builders/op_builder.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" +#include "tensorflow/lite/schema/schema_generated.h" + +namespace tflite { + +// Represents an abstraction of a Hexagon NNLib graph with functionality to +// initialize, prepare and invoke it based on the TFLite subgraph to be +// delegated. +class HexagonDelegateKernel { + public: + enum class HexagonKernelState { + HEALTHY = 0, + FAST_RPC_SETUP_FAILED = 1, + FAILED_TO_INIT_GRAPH = 2, + FAILED_TO_PREPARE_GRAPH = 3, + MULTIPLE_INPUTS = 4, + INPUT_RANK_NOT_SUPPORTED = 5, + MULTIPLE_OUTPUTS = 6, + FAILED_TO_EXECUTE_GRAPH = 7, + }; + + // Initialize the Hexagon graph and add required nodes. + TfLiteStatus Init(TfLiteContext* context, const TfLiteDelegateParams* params); + + // Prepare the Hexagon graph with hexagon_nn_prepare. + TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node); + + // Allocate Hexagon tensordefs for graph I/O & execute it. + TfLiteStatus Invoke(TfLiteContext* context, TfLiteNode* node); + + ~HexagonDelegateKernel(); + + // Sets the environment required for Hexagon execution: DSP attributes, + // rpcmem, etc. + static void InitState(); + + // Teardown the environment initialized in InitState. + static void Teardown(); + + private: + // Builds the Hexagon graph based on delegated TFLite subgraph. + TfLiteStatus BuildGraph(TfLiteContext* context, + const TfLiteIntArray* input_tensors, + const TfLiteIntArray* output_tensors); + + void ReportError(TfLiteContext* context, HexagonKernelState state, + const std::string& msg); + + void PrintLog(); + + // Prints performance information about the graph including cycles per node. + void PrintPerformanceData(); + + // Print debugging information about the graph constructed. + // Amount of information can be increased with debug level. + void PrintDebuggingGraph(); + + HexagonKernelState state_ = HexagonKernelState::HEALTHY; + const HexagonNN* hexagon_nn_ = nullptr; // Not owned. + std::unique_ptr builder_; + hexagon_nn_nn_id graph_id_ = -1; + // Indices of nodes in the delegated TfLite subgraph. + std::vector nodes_; + ::TfLiteHexagonDelegateOptions params_; +}; + +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_DELEGATE_KERNEL_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.cc b/tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.cc new file mode 100644 index 00000000000..9499f4b388d --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.cc @@ -0,0 +1,89 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/hexagon_implementation.h" + +#include +#include + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn_interface.h" +#include "tensorflow/lite/kernels/internal/compatibility.h" +#include "tensorflow/lite/minimal_logging.h" + +namespace tflite { +namespace { + +void* LoadFunction(void* dl_handle, const char* name) { + TFLITE_DCHECK(dl_handle != nullptr); + auto* func_pt = dlsym(dl_handle, name); + if (func_pt == nullptr) { + TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Function %s is NULL", name); + } + return func_pt; +} + +#define LOAD_FUNCTION(dl_handle, method_name, hexagon_obj) \ + hexagon_obj.method_name = reinterpret_cast( \ + LoadFunction(dl_handle, #method_name)); \ + if ((hexagon_obj.method_name) == nullptr) { \ + TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "%s is NULL", (#method_name)); \ + return hexagon_obj; \ + } + +HexagonNN CreateNewHexagonInterface() { + HexagonNN hexagon_nn; + void* libhexagon_interface = + dlopen("libhexagon_interface.so", RTLD_LAZY | RTLD_LOCAL); + if (libhexagon_interface == nullptr) { + TFLITE_LOG_PROD(TFLITE_LOG_ERROR, "Failed to load libhexagon_interface.so"); + return hexagon_nn; + } + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_config, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_init, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_prepare, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_set_powersave_level, + hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_set_debug_level, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_append_node, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_append_const_node, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_execute, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_execute_new, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_teardown, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_snpprint, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_getlog, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_get_perfinfo, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_reset_perfinfo, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_op_id_to_name, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_global_teardown, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_global_init, hexagon_nn); + LOAD_FUNCTION(libhexagon_interface, hexagon_nn_is_device_supported, + hexagon_nn); + hexagon_nn.interface_loaded = true; + return hexagon_nn; +} + +} // namespace + +const HexagonNN* HexagonNNImplementation() { + static HexagonNN hexagon_nn = CreateNewHexagonInterface(); + if (!hexagon_nn.interface_loaded) { + return nullptr; + } + return &hexagon_nn; +} + +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.h b/tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.h new file mode 100644 index 00000000000..25168d46070 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_implementation.h @@ -0,0 +1,137 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_IMPLEMENTATION_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_IMPLEMENTATION_H_ + +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn_interface.h" + +namespace tflite { +// Holds the methods to use to Construct/Execute NN graph using Hexagon NNLib. +struct HexagonNN { + // Call this function before creating a graph. It allows the environment on + // the DSP to configure some settings. + hexagon_nn_config_fn* hexagon_nn_config; + + // Creates a new graph and returns an identifier to refer to the new graph. + // After a graph is + // initialized, nodes can be added to it. + // The returned graph is empty and cannot be executed until all nodes have + // been added and the graph is finalized with hexagon_nn_prepare(). Multiple + // graphs can be created and can be kept alive in the DSP environment + // simultaneously. + hexagon_nn_init_fn* hexagon_nn_init; + + // Provides a simple parameter between 0 and 255 to control the power saving + // mode. + // A level of 255 indicates that preference should be given to minimizing + // power consumption. A level of 0 indicates that preference should be given + // to executing as fast as possible. + // + // Returns 0 on success, otherwise failure. + hexagon_nn_set_powersave_level_fn* hexagon_nn_set_powersave_level; + + // Changes the debug verbosity level for messages. + hexagon_nn_set_debug_level_fn* hexagon_nn_set_debug_level; + + // Prepares a network for execution. + // This function is required after all the nodes have been appended and before + // execution. + // This call provides a hook where memory can be allocated, data + // can be rearranged, inputs and outputs can be linked up, and things in the + // graph can be optimized. + // Once a network has been prepared, it can no longer + // be appended to, but it can be executed. + // + // Returns 0 on success, otherwise failure. + hexagon_nn_prepare_fn* hexagon_nn_prepare; + + // Adds an ordinary (non-constant) node to the graph. + // Non-constant nodes can have zero or more inputs and zero or more outputs. + // An input is described as a source node ID as well as an output index to + // refer to which one of several outputs a node may have. + // An output is described with a maximum size. The true size of an output can + // be computed dynamically, but the caller must define the maximum amount of + // data storage required by the output during node creation. + // + // Returns 0 on success, otherwise failure. + hexagon_nn_append_node_fn* hexagon_nn_append_node; + + // Adds constant nodes to a graph. + // Constant nodes produce a single output that can be connected to one graph + // node input. Unique node_ids are required for referencing nodes when + // connecting the graph (for example, specifying which outputs of earlier + // nodes will be used as inputs to particular subsequent nodes). Node_ids are + // selected by the caller, but node_id=0 and node_id>0xF0000000 are reserved. + // Node_ids must be unique. + // *** NOTE: On SDM835 and older targets, + // hexagon_nn_append_const_node() will not work properly for arrays larger + // than 32 MB. Instead, use hexagon_nn_append_empty_const_node_large_array(), + // which expects the same arguments. + // + // Returns 0 on success, otherwise failure. + hexagon_nn_append_const_node_fn* hexagon_nn_append_const_node; + + // Executes a network, with provided input data and returning output data. + // Execution will fail if the network has not been prepared. + // Input is provided to the INPUT node, and output is returned from the OUTPUT + // node. + // + // Returns 0 on success, otherwise failure. + hexagon_nn_execute_fn* hexagon_nn_execute; + + // Newer version of hexagon_nn_execute that utilizes hexagon_nn_tensordefs to + // represent inputs & outputs. Executes a network with provided input tensors + // and returns output tensors. Execution will fail if the network has not + // been prepared. + // + // Returns 0 on success, otherwise failure. + hexagon_nn_execute_new_fn* hexagon_nn_execute_new; + + // Tears down and frees an NN graph. This can be done at any time after + // hexagon_nn_init(). After this function has been invoked, the nn_id id is + // invalid. + // + // Returns 0 on success, otherwise failure. + hexagon_nn_teardown_fn* hexagon_nn_teardown; + + hexagon_nn_snpprint_fn* hexagon_nn_snpprint; + + hexagon_nn_getlog_fn* hexagon_nn_getlog; + + hexagon_nn_get_perfinfo_fn* hexagon_nn_get_perfinfo; + + hexagon_nn_reset_perfinfo_fn* hexagon_nn_reset_perfinfo; + + hexagon_nn_op_id_to_name_fn* hexagon_nn_op_id_to_name; + + // Should be called once to shutdown DSP and cleanup. + hexagon_nn_global_teardown_fn* hexagon_nn_global_teardown; + + // Should be called once to initialize DSP. + hexagon_nn_global_init_fn* hexagon_nn_global_init; + + // Returns true if the device SoC is supported by hexagon library. False + // Otherwise. + hexagon_nn_is_device_supported_fn* hexagon_nn_is_device_supported; + + bool interface_loaded = false; +}; + +// Returns an instance of HexagonNN. +const HexagonNN* HexagonNNImplementation(); + +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_IMPLEMENTATION_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/BUILD b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/BUILD new file mode 100644 index 00000000000..36a7d1712c7 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/BUILD @@ -0,0 +1,31 @@ +# Copyright 2019 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(default_visibility = [ + "//visibility:public", +]) + +licenses(["notice"]) # Apache 2.0 + +cc_library( + name = "hexagon_nn_header", + hdrs = [ + "hexagon_nn.h", + "hexagon_nn_init.h", + ], + deps = [ + "@hexagon_nn//:hexagon_nn_header", + ], +) diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h new file mode 100644 index 00000000000..31770fbbad5 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h @@ -0,0 +1,21 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_HEXAGON_NN_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_HEXAGON_NN_H_ + +#include "hexagon/hexagon_nn.h" +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn_init.h" + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_HEXAGON_NN_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn_init.h b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn_init.h new file mode 100644 index 00000000000..812eb792a5c --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn_init.h @@ -0,0 +1,27 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_HEXAGON_NN_INIT_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_HEXAGON_NN_INIT_H_ + +#ifdef __cplusplus +extern "C" { +#endif +void hexagon_nn_global_teardown(void); +void hexagon_nn_global_init(void); +bool hexagon_nn_is_device_supported(); +#ifdef __cplusplus +} +#endif +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_HEXAGON_NN_INIT_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/version_scripts.lds b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/version_scripts.lds new file mode 100644 index 00000000000..5dee3500478 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/version_scripts.lds @@ -0,0 +1,25 @@ +VERS_1.0 { + global: + hexagon_nn_config; + hexagon_nn_init; + hexagon_nn_prepare; + hexagon_nn_set_powersave_level; + hexagon_nn_set_debug_level; + hexagon_nn_append_node; + hexagon_nn_append_const_node; + hexagon_nn_execute; + hexagon_nn_execute_new; + hexagon_nn_teardown; + hexagon_nn_snpprint; + hexagon_nn_getlog; + hexagon_nn_get_perfinfo; + hexagon_nn_reset_perfinfo; + hexagon_nn_op_id_to_name; + hexagon_nn_global_teardown; + hexagon_nn_global_init; + hexagon_nn_is_device_supported; + + # Hide everything else. + local: + *; +}; \ No newline at end of file diff --git a/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn_interface.h b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn_interface.h new file mode 100644 index 00000000000..a2e3a765919 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/hexagon_nn_interface.h @@ -0,0 +1,57 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_INTERFACE_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_INTERFACE_H_ + +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_nn/hexagon_nn.h" + +using hexagon_nn_config_fn = decltype(hexagon_nn_config); +using hexagon_nn_init_fn = decltype(hexagon_nn_init); + +using hexagon_nn_set_powersave_level_fn = + decltype(hexagon_nn_set_powersave_level); + +using hexagon_nn_set_debug_level_fn = decltype(hexagon_nn_set_debug_level); + +using hexagon_nn_prepare_fn = decltype(hexagon_nn_prepare); + +using hexagon_nn_append_node_fn = decltype(hexagon_nn_append_node); + +using hexagon_nn_append_const_node_fn = decltype(hexagon_nn_append_const_node); + +using hexagon_nn_execute_fn = decltype(hexagon_nn_execute); + +using hexagon_nn_execute_new_fn = decltype(hexagon_nn_execute_new); + +using hexagon_nn_teardown_fn = decltype(hexagon_nn_teardown); + +using hexagon_nn_snpprint_fn = decltype(hexagon_nn_snpprint); + +using hexagon_nn_getlog_fn = decltype(hexagon_nn_getlog); + +using hexagon_nn_get_perfinfo_fn = decltype(hexagon_nn_get_perfinfo); + +using hexagon_nn_reset_perfinfo_fn = decltype(hexagon_nn_reset_perfinfo); + +using hexagon_nn_op_id_to_name_fn = decltype(hexagon_nn_op_id_to_name); + +using hexagon_nn_global_teardown_fn = decltype(hexagon_nn_global_teardown); + +using hexagon_nn_global_init_fn = decltype(hexagon_nn_global_init); + +using hexagon_nn_is_device_supported_fn = + decltype(hexagon_nn_is_device_supported); + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_HEXAGON_NN_INTERFACE_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/java/AndroidManifest.xml b/tensorflow/lite/experimental/delegates/hexagon/java/AndroidManifest.xml new file mode 100644 index 00000000000..a76a727ec75 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/java/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/tensorflow/lite/experimental/delegates/hexagon/java/BUILD b/tensorflow/lite/experimental/delegates/hexagon/java/BUILD new file mode 100644 index 00000000000..0502c0f822e --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/java/BUILD @@ -0,0 +1,62 @@ +load("@build_bazel_rules_android//android:rules.bzl", "android_library") +load("//tensorflow/lite:build_def.bzl", "tflite_jni_binary") +load("//tensorflow/lite/java:aar_with_jni.bzl", "aar_with_jni") + +package( + default_visibility = ["//visibility:public"], + licenses = ["notice"], # Apache 2.0 +) + +# EXPERIMENTAL: Native target that runs inference on the Hexagon backend. +# The Hexagon backend-related targets are intentionally not included in BUILD.bazel. +tflite_jni_binary( + name = "libtensorflowlite_hexagon_jni.so", + linkscript = "//tensorflow/lite/experimental/delegates/hexagon:version_script.lds", + tags = [ + "manual", + "nobuilder", + "notap", + ], + deps = [ + "//tensorflow/lite/experimental/delegates/hexagon/java/src/main/native", + ], +) + +cc_library( + name = "tensorflowlite_hexagon", + srcs = [ + "libtensorflowlite_hexagon_jni.so", + ] + select({ + "//tensorflow:android_arm64": ["@hexagon_nn//:hexagon/arm64-v8a/libhexagon_interface.so"], + "//tensorflow:android_arm": ["@hexagon_nn//:hexagon/armeabi-v7a/libhexagon_interface.so"], + "//conditions:default": [], + }), + tags = [ + "manual", + "nobuilder", + "notap", + ], +) + +android_library( + name = "tensorflowlite_java_hexagon", + srcs = ["//tensorflow/lite/experimental/delegates/hexagon/java/src/main/java/org/tensorflow/lite/experimental:hexagon_delegate"], + manifest = "AndroidManifest.xml", + proguard_specs = ["proguard.flags"], + tags = [ + "manual", + "nobuilder", + "notap", + ], + deps = [ + ":tensorflowlite_hexagon", + "//tensorflow/lite/java:tensorflowlite_java", + "@org_checkerframework_qual", + ], +) + +aar_with_jni( + name = "tensorflow-lite-hexagon", + android_library = ":tensorflowlite_java_hexagon", + headers = ["//tensorflow/lite/experimental/delegates/hexagon:hexagon_delegate.h"], +) diff --git a/tensorflow/lite/experimental/delegates/hexagon/java/proguard.flags b/tensorflow/lite/experimental/delegates/hexagon/java/proguard.flags new file mode 100644 index 00000000000..8ee3d7e7ae7 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/java/proguard.flags @@ -0,0 +1,3 @@ +-keepclassmembers class org.tensorflow.lite.NativeInterpreterWrapper { + private long inferenceDurationNanoseconds; +} \ No newline at end of file diff --git a/tensorflow/lite/experimental/delegates/hexagon/java/src/main/java/org/tensorflow/lite/experimental/BUILD b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/java/org/tensorflow/lite/experimental/BUILD new file mode 100644 index 00000000000..535bdf1fd22 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/java/org/tensorflow/lite/experimental/BUILD @@ -0,0 +1,7 @@ +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "hexagon_delegate", + srcs = ["HexagonDelegate.java"], + visibility = ["//visibility:public"], +) diff --git a/tensorflow/lite/experimental/delegates/hexagon/java/src/main/java/org/tensorflow/lite/experimental/HexagonDelegate.java b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/java/org/tensorflow/lite/experimental/HexagonDelegate.java new file mode 100644 index 00000000000..ac335884cd4 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/java/org/tensorflow/lite/experimental/HexagonDelegate.java @@ -0,0 +1,69 @@ +/* Copyright 2019 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.lite.experimental; + +import android.content.Context; +import java.io.Closeable; +import org.tensorflow.lite.Delegate; + +/** {@link Delegate} for Hexagon inference. */ +public class HexagonDelegate implements Delegate, Closeable { + + private static final long INVALID_DELEGATE_HANDLE = 0; + private static final String TFLITE_HEXAGON_LIB = "tensorflowlite_hexagon_jni"; + + private long delegateHandle; + + /* + * Creates a new HexagonDelegate object given the current 'context'. + * Throws UnsupportedOperationException if Hexagon DSP delegation is not available + * on this device. + */ + public HexagonDelegate(Context context) throws UnsupportedOperationException { + setAdspLibraryPath(context.getApplicationInfo().nativeLibraryDir); + delegateHandle = createDelegate(); + if (delegateHandle == INVALID_DELEGATE_HANDLE) { + throw new UnsupportedOperationException("This Device doesn't support Hexagon DSP execution."); + } + } + + @Override + public long getNativeHandle() { + return delegateHandle; + } + + /** + * Frees TFLite resources in C runtime. + * + *

User is expected to call this method explicitly. + */ + @Override + public void close() { + if (delegateHandle != INVALID_DELEGATE_HANDLE) { + deleteDelegate(delegateHandle); + delegateHandle = INVALID_DELEGATE_HANDLE; + } + } + + static { + System.loadLibrary(TFLITE_HEXAGON_LIB); + } + + private static native long createDelegate(); + + private static native void deleteDelegate(long delegateHandle); + + private static native boolean setAdspLibraryPath(String libraryPath); +} diff --git a/tensorflow/lite/experimental/delegates/hexagon/java/src/main/native/BUILD b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/native/BUILD new file mode 100644 index 00000000000..fa0ee8a8897 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/native/BUILD @@ -0,0 +1,25 @@ +# Description: +# Java Native Interface (JNI) library intended for implementing the +# TensorFlow Lite Hexagon delegate Java API using the TensorFlow Lite CC library. + +package(default_visibility = ["//tensorflow/lite/experimental/delegates/hexagon/java:__subpackages__"]) + +load("//tensorflow/lite:build_def.bzl", "tflite_copts") + +licenses(["notice"]) # Apache 2.0 + +cc_library( + name = "native", + srcs = ["hexagon_delegate_jni.cc"], + copts = tflite_copts(), + tags = [ + "manual", + "nobuilder", + "notap", + ], + deps = [ + "//tensorflow/lite/experimental/delegates/hexagon:hexagon_delegate", + "//tensorflow/lite/java/jni", + ], + alwayslink = 1, +) diff --git a/tensorflow/lite/experimental/delegates/hexagon/java/src/main/native/hexagon_delegate_jni.cc b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/native/hexagon_delegate_jni.cc new file mode 100644 index 00000000000..e5bfce314e3 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/java/src/main/native/hexagon_delegate_jni.cc @@ -0,0 +1,55 @@ +/* Copyright 2019 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 + +#include + +#include "tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.h" + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jlong JNICALL +Java_org_tensorflow_lite_experimental_HexagonDelegate_createDelegate( + JNIEnv* env, jclass clazz) { + // Auto-choosing the best performing config for closed release. + TfLiteHexagonDelegateOptions options = {0}; + TfLiteHexagonInit(); + return reinterpret_cast(TfLiteHexagonDelegateCreate(&options)); +} + +JNIEXPORT void JNICALL +Java_org_tensorflow_lite_experimental_HexagonDelegate_deleteDelegate( + JNIEnv* env, jclass clazz, jlong delegate) { + TfLiteHexagonDelegateDelete(reinterpret_cast(delegate)); + TfLiteHexagonTearDown(); +} + +JNIEXPORT jboolean JNICALL +Java_org_tensorflow_lite_experimental_HexagonDelegate_setAdspLibraryPath( + JNIEnv* env, jclass clazz, jstring native_lib_path) { + const char* lib_dir_path = env->GetStringUTFChars(native_lib_path, nullptr); + std::stringstream path; + path << lib_dir_path + << ";/system/lib/rfsa/adsp;/system/vendor/lib/rfsa/adsp;/dsp"; + return setenv("ADSP_LIBRARY_PATH", path.str().c_str(), 1 /*override*/) == 0 + ? JNI_TRUE + : JNI_FALSE; +} + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/tensorflow/lite/experimental/delegates/hexagon/utils.cc b/tensorflow/lite/experimental/delegates/hexagon/utils.cc new file mode 100644 index 00000000000..c9f8c67c0e7 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/utils.cc @@ -0,0 +1,270 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/utils.h" + +#include + +#include "tensorflow/lite/builtin_ops.h" +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/kernel_util.h" + +namespace tflite { +namespace { + +bool IsActivationReluOrNone(TfLiteFusedActivation activation) { + return (activation == kTfLiteActRelu || activation == kTfLiteActRelu6 || + activation == kTfLiteActRelu1 || activation == kTfLiteActNone); +} + +bool TensorTypeMatch(int tensor_id, TfLiteContext* context, + TfLiteType tensor_type) { + const auto& tensor = context->tensors[tensor_id]; + return tensor.type == tensor_type; +} + +bool InputsWithCorrectTypes(const TfLiteNode* node, TfLiteContext* context, + const std::vector& input_types) { + if (node->inputs->size != input_types.size()) return false; + for (int i = 0; i < input_types.size(); ++i) { + if (!TensorTypeMatch(node->inputs->data[i], context, input_types[i])) + return false; + } + return true; +} + +} // namespace + +TfLiteStatus Get4DShape(unsigned int* batch_size, unsigned int* height_size, + unsigned int* width_size, unsigned int* depth_size, + TfLiteIntArray* dims) { + if (dims->size > 4) return kTfLiteError; + unsigned int* dim[] = {batch_size, height_size, width_size, depth_size}; + for (int i = 0; i < 4; ++i) *(dim[i]) = 1; + for (int i = 4 - dims->size; i < 4; ++i) { + *dim[i] = dims->data[i - (4 - dims->size)]; + } + return kTfLiteOk; +} + +bool IsNodeSupportedByHexagon(const TfLiteRegistration* registration, + const TfLiteNode* node, TfLiteContext* context) { + // Ensure all inputs & outputs have dim <= 4. + int tensor_id; + for (int i = 0; i < node->inputs->size; ++i) { + tensor_id = node->inputs->data[i]; + const auto& tensor = context->tensors[tensor_id]; + if (tensor.dims->size > 4) return false; + } + for (int i = 0; i < node->outputs->size; ++i) { + tensor_id = node->outputs->data[i]; + const auto& tensor = context->tensors[tensor_id]; + if (tensor.dims->size > 4) return false; + } + + // Most hexagon kernels are not compatible with op versions > 1. + // We maintain a 'whitelist' here to ensure we don't accept unintended nodes. + if (registration->version > 1) { + if (registration->builtin_code == kTfLiteBuiltinDepthwiseConv2d && + registration->version == 2) { + return true; + } + return false; + } + + switch (registration->builtin_code) { + case kTfLiteBuiltinAdd: { + if (!InputsWithCorrectTypes(node, context, {kTfLiteUInt8, kTfLiteUInt8})) + return false; + const TfLiteAddParams* add_params = + reinterpret_cast(node->builtin_data); + return IsActivationReluOrNone(add_params->activation); + } + case kTfLiteBuiltinMul: { + if (!InputsWithCorrectTypes(node, context, {kTfLiteUInt8, kTfLiteUInt8})) + return false; + const TfLiteMulParams* mul_params = + reinterpret_cast(node->builtin_data); + // TODO(b/129276536): Add support for activation on Mul node. + return mul_params->activation == kTfLiteActNone; + } + case kTfLiteBuiltinSub: { + if (!InputsWithCorrectTypes(node, context, {kTfLiteUInt8, kTfLiteUInt8})) + return false; + const TfLiteSubParams* sub_params = + reinterpret_cast(node->builtin_data); + return IsActivationReluOrNone(sub_params->activation); + } + case kTfLiteBuiltinSum: + case kTfLiteBuiltinMean: { + // TODO(b/139277813): Enable these when they pass unit tests. These seem + // to recompute the output min/max instead of taking them as inputs, which + // causes an unexpected shift in dequantized values. + return false; + } + case kTfLiteBuiltinPad: { + // TODO(b/139277813): Currently we only support padding with the default + // of 0. Add support for user-defined constant if required. + return ( + node->inputs->size == 2 && + InputsWithCorrectTypes(node, context, {kTfLiteUInt8, kTfLiteInt32}) && + IsConstantTensor(&context->tensors[node->inputs->data[1]])); + } + case kTfLiteBuiltinFullyConnected: { + if (!InputsWithCorrectTypes(node, context, + {kTfLiteUInt8, kTfLiteUInt8, kTfLiteInt32})) + return false; + const TfLiteFullyConnectedParams* matmul_params = + reinterpret_cast( + node->builtin_data); + return (IsActivationReluOrNone(matmul_params->activation) && + matmul_params->keep_num_dims == false && + matmul_params->weights_format == + kTfLiteFullyConnectedWeightsFormatDefault); + } + case kTfLiteBuiltinConcatenation: { + // All concatenated tensors must be Uint8 type. + for (int i = 0; i < node->inputs->size; ++i) { + if (!TensorTypeMatch(node->inputs->data[i], context, kTfLiteUInt8)) + return false; + } + // Hexagon only supports concatenation at axis 3. + const TfLiteConcatenationParams* concat_params = + reinterpret_cast( + node->builtin_data); + return (concat_params->axis == 3); + } + case kTfLiteBuiltinMaxPool2d: { + if (!InputsWithCorrectTypes(node, context, {kTfLiteUInt8})) return false; + // TODO(b/129276536): Add support for activation here. + const TfLitePoolParams* pool_params = + reinterpret_cast(node->builtin_data); + return pool_params->activation == kTfLiteActNone; + } + case kTfLiteBuiltinAveragePool2d: { + if (!InputsWithCorrectTypes(node, context, {kTfLiteUInt8})) return false; + // AvgPool works fine for filter dim <=7. + const TfLitePoolParams* pool_params = + reinterpret_cast(node->builtin_data); + return (node->inputs->size == 1 && + pool_params->activation == kTfLiteActNone); + } + case kTfLiteBuiltinTransposeConv: { + if (!InputsWithCorrectTypes(node, context, + {kTfLiteInt32, kTfLiteUInt8, kTfLiteUInt8})) + return false; + const TfLiteTransposeConvParams* params = + reinterpret_cast( + node->builtin_data); + return (params->stride_height <= 3 && params->stride_width <= 3 && + (params->padding == kTfLitePaddingSame || + params->padding == kTfLitePaddingValid)); + } + case kTfLiteBuiltinConv2d: { + if (!InputsWithCorrectTypes(node, context, + {kTfLiteUInt8, kTfLiteUInt8, kTfLiteInt32})) + return false; + const TfLiteConvParams* conv_params = + reinterpret_cast(node->builtin_data); + return (IsActivationReluOrNone(conv_params->activation) && + conv_params->stride_height <= 3 && + conv_params->stride_width <= 3 && + conv_params->dilation_height_factor == 1 && + conv_params->dilation_width_factor == 1); + } + case kTfLiteBuiltinDepthwiseConv2d: { + if (!InputsWithCorrectTypes(node, context, + {kTfLiteUInt8, kTfLiteUInt8, kTfLiteInt32})) + return false; + // Hexagon only supports width of 3 for Depthwise Conv. + const auto& tensor = context->tensors[node->inputs->data[1]]; + if (tensor.dims->data[2] != 3) return false; + const TfLiteDepthwiseConvParams* conv_params = + reinterpret_cast( + node->builtin_data); + const bool dilation = conv_params->dilation_height_factor != 1 || + conv_params->dilation_width_factor != 1; + if (dilation) { + // We only support dilations when stride == 1. + if (conv_params->stride_height != 1 || conv_params->stride_width != 1) + return false; + } + return (IsActivationReluOrNone(conv_params->activation) && + conv_params->stride_height <= 3 && + conv_params->stride_width <= 3 && + conv_params->depth_multiplier == 1); + } + case kTfLiteBuiltinReshape: { + if (node->inputs->size > 2 || + !TensorTypeMatch(node->inputs->data[0], context, kTfLiteUInt8)) + return false; + return true; + } + case kTfLiteBuiltinSoftmax: + case kTfLiteBuiltinRelu: + case kTfLiteBuiltinRelu6: + case kTfLiteBuiltinTanh: + case kTfLiteBuiltinLogistic: { + return InputsWithCorrectTypes(node, context, {kTfLiteUInt8}); + } + case kTfLiteBuiltinResizeNearestNeighbor: { + return InputsWithCorrectTypes(node, context, + {kTfLiteUInt8, kTfLiteInt32}); + } + case kTfLiteBuiltinL2Normalization: { + if (!InputsWithCorrectTypes(node, context, {kTfLiteUInt8})) return false; + const TfLiteL2NormParams* norm_params = + reinterpret_cast(node->builtin_data); + return (norm_params->activation == kTfLiteActNone); + } + case kTfLiteBuiltinArgMax: + case kTfLiteBuiltinArgMin: + return InputsWithCorrectTypes(node, context, + {kTfLiteUInt8, kTfLiteInt32}); + case kTfLiteBuiltinSplit: { + if (!InputsWithCorrectTypes(node, context, {kTfLiteInt32, kTfLiteUInt8})) + return false; + const auto& input_tensor = context->tensors[node->inputs->data[1]]; + const bool is_four_dim_or_less = input_tensor.dims->size < 5; + // We need splitting axis to be constant, so Hexagon knows output shapes. + return is_four_dim_or_less && + IsConstantTensor(&context->tensors[node->inputs->data[0]]); + } + case kTfLiteBuiltinResizeBilinear: { + if (!InputsWithCorrectTypes(node, context, + {kTfLiteUInt8, kTfLiteInt32}) || + !IsConstantTensor(&context->tensors[node->inputs->data[1]])) { + return false; + } + const auto& size_tensor = context->tensors[node->inputs->data[1]]; + // TODO(b/143105433): Latency increase significantly with large size + // value. Limiting to 65 for now. + return NumElements(&size_tensor) == 2 && size_tensor.data.i32[0] < 66 && + size_tensor.data.i32[1] < 66; + } + case kTfLiteBuiltinNeg: { + return InputsWithCorrectTypes(node, context, {kTfLiteUInt8}); + } + case kTfLiteBuiltinTranspose: { + return InputsWithCorrectTypes(node, context, + {kTfLiteUInt8, kTfLiteInt32}); + } + default: + return false; + } + return false; +} + +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/utils.h b/tensorflow/lite/experimental/delegates/hexagon/utils.h new file mode 100644 index 00000000000..0438b37e7c2 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/utils.h @@ -0,0 +1,38 @@ +/* Copyright 2019 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_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_UTILS_H_ +#define TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_UTILS_H_ + +#include "tensorflow/lite/c/common.h" + +namespace tflite { + +// Interpretes data from 'dims' as a 4D shape {batch, height, width, depth} and +// populates the corresponding values. If dims->size < 4, the shape is prefixed +// with 1s. +// For example, dims {2, 3} is interpreted as: {1, 1, 2, 3}. +// Returns kTfLiteError if dims->size > 4, kTfLiteOk otherwise. +TfLiteStatus Get4DShape(unsigned int* batch_size, unsigned int* height_size, + unsigned int* width_size, unsigned int* depth_size, + TfLiteIntArray* dims); + +// Returns true if provided node is supported by Hexagon NNLib in the current +// context. +bool IsNodeSupportedByHexagon(const TfLiteRegistration* registration, + const TfLiteNode* node, TfLiteContext* context); + +} // namespace tflite + +#endif // TENSORFLOW_LITE_EXPERIMENTAL_DELEGATES_HEXAGON_UTILS_H_ diff --git a/tensorflow/lite/experimental/delegates/hexagon/utils_test.cc b/tensorflow/lite/experimental/delegates/hexagon/utils_test.cc new file mode 100644 index 00000000000..514912e3947 --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/utils_test.cc @@ -0,0 +1,71 @@ +/* Copyright 2019 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/lite/experimental/delegates/hexagon/utils.h" + +#include +#include + +#include +#include "tensorflow/lite/c/common.h" + +namespace tflite { +namespace { + +TEST(UtilsTest, Get4DShapeTest_4DInput) { + unsigned int batch_dim, height_dim, width_dim, depth_dim; + TfLiteIntArray* shape_4d = TfLiteIntArrayCreate(4); + shape_4d->data[0] = 4; + shape_4d->data[1] = 3; + shape_4d->data[2] = 2; + shape_4d->data[3] = 1; + EXPECT_EQ( + Get4DShape(&batch_dim, &height_dim, &width_dim, &depth_dim, shape_4d), + kTfLiteOk); + EXPECT_EQ(batch_dim, shape_4d->data[0]); + EXPECT_EQ(height_dim, shape_4d->data[1]); + EXPECT_EQ(width_dim, shape_4d->data[2]); + EXPECT_EQ(depth_dim, shape_4d->data[3]); + + TfLiteIntArrayFree(shape_4d); +} + +TEST(UtilsTest, Get4DShapeTest_2DInput) { + unsigned int batch_dim, height_dim, width_dim, depth_dim; + TfLiteIntArray* shape_2d = TfLiteIntArrayCreate(2); + shape_2d->data[0] = 4; + shape_2d->data[1] = 3; + EXPECT_EQ( + Get4DShape(&batch_dim, &height_dim, &width_dim, &depth_dim, shape_2d), + kTfLiteOk); + EXPECT_EQ(batch_dim, 1); + EXPECT_EQ(height_dim, 1); + EXPECT_EQ(width_dim, shape_2d->data[0]); + EXPECT_EQ(depth_dim, shape_2d->data[1]); + + TfLiteIntArrayFree(shape_2d); +} + +TEST(UtilsTest, Get4DShapeTest_5DInput) { + unsigned int batch_dim, height_dim, width_dim, depth_dim; + TfLiteIntArray* shape_5d = TfLiteIntArrayCreate(5); + EXPECT_EQ( + Get4DShape(&batch_dim, &height_dim, &width_dim, &depth_dim, shape_5d), + kTfLiteError); + + TfLiteIntArrayFree(shape_5d); +} + +} // namespace +} // namespace tflite diff --git a/tensorflow/lite/experimental/delegates/hexagon/version_script.lds b/tensorflow/lite/experimental/delegates/hexagon/version_script.lds new file mode 100644 index 00000000000..e39083bc74c --- /dev/null +++ b/tensorflow/lite/experimental/delegates/hexagon/version_script.lds @@ -0,0 +1,13 @@ +VERS_1.0 { + # Export JNI symbols. + global: + Java_*; + JNI_OnLoad; + JNI_OnUnload; + # TODO(b/138605512): Remove this and build separate .so for c++ api ? + TfLiteHexagon*; + + # Hide everything else. + local: + *; +}; \ No newline at end of file diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/Makefile.inc b/tensorflow/lite/experimental/micro/examples/hello_world/Makefile.inc deleted file mode 100644 index 472e82ad5e2..00000000000 --- a/tensorflow/lite/experimental/micro/examples/hello_world/Makefile.inc +++ /dev/null @@ -1,42 +0,0 @@ -HELLO_WORLD_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/hello_world/hello_world_test.cc \ -tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.cc - -HELLO_WORLD_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h - -OUTPUT_HANDLER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/hello_world/output_handler_test.cc \ -tensorflow/lite/experimental/micro/examples/hello_world/output_handler.cc - -OUTPUT_HANDLER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h \ -tensorflow/lite/experimental/micro/examples/hello_world/constants.h - -HELLO_WORLD_SRCS := \ -tensorflow/lite/experimental/micro/examples/hello_world/main.cc \ -tensorflow/lite/experimental/micro/examples/hello_world/main_functions.cc \ -tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.cc \ -tensorflow/lite/experimental/micro/examples/hello_world/output_handler.cc \ -tensorflow/lite/experimental/micro/examples/hello_world/constants.cc - -HELLO_WORLD_HDRS := \ -tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h \ -tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h \ -tensorflow/lite/experimental/micro/examples/hello_world/constants.h \ -tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h - -#Find any platform-specific rules for this example. -include $(wildcard tensorflow/lite/experimental/micro/examples/hello_world/*/Makefile.inc) - -# Tests loading and running the sine model. -$(eval $(call microlite_test,hello_world_test,\ -$(HELLO_WORLD_TEST_SRCS),$(HELLO_WORLD_TEST_HDRS))) - -# Tests producing an output. -$(eval $(call microlite_test,output_handler_test,\ -$(OUTPUT_HANDLER_TEST_SRCS),$(OUTPUT_HANDLER_TEST_HDRS))) - -# Builds a standalone binary. -$(eval $(call microlite_test,hello_world,\ -$(HELLO_WORLD_SRCS),$(HELLO_WORLD_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/create_sine_model.ipynb b/tensorflow/lite/experimental/micro/examples/hello_world/create_sine_model.ipynb deleted file mode 100644 index f776a333be5..00000000000 --- a/tensorflow/lite/experimental/micro/examples/hello_world/create_sine_model.ipynb +++ /dev/null @@ -1,1419 +0,0 @@ -{ - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "name": "create_sine_model.ipynb", - "provenance": [], - "collapsed_sections": [], - "toc_visible": true - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - } - }, - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "sblS7n3zWCWV", - "colab_type": "text" - }, - "source": [ - "**Copyright 2019 The TensorFlow Authors.**" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "0rvUzWmoWMH5", - "colab_type": "code", - "colab": {} - }, - "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", - "# You may obtain a copy of the License at\n", - "#\n", - "# https://www.apache.org/licenses/LICENSE-2.0\n", - "#\n", - "# Unless required by applicable law or agreed to in writing, software\n", - "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", - "# 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": "aCZBFzjClURz", - "colab_type": "text" - }, - "source": [ - "# Create and convert a TensorFlow model\n", - "This notebook is designed to demonstrate the process of creating a TensorFlow model and converting it to use with TensorFlow Lite. The model created in this notebook is used in the [hello_world](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/hello_world) sample for [TensorFlow Lite for Microcontrollers](https://www.tensorflow.org/lite/microcontrollers/overview).\n", - "\n", - "\n", - " \n", - " \n", - "
\n", - " Run in Google Colab\n", - " \n", - " View source on GitHub\n", - "
\n" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "dh4AXGuHWeu1", - "colab_type": "text" - }, - "source": [ - "## Import dependencies\n", - "Our first task is to import the dependencies we need. Run the following cell to do so:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "53PBJBv1jEtJ", - "colab_type": "code", - "colab": {} - }, - "source": [ - "# TensorFlow is an open source machine learning library\n", - "!pip install tensorflow==2.0\n", - "import tensorflow as tf\n", - "# Numpy is a math library\n", - "import numpy as np\n", - "# Matplotlib is a graphing library\n", - "import matplotlib.pyplot as plt\n", - "# math is Python's math library\n", - "import math" - ], - "execution_count": 0, - "outputs": [] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "p-PuBEb6CMeo", - "colab_type": "text" - }, - "source": [ - "## Generate data\n", - "Deep learning networks learn to model patterns in underlying data. In this notebook, we're going to train a network to model data generated by a [sine](https://en.wikipedia.org/wiki/Sine) function. This will result in a model that can take a value, `x`, and predict its sine, `y`.\n", - "\n", - "In a real world application, if you needed the sine of `x`, you could just calculate it directly. However, by training a model to do this, we can demonstrate the basic principles of machine learning.\n", - "\n", - "In the [hello_world](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/hello_world) sample for [TensorFlow Lite for Microcontrollers](https://www.tensorflow.org/lite/microcontrollers/overview), we'll use this model to control LEDs that light up in a sequence.\n", - "\n", - "The code in the following cell will generate a set of random `x` values, calculate their sine values, and display them on a graph:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "uKjg7QeMDsDx", - "colab_type": "code", - "outputId": "0387a48d-286d-4ae5-b5c1-b0f2c2b41472", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 269 - } - }, - "source": [ - "# We'll generate this many sample datapoints\n", - "SAMPLES = 1000\n", - "\n", - "# Set a \"seed\" value, so we get the same random numbers each time we run this\n", - "# notebook. Any number can be used here.\n", - "SEED = 1337\n", - "np.random.seed(SEED)\n", - "tf.random.set_seed(SEED)\n", - "\n", - "# Generate a uniformly distributed set of random numbers in the range from\n", - "# 0 to 2π, which covers a complete sine wave oscillation\n", - "x_values = np.random.uniform(low=0, high=2*math.pi, size=SAMPLES)\n", - "\n", - "# Shuffle the values to guarantee they're not in order\n", - "np.random.shuffle(x_values)\n", - "\n", - "# Calculate the corresponding sine values\n", - "y_values = np.sin(x_values)\n", - "\n", - "# Plot our data. The 'b.' argument tells the library to print blue dots.\n", - "plt.plot(x_values, y_values, 'b.')\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\nb+vxVQAAAABJRU5ErkJggg==\n", - "text/plain": [ - "

" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "iWOlC7W_FYvA", - "colab_type": "text" - }, - "source": [ - "## Add some noise\n", - "Since it was generated directly by the sine function, our data fits a nice, smooth curve.\n", - "\n", - "However, machine learning models are good at extracting underlying meaning from messy, real world data. To demonstrate this, we can add some noise to our data to approximate something more life-like.\n", - "\n", - "In the following cell, we'll add some random noise to each value, then draw a new graph:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "i0FJe3Y-Gkac", - "colab_type": "code", - "outputId": "481dad2e-1bfe-427c-a9ef-c345a821dfb9", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 269 - } - }, - "source": [ - "# Add a small random number to each y value\n", - "y_values += 0.1 * np.random.randn(*y_values.shape)\n", - "\n", - "# Plot our data\n", - "plt.plot(x_values, y_values, 'b.')\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\nAABJRU5ErkJggg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Up8Xk_pMH4Rt", - "colab_type": "text" - }, - "source": [ - "## Split our data\n", - "We now have a noisy dataset that approximates real world data. We'll be using this to train our model.\n", - "\n", - "To evaluate the accuracy of the model we train, we'll need to compare its predictions to real data and check how well they match up. This evaluation happens during training (where it is referred to as validation) and after training (referred to as testing) It's important in both cases that we use fresh data that was not already used to train the model.\n", - "\n", - "To ensure we have data to use for evaluation, we'll set some aside before we begin training. We'll reserve 20% of our data for validation, and another 20% for testing. The remaining 60% will be used to train the model. This is a typical split used when training models.\n", - "\n", - "The following code will split our data and then plot each set as a different color:\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "nNYko5L1keqZ", - "colab_type": "code", - "outputId": "2ebd8e8c-e5ef-4812-af10-1d60e70c222e", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 269 - } - }, - "source": [ - "# We'll use 60% of our data for training and 20% for testing. The remaining 20%\n", - "# will be used for validation. Calculate the indices of each section.\n", - "TRAIN_SPLIT = int(0.6 * SAMPLES)\n", - "TEST_SPLIT = int(0.2 * SAMPLES + TRAIN_SPLIT)\n", - "\n", - "# Use np.split to chop our data into three parts.\n", - "# The second argument to np.split is an array of indices where the data will be\n", - "# split. We provide two indices, so the data will be divided into three chunks.\n", - "x_train, x_validate, x_test = np.split(x_values, [TRAIN_SPLIT, TEST_SPLIT])\n", - "y_train, y_validate, y_test = np.split(y_values, [TRAIN_SPLIT, TEST_SPLIT])\n", - "\n", - "# Double check that our splits add up correctly\n", - "assert (x_train.size + x_validate.size + x_test.size) == SAMPLES\n", - "\n", - "# Plot the data in each partition in different colors:\n", - "plt.plot(x_train, y_train, 'b.', label=\"Train\")\n", - "plt.plot(x_validate, y_validate, 'y.', label=\"Validate\")\n", - "plt.plot(x_test, y_test, 'r.', label=\"Test\")\n", - "plt.legend()\n", - "plt.show()\n" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "t5McVnHmNiDw", - "colab_type": "text" - }, - "source": [ - "## Design a model\n", - "We're going to build a model that will take an input value (in this case, `x`) and use it to predict a numeric output value (the sine of `x`). This type of problem is called a _regression_.\n", - "\n", - "To achieve this, we're going to create a simple neural network. It will use _layers_ of _neurons_ to attempt to learn any patterns underlying the training data, so it can make predictions.\n", - "\n", - "To begin with, we'll define two layers. The first layer takes a single input (our `x` value) and runs it through 16 neurons. Based on this input, each neuron will become _activated_ to a certain degree based on its internal state (its _weight_ and _bias_ values). A neuron's degree of activation is expressed as a number.\n", - "\n", - "The activation numbers from our first layer will be fed as inputs to our second layer, which is a single neuron. It will apply its own weights and bias to these inputs and calculate its own activation, which will be output as our `y` value.\n", - "\n", - "**Note:** To learn more about how neural networks function, you can explore the [Learn TensorFlow](https://codelabs.developers.google.com/codelabs/tensorflow-lab1-helloworld) codelabs.\n", - "\n", - "The code in the following cell defines our model using [Keras](https://www.tensorflow.org/guide/keras), TensorFlow's high-level API for creating deep learning networks. Once the network is defined, we _compile_ it, specifying parameters that determine how it will be trained:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "gD60bE8cXQId", - "colab_type": "code", - "colab": { - }, - "outputId": "90d25fd8-bf3c-4a31-a275-0777fe3aa475" - }, - "source": [ - "# We'll use Keras to create a simple model architecture\n", - "from tensorflow.keras import layers\n", - "model_1 = tf.keras.Sequential()\n", - "\n", - "# First layer takes a scalar input and feeds it through 16 \"neurons\". The\n", - "# neurons decide whether to activate based on the 'relu' activation function.\n", - "model_1.add(layers.Dense(16, activation='relu', input_shape=(1,)))\n", - "\n", - "# Final layer is a single neuron, since we want to output a single value\n", - "model_1.add(layers.Dense(1))\n", - "\n", - "# Compile the model using a standard optimizer and loss function for regression\n", - "model_1.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])\n", - "\n", - "# Print a summary of the model's architecture\n", - "model_1.summary()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Model: \"sequential\"\n", - "_________________________________________________________________\n", - "Layer (type) Output Shape Param # \n", - "=================================================================\n", - "dense (Dense) (None, 16) 32 \n", - "_________________________________________________________________\n", - "dense_1 (Dense) (None, 1) 17 \n", - "=================================================================\n", - "Total params: 49\n", - "Trainable params: 49\n", - "Non-trainable params: 0\n", - "_________________________________________________________________\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "O0idLyRLQeGj", - "colab_type": "text" - }, - "source": [ - "## Train the model\n", - "Once we've defined the model, we can use our data to _train_ it. Training involves passing an `x` value into the neural network, checking how far the network's output deviates from the expected `y` value, and adjusting the neurons' weights and biases so that the output is more likely to be correct the next time.\n", - "\n", - "Training runs this process on the full dataset multiple times, and each full run-through is known as an _epoch_. The number of epochs to run during training is a parameter we can set.\n", - "\n", - "During each epoch, data is run through the network in multiple _batches_. Each batch, several pieces of data are passed into the network, producing output values. These outputs' correctness is measured in aggregate and the network's weights and biases are adjusted accordingly, once per batch. The _batch size_ is also a parameter we can set.\n", - "\n", - "The code in the following cell uses the `x` and `y` values from our training data to train the model. It runs for 1000 _epochs_, with 16 pieces of data in each _batch_. We also pass in some data to use for _validation_. As you will see when you run the cell, training can take a while to complete:\n", - "\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "p8hQKr4cVOdE", - "colab_type": "code", - "outputId": "cab8f6d2-89fa-4bbc-f116-5e9ab633a9c8", - "colab": { - "base_uri": "https://localhost:8080/" - } - }, - "source": [ - "# Train the model on our training data while validating on our validation set\n", - "history_1 = model_1.fit(x_train, y_train, epochs=1000, batch_size=16,\n", - " validation_data=(x_validate, y_validate))" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Train on 600 samples, validate on 200 samples\n", - "Epoch 1/1000\n", - "600/600 [==============================] - 1s 1ms/sample - loss: 0.7887 - mae: 0.7848 - val_loss: 0.5824 - val_mae: 0.6867\n", - "Epoch 2/1000\n", - "600/600 [==============================] - 0s 155us/sample - loss: 0.4883 - mae: 0.6194 - val_loss: 0.4742 - val_mae: 0.6056\n", - "...", - "Epoch 999/1000\n", - "600/600 [==============================] - 0s 149us/sample - loss: 0.1535 - mae: 0.3069 - val_loss: 0.1619 - val_mae: 0.3153\n", - "Epoch 1000/1000\n", - "600/600 [==============================] - 0s 124us/sample - loss: 0.1524 - mae: 0.3039 - val_loss: 0.1737 - val_mae: 0.3249\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "cRE8KpEqVfaS", - "colab_type": "text" - }, - "source": [ - "## Check the training metrics\n", - "During training, the model's performance is constantly being measured against both our training data and the validation data that we set aside earlier. Training produces a log of data that tells us how the model's performance changed over the course of the training process.\n", - "\n", - "The following cells will display some of that data in a graphical form:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "CmvA-ksoln8r", - "colab_type": "code", - "outputId": "fdbc614f-f198-4d92-a393-5c6e034cb7a6", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 295 - } - }, - "source": [ - "# Draw a graph of the loss, which is the distance between\n", - "# the predicted and actual values during training and validation.\n", - "loss = history_1.history['loss']\n", - "val_loss = history_1.history['val_loss']\n", - "\n", - "epochs = range(1, len(loss) + 1)\n", - "\n", - "plt.plot(epochs, loss, 'g.', label='Training loss')\n", - "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n", - "plt.title('Training and validation loss')\n", - "plt.xlabel('Epochs')\n", - "plt.ylabel('Loss')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "iOFBSbPcYCN4", - "colab_type": "text" - }, - "source": [ - "## Look closer at the data\n", - "The graph shows the _loss_ (or the difference between the model's predictions and the actual data) for each epoch. There are several ways to calculate loss, and the method we have used is _mean squared error_. There is a distinct loss value given for the training and the validation data.\n", - "\n", - "As we can see, the amount of loss rapidly decreases over the first 50 epochs, before flattening out. This means that the model is improving and producing more accurate predictions!\n", - "\n", - "Our goal is to stop training when either the model is no longer improving, or when the _training loss_ is less than the _validation loss_, which would mean that the model has learned to predict the training data so well that it can no longer generalize to new data.\n", - "\n", - "To make the flatter part of the graph more readable, let's skip the first 100 epochs:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "Zo0RYroFZYIV", - "colab_type": "code", - "outputId": "69322f09-01af-4c63-b33b-934acecc9e7d", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 295 - } - }, - "source": [ - "# Exclude the first few epochs so the graph is easier to read\n", - "SKIP = 100\n", - "\n", - "plt.plot(epochs[SKIP:], loss[SKIP:], 'g.', label='Training loss')\n", - "plt.plot(epochs[SKIP:], val_loss[SKIP:], 'b.', label='Validation loss')\n", - "plt.title('Training and validation loss')\n", - "plt.xlabel('Epochs')\n", - "plt.ylabel('Loss')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "W4EQD-Bb8hLM", - "colab_type": "text" - }, - "source": [ - "## Further metrics\n", - "From the plot, we can see that loss continues to reduce until around 600 epochs, at which point it is mostly stable. This means that there's probably no need to train our network for so long.\n", - "\n", - "However, we can also see that the lowest loss values are around 0.155. This is relatively high. In addition, the validation loss values are consistently higher.\n", - "\n", - "To gain more insight into our model's performance we can plot some more data. This time, we'll plot the _mean absolute error_, which is another way of measuring how far the network's predictions are from the actual numbers:\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "Md9E_azmpkZU", - "colab_type": "code", - "outputId": "093496ad-2ec8-4152-f360-b1c0a21f0bb9", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 295 - } - }, - "source": [ - "# Draw a graph of mean absolute error, which is another way of\n", - "# measuring the amount of error in the prediction.\n", - "mae = history_1.history['mae']\n", - "val_mae = history_1.history['val_mae']\n", - "\n", - "plt.plot(epochs[SKIP:], mae[SKIP:], 'g.', label='Training MAE')\n", - "plt.plot(epochs[SKIP:], val_mae[SKIP:], 'b.', label='Validation MAE')\n", - "plt.title('Training and validation mean absolute error')\n", - "plt.xlabel('Epochs')\n", - "plt.ylabel('MAE')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ctawd0CXAVEw", - "colab_type": "text" - }, - "source": [ - "This graph of _mean absolute error_ gives us some further clues. We can see that predictions with our training data show consistently lower error than with our validation data, which means that the network has likely _overfit_, or learned the training data so rigidly that it can't make effective predictions about new data.\n", - "\n", - "In addition, the mean absolute error values are quite high, around ~0.31, which means many of the model's predictions are at least 31% off. A 31% error means we are very far from accurately modelling the sine wave.\n", - "\n", - "To get more insight into what is happening, we can plot our network's predictions for the training data against the expected values:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "i13eVIT3B9Mj", - "colab_type": "code", - "outputId": "fbf1c81e-7d45-4a1c-d8f4-a05291688b9f", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 281 - } - }, - "source": [ - "# Use the model to make predictions from our validation data\n", - "predictions = model_1.predict(x_train)\n", - "\n", - "# Plot the predictions along with to the test data\n", - "plt.clf()\n", - "plt.title('Training data predicted vs actual values')\n", - "plt.plot(x_test, y_test, 'b.', label='Actual')\n", - "plt.plot(x_train, predictions, 'r.', label='Predicted')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\nYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Wokallj1D21L", - "colab_type": "text" - }, - "source": [ - "Oh dear! The graph makes it clear that our network has learned to approximate the sine function in a very limited way. The predictions are highly linear, and only very roughly fit the data.\n", - "\n", - "The rigidity of this fit suggests that the model does not have enough capacity to learn the full complexity of the sine wave function, so it's only able to approximate it in an overly simplistic way. By making our model bigger, we should be able to improve its performance.\n", - "\n", - "## Change our model\n", - "To make our model bigger, let's add an additional layer of neurons. The following cell redefines our model in the same way as earlier, but with an additional layer of 16 neurons in the middle:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "oW0xus6AF-4o", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 255 - }, - "outputId": "8e677f2e-25c6-4933-d8ff-0499a0a22bd6" - }, - "source": [ - "model_2 = tf.keras.Sequential()\n", - "\n", - "# First layer takes a scalar input and feeds it through 16 \"neurons\". The\n", - "# neurons decide whether to activate based on the 'relu' activation function.\n", - "model_2.add(layers.Dense(16, activation='relu', input_shape=(1,)))\n", - "\n", - "# The new second layer may help the network learn more complex representations\n", - "model_2.add(layers.Dense(16, activation='relu'))\n", - "\n", - "# Final layer is a single neuron, since we want to output a single value\n", - "model_2.add(layers.Dense(1))\n", - "\n", - "# Compile the model using a standard optimizer and loss function for regression\n", - "model_2.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])\n", - "\n", - "# Show a summary of the model\n", - "model_2.summary()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Model: \"sequential_1\"\n", - "_________________________________________________________________\n", - "Layer (type) Output Shape Param # \n", - "=================================================================\n", - "dense_2 (Dense) (None, 16) 32 \n", - "_________________________________________________________________\n", - "dense_3 (Dense) (None, 16) 272 \n", - "_________________________________________________________________\n", - "dense_4 (Dense) (None, 1) 17 \n", - "=================================================================\n", - "Total params: 321\n", - "Trainable params: 321\n", - "Non-trainable params: 0\n", - "_________________________________________________________________\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Dv2SC409Grap", - "colab_type": "text" - }, - "source": [ - "We'll now train the new model. To save time, we'll train for only 600 epochs:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "DPAUrdkmGq1M", - "colab_type": "code", - "outputId": "bd73e2e2-b5f7-472d-b054-fdb86ff87126", - "colab": { - "base_uri": "https://localhost:8080/" - } - }, - "source": [ - "history_2 = model_2.fit(x_train, y_train, epochs=600, batch_size=16,\n", - " validation_data=(x_validate, y_validate))" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Train on 600 samples, validate on 200 samples\n", - "Epoch 1/600\n", - "600/600 [==============================] - 1s 1ms/sample - loss: 0.6993 - mae: 0.7257 - val_loss: 0.4758 - val_mae: 0.6040\n", - "Epoch 2/600\n", - "600/600 [==============================] - 0s 153us/sample - loss: 0.4000 - mae: 0.5489 - val_loss: 0.3766 - val_mae: 0.5306\n", - "...", - "Epoch 599/600\n", - "600/600 [==============================] - 0s 150us/sample - loss: 0.0116 - mae: 0.0860 - val_loss: 0.0104 - val_mae: 0.0804\n", - "Epoch 600/600\n", - "600/600 [==============================] - 0s 150us/sample - loss: 0.0115 - mae: 0.0859 - val_loss: 0.0104 - val_mae: 0.0806\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Mc_CQu2_IvOP", - "colab_type": "text" - }, - "source": [ - "## Evaluate our new model\n", - "Each training epoch, the model prints out its loss and mean absolute error for training and validation. You can read this in the output above:\n", - "\n", - "```\n", - "Epoch 600/600\n", - "600/600 [==============================] - 0s 143us/sample - loss: 0.0115 - mae: 0.0859 - val_loss: 0.0104 - val_mae: 0.0806\n", - "```\n", - "\n", - "You can see that we've already got a huge improvement - validation loss has dropped from 0.17 to 0.01, and validation MAE has dropped from 0.36 to 0.08.\n", - "\n", - "The following cell will print the same graphs we used to evaluate our original model, but showing our new training history:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "SYHGswAJJgrC", - "colab_type": "code", - "outputId": "e7000158-aa8a-47ce-f372-e2b0c41c34e9", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 851 - } - }, - "source": [ - "# Draw a graph of the loss, which is the distance between\n", - "# the predicted and actual values during training and validation.\n", - "loss = history_2.history['loss']\n", - "val_loss = history_2.history['val_loss']\n", - "\n", - "epochs = range(1, len(loss) + 1)\n", - "\n", - "plt.plot(epochs, loss, 'g.', label='Training loss')\n", - "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n", - "plt.title('Training and validation loss')\n", - "plt.xlabel('Epochs')\n", - "plt.ylabel('Loss')\n", - "plt.legend()\n", - "plt.show()\n", - "\n", - "# Exclude the first few epochs so the graph is easier to read\n", - "SKIP = 80\n", - "\n", - "plt.clf()\n", - "\n", - "plt.plot(epochs[SKIP:], loss[SKIP:], 'g.', label='Training loss')\n", - "plt.plot(epochs[SKIP:], val_loss[SKIP:], 'b.', label='Validation loss')\n", - "plt.title('Training and validation loss')\n", - "plt.xlabel('Epochs')\n", - "plt.ylabel('Loss')\n", - "plt.legend()\n", - "plt.show()\n", - "\n", - "plt.clf()\n", - "\n", - "# Draw a graph of mean absolute error, which is another way of\n", - "# measuring the amount of error in the prediction.\n", - "mae = history_2.history['mae']\n", - "val_mae = history_2.history['val_mae']\n", - "\n", - "plt.plot(epochs[SKIP:], mae[SKIP:], 'g.', label='Training MAE')\n", - "plt.plot(epochs[SKIP:], val_mae[SKIP:], 'b.', label='Validation MAE')\n", - "plt.title('Training and validation mean absolute error')\n", - "plt.xlabel('Epochs')\n", - "plt.ylabel('MAE')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\ngg==\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - }, - { - "output_type": "display_data", - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZIAAAEWCAYAAABMoxE0AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzsvXl8VNX5wP19ZrKgotBGW0QCQW2V\nJcoSlxSRQSzFhapFW7cGcYmIqGj7WrBqqfqKWq0UtAouSFyKVt4iCqg/A1GQkR2ksghKBAQUUkUR\nyGRmnvePOzOZTGYmM5k14Xw/n3wyd+bce8+5y3nOs5zniKpiMBgMBkNzsWW6AgaDwWBo2RhBYjAY\nDIaEMILEYDAYDAlhBInBYDAYEsIIEoPBYDAkhBEkBoPBYEgII0gMGUdE7CKyT0Q6J7NsJhGRE0Uk\n6bH1InKuiFQHbW8Ukf6xlG3GuZ4Vkbuau3+U4z4gIi8k+7iGzJGT6QoYWh4isi9o83CgFvD4tm9U\n1ZfjOZ6qeoC2yS57KKCqJyXjOCJyPXC1qjqCjn19Mo5taP0YQWKIG1UNdOS+Ee/1qvpepPIikqOq\n7nTUzWAwpB9j2jIkHZ/p4lUR+ZeIfA9cLSKlIvKRiHwrIjtFZJKI5PrK54iIikiRb/sl3+/zROR7\nEXGKSNd4y/p+P09EPhWRvSIyWUQ+FJFrItQ7ljreKCKbReQbEZkUtK9dRB4XkRoR+RwYEuX6/FlE\nZoR896SI/N33+XoRWe9rz2c+bSHSsbaLiMP3+XARedFXt0+AviFl7xaRz33H/UREfu37vhh4Aujv\nMxvuCbq244P2H+lre42IzBKRY2O5Nk0hIpf46vOtiMwXkZOCfrtLRHaIyHcisiGorWeKyErf91+J\nyN9iPZ8hBaiq+TN/zf4DqoFzQ757AHABQ7EGK4cBpwFnYGnBxwOfAqN95XMABYp82y8Be4ASIBd4\nFXipGWV/AnwPXOT77Q6gDrgmQltiqeMbQDugCPifv+3AaOAToBNQAHxgvV5hz3M8sA84IujYXwMl\nvu2hvjICnAMcAE7x/XYuUB10rO2Aw/f5UaAK+BHQBVgXUva3wLG+e3Klrw4/9f12PVAVUs+XgPG+\nz4N9dewFtAH+CcyP5dqEaf8DwAu+z9189TjHd4/uAjb6PvcAvgA6+Mp2BY73fV4GXOH7fCRwRqbf\nhUP5z2gkhlSxSFXfVFWvqh5Q1WWqukRV3ar6OTAVGBBl/9dVdbmq1gEvY3Vg8Za9EFitqm/4fnsc\nS+iEJcY6TlDVvapajdVp+8/1W+BxVd2uqjXAQ1HO8znwXywBB/BL4BtVXe77/U1V/Vwt5gOVQFiH\negi/BR5Q1W9U9QssLSP4vK+p6k7fPXkFaxBQEsNxAa4CnlXV1ap6EBgLDBCRTkFlIl2baFwOzFbV\n+b579BCWMDoDcGMJrR4+8+gW37UDa0DwMxEpUNXvVXVJjO0wpAAjSAypYlvwhoicLCJzRGSXiHwH\n3AccHWX/XUGf9xPdwR6pbMfgeqiqYo3gwxJjHWM6F9ZIOhqvAFf4Pl/p2/bX40IRWSIi/xORb7G0\ngWjXys+x0eogIteIyBqfCelb4OQYjwtW+wLHU9XvgG+A44LKxHPPIh3Xi3WPjlPVjcAfsO7D1z5T\naQdf0RFAd2CjiCwVkfNjbIchBRhBYkgVoaGvU7BG4Seq6lHAvVimm1SyE8vUBICICA07vlASqeNO\noDBou6nw5NeAc0XkOCzN5BVfHQ8DXgcmYJmd2gPvxliPXZHqICLHA08BNwEFvuNuCDpuU6HKO7DM\nZf7jHYllQvsyhnrFc1wb1j37EkBVX1LVflhmLTvWdUFVN6rq5Vjmy8eAmSLSJsG6GJqJESSGdHEk\nsBf4QUS6ATem4ZxvAX1EZKiI5AC3AcekqI6vAWNE5DgRKQD+FK2wqu4CFgEvABtVdZPvp3wgD9gN\neETkQmBQHHW4S0TaizXPZnTQb22xhMVuLJl6A5ZG4ucroJM/uCAM/wKuE5FTRCQfq0NfqKoRNbw4\n6vxrEXH4zv3/YPm1lohINxEZ6DvfAd+fF6sBvxeRo30azF5f27wJ1sXQTIwgMaSLPwDDsTqJKVhO\n8ZSiql8BvwP+DtQAJwCrsOa9JLuOT2H5MtZiOYJfj2GfV7Cc5wGzlqp+C9wO/AfLYX0plkCMhb9g\naUbVwDygIui4HwOTgaW+MicBwX6F/wM2AV+JSLCJyr//21gmpv/49u+M5TdJCFX9BOuaP4Ul5IYA\nv/b5S/KBR7D8WruwNKA/+3Y9H1gvVlTgo8DvVNWVaH0MzUMss7HB0PoRETuWKeVSVV2Y6foYDK0F\no5EYWjUiMsRn6skH7sGK9lma4WoZDK0KI0gMrZ2zgM+xzCa/Ai5R1UimLYPB0AyMactgMBgMCWE0\nEoPBYDAkxCGRtPHoo4/WoqKiTFfDYDAYWhQrVqzYo6rRQuaBQ0SQFBUVsXz58kxXw2AwGFoUItJU\nhgbAmLYMBoPBkCBGkBgMBoMhIYwgMRgMBkNCHBI+EoPBkF7q6urYvn07Bw8ezHRVDDHQpk0bOnXq\nRG5upFRr0TGCxGAwJJ3t27dz5JFHUlRUhJV02ZCtqCo1NTVs376drl27Nr1DGIxpy2AwJJ2DBw9S\nUFBghEgLQEQoKChISHs0gqSV4nTChAnWf4MhExgh0nJI9F4Z01YrxOmEQYPA5YK8PKishNLSTNfK\nYDC0VoxG0gqpqrKEiMdj/a+qynSNDIb0UlNTQ69evejVqxcdOnTguOOOC2y7XLEtWzJixAg2btwY\ntcyTTz7Jyy+/nIwqc9ZZZ7F69eqkHCvdGI2kFeJwWJqIXyNxODJdI4MhvRQUFAQ65fHjx9O2bVv+\n+Mc/NiijqqgqNlv48fS0adOaPM/NN9+ceGVbAUYjaYWUllrmrPvvN2YtQ8vBuc3JhIUTcG5LnWNv\n8+bNdO/enauuuooePXqwc+dOysvLKSkpoUePHtx3332Bsn4Nwe120759e8aOHcupp55KaWkpX3/9\nNQB33303EydODJQfO3Ysp59+OieddBKLFy8G4IcffmDYsGF0796dSy+9lJKSkiY1j5deeoni4mJ6\n9uzJXXfdBYDb7eb3v/994PtJkyYB8Pjjj9O9e3dOOeUUrr766qRfs1gwGkkrpbTUCBBDy8G5zcmg\nikG4PC7y7HlUllVSWpiaB3jDhg1UVFRQUlICwEMPPcSPf/xj3G43AwcO5NJLL6V79+4N9tm7dy8D\nBgzgoYce4o477uD5559n7NixjY6tqixdupTZs2dz33338fbbbzN58mQ6dOjAzJkzWbNmDX369Ila\nv+3bt3P33XezfPly2rVrx7nnnstbb73FMcccw549e1i7di0A3377LQCPPPIIX3zxBXl5eYHv0o3R\nSAwGQ8apqq7C5XHhUQ8uj4uq6qqUneuEE04ICBGAf/3rX/Tp04c+ffqwfv161q1b12ifww47jPPO\nOw+Avn37Ul1dHfbYv/nNbxqVWbRoEZdffjkAp556Kj169IhavyVLlnDOOedw9NFHk5uby5VXXskH\nH3zAiSeeyMaNG7n11lt55513aNeuHQA9evTg6quv5uWXX272hMJESakg8S1zulFENotII/EtIvki\n8qrv9yUiUuT7/pciskJE1vr+n+P7/nARmSMiG0TkExF5KJX1NxgM6cFR5CDPnodd7OTZ83AUOVJ2\nriOOOCLwedOmTfzjH/9g/vz5fPzxxwwZMiTsfIq8vLzAZ7vdjtvtDnvs/Pz8Jss0l4KCAj7++GP6\n9+/Pk08+yY033gjAO++8w8iRI1m2bBmnn346Ho8nqeeNhZQJEhGxA08C5wHdgStEpHtIseuAb1T1\nROBx4GHf93uAoapaDAwHXgza51FVPRnoDfQTkfNS1QaDwZAeSgtLqSyr5P6B96fUrBXKd999x5FH\nHslRRx3Fzp07eeedd5J+jn79+vHaa68BsHbt2rAaTzBnnHEGCxYsoKamBrfbzYwZMxgwYAC7d+9G\nVbnsssu47777WLlyJR6Ph+3bt3POOefwyCOPsGfPHvbv35/0NjRFKn0kpwObVfVzABGZAVwEBF/F\ni4Dxvs+vA0+IiKjqqqAynwCHiUi+qu4HFgCoqktEVgKdUtgGg8GQJkoLS9MmQPz06dOH7t27c/LJ\nJ9OlSxf69euX9HPccsstlJWV0b1798Cf3ywVjk6dOnH//ffjcDhQVYYOHcoFF1zAypUrue6661BV\nRISHH34Yt9vNlVdeyffff4/X6+WPf/wjRx55ZNLb0BQpW7NdRC4Fhqjq9b7t3wNnqOrooDL/9ZXZ\n7tv+zFdmT8hxRqrquSHHbw+sBM71C6uQ38uBcoDOnTv3/eKLmNZnMRgMSWD9+vV069Yt09XICtxu\nN263mzZt2rBp0yYGDx7Mpk2byMnJrlincPdMRFaoakmEXQJkV0tCEJEeWOauwSHf5wD/AiaFEyIA\nqjoVmApQUlKSGmlpMBgMTbBv3z4GDRqE2+1GVZkyZUrWCZFESWVrvgQKg7Y7+b4LV2a7Tzi0A2oA\nRKQT8B+gTFU/C9lvKrBJVSemouIGg8GQLNq3b8+KFSsyXY2UksqorWXAz0Skq4jkAZcDs0PKzMZy\npgNcCsxXVfWZreYAY1X1w+AdROQBLIEzJoV1NxgMBkOMpEyQqKobGA28A6wHXlPVT0TkPhH5ta/Y\nc0CBiGwG7gD8IcKjgROBe0Vkte/vJz4t5c9YUWArfd9fn6o2GAwGg6FpUmqoU9W5wNyQ7+4N+nwQ\nuCzMfg8AD0Q4rMlNbTAYDFmEmdluMBgMhoQwgsRgMLQ6Bg4c2Ghy4cSJE7npppui7te2bVsAduzY\nwaWXXhq2jMPhYPny5VGPM3HixAYTA88///yk5MEaP348jz76aMLHSTZGkBgMhlbHFVdcwYwZMxp8\nN2PGDK644oqY9u/YsSOvv/56s88fKkjmzp1L+/btm328bMcIEoPBkBUkc3noSy+9lDlz5gQWsaqu\nrmbHjh30798/MK+jT58+FBcX88YbbzTav7q6mp49ewJw4MABLr/8crp168Yll1zCgQMHAuVuuumm\nQAr6v/zlLwBMmjSJHTt2MHDgQAYOHAhAUVERe/ZY86z//ve/07NnT3r27BlIQV9dXU23bt244YYb\n6NGjB4MHD25wnnCsXr2aM888k1NOOYVLLrmEb775JnB+f1p5f7LI999/P7CwV+/evfn++++bfW3D\n4l/cpTX/9e3bVw0GQ/pYt25dXOUXL1Y97DBVu936v3hx4nW44IILdNasWaqqOmHCBP3DH/6gqqp1\ndXW6d+9eVVXdvXu3nnDCCer1elVV9YgjjlBV1S1btmiPHj1UVfWxxx7TESNGqKrqmjVr1G6367Jl\ny1RVtaamRlVV3W63DhgwQNesWaOqql26dNHdu3cH6uLfXr58ufbs2VP37dun33//vXbv3l1Xrlyp\nW7ZsUbvdrqtWrVJV1csuu0xffPHFRm36y1/+on/7299UVbW4uFirqqpUVfWee+7R2267TVVVjz32\nWD148KCqqn7zzTeqqnrhhRfqokWLVFX1+++/17q6ukbHDnfPgOUaQx9rNBKDwZBxUrE8dLB5K9is\nparcddddnHLKKZx77rl8+eWXfPXVVxGP88EHHwQWjDrllFM45ZRTAr+99tpr9OnTh969e/PJJ580\nmZBx0aJFXHLJJRxxxBG0bduW3/zmNyxcuBCArl270qtXLyB6qnqw1kf59ttvGTBgAADDhw/ngw8+\nCNTxqquu4qWXXgrMoO/Xrx933HEHkyZN4ttvv036zHojSAwGQ8bxLw9ttydveeiLLrqIyspKVq5c\nyf79++nbty8AL7/8Mrt372bFihWsXr2an/70p2FTxzfFli1bePTRR6msrOTjjz/mggsuaNZx/PhT\n0ENiaejnzJnDzTffzMqVKznttNNwu92MHTuWZ599lgMHDtCvXz82bNjQ7HqGwwgSg8GQcVKxPHTb\ntm0ZOHAg1157bQMn+969e/nJT35Cbm4uCxYsoKmErmeffTavvPIKAP/973/5+OOPASsF/RFHHEG7\n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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - }, - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "f86dWOyZKmN9", - "colab_type": "text" - }, - "source": [ - "Great results! From these graphs, we can see two exciting things:\n", - "\n", - "* Metrics are better for validation than training, which means the network is not overfitting\n", - "* The overall loss and MAE are much better than our previous network\n", - "\n", - "The reason the metrics for validation are better than those for training (and not merely identical) is that validation metrics are calculated at the end of each epoch, while training metrics are calculated throughout the epoch, so validation happens on a model that has been trained slightly longer.\n", - "\n", - "This all means our network seems to be performing well! To confirm, let's check its predictions against the test dataset we set aside earlier:\n" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "lZfztKKyhLxX", - "colab_type": "code", - "outputId": "021c3cdf-1a38-4f7c-e535-885a87d8c09e", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 318 - } - }, - "source": [ - "# Calculate and print the loss on our test dataset\n", - "loss = model_2.evaluate(x_test, y_test)\n", - "\n", - "# Make predictions based on our test dataset\n", - "predictions = model_2.predict(x_test)\n", - "\n", - "# Graph the predictions against the actual values\n", - "plt.clf()\n", - "plt.title('Comparison of predictions and actual values')\n", - "plt.plot(x_test, y_test, 'b.', label='Actual')\n", - "plt.plot(x_test, predictions, 'r.', label='Predicted')\n", - "plt.legend()\n", - "plt.show()" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "\r200/1 [================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================] - 0s 71us/sample - loss: 0.0103 - mae: 0.0718\n" - ], - "name": "stdout" - }, - { - "output_type": "display_data", - "data": { - "image/png": "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\nmPgbhmGUIf8fPq1jq/CO0nkAAAAASUVORK5CYII=\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "3h7IcvuOOS4J", - "colab_type": "text" - }, - "source": [ - "Much better! The evaluation metrics we printed show that the model has a low loss and MAE on the test data, and the predictions line up visually with our data fairly well.\n", - "\n", - "The model isn't perfect; its predictions don't form a smooth sine curve. For instance, the line becomes almost straight when `x` is between 4 and 5. If we wanted to go further, we could try further increasing the capacity of the model, perhaps using some techniques to defend from overfitting.\n", - "\n", - "However, an important part of machine learning is knowing when to quit, and this model is good enough for our use case - which is to make some LEDs blink in a pleasing pattern.\n", - "\n", - "## Convert to TensorFlow Lite\n", - "We now have an acceptably accurate model in-memory. However, to use this with TensorFlow Lite for Microcontrollers, we'll need to convert it into the correct format and download it as a file. To do this, we'll use the [TensorFlow Lite Converter](https://www.tensorflow.org/lite/convert). The converter outputs a file in a special, space-efficient format for use on memory-constrained devices.\n", - "\n", - "Since this model is going to be deployed on a microcontroller, we want it to be as tiny as possible! One technique for reducing the size of models is called [quantization](https://www.tensorflow.org/lite/performance/post_training_quantization). It reduces the precision of the model's weights, which saves memory, often without much impact on accuracy. Quantized models also run faster, since the calculations required are simpler.\n", - "\n", - "The TensorFlow Lite Converter can apply quantization while it converts the model. In the following cell, we'll convert the model twice—once with quantization, once without:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "1muAoUm8lSXL", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 34 - }, - "outputId": "4c008dac-7629-471d-bfed-2853f4fca115" - }, - "source": [ - "# Convert the model to the TensorFlow Lite format without quantization\n", - "converter = tf.lite.TFLiteConverter.from_keras_model(model_2)\n", - "tflite_model = converter.convert()\n", - "\n", - "# Save the model to disk\n", - "open(\"sine_model.tflite\", \"wb\").write(tflite_model)\n", - "\n", - "# Convert the model to the TensorFlow Lite format with quantization\n", - "converter = tf.lite.TFLiteConverter.from_keras_model(model_2)\n", - "# Indicate that we want to perform the default optimizations,\n", - "# which includes quantization\n", - "converter.optimizations = [tf.lite.Optimize.DEFAULT]\n", - "# Define a generator function that provides our test data's x values\n", - "# as a representative dataset, and tell the converter to use it\n", - "def representative_dataset_generator():\n", - " for value in x_test:\n", - " # Each scalar value must be inside of a 2D array that is wrapped in a list\n", - " yield [np.array(value, dtype=np.float32, ndmin=2)]\n", - "converter.representative_dataset = representative_dataset_generator\n", - "# Convert the model\n", - "tflite_model = converter.convert()\n", - "\n", - "# Save the model to disk\n", - "open(\"sine_model_quantized.tflite\", \"wb\").write(tflite_model)" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "execute_result", - "data": { - "text/plain": [ - "2512" - ] - }, - "metadata": { - "tags": [] - }, - "execution_count": 15 - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "g3n1TwSS091-", - "colab_type": "text" - }, - "source": [ - "To create a quantized model that runs as efficiently as possible, we have to provide a \"representative dataset\"—a set of numbers that represent the full range of input values the dataset the model was trained on.\n", - "\n", - "In the above cell, we can use our test dataset's `x` values as a representative dataset. We define a function, `representative_dataset_generator()`, that uses the `yield` operator to return them one by one." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "L_vE-ZDkHVxe", - "colab_type": "text" - }, - "source": [ - "## Test the converted models\n", - "To prove these models are still accurate after conversion and quantization, we'll use both of them to make predictions and compare these against our test results:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "xvluIurpelrQ", - "colab_type": "code", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 281 - }, - "outputId": "a9a7d9ae-c17c-4f68-dda5-40a95b923647" - }, - "source": [ - "# Instantiate an interpreter for each model\n", - "sine_model = tf.lite.Interpreter('sine_model.tflite')\n", - "sine_model_quantized = tf.lite.Interpreter('sine_model_quantized.tflite')\n", - "\n", - "# Allocate memory for each model\n", - "sine_model.allocate_tensors()\n", - "sine_model_quantized.allocate_tensors()\n", - "\n", - "# Get indexes of the input and output tensors\n", - "sine_model_input_index = sine_model.get_input_details()[0][\"index\"]\n", - "sine_model_output_index = sine_model.get_output_details()[0][\"index\"]\n", - "sine_model_quantized_input_index = sine_model_quantized.get_input_details()[0][\"index\"]\n", - "sine_model_quantized_output_index = sine_model_quantized.get_output_details()[0][\"index\"]\n", - "\n", - "# Create arrays to store the results\n", - "sine_model_predictions = []\n", - "sine_model_quantized_predictions = []\n", - "\n", - "# Run each model's interpreter for each value and store the results in arrays\n", - "for x_value in x_test:\n", - " # Create a 2D tensor wrapping the current x value\n", - " x_value_tensor = tf.convert_to_tensor([[x_value]], dtype=np.float32)\n", - " # Write the value to the input tensor\n", - " sine_model.set_tensor(sine_model_input_index, x_value_tensor)\n", - " # Run inference\n", - " sine_model.invoke()\n", - " # Read the prediction from the output tensor\n", - " sine_model_predictions.append(\n", - " sine_model.get_tensor(sine_model_output_index)[0])\n", - " # Do the same for the quantized model\n", - " sine_model_quantized.set_tensor(sine_model_quantized_input_index, x_value_tensor)\n", - " sine_model_quantized.invoke()\n", - " sine_model_quantized_predictions.append(\n", - " sine_model_quantized.get_tensor(sine_model_quantized_output_index)[0])\n", - "\n", - "\n", - "# See how they line up with the data\n", - "plt.clf()\n", - "plt.title('Comparison of various models against actual values')\n", - "plt.plot(x_test, y_test, 'bo', label='Actual')\n", - "plt.plot(x_test, predictions, 'ro', label='Original predictions')\n", - "plt.plot(x_test, sine_model_predictions, 'bx', label='Lite predictions')\n", - "plt.plot(x_test, sine_model_quantized_predictions, 'gx', label='Lite quantized predictions')\n", - "plt.legend()\n", - "plt.show()\n" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "tags": [] - } - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "jWxvLGexKv0D", - "colab_type": "text" - }, - "source": [ - "We can see from the graph that the predictions for the original model, the converted model, and the quantized model are all close enough to be almost indistinguishable. This means that our quantized model is ready to use!\n", - "\n", - "We can print the difference in file size:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "6r42iBnULP4X", - "colab_type": "code", - "outputId": "9afd8a71-362a-4d59-bd0e-0f9ee70c6e78", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 68 - } - }, - "source": [ - "import os\n", - "basic_model_size = os.path.getsize(\"sine_model.tflite\")\n", - "print(\"Basic model is %d bytes\" % basic_model_size)\n", - "quantized_model_size = os.path.getsize(\"sine_model_quantized.tflite\")\n", - "print(\"Quantized model is %d bytes\" % quantized_model_size)\n", - "difference = basic_model_size - quantized_model_size\n", - "print(\"Difference is %d bytes\" % difference)" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Basic model is 2736 bytes\n", - "Quantized model is 2512 bytes\n", - "Difference is 224 bytes\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "C2vpZE9ZshVH", - "colab_type": "text" - }, - "source": [ - "Our quantized model is 224 bytes smaller than the original version, which is great - but it's only a minor reduction in size. At around 2.4 kilobytes, this model is already so small that the weights make up a small proportion of the overall size, meaning quantization only has a small effect.\n", - "\n", - "More complex models have many more weights, meaning the space saving from quantization will be much higher, approaching 4x for most sophisticated models.\n", - "\n", - "Regardless, our quantized model will take less time to execute than the original version, which is important on a tiny microcontroller!\n", - "\n", - "## Write to a C file\n", - "The final step in preparing our model for use with TensorFlow Lite for Microcontrollers is to convert it into a C source file. You can see an example of this format in [`hello_world/sine_model_data.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.cc).\n", - "\n", - "To do so, we can use a command line utility named [`xxd`](https://linux.die.net/man/1/xxd). The following cell runs `xxd` on our quantized model and prints the output:" - ] - }, - { - "cell_type": "code", - "metadata": { - "id": "l4-WhtGpvb-E", - "colab_type": "code", - "outputId": "87846170-e82c-45d1-8dca-a1518d1f6a1e", - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1000 - } - }, - "source": [ - "# Install xxd if it is not available\n", - "!apt-get -qq install xxd\n", - "# Save the file as a C source file\n", - "!xxd -i sine_model_quantized.tflite > sine_model_quantized.cc\n", - "# Print the source file\n", - "!cat sine_model_quantized.cc" - ], - "execution_count": 0, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Selecting previously unselected package xxd.\n", - "(Reading database ... 131183 files and directories currently installed.)\n", - "Preparing to unpack .../xxd_2%3a8.0.1453-1ubuntu1.1_amd64.deb ...\n", - "Unpacking xxd (2:8.0.1453-1ubuntu1.1) ...\n", - "Setting up xxd (2:8.0.1453-1ubuntu1.1) ...\n", - "Processing triggers for man-db (2.8.3-2ubuntu0.1) ...\n", - "unsigned char sine_model_quantized_tflite[] = {\n", - " 0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x12, 0x00,\n", - " 0x1c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00,\n", - " 0x00, 0x00, 0x18, 0x00, 0x12, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,\n", - " 0x60, 0x09, 0x00, 0x00, 0xa8, 0x02, 0x00, 0x00, 0x90, 0x02, 0x00, 0x00,\n", - " 0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x08, 0x00,\n", - " 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,\n", - " 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x75, 0x6e, 0x74,\n", - " 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00,\n", - " 0x0c, 0x00, 0x00, 0x00, 0x48, 0x02, 0x00, 0x00, 0x34, 0x02, 0x00, 0x00,\n", - " 0x0c, 0x02, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xac, 0x00, 0x00, 0x00,\n", - " 0x8c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,\n", - " 0x2c, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,\n", - " 0x05, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x35, 0x2e, 0x30, 0x00, 0x00, 0x00,\n", - " 0x7c, 0xfd, 0xff, 0xff, 0x80, 0xfd, 0xff, 0xff, 0x84, 0xfd, 0xff, 0xff,\n", - " 0x88, 0xfd, 0xff, 0xff, 0x22, 0xfe, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,\n", - " 0x40, 0x00, 0x00, 0x00, 0xfd, 0x13, 0x00, 0x00, 0xd2, 0x0e, 0x00, 0x00,\n", - " 0x5e, 0x0e, 0x00, 0x00, 0x28, 0xfe, 0xff, 0xff, 0x30, 0x0e, 0x00, 0x00,\n", - " 0x61, 0xe9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xcd, 0x0a, 0x00, 0x00,\n", - " 0x0f, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x0b, 0x00, 0x00,\n", - " 0x44, 0x0d, 0x00, 0x00, 0x2c, 0x0c, 0x00, 0x00, 0x91, 0xf0, 0xff, 0xff,\n", - " 0xb6, 0xef, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xfe, 0xff, 0xff,\n", - " 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x19, 0x40, 0x4a, 0x52,\n", - " 0xb5, 0x95, 0xa8, 0xd3, 0x6a, 0x7f, 0x7a, 0x2a, 0xdd, 0x46, 0xe4, 0xd5,\n", - " 0x8a, 0xfe, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,\n", - " 0x15, 0xfa, 0xff, 0xff, 0x2f, 0xe2, 0xff, 0xff, 0x04, 0xe6, 0xff, 0xff,\n", - " 0x2c, 0xee, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb7, 0xda, 0xff, 0xff,\n", - " 0xe4, 0x06, 0x00, 0x00, 0x86, 0xf2, 0xff, 0xff, 0x2e, 0xee, 0xff, 0xff,\n", - " 0x00, 0x00, 0x00, 0x00, 0x70, 0x20, 0x00, 0x00, 0xbd, 0x04, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0xd6, 0xfe, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,\n", - " 0x00, 0x01, 0x00, 0x00, 0x14, 0xc2, 0x10, 0xf6, 0xf7, 0xe0, 0xde, 0xce,\n", - " 0xee, 0x96, 0xb2, 0x2d, 0x34, 0x3b, 0x4b, 0x1b, 0xfd, 0x81, 0xd6, 0x0a,\n", - " 0x15, 0xca, 0x10, 0xc8, 0xee, 0xff, 0xc7, 0xf9, 0x1e, 0x40, 0xe3, 0xec,\n", - " 0x14, 0xac, 0xc7, 0xc7, 0x21, 0x3c, 0xf4, 0xf8, 0xe3, 0x2c, 0xc2, 0xff,\n", - " 0xdb, 0x3d, 0x2f, 0x39, 0x1d, 0xf2, 0x2e, 0x01, 0xdb, 0x13, 0x35, 0xe9,\n", - " 0xd8, 0xcf, 0x24, 0xda, 0xf4, 0xf7, 0x0b, 0xdc, 0x29, 0xcb, 0xc5, 0x12,\n", - " 0x02, 0xcc, 0x22, 0x2d, 0xbf, 0x0e, 0x36, 0x10, 0xf8, 0x35, 0x46, 0x0d,\n", - " 0x1c, 0x47, 0x35, 0xda, 0xd8, 0xfc, 0xcc, 0x15, 0x41, 0xe5, 0x36, 0x35,\n", - " 0x3b, 0xc8, 0xfd, 0xda, 0xcf, 0x15, 0xe4, 0xc5, 0x00, 0xd6, 0xce, 0xe3,\n", - " 0x03, 0x1b, 0xe2, 0x03, 0xc8, 0xde, 0xc6, 0xf2, 0xe6, 0xee, 0xe9, 0xbb,\n", - " 0x1b, 0xee, 0x21, 0x07, 0x0b, 0x07, 0x29, 0x3d, 0x13, 0xff, 0xf1, 0x2c,\n", - " 0x1b, 0xcc, 0x1b, 0x10, 0x21, 0xd6, 0x10, 0xf9, 0x0b, 0x89, 0xce, 0xc7,\n", - " 0xf4, 0x09, 0x3c, 0xe4, 0x21, 0xd1, 0x0d, 0x07, 0xd4, 0xec, 0x09, 0xea,\n", - " 0xdf, 0xe6, 0xe7, 0x33, 0xd3, 0xdd, 0xd8, 0xee, 0xea, 0xc2, 0xde, 0xf5,\n", - " 0x2c, 0x0d, 0xfc, 0xd2, 0xdd, 0x24, 0x27, 0x0c, 0xea, 0x0e, 0xf2, 0x2d,\n", - " 0x18, 0xc2, 0xe5, 0xb4, 0xdd, 0x15, 0xc4, 0x2e, 0xae, 0xe3, 0x20, 0x21,\n", - " 0xf3, 0x2d, 0x02, 0xfb, 0x19, 0xb1, 0xf3, 0xcd, 0x1a, 0xf1, 0x2f, 0x22,\n", - " 0x10, 0x05, 0x1e, 0xdf, 0xed, 0x3c, 0x24, 0xd6, 0xfb, 0x54, 0x43, 0x0d,\n", - " 0xd2, 0x10, 0x00, 0xdd, 0x00, 0x26, 0x02, 0x01, 0xf6, 0xc4, 0xc8, 0xcd,\n", - " 0x19, 0x21, 0x1e, 0x35, 0x3b, 0x1a, 0x27, 0xd1, 0xfc, 0x05, 0x0e, 0x11,\n", - " 0x06, 0xf8, 0xdf, 0x38, 0x27, 0xfe, 0x26, 0xd5, 0x13, 0xec, 0x39, 0x1d,\n", - " 0xcb, 0xc5, 0xd2, 0xd9, 0x0e, 0xe0, 0xdd, 0x09, 0xe2, 0xff, 0xff, 0xff,\n", - " 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xaf, 0x4f, 0x56, 0x1e,\n", - " 0xe8, 0x7f, 0xe0, 0xef, 0xc9, 0xdd, 0xe8, 0x42, 0xf7, 0x24, 0x1f, 0xdc,\n", - " 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xb1, 0xe9, 0xff, 0xff,\n", - " 0x80, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x54, 0x4f, 0x43, 0x4f,\n", - " 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xbc, 0xf9, 0xff, 0xff,\n", - " 0x48, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00,\n", - " 0xb8, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x1a, 0xff, 0xff, 0xff, 0x02, 0x00, 0x00, 0x00,\n", - " 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0xca, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x00, 0x00, 0x00,\n", - " 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,\n", - " 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x00, 0x00,\n", - " 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x08, 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0xba, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01,\n", - " 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00,\n", - " 0x07, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,\n", - " 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", - " 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n", - " 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x10, 0x00, 0x04, 0x00,\n", - " 0x08, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xdc, 0x04, 0x00, 0x00,\n", - " 0x54, 0x04, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00, 0x54, 0x03, 0x00, 0x00,\n", - " 0xd0, 0x02, 0x00, 0x00, 0x4c, 0x02, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00,\n", - " 0x5c, 0x01, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00,\n", - " 0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xd8, 0xff, 0xff, 0xff,\n", - " 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,\n", - " 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x00, 0x00, 0x00, 0x00,\n", - " 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x0c, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,\n", - " 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", - " 0x0d, 0x00, 0x00, 0x00, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, 0x5f,\n", - " 0x69, 0x6e, 0x70, 0x75, 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xc2, 0xfb, 0xff, 0xff,\n", - " 0x00, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xc4, 0xfc, 0xff, 0xff,\n", - " 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x68, 0xf6, 0x91, 0x38, 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75,\n", - " 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e,\n", - " 0x73, 0x65, 0x5f, 0x34, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f,\n", - " 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x2a, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09,\n", - " 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x2c, 0xfd, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0xfc, 0x4c, 0x3c,\n", - " 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69,\n", - " 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x34,\n", - " 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64,\n", - " 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74,\n", - " 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00,\n", - " 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", - " 0xaa, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, 0x6c, 0x00, 0x00, 0x00,\n", - " 0x09, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", - " 0x9c, 0xfc, 0xff, 0xff, 0x30, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,\n", - " 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0xd0, 0x49, 0xb6, 0x3b, 0x01, 0x00, 0x00, 0x00,\n", - " 0x86, 0x93, 0xb5, 0x3f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x19, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69,\n", - " 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x33,\n", - " 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x2a, 0xfd, 0xff, 0xff,\n", - " 0x00, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,\n", - " 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2c, 0xfe, 0xff, 0xff,\n", - " 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x07, 0xcc, 0xb7, 0x38, 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75,\n", - " 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e,\n", - " 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f,\n", - " 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x10, 0x00, 0x00, 0x00, 0x92, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09,\n", - " 0x6c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x94, 0xfe, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa9, 0x9f, 0xea, 0x3b,\n", - " 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69,\n", - " 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x33,\n", - " 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64,\n", - " 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74,\n", - " 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00,\n", - " 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", - " 0x12, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, 0x6c, 0x00, 0x00, 0x00,\n", - " 0x07, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", - " 0x04, 0xfe, 0xff, 0xff, 0x30, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,\n", - " 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0xe4, 0x8a, 0x48, 0x3c, 0x01, 0x00, 0x00, 0x00,\n", - " 0x59, 0xc2, 0x47, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x19, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69,\n", - " 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32,\n", - " 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x92, 0xfe, 0xff, 0xff,\n", - " 0x00, 0x00, 0x00, 0x02, 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", - " 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xff, 0xff, 0xff,\n", - " 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x11, 0xae, 0xbf, 0x38, 0x20, 0x00, 0x00, 0x00,\n", - " 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31,\n", - " 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, 0x2f, 0x4d, 0x61, 0x74,\n", - " 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0xff, 0xff,\n", - " 0x00, 0x00, 0x00, 0x09, 0x78, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,\n", - " 0x34, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00,\n", - " 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x2b, 0x85, 0x73, 0x3b, 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75,\n", - " 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e,\n", - " 0x73, 0x65, 0x5f, 0x32, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f,\n", - " 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65,\n", - " 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65,\n", - " 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x8a, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09,\n", - " 0x60, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,\n", - " 0x04, 0x00, 0x00, 0x00, 0x7c, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00,\n", - " 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n", - " 0x01, 0x00, 0x00, 0x00, 0xc9, 0x80, 0xc9, 0x3c, 0x01, 0x00, 0x00, 0x00,\n", - " 0x48, 0xb7, 0xc8, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", - " 0x12, 0x00, 0x00, 0x00, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, 0x5f,\n", - " 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x38, 0x00, 0x00,\n", - " 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x08, 0x00, 0x07, 0x00, 0x0c, 0x00,\n", - " 0x10, 0x00, 0x14, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,\n", - " 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00,\n", - " 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x14, 0x00, 0x04, 0x00, 0x08, 0x00,\n", - " 0x0c, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,\n", - " 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", - " 0x01, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n", - " 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb3, 0x24, 0x01, 0x3c,\n", - " 0x01, 0x00, 0x00, 0x00, 0x8e, 0xee, 0x80, 0x3f, 0x01, 0x00, 0x00, 0x00,\n", - " 0x8e, 0x58, 0x80, 0xbf, 0x0d, 0x00, 0x00, 0x00, 0x49, 0x64, 0x65, 0x6e,\n", - " 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x6e, 0x74, 0x38, 0x00, 0x00, 0x00,\n", - " 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", - " 0x03, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,\n", - " 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x00, 0x07, 0x00,\n", - " 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\n", - " 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00,\n", - " 0x06, 0x00, 0x00, 0x00, 0x00, 0x72, 0x0a, 0x00, 0x0c, 0x00, 0x07, 0x00,\n", - " 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,\n", - " 0x04, 0x00, 0x00, 0x00\n", - "};\n", - "unsigned int sine_model_quantized_tflite_len = 2512;\n" - ], - "name": "stdout" - } - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "1sqrhBLXwILt", - "colab_type": "text" - }, - "source": [ - "We can either copy and paste this output into our project's source code, or download the file using the collapsible menu on the left hand side of this Colab.\n", - "\n" - ] - } - ] -} \ No newline at end of file diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.cc b/tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.cc deleted file mode 100644 index d3cd3a269fe..00000000000 --- a/tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.cc +++ /dev/null @@ -1,238 +0,0 @@ -/* Copyright 2019 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. -==============================================================================*/ - -// Automatically created from a TensorFlow Lite flatbuffer using the command: -// xxd -i sine_model.tflite > sine_model_data.cc -// See the README for a full description of the creation process. - -#include "tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h" - -// We need to keep the data array aligned on some architectures. -#ifdef __has_attribute -#define HAVE_ATTRIBUTE(x) __has_attribute(x) -#else -#define HAVE_ATTRIBUTE(x) 0 -#endif -#if HAVE_ATTRIBUTE(aligned) || (defined(__GNUC__) && !defined(__clang__)) -#define DATA_ALIGN_ATTRIBUTE __attribute__((aligned(4))) -#else -#define DATA_ALIGN_ATTRIBUTE -#endif - -const unsigned char g_sine_model_data[] DATA_ALIGN_ATTRIBUTE = { - 0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, - 0x10, 0x00, 0x14, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x10, 0x09, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x30, 0x02, 0x00, 0x00, - 0x1c, 0x02, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0xa4, 0x01, 0x00, 0x00, - 0x94, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x7c, 0xfd, 0xff, 0xff, 0x80, 0xfd, 0xff, 0xff, - 0x84, 0xfd, 0xff, 0xff, 0x88, 0xfd, 0xff, 0xff, 0x22, 0xfe, 0xff, 0xff, - 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xad, 0x67, 0x48, 0xc4, - 0x7f, 0x82, 0x9c, 0x47, 0x5f, 0x28, 0x36, 0x35, 0x89, 0x38, 0x8b, 0xed, - 0x3e, 0xfe, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x6f, 0x01, 0x00, 0x00, 0x13, 0xf6, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, 0x25, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xdd, 0xe9, 0xff, 0xff, 0x25, 0xef, 0xff, 0xff, - 0x36, 0xe5, 0xff, 0xff, 0xf8, 0xf2, 0xff, 0xff, 0x65, 0x15, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x38, 0xe9, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x8a, 0xfe, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x00, 0xe7, 0xf4, 0x03, 0xe5, 0x0e, 0x19, 0x0d, 0xe3, - 0x1a, 0xca, 0x16, 0x1e, 0xe3, 0x02, 0xf6, 0xff, 0xfb, 0x10, 0x1f, 0xf4, - 0xfa, 0xf1, 0xff, 0xff, 0x0f, 0xb6, 0xf5, 0x19, 0x0e, 0xf3, 0xe1, 0xf9, - 0xdc, 0x13, 0xf2, 0xea, 0xf4, 0xd9, 0xef, 0xd9, 0x1b, 0xfd, 0xe4, 0x14, - 0x20, 0xc9, 0x1c, 0x0e, 0xe2, 0xda, 0xfc, 0xfe, 0xe1, 0x0b, 0x06, 0xde, - 0xdf, 0xe3, 0xde, 0x1d, 0x11, 0xf5, 0xec, 0x1d, 0x18, 0xf9, 0xe4, 0xe9, - 0xe0, 0x16, 0xea, 0xfd, 0x1d, 0xf1, 0x08, 0x0e, 0x0f, 0x1d, 0x15, 0xfe, - 0x13, 0xd6, 0xe8, 0xec, 0xdd, 0xf4, 0xdd, 0xf9, 0xee, 0xdd, 0x09, 0x15, - 0x01, 0xec, 0x13, 0xdf, 0x13, 0xea, 0x17, 0x1d, 0xe3, 0x05, 0x1d, 0x09, - 0xe3, 0x0d, 0xfc, 0xda, 0xe9, 0xf6, 0x0b, 0xeb, 0x06, 0xf6, 0x10, 0xdc, - 0x09, 0xf8, 0x0f, 0x18, 0xda, 0x2b, 0xf2, 0x19, 0x09, 0xeb, 0x00, 0xee, - 0x01, 0xe8, 0x1c, 0xf1, 0x0c, 0xf2, 0x1b, 0xc4, 0x0c, 0xd2, 0xf0, 0x0b, - 0xe4, 0x87, 0xdc, 0x1b, 0x0d, 0xf1, 0x14, 0xe1, 0x28, 0x12, 0x16, 0xd0, - 0xf1, 0xca, 0x09, 0xf5, 0xdd, 0xbf, 0x19, 0x0d, 0xdc, 0x15, 0xea, 0x18, - 0x05, 0xf3, 0x12, 0xfb, 0x17, 0x3b, 0x1a, 0xf1, 0xf6, 0x32, 0x15, 0x10, - 0x04, 0x0d, 0x0e, 0x16, 0x20, 0x12, 0xff, 0x07, 0x2b, 0x04, 0xe7, 0x02, - 0xed, 0x17, 0xdb, 0x1b, 0xe9, 0xde, 0x07, 0x15, 0x17, 0xdc, 0x05, 0x21, - 0xdb, 0xdf, 0x0a, 0xf1, 0x0a, 0xff, 0xdd, 0xf4, 0xf7, 0x1c, 0xf1, 0x1f, - 0x34, 0xf4, 0x04, 0x81, 0xcc, 0x6f, 0xb2, 0x20, 0x08, 0x86, 0x20, 0x0c, - 0xea, 0x0f, 0xfe, 0xfb, 0xe8, 0xe1, 0xfb, 0xe3, 0xf6, 0xf3, 0xe4, 0xe7, - 0xe4, 0x07, 0xda, 0xf1, 0xe9, 0xd7, 0x04, 0xf8, 0x07, 0x18, 0x18, 0xde, - 0xed, 0xd7, 0xdf, 0x12, 0xfa, 0xef, 0xfc, 0xfc, 0x96, 0xff, 0xff, 0xff, - 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x22, 0x03, 0x00, 0x00, - 0x63, 0x03, 0x00, 0x00, 0xf8, 0x0c, 0x00, 0x00, 0x0a, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x6c, 0xf8, 0xff, 0xff, 0x3b, 0x07, 0x00, 0x00, 0x5e, 0x0e, 0x00, 0x00, - 0x82, 0xed, 0xff, 0xff, 0x25, 0xfa, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x09, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfd, 0xff, 0xff, - 0xe2, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0xea, 0xe6, 0xb9, 0x08, 0xe0, 0x92, 0x01, 0x00, 0x23, 0xb3, 0x24, 0x19, - 0xd8, 0x7f, 0xf8, 0x17, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x31, 0xf5, 0xff, 0xff, 0x80, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, - 0x54, 0x4f, 0x43, 0x4f, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, - 0x65, 0x64, 0x2e, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0xbc, 0xf9, 0xff, 0xff, 0x48, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, - 0x30, 0x01, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x04, 0x01, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1a, 0xff, 0xff, 0xff, - 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xca, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x08, - 0x1c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xba, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, - 0x10, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0xdc, 0x04, 0x00, 0x00, 0x54, 0x04, 0x00, 0x00, 0xc4, 0x03, 0x00, 0x00, - 0x54, 0x03, 0x00, 0x00, 0xd0, 0x02, 0x00, 0x00, 0x4c, 0x02, 0x00, 0x00, - 0xe0, 0x01, 0x00, 0x00, 0x5c, 0x01, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, - 0x6c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0xd8, 0xff, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x64, 0x65, 0x6e, 0x73, - 0x65, 0x5f, 0x32, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0xc2, 0xfb, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0xc4, 0xfc, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x1d, 0xc4, 0x1c, 0x39, 0x20, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, - 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x34, 0x2f, 0x4d, 0x61, 0x74, - 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0xfc, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x09, 0x6c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x2c, 0xfd, 0xff, 0xff, - 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0xae, 0x03, 0x63, 0x3c, 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, - 0x73, 0x65, 0x5f, 0x34, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, - 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0xaa, 0xfc, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, - 0x6c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x9c, 0xfc, 0xff, 0xff, 0x30, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x42, 0xc8, 0x30, 0x3c, - 0x01, 0x00, 0x00, 0x00, 0x7a, 0x17, 0x30, 0x40, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, - 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x2a, 0xfd, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0x58, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x2c, 0xfe, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x61, 0x33, 0x28, 0x39, 0x20, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, - 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x4d, 0x61, 0x74, - 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x92, 0xfd, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x09, 0x6c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x94, 0xfe, 0xff, 0xff, - 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0xa0, 0xef, 0x36, 0x3c, 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, - 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, - 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, - 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, - 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x12, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, - 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x04, 0xfe, 0xff, 0xff, 0x30, 0x00, 0x00, 0x00, - 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x22, 0x61, 0x6b, 0x3c, - 0x01, 0x00, 0x00, 0x00, 0xc1, 0x75, 0x6a, 0x40, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, - 0x73, 0x65, 0x5f, 0x32, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x92, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x02, 0x5c, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x94, 0xff, 0xff, 0xff, 0x18, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2d, 0x60, 0xd5, 0x38, - 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, - 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0xfe, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, 0x78, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x37, 0xb9, 0x87, 0x3b, 0x34, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, - 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, 0x2f, 0x4d, 0x61, 0x74, - 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, - 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, - 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x8a, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x09, 0x60, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7c, 0xff, 0xff, 0xff, - 0x2c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, 0xba, 0x3b, 0xc9, 0x3c, - 0x01, 0x00, 0x00, 0x00, 0x7e, 0x72, 0xc8, 0x40, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x64, 0x65, 0x6e, 0x73, - 0x65, 0x5f, 0x32, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, - 0x74, 0x38, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x08, 0x00, - 0x07, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x09, 0x6c, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x50, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x14, 0x00, - 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0xfb, 0x3a, 0xfd, 0x3b, 0x01, 0x00, 0x00, 0x00, 0x2e, 0xed, 0x76, 0x3f, - 0x01, 0x00, 0x00, 0x00, 0x29, 0xc7, 0x80, 0xbf, 0x0d, 0x00, 0x00, 0x00, - 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x6e, 0x74, - 0x38, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, - 0x0e, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, - 0x06, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x72, 0x0a, 0x00, - 0x0c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x00}; -const int g_sine_model_data_len = 2432; diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/Makefile.inc b/tensorflow/lite/experimental/micro/examples/magic_wand/Makefile.inc deleted file mode 100644 index f739aefa074..00000000000 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/Makefile.inc +++ /dev/null @@ -1,89 +0,0 @@ -ifeq ($(TARGET), sparkfun_edge) - INCLUDES += \ - -I$(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/ \ - -I$(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/ - - THIRD_PARTY_CC_SRCS += \ - $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/tf_accelerometer.c \ - $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/lis2dh12_reg.c \ - $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/tf_adc.c - - THIRD_PARTY_CC_HDRS += \ - $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/tf_accelerometer.h \ - $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/lis2dh12_reg.h \ - $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/tf_adc.h -endif - -ACCELEROMETER_HANDLER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler_test.cc - -ACCELEROMETER_HANDLER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h - -OUTPUT_HANDLER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/output_handler_test.cc - -OUTPUT_HANDLER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h - -GESTURE_PREDICTOR_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/constants.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor_test.cc - -GESTURE_PREDICTOR_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/constants.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h \ - -magic_wand_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_test.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.cc - -magic_wand_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.h - -magic_wand_SRCS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/main.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/constants.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.cc \ -tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.cc - -magic_wand_HDRS := \ -tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/constants.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h \ -tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h - -# Find any platform-specific rules for this example. -include $(wildcard tensorflow/lite/experimental/micro/examples/magic_wand/*/Makefile.inc) - -# Tests the accelerometer handler -$(eval $(call microlite_test,gesture_accelerometer_handler_test,\ -$(ACCELEROMETER_HANDLER_TEST_SRCS),$(ACCELEROMETER_HANDLER_TEST_HDRS))) - -# Tests the output handler -$(eval $(call microlite_test,gesture_output_handler_test,\ -$(OUTPUT_HANDLER_TEST_SRCS),$(OUTPUT_HANDLER_TEST_HDRS))) - -# Tests the gesture predictor -$(eval $(call microlite_test,gesture_predictor_test,\ -$(GESTURE_PREDICTOR_TEST_SRCS),$(GESTURE_PREDICTOR_TEST_HDRS))) - -# Tests loading and running the gesture recognition model -$(eval $(call microlite_test,magic_wand_test,\ -$(magic_wand_TEST_SRCS),$(magic_wand_TEST_HDRS))) - -# Builds a standalone binary -$(eval $(call microlite_test,magic_wand,\ -$(magic_wand_SRCS),$(magic_wand_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.cc b/tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.cc deleted file mode 100644 index ce368953ec7..00000000000 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.cc +++ /dev/null @@ -1,1669 +0,0 @@ -/* Copyright 2019 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. -==============================================================================*/ - -// Automatically created from a TensorFlow Lite flatbuffer using the command: -// xxd -i magic_wand_model.tflite > magic_wand_model_data.cc -// See the README for a full description of the creation process. - -#include "tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h" - -// We need to keep the data array aligned on some architectures. -#ifdef __has_attribute -#define HAVE_ATTRIBUTE(x) __has_attribute(x) -#else -#define HAVE_ATTRIBUTE(x) 0 -#endif -#if HAVE_ATTRIBUTE(aligned) || (defined(__GNUC__) && !defined(__clang__)) -#define DATA_ALIGN_ATTRIBUTE __attribute__((aligned(4))) -#else -#define DATA_ALIGN_ATTRIBUTE -#endif - -const unsigned char g_magic_wand_model_data[] DATA_ALIGN_ATTRIBUTE = { - 0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00, - 0x18, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x18, 0x4c, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x20, 0x44, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, - 0x54, 0x4f, 0x43, 0x4f, 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, - 0x65, 0x64, 0x2e, 0x00, 0x11, 0x00, 0x00, 0x00, 0xf4, 0x43, 0x00, 0x00, - 0xa4, 0x43, 0x00, 0x00, 0x84, 0x43, 0x00, 0x00, 0x34, 0x43, 0x00, 0x00, - 0x2c, 0x43, 0x00, 0x00, 0x1c, 0x42, 0x00, 0x00, 0x14, 0x42, 0x00, 0x00, - 0x04, 0x0a, 0x00, 0x00, 0xd4, 0x09, 0x00, 0x00, 0xc4, 0x01, 0x00, 0x00, - 0xbc, 0x01, 0x00, 0x00, 0xb4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x24, 0xb6, 0xff, 0xff, 0x28, 0xb6, 0xff, 0xff, - 0x2c, 0xb6, 0xff, 0xff, 0x76, 0xb6, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, - 0x80, 0x01, 0x00, 0x00, 0x13, 0x00, 0x8a, 0xbe, 0x50, 0x7c, 0x49, 0x3e, - 0xb4, 0x06, 0x5a, 0xbc, 0xa6, 0xf2, 0xa8, 0xbd, 0x73, 0x50, 0x62, 0x3c, - 0xdd, 0x0a, 0x4a, 0xbe, 0xe2, 0x77, 0x9c, 0x3e, 0x8b, 0xe0, 0xc7, 0xbd, - 0xb2, 0xfd, 0xf5, 0x3d, 0x13, 0xb1, 0xb2, 0xbe, 0xcd, 0x78, 0x5a, 0xbd, - 0xfb, 0x15, 0x4e, 0xbc, 0x58, 0x7b, 0x3b, 0x3e, 0x14, 0x4b, 0x22, 0xbc, - 0x7e, 0x44, 0xd2, 0x3d, 0xdc, 0xda, 0x72, 0x3c, 0x1a, 0x87, 0xd9, 0x3d, - 0x3e, 0xdc, 0x13, 0x3d, 0x01, 0x1e, 0x75, 0xbe, 0x3d, 0x4f, 0x6a, 0xbd, - 0xa6, 0x52, 0x54, 0xbe, 0xc7, 0x7f, 0x5f, 0xbe, 0x97, 0x5f, 0x35, 0xbc, - 0xc5, 0x84, 0x5b, 0xbe, 0x7c, 0xd5, 0x6f, 0xbd, 0x90, 0x9b, 0x30, 0xbd, - 0x52, 0x86, 0xec, 0xbc, 0xc0, 0x4e, 0x0b, 0xbf, 0xfc, 0x3d, 0xec, 0xbd, - 0x92, 0x71, 0x26, 0x3e, 0x34, 0x26, 0x33, 0x3d, 0x06, 0x68, 0xfc, 0xbd, - 0x54, 0x5f, 0x2f, 0xbd, 0xa2, 0xce, 0xdd, 0x3d, 0x83, 0x6a, 0x76, 0xbc, - 0x64, 0xba, 0x95, 0xbd, 0x44, 0x69, 0x09, 0x3e, 0xea, 0x7b, 0x08, 0x3e, - 0xec, 0x13, 0x9f, 0xbd, 0x80, 0x2a, 0x04, 0xbe, 0x64, 0xf5, 0x84, 0x3e, - 0x31, 0xf8, 0xb4, 0xbd, 0xfa, 0x18, 0xb3, 0xbd, 0x4b, 0x3d, 0xf9, 0xbc, - 0xee, 0x0e, 0x8f, 0xbd, 0x3b, 0x21, 0x39, 0xbc, 0x35, 0xa0, 0xbb, 0xbc, - 0xd5, 0x5f, 0xbe, 0xbd, 0x9e, 0xc4, 0x0b, 0x3d, 0x4a, 0x8d, 0x82, 0xbe, - 0x01, 0xfb, 0x19, 0xbd, 0xb0, 0x51, 0xae, 0x3c, 0xb5, 0xd8, 0x68, 0xbe, - 0x97, 0x45, 0x73, 0x3d, 0xc7, 0x33, 0x2a, 0x3e, 0x9f, 0x82, 0x09, 0x3e, - 0x32, 0x36, 0xba, 0xbd, 0x93, 0x0d, 0x7e, 0xbb, 0xc2, 0x5f, 0xa6, 0xbd, - 0x13, 0x20, 0x55, 0xbe, 0xbf, 0x03, 0x08, 0xbe, 0xeb, 0xe0, 0xa9, 0xbd, - 0xf6, 0x4a, 0xcc, 0xbd, 0x8f, 0xf6, 0x28, 0xbd, 0x29, 0xe0, 0x81, 0x3d, - 0x92, 0x9d, 0x65, 0xbd, 0xe3, 0xb6, 0x17, 0x3e, 0x53, 0x07, 0xa6, 0xbc, - 0xba, 0x44, 0x3c, 0xbb, 0x05, 0x63, 0x36, 0xbc, 0xe1, 0x45, 0x23, 0xbd, - 0x0e, 0x10, 0x08, 0x3d, 0xee, 0xe5, 0x77, 0x3e, 0xf2, 0xe4, 0x76, 0xbe, - 0x61, 0x45, 0xbc, 0x3d, 0xda, 0xeb, 0xe4, 0x3e, 0xd4, 0xe1, 0xbc, 0xbd, - 0x0e, 0x17, 0x9a, 0xbe, 0x2a, 0x52, 0xbf, 0xbe, 0x71, 0x90, 0x91, 0x3e, - 0xfb, 0xfa, 0x6b, 0xbd, 0xdb, 0x52, 0x68, 0x3e, 0x7f, 0xfb, 0x49, 0x3d, - 0xd7, 0x8a, 0x5a, 0x3d, 0x20, 0x58, 0x09, 0xbe, 0xc4, 0x74, 0xd7, 0x3d, - 0x3b, 0x3e, 0xe8, 0xbc, 0x45, 0x92, 0xe0, 0xbc, 0x6d, 0x8e, 0xb8, 0xbe, - 0x24, 0x52, 0x32, 0xbd, 0x6d, 0x5a, 0x85, 0x3e, 0xb4, 0xc1, 0xaf, 0xbc, - 0x0e, 0xdf, 0x1a, 0xbe, 0xc8, 0xd1, 0x8e, 0xbe, 0x95, 0xba, 0xb2, 0xbd, - 0xe6, 0x9d, 0x7e, 0x3d, 0xbc, 0xb7, 0xff, 0xff, 0xc0, 0xb7, 0xff, 0xff, - 0xc4, 0xb7, 0xff, 0xff, 0x0e, 0xb8, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x08, 0x00, 0x00, 0x65, 0x15, 0x07, 0x3e, 0x19, 0xc0, 0x05, 0xbe, - 0xcb, 0xe0, 0xb8, 0x3d, 0xbc, 0x81, 0x7d, 0xbd, 0xdb, 0xac, 0xcb, 0x3d, - 0x28, 0x09, 0xa9, 0x3e, 0x16, 0x58, 0x9d, 0xbe, 0x1e, 0xc4, 0xd2, 0xbd, - 0x87, 0x2e, 0xdb, 0x3d, 0xd2, 0xdc, 0x80, 0xbd, 0xdc, 0x90, 0x41, 0x3d, - 0xeb, 0x0b, 0x5a, 0xbe, 0x4a, 0x91, 0xa2, 0xbc, 0x93, 0xff, 0x81, 0xbb, - 0x5f, 0xb4, 0x8e, 0xbd, 0x88, 0xba, 0x5c, 0xbd, 0x7b, 0x71, 0xef, 0x3c, - 0x46, 0xe5, 0x4d, 0xbf, 0x3a, 0x1f, 0x96, 0xbd, 0x4d, 0x39, 0xa1, 0xbf, - 0xe4, 0x63, 0x25, 0xbe, 0xc1, 0x1b, 0xa9, 0xbd, 0xba, 0x02, 0x88, 0xbd, - 0xd8, 0xcf, 0x75, 0xbe, 0x53, 0x42, 0xfa, 0xbd, 0xdd, 0xc5, 0xa5, 0xbe, - 0x0a, 0x04, 0x21, 0xbe, 0xab, 0x3c, 0x88, 0xbf, 0x2e, 0x1f, 0x50, 0xbe, - 0xc8, 0xb7, 0xe2, 0xbd, 0x71, 0xed, 0xd5, 0x3e, 0x0c, 0xf3, 0x00, 0xbd, - 0xae, 0x1e, 0x3e, 0x3d, 0x29, 0xf0, 0x91, 0xbd, 0x72, 0xf6, 0x19, 0xbe, - 0x29, 0xb6, 0x28, 0xbd, 0x24, 0xa2, 0x03, 0xbe, 0xe9, 0xcc, 0x83, 0xbd, - 0x4a, 0x72, 0x17, 0x3d, 0xf7, 0xe0, 0xbe, 0xbc, 0xd8, 0x7d, 0x59, 0xbd, - 0xa1, 0xc0, 0x05, 0x3c, 0xf0, 0xcd, 0x51, 0xbe, 0xfd, 0xb6, 0x15, 0xbd, - 0xa1, 0x24, 0x0a, 0x3d, 0x9e, 0x14, 0x22, 0xbd, 0xb7, 0x88, 0x1a, 0x3f, - 0x61, 0x5e, 0x35, 0x3e, 0x90, 0x8c, 0x7c, 0xbc, 0x0d, 0x7a, 0x71, 0xbf, - 0x35, 0x85, 0xb8, 0xbe, 0x38, 0x20, 0x11, 0xbf, 0x30, 0x01, 0x62, 0xbf, - 0xce, 0x28, 0x64, 0xbf, 0xab, 0x4d, 0x87, 0xbd, 0x97, 0xbd, 0xeb, 0xbd, - 0xbd, 0x54, 0x3f, 0x3e, 0x91, 0x0b, 0x9f, 0x3e, 0x6b, 0x12, 0x5b, 0xbe, - 0x31, 0xa1, 0xf4, 0xbe, 0x37, 0xc2, 0x85, 0xbe, 0x8a, 0x6a, 0x61, 0xbe, - 0x7c, 0xa0, 0x46, 0xbc, 0x6b, 0x1e, 0x16, 0xbe, 0x8d, 0x2c, 0xae, 0xbd, - 0xbb, 0x9b, 0x20, 0xbd, 0x96, 0x53, 0x8c, 0xbd, 0xb6, 0x3a, 0x93, 0xbd, - 0xf8, 0x58, 0xb1, 0xbd, 0x46, 0xf3, 0xdd, 0xbd, 0x5f, 0x9b, 0xa1, 0xbe, - 0x67, 0x80, 0xb8, 0x3d, 0x77, 0x4f, 0xd4, 0xbc, 0xc9, 0x54, 0xba, 0x3e, - 0x1c, 0x0e, 0x20, 0xbd, 0xf5, 0x0c, 0x5f, 0x3e, 0x76, 0xbf, 0xfb, 0xbd, - 0xfd, 0xe5, 0xcf, 0xbd, 0xe5, 0xa7, 0x8a, 0xbe, 0x3e, 0x47, 0x5a, 0xbd, - 0x27, 0x5e, 0xe8, 0x3c, 0x4d, 0x54, 0xfc, 0x3c, 0x0b, 0x66, 0x4e, 0x3d, - 0x4f, 0x28, 0x98, 0x3d, 0x15, 0x91, 0x87, 0xbd, 0x57, 0x09, 0x44, 0xbd, - 0x98, 0xb6, 0x34, 0xbe, 0xe5, 0x89, 0x9e, 0x3d, 0xdf, 0x9a, 0xe4, 0x3b, - 0xb6, 0x3c, 0x2c, 0x3e, 0x1f, 0xe0, 0x7a, 0x3d, 0xab, 0xa4, 0x1a, 0x3e, - 0xea, 0x68, 0xdd, 0xbd, 0x60, 0x6a, 0xed, 0xbd, 0xf5, 0x22, 0x37, 0xbe, - 0x93, 0x1c, 0x81, 0x3e, 0xda, 0xdd, 0x2f, 0x3e, 0xfd, 0x91, 0x0b, 0xbc, - 0x9a, 0xce, 0xfc, 0xbd, 0x3a, 0x51, 0xf7, 0x3d, 0xe5, 0x05, 0x96, 0x3e, - 0x96, 0x11, 0x9f, 0xbd, 0x69, 0x79, 0xca, 0xbd, 0x6b, 0x20, 0x0d, 0x3d, - 0x83, 0x7f, 0x35, 0x3d, 0xce, 0x14, 0x5e, 0x3a, 0x37, 0xce, 0x5e, 0xbd, - 0xc5, 0xf1, 0x35, 0x3e, 0xc4, 0x9b, 0xc4, 0xbd, 0x85, 0xbc, 0x4b, 0xbe, - 0x89, 0x78, 0x9a, 0xbd, 0xcc, 0x0f, 0x96, 0x3e, 0xda, 0xe4, 0xee, 0xbd, - 0x7d, 0x4b, 0x7a, 0xbd, 0xb9, 0xc3, 0x0e, 0x3e, 0x6c, 0x9a, 0xbb, 0xbd, - 0xd0, 0xa2, 0x11, 0xbf, 0x00, 0xe5, 0x7a, 0xbe, 0xae, 0x1b, 0xd3, 0xbd, - 0x82, 0x2f, 0x48, 0x3d, 0xb3, 0x89, 0x4e, 0xbe, 0xd8, 0x30, 0x26, 0xbd, - 0xff, 0xa7, 0x03, 0x3e, 0xff, 0x72, 0x80, 0xbe, 0xf2, 0xe6, 0x90, 0xbe, - 0x44, 0xf8, 0x94, 0x3d, 0x3b, 0xe8, 0x8d, 0xbd, 0x09, 0xc6, 0x94, 0xbe, - 0x78, 0xfe, 0x78, 0x3d, 0x1a, 0x39, 0x44, 0xbe, 0xc2, 0x92, 0xf8, 0x3b, - 0x76, 0x1f, 0x18, 0xbe, 0x4b, 0xfb, 0xbe, 0xbd, 0xdc, 0x05, 0x18, 0x3f, - 0x3f, 0x5a, 0x93, 0xbe, 0x8c, 0xec, 0x94, 0xbd, 0x80, 0x00, 0x7b, 0xbd, - 0x83, 0x0d, 0x01, 0xbe, 0x88, 0x9a, 0x86, 0x3d, 0xae, 0x82, 0x25, 0xbe, - 0xe0, 0xc3, 0xe3, 0xbd, 0x80, 0xd8, 0x1a, 0xbe, 0xb9, 0x65, 0x9c, 0xbe, - 0x31, 0xae, 0x3d, 0xbe, 0x02, 0xa7, 0xfb, 0xbd, 0x1c, 0xf6, 0x85, 0xbe, - 0xe7, 0xe5, 0x56, 0x3d, 0xc4, 0xc3, 0x4b, 0x3e, 0x61, 0xca, 0x8f, 0xbe, - 0x41, 0xca, 0x0d, 0xbe, 0x71, 0x61, 0x85, 0xbe, 0x23, 0xcf, 0x05, 0x3d, - 0xe9, 0x93, 0xc8, 0xbd, 0x8a, 0xc2, 0xda, 0xbe, 0xdb, 0xbd, 0x0c, 0x3d, - 0x48, 0x7f, 0x5a, 0xbf, 0x79, 0x35, 0xbb, 0xbe, 0xe7, 0x31, 0x20, 0xbe, - 0x81, 0x36, 0x84, 0x3e, 0x36, 0x72, 0x1e, 0xbe, 0xd1, 0x0b, 0x56, 0xbd, - 0x92, 0xc1, 0x06, 0x3c, 0xab, 0x4d, 0x91, 0xbd, 0xe1, 0x1c, 0x1f, 0xbd, - 0xf7, 0x66, 0x72, 0x3e, 0x34, 0xbf, 0x57, 0x3c, 0xb9, 0x6d, 0xf9, 0x3d, - 0xec, 0xb4, 0xfe, 0xbc, 0xc1, 0x36, 0x5d, 0x3d, 0xef, 0x44, 0x2b, 0x3d, - 0xe3, 0x49, 0x80, 0xbc, 0xa4, 0xe2, 0x60, 0xbd, 0x16, 0xb8, 0xa9, 0xbc, - 0x1d, 0x4e, 0xa5, 0xbd, 0xe4, 0x9f, 0x54, 0x3e, 0x0f, 0xe1, 0x25, 0xbd, - 0xbf, 0x92, 0xe2, 0x3d, 0xaa, 0x39, 0x38, 0x3d, 0xb7, 0x42, 0xe7, 0x3d, - 0x3d, 0x38, 0x4a, 0x3d, 0x73, 0xbc, 0x52, 0xbe, 0xed, 0xb3, 0x24, 0xbe, - 0xba, 0x9a, 0xdd, 0xbe, 0xed, 0xfc, 0xa6, 0x3d, 0xf1, 0xb5, 0x0a, 0x3e, - 0x0d, 0x25, 0x15, 0xbd, 0xc1, 0xce, 0xed, 0xbd, 0xd8, 0x5d, 0x5d, 0xbd, - 0x2d, 0x15, 0x52, 0xbe, 0xa9, 0x58, 0x4b, 0xbe, 0x1c, 0x97, 0x9f, 0x3e, - 0x4d, 0x40, 0xba, 0xbd, 0x41, 0xba, 0x8a, 0x3d, 0xb8, 0x8d, 0x34, 0xbe, - 0x04, 0x75, 0xc9, 0xbd, 0x5e, 0x58, 0x99, 0xbd, 0xe0, 0xed, 0x47, 0xbe, - 0x83, 0xf7, 0x93, 0xbc, 0x67, 0x34, 0x49, 0xbe, 0x10, 0xbc, 0x5e, 0xbe, - 0x3c, 0xa1, 0xa6, 0x3d, 0x7d, 0xaf, 0x82, 0xbd, 0xdf, 0xf9, 0x34, 0xbd, - 0x5c, 0x02, 0x82, 0xbd, 0xb7, 0x0d, 0x90, 0xbd, 0x64, 0x2e, 0xd4, 0xbd, - 0x82, 0xea, 0xb3, 0xbd, 0x0d, 0xdd, 0x89, 0xbd, 0xf2, 0x85, 0xa4, 0x39, - 0x03, 0x77, 0xd3, 0xbd, 0x43, 0x9a, 0xbf, 0xbd, 0xce, 0xa8, 0xa7, 0xbd, - 0xa9, 0x42, 0x38, 0xbd, 0xe0, 0x11, 0x7e, 0xbd, 0x11, 0x56, 0x33, 0x3e, - 0x06, 0x51, 0x0f, 0xbd, 0x1c, 0x88, 0xf8, 0xbc, 0xf1, 0x03, 0xb2, 0x3e, - 0xdb, 0x70, 0x38, 0x3d, 0x72, 0x68, 0x71, 0xbd, 0x24, 0x2f, 0x01, 0xbd, - 0x5e, 0xc0, 0x37, 0x3d, 0x0e, 0xc6, 0xae, 0x3e, 0x80, 0x25, 0x2a, 0x3e, - 0x17, 0xee, 0x35, 0xbe, 0x58, 0x77, 0x22, 0x3c, 0xb0, 0x2b, 0x71, 0x3d, - 0x5e, 0x6f, 0x07, 0x3e, 0x61, 0x0b, 0x16, 0xbd, 0x49, 0x56, 0x8b, 0x3d, - 0x40, 0x4d, 0x83, 0xbe, 0x03, 0x90, 0x24, 0x3e, 0x90, 0x49, 0x15, 0xbd, - 0x65, 0xa5, 0xd2, 0xbd, 0x1e, 0x47, 0x60, 0x3e, 0x4e, 0x30, 0xa2, 0xbd, - 0x5a, 0xc3, 0xe6, 0x3c, 0x13, 0xd6, 0x00, 0x3e, 0x4e, 0x66, 0x35, 0xbe, - 0x8f, 0xb9, 0xc1, 0xbd, 0xd1, 0x6f, 0x90, 0x3e, 0x15, 0x80, 0x38, 0xbe, - 0xa1, 0x60, 0x37, 0xbe, 0x6b, 0x42, 0x03, 0xbe, 0x1e, 0xf1, 0x11, 0xbd, - 0x15, 0xf1, 0x0d, 0xbd, 0x92, 0x64, 0x37, 0xbe, 0xba, 0x45, 0x42, 0xbc, - 0xa3, 0x48, 0x3a, 0x3e, 0x26, 0x58, 0x4a, 0xbe, 0xa8, 0x08, 0x9b, 0xbe, - 0x04, 0x3a, 0xf8, 0xbd, 0xa7, 0x3d, 0x2f, 0xbd, 0x9f, 0x78, 0xd9, 0xbd, - 0xc0, 0x6b, 0xac, 0x3d, 0x8c, 0x68, 0xd9, 0xbb, 0x33, 0x7b, 0xf5, 0xbd, - 0x61, 0xeb, 0xd6, 0xbd, 0xf5, 0x3d, 0xe8, 0xbd, 0x0d, 0x30, 0xdc, 0xbd, - 0x5e, 0xcf, 0x5e, 0xbc, 0x32, 0x0e, 0x2b, 0x3d, 0x46, 0xad, 0x2b, 0x3c, - 0x19, 0x91, 0x17, 0xbe, 0x31, 0x1c, 0x28, 0xbd, 0xfc, 0xe5, 0x40, 0xbc, - 0x76, 0xe8, 0x1e, 0xbe, 0x00, 0x7f, 0xe1, 0xbc, 0x8f, 0xc2, 0xa9, 0x3d, - 0xd1, 0x05, 0x16, 0xbc, 0x94, 0xf8, 0x0f, 0x3e, 0xec, 0x92, 0x05, 0xbe, - 0x5d, 0xc2, 0x7f, 0x3d, 0x39, 0xdb, 0x83, 0xbc, 0xef, 0x1e, 0x27, 0xbe, - 0x70, 0xa7, 0xed, 0x3d, 0xc8, 0x28, 0x87, 0x3d, 0x95, 0xd5, 0x17, 0xbc, - 0x34, 0xba, 0xba, 0x3d, 0x47, 0xdf, 0xe5, 0xbd, 0x99, 0xa7, 0x70, 0x3e, - 0x05, 0x82, 0x59, 0x3d, 0x3a, 0x54, 0x01, 0xbe, 0xbb, 0x90, 0xa4, 0x3e, - 0x8b, 0x70, 0x82, 0x3d, 0x85, 0xf1, 0x3a, 0x3c, 0x13, 0xd2, 0xb8, 0xbb, - 0xd4, 0x79, 0x67, 0xbd, 0xe7, 0x66, 0x04, 0xbf, 0x00, 0x2a, 0xd4, 0xbd, - 0xef, 0xb8, 0xe8, 0x3d, 0x34, 0xc7, 0x37, 0xbf, 0x28, 0x13, 0x82, 0xbd, - 0x18, 0x6f, 0x8c, 0xbd, 0x5e, 0x9b, 0x8c, 0x3d, 0x0d, 0x39, 0x3d, 0xba, - 0x1c, 0x41, 0x40, 0xbf, 0x0d, 0x81, 0xbf, 0xbc, 0xcc, 0x20, 0x88, 0xbd, - 0x9e, 0x17, 0x32, 0xbf, 0xf5, 0x2c, 0xbb, 0xbc, 0xdf, 0x7c, 0x88, 0x3e, - 0xbc, 0xfa, 0x77, 0x3d, 0x09, 0x39, 0x47, 0x3d, 0xc2, 0x01, 0x6e, 0xbf, - 0xa1, 0x93, 0x46, 0xbe, 0xf5, 0x92, 0x9f, 0xbc, 0xc0, 0x5e, 0x02, 0xbf, - 0x74, 0x33, 0xab, 0x3d, 0x0d, 0x66, 0x5d, 0x3d, 0x02, 0x39, 0xbc, 0xbc, - 0xbe, 0x1d, 0x2a, 0x3d, 0x6d, 0x7b, 0x55, 0xbf, 0x34, 0xff, 0x4b, 0xbe, - 0xba, 0x10, 0x22, 0x3e, 0xdb, 0x9f, 0xf8, 0xbe, 0x6d, 0x59, 0x64, 0x3e, - 0x6c, 0x3f, 0x62, 0x3d, 0x11, 0xf8, 0x83, 0xbb, 0xb8, 0xf2, 0xf2, 0xbd, - 0xe1, 0xe8, 0xb1, 0xbc, 0xa0, 0xec, 0xfb, 0x3c, 0x06, 0x18, 0xb9, 0xbb, - 0x57, 0xb4, 0xf2, 0x3d, 0xb9, 0xd2, 0x24, 0xbe, 0x8e, 0x77, 0x84, 0xbd, - 0x45, 0xf8, 0x60, 0x3d, 0x4a, 0x83, 0x90, 0x3e, 0xee, 0x8d, 0xab, 0x3d, - 0x53, 0x05, 0xfc, 0x3d, 0xf3, 0xe0, 0x07, 0x3d, 0x82, 0x2c, 0xec, 0x3d, - 0x4c, 0x82, 0x5a, 0xbd, 0x6b, 0x30, 0xa1, 0xbd, 0x27, 0x70, 0x39, 0x3e, - 0x7e, 0xb3, 0x05, 0xbd, 0xe3, 0x2f, 0xf0, 0x3d, 0xc0, 0xdb, 0x7e, 0xbf, - 0xaf, 0xb2, 0xec, 0x3c, 0x4d, 0xe6, 0x7f, 0xbf, 0x60, 0xb0, 0xb0, 0xbe, - 0xa0, 0x89, 0xe1, 0xbd, 0xde, 0xdf, 0x65, 0xbb, 0xdf, 0xe8, 0xd4, 0xbd, - 0x33, 0xb0, 0x07, 0xbd, 0x65, 0xfe, 0x8d, 0x3d, 0xfc, 0xa0, 0xe5, 0x3c, - 0x5d, 0x9e, 0xc6, 0xbf, 0x48, 0x58, 0x1d, 0xbf, 0xde, 0x0a, 0x1b, 0xbd, - 0x3e, 0x08, 0x84, 0xbd, 0x4e, 0x3c, 0x90, 0xbe, 0x7f, 0x92, 0x4b, 0x3d, - 0x97, 0x3a, 0xa6, 0x3e, 0x60, 0x7c, 0xd5, 0x3c, 0xf3, 0x8f, 0x0d, 0xbd, - 0xe4, 0x0b, 0x16, 0x3e, 0x8e, 0x51, 0x8d, 0x3e, 0xb4, 0xab, 0x8c, 0xbd, - 0x1c, 0x39, 0xb4, 0x3d, 0x3e, 0x15, 0x97, 0x3c, 0x40, 0x4e, 0x7d, 0xbd, - 0x1f, 0x14, 0x49, 0x3c, 0x7b, 0x76, 0x19, 0x3c, 0xf0, 0x8e, 0xe2, 0xbc, - 0xbf, 0x43, 0x2f, 0xbd, 0x50, 0x19, 0x40, 0xbe, 0x1d, 0x4b, 0x08, 0x3d, - 0x35, 0x31, 0xd6, 0x3b, 0xf5, 0x60, 0x73, 0xbe, 0xd1, 0x7e, 0x1e, 0x3d, - 0xdc, 0xcb, 0x01, 0xbd, 0xf4, 0x76, 0x43, 0xbe, 0xf4, 0xf1, 0xaa, 0xbd, - 0x1a, 0x7c, 0x61, 0xbe, 0x5c, 0xe5, 0xe3, 0x3c, 0xca, 0x20, 0x1b, 0xbd, - 0x70, 0x90, 0xba, 0xbe, 0x85, 0xce, 0x8e, 0xbc, 0x65, 0x12, 0x92, 0xbe, - 0x43, 0x76, 0x95, 0xbe, 0x8e, 0xb3, 0xe0, 0xbd, 0xce, 0xd7, 0x67, 0xbd, - 0xf4, 0x59, 0x75, 0x3d, 0x63, 0x60, 0x48, 0xbd, 0x9e, 0x67, 0xb1, 0xbd, - 0xdd, 0x99, 0x7b, 0xbd, 0x11, 0x04, 0x11, 0xbd, 0xcb, 0x62, 0x0a, 0xbd, - 0x0a, 0x96, 0x3d, 0x3d, 0x89, 0xed, 0xb5, 0xbc, 0x4a, 0x5a, 0x5c, 0x3e, - 0x4d, 0x75, 0x42, 0x3c, 0xe6, 0x3d, 0x95, 0xbd, 0x30, 0x6e, 0x76, 0xbc, - 0x46, 0x76, 0x9c, 0xbd, 0xc1, 0xde, 0x3f, 0xbc, 0x61, 0xde, 0x86, 0x3d, - 0xda, 0x7a, 0x42, 0xbd, 0x3c, 0x07, 0x40, 0xbb, 0xd2, 0xda, 0x6c, 0x3d, - 0xda, 0x02, 0x8a, 0xbd, 0x8b, 0x01, 0x9b, 0xbd, 0xad, 0x5f, 0x96, 0xbd, - 0xa9, 0xa1, 0x22, 0xbc, 0x86, 0x14, 0x8d, 0xbd, 0xe2, 0x5a, 0x2b, 0xbd, - 0xcf, 0x71, 0x1c, 0xbd, 0x6b, 0x79, 0xe8, 0x3b, 0xb1, 0x03, 0x0f, 0xbd, - 0xb2, 0xcf, 0xc0, 0x3e, 0x69, 0xbf, 0x93, 0xbd, 0x90, 0x93, 0xcb, 0xbb, - 0xb3, 0x02, 0x63, 0x3e, 0xcd, 0x01, 0xfd, 0xbc, 0xee, 0x45, 0x89, 0xbd, - 0xda, 0x2c, 0x3f, 0x3f, 0xe6, 0xf6, 0x50, 0xbe, 0x93, 0x38, 0xd9, 0xbd, - 0x07, 0x39, 0xe3, 0xbe, 0x25, 0xe0, 0x14, 0xbd, 0xab, 0xcf, 0x9a, 0x3d, - 0x8a, 0xc9, 0x22, 0xbe, 0x71, 0x67, 0x9a, 0xbe, 0x4b, 0x9b, 0x9a, 0xbd, - 0x65, 0xcf, 0x3c, 0xbe, 0x21, 0xef, 0xa7, 0xbe, 0xaf, 0x6e, 0x2c, 0xbe, - 0x00, 0xc9, 0x3e, 0x3e, 0xa1, 0xcf, 0xf3, 0xbc, 0x6a, 0xfe, 0xc4, 0xbc, - 0x9c, 0x98, 0xd9, 0xbd, 0xa8, 0xd5, 0x15, 0xbe, 0xef, 0x18, 0x0c, 0xbe, - 0xcb, 0x6c, 0x7d, 0xbe, 0x1e, 0xdc, 0xee, 0x3d, 0xba, 0x96, 0x90, 0x3b, - 0xa4, 0x11, 0xad, 0xbd, 0xac, 0x6f, 0x2a, 0x3e, 0x79, 0xbb, 0xeb, 0x3d, - 0x4f, 0xf4, 0xa5, 0xbe, 0xff, 0x82, 0xa1, 0xbd, 0x6f, 0x36, 0x0e, 0xbe, - 0xb8, 0xe2, 0xaf, 0x3d, 0xed, 0x05, 0x26, 0xbd, 0x51, 0x5e, 0x0a, 0xbe, - 0x48, 0xaa, 0x18, 0x3d, 0x37, 0x93, 0x10, 0x3e, 0xd7, 0x45, 0xbd, 0xbc, - 0x16, 0x89, 0xcf, 0x3d, 0xd3, 0x17, 0x44, 0x3e, 0xdf, 0x89, 0x6e, 0xbd, - 0xb1, 0xc1, 0x9e, 0xbe, 0x9f, 0x9c, 0x48, 0xbe, 0x4f, 0x84, 0x6b, 0xbd, - 0xae, 0xed, 0x1b, 0x3e, 0x48, 0xb3, 0xef, 0xbc, 0x33, 0xb1, 0x9a, 0x3d, - 0x00, 0xf4, 0xfe, 0x3a, 0x45, 0x6e, 0x93, 0xbe, 0xfa, 0xcd, 0x18, 0xbe, - 0x0c, 0x06, 0x8f, 0xbd, 0xb9, 0x78, 0xda, 0xbc, 0x3a, 0x7f, 0x61, 0xbe, - 0xf4, 0x3e, 0x85, 0x3e, 0x92, 0x54, 0xc6, 0xbc, 0x92, 0xa3, 0xb2, 0x3d, - 0xda, 0xd8, 0x3f, 0xbe, 0x8f, 0x01, 0x43, 0x3e, 0xf1, 0x63, 0x0a, 0xbc, - 0xeb, 0xa8, 0x11, 0xbd, 0x48, 0xbe, 0xf0, 0xbe, 0xb6, 0x72, 0x25, 0x3e, - 0xe3, 0x2b, 0xcb, 0xbd, 0x55, 0x0e, 0x49, 0xbd, 0xd3, 0x04, 0x9f, 0x3c, - 0xf0, 0x94, 0xfa, 0x3d, 0x9e, 0xf1, 0x4e, 0x3c, 0xe4, 0x06, 0x12, 0xbe, - 0x13, 0x9b, 0x0b, 0xbe, 0xe3, 0x59, 0x36, 0xbe, 0x0f, 0x2a, 0x0d, 0x3c, - 0x26, 0x58, 0xef, 0xbd, 0x44, 0xe0, 0x20, 0xbe, 0xcd, 0xd4, 0x1c, 0xbe, - 0x2b, 0x21, 0xa2, 0xbd, 0x90, 0x76, 0x01, 0x3e, 0x13, 0xac, 0xfe, 0x3d, - 0x70, 0xec, 0xb9, 0xbe, 0x44, 0xde, 0x71, 0x3e, 0x04, 0xdd, 0x56, 0x3c, - 0xe1, 0xb1, 0x1b, 0xbe, 0xf5, 0x9e, 0xf3, 0xbd, 0x4b, 0xe2, 0xa6, 0xbc, - 0xc4, 0x85, 0xdc, 0xbd, 0xe0, 0x04, 0x24, 0x3e, 0x19, 0x78, 0x6e, 0xbe, - 0x5a, 0x56, 0xc2, 0x3d, 0x05, 0x9e, 0x4a, 0xbd, 0x89, 0x0e, 0x60, 0xbe, - 0x76, 0xd1, 0x3a, 0xbd, 0x69, 0xda, 0x15, 0x3d, 0x77, 0x91, 0xb2, 0xbe, - 0xbc, 0x64, 0xe1, 0xbd, 0x41, 0x8e, 0x86, 0xbd, 0x95, 0xf4, 0xb0, 0x3d, - 0xca, 0x6b, 0xd2, 0x3e, 0x85, 0xd7, 0xe5, 0xbd, 0x5b, 0x41, 0xb9, 0x3b, - 0xf3, 0xe7, 0x5d, 0x3d, 0x88, 0x1b, 0xd4, 0xbe, 0x41, 0xf2, 0x44, 0xbc, - 0x1a, 0xc0, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x74, 0xf0, 0x28, 0xc0, 0xa1, 0x7a, 0x83, 0x3c, 0xde, 0x1a, 0x9d, 0xc0, - 0x0a, 0x33, 0xac, 0xbf, 0x7e, 0x4e, 0xb6, 0xbe, 0x86, 0xa2, 0xe1, 0xbf, - 0x43, 0xc5, 0x21, 0xbf, 0xd3, 0xea, 0x4d, 0xbf, 0x46, 0xc0, 0xff, 0xff, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x51, 0x65, 0x08, 0xbe, - 0x6a, 0x72, 0x8d, 0xbe, 0x28, 0xac, 0x14, 0x3d, 0xbb, 0xe8, 0xab, 0xbc, - 0x08, 0xce, 0xe4, 0x3d, 0x97, 0x8a, 0x3f, 0x3d, 0xb9, 0xb3, 0x98, 0xbd, - 0xff, 0xb0, 0x91, 0xbe, 0x04, 0x0e, 0x86, 0x3c, 0xa3, 0xc9, 0xc5, 0xbd, - 0xe2, 0x83, 0x5e, 0xbc, 0x68, 0xd2, 0xfd, 0x3a, 0x90, 0x76, 0xa7, 0xbd, - 0x75, 0x80, 0xcc, 0xbd, 0x7d, 0xe2, 0xa4, 0xbc, 0x60, 0x60, 0xb7, 0x3d, - 0x87, 0x7d, 0xe6, 0xbd, 0x02, 0xed, 0xf7, 0xbe, 0x7f, 0xb1, 0xa5, 0x3b, - 0x72, 0x82, 0xfd, 0xbd, 0x3a, 0x88, 0x8f, 0x3e, 0x59, 0x93, 0x3e, 0x3d, - 0xc7, 0xa3, 0xf5, 0xbe, 0xfe, 0xdb, 0x79, 0xbe, 0x20, 0x7c, 0x2d, 0xbd, - 0xcb, 0x1e, 0x08, 0x3e, 0x08, 0x4e, 0x8c, 0xbe, 0x5f, 0x57, 0x0f, 0xbe, - 0xf7, 0xe0, 0x48, 0xbe, 0xd5, 0x16, 0xa7, 0xbe, 0x30, 0xea, 0x47, 0xbd, - 0x3e, 0xe5, 0x01, 0x3e, 0x28, 0xbb, 0x9a, 0xbe, 0xa4, 0x1a, 0xae, 0xbe, - 0x19, 0x07, 0x00, 0x3c, 0x6f, 0x9b, 0xe0, 0xbd, 0x27, 0xba, 0x71, 0x3e, - 0x86, 0xa0, 0xa7, 0x3c, 0x95, 0xca, 0xf8, 0xbc, 0x5b, 0xf5, 0xe0, 0xbe, - 0xfb, 0x51, 0x8c, 0xbb, 0x9e, 0x2f, 0x52, 0x3d, 0x11, 0x1d, 0x07, 0xbe, - 0xed, 0xea, 0x0e, 0xbf, 0x52, 0x0f, 0xa3, 0xbe, 0x16, 0xd8, 0xb0, 0xbe, - 0xba, 0xf0, 0x85, 0xbd, 0x60, 0xc0, 0x31, 0x3e, 0xce, 0x94, 0xb5, 0x3d, - 0xd9, 0x06, 0xc2, 0xbd, 0xdd, 0xd7, 0x67, 0x3c, 0x5c, 0x4b, 0x85, 0xbe, - 0x62, 0xc3, 0x0b, 0x3e, 0x8a, 0xb4, 0x84, 0xbc, 0xa7, 0x1c, 0xe3, 0xbd, - 0xc4, 0x5f, 0x1f, 0xbf, 0x5c, 0xf8, 0x2d, 0xbc, 0x71, 0xad, 0x53, 0x3d, - 0xab, 0xd8, 0x45, 0xbe, 0xb4, 0x7d, 0xee, 0xbe, 0x17, 0x02, 0x9b, 0xbe, - 0x71, 0x8b, 0x32, 0xbe, 0xca, 0x90, 0x82, 0xbe, 0xa6, 0xa0, 0x54, 0x3d, - 0xbc, 0xd1, 0x1e, 0x3b, 0xbb, 0xb7, 0x21, 0x3d, 0x63, 0x9b, 0x25, 0x3c, - 0x33, 0xf6, 0x80, 0xbe, 0xc0, 0x78, 0xd8, 0xbd, 0xd1, 0xe1, 0x85, 0x3d, - 0x79, 0xc6, 0x4a, 0xbe, 0xf2, 0x30, 0xa5, 0xbe, 0x4a, 0x82, 0x5f, 0xbc, - 0x6d, 0xed, 0x27, 0xbe, 0x73, 0x70, 0x5d, 0xbd, 0x6c, 0xc1, 0x8b, 0xbe, - 0x8b, 0x5b, 0xd0, 0xbe, 0xa7, 0x56, 0xc6, 0xbe, 0x8b, 0xe3, 0xcd, 0xbd, - 0x21, 0x4a, 0x0a, 0xbf, 0x1e, 0x33, 0xbf, 0xbd, 0x2c, 0x16, 0x8f, 0xbe, - 0x4f, 0xdf, 0x3e, 0x3c, 0x6c, 0x99, 0x6e, 0xbe, 0x2d, 0xa3, 0x23, 0xbf, - 0xe4, 0x4a, 0x7f, 0xbd, 0x8b, 0xb0, 0x47, 0x3d, 0xa3, 0x4b, 0x06, 0xbf, - 0xec, 0x51, 0x9d, 0x3d, 0x6f, 0xff, 0xad, 0xbe, 0x2b, 0x11, 0x6c, 0xbe, - 0xe1, 0x3b, 0xc7, 0xbe, 0x88, 0x1c, 0x8b, 0xbe, 0xb9, 0x83, 0xe6, 0xbd, - 0xd8, 0xb8, 0xf9, 0xbd, 0xf1, 0x65, 0x39, 0xbf, 0x6c, 0x6f, 0x4a, 0x3c, - 0xaa, 0x1c, 0x81, 0xbe, 0xc8, 0x75, 0x2f, 0xbc, 0x48, 0x70, 0xe6, 0xbc, - 0x85, 0x30, 0x25, 0xbe, 0x66, 0x16, 0x10, 0xbe, 0xda, 0x33, 0x2e, 0xbd, - 0x8e, 0xf8, 0xa7, 0x3c, 0x93, 0x7d, 0x3b, 0x3d, 0x9e, 0x08, 0xeb, 0xbe, - 0xfd, 0x39, 0x1b, 0x3e, 0xce, 0xd4, 0xbd, 0x3d, 0xca, 0x9a, 0x25, 0xbe, - 0x0f, 0x1b, 0x56, 0xbe, 0x78, 0x2b, 0x3e, 0xbd, 0xee, 0xcd, 0x5c, 0xbe, - 0xe1, 0xbf, 0x4a, 0x3e, 0x29, 0x66, 0xd6, 0x3d, 0xad, 0x7c, 0x7e, 0x3d, - 0xf4, 0x45, 0xe1, 0xbc, 0xf3, 0x16, 0x72, 0x3e, 0xa5, 0x39, 0x22, 0xbd, - 0x09, 0x13, 0x12, 0xbd, 0x0c, 0xa7, 0xa3, 0x3c, 0x77, 0x0c, 0xa0, 0x3d, - 0xfb, 0x86, 0x83, 0xbe, 0xf9, 0x02, 0x13, 0x3d, 0x14, 0xf6, 0x10, 0x3e, - 0x2c, 0xd6, 0x83, 0xbb, 0x67, 0xda, 0x29, 0x3d, 0x2b, 0x11, 0xb6, 0xbd, - 0x1c, 0xb6, 0x96, 0xbc, 0x1a, 0xba, 0x47, 0x3e, 0x86, 0x86, 0x49, 0x3e, - 0xc1, 0x7f, 0xc5, 0x3c, 0x1c, 0xec, 0xb3, 0x3c, 0x6f, 0xc9, 0xb2, 0x3d, - 0x7b, 0xfd, 0x7c, 0x3c, 0x5b, 0x57, 0x51, 0x3d, 0xd1, 0x90, 0xf2, 0x3d, - 0xe2, 0x1f, 0x8e, 0xbd, 0x94, 0x45, 0xe2, 0xbd, 0x4f, 0x30, 0x38, 0x3d, - 0x0e, 0xfb, 0x70, 0x3d, 0xcf, 0x37, 0x09, 0x3d, 0xbd, 0xed, 0xb4, 0xba, - 0x83, 0x49, 0x89, 0xbc, 0xf3, 0xc2, 0x36, 0xbd, 0xf6, 0x2e, 0x4b, 0x3d, - 0x55, 0x1b, 0x4f, 0x3d, 0x01, 0xa4, 0x08, 0xbe, 0x6b, 0x3b, 0x6d, 0x3c, - 0x3c, 0xc8, 0x2e, 0x3e, 0x7e, 0x71, 0xd9, 0xbd, 0x35, 0x82, 0xe3, 0x3c, - 0xea, 0xcd, 0x15, 0x3d, 0x7e, 0xda, 0x5d, 0xbe, 0xb3, 0x5e, 0x8f, 0x3c, - 0x19, 0x9b, 0x17, 0x3e, 0xf5, 0x9c, 0x17, 0x3e, 0x64, 0x8d, 0xd2, 0xbd, - 0xfe, 0x03, 0xd2, 0x38, 0x78, 0x67, 0x1e, 0xbd, 0x1e, 0x84, 0x49, 0xbe, - 0x01, 0x92, 0xf5, 0xbb, 0xa8, 0x84, 0x64, 0x3d, 0x12, 0x45, 0x94, 0x3c, - 0xbe, 0xa5, 0x2b, 0xbc, 0x95, 0xc6, 0x95, 0xbd, 0xde, 0xce, 0x66, 0xbd, - 0xbd, 0x15, 0x5e, 0xbd, 0xe1, 0x2d, 0xcb, 0xbe, 0x6b, 0x78, 0xdc, 0x3d, - 0xb2, 0xb9, 0xaf, 0x3c, 0xe9, 0x5b, 0xcd, 0x3d, 0xee, 0xdb, 0x7a, 0x3d, - 0x54, 0x07, 0x3b, 0x3d, 0xd6, 0xc4, 0x3d, 0xbe, 0x3a, 0x39, 0x0f, 0xbe, - 0x66, 0x97, 0xe3, 0xbd, 0xfc, 0x19, 0x9a, 0xbd, 0x6e, 0x94, 0x2c, 0xbe, - 0x86, 0x80, 0x05, 0x3e, 0x84, 0xc6, 0x3b, 0xbc, 0xae, 0xea, 0xc2, 0x3d, - 0x8c, 0xf1, 0xb5, 0x3d, 0x80, 0xe5, 0x5f, 0xbd, 0xe6, 0x67, 0xc1, 0x3d, - 0xad, 0x07, 0xb3, 0x3c, 0x64, 0xb5, 0xf6, 0x3d, 0x34, 0xdd, 0xb1, 0x3e, - 0xb2, 0xa3, 0xb6, 0xbc, 0x85, 0x88, 0x2c, 0x3e, 0x50, 0x4a, 0xc0, 0xbe, - 0xbb, 0x1d, 0xd1, 0xbd, 0x71, 0x41, 0xd4, 0x3b, 0x80, 0xf4, 0x8b, 0xbd, - 0x28, 0x87, 0x11, 0x3e, 0xee, 0x2e, 0xc6, 0xbe, 0xca, 0x0d, 0x0a, 0x3d, - 0xbd, 0x9e, 0x89, 0x3d, 0x74, 0x21, 0xaa, 0xbd, 0x31, 0x04, 0x29, 0x3d, - 0xc1, 0xec, 0x3b, 0x3d, 0xa0, 0xdd, 0xe5, 0xbd, 0x2f, 0x36, 0xd0, 0x3d, - 0xc2, 0xc1, 0xa6, 0x3e, 0x55, 0x83, 0xf1, 0x3c, 0x73, 0x10, 0x48, 0x3e, - 0x88, 0x5a, 0xc5, 0xbc, 0x58, 0x8f, 0x33, 0x3d, 0x7f, 0x90, 0xd2, 0xbd, - 0x48, 0xf8, 0x2e, 0x3e, 0x93, 0x63, 0xb2, 0xbe, 0xf3, 0x64, 0x9f, 0xbf, - 0x3d, 0x5d, 0x74, 0xbe, 0x04, 0xac, 0x96, 0x3e, 0xad, 0x8d, 0xfc, 0xbd, - 0x32, 0xe0, 0x65, 0xbe, 0x16, 0x66, 0x1d, 0x3d, 0x08, 0xf3, 0x62, 0x3c, - 0xe5, 0x22, 0x15, 0x3f, 0x19, 0xc3, 0x9a, 0xbe, 0xf4, 0xee, 0x70, 0x3d, - 0x38, 0xa6, 0x91, 0x3e, 0x07, 0xc3, 0xe3, 0xbd, 0x3c, 0x7d, 0x94, 0xbc, - 0xe6, 0x5b, 0x0f, 0x3d, 0xd8, 0x13, 0x29, 0x3e, 0xec, 0x5f, 0xbb, 0x3e, - 0xbc, 0x9f, 0x47, 0x3d, 0x65, 0xe7, 0x86, 0x3e, 0xec, 0xbe, 0x18, 0x3e, - 0x00, 0xcc, 0x65, 0x3d, 0x74, 0x25, 0xa0, 0x3e, 0xbe, 0x34, 0x83, 0x3e, - 0xb7, 0x29, 0xd4, 0x3b, 0x0f, 0xab, 0x83, 0x3b, 0x1d, 0xa0, 0xe0, 0x3e, - 0xe6, 0x98, 0x49, 0x3e, 0x99, 0xa2, 0xda, 0x3e, 0xa0, 0x91, 0x91, 0x3c, - 0xf3, 0xb5, 0x67, 0x3e, 0x2c, 0x0b, 0x10, 0x3e, 0xb8, 0x50, 0xc5, 0x3e, - 0xa3, 0xbc, 0xb4, 0x3e, 0xe7, 0xab, 0x8c, 0xbd, 0xc3, 0x42, 0x52, 0x3d, - 0x36, 0xc7, 0x96, 0x3e, 0x80, 0x65, 0x7d, 0x3e, 0x93, 0xb5, 0x61, 0xbe, - 0x58, 0x0f, 0xc0, 0x3d, 0xa3, 0x92, 0xba, 0x39, 0x8c, 0xa3, 0x9b, 0xbd, - 0x90, 0x35, 0xaa, 0x3e, 0x1a, 0xd9, 0x13, 0x3f, 0xa5, 0xac, 0x30, 0x3e, - 0x40, 0x80, 0x65, 0xbe, 0x9f, 0xaa, 0xe4, 0x3d, 0x8d, 0x20, 0x2e, 0x3c, - 0x2b, 0x0a, 0xb6, 0xbd, 0xed, 0x8b, 0x68, 0x3e, 0x5d, 0x45, 0x3a, 0xbc, - 0xee, 0xda, 0x64, 0x3e, 0x35, 0x0f, 0xa7, 0x3d, 0x3e, 0x00, 0x5d, 0x3e, - 0x4a, 0xcb, 0x91, 0xbd, 0x58, 0x88, 0x0c, 0xbe, 0xbd, 0x7d, 0xd7, 0xbb, - 0x11, 0x70, 0xe2, 0xbd, 0x1e, 0x32, 0xc9, 0x3a, 0x1c, 0xa7, 0x6c, 0xbe, - 0x7e, 0x60, 0xec, 0x3d, 0x8d, 0xc8, 0xa2, 0xbe, 0x20, 0x9b, 0x04, 0x3e, - 0x51, 0xff, 0x84, 0xbe, 0xe6, 0x13, 0x96, 0x3c, 0x82, 0x00, 0x82, 0xbd, - 0x5e, 0x93, 0x60, 0x3c, 0x7f, 0xa2, 0xe0, 0x3c, 0xee, 0x3e, 0x00, 0xbe, - 0xbe, 0xd5, 0x85, 0x3e, 0xf6, 0xd6, 0xcb, 0x3d, 0x6d, 0x2f, 0x21, 0xbe, - 0x31, 0x8c, 0x07, 0x3d, 0xa3, 0x68, 0x89, 0x3b, 0x01, 0x37, 0x1c, 0xbe, - 0x34, 0x87, 0x98, 0xbd, 0xff, 0xae, 0x46, 0x3d, 0xf4, 0xc5, 0x91, 0xbe, - 0xd0, 0xc9, 0x39, 0xbd, 0x2e, 0xa1, 0x8d, 0xbe, 0x3d, 0x09, 0x8a, 0xbe, - 0x0d, 0x34, 0xc2, 0x3d, 0x59, 0x37, 0x83, 0x3c, 0xa2, 0x3d, 0x2f, 0x3e, - 0x0a, 0x47, 0x22, 0xbe, 0x5f, 0xa9, 0x21, 0x3d, 0xa8, 0x57, 0xe9, 0xbb, - 0x48, 0xf6, 0xe6, 0xbd, 0x1a, 0xa2, 0x79, 0x3c, 0xfc, 0x3a, 0x25, 0xbe, - 0xff, 0x7c, 0x29, 0x3d, 0x3a, 0xe3, 0x14, 0xbc, 0xf2, 0xa1, 0xeb, 0x3b, - 0xfa, 0xa1, 0x2f, 0xbe, 0xc3, 0xd9, 0x58, 0x3d, 0x17, 0x7b, 0xae, 0xbe, - 0xb6, 0xb7, 0x88, 0xbe, 0x83, 0xc0, 0xdc, 0xbd, 0x4e, 0xe1, 0x26, 0xbb, - 0x9b, 0x14, 0x5f, 0x3d, 0x03, 0x83, 0x6c, 0x3d, 0x2c, 0xac, 0x73, 0x3e, - 0x97, 0x00, 0x20, 0xbe, 0x84, 0x57, 0x09, 0xbe, 0x7c, 0x88, 0xec, 0x3d, - 0x92, 0xfa, 0x8a, 0x3d, 0xa4, 0xb5, 0x1d, 0xbd, 0x42, 0x73, 0xe6, 0xbc, - 0x7a, 0x13, 0x93, 0x3d, 0xaf, 0x27, 0x8b, 0xbe, 0x55, 0x63, 0xa9, 0xbc, - 0x87, 0x88, 0x6b, 0xbe, 0xea, 0x54, 0x79, 0xbe, 0x76, 0x8b, 0x2e, 0x3d, - 0x5e, 0x9c, 0x5f, 0x3a, 0xb7, 0x02, 0xfe, 0x3d, 0xdc, 0xc1, 0xf6, 0xbc, - 0xf8, 0x19, 0x3d, 0x3e, 0x14, 0x2b, 0x15, 0xbe, 0x04, 0x85, 0x47, 0xbe, - 0xbb, 0xd1, 0x24, 0xbd, 0x2e, 0x79, 0x94, 0x3d, 0xb1, 0xd1, 0x10, 0xbe, - 0xac, 0x70, 0x64, 0xbd, 0x7f, 0xa3, 0x7a, 0x3c, 0xf9, 0xd9, 0x2a, 0xbe, - 0x36, 0x5a, 0x9d, 0x3d, 0xb0, 0xeb, 0x1b, 0xbd, 0x55, 0xe9, 0x56, 0xbe, - 0x0b, 0x05, 0x22, 0xbe, 0xa1, 0xc7, 0x9e, 0xbd, 0x62, 0xaa, 0x0a, 0x39, - 0x77, 0x8f, 0x86, 0xbe, 0xe4, 0x24, 0xe2, 0x3d, 0x67, 0xb9, 0xa0, 0xbd, - 0xa2, 0xed, 0xdb, 0xbd, 0xd9, 0xde, 0x2a, 0x3d, 0xbb, 0x01, 0x9d, 0xba, - 0x9b, 0xff, 0x10, 0x3d, 0xfb, 0xe6, 0xd3, 0x3c, 0xfc, 0x63, 0x2a, 0x3d, - 0x44, 0x96, 0x1c, 0xbe, 0xbf, 0xdd, 0xb5, 0xbc, 0x7d, 0xca, 0x78, 0xbe, - 0x34, 0x8b, 0x36, 0xbe, 0x93, 0xd1, 0x98, 0xbc, 0x79, 0xd8, 0xd6, 0x3b, - 0x9c, 0xe9, 0x84, 0x3d, 0x76, 0xd6, 0x79, 0xbd, 0x52, 0x53, 0x3a, 0xbd, - 0xdf, 0x15, 0x18, 0xbd, 0x31, 0x0e, 0x2a, 0xbe, 0xc4, 0xac, 0x19, 0x3d, - 0xc7, 0x94, 0xa9, 0x3d, 0x2d, 0x01, 0x68, 0xbc, 0x05, 0x38, 0xbb, 0xbd, - 0xd4, 0xe5, 0x4a, 0x3d, 0x82, 0x7e, 0x29, 0xbe, 0xb7, 0x4a, 0x0b, 0xbd, - 0xae, 0x9a, 0x54, 0xbd, 0x0b, 0xf3, 0x7f, 0xbe, 0xfa, 0x90, 0x42, 0xbe, - 0x89, 0x13, 0x09, 0xbe, 0x3c, 0x22, 0x41, 0x3d, 0x44, 0xa9, 0x86, 0xbe, - 0x60, 0x6e, 0xda, 0x3d, 0x51, 0x3e, 0x1a, 0xbe, 0x88, 0x8f, 0x22, 0xbe, - 0xc7, 0xbb, 0x4f, 0x3d, 0x20, 0xcf, 0x21, 0x3e, 0xc0, 0xfc, 0xdc, 0xbd, - 0x5a, 0xe0, 0x13, 0xbe, 0x9f, 0xe7, 0x68, 0xbd, 0x67, 0x67, 0xa9, 0xbd, - 0xe3, 0x54, 0x63, 0x3d, 0x2c, 0x90, 0x0e, 0xbe, 0x0e, 0xcf, 0x02, 0x3c, - 0x6a, 0xf8, 0xf9, 0x3c, 0xff, 0x14, 0x6e, 0xbd, 0x73, 0x5b, 0x26, 0x3d, - 0x1b, 0x87, 0xea, 0xbd, 0x78, 0x86, 0xb2, 0x3c, 0x55, 0x66, 0x06, 0xbe, - 0x07, 0x7e, 0x10, 0xbe, 0x26, 0xf7, 0x7f, 0x3d, 0xcf, 0x1d, 0xfa, 0x3d, - 0xb3, 0x3b, 0x40, 0xbe, 0x8c, 0x81, 0x3e, 0x3c, 0xfb, 0xa5, 0x06, 0xbe, - 0xf0, 0x50, 0x78, 0xbd, 0xc1, 0x2e, 0x07, 0xbd, 0x70, 0x17, 0xb4, 0xbe, - 0x21, 0xcf, 0xb5, 0xbd, 0x29, 0x3e, 0xd2, 0xbc, 0x54, 0x68, 0x32, 0xbe, - 0x53, 0xb0, 0xbf, 0x3d, 0x5a, 0x5b, 0xba, 0x3d, 0x65, 0x81, 0xb1, 0xbd, - 0xc5, 0xb8, 0x2a, 0xbe, 0x4e, 0x76, 0x65, 0xbe, 0x0d, 0x31, 0x17, 0xbc, - 0x94, 0x64, 0x0b, 0x3e, 0xb2, 0x11, 0x37, 0xbe, 0x9b, 0xaa, 0xe0, 0xbc, - 0xed, 0x6e, 0x2c, 0xbc, 0xf1, 0x73, 0x49, 0xbe, 0xff, 0x4c, 0x7d, 0xbc, - 0x5c, 0xc4, 0xc3, 0xbd, 0xb5, 0x85, 0x01, 0xbe, 0xfa, 0x7d, 0xa8, 0xbd, - 0x46, 0xf8, 0xfd, 0xbd, 0x48, 0x17, 0x7e, 0xbc, 0x7c, 0xf9, 0x74, 0xbe, - 0xe9, 0x26, 0x02, 0xbc, 0x48, 0x50, 0x4b, 0xbd, 0xb3, 0x26, 0x63, 0xbe, - 0x28, 0x92, 0xa2, 0x3d, 0xef, 0xa5, 0x8b, 0x3d, 0x48, 0xae, 0x0f, 0xbe, - 0x59, 0xb5, 0x78, 0x3d, 0x50, 0xdc, 0xb1, 0x3d, 0x16, 0xc1, 0x3f, 0xbe, - 0x5d, 0x98, 0x9d, 0xbc, 0xd3, 0x51, 0x04, 0x3e, 0x6e, 0xa4, 0x74, 0x3b, - 0xe0, 0x11, 0xa8, 0x3e, 0xf7, 0xf2, 0x7b, 0xbd, 0xba, 0xb4, 0xfe, 0x3c, - 0x1f, 0xd5, 0x49, 0xbc, 0x9d, 0x07, 0x66, 0x3e, 0x5c, 0x6e, 0xc3, 0xbb, - 0x9c, 0x2d, 0x30, 0x3e, 0x3b, 0xb0, 0x96, 0x3d, 0x31, 0xf1, 0x22, 0xbe, - 0x8a, 0x02, 0x8b, 0x3e, 0x18, 0x91, 0x2c, 0x3d, 0x33, 0xe5, 0x18, 0x3e, - 0x71, 0x2f, 0x04, 0xbd, 0xcc, 0xd1, 0x23, 0x3f, 0x5b, 0x92, 0x16, 0x3e, - 0xb4, 0xae, 0x49, 0xbd, 0xf1, 0x8d, 0x0a, 0x3e, 0x9e, 0x4b, 0x74, 0x3a, - 0xa3, 0x6c, 0x53, 0x3d, 0xaf, 0x2e, 0x85, 0x3d, 0x57, 0x77, 0x62, 0x3d, - 0x70, 0x7b, 0xe9, 0xbd, 0x5c, 0x0c, 0x2b, 0xbd, 0xf4, 0x6e, 0x2a, 0xbc, - 0x57, 0xae, 0x03, 0xbe, 0x05, 0x7c, 0x6c, 0x3d, 0x02, 0x47, 0x86, 0x3c, - 0x5c, 0xe2, 0x02, 0xbc, 0x20, 0xf5, 0x3e, 0x3e, 0xf8, 0xb1, 0x92, 0x3d, - 0x3b, 0xa0, 0x86, 0xbc, 0x63, 0x6d, 0xa0, 0xbe, 0x35, 0x09, 0x42, 0x3e, - 0x8a, 0xe9, 0x90, 0xbc, 0x72, 0xd0, 0xe5, 0xbd, 0x4f, 0x52, 0x77, 0x3e, - 0x12, 0xab, 0xe1, 0xbd, 0x81, 0x2a, 0xd3, 0xbe, 0x38, 0x6b, 0x80, 0xbe, - 0xa1, 0xfa, 0x31, 0x3d, 0xd6, 0x96, 0x8b, 0xbd, 0xa3, 0x3c, 0x4d, 0xbe, - 0x3e, 0x26, 0x6b, 0x3c, 0x6a, 0x87, 0xd6, 0xbd, 0x88, 0x4a, 0xc1, 0xbb, - 0xd1, 0x25, 0xc2, 0x3c, 0xbe, 0x9e, 0x89, 0x3e, 0x4c, 0x00, 0xc6, 0xbe, - 0x80, 0x9e, 0x5b, 0xbd, 0x7f, 0x49, 0xc8, 0x3c, 0x9f, 0x06, 0x72, 0x3d, - 0xb7, 0x0d, 0x50, 0x3e, 0x3a, 0xe6, 0x43, 0xbd, 0x9a, 0x90, 0xbe, 0xbe, - 0xc6, 0x7a, 0x62, 0xbe, 0x97, 0x43, 0x47, 0xbd, 0x30, 0x6f, 0x86, 0xbc, - 0x72, 0x60, 0xb2, 0xbe, 0x21, 0x82, 0x95, 0xbe, 0xa8, 0xf2, 0x2f, 0xbe, - 0xa9, 0xb6, 0xb3, 0xbe, 0x92, 0xea, 0x9e, 0xbc, 0xd4, 0x68, 0x5f, 0x3d, - 0x16, 0xc7, 0xc6, 0xbe, 0x5b, 0xcc, 0x67, 0xbe, 0xf1, 0x96, 0xb3, 0x3c, - 0xbd, 0xd6, 0xad, 0xbc, 0x80, 0x17, 0x03, 0x3c, 0x07, 0x4c, 0x12, 0xbe, - 0xb7, 0x36, 0x26, 0xbf, 0xd1, 0x8a, 0xaf, 0xbe, 0x99, 0x6e, 0xab, 0xbc, - 0xe2, 0x91, 0x2e, 0x3d, 0xf2, 0x40, 0xa3, 0xbe, 0x42, 0x3e, 0xb7, 0xbe, - 0x33, 0xdf, 0xe4, 0xbd, 0xaa, 0xf5, 0x7b, 0xbe, 0x84, 0x23, 0xc7, 0x3a, - 0xe8, 0xfb, 0x64, 0x3c, 0x9b, 0xd5, 0xbc, 0x3c, 0x80, 0xed, 0x08, 0xbd, - 0x05, 0x00, 0x80, 0x3d, 0x9d, 0x1e, 0x2d, 0xbe, 0x5c, 0x74, 0x3e, 0x3e, - 0x0a, 0x27, 0x01, 0x3e, 0xc4, 0x8e, 0xe9, 0x3c, 0x55, 0xa2, 0x6c, 0x3d, - 0x97, 0x63, 0xb4, 0xbd, 0x7a, 0x18, 0xa9, 0xbd, 0x6b, 0xf8, 0x71, 0xbe, - 0xf5, 0x36, 0x21, 0xbf, 0xaf, 0x92, 0xa8, 0xbd, 0xea, 0xc5, 0x03, 0xbf, - 0xad, 0xec, 0x0d, 0xbd, 0x2d, 0xdb, 0xad, 0xbd, 0x98, 0x78, 0x34, 0xbe, - 0x92, 0x4f, 0xe5, 0x3e, 0x19, 0xb8, 0x59, 0xbb, 0x7a, 0x81, 0x67, 0xbe, - 0xd4, 0x5d, 0xc4, 0xbd, 0x08, 0x38, 0xdd, 0x3c, 0x38, 0xc2, 0x05, 0x3d, - 0x2d, 0x51, 0xbc, 0xbd, 0x7c, 0x64, 0xaf, 0xbc, 0x66, 0xf0, 0x6d, 0xbe, - 0x9f, 0x6b, 0x4b, 0x3d, 0xb9, 0xa3, 0x36, 0xbf, 0xb5, 0x35, 0x14, 0xbd, - 0x9b, 0x8f, 0xd5, 0xbe, 0xff, 0x9a, 0x11, 0x3d, 0x29, 0x0c, 0x97, 0xbc, - 0x5a, 0xc6, 0xb8, 0xbe, 0x7e, 0xab, 0xa5, 0xbe, 0x29, 0x5c, 0x9c, 0x3d, - 0xf5, 0x1d, 0xba, 0xbe, 0xbf, 0x29, 0x47, 0xbd, 0xa8, 0x68, 0xc7, 0xbd, - 0x5d, 0xa8, 0xb8, 0xbd, 0x2c, 0x5c, 0x3f, 0xbe, 0x19, 0xe4, 0x10, 0xbe, - 0x6a, 0xdd, 0x1e, 0xbe, 0x7f, 0xd5, 0x76, 0xbd, 0xa9, 0xfe, 0x16, 0xbf, - 0x87, 0x10, 0xad, 0xbc, 0x26, 0x05, 0x16, 0xbe, 0x34, 0xd5, 0x77, 0xbe, - 0x77, 0x94, 0xfb, 0x3d, 0xe4, 0x9d, 0x3b, 0x3d, 0x59, 0xa7, 0x9c, 0xbd, - 0x94, 0x91, 0x03, 0xbb, 0xd8, 0x57, 0xef, 0xbe, 0x1b, 0x23, 0xa0, 0x3d, - 0xb6, 0x7f, 0x69, 0xbd, 0xdb, 0x6f, 0xa0, 0xbc, 0x88, 0x2c, 0x77, 0xbd, - 0x0e, 0xb5, 0x4b, 0xbe, 0xa3, 0x71, 0xde, 0x3c, 0xe0, 0x46, 0x20, 0xbe, - 0xfd, 0xe2, 0x85, 0xbe, 0x1a, 0xe2, 0x8f, 0x3d, 0xd4, 0x21, 0x0b, 0xbf, - 0x61, 0x74, 0xc6, 0xbc, 0xdb, 0x3c, 0x9c, 0x3d, 0x13, 0xbe, 0x39, 0xbe, - 0x2b, 0x19, 0x4f, 0x3d, 0x65, 0x4b, 0x59, 0xbd, 0x0d, 0xa4, 0x47, 0xbe, - 0x3e, 0xad, 0x8e, 0x3e, 0xce, 0x5f, 0xe8, 0xbd, 0xf4, 0x82, 0xe7, 0xbd, - 0xec, 0x7b, 0xba, 0x3d, 0x16, 0x94, 0x9c, 0xbe, 0x45, 0x7a, 0x48, 0x3c, - 0x17, 0xd6, 0xad, 0xbe, 0x04, 0x6b, 0x14, 0x3e, 0xe6, 0x9c, 0x0a, 0x3e, - 0xea, 0xfe, 0x99, 0xbe, 0x4c, 0xe4, 0x3c, 0xbd, 0xfd, 0x98, 0x2e, 0x3e, - 0x31, 0xf3, 0x5b, 0x3e, 0x6c, 0xce, 0x5f, 0x3e, 0xd4, 0x35, 0xe9, 0xbb, - 0xb3, 0x51, 0x80, 0xbe, 0x9f, 0xde, 0x84, 0x3e, 0x5d, 0xaf, 0xd8, 0xba, - 0xbf, 0x58, 0x32, 0x3d, 0x96, 0xb7, 0x34, 0x3d, 0x75, 0x1d, 0x7b, 0xbe, - 0xd4, 0x3d, 0xd0, 0xbd, 0x51, 0xf9, 0xa0, 0xbd, 0x95, 0x7a, 0x31, 0x3d, - 0xba, 0x85, 0xb5, 0x3c, 0x5e, 0xe0, 0x39, 0xbe, 0x7d, 0xbc, 0xde, 0xbd, - 0xfc, 0x54, 0x57, 0x3d, 0x21, 0x22, 0x54, 0x3e, 0xf0, 0xab, 0x14, 0x3e, - 0x22, 0x50, 0xfd, 0xbd, 0x80, 0x95, 0x1d, 0xbe, 0x2b, 0x71, 0xb6, 0x3e, - 0x88, 0x87, 0x78, 0x3d, 0x75, 0xd9, 0xdc, 0x3d, 0xa8, 0x06, 0x33, 0x3d, - 0x08, 0xe0, 0xb9, 0xbd, 0x63, 0x1e, 0xfb, 0xbd, 0x0e, 0x1f, 0x99, 0x3c, - 0xa5, 0x91, 0xac, 0x3d, 0xd3, 0xf7, 0x65, 0x3d, 0xa4, 0x97, 0x6a, 0xbc, - 0xda, 0x99, 0xa2, 0xbd, 0x54, 0x1b, 0x43, 0x3d, 0xc9, 0xed, 0x1d, 0x3e, - 0x9f, 0x4e, 0x79, 0x3e, 0x06, 0x3b, 0x2e, 0x3c, 0x76, 0x00, 0x59, 0xbe, - 0x5b, 0x29, 0x66, 0x3e, 0xd2, 0xad, 0x86, 0xbb, 0xf1, 0x9c, 0xc3, 0x3d, - 0x39, 0x62, 0xe7, 0x3d, 0x48, 0x95, 0x14, 0x3e, 0x66, 0x7b, 0xb8, 0xbc, - 0x9e, 0xdb, 0x27, 0x3c, 0x08, 0xf3, 0x14, 0x3e, 0xcc, 0x20, 0x32, 0xbd, - 0x5b, 0xa5, 0x4b, 0xbc, 0xd1, 0xa4, 0x6c, 0xbd, 0x06, 0xee, 0x8d, 0x3d, - 0xde, 0x1a, 0x02, 0x3e, 0x48, 0x84, 0x05, 0x3e, 0x9f, 0xa2, 0x22, 0x3d, - 0xb4, 0xe8, 0x97, 0xbe, 0x9f, 0xae, 0x10, 0x3e, 0x40, 0x00, 0x44, 0x3d, - 0x15, 0xf7, 0x21, 0x3e, 0x63, 0x06, 0x8e, 0x3c, 0x19, 0x86, 0xca, 0x3d, - 0xbb, 0xa3, 0xa0, 0x3d, 0x66, 0x6f, 0x1b, 0x3e, 0xec, 0xb3, 0x1e, 0x3d, - 0x3e, 0x2f, 0x5a, 0xbc, 0x3b, 0x15, 0x01, 0xbe, 0x5a, 0x6c, 0x01, 0xbe, - 0xf2, 0xa8, 0x8b, 0xbe, 0xca, 0x27, 0x99, 0x3b, 0xf9, 0x1b, 0xf8, 0xba, - 0xd2, 0x64, 0x34, 0x3d, 0x22, 0xee, 0x28, 0xbd, 0x8c, 0xda, 0x16, 0xbe, - 0xca, 0xc6, 0x3e, 0xb9, 0x51, 0xc4, 0x88, 0x3e, 0xbc, 0x01, 0x08, 0x3e, - 0x89, 0xe1, 0x82, 0x3e, 0x02, 0x9a, 0x86, 0xbe, 0x72, 0x13, 0x0f, 0x3c, - 0xcb, 0x5d, 0x7d, 0xbc, 0xea, 0xe3, 0x89, 0xbd, 0xd7, 0xc3, 0x07, 0xbe, - 0x1a, 0xca, 0x47, 0xbd, 0x45, 0xab, 0x62, 0xbd, 0xce, 0x31, 0x2d, 0xbd, - 0x96, 0x59, 0xa4, 0x3d, 0xb1, 0xa1, 0x9a, 0x3b, 0x28, 0xf7, 0x8a, 0xbb, - 0xe3, 0xfe, 0xb6, 0x3c, 0x55, 0x09, 0x39, 0xbe, 0x58, 0xd0, 0xed, 0xbd, - 0xb7, 0x81, 0xc2, 0xbd, 0x7d, 0xa9, 0x20, 0xbe, 0x6a, 0xd7, 0x05, 0xbd, - 0x2c, 0x2e, 0x68, 0x3d, 0x1d, 0xba, 0x88, 0xbc, 0x19, 0x87, 0x4f, 0x3c, - 0x2b, 0xa2, 0x33, 0xbb, 0x32, 0x17, 0x00, 0xbe, 0x08, 0x41, 0xd8, 0x3c, - 0x80, 0x2c, 0x8d, 0x3d, 0xc9, 0x58, 0xcc, 0xbd, 0x33, 0x97, 0x0c, 0xbe, - 0xf5, 0x8b, 0x44, 0xbe, 0x45, 0xd4, 0x30, 0x3d, 0xe8, 0x83, 0xd0, 0x3c, - 0xf2, 0x86, 0x4b, 0x3d, 0xf3, 0xdb, 0x2d, 0x3c, 0x30, 0x12, 0xcc, 0xbc, - 0x30, 0x19, 0xaf, 0xbd, 0x17, 0xe9, 0xb9, 0x3d, 0xb7, 0x7c, 0xa8, 0x3d, - 0x5c, 0xb3, 0x76, 0xbd, 0x6a, 0xd0, 0xe2, 0xbd, 0x91, 0xb5, 0x08, 0xbd, - 0xe4, 0xcf, 0xee, 0xbd, 0xb0, 0x0a, 0xe5, 0xbd, 0x83, 0x2a, 0xd8, 0xbd, - 0x6e, 0x37, 0x59, 0xbe, 0x1c, 0xdf, 0x5e, 0xbd, 0x4f, 0xf7, 0x98, 0x3d, - 0xc3, 0x62, 0x42, 0x3a, 0x8c, 0x59, 0x06, 0xbe, 0xa0, 0x77, 0xd7, 0x3d, - 0x8a, 0x88, 0xb9, 0xbd, 0xa2, 0x69, 0xd4, 0x3d, 0xa4, 0x20, 0x9c, 0x3b, - 0x71, 0x43, 0xa1, 0xbd, 0x7a, 0xbf, 0x35, 0xbe, 0x97, 0xa2, 0x1f, 0x3d, - 0x47, 0x87, 0x25, 0xbc, 0x95, 0xd7, 0xa1, 0xbc, 0x7b, 0x9b, 0xd9, 0x3d, - 0xac, 0x3f, 0xd9, 0xbd, 0xcf, 0xe2, 0x90, 0x3d, 0x3b, 0x94, 0x3b, 0xbe, - 0x0f, 0x98, 0x24, 0xbe, 0x9e, 0x99, 0xb9, 0xbd, 0x97, 0xcf, 0xe6, 0x3d, - 0x22, 0x7a, 0xc6, 0x3d, 0x00, 0x6a, 0x36, 0xbe, 0x97, 0xdf, 0x90, 0xbc, - 0x8d, 0x8b, 0xc2, 0xbd, 0xef, 0x38, 0x21, 0xbe, 0x68, 0xc1, 0xc3, 0xbc, - 0x37, 0xc3, 0x1a, 0xbe, 0x07, 0x71, 0x85, 0xbd, 0xef, 0xae, 0x0b, 0xbe, - 0xda, 0xcc, 0x09, 0xbd, 0xe4, 0x36, 0x0c, 0xbd, 0xa8, 0x2e, 0x09, 0xbd, - 0xe4, 0x1a, 0x96, 0x3d, 0x7b, 0x8f, 0xf6, 0x3d, 0x69, 0xba, 0x83, 0xbd, - 0xbc, 0xb5, 0x90, 0x3d, 0xfd, 0x5b, 0x29, 0xbd, 0xac, 0xc7, 0x3d, 0x3c, - 0xa5, 0x08, 0x05, 0x3e, 0x55, 0x17, 0x95, 0x3d, 0x11, 0xf5, 0x08, 0xbd, - 0x52, 0xe8, 0x1f, 0xbe, 0xfb, 0x88, 0x18, 0xbd, 0x08, 0x10, 0x97, 0xbd, - 0x07, 0x59, 0x59, 0xbd, 0x12, 0xef, 0x0c, 0xbe, 0xb4, 0x09, 0x81, 0xbd, - 0xa8, 0xf3, 0x8e, 0xbd, 0x74, 0xc9, 0x8d, 0xbc, 0x28, 0x2f, 0x8b, 0x3d, - 0x24, 0xd0, 0x44, 0xbe, 0x29, 0x65, 0x37, 0xbe, 0x73, 0x3a, 0xc4, 0xbd, - 0x6c, 0xda, 0x05, 0xbe, 0x06, 0x3a, 0x40, 0x3c, 0xc7, 0x1e, 0x6b, 0x3d, - 0x1e, 0xc2, 0x55, 0x3d, 0xc3, 0x25, 0x36, 0xbe, 0x5f, 0x0f, 0x89, 0x3c, - 0xb9, 0x13, 0x40, 0xbe, 0x3e, 0x1c, 0x0e, 0xbd, 0x4d, 0x50, 0xbf, 0x3d, - 0xc2, 0x97, 0xa0, 0xbd, 0x35, 0x36, 0x37, 0xbe, 0xc5, 0x4a, 0x88, 0xbd, - 0xe9, 0x8c, 0x35, 0xbd, 0x1c, 0xb0, 0xa1, 0xbd, 0x94, 0x23, 0xc0, 0xbd, - 0x5c, 0xc9, 0xba, 0x3d, 0x11, 0x1e, 0x99, 0xbd, 0x9f, 0x76, 0x19, 0xbe, - 0xe7, 0xfe, 0x45, 0xbd, 0x25, 0x75, 0x55, 0xbd, 0x99, 0xd5, 0x04, 0xbd, - 0x4a, 0x14, 0xab, 0xbc, 0x29, 0xc6, 0x5e, 0xbd, 0xd4, 0xfe, 0x88, 0xbd, - 0xb4, 0xc3, 0x1b, 0xbe, 0xc5, 0x14, 0xb0, 0x3d, 0xe1, 0x04, 0x35, 0xbe, - 0x53, 0xe2, 0x49, 0xbe, 0x0b, 0xd9, 0xff, 0xb9, 0x87, 0xb6, 0x89, 0xbd, - 0xbd, 0x80, 0xcf, 0x3c, 0xf1, 0xa8, 0x05, 0xbd, 0x34, 0xc4, 0x17, 0xbe, - 0xc3, 0x8c, 0xd0, 0xbb, 0x12, 0x67, 0x21, 0xbd, 0x54, 0x5c, 0x0d, 0x3d, - 0xef, 0x00, 0x40, 0xbd, 0xeb, 0x1d, 0xbd, 0xbd, 0x98, 0xd9, 0x94, 0xbc, - 0xd2, 0x24, 0xbd, 0x3d, 0xbe, 0x85, 0xc4, 0xbd, 0x70, 0xf0, 0xb0, 0x3d, - 0x1c, 0x55, 0xf4, 0xbd, 0xe3, 0x60, 0x07, 0xbc, 0xd5, 0xf3, 0x8f, 0xbc, - 0x73, 0xac, 0xae, 0xbd, 0xc0, 0x55, 0x50, 0xbc, 0x32, 0xa0, 0x27, 0xbd, - 0x6a, 0x9c, 0xa6, 0x3d, 0x7a, 0x1e, 0xe7, 0x3d, 0x09, 0x5a, 0x0a, 0xbe, - 0xa2, 0xee, 0x91, 0xbd, 0x19, 0xd4, 0x12, 0x3a, 0xf2, 0x2b, 0x1a, 0xbd, - 0x58, 0x36, 0x61, 0x3d, 0x88, 0x27, 0xe5, 0xbd, 0xe1, 0x6e, 0x18, 0xbd, - 0xd3, 0xc5, 0x50, 0x3d, 0x49, 0xd7, 0xe3, 0xbd, 0x5c, 0xe9, 0x1f, 0xbe, - 0xe5, 0x12, 0x0a, 0xbe, 0x84, 0x7a, 0x04, 0xbe, 0x3a, 0x10, 0x5d, 0x3c, - 0x84, 0x7f, 0xc0, 0xbd, 0x9f, 0xd3, 0xa4, 0xbd, 0x26, 0xfc, 0x05, 0xbe, - 0xba, 0xff, 0x01, 0xbe, 0x1b, 0xbf, 0x54, 0xbc, 0x1f, 0x41, 0x63, 0xbe, - 0x97, 0x95, 0x70, 0xbd, 0x4b, 0x69, 0x0f, 0xbd, 0xe3, 0x8a, 0xc2, 0xbc, - 0x4c, 0xa2, 0x89, 0x3d, 0xaa, 0x69, 0xc0, 0x3d, 0x75, 0x2f, 0x7e, 0x3d, - 0x5b, 0x43, 0x61, 0xbe, 0x03, 0x02, 0xaa, 0xbc, 0x21, 0x53, 0x46, 0xbe, - 0xd9, 0x8b, 0x33, 0xbe, 0x69, 0x45, 0x12, 0xbc, 0xa1, 0x6f, 0x06, 0x3d, - 0x01, 0x50, 0xe5, 0xbc, 0x80, 0xe4, 0xb1, 0xbd, 0xf3, 0xd1, 0xa5, 0xbd, - 0x0e, 0x36, 0x98, 0xbd, 0x7a, 0xc6, 0x3d, 0x3d, 0x7d, 0x67, 0x42, 0xbe, - 0x5d, 0xb5, 0xcc, 0x3c, 0x69, 0xf6, 0x1c, 0x3d, 0x2b, 0xc6, 0x54, 0xba, - 0xd0, 0x1d, 0xf3, 0xbd, 0xb2, 0xe2, 0x1c, 0xbc, 0x8d, 0x7f, 0x0a, 0x3d, - 0x58, 0x09, 0x2a, 0xbe, 0xbc, 0x22, 0x70, 0xbe, 0x63, 0x6b, 0x00, 0xbe, - 0x8f, 0xb0, 0x2a, 0xbd, 0xab, 0x41, 0x6a, 0xbc, 0x74, 0x4c, 0xfe, 0xbd, - 0x26, 0xd0, 0x18, 0xbc, 0x5f, 0xa3, 0x1c, 0xbd, 0x24, 0x92, 0xfe, 0xbd, - 0x6f, 0x03, 0x12, 0xbe, 0xcc, 0x07, 0x6d, 0x3a, 0x38, 0xf9, 0xe7, 0x3c, - 0xab, 0x6a, 0xbe, 0xbd, 0x10, 0x01, 0x41, 0xbd, 0xfc, 0x0d, 0x14, 0xbe, - 0x20, 0x6f, 0x2f, 0xbe, 0x54, 0x18, 0x42, 0xbe, 0xab, 0x0c, 0xc9, 0x3b, - 0x90, 0x79, 0x8f, 0xbd, 0x8c, 0x1c, 0x60, 0x3c, 0x9e, 0x23, 0x39, 0x3d, - 0x7c, 0x18, 0x0f, 0xbe, 0xf4, 0x30, 0xd4, 0xbd, 0x4a, 0xe3, 0x2f, 0x3d, - 0x5d, 0x1c, 0xd9, 0x3c, 0x99, 0x4b, 0xa2, 0xbd, 0x52, 0x5f, 0x2b, 0xbe, - 0x8c, 0x1d, 0x75, 0x3d, 0xfb, 0x43, 0xff, 0xbc, 0x05, 0x2f, 0x89, 0xbd, - 0xbe, 0x2e, 0xd1, 0xbd, 0x67, 0x7c, 0x1b, 0xbe, 0x7f, 0xa6, 0xc6, 0xbd, - 0xc8, 0x57, 0x49, 0xbc, 0x9f, 0x9a, 0xc0, 0xbd, 0xba, 0x26, 0x25, 0xbd, - 0xa2, 0x48, 0xd8, 0xbd, 0xfd, 0xa8, 0x12, 0x3b, 0x27, 0x03, 0x06, 0xbd, - 0x99, 0xcb, 0x94, 0xbd, 0xd1, 0x34, 0x09, 0xbe, 0x6c, 0x75, 0x6f, 0xbc, - 0xe7, 0xa5, 0x2c, 0xbe, 0x6e, 0x39, 0xd1, 0xbc, 0xbd, 0x54, 0xe3, 0xbb, - 0x38, 0x4f, 0xf5, 0x3c, 0x8f, 0x18, 0xd4, 0x3c, 0x2b, 0x8c, 0xd3, 0x3d, - 0x23, 0x06, 0x0e, 0x3d, 0x45, 0x3d, 0x52, 0xbc, 0x06, 0x2e, 0x0a, 0xbc, - 0xb8, 0x33, 0x97, 0x3c, 0xaa, 0x6e, 0x06, 0xbe, 0xa0, 0x52, 0x39, 0x3e, - 0x2d, 0xea, 0xc7, 0x3c, 0x35, 0xa4, 0x6b, 0x3e, 0xa9, 0xec, 0xb4, 0x3c, - 0x3f, 0xec, 0xd9, 0x3c, 0xf2, 0x08, 0x89, 0x3d, 0xe9, 0x93, 0xb2, 0xbd, - 0xdb, 0xa2, 0xf2, 0xba, 0x80, 0xb6, 0x1d, 0xbd, 0x50, 0x87, 0xbf, 0xbd, - 0x4a, 0x4f, 0x88, 0x3e, 0x33, 0x4d, 0xa4, 0x3d, 0xe8, 0x46, 0x2f, 0xbe, - 0xd8, 0xde, 0x0f, 0x3e, 0x5b, 0xc0, 0x39, 0xbc, 0xd3, 0x54, 0xf7, 0xbc, - 0xaf, 0x0e, 0x13, 0x3e, 0x89, 0x58, 0x54, 0xbe, 0x3e, 0x50, 0x3e, 0x3c, - 0xa5, 0x50, 0x2f, 0xbe, 0x99, 0xa4, 0xe7, 0xbb, 0xbf, 0x57, 0xf2, 0xbc, - 0x48, 0xe2, 0x23, 0xbe, 0xda, 0x15, 0xfa, 0xbd, 0xac, 0xaa, 0x24, 0xbd, - 0x4d, 0x66, 0xf9, 0xbd, 0x31, 0x3e, 0x3e, 0x3e, 0xaa, 0x51, 0x3d, 0x3d, - 0x3d, 0x44, 0x68, 0xbe, 0x59, 0x7c, 0x93, 0xbe, 0xef, 0x7d, 0x79, 0x3c, - 0xcd, 0xe8, 0xae, 0x3c, 0xde, 0x1d, 0x31, 0x3e, 0xa8, 0xc9, 0x99, 0xbe, - 0xca, 0xba, 0xa3, 0xbd, 0x08, 0x18, 0x41, 0x3a, 0xf4, 0xdf, 0x9e, 0x3d, - 0x58, 0x94, 0xc4, 0xbc, 0x5e, 0x08, 0x50, 0xbe, 0x3f, 0x39, 0xf4, 0xbd, - 0x10, 0x83, 0x06, 0xbc, 0xae, 0x56, 0x77, 0xbe, 0x53, 0x59, 0x96, 0x3c, - 0x62, 0xcd, 0xbd, 0x3c, 0xd1, 0x5b, 0x92, 0xbe, 0x09, 0x3b, 0xa2, 0xbe, - 0x9b, 0x6a, 0x82, 0xbc, 0x53, 0x14, 0x40, 0xbd, 0x19, 0x68, 0xea, 0x3c, - 0x30, 0xe0, 0xac, 0xbe, 0xf4, 0x0d, 0x49, 0xbd, 0x5a, 0x9b, 0x1f, 0xbd, - 0x0b, 0x2f, 0x8f, 0xbd, 0x04, 0x54, 0x05, 0x3e, 0xdf, 0xcd, 0xab, 0xbe, - 0x4f, 0xbd, 0x62, 0xbe, 0x9b, 0x58, 0xc1, 0xbb, 0xf4, 0xba, 0x01, 0xbe, - 0x39, 0x4c, 0x05, 0xbe, 0xd3, 0xf4, 0x80, 0x3d, 0xec, 0x05, 0x39, 0xbf, - 0x7b, 0x41, 0x25, 0xbe, 0x57, 0xd3, 0xd2, 0xbd, 0xb8, 0x7a, 0xb4, 0xbd, - 0x93, 0x9a, 0xf4, 0x3d, 0x99, 0x32, 0xee, 0x3b, 0x26, 0x3c, 0x03, 0xbd, - 0x57, 0x1b, 0xb4, 0xbe, 0x4f, 0xfe, 0x21, 0xbe, 0x6f, 0x1f, 0xb2, 0x3c, - 0x5d, 0xb3, 0x5a, 0xbe, 0x99, 0x4e, 0x91, 0xbe, 0x90, 0x78, 0x83, 0xbc, - 0x3e, 0xa0, 0x39, 0xbe, 0x66, 0x25, 0x5a, 0x3e, 0x29, 0x09, 0x04, 0xbd, - 0xd7, 0x02, 0xbf, 0xbe, 0x83, 0x06, 0x10, 0xbe, 0xdf, 0xa1, 0x05, 0xbe, - 0x56, 0x60, 0x0b, 0xbd, 0xd6, 0xea, 0x8d, 0x3d, 0x3d, 0xcd, 0xac, 0xbe, - 0x05, 0xc3, 0xdf, 0x3c, 0x04, 0x87, 0xa8, 0xbe, 0x1f, 0xe7, 0x28, 0xbd, - 0xd1, 0xe5, 0xa9, 0xbc, 0xff, 0x7a, 0x1e, 0xbe, 0x4a, 0x57, 0xda, 0xbe, - 0x68, 0x43, 0xa2, 0x38, 0xbf, 0x6f, 0xc9, 0xbe, 0x5f, 0xe1, 0x07, 0xbd, - 0xa3, 0xdf, 0x2d, 0xbe, 0xad, 0x95, 0xdd, 0xbe, 0x28, 0xb9, 0x88, 0xbe, - 0x29, 0xb2, 0xca, 0xbd, 0x36, 0xbe, 0x55, 0x3c, 0x2c, 0x51, 0xec, 0x3a, - 0x0c, 0x61, 0x08, 0xbf, 0x2f, 0x85, 0xe1, 0xbd, 0x47, 0x9e, 0xc3, 0xbe, - 0x9b, 0x8f, 0x14, 0xbe, 0x7f, 0xea, 0x9d, 0xbe, 0xfd, 0xdd, 0x56, 0xbe, - 0x31, 0xc6, 0xc9, 0xbe, 0xf0, 0xb6, 0x29, 0xbc, 0xdc, 0xa8, 0x0a, 0xbf, - 0x32, 0x7d, 0xbc, 0x3d, 0x63, 0xe6, 0xb9, 0xbd, 0x0c, 0x53, 0x88, 0xbe, - 0x48, 0x6b, 0xb9, 0xbe, 0xa7, 0x7c, 0xa7, 0xbc, 0x06, 0x6e, 0x1e, 0x3e, - 0x83, 0x3e, 0x82, 0x3d, 0xbd, 0x5d, 0xf0, 0xbe, 0xf6, 0xfe, 0x82, 0xbe, - 0x7d, 0xa9, 0x4f, 0xbe, 0xb9, 0x06, 0x97, 0xbe, 0x40, 0xb4, 0xc4, 0xbe, - 0x70, 0xb4, 0x82, 0xbc, 0xe2, 0x0f, 0x6e, 0xbe, 0xa5, 0xf3, 0x0c, 0x3d, - 0x71, 0x95, 0xa4, 0xbe, 0xee, 0x3e, 0xa8, 0xbe, 0x31, 0xee, 0x35, 0xbd, - 0x58, 0x84, 0xfd, 0xbd, 0x42, 0x69, 0xc5, 0xbd, 0x50, 0x25, 0x8c, 0x3d, - 0x16, 0x21, 0x2f, 0xbd, 0xa0, 0x26, 0x84, 0x3d, 0xfd, 0xd8, 0xb9, 0xbe, - 0xf0, 0x13, 0x95, 0xbe, 0x14, 0x5b, 0x4d, 0xbe, 0x7a, 0x7f, 0x98, 0xbe, - 0x25, 0x1a, 0xd3, 0xbe, 0x94, 0xc8, 0x64, 0x3e, 0x1e, 0x87, 0xe0, 0x3e, - 0xcf, 0x06, 0xe4, 0x3c, 0xa2, 0xba, 0x48, 0x3e, 0xd1, 0xaa, 0x4f, 0xbf, - 0x13, 0xf7, 0x0c, 0xbd, 0x6e, 0x86, 0x17, 0xbd, 0xcb, 0x9e, 0x68, 0x3e, - 0xb7, 0x33, 0x0e, 0x3e, 0xc0, 0xc6, 0x69, 0xbe, 0x0c, 0x65, 0x92, 0x3d, - 0x8b, 0x10, 0x4d, 0x3d, 0x35, 0x32, 0xa4, 0xbd, 0x54, 0xee, 0x1d, 0x3e, - 0x75, 0x9e, 0x49, 0xbe, 0x68, 0x0f, 0xfe, 0xbd, 0x55, 0xbf, 0x1a, 0x3e, - 0x03, 0xb3, 0x91, 0x3e, 0xdb, 0x3e, 0xbf, 0x3c, 0xb2, 0xa4, 0x3c, 0x3d, - 0x8a, 0x24, 0x33, 0xbd, 0xdd, 0x8b, 0xe5, 0x3c, 0x84, 0x5a, 0x2e, 0x3c, - 0x16, 0x89, 0x1a, 0x3e, 0x10, 0xdc, 0x9e, 0x3d, 0x66, 0xf8, 0xf5, 0xbe, - 0x43, 0xb1, 0x54, 0x3e, 0x54, 0x91, 0xe8, 0x3d, 0x77, 0x2b, 0xf8, 0x3b, - 0x0d, 0x63, 0xc4, 0x3c, 0xdf, 0xd4, 0xad, 0xbd, 0xba, 0xea, 0xac, 0xbd, - 0x27, 0xb0, 0x47, 0xbd, 0x42, 0x6d, 0x0c, 0xbe, 0xa9, 0x41, 0x35, 0x3e, - 0xf0, 0xd6, 0xc7, 0xbc, 0xe0, 0x72, 0x8e, 0x3d, 0x82, 0xae, 0xb3, 0x3d, - 0x74, 0xc7, 0x73, 0x3d, 0x0e, 0x68, 0x89, 0x3d, 0x94, 0x54, 0x57, 0x3d, - 0x42, 0xc7, 0x6e, 0xbe, 0x00, 0x64, 0x18, 0x3e, 0x4c, 0xfd, 0x61, 0x3c, - 0x40, 0x0c, 0xe3, 0x3c, 0x89, 0x73, 0x51, 0xbd, 0x93, 0x1d, 0xba, 0xbb, - 0xff, 0xa0, 0xde, 0xbd, 0x05, 0x30, 0x53, 0xbd, 0xcc, 0x54, 0x62, 0xbd, - 0xa1, 0x56, 0x1b, 0xbe, 0x58, 0x30, 0x99, 0x3d, 0xf0, 0x2e, 0x2f, 0x3d, - 0xba, 0xca, 0x4b, 0xbd, 0x2d, 0xad, 0xa3, 0x3d, 0x55, 0xd0, 0x66, 0x3d, - 0x18, 0x06, 0x14, 0xbd, 0x99, 0x79, 0xdd, 0xbd, 0x3e, 0x3f, 0x13, 0x3e, - 0x15, 0x5a, 0x1f, 0x3d, 0x4b, 0x71, 0xf9, 0x3d, 0x2e, 0xe2, 0x10, 0x3d, - 0xd2, 0x3b, 0x61, 0x3e, 0x73, 0x71, 0x5c, 0x3e, 0xbe, 0xf0, 0xb5, 0x3d, - 0x37, 0x15, 0x91, 0xbe, 0x46, 0x10, 0xa2, 0xbf, 0xdf, 0x3d, 0x84, 0xbd, - 0xbf, 0x14, 0x1a, 0x3e, 0x89, 0x2f, 0xb0, 0xbd, 0xab, 0x5c, 0x10, 0xbe, - 0x27, 0x16, 0xab, 0xbd, 0x74, 0x92, 0x54, 0x3c, 0xb2, 0x2c, 0x0c, 0x3f, - 0x53, 0xb6, 0xd2, 0xbe, 0xeb, 0xa4, 0xc5, 0xbc, 0x68, 0xba, 0x64, 0x3e, - 0xae, 0xf3, 0xc3, 0x3c, 0x55, 0x18, 0x00, 0x3e, 0x97, 0x9f, 0x81, 0x3c, - 0x14, 0x50, 0xa0, 0xbd, 0x5e, 0x56, 0x70, 0xbe, 0x11, 0x21, 0x8e, 0x3d, - 0x22, 0x03, 0xa0, 0x3d, 0x35, 0x7a, 0x4c, 0x3e, 0x2d, 0xfe, 0x31, 0x3e, - 0xed, 0x55, 0x7b, 0xbe, 0x29, 0x99, 0x83, 0xbc, 0x3d, 0x90, 0x14, 0xbd, - 0x12, 0x63, 0xbd, 0xbd, 0x17, 0x0c, 0xb0, 0xbd, 0x20, 0x39, 0x13, 0x3d, - 0x6f, 0xbd, 0x04, 0x3d, 0x92, 0x58, 0x0f, 0xbe, 0xdf, 0xa2, 0xcb, 0xbc, - 0x31, 0xf9, 0x03, 0xbd, 0x53, 0x16, 0x13, 0xbf, 0x43, 0xf4, 0xa8, 0xbe, - 0xc0, 0x7c, 0x6d, 0x3d, 0x48, 0xc1, 0x83, 0xbe, 0x54, 0xd0, 0xcd, 0x3c, - 0xf2, 0x6b, 0x79, 0xbb, 0x94, 0x21, 0x3c, 0x3e, 0xe3, 0x7c, 0xdc, 0x3d, - 0x30, 0xd9, 0x8a, 0xbc, 0x99, 0x00, 0x63, 0x3d, 0xef, 0xab, 0x5d, 0xbe, - 0x50, 0x7b, 0x09, 0xbe, 0xb5, 0x1b, 0x4b, 0x3d, 0xd7, 0xba, 0x86, 0xbe, - 0xf3, 0xe5, 0x5f, 0xbc, 0xe5, 0x75, 0x81, 0xbc, 0x0f, 0x70, 0x83, 0x3e, - 0x30, 0xf5, 0x41, 0xbe, 0xf9, 0x35, 0x81, 0x3d, 0xaf, 0x07, 0x82, 0xbe, - 0xdc, 0xd6, 0x93, 0x3d, 0x76, 0xdf, 0xbf, 0x3c, 0x4c, 0x4d, 0x1d, 0x3e, - 0x94, 0x7c, 0x6f, 0x3e, 0xa7, 0x3a, 0xde, 0x3c, 0x39, 0xaf, 0x26, 0xbe, - 0xcc, 0x77, 0x0f, 0x3e, 0x13, 0xdb, 0xf9, 0xbd, 0x09, 0x17, 0xb1, 0x3d, - 0x80, 0x68, 0xc7, 0xbc, 0xec, 0x9d, 0xc7, 0xbd, 0x60, 0xc6, 0xf1, 0x3d, - 0x38, 0x4e, 0xba, 0x3d, 0x31, 0x3a, 0x76, 0x3e, 0xa3, 0x8a, 0x41, 0x3d, - 0xfe, 0x0a, 0x6b, 0xbe, 0x42, 0x2e, 0x69, 0x3e, 0x66, 0x24, 0x01, 0x3e, - 0x42, 0x97, 0x05, 0xbe, 0x76, 0x45, 0xa5, 0xbd, 0x0d, 0xa3, 0x20, 0xbe, - 0x48, 0xbb, 0x8a, 0xbe, 0xec, 0x1c, 0x25, 0xbb, 0x36, 0x80, 0x7c, 0x3d, - 0x5a, 0x73, 0x16, 0x3d, 0xc6, 0x28, 0x02, 0x3d, 0xb8, 0xe4, 0x40, 0xbe, - 0x35, 0x1b, 0x97, 0xbc, 0x4a, 0xb3, 0x7d, 0x3d, 0xd1, 0x9d, 0xcb, 0xba, - 0x6e, 0xf8, 0x60, 0xbd, 0x55, 0x8e, 0x2f, 0xbe, 0x87, 0xc2, 0x6e, 0x3e, - 0x8b, 0xc5, 0xac, 0x3c, 0x13, 0x8d, 0x8b, 0xbb, 0x7b, 0x4c, 0xf0, 0xbc, - 0xd9, 0x89, 0x33, 0xbe, 0x04, 0xd9, 0x5a, 0x3d, 0xad, 0x5a, 0x46, 0x3c, - 0x97, 0xe6, 0x08, 0x3e, 0xd7, 0x70, 0x90, 0x3c, 0x96, 0x8d, 0x37, 0xbe, - 0xe1, 0x25, 0x3f, 0xbd, 0x49, 0x98, 0x95, 0x3d, 0x1c, 0x55, 0x6d, 0x3d, - 0xdd, 0x08, 0x9c, 0x3e, 0x2b, 0x2d, 0xdc, 0x3d, 0x61, 0x8f, 0x8e, 0xbe, - 0x80, 0x66, 0x62, 0x3e, 0xa3, 0x09, 0xdc, 0x3d, 0xd9, 0x94, 0x07, 0x3e, - 0xa0, 0x7a, 0x32, 0xbd, 0x42, 0x14, 0x0a, 0xbe, 0xb3, 0xfa, 0x35, 0x3d, - 0xa2, 0x26, 0x1d, 0x3d, 0x35, 0xb7, 0x87, 0x3c, 0xc7, 0x87, 0xcd, 0x3d, - 0x5a, 0x7f, 0xd4, 0xbd, 0x63, 0x29, 0xfa, 0xbd, 0xfe, 0x64, 0xb1, 0x3d, - 0x2d, 0xfa, 0x31, 0x3e, 0xe3, 0x21, 0x9a, 0x3c, 0xbf, 0xd3, 0x5b, 0xbc, - 0xc5, 0xbe, 0x97, 0xbe, 0x9a, 0xe8, 0x72, 0x3e, 0xe4, 0x5a, 0xb8, 0xba, - 0x7f, 0x3c, 0xd3, 0xbb, 0xa2, 0x1e, 0x8a, 0x3c, 0xf1, 0xfd, 0x44, 0xbe, - 0x75, 0x23, 0x0b, 0x3e, 0x3b, 0xda, 0x06, 0xbd, 0x92, 0x2e, 0x84, 0x3d, - 0x2b, 0xa7, 0xd7, 0x3c, 0xb7, 0x45, 0x35, 0xbd, 0x47, 0x60, 0xd0, 0xbc, - 0xee, 0x6f, 0xc2, 0xbc, 0xd1, 0x2b, 0x36, 0x3e, 0x5e, 0x34, 0xbc, 0x3d, - 0x97, 0xa5, 0x65, 0x3d, 0xc9, 0x9d, 0x88, 0xbe, 0xad, 0x3a, 0x97, 0x3e, - 0x59, 0x30, 0x04, 0x3d, 0x89, 0x47, 0x9a, 0x3d, 0x56, 0xde, 0x5c, 0xbd, - 0x50, 0x0d, 0xdd, 0xbd, 0xf9, 0x43, 0xf1, 0xbd, 0xfb, 0xb6, 0xa7, 0xbd, - 0xcd, 0x08, 0xbd, 0x3d, 0x93, 0x12, 0x06, 0xbd, 0x1b, 0x59, 0xc3, 0xbd, - 0x8d, 0x84, 0xb1, 0xbd, 0xf2, 0x75, 0x3b, 0xbc, 0x9a, 0xe4, 0xeb, 0x3d, - 0x4d, 0xe4, 0x2f, 0x3e, 0xee, 0x7c, 0xc2, 0x3d, 0x15, 0xb6, 0xe1, 0xbe, - 0x04, 0x9a, 0x84, 0x3e, 0xc7, 0xd5, 0xd3, 0x3d, 0x11, 0xef, 0xca, 0x3d, - 0x03, 0xa5, 0x36, 0x3c, 0xa0, 0x78, 0x9c, 0xbd, 0x06, 0x60, 0xaa, 0xbc, - 0x72, 0xee, 0x5d, 0xbe, 0x50, 0xd1, 0x32, 0x3d, 0x58, 0x35, 0x23, 0xbc, - 0x20, 0xc3, 0xba, 0xbd, 0x23, 0x78, 0xed, 0xbd, 0xa1, 0xad, 0xc3, 0x3c, - 0xbf, 0xc2, 0x09, 0x3e, 0x55, 0x5f, 0x4e, 0x3e, 0xbe, 0x15, 0x1f, 0x3c, - 0x31, 0x20, 0xbc, 0xbe, 0xe1, 0x69, 0xce, 0x3e, 0xc1, 0x0b, 0x9d, 0x3d, - 0x61, 0xa0, 0xea, 0x3d, 0x37, 0x8f, 0x23, 0xbd, 0xd6, 0xfd, 0xec, 0xbd, - 0xd5, 0x39, 0xb9, 0xbd, 0x67, 0x21, 0x55, 0xbd, 0x02, 0x3b, 0x7d, 0x3c, - 0xbf, 0x75, 0x23, 0xbd, 0x94, 0xa6, 0xde, 0xbd, 0x05, 0x4d, 0x27, 0xbe, - 0x3b, 0xf9, 0x92, 0x3d, 0x70, 0x6e, 0x4f, 0x3e, 0xd3, 0x87, 0x62, 0x3e, - 0x4b, 0x0c, 0x15, 0x3d, 0x93, 0x56, 0x8a, 0xbe, 0xea, 0xa6, 0x8a, 0x3e, - 0xde, 0x06, 0xaf, 0xbd, 0xdf, 0x0b, 0xb7, 0x3d, 0x4e, 0x4a, 0x0d, 0xbd, - 0xd1, 0x4f, 0xbe, 0xbc, 0xb6, 0x01, 0x0e, 0xbe, 0x2d, 0x74, 0xf6, 0x3d, - 0xb0, 0xb8, 0xd9, 0x3d, 0xcf, 0xc4, 0x55, 0xbc, 0x1d, 0x55, 0xd6, 0xbd, - 0xfa, 0x1d, 0x12, 0xbe, 0xa9, 0x68, 0xde, 0x3d, 0xf6, 0x81, 0xa0, 0x3e, - 0x24, 0x3a, 0x8c, 0x3d, 0xbd, 0xa6, 0x84, 0x3d, 0x19, 0xaa, 0xe1, 0xbe, - 0x38, 0xde, 0x9e, 0x3c, 0x18, 0x62, 0xab, 0xbd, 0x8d, 0x5e, 0xcd, 0x3d, - 0x63, 0x0f, 0xc6, 0x3d, 0xcc, 0x70, 0x97, 0x3c, 0xbf, 0xc8, 0xf2, 0xbd, - 0x23, 0x59, 0x8d, 0xbd, 0x85, 0x7c, 0x25, 0x3e, 0xf8, 0x02, 0x30, 0xbd, - 0x45, 0xc0, 0x0f, 0xbe, 0x0e, 0xff, 0xfc, 0xbd, 0x6a, 0x25, 0x90, 0x3d, - 0xef, 0x19, 0x74, 0x3e, 0x89, 0x24, 0xc8, 0x3d, 0xa6, 0x66, 0x56, 0x3d, - 0xac, 0x62, 0xc8, 0xbd, 0x76, 0xfe, 0xff, 0xbd, 0xb4, 0x51, 0xd6, 0xbd, - 0xbd, 0xad, 0xf7, 0x3c, 0xcc, 0x95, 0xa1, 0x3d, 0xc3, 0xf8, 0xc9, 0x3d, - 0x59, 0x47, 0x25, 0xbf, 0xc4, 0xf0, 0xa5, 0xbd, 0x4e, 0x30, 0xaf, 0x3d, - 0x7d, 0x81, 0x3c, 0xbe, 0x9e, 0x67, 0xb5, 0xbc, 0x4e, 0xc0, 0x6c, 0xbe, - 0x9f, 0xcf, 0xe5, 0xbd, 0x4c, 0x11, 0x37, 0xbe, 0xd5, 0x39, 0x16, 0xbe, - 0xc5, 0xd9, 0x0e, 0x3e, 0x6c, 0x92, 0x03, 0x3c, 0xab, 0x34, 0xa3, 0xbd, - 0x50, 0xe0, 0x7d, 0xbe, 0x3c, 0x60, 0xf6, 0x3d, 0xc9, 0x51, 0xa8, 0xbe, - 0x30, 0x4f, 0x5a, 0x3e, 0x37, 0x97, 0x76, 0xbf, 0x7d, 0x59, 0x18, 0x3c, - 0xc1, 0xef, 0x3b, 0xbc, 0xd4, 0xe6, 0x8e, 0xbe, 0x89, 0xa1, 0x47, 0xbe, - 0x69, 0x10, 0xc7, 0xbe, 0x44, 0x38, 0xdd, 0xbe, 0x30, 0x7d, 0x5f, 0x3e, - 0xa4, 0xe6, 0x04, 0x3f, 0x40, 0xc8, 0x8b, 0x3d, 0xe2, 0x2b, 0xbb, 0x3e, - 0x0f, 0x74, 0xbd, 0x3d, 0x8b, 0x41, 0x8a, 0xbc, 0xc5, 0x27, 0x96, 0x3e, - 0x5d, 0x6c, 0x19, 0x3e, 0xb4, 0xb1, 0xea, 0x3d, 0x6b, 0x50, 0xe2, 0xbd, - 0xe8, 0xa0, 0xe1, 0x3e, 0x71, 0x0c, 0x20, 0x3d, 0xb3, 0x71, 0x56, 0x3e, - 0x59, 0x48, 0x24, 0x3f, 0xb2, 0xfa, 0x6e, 0x3d, 0xa7, 0x11, 0xe2, 0x3d, - 0x2b, 0xbc, 0x71, 0x3e, 0x2a, 0x4f, 0x39, 0x3f, 0xd0, 0xbf, 0x11, 0x3d, - 0xfb, 0x9b, 0x39, 0x3d, 0x94, 0x08, 0xab, 0x3e, 0xc6, 0x55, 0x23, 0x3e, - 0x10, 0xb4, 0x0c, 0xbe, 0x0b, 0x04, 0xd3, 0x3c, 0x6c, 0xb4, 0xc5, 0x3d, - 0x75, 0xfc, 0x80, 0xbe, 0xeb, 0x7c, 0x54, 0x3e, 0x52, 0x0d, 0x24, 0x3f, - 0x81, 0xd0, 0xda, 0x3d, 0x5c, 0xd6, 0x1f, 0x3e, 0xb8, 0x60, 0xf6, 0x3d, - 0x56, 0x7a, 0x01, 0x3c, 0xaa, 0x9c, 0x00, 0xbe, 0x36, 0x21, 0x38, 0xbe, - 0xe2, 0x0c, 0x12, 0xbe, 0xe4, 0xcf, 0x6a, 0x3e, 0x1f, 0x3f, 0x95, 0x3d, - 0x36, 0xd1, 0x54, 0x3e, 0xa8, 0xea, 0x2a, 0xbe, 0x21, 0x4f, 0x00, 0xbd, - 0xeb, 0xaf, 0x10, 0x3d, 0x34, 0xba, 0x53, 0xbe, 0xb1, 0xc6, 0x93, 0xbd, - 0x41, 0x20, 0x08, 0xbd, 0xd9, 0xe9, 0x81, 0x3d, 0xbd, 0x24, 0x93, 0xbe, - 0x4e, 0x30, 0xc7, 0x3d, 0xc5, 0x27, 0x44, 0xbd, 0xc1, 0xdc, 0x25, 0xbd, - 0xb4, 0x22, 0xb6, 0xbe, 0xb7, 0x4a, 0x43, 0xbd, 0x05, 0x6b, 0x46, 0x3d, - 0xb9, 0xa4, 0xcd, 0xbd, 0x6c, 0xdc, 0xa8, 0x3e, 0x61, 0x8e, 0xd6, 0xbd, - 0x9e, 0x80, 0x00, 0xbd, 0xa9, 0x33, 0x9f, 0xbd, 0x27, 0x49, 0x08, 0xbd, - 0x8e, 0xb7, 0xa7, 0xbc, 0xda, 0x62, 0xec, 0x3c, 0x5f, 0x7e, 0x06, 0x3d, - 0xee, 0xeb, 0xe8, 0xbd, 0x53, 0xbd, 0xef, 0x3d, 0x1e, 0x22, 0x15, 0xbe, - 0x23, 0x6f, 0x5f, 0xbe, 0xb1, 0x3c, 0x84, 0xbe, 0xec, 0x84, 0xbc, 0xbc, - 0x0a, 0xce, 0x08, 0x3c, 0x36, 0xff, 0x7b, 0xbd, 0xb1, 0xd0, 0x52, 0x3e, - 0x5a, 0xfe, 0x35, 0x3d, 0x1c, 0xd2, 0xd4, 0xbd, 0xc7, 0x53, 0x84, 0x3d, - 0x00, 0xb9, 0xa8, 0x3d, 0x3e, 0xd7, 0x96, 0x3d, 0xb4, 0x7f, 0x72, 0xbd, - 0x14, 0x36, 0x1a, 0x3d, 0x68, 0xa3, 0x95, 0xbe, 0xf9, 0xa2, 0xa9, 0x3d, - 0x08, 0xc6, 0xf4, 0xbd, 0x9a, 0xbd, 0x43, 0xbd, 0x6b, 0x8c, 0xe7, 0xbe, - 0x37, 0x84, 0x22, 0xbd, 0x06, 0x91, 0x48, 0xbd, 0x1a, 0xee, 0x75, 0x3e, - 0x14, 0xe4, 0xa1, 0x3d, 0xba, 0x02, 0x5c, 0xbd, 0x56, 0x29, 0xbe, 0xbc, - 0x45, 0x6b, 0x89, 0x3d, 0x37, 0xe9, 0x42, 0x3d, 0x3a, 0xdc, 0x2c, 0x3d, - 0x45, 0x30, 0x4a, 0xb9, 0xaa, 0xf3, 0x11, 0x3d, 0xe1, 0xad, 0x4e, 0xbe, - 0xc7, 0x41, 0xde, 0x3d, 0x5b, 0x07, 0x9c, 0xbe, 0x1c, 0x04, 0xd1, 0xbd, - 0xe6, 0x2d, 0xc6, 0xbc, 0x01, 0x35, 0x67, 0xbd, 0x42, 0xc5, 0xf5, 0x3c, - 0xbf, 0x95, 0x85, 0x3d, 0x7e, 0xf7, 0x00, 0x3e, 0xbe, 0x33, 0x89, 0xbd, - 0xeb, 0x9f, 0x41, 0xbd, 0x31, 0x36, 0x22, 0xbc, 0xa4, 0x37, 0x69, 0xbc, - 0xaa, 0xfa, 0x15, 0xbe, 0x1a, 0x91, 0x59, 0x3d, 0xf4, 0xf0, 0x59, 0xbd, - 0xd7, 0xda, 0x49, 0xbe, 0x8a, 0x21, 0xd5, 0x3d, 0x7e, 0x56, 0x7d, 0xbd, - 0x30, 0x2d, 0x01, 0xbe, 0x71, 0x1a, 0x2e, 0xbe, 0x53, 0xd8, 0xb7, 0xbd, - 0x64, 0x41, 0xcf, 0x3d, 0xa7, 0x59, 0x00, 0xbe, 0x2c, 0xb1, 0x09, 0x3e, - 0xdb, 0xd1, 0x02, 0xbe, 0x8a, 0xd5, 0xbb, 0xbd, 0xb0, 0xde, 0xb0, 0x3d, - 0x90, 0x25, 0x22, 0x3c, 0xbd, 0xc3, 0x84, 0xbd, 0x69, 0x9b, 0xbe, 0x3d, - 0x04, 0x9b, 0x92, 0xbb, 0x14, 0xad, 0x0f, 0xbe, 0x7f, 0x14, 0xa2, 0x3c, - 0x6f, 0xc6, 0xbf, 0xbd, 0xa9, 0xfa, 0xa2, 0xbe, 0x93, 0xae, 0x09, 0xbe, - 0x91, 0x2d, 0x0f, 0xbe, 0x9c, 0x2b, 0xf1, 0x3d, 0xc1, 0x6a, 0x06, 0xbe, - 0x05, 0xf1, 0x48, 0x3d, 0x89, 0x60, 0xe0, 0xbd, 0xe1, 0xf7, 0x0a, 0xbe, - 0x86, 0xf4, 0x42, 0x3d, 0x55, 0xb4, 0xa7, 0x3d, 0xbc, 0xa3, 0x8f, 0x3d, - 0xec, 0x59, 0xae, 0x3d, 0x6a, 0x78, 0x95, 0x3d, 0x57, 0x04, 0x78, 0xbe, - 0x85, 0x67, 0x57, 0x3d, 0x41, 0x8e, 0x9f, 0xbd, 0xa8, 0x5b, 0x38, 0xbe, - 0xb5, 0x5b, 0x99, 0xbe, 0x68, 0xda, 0x7c, 0xbe, 0xa3, 0x89, 0x26, 0x3e, - 0x6f, 0x72, 0x41, 0xbe, 0x4c, 0xee, 0x1b, 0xbb, 0x41, 0xbf, 0x65, 0xbe, - 0xe0, 0x10, 0xf5, 0xbd, 0x92, 0xa3, 0xd1, 0xbc, 0xf9, 0x87, 0xfe, 0x3d, - 0x66, 0xc3, 0x6b, 0xbd, 0xe5, 0x15, 0xa7, 0x3d, 0xc2, 0x28, 0x0d, 0x3d, - 0x5f, 0x68, 0x88, 0xbe, 0x55, 0x4d, 0x55, 0x3d, 0x43, 0x4a, 0xbb, 0xbd, - 0x7c, 0x92, 0x81, 0xbe, 0x0d, 0x1e, 0x49, 0xbe, 0x36, 0x38, 0x49, 0xbe, - 0x32, 0xe8, 0x8c, 0x3d, 0x11, 0x50, 0x36, 0xbe, 0xf6, 0x62, 0x15, 0xbd, - 0x1d, 0x0a, 0x81, 0xbe, 0x67, 0x2b, 0xe0, 0xbc, 0xe4, 0x14, 0x24, 0xbc, - 0xdf, 0x62, 0x1e, 0x3e, 0xc1, 0xc8, 0xda, 0xbd, 0x5e, 0x17, 0x0c, 0x3b, - 0x42, 0x5c, 0xbe, 0xbc, 0x32, 0x72, 0x2d, 0xbe, 0xb0, 0x84, 0x21, 0xbd, - 0xec, 0xb0, 0x5e, 0xbc, 0x5e, 0x31, 0x4e, 0xbe, 0xad, 0x07, 0x02, 0x3c, - 0x30, 0x64, 0x15, 0xbe, 0xb7, 0x44, 0x22, 0x3d, 0x6f, 0x13, 0x8a, 0x3d, - 0x26, 0x86, 0xe0, 0x3d, 0x43, 0x72, 0xa7, 0xbd, 0x07, 0x4c, 0x30, 0xbd, - 0x93, 0x3a, 0xc5, 0x3d, 0xc4, 0x9d, 0xa4, 0x3c, 0xde, 0x10, 0x80, 0xbe, - 0x64, 0x31, 0x12, 0xbc, 0x81, 0x40, 0x9d, 0xbd, 0x13, 0x7d, 0x4e, 0xbe, - 0x2a, 0x25, 0xf1, 0x3c, 0xe5, 0x22, 0x02, 0xbd, 0xff, 0x20, 0x44, 0xbe, - 0x0e, 0x32, 0xd5, 0xbc, 0xb5, 0x60, 0x63, 0xbd, 0xcf, 0xdd, 0xcf, 0xbc, - 0x13, 0x18, 0x81, 0xbe, 0xce, 0x00, 0x0a, 0x3e, 0x0d, 0x74, 0x27, 0x3d, - 0x7b, 0x28, 0xc7, 0xbd, 0xa2, 0xcc, 0x54, 0x3d, 0x6f, 0x0f, 0xad, 0xbe, - 0x63, 0x70, 0xf0, 0xbd, 0xaa, 0x4a, 0xe9, 0x3d, 0x5e, 0x29, 0x29, 0x3d, - 0x29, 0xd4, 0x06, 0xbe, 0xb4, 0x45, 0xc5, 0x3d, 0xe8, 0x20, 0x81, 0x3e, - 0x82, 0x91, 0xbb, 0x3d, 0x1d, 0x15, 0xa8, 0x3e, 0xd2, 0x6a, 0xa9, 0xbe, - 0x66, 0x78, 0x20, 0x3c, 0x03, 0xff, 0x78, 0xbd, 0xba, 0xdf, 0xe4, 0x3d, - 0x3c, 0x40, 0x4e, 0x3e, 0x46, 0x0d, 0x80, 0x3e, 0x06, 0x22, 0xdf, 0x3d, - 0x0d, 0x2e, 0x95, 0x3d, 0xf6, 0x29, 0x53, 0x3d, 0x8c, 0xa4, 0xa5, 0x3c, - 0x88, 0xaa, 0xb0, 0x3d, 0xbc, 0x7f, 0x97, 0xbc, 0xe3, 0xd6, 0x09, 0x3f, - 0xd9, 0x0e, 0x1f, 0x3e, 0x0a, 0x9d, 0xab, 0x3d, 0x2a, 0x87, 0xae, 0x3d, - 0xba, 0x72, 0xc1, 0xbd, 0x4f, 0xce, 0x07, 0xbe, 0x46, 0x31, 0x3a, 0xbe, - 0x81, 0x34, 0x14, 0x3d, 0x4a, 0x11, 0xa6, 0xbd, 0x56, 0x3c, 0x32, 0x3d, - 0xec, 0x1a, 0x55, 0xbe, 0x74, 0x5e, 0xbc, 0x3d, 0x37, 0xd1, 0x12, 0xbe, - 0xdd, 0x87, 0x10, 0xbd, 0xcb, 0xae, 0x2a, 0xbe, 0x28, 0xcd, 0x0e, 0xbd, - 0xb4, 0xce, 0xe7, 0xbc, 0x99, 0xde, 0x3c, 0xbe, 0x59, 0xf0, 0xf3, 0xbd, - 0xac, 0xed, 0x33, 0xbe, 0x48, 0x07, 0x97, 0xbc, 0xef, 0x69, 0x67, 0xbe, - 0x4d, 0x53, 0x2f, 0x3b, 0x4b, 0xb2, 0xa0, 0xbc, 0xd5, 0xc5, 0x93, 0x3d, - 0x03, 0x09, 0x29, 0x3d, 0x5b, 0x1c, 0x97, 0xbd, 0xc0, 0xee, 0x8a, 0xb8, - 0x5d, 0xe4, 0xcd, 0xbd, 0xbb, 0x6c, 0x51, 0x3c, 0x73, 0xce, 0x2c, 0xbd, - 0x2a, 0xd4, 0x17, 0xbe, 0xcc, 0x22, 0x07, 0xbd, 0x6a, 0x7c, 0x45, 0xbe, - 0xe7, 0xdc, 0xc5, 0xbd, 0xf6, 0x6f, 0xa4, 0x3d, 0xc6, 0xa5, 0x2e, 0xbe, - 0x5c, 0x03, 0x6e, 0xbe, 0x26, 0x5a, 0xf2, 0xbd, 0xe6, 0x73, 0x16, 0xbe, - 0x52, 0x75, 0xa9, 0x3a, 0xfe, 0xe5, 0x30, 0xbe, 0xb0, 0xe7, 0x02, 0x3d, - 0xc8, 0x3f, 0x90, 0xbd, 0xc0, 0xec, 0xaa, 0xbd, 0xfd, 0x46, 0x23, 0xbe, - 0xf6, 0x9d, 0xdf, 0xba, 0x01, 0x87, 0xa3, 0x3d, 0x5a, 0xa6, 0x45, 0xbc, - 0x3c, 0xd2, 0x80, 0x3d, 0x4a, 0x03, 0x3a, 0x3d, 0x00, 0xdb, 0xe9, 0xbd, - 0xf6, 0xe4, 0x6f, 0xbd, 0xdd, 0x5f, 0x82, 0x3d, 0xeb, 0x5c, 0x5a, 0x3d, - 0x98, 0x92, 0xe1, 0xbc, 0xe4, 0xfd, 0xe7, 0xbd, 0x4b, 0x54, 0xcb, 0xbd, - 0x30, 0x3b, 0x59, 0xbb, 0xe7, 0x1e, 0x9e, 0x3c, 0x55, 0x1f, 0x41, 0xbd, - 0x0b, 0x81, 0x31, 0xbd, 0x56, 0x18, 0x08, 0xbe, 0xb5, 0x8c, 0xcb, 0xbd, - 0x83, 0x5d, 0x8c, 0xbd, 0xd3, 0x43, 0x07, 0xbe, 0x3b, 0xb8, 0xe0, 0xbc, - 0x40, 0x1d, 0xa4, 0xbd, 0xc4, 0x96, 0xeb, 0xbd, 0xf6, 0x0b, 0x3a, 0x3d, - 0xad, 0xe7, 0x34, 0x3d, 0x8d, 0x48, 0x77, 0xbb, 0x6c, 0xe9, 0x85, 0x3d, - 0x6a, 0xe2, 0x11, 0x3d, 0xf8, 0x44, 0x29, 0x3d, 0xc5, 0xa3, 0xab, 0x3d, - 0x6e, 0xfe, 0x99, 0x3d, 0x31, 0xd2, 0x2f, 0xbe, 0x48, 0x56, 0xff, 0x3c, - 0x0d, 0x6c, 0x2b, 0xbe, 0xd3, 0x99, 0x93, 0xbd, 0x9e, 0xfc, 0xe4, 0xbd, - 0xb3, 0x52, 0x9f, 0x3d, 0xd2, 0x89, 0xb0, 0x3d, 0x3d, 0xfd, 0x53, 0x3d, - 0x5d, 0x0d, 0x52, 0xbe, 0x8e, 0xbd, 0x18, 0xbe, 0x2e, 0x74, 0x5e, 0xbb, - 0xcf, 0xbb, 0x81, 0x3c, 0x30, 0x87, 0x1b, 0xbe, 0x2c, 0xe5, 0x93, 0xbd, - 0xb3, 0x52, 0xb8, 0x3c, 0x39, 0x30, 0xee, 0xbd, 0xbf, 0xcb, 0xa0, 0xbc, - 0x08, 0xa0, 0x9a, 0x3c, 0xae, 0xd0, 0x2e, 0xbd, 0x48, 0xef, 0x62, 0xbd, - 0x79, 0xbe, 0xcb, 0xbd, 0xf1, 0xe5, 0x41, 0xbd, 0xfa, 0xd2, 0x1b, 0xbe, - 0xa8, 0x11, 0xaf, 0xbd, 0x51, 0xec, 0x85, 0xbc, 0x7b, 0x0b, 0x34, 0xbe, - 0x5a, 0x00, 0xaf, 0x3c, 0xf1, 0x1a, 0x4d, 0xbe, 0xa0, 0xf3, 0x8d, 0xbd, - 0x40, 0xb5, 0x60, 0xbe, 0xd8, 0xcd, 0x0f, 0xbe, 0xf5, 0x95, 0x43, 0xbe, - 0xff, 0x65, 0x3e, 0xbc, 0xf4, 0x00, 0x01, 0x3d, 0x78, 0xce, 0x1f, 0xbe, - 0x7b, 0x17, 0x00, 0x3c, 0xb7, 0x39, 0x2a, 0x3c, 0x11, 0x1b, 0xe5, 0xbd, - 0x6c, 0x62, 0xe7, 0xbc, 0xd9, 0x77, 0x18, 0xbe, 0x80, 0x9b, 0x39, 0xbc, - 0xf0, 0xea, 0x6a, 0xbd, 0xe3, 0xc0, 0xca, 0xbb, 0xb3, 0x49, 0xbf, 0xbd, - 0xc3, 0x76, 0x19, 0xbd, 0x69, 0x6b, 0x83, 0x3c, 0x1b, 0x3a, 0xc7, 0xbd, - 0x2d, 0x1e, 0x33, 0xbd, 0xcf, 0x7f, 0x75, 0x3d, 0x9b, 0x3e, 0xc6, 0xbd, - 0x81, 0xf1, 0xe6, 0xbd, 0xb9, 0x54, 0x9c, 0x3c, 0x3a, 0xe6, 0x16, 0xbe, - 0x76, 0x7f, 0xa7, 0xbc, 0x3e, 0x32, 0x27, 0xbe, 0x51, 0x02, 0xcc, 0xbd, - 0x80, 0x5b, 0x81, 0xbd, 0x9a, 0xef, 0x1e, 0x3d, 0x76, 0x21, 0xdc, 0xbd, - 0xdf, 0x2e, 0x03, 0xbd, 0x26, 0x4d, 0xe5, 0x3b, 0xb8, 0xa1, 0xbf, 0xbd, - 0x11, 0x21, 0xcb, 0x3c, 0x00, 0xbe, 0xbc, 0x3d, 0x69, 0x50, 0x65, 0xbd, - 0x87, 0x6c, 0x69, 0xbe, 0xa5, 0xdd, 0xb5, 0x3c, 0x79, 0x39, 0x13, 0x3d, - 0x25, 0xea, 0x11, 0xbe, 0x51, 0xa6, 0x45, 0xbe, 0xa3, 0x84, 0x07, 0xbe, - 0xe4, 0x77, 0x7e, 0xbd, 0x68, 0x04, 0xf7, 0xbc, 0x1c, 0x98, 0x2c, 0xbe, - 0x53, 0xfb, 0xae, 0xbd, 0x58, 0x42, 0x40, 0x3d, 0x70, 0x64, 0x8c, 0xbd, - 0x71, 0xd2, 0x22, 0xbe, 0x77, 0x7b, 0xd9, 0xbc, 0x62, 0x5a, 0x0d, 0x3d, - 0x3a, 0x08, 0x15, 0xbe, 0xee, 0x24, 0x3d, 0xbe, 0x0f, 0x4c, 0x2c, 0xbe, - 0xd4, 0x30, 0x01, 0xbe, 0x86, 0xb6, 0x09, 0xbe, 0x28, 0xcd, 0x8d, 0x3d, - 0xe7, 0x9a, 0x04, 0x3c, 0x71, 0xda, 0xe8, 0xbc, 0x64, 0x99, 0x8a, 0xbd, - 0x7f, 0x9d, 0xd7, 0xbd, 0xc6, 0x45, 0x84, 0xbd, 0x5e, 0xb6, 0xa2, 0xbd, - 0x2c, 0x3f, 0x51, 0xbe, 0x41, 0x3f, 0xf1, 0xbd, 0x90, 0x2e, 0xd8, 0x3c, - 0xfe, 0x52, 0x40, 0xbd, 0x26, 0x1b, 0x1d, 0xbd, 0x77, 0xdd, 0x57, 0xbe, - 0xeb, 0xfe, 0x06, 0x3d, 0x32, 0x96, 0x39, 0x3d, 0xf4, 0xb2, 0x26, 0xbe, - 0x1a, 0xc7, 0x10, 0xbe, 0x5c, 0xb8, 0xc0, 0xbc, 0x2a, 0x33, 0x3b, 0xbe, - 0xdd, 0x2a, 0xa8, 0xbd, 0xb7, 0xa2, 0x72, 0xbd, 0x3d, 0xdb, 0x11, 0x3d, - 0x7d, 0x46, 0x00, 0xbe, 0x08, 0xf0, 0x22, 0x3d, 0x69, 0xc7, 0x14, 0xbe, - 0xea, 0x65, 0xcb, 0xbc, 0xca, 0x9f, 0x9e, 0x3c, 0x28, 0x31, 0x09, 0xbd, - 0x19, 0x10, 0x11, 0xbd, 0xfb, 0xa9, 0x03, 0xbe, 0x30, 0x97, 0x2d, 0xbe, - 0xd9, 0x4c, 0x15, 0xbe, 0x29, 0x35, 0xf9, 0xbd, 0xdc, 0x4a, 0x3c, 0x3c, - 0x2f, 0x44, 0x42, 0xbe, 0x2f, 0x06, 0x67, 0xbd, 0xaa, 0x84, 0x26, 0xbe, - 0x1d, 0xd8, 0xd7, 0xbd, 0x84, 0x1a, 0x88, 0xbb, 0xb9, 0x0c, 0x3e, 0xbe, - 0x19, 0x38, 0x0a, 0xbe, 0xfe, 0x4b, 0xe1, 0xbd, 0x86, 0x7c, 0x47, 0xbe, - 0x33, 0x85, 0x7f, 0xbe, 0x54, 0x42, 0x67, 0xbc, 0xbd, 0xd5, 0x17, 0xbe, - 0x70, 0x39, 0xd2, 0xbc, 0x3d, 0x74, 0xbd, 0xbc, 0x38, 0x96, 0x0a, 0xbd, - 0xcc, 0x73, 0xf0, 0xbd, 0xd7, 0xd8, 0xea, 0x3c, 0xc4, 0xa0, 0x3c, 0xbe, - 0x95, 0x21, 0x2c, 0x3d, 0x81, 0xc3, 0x76, 0xbd, 0x0e, 0x36, 0xc5, 0xbd, - 0xff, 0xc4, 0xa0, 0x3d, 0x26, 0xaf, 0x06, 0xbe, 0x46, 0x2a, 0x32, 0xbe, - 0xc9, 0xd0, 0xda, 0xbc, 0xf2, 0x30, 0x83, 0xbe, 0x33, 0x66, 0x6d, 0xbe, - 0xda, 0x14, 0x90, 0xbd, 0xd5, 0xa5, 0x16, 0xbe, 0x5f, 0x41, 0x27, 0xbe, - 0x8f, 0x79, 0xbe, 0x3d, 0x9a, 0xa9, 0x80, 0xbb, 0x07, 0x53, 0x33, 0xbe, - 0x1e, 0xb8, 0x94, 0xbd, 0x6f, 0xe2, 0x78, 0x3d, 0xea, 0x83, 0x6d, 0x3d, - 0x07, 0x2d, 0x3a, 0xbe, 0x5c, 0x8f, 0xa4, 0xbc, 0xf2, 0xb9, 0xc8, 0xbd, - 0x49, 0x4a, 0x4c, 0x3c, 0x90, 0x69, 0xf4, 0x3c, 0xa4, 0xaa, 0x3e, 0xbe, - 0x82, 0x60, 0x27, 0xbe, 0xfa, 0x9c, 0x0f, 0x3d, 0xf9, 0x8b, 0x14, 0xbe, - 0xcf, 0x1c, 0xf6, 0xbd, 0x4f, 0x8a, 0x1a, 0x3d, 0x4d, 0xba, 0x4a, 0x3c, - 0x7b, 0x1b, 0x98, 0xbd, 0xfd, 0x3d, 0x64, 0x3d, 0x3e, 0x05, 0xc5, 0x3d, - 0x0d, 0x10, 0x43, 0xbe, 0x67, 0x2a, 0x06, 0xbe, 0x35, 0xd0, 0xbb, 0xba, - 0x3d, 0x6f, 0x64, 0xbe, 0xd1, 0x5c, 0xf4, 0x3d, 0x4c, 0x13, 0xde, 0xbd, - 0xa3, 0x5f, 0x93, 0x3d, 0xa3, 0x63, 0xaf, 0xbb, 0x8e, 0x60, 0x2c, 0xbe, - 0x65, 0xc6, 0x41, 0xbe, 0x4d, 0x16, 0x35, 0xbe, 0x94, 0xc7, 0x05, 0xbd, - 0x51, 0xfb, 0x28, 0xbe, 0x46, 0x77, 0xa9, 0x3d, 0x5c, 0xab, 0x97, 0x3b, - 0x69, 0x4f, 0x1d, 0xbd, 0x1a, 0x88, 0xa4, 0x3d, 0x33, 0x54, 0x55, 0x3d, - 0xb4, 0xee, 0x34, 0xbe, 0x5e, 0x36, 0x21, 0xbd, 0xc0, 0x7c, 0x9f, 0x3d, - 0xee, 0x60, 0x89, 0xbd, 0x15, 0x6c, 0xda, 0xbd, 0x49, 0x7c, 0xdd, 0xbd, - 0x09, 0x1d, 0x96, 0x3c, 0xaf, 0x78, 0x75, 0x3d, 0xb0, 0x39, 0xf1, 0xbc, - 0xed, 0x4c, 0xdf, 0x3d, 0x11, 0x86, 0x1e, 0x3d, 0x6c, 0xe4, 0x9c, 0x3c, - 0xe5, 0xb4, 0x59, 0x3d, 0xdb, 0x17, 0x46, 0x3d, 0x55, 0x72, 0x83, 0xbd, - 0xb2, 0xd9, 0x0f, 0xbe, 0x14, 0x59, 0x8f, 0x3d, 0x2f, 0xb2, 0x82, 0x3c, - 0x8e, 0x39, 0x1a, 0xbe, 0xfb, 0xf2, 0xb5, 0x3d, 0x6d, 0xd5, 0x18, 0xbe, - 0xe9, 0xc9, 0x6a, 0x3c, 0x5b, 0xc6, 0xfd, 0xbd, 0x89, 0x76, 0x47, 0x3d, - 0xf6, 0xb2, 0xec, 0x3d, 0xec, 0x11, 0x9d, 0xbb, 0x70, 0x17, 0x32, 0x3d, - 0x74, 0x8b, 0x33, 0xbe, 0x3d, 0xdd, 0x39, 0xbe, 0xe4, 0x72, 0xdd, 0xbd, - 0x25, 0x62, 0xe1, 0xbd, 0x13, 0x8e, 0x8e, 0xbd, 0x0e, 0xac, 0x34, 0xbe, - 0xc0, 0x99, 0xa1, 0xbd, 0x6f, 0x10, 0x01, 0x3e, 0x0e, 0xfa, 0x8a, 0xbd, - 0xa7, 0x54, 0x7b, 0xbd, 0x02, 0xc7, 0x96, 0x3d, 0x67, 0x2d, 0xc2, 0x3d, - 0x8c, 0x1b, 0x17, 0xbe, 0x11, 0xb8, 0xbb, 0x3d, 0xb6, 0xda, 0x0f, 0xbe, - 0x79, 0xf6, 0xd5, 0xbd, 0x5a, 0x07, 0x05, 0x3b, 0xfa, 0x57, 0x22, 0x3d, - 0x6c, 0x5e, 0x02, 0xbe, 0x28, 0x24, 0x0e, 0xbe, 0x85, 0xe6, 0x14, 0xbe, - 0xce, 0xf9, 0xcc, 0xbd, 0x33, 0xa4, 0x06, 0xbe, 0x50, 0xe2, 0x04, 0xbe, - 0xba, 0x6e, 0x7c, 0x3d, 0x6a, 0xd6, 0x42, 0xbd, 0x70, 0x7c, 0x11, 0xbe, - 0xc5, 0xd8, 0x31, 0xbd, 0x4e, 0x90, 0x82, 0x3d, 0xa9, 0x4a, 0x6b, 0x3d, - 0x7f, 0x66, 0x38, 0xbe, 0x3b, 0x46, 0xdc, 0x3c, 0x6d, 0x54, 0x2f, 0xbe, - 0xe4, 0x7f, 0x0b, 0xbe, 0x67, 0x89, 0xe5, 0xbc, 0x28, 0x6a, 0xea, 0xbc, - 0xb8, 0xfe, 0x09, 0x3d, 0x8c, 0xbc, 0xd0, 0xbd, 0xad, 0x2a, 0x96, 0x3c, - 0x59, 0x9d, 0x6e, 0x3c, 0x6b, 0xc1, 0x97, 0xbd, 0xec, 0xb2, 0x32, 0xbe, - 0x68, 0xd3, 0x55, 0xbc, 0xb2, 0x06, 0x85, 0xbd, 0xc2, 0x41, 0xb3, 0x3c, - 0x3c, 0x7a, 0x0c, 0x3e, 0xdb, 0x8e, 0x4e, 0xbe, 0x81, 0x14, 0x31, 0x3d, - 0xdf, 0x20, 0x02, 0x3d, 0xcd, 0xd8, 0x87, 0xbb, 0xa6, 0xc4, 0x96, 0x3d, - 0x54, 0x05, 0xa9, 0xbd, 0x6c, 0x25, 0x35, 0x3d, 0x54, 0x47, 0xfc, 0xbd, - 0x0c, 0x63, 0x5e, 0x3c, 0x4e, 0xba, 0xb7, 0xbd, 0x35, 0x29, 0x2b, 0x3d, - 0xd8, 0x7f, 0xf1, 0xbd, 0x9c, 0x72, 0x86, 0xbc, 0xdf, 0x27, 0x52, 0x3d, - 0x39, 0xf6, 0x5f, 0xbe, 0x88, 0xe8, 0x38, 0xbd, 0x0c, 0x0f, 0xc0, 0xbd, - 0x61, 0x89, 0xad, 0xbd, 0x52, 0x20, 0xf7, 0xbd, 0x1d, 0x27, 0x8f, 0xbc, - 0x0d, 0x3c, 0x2a, 0xbe, 0x4d, 0x7d, 0xc2, 0xbd, 0x22, 0x71, 0x63, 0x3d, - 0x61, 0x26, 0x23, 0xbe, 0x63, 0x66, 0x1c, 0xbe, 0x95, 0xb4, 0x24, 0xbe, - 0xd7, 0xc0, 0x81, 0x3d, 0xa3, 0xe1, 0xbd, 0x3d, 0x47, 0x07, 0x01, 0xbe, - 0x77, 0x65, 0x1e, 0x3d, 0x67, 0x0a, 0x2e, 0xbe, 0xa2, 0x00, 0xc4, 0xbd, - 0x32, 0xae, 0xee, 0x3c, 0x75, 0x02, 0x4a, 0xbe, 0x60, 0x10, 0x4e, 0x3d, - 0xe2, 0xd6, 0x3d, 0xbe, 0xb0, 0x8e, 0xc1, 0xbd, 0x8a, 0x54, 0xf0, 0xbd, - 0x57, 0x39, 0x1d, 0xbe, 0x3d, 0x31, 0xd6, 0x3d, 0x60, 0x95, 0x8e, 0xbc, - 0xb6, 0x30, 0xd8, 0xbd, 0xc6, 0x52, 0x32, 0xbd, 0xa3, 0xb9, 0xd3, 0xbd, - 0x54, 0x04, 0x9d, 0xbd, 0x02, 0x45, 0x86, 0xbd, 0x80, 0xdc, 0x85, 0xbb, - 0xf2, 0x22, 0x2e, 0xbe, 0x42, 0x66, 0xb7, 0x3d, 0x6f, 0x93, 0xb6, 0x3d, - 0x22, 0xa6, 0xd2, 0xbd, 0x1f, 0x71, 0x9c, 0xbd, 0xbd, 0xfc, 0x48, 0xbe, - 0x4e, 0xf8, 0xfc, 0xbd, 0x22, 0x9a, 0x80, 0xbd, 0xf6, 0x68, 0xee, 0xbc, - 0x3e, 0x77, 0x0f, 0xbe, 0x44, 0x8e, 0xe0, 0x3d, 0x2b, 0x6c, 0x8d, 0xbd, - 0x34, 0xd2, 0xfb, 0xbd, 0xcc, 0x74, 0x24, 0xbe, 0x89, 0x50, 0x1a, 0x3d, - 0x66, 0x7d, 0x93, 0xbd, 0x87, 0x48, 0x3b, 0xbe, 0xaa, 0xfc, 0x6e, 0xbd, - 0x84, 0x1c, 0xa0, 0x3c, 0xc5, 0x1e, 0x65, 0x3d, 0x54, 0xc3, 0x62, 0x3d, - 0x28, 0x65, 0xb0, 0x3b, 0x07, 0x6a, 0x24, 0xbe, 0xba, 0x8a, 0x21, 0x3d, - 0x17, 0xbe, 0x97, 0xbc, 0x30, 0x90, 0x86, 0xbd, 0x7a, 0xb4, 0xa1, 0xbd, - 0x4d, 0x41, 0x4a, 0xbd, 0x5b, 0x7d, 0x4c, 0xbe, 0x98, 0xb7, 0xa7, 0x3d, - 0xeb, 0x1c, 0xe9, 0xbc, 0x97, 0xc3, 0xd4, 0xbd, 0xdf, 0x53, 0x7b, 0x3a, - 0x15, 0x0f, 0x4a, 0xbe, 0x65, 0xf2, 0x0b, 0xbd, 0x9f, 0x21, 0x1a, 0x3b, - 0x8c, 0x7f, 0x0c, 0xbe, 0x50, 0x6a, 0xc8, 0x3a, 0x56, 0x93, 0xf9, 0xbd, - 0x99, 0x4f, 0x1a, 0xbe, 0x8c, 0x61, 0x1b, 0xbe, 0x02, 0x51, 0x5b, 0xbd, - 0x65, 0xa4, 0x1c, 0xbe, 0x37, 0x2c, 0x5e, 0xbc, 0x3c, 0x24, 0xfc, 0x3c, - 0x47, 0xb2, 0xe5, 0x3c, 0xd1, 0xc7, 0xcb, 0xbd, 0x88, 0xf1, 0x43, 0xbe, - 0xe7, 0xb5, 0xd2, 0x3d, 0xec, 0xa3, 0x57, 0xbe, 0x11, 0xe0, 0x30, 0xbb, - 0x3d, 0xf4, 0x37, 0xbe, 0x81, 0x9d, 0x2f, 0xbe, 0x38, 0x66, 0x3d, 0xbe, - 0xad, 0x19, 0x70, 0x3c, 0x02, 0x84, 0x85, 0x3c, 0x03, 0x25, 0x9c, 0x3d, - 0xde, 0x60, 0x19, 0xbe, 0xd9, 0xfa, 0xf1, 0x3d, 0xa4, 0xd6, 0x32, 0xbe, - 0x4c, 0x62, 0x30, 0xbe, 0x51, 0x28, 0x99, 0xbd, 0xe7, 0x54, 0x21, 0xbe, - 0x8b, 0x0c, 0x78, 0xbc, 0xcb, 0xe0, 0xf8, 0xbd, 0xbb, 0xd3, 0xe2, 0xbd, - 0xc4, 0xab, 0x4a, 0x3e, 0x19, 0x1b, 0x62, 0xbc, 0x27, 0x7a, 0x01, 0x3d, - 0xde, 0x47, 0xf7, 0x3d, 0x8b, 0x03, 0x56, 0x3d, 0x2d, 0x5f, 0x88, 0xbe, - 0xaa, 0x91, 0xc8, 0xba, 0xab, 0x3d, 0x25, 0xbd, 0xa8, 0x81, 0x11, 0xbd, - 0xdf, 0xea, 0xdc, 0x3d, 0x37, 0x3b, 0x58, 0x3d, 0x34, 0x29, 0x1f, 0xbe, - 0x81, 0x90, 0xc4, 0xbd, 0x7c, 0xf3, 0x6a, 0x3d, 0x55, 0x20, 0x20, 0xbe, - 0xd8, 0x9e, 0x4b, 0xbe, 0x4e, 0x38, 0x01, 0xbe, 0xba, 0x8f, 0x0e, 0x3d, - 0x57, 0xee, 0x41, 0xbe, 0x90, 0x1e, 0xef, 0x3d, 0x45, 0x4b, 0x68, 0xbd, - 0x28, 0xa9, 0x38, 0xbe, 0xdd, 0x77, 0x24, 0xbe, 0x05, 0x63, 0x04, 0xbd, - 0xd3, 0xab, 0xea, 0xbd, 0xbd, 0xa5, 0x70, 0xbe, 0xe1, 0xb1, 0x32, 0xbe, - 0xe3, 0xae, 0xe6, 0xbd, 0x64, 0xca, 0xa9, 0xbe, 0x9e, 0xbf, 0x22, 0xbe, - 0x39, 0xf0, 0x54, 0xbb, 0x1b, 0xcf, 0xc0, 0xbc, 0x95, 0x25, 0x9b, 0xbe, - 0xc6, 0xde, 0xb2, 0x3b, 0xcf, 0x3f, 0x40, 0xbd, 0xda, 0xf8, 0x09, 0xbe, - 0x1d, 0x7c, 0xdc, 0x3c, 0xe5, 0xbc, 0xc4, 0x3d, 0xa2, 0x1e, 0xd9, 0x3d, - 0x7d, 0x35, 0xc3, 0xbc, 0x9f, 0x5d, 0x63, 0xbe, 0x65, 0xb0, 0x30, 0xbd, - 0x6f, 0x1d, 0x97, 0xbe, 0xa3, 0xdd, 0x03, 0xbe, 0xa7, 0x47, 0x70, 0xbd, - 0x13, 0xac, 0xcb, 0x3c, 0xd3, 0xcb, 0xea, 0xbd, 0xb1, 0xdf, 0x4c, 0xbd, - 0x1d, 0x2a, 0x25, 0x3e, 0xdf, 0x50, 0x0e, 0x3d, 0xab, 0xd1, 0x8c, 0xbd, - 0x02, 0xcc, 0x40, 0xbd, 0x24, 0x27, 0x10, 0x3d, 0x75, 0x0e, 0x02, 0xbd, - 0x2c, 0xf4, 0x7d, 0xbd, 0x2e, 0xe5, 0x32, 0xbd, 0xbf, 0xf6, 0x65, 0xbe, - 0x3f, 0x1a, 0x37, 0xbd, 0x05, 0x73, 0x91, 0xbe, 0xa3, 0x40, 0x8a, 0xbc, - 0xc2, 0xd6, 0xc9, 0x3d, 0x21, 0x03, 0x92, 0xbd, 0x97, 0xea, 0x33, 0x3d, - 0xaa, 0x81, 0x68, 0x3b, 0x5b, 0x83, 0xc4, 0xbc, 0x0a, 0xa9, 0x25, 0x3d, - 0x9b, 0x2c, 0x25, 0xbe, 0x14, 0x3e, 0xac, 0x3c, 0xc1, 0x23, 0x08, 0xba, - 0x8b, 0x1e, 0xbc, 0x3d, 0x97, 0x46, 0x30, 0x3d, 0x26, 0x0d, 0x65, 0xbe, - 0xa6, 0x8a, 0x41, 0x3c, 0x15, 0xf2, 0xd3, 0xbd, 0xfa, 0xc8, 0xf3, 0xbd, - 0x5f, 0x15, 0x43, 0x3d, 0xd3, 0x93, 0x61, 0xbe, 0x92, 0x8b, 0xfa, 0xbc, - 0xba, 0x67, 0x25, 0x3d, 0x84, 0x5e, 0xdc, 0xbc, 0x84, 0x0d, 0x2f, 0xbe, - 0x88, 0x7b, 0x80, 0xbc, 0x5d, 0x24, 0x64, 0xbe, 0xc5, 0xec, 0xe4, 0x3e, - 0xeb, 0x0b, 0xda, 0x3d, 0xc1, 0xb1, 0x01, 0x3c, 0x6d, 0xb4, 0xd5, 0xbd, - 0xa8, 0xf8, 0x8c, 0xbe, 0x3c, 0x26, 0x2a, 0x3e, 0xe6, 0xc3, 0xaf, 0xbd, - 0xa2, 0x32, 0x8a, 0xbd, 0x72, 0x85, 0x25, 0x3e, 0x18, 0x8d, 0xa7, 0xbe, - 0x6a, 0xd1, 0xf4, 0xbd, 0x7b, 0xd8, 0x1c, 0x3e, 0x8f, 0xa7, 0xe6, 0x3d, - 0xba, 0xb7, 0xe9, 0x3d, 0x92, 0xff, 0x1b, 0x39, 0xd6, 0xeb, 0xa9, 0xbe, - 0x4e, 0x35, 0x89, 0x3e, 0x22, 0x86, 0xce, 0x3d, 0x91, 0x39, 0x39, 0x3d, - 0x89, 0x38, 0x7f, 0xbd, 0xbd, 0x81, 0x95, 0xbe, 0xc3, 0xfc, 0xa0, 0x3c, - 0x39, 0xdc, 0x59, 0x3d, 0xfc, 0x49, 0x82, 0xbd, 0x43, 0x94, 0xf6, 0x3d, - 0x8d, 0x26, 0x7e, 0xbe, 0x72, 0xaa, 0x00, 0xbd, 0x19, 0xc0, 0xd3, 0x3d, - 0x19, 0xf2, 0x31, 0x3e, 0x15, 0x09, 0x15, 0x3e, 0x8a, 0x23, 0x21, 0xbe, - 0x31, 0xea, 0x87, 0xbe, 0x97, 0x3c, 0x88, 0x3e, 0x95, 0x2e, 0xd4, 0xba, - 0xbf, 0x26, 0x22, 0x3e, 0x6f, 0x2e, 0xb6, 0x3b, 0x97, 0xfd, 0x6d, 0xbe, - 0xdb, 0x4a, 0x5d, 0xbc, 0x64, 0x0b, 0x07, 0xbd, 0xf0, 0x4f, 0xd2, 0x3d, - 0xf1, 0x6e, 0x08, 0x3e, 0x7a, 0xae, 0x2d, 0xbe, 0x17, 0xa3, 0x6d, 0x3b, - 0x20, 0xed, 0x04, 0x3e, 0x5a, 0xea, 0xcc, 0x3d, 0x25, 0x06, 0xa2, 0x3e, - 0x3a, 0x03, 0x3e, 0xbc, 0xa1, 0x25, 0x70, 0xbe, 0x87, 0x25, 0x77, 0x3e, - 0x5b, 0xc3, 0xb6, 0xbc, 0xae, 0xab, 0x0a, 0x3c, 0x11, 0x12, 0x9b, 0x3d, - 0xe4, 0x8b, 0x7a, 0xbd, 0xdc, 0x8e, 0x30, 0x3d, 0xd1, 0xa4, 0xe0, 0xbd, - 0x1f, 0xe5, 0x8a, 0x3d, 0xad, 0x0e, 0xdf, 0x3d, 0x20, 0x65, 0x9f, 0xbd, - 0xaf, 0xa1, 0x35, 0xbd, 0xa5, 0x64, 0xa8, 0x3d, 0x2c, 0x9f, 0x0e, 0x3e, - 0x0a, 0x1f, 0xbe, 0x3e, 0x56, 0xb8, 0x1c, 0xbd, 0xf3, 0xee, 0x84, 0xbe, - 0xb6, 0x05, 0x2b, 0x3e, 0xe0, 0x9a, 0x33, 0x3d, 0x02, 0x7f, 0xca, 0x3c, - 0x97, 0x85, 0xe2, 0x3c, 0x3a, 0xfa, 0xbb, 0x3d, 0x2c, 0x24, 0x0a, 0x3e, - 0x01, 0x6d, 0x53, 0x3d, 0xfb, 0xcf, 0xfd, 0x3d, 0x41, 0x61, 0x80, 0x3c, - 0x09, 0xb0, 0x69, 0x3d, 0x61, 0x38, 0x15, 0xbe, 0x97, 0x60, 0xf9, 0xbd, - 0xcd, 0xe6, 0x39, 0x3e, 0xb0, 0xae, 0x5e, 0x3e, 0x31, 0x32, 0x2a, 0x3c, - 0x9e, 0xa0, 0xa4, 0xbe, 0xcd, 0xb6, 0x2b, 0x3e, 0x07, 0xb6, 0x46, 0x3c, - 0x44, 0xa0, 0x2e, 0x3d, 0x68, 0x10, 0x6d, 0xbd, 0xe3, 0xce, 0xde, 0x3d, - 0xca, 0xbe, 0x04, 0x3e, 0x27, 0x2d, 0x11, 0x3e, 0x63, 0xca, 0x85, 0x3d, - 0x89, 0x51, 0xc7, 0xbd, 0x00, 0xdc, 0xa9, 0x3c, 0xeb, 0x09, 0x6e, 0xbe, - 0x9f, 0x97, 0x86, 0xbe, 0xfa, 0x44, 0xe9, 0xba, 0x05, 0x4f, 0xfe, 0xbd, - 0x44, 0x2e, 0xd3, 0xba, 0x90, 0x31, 0xac, 0xbe, 0x4b, 0x71, 0xbc, 0xbe, - 0x9c, 0x9e, 0x3e, 0xbe, 0x7e, 0x25, 0x6a, 0x3d, 0x1e, 0xbc, 0x39, 0xbe, - 0x16, 0xcb, 0x10, 0x3e, 0x8a, 0x63, 0x30, 0xbe, 0x8e, 0x13, 0x21, 0xbe, - 0xd8, 0x96, 0xf3, 0xbd, 0x85, 0xba, 0x0c, 0xbe, 0xc9, 0xd6, 0x97, 0xbe, - 0xd1, 0xa0, 0x14, 0xbe, 0xd3, 0x16, 0xa8, 0xbe, 0x6c, 0x1a, 0xa5, 0xbe, - 0x6b, 0x6f, 0x81, 0xbd, 0xcb, 0x80, 0xc5, 0x3d, 0x71, 0xfe, 0x0b, 0xbd, - 0x32, 0x87, 0xbe, 0xbe, 0x0b, 0x64, 0x0f, 0xbe, 0xe7, 0x2f, 0x3c, 0xbe, - 0xc1, 0xe2, 0x28, 0xbd, 0xa4, 0x5c, 0xd8, 0x3d, 0xfc, 0x5f, 0xc1, 0xbe, - 0x95, 0x90, 0x9e, 0xbe, 0x17, 0xfb, 0xad, 0xbd, 0x91, 0x30, 0xec, 0xbd, - 0xaf, 0xbf, 0x43, 0x3c, 0xdd, 0x67, 0x55, 0xbe, 0xf9, 0x25, 0x8c, 0x3e, - 0x1b, 0x3c, 0x0b, 0xbe, 0x0b, 0x35, 0x85, 0xbe, 0x7c, 0xc2, 0x3f, 0x3e, - 0x2f, 0x5e, 0xf2, 0xb9, 0x15, 0x1a, 0xda, 0x3d, 0xc6, 0x50, 0x3b, 0xbe, - 0x9d, 0x9d, 0xaf, 0xbe, 0x9d, 0x8e, 0x34, 0xbd, 0x51, 0x22, 0x2f, 0xbe, - 0xa2, 0x60, 0xaa, 0xbe, 0x5d, 0x8a, 0x94, 0x3c, 0x0d, 0xb3, 0xfb, 0x3c, - 0xf1, 0x3c, 0xe8, 0xbd, 0x58, 0x82, 0x20, 0xbc, 0x1a, 0xe6, 0xb5, 0xbe, - 0xde, 0x09, 0xc2, 0x3d, 0x83, 0xac, 0xa9, 0x3c, 0xf2, 0x22, 0x58, 0xbe, - 0x71, 0x1c, 0x44, 0x3d, 0x83, 0xda, 0xc1, 0xbd, 0x1b, 0xdb, 0x3d, 0xbd, - 0x9a, 0x3d, 0xa5, 0x3d, 0x99, 0xda, 0x96, 0xbe, 0xbe, 0xf8, 0xe4, 0xbd, - 0x97, 0x5a, 0x8e, 0x3d, 0x90, 0xc8, 0x3c, 0xbc, 0xfd, 0xcc, 0x1d, 0xbd, - 0x6c, 0xd9, 0x72, 0xbd, 0x20, 0x59, 0xe7, 0xbd, 0x96, 0xbf, 0x2c, 0xbe, - 0x35, 0x83, 0x6d, 0x3a, 0x7b, 0x12, 0xb8, 0xbd, 0xae, 0xcd, 0x13, 0x3e, - 0xc6, 0x77, 0x89, 0xbe, 0x78, 0x0c, 0xa5, 0x3c, 0xa1, 0xd2, 0x74, 0xbe, - 0x38, 0x35, 0xd9, 0xbd, 0x6d, 0x6b, 0xcf, 0xbd, 0xb6, 0xb3, 0x22, 0xbf, - 0xe3, 0x2c, 0x91, 0xbe, 0xc5, 0xce, 0x16, 0x3d, 0x60, 0x54, 0x73, 0xbd, - 0x60, 0x5b, 0x46, 0xbc, 0xb6, 0xe1, 0x22, 0xbe, 0xa6, 0xe4, 0xcb, 0xbd, - 0x24, 0xdd, 0xaf, 0xbe, 0xec, 0x5d, 0x24, 0xbe, 0x4c, 0xf7, 0x50, 0xbe, - 0x1c, 0x47, 0xbb, 0xbc, 0xbc, 0x5c, 0x83, 0xbe, 0xcc, 0x3f, 0x4c, 0x3d, - 0x1c, 0xa0, 0x7f, 0xbe, 0x4d, 0xe1, 0x95, 0xbd, 0x2a, 0xfd, 0x96, 0xbc, - 0x9f, 0xb1, 0x8e, 0x3d, 0xff, 0x4d, 0xf1, 0xbd, 0x78, 0x7c, 0x37, 0x3c, - 0x1b, 0x0d, 0x9a, 0xbd, 0x5e, 0xc0, 0x93, 0xbe, 0xf2, 0x86, 0x2b, 0xbf, - 0x89, 0x8b, 0x7d, 0xbe, 0xa3, 0xa6, 0x08, 0xbf, 0x39, 0x38, 0x19, 0xbe, - 0xc8, 0xdb, 0x8c, 0xbe, 0x78, 0x36, 0x1e, 0xbe, 0x04, 0xb4, 0x53, 0xbe, - 0xba, 0xb2, 0x8c, 0x3d, 0x08, 0xbb, 0x75, 0xbe, 0x98, 0x92, 0x2a, 0xbc, - 0x2b, 0x5e, 0x06, 0xbe, 0x21, 0xfe, 0x09, 0x3b, 0xb5, 0x4d, 0xfe, 0xbe, - 0x23, 0xb7, 0x4d, 0x3e, 0xa7, 0x88, 0x2e, 0x3c, 0x61, 0x2c, 0x3f, 0xbe, - 0xf9, 0x50, 0x36, 0xbf, 0xc5, 0x26, 0x35, 0xbe, 0x50, 0x45, 0xb1, 0x3d, - 0x31, 0x16, 0xde, 0xbd, 0xe7, 0x2c, 0xe6, 0xbd, 0x9b, 0xae, 0xb1, 0x3d, - 0x24, 0xa9, 0xad, 0x3c, 0x9a, 0x3a, 0x98, 0x3c, 0xfc, 0xa3, 0x28, 0xbc, - 0x49, 0x45, 0x84, 0xbe, 0x7f, 0x97, 0x7e, 0xbd, 0xbf, 0xd4, 0x3f, 0x3d, - 0xfc, 0x7f, 0xb5, 0xbc, 0x9b, 0x93, 0x9e, 0x3d, 0xab, 0x88, 0x9f, 0xbe, - 0x44, 0xe4, 0xa0, 0xbd, 0x82, 0x13, 0x1a, 0x3e, 0xa0, 0xdf, 0x0a, 0xbc, - 0x5c, 0x20, 0x06, 0xbe, 0xd1, 0x2a, 0x53, 0xbd, 0xd0, 0xf1, 0x2b, 0xbe, - 0x3d, 0xed, 0x4d, 0x3e, 0x85, 0xfc, 0xf9, 0xbd, 0x67, 0x4c, 0x11, 0xbd, - 0x0b, 0x96, 0x40, 0xbc, 0x58, 0x16, 0x18, 0xbd, 0x23, 0x34, 0xfe, 0x3b, - 0xb6, 0xfb, 0x8b, 0x3a, 0xea, 0x06, 0x95, 0x3d, 0x10, 0xcd, 0xa1, 0x3d, - 0xc5, 0x71, 0x4c, 0xbe, 0xb1, 0x53, 0x81, 0x3d, 0xb4, 0x47, 0x13, 0x3e, - 0x94, 0x6e, 0x17, 0xbd, 0x5a, 0xcb, 0x8c, 0x3d, 0x83, 0x49, 0x3b, 0xbe, - 0x2e, 0xa6, 0x17, 0xbe, 0x9f, 0x1a, 0xa9, 0x3d, 0xc1, 0xaf, 0xe4, 0x3d, - 0x31, 0x59, 0x32, 0xbd, 0xe4, 0xf4, 0x61, 0x3c, 0x1c, 0x13, 0xe7, 0x3d, - 0x51, 0xcf, 0x80, 0xbd, 0x98, 0x24, 0xf3, 0xba, 0xe8, 0x29, 0x8f, 0x3c, - 0x38, 0x73, 0x5b, 0x3d, 0xd2, 0x0a, 0x90, 0xbe, 0xe9, 0xbb, 0x8f, 0x3d, - 0x8b, 0xa2, 0x69, 0x3d, 0xed, 0xa3, 0x6e, 0xbd, 0xfc, 0x91, 0x15, 0xbe, - 0xe4, 0xf2, 0x82, 0xbd, 0x43, 0x1f, 0x34, 0xbe, 0x32, 0x26, 0xdb, 0x3d, - 0x1b, 0x8c, 0x82, 0x3e, 0x31, 0xa0, 0x82, 0x3c, 0xfd, 0xf9, 0x30, 0x3d, - 0x43, 0x0a, 0x49, 0x3e, 0x39, 0x25, 0x9b, 0xbd, 0x49, 0x70, 0xd5, 0x3c, - 0x85, 0x0e, 0x22, 0xbd, 0x42, 0xfe, 0x84, 0x3d, 0x4a, 0x16, 0xce, 0xbd, - 0x93, 0x1b, 0xd2, 0x3d, 0x3d, 0xb3, 0x20, 0x3e, 0x09, 0x6f, 0x12, 0x3d, - 0x15, 0x35, 0x9c, 0xbd, 0x57, 0xfb, 0x86, 0xbd, 0x84, 0xad, 0xa9, 0xbd, - 0x4d, 0x00, 0x75, 0x3d, 0x03, 0x13, 0xbc, 0x3c, 0x8e, 0x74, 0xbf, 0x3c, - 0x5c, 0xbd, 0xac, 0x3c, 0xa1, 0x42, 0xc5, 0xbc, 0xf4, 0x73, 0x99, 0x3b, - 0x05, 0xe3, 0x6a, 0x3d, 0x14, 0x1e, 0xa4, 0xbc, 0x7e, 0x6d, 0x01, 0x3d, - 0xfe, 0x09, 0x9a, 0xbd, 0x1e, 0x4f, 0xa5, 0x3d, 0x09, 0x7c, 0x11, 0x3d, - 0xa0, 0x60, 0xce, 0x3d, 0x6d, 0x50, 0x2d, 0xbc, 0x6d, 0xf9, 0x47, 0xbd, - 0xe9, 0x80, 0xde, 0xbc, 0x78, 0xdb, 0xc1, 0x3d, 0x62, 0x70, 0x87, 0x3d, - 0x14, 0xe1, 0x5b, 0x3e, 0x1a, 0x00, 0xa3, 0x3a, 0x3e, 0x19, 0x0c, 0x3e, - 0x52, 0xd9, 0x91, 0x3d, 0x52, 0x1c, 0xb2, 0x3c, 0x64, 0x74, 0x6d, 0x3d, - 0xd0, 0x12, 0x16, 0x3d, 0xcf, 0x02, 0xc3, 0xbc, 0x7a, 0x0a, 0xad, 0x3d, - 0x6f, 0x3f, 0xa2, 0x3d, 0x19, 0x2d, 0x00, 0x3e, 0x7f, 0xcb, 0xdd, 0xbd, - 0x0e, 0x7c, 0x01, 0x3c, 0x00, 0x01, 0xe6, 0xbd, 0xc3, 0x6a, 0x1b, 0xbc, - 0xa7, 0xff, 0x97, 0xbd, 0xd9, 0x84, 0xf7, 0xbd, 0x5a, 0x3b, 0x11, 0xbe, - 0x91, 0xfc, 0x3c, 0x3d, 0xe1, 0x9c, 0xaf, 0x3c, 0x69, 0x77, 0x00, 0x3e, - 0xfd, 0xee, 0xbb, 0x3d, 0x06, 0x52, 0xb6, 0xbe, 0x1e, 0x8e, 0x1f, 0x3d, - 0x2f, 0xc2, 0xcb, 0x3e, 0x6a, 0xdb, 0x31, 0x3d, 0xab, 0x2d, 0xd0, 0x3d, - 0xc7, 0xf5, 0x91, 0xbe, 0x65, 0x4d, 0x13, 0x3e, 0x10, 0x24, 0xcf, 0xbd, - 0x40, 0xde, 0x08, 0xbe, 0x12, 0x5d, 0x71, 0xbe, 0x5d, 0xb2, 0xa6, 0xbf, - 0x53, 0x7a, 0x2f, 0xbe, 0x47, 0xf8, 0x51, 0x3d, 0x43, 0x58, 0xc7, 0xbe, - 0x5e, 0x8c, 0xeb, 0xbc, 0xdb, 0xbf, 0x36, 0xbe, 0xd8, 0xc1, 0x53, 0xbe, - 0x62, 0x50, 0xdf, 0x3d, 0xe0, 0xdc, 0x88, 0x3d, 0xfc, 0x2e, 0xbf, 0x3d, - 0x18, 0x9d, 0x6e, 0x3d, 0xa8, 0x03, 0xf2, 0xbc, 0xd9, 0x3b, 0x31, 0x3d, - 0x1e, 0x09, 0xf3, 0x3d, 0xd8, 0x5f, 0xdd, 0xbd, 0x86, 0x22, 0x63, 0xbe, - 0x8c, 0x13, 0xb0, 0xbf, 0x6a, 0x75, 0x46, 0xbe, 0xfd, 0x03, 0x5a, 0x3e, - 0x61, 0x03, 0xc3, 0xbe, 0x87, 0x78, 0xb8, 0xbe, 0x50, 0x6c, 0x8b, 0xbd, - 0x0b, 0x20, 0x86, 0xbe, 0x27, 0xfd, 0x09, 0x3e, 0xec, 0x74, 0x51, 0xbd, - 0x9b, 0x46, 0xbd, 0xbc, 0x4f, 0x6b, 0xad, 0x3d, 0x85, 0xbe, 0x9d, 0xbe, - 0xb0, 0x4d, 0x9b, 0xbe, 0xf6, 0x3f, 0xc0, 0xbb, 0x81, 0x32, 0x70, 0x3a, - 0x9b, 0x91, 0xaa, 0xbe, 0x55, 0x99, 0x7c, 0xbf, 0x7a, 0xac, 0x4a, 0xbe, - 0x47, 0xf4, 0xaa, 0x3d, 0x0d, 0xb4, 0xbb, 0xbd, 0xb3, 0x66, 0x83, 0xbe, - 0x4a, 0xaf, 0xad, 0x3d, 0x36, 0x81, 0x48, 0xbf, 0xbd, 0x08, 0x55, 0x3e, - 0xb3, 0x47, 0xcf, 0xbe, 0x7c, 0x6b, 0x82, 0xbc, 0xe0, 0x19, 0x35, 0x3c, - 0x05, 0x79, 0x2d, 0x3c, 0x78, 0x94, 0x62, 0x3c, 0x80, 0x26, 0xe6, 0x3e, - 0x2f, 0x2b, 0x0d, 0xbe, 0xce, 0x6d, 0x03, 0xbe, 0xe3, 0x47, 0x18, 0xbd, - 0x51, 0x31, 0x9d, 0xbd, 0x13, 0x5a, 0x8a, 0x3d, 0x97, 0x14, 0x9a, 0xbc, - 0xfe, 0xf8, 0x9a, 0xbd, 0x72, 0x24, 0x9b, 0xbd, 0xa5, 0x7d, 0xe0, 0xbd, - 0xb2, 0xbf, 0x7d, 0x3e, 0x44, 0xbe, 0xf9, 0xbd, 0xd8, 0x53, 0xef, 0x3c, - 0x9a, 0x0c, 0xfb, 0xbd, 0x1b, 0x7b, 0x24, 0xbf, 0x9a, 0x19, 0x4d, 0x3c, - 0x84, 0xb6, 0x1e, 0xbe, 0x55, 0x11, 0x57, 0x3d, 0x72, 0x25, 0x82, 0xbd, - 0x0d, 0x3b, 0xcb, 0x3c, 0x15, 0xc6, 0x38, 0x3e, 0xc2, 0x13, 0x8b, 0xbe, - 0x13, 0x3f, 0x44, 0xbd, 0xfd, 0xa6, 0xec, 0x3b, 0xfd, 0x2c, 0x2d, 0xbd, - 0x70, 0xdc, 0x0d, 0xbe, 0x91, 0x67, 0x90, 0x3e, 0x3a, 0xe4, 0x1d, 0xbe, - 0x0f, 0xaa, 0x72, 0xbe, 0xf9, 0xf7, 0x09, 0xbb, 0x19, 0xae, 0x93, 0xbc, - 0x5f, 0x29, 0x5b, 0xbd, 0xc8, 0x13, 0x0d, 0x3e, 0x10, 0x64, 0xcf, 0xbe, - 0x68, 0x54, 0x0c, 0x3e, 0xbf, 0x9c, 0xdf, 0x3c, 0xc5, 0xf3, 0xc5, 0x3c, - 0x9d, 0x2c, 0xa8, 0x3b, 0x5c, 0x79, 0x74, 0xbd, 0xaa, 0x55, 0x3b, 0x3d, - 0xc0, 0xfc, 0xd2, 0xbd, 0xdd, 0xbf, 0x8a, 0xbd, 0x49, 0x32, 0x99, 0x3e, - 0x4c, 0xfc, 0x60, 0xbe, 0xc5, 0x19, 0x2b, 0xbe, 0xb8, 0x87, 0x97, 0x3d, - 0xde, 0x93, 0x40, 0x3e, 0x4c, 0x60, 0xdb, 0x3c, 0xab, 0x0a, 0xbd, 0x3d, - 0xb9, 0xdb, 0xd1, 0xbd, 0x7a, 0xe3, 0x0f, 0xbe, 0x36, 0x1c, 0x00, 0xbc, - 0x1e, 0x22, 0xbd, 0x3d, 0x35, 0x60, 0x22, 0x3e, 0x96, 0xd5, 0x64, 0xbe, - 0xa4, 0x7b, 0x87, 0x3d, 0x4a, 0x4a, 0xae, 0x3d, 0xc0, 0xa6, 0x6c, 0x3d, - 0xc7, 0x26, 0x47, 0x3e, 0x9c, 0x97, 0x3e, 0x3e, 0x3c, 0x81, 0x14, 0xbe, - 0x99, 0xb0, 0x66, 0x3d, 0x41, 0xf2, 0x82, 0xbc, 0x43, 0x79, 0x1f, 0x3e, - 0x6c, 0x61, 0x90, 0x3d, 0x95, 0x81, 0xb8, 0x3d, 0x68, 0x30, 0x17, 0xbe, - 0x6c, 0x5f, 0x20, 0xbd, 0x24, 0x03, 0xcc, 0x3d, 0xe5, 0xe1, 0x2b, 0xbe, - 0x1f, 0x42, 0x68, 0xbd, 0x51, 0xe0, 0x2f, 0x3d, 0x74, 0xff, 0xf0, 0x3d, - 0x92, 0xbe, 0xbb, 0x3c, 0x9c, 0xa9, 0x2f, 0xbc, 0x33, 0xdd, 0x1d, 0x3e, - 0x05, 0x8a, 0x5f, 0xbe, 0x30, 0x4b, 0x56, 0x3c, 0x64, 0x0c, 0x42, 0x3e, - 0xce, 0xa6, 0x30, 0xbd, 0x8c, 0xcc, 0x29, 0x3e, 0x0a, 0x83, 0x54, 0x3d, - 0x5e, 0xf6, 0xb8, 0x3c, 0xea, 0x39, 0xd4, 0xbd, 0x5a, 0xf2, 0xa0, 0x3d, - 0x0e, 0x5b, 0x59, 0xbe, 0xad, 0x47, 0x3f, 0xbe, 0x2b, 0xdd, 0xcc, 0x3c, - 0x5b, 0xf8, 0xf8, 0x3c, 0x70, 0xde, 0x8d, 0x3d, 0x44, 0x74, 0x72, 0xbc, - 0x56, 0x47, 0xa0, 0x3d, 0x22, 0x81, 0xfb, 0xbc, 0xf0, 0xdc, 0x3b, 0xbd, - 0x84, 0xe8, 0x01, 0x3e, 0x67, 0xc4, 0xc9, 0x3d, 0x89, 0xc4, 0x2e, 0x3e, - 0xf1, 0x50, 0x11, 0xbe, 0x13, 0x78, 0x9c, 0xbe, 0xa3, 0x16, 0xe7, 0xbc, - 0x71, 0xf7, 0x9f, 0x3d, 0x30, 0xe0, 0xb8, 0xbe, 0x02, 0x50, 0x80, 0xbd, - 0x93, 0x01, 0x7d, 0x3d, 0x37, 0xd7, 0xbe, 0x3d, 0xfa, 0xda, 0xbb, 0x3d, - 0xdd, 0xa4, 0x8c, 0xbc, 0x64, 0xf7, 0x24, 0x3e, 0x18, 0xfa, 0x90, 0xbd, - 0xb8, 0x50, 0x3e, 0x3d, 0x29, 0xf3, 0x31, 0x3e, 0x98, 0xfc, 0xfd, 0x3c, - 0x2f, 0x27, 0x11, 0x3e, 0x81, 0x72, 0x74, 0x3c, 0xf7, 0xf2, 0x9a, 0xbd, - 0xc1, 0xdc, 0x30, 0xbd, 0x95, 0x24, 0x7c, 0xbc, 0x5d, 0x1b, 0x15, 0xbe, - 0xe5, 0xe1, 0x7a, 0xbd, 0xd5, 0xa1, 0x25, 0xbc, 0x8a, 0x2c, 0x81, 0x3d, - 0x17, 0x03, 0xde, 0x3c, 0xda, 0x46, 0x2e, 0x3e, 0x48, 0x3e, 0x50, 0x3d, - 0x36, 0x32, 0x01, 0xbe, 0x45, 0x72, 0x1e, 0x3d, 0xcd, 0xbe, 0xeb, 0x3d, - 0x8e, 0x04, 0xfd, 0x3c, 0xf4, 0x5b, 0xce, 0x3d, 0xfc, 0x6e, 0xb0, 0xbc, - 0xb6, 0xf6, 0x0b, 0xbe, 0xa4, 0x70, 0x61, 0x3d, 0x21, 0x74, 0x03, 0x3d, - 0x98, 0xe1, 0x35, 0xbe, 0x58, 0xbb, 0x17, 0xbd, 0x26, 0x4a, 0x1a, 0xbc, - 0xbe, 0xf7, 0xb4, 0x3d, 0x63, 0x36, 0x86, 0xbc, 0x05, 0xdd, 0x6b, 0xbe, - 0x4e, 0xbb, 0x40, 0x3e, 0x4b, 0x4c, 0x03, 0xbe, 0x3e, 0x52, 0xad, 0x3c, - 0xbc, 0x71, 0x3f, 0x3e, 0xbe, 0x5b, 0x3e, 0x3d, 0x5f, 0x43, 0x09, 0x3e, - 0x81, 0x00, 0x25, 0xbd, 0x38, 0xad, 0x07, 0xbe, 0x18, 0x53, 0x4d, 0x3d, - 0x4c, 0x43, 0x61, 0x3d, 0x0c, 0xa1, 0x07, 0xbd, 0x4e, 0xba, 0xea, 0x3c, - 0xea, 0x72, 0x01, 0xbd, 0xc0, 0x41, 0x81, 0x3d, 0xe1, 0x81, 0x5b, 0x3d, - 0x4c, 0xb6, 0x97, 0xbe, 0xe7, 0x0b, 0xa5, 0xbc, 0x48, 0xf2, 0x32, 0xbe, - 0xd2, 0xda, 0x84, 0x3d, 0x8c, 0x5f, 0x5e, 0x3e, 0xa1, 0xc7, 0x02, 0x3e, - 0x0c, 0xc9, 0x31, 0x3e, 0x80, 0xd2, 0x72, 0x3c, 0x21, 0x71, 0x2f, 0x3d, - 0x69, 0x6f, 0xa3, 0xbd, 0x8e, 0x40, 0xd2, 0x3d, 0xe0, 0xc8, 0x9b, 0xbe, - 0x47, 0x50, 0x0e, 0xbd, 0x5b, 0x11, 0xa7, 0xbd, 0x8e, 0x8b, 0x3d, 0x3e, - 0xa3, 0xc7, 0xac, 0x3c, 0x39, 0x1c, 0x62, 0xbd, 0xee, 0x82, 0x84, 0xbd, - 0xc7, 0x1b, 0x80, 0xbe, 0x74, 0xb2, 0xcf, 0xbd, 0xe7, 0x3f, 0x80, 0x3e, - 0x4c, 0x39, 0x64, 0x3e, 0xfb, 0x88, 0xd1, 0x3d, 0x87, 0x2f, 0xbe, 0x3c, - 0x20, 0xd9, 0xa1, 0x3d, 0x56, 0xa4, 0x1a, 0xbd, 0xe4, 0xef, 0x2a, 0x3e, - 0x28, 0x30, 0x66, 0xbe, 0xf6, 0x48, 0x88, 0xbd, 0xda, 0x26, 0x73, 0xbe, - 0x86, 0x7c, 0x3d, 0x3d, 0xb6, 0x51, 0x21, 0xbe, 0x59, 0x39, 0x45, 0xbe, - 0xb5, 0xf1, 0x8b, 0xbc, 0xf2, 0x0c, 0x8d, 0xbe, 0x38, 0x94, 0x7a, 0xbe, - 0x38, 0xf4, 0xdc, 0x3e, 0x50, 0x49, 0xa9, 0x3b, 0xad, 0xce, 0xd0, 0x3e, - 0x09, 0x89, 0x7c, 0xbe, 0x22, 0x0b, 0x23, 0x3e, 0x32, 0xe2, 0x6d, 0x3d, - 0x93, 0x63, 0xa1, 0xbc, 0xd8, 0xbf, 0x2b, 0xbe, 0x03, 0x66, 0x85, 0xbd, - 0xb1, 0x64, 0x05, 0xbf, 0x62, 0x73, 0x02, 0x3e, 0x64, 0xd3, 0x3f, 0xbe, - 0x5b, 0xa5, 0x81, 0xbd, 0xac, 0xae, 0xfb, 0x3d, 0x14, 0xe3, 0x42, 0xbe, - 0x82, 0xe4, 0x8c, 0xbe, 0x93, 0x8a, 0xfd, 0x3e, 0xd5, 0xc6, 0xdb, 0xbd, - 0x3e, 0xb8, 0xdf, 0x3a, 0xec, 0x0c, 0x96, 0xbe, 0xcc, 0xe5, 0x00, 0xbe, - 0xb9, 0x58, 0x06, 0x3e, 0x55, 0x01, 0x22, 0xbd, 0x71, 0x20, 0xa9, 0xbe, - 0x5d, 0xec, 0xd8, 0xbd, 0x74, 0x9f, 0xe8, 0xbe, 0x5f, 0x04, 0x08, 0xbf, - 0x33, 0x01, 0x59, 0xbe, 0x2f, 0xca, 0x3d, 0x3c, 0xd6, 0xba, 0x4e, 0xbd, - 0xbe, 0xce, 0xbd, 0xbd, 0x1f, 0x27, 0xae, 0xbe, 0x00, 0x1d, 0x02, 0x3b, - 0x64, 0x89, 0x51, 0xbe, 0xdc, 0xd5, 0x38, 0xbe, 0x6d, 0x74, 0x51, 0xbd, - 0xec, 0xf8, 0x1d, 0xbe, 0xd2, 0xdc, 0x1d, 0xbe, 0xa5, 0x83, 0x47, 0xbe, - 0x9e, 0x83, 0x2e, 0xbe, 0x4b, 0x5b, 0x5e, 0xbd, 0xbb, 0x71, 0x3d, 0xbe, - 0x6e, 0x37, 0x01, 0xbc, 0x23, 0x82, 0xde, 0xbd, 0x75, 0x10, 0x3d, 0xbe, - 0x4e, 0x0b, 0x6d, 0xbd, 0xad, 0xd3, 0x51, 0xbe, 0x40, 0xb4, 0x4e, 0x3d, - 0xd0, 0x14, 0x46, 0xbd, 0x96, 0x9f, 0x14, 0x3b, 0x92, 0x6d, 0xaf, 0x3c, - 0x83, 0x38, 0xe2, 0x3d, 0x0e, 0x19, 0xe3, 0xbd, 0x7c, 0xf9, 0x4a, 0xbe, - 0x18, 0x3e, 0xef, 0x3c, 0xf6, 0xe1, 0x5f, 0xbd, 0x57, 0x00, 0x15, 0xbe, - 0x62, 0xd6, 0xa2, 0x3d, 0xaf, 0xad, 0x39, 0x3b, 0x0a, 0x48, 0x6e, 0xbd, - 0x93, 0xd2, 0xa8, 0xbd, 0x56, 0x66, 0x72, 0xbc, 0xc8, 0x72, 0x73, 0xbd, - 0x09, 0xfd, 0xba, 0xbd, 0x46, 0xcd, 0x21, 0x3d, 0x27, 0xb5, 0xae, 0x3d, - 0x16, 0xb6, 0x07, 0xbd, 0x5d, 0x77, 0xa7, 0x3d, 0xa9, 0x08, 0xdb, 0xbd, - 0xfb, 0xca, 0x1e, 0xbe, 0xe3, 0x5f, 0xe6, 0x3c, 0x8c, 0x4e, 0xca, 0xbc, - 0x6e, 0xf4, 0x4e, 0xbe, 0x15, 0x45, 0x28, 0x3d, 0x27, 0x81, 0x80, 0xbd, - 0x7c, 0x6d, 0xb3, 0x3d, 0x42, 0xaf, 0xb8, 0xbd, 0x66, 0x4f, 0x9b, 0xbd, - 0xc6, 0x85, 0xb3, 0x3d, 0x78, 0x3f, 0x13, 0xbe, 0xdd, 0x23, 0xe7, 0x3c, - 0x21, 0x1f, 0x32, 0xbc, 0x5c, 0x0e, 0xde, 0x3d, 0xc3, 0xbc, 0x6d, 0x3c, - 0x26, 0xe5, 0x21, 0xbe, 0x3d, 0xbe, 0x0a, 0xbc, 0x67, 0x62, 0x05, 0xbe, - 0xb3, 0xda, 0xa7, 0x3d, 0x56, 0xe4, 0xad, 0xbd, 0xbb, 0xf0, 0xa0, 0xbd, - 0xfa, 0x95, 0x27, 0x3d, 0x7f, 0x8b, 0x9f, 0xbd, 0xbf, 0xe7, 0x3e, 0xbe, - 0xfe, 0xbc, 0x71, 0xbd, 0x03, 0x90, 0x3c, 0x3b, 0x78, 0x8d, 0x01, 0xbe, - 0x43, 0x0b, 0x07, 0xbd, 0xcf, 0xa0, 0x05, 0xbe, 0xf3, 0x7d, 0xcd, 0x3d, - 0xf3, 0xdb, 0x32, 0xba, 0x71, 0x00, 0xee, 0xbd, 0xfd, 0x8c, 0x1f, 0x3d, - 0xd8, 0x98, 0xb2, 0xbd, 0x51, 0x9d, 0x68, 0x3d, 0x8b, 0x76, 0xa4, 0x3c, - 0xeb, 0x34, 0x3f, 0xbe, 0x80, 0x0c, 0xeb, 0x3c, 0xef, 0x6f, 0x09, 0xbe, - 0x04, 0x71, 0xb5, 0xbc, 0xfa, 0x53, 0x31, 0xbe, 0x94, 0xf8, 0x75, 0xbd, - 0x93, 0x96, 0x73, 0xbd, 0xc6, 0x55, 0x25, 0xbe, 0x01, 0x35, 0x9b, 0xbd, - 0x95, 0x30, 0x48, 0x3d, 0x6f, 0x3e, 0x7f, 0x3d, 0x81, 0x06, 0x23, 0xbe, - 0x29, 0x41, 0x82, 0xbb, 0x4f, 0xda, 0x90, 0x3b, 0x17, 0x92, 0x67, 0xbc, - 0x37, 0x1e, 0x30, 0xbe, 0x4b, 0x99, 0xb6, 0x3c, 0xe1, 0xdb, 0x2a, 0x3d, - 0x74, 0x6d, 0x50, 0xbe, 0x48, 0xe0, 0x9e, 0x3d, 0xd3, 0x44, 0xce, 0xbb, - 0xd4, 0xb7, 0x20, 0xbd, 0x0c, 0xaf, 0x10, 0xbe, 0x0c, 0xe1, 0x4e, 0x3d, - 0xbe, 0xf9, 0xda, 0x3d, 0xde, 0x3a, 0xde, 0xbc, 0xd0, 0x93, 0x1b, 0x3d, - 0x7f, 0x3a, 0xf3, 0xbd, 0xcc, 0x62, 0x9a, 0xbc, 0x91, 0x0a, 0x19, 0xbe, - 0x47, 0x64, 0xe1, 0xbd, 0xe7, 0x66, 0xd4, 0xbd, 0x04, 0x6e, 0x06, 0xbe, - 0x1a, 0xe8, 0x3e, 0xbd, 0xbd, 0xcd, 0x21, 0xbe, 0x6c, 0x13, 0x96, 0xbd, - 0x62, 0xb7, 0x05, 0xbd, 0x6c, 0xda, 0xd6, 0xbd, 0x4c, 0x75, 0xe3, 0xbb, - 0xe6, 0x13, 0x5f, 0x3d, 0xde, 0xcd, 0x03, 0xbd, 0x28, 0xc6, 0xcc, 0xbd, - 0x73, 0x0b, 0xe2, 0x3c, 0x40, 0x3c, 0x0b, 0x3e, 0xed, 0xbc, 0xfd, 0xbd, - 0x77, 0x82, 0x93, 0x3b, 0x79, 0xbe, 0x02, 0x3e, 0xa7, 0xa9, 0x39, 0xbe, - 0x04, 0x0e, 0xa9, 0xbc, 0x62, 0x00, 0x07, 0x3c, 0x2a, 0x45, 0x9d, 0xbd, - 0xd8, 0x19, 0xd3, 0x3d, 0x8d, 0x9f, 0xd5, 0xbc, 0xcc, 0x7c, 0x3c, 0xbe, - 0xf1, 0x59, 0x82, 0x3a, 0x9f, 0x7c, 0x08, 0xbe, 0xfe, 0xb1, 0x2e, 0xbe, - 0x00, 0x11, 0xbe, 0xbd, 0xb8, 0x0a, 0x57, 0x3d, 0x78, 0x76, 0xd0, 0x3d, - 0xcb, 0x99, 0x55, 0xbd, 0x1e, 0x3f, 0x9d, 0x3c, 0xf9, 0xe4, 0x1f, 0xbd, - 0x47, 0x65, 0x2a, 0x3d, 0x98, 0xaf, 0x32, 0xbe, 0xd9, 0xdb, 0x9b, 0xbd, - 0x16, 0x03, 0x3d, 0x3d, 0x7e, 0x94, 0x96, 0x3d, 0x9c, 0xa5, 0x84, 0x3d, - 0xea, 0x1e, 0xa4, 0xbd, 0x75, 0xea, 0x0e, 0xbd, 0x65, 0xa6, 0x8a, 0xbd, - 0xce, 0xcb, 0xe2, 0xbd, 0xe2, 0xd0, 0x14, 0x3c, 0x94, 0x2b, 0x0d, 0x3c, - 0xb0, 0x98, 0x96, 0x3c, 0x7b, 0x06, 0xf7, 0xbd, 0x0f, 0x9f, 0x99, 0xbd, - 0x3a, 0xab, 0x81, 0x3d, 0xc8, 0x58, 0x4d, 0xbd, 0x47, 0xdc, 0x41, 0xbe, - 0xcc, 0x1a, 0xd5, 0xbd, 0x5d, 0xd0, 0xbf, 0xbc, 0x4f, 0xb5, 0xdd, 0xbc, - 0xba, 0x42, 0x7c, 0x3d, 0x82, 0x2a, 0x5d, 0xbe, 0x69, 0xcc, 0x0c, 0x3d, - 0x03, 0x0e, 0x1d, 0xbc, 0x08, 0x62, 0x26, 0xbe, 0xcf, 0x3a, 0x69, 0x3d, - 0x0f, 0x37, 0x0c, 0x3b, 0x1f, 0xd6, 0x00, 0xbe, 0x8b, 0x1b, 0xfb, 0x3b, - 0xd7, 0x48, 0x0a, 0xbe, 0xe8, 0xed, 0xf2, 0xbd, 0xb5, 0x73, 0x82, 0xbe, - 0x72, 0x26, 0xc8, 0xbc, 0xe3, 0xa4, 0x08, 0x3d, 0x49, 0xbd, 0x79, 0x3d, - 0xcc, 0xaf, 0x68, 0xbd, 0x4a, 0x79, 0xce, 0xbd, 0x1c, 0x2f, 0xe3, 0xbd, - 0xf5, 0x22, 0x5d, 0x3c, 0x03, 0xfd, 0x16, 0xbe, 0x90, 0x73, 0x9e, 0xbb, - 0xaa, 0x00, 0xd2, 0xbd, 0x5d, 0x67, 0x0e, 0xbd, 0x13, 0x75, 0xa6, 0xbc, - 0x77, 0x8a, 0x5d, 0xbd, 0x2f, 0xb0, 0x14, 0xbe, 0xca, 0xd1, 0x82, 0xbd, - 0x62, 0x82, 0xf5, 0xbd, 0x1a, 0x49, 0x38, 0xbc, 0x3f, 0xcd, 0xa8, 0x3c, - 0x25, 0x82, 0xdf, 0xbd, 0xef, 0x74, 0x2a, 0xbd, 0x0d, 0x65, 0x45, 0xbe, - 0xe2, 0x98, 0x70, 0xbe, 0x19, 0xe7, 0xf9, 0xbd, 0x14, 0xc2, 0xa7, 0xbc, - 0x0c, 0x41, 0x88, 0x3d, 0x11, 0xdc, 0x93, 0xbd, 0x7b, 0x88, 0xa7, 0x3c, - 0xad, 0x5a, 0xad, 0xbd, 0xe0, 0x30, 0x3f, 0xbd, 0x3d, 0x38, 0xbf, 0xbd, - 0x53, 0x11, 0x87, 0x3d, 0x09, 0x50, 0x71, 0xbc, 0xbb, 0x0a, 0x2f, 0x3c, - 0x07, 0x2a, 0xa4, 0x3d, 0x93, 0x65, 0xae, 0xbb, 0xeb, 0xbd, 0x11, 0x3d, - 0x83, 0xb3, 0xd4, 0xbd, 0xca, 0x7b, 0xa9, 0xbd, 0xf3, 0x58, 0xdc, 0xbd, - 0x3c, 0x78, 0x37, 0xbe, 0xb7, 0x9c, 0x01, 0xbe, 0x0e, 0xb9, 0x2d, 0xbe, - 0xc1, 0x26, 0x3d, 0x3c, 0xb6, 0x82, 0x56, 0x3c, 0xfa, 0x8a, 0x83, 0xbd, - 0xe1, 0xea, 0x54, 0xbe, 0x25, 0x99, 0x5d, 0x3b, 0x35, 0xd7, 0x81, 0x3c, - 0x47, 0x23, 0x57, 0xbe, 0xae, 0x9f, 0x12, 0xbe, 0x70, 0x8a, 0x90, 0xbd, - 0x8c, 0x8c, 0x90, 0x3d, 0x8a, 0xab, 0x08, 0xbc, 0x6d, 0xe6, 0xff, 0xbc, - 0x49, 0xd6, 0x4b, 0xbe, 0xce, 0x0a, 0x03, 0xbe, 0xec, 0x92, 0x55, 0xbd, - 0x53, 0xd9, 0x58, 0x3c, 0xda, 0x71, 0x4a, 0x3d, 0x83, 0x8e, 0xd0, 0xbc, - 0x4c, 0xa3, 0xb9, 0xbd, 0x64, 0x98, 0xbe, 0xbe, 0x7b, 0x90, 0x41, 0x3c, - 0x35, 0xd0, 0x07, 0xbe, 0x25, 0x75, 0x17, 0xbf, 0xf0, 0xbc, 0x59, 0xbd, - 0x8c, 0xd5, 0x09, 0xbe, 0x31, 0xed, 0xac, 0x3e, 0x0d, 0xae, 0x37, 0xbe, - 0xe0, 0x63, 0xc1, 0x3c, 0xbc, 0x5f, 0x3b, 0xbe, 0x7d, 0x19, 0x1f, 0xbf, - 0xba, 0x8d, 0xa0, 0xbd, 0xee, 0x0e, 0x4a, 0xbd, 0xf2, 0x50, 0xab, 0x3e, - 0x1f, 0x9d, 0x39, 0x3e, 0x00, 0x99, 0x1b, 0xbd, 0xfb, 0x90, 0x9c, 0xbe, - 0xf2, 0xff, 0x0e, 0xbe, 0x0a, 0xde, 0x28, 0x3d, 0x11, 0xf9, 0xb4, 0xbd, - 0x78, 0x38, 0x80, 0xbd, 0xc8, 0x49, 0x02, 0xbe, 0xe0, 0xb6, 0x2d, 0x3e, - 0xa1, 0x09, 0x93, 0xbd, 0xeb, 0xce, 0x0c, 0xbf, 0xe5, 0xc3, 0x63, 0xbe, - 0xfa, 0x55, 0x81, 0x3d, 0x70, 0x7c, 0x98, 0xbe, 0x18, 0xb3, 0xaa, 0xbe, - 0xa2, 0xab, 0x32, 0xbd, 0x36, 0x7c, 0x4e, 0x3e, 0xb8, 0x9e, 0x29, 0x3d, - 0xf9, 0xd3, 0xdc, 0xbd, 0x2e, 0x1b, 0xac, 0xbe, 0x87, 0x2c, 0xa5, 0xbc, - 0xfe, 0x34, 0xac, 0x3d, 0x67, 0x24, 0xc9, 0xbd, 0xdd, 0x28, 0x07, 0xbe, - 0x9b, 0x67, 0x79, 0x3e, 0x34, 0xc0, 0x6e, 0xbe, 0x16, 0x73, 0x33, 0xbf, - 0xcf, 0x8b, 0x19, 0xbd, 0xfa, 0xcb, 0x8f, 0x3d, 0x2b, 0x37, 0x99, 0xbe, - 0x78, 0x90, 0xbb, 0xbd, 0x22, 0xb2, 0xc3, 0xbe, 0xeb, 0xd9, 0x6e, 0xbd, - 0xc0, 0x7b, 0xab, 0xbb, 0x8b, 0x70, 0x04, 0x3e, 0x8c, 0x9a, 0xda, 0x3c, - 0xc0, 0x13, 0x4d, 0xbe, 0x34, 0xf5, 0xb3, 0x3d, 0xbe, 0x31, 0x2d, 0xbe, - 0xda, 0xf7, 0x93, 0xbd, 0x60, 0xfb, 0x8b, 0x3e, 0x0e, 0xae, 0x21, 0xbc, - 0x8e, 0x7d, 0xb7, 0xbe, 0xa2, 0x37, 0xe7, 0x3d, 0x16, 0x8a, 0x2e, 0xbe, - 0xa2, 0xa9, 0x32, 0x3d, 0x23, 0x15, 0x81, 0x3e, 0xb9, 0x81, 0x28, 0x3e, - 0xc3, 0xc1, 0x52, 0x3b, 0xa4, 0xe2, 0x00, 0x3d, 0xea, 0xa0, 0x0a, 0x3e, - 0xee, 0x8c, 0xd8, 0x3d, 0x95, 0xce, 0x09, 0xbe, 0x58, 0xf5, 0x39, 0xbe, - 0x0c, 0xb5, 0x3c, 0x3e, 0x6a, 0x34, 0x3c, 0xbe, 0xdf, 0x9d, 0x54, 0x3e, - 0x2a, 0xfb, 0xe8, 0x3d, 0x80, 0x51, 0x47, 0xbe, 0x6e, 0xe5, 0xb8, 0x3d, - 0x1c, 0x48, 0x07, 0x3e, 0xa6, 0x79, 0x26, 0x3e, 0x46, 0x02, 0x04, 0x3e, - 0xe6, 0x61, 0xf8, 0x3d, 0x5d, 0x43, 0x7c, 0x3d, 0x9f, 0xab, 0x1d, 0xbe, - 0xc3, 0x37, 0x38, 0x3e, 0x76, 0x01, 0x6d, 0xbe, 0x56, 0xc2, 0xa7, 0xbd, - 0xde, 0xa0, 0x55, 0xbd, 0xbb, 0xda, 0xd0, 0x3d, 0xa3, 0xa4, 0x38, 0xbc, - 0x4e, 0x49, 0x10, 0x3e, 0xa6, 0x31, 0xa4, 0xbd, 0x03, 0x72, 0x93, 0xbe, - 0x6f, 0x0d, 0x86, 0xbd, 0x05, 0x7f, 0xa1, 0x3e, 0xda, 0x61, 0xc2, 0x3d, - 0xe5, 0x8d, 0xb2, 0x3d, 0x4b, 0x95, 0x46, 0x3d, 0xea, 0x15, 0x20, 0xbd, - 0xaf, 0x63, 0x96, 0xbd, 0x41, 0x67, 0x5e, 0x3d, 0x1c, 0x81, 0x14, 0xbf, - 0xb4, 0xfc, 0xcf, 0xbd, 0xe0, 0x2d, 0x79, 0x3c, 0x3e, 0x06, 0x84, 0x3d, - 0x5f, 0x90, 0x85, 0x3d, 0xba, 0xb2, 0x6a, 0x3c, 0x0c, 0x53, 0x47, 0x3d, - 0x59, 0x81, 0x17, 0xbe, 0x6b, 0x35, 0x05, 0x3d, 0xed, 0xbb, 0x12, 0x3e, - 0xb1, 0x03, 0x70, 0x3d, 0x28, 0xf3, 0x19, 0x3e, 0xaa, 0x14, 0x9d, 0x3d, - 0xe5, 0x3c, 0xc7, 0xbd, 0x45, 0xe5, 0x42, 0xbd, 0xb8, 0xf0, 0x92, 0xb9, - 0xd0, 0xf3, 0xa7, 0xbe, 0xdc, 0x6a, 0x01, 0xbe, 0xeb, 0xb2, 0xfd, 0xbd, - 0x8b, 0x73, 0x61, 0x3d, 0xa5, 0x00, 0x86, 0x3d, 0x81, 0x66, 0xd5, 0x3d, - 0xfb, 0xdb, 0x27, 0x3e, 0xd1, 0xd1, 0x18, 0xbe, 0x31, 0xcc, 0x89, 0x3d, - 0x6b, 0xb9, 0x72, 0x3e, 0x1f, 0x4f, 0x62, 0x3d, 0x8b, 0x57, 0x02, 0x3e, - 0xc7, 0x56, 0x0c, 0xbd, 0x21, 0x4a, 0x74, 0xbe, 0xcf, 0x0b, 0x6c, 0xbd, - 0xd3, 0xc8, 0x95, 0x3d, 0xf8, 0xaf, 0x35, 0xbe, 0xae, 0xe1, 0xb4, 0xbd, - 0x49, 0x6e, 0x66, 0xbd, 0x37, 0x2f, 0xc2, 0x3d, 0x39, 0xc8, 0xe0, 0x3b, - 0xfe, 0x09, 0x94, 0xbd, 0xfc, 0x43, 0x4b, 0x3d, 0xec, 0x5a, 0x66, 0xbe, - 0xbb, 0x44, 0x52, 0x3c, 0xa6, 0x28, 0x88, 0x3e, 0x26, 0x58, 0x35, 0x3e, - 0xd4, 0x3f, 0xf6, 0x3d, 0x66, 0x6c, 0x6b, 0xbd, 0x2b, 0x07, 0x8b, 0xbd, - 0xea, 0x1e, 0x0d, 0x3d, 0x81, 0x04, 0x08, 0x3e, 0x83, 0x25, 0x6c, 0xbe, - 0xb2, 0xdd, 0xf6, 0xbc, 0x2e, 0x25, 0x17, 0xbd, 0x85, 0xfc, 0xc2, 0x3d, - 0xc2, 0x55, 0x74, 0x3d, 0x6a, 0x9d, 0x34, 0xbd, 0x47, 0x35, 0xf9, 0x3d, - 0x1f, 0xbd, 0x76, 0xbe, 0xfe, 0x3e, 0x76, 0x3d, 0x4c, 0x4a, 0x56, 0x3e, - 0x34, 0x8f, 0xb9, 0x3d, 0xa6, 0xf1, 0x35, 0x3e, 0x19, 0xe1, 0xe6, 0xbc, - 0xa4, 0x8d, 0xb1, 0xbd, 0x07, 0xca, 0x57, 0x3b, 0x8b, 0xef, 0x06, 0x3d, - 0x7d, 0x4b, 0x5b, 0xbe, 0xcf, 0xc6, 0xab, 0xbd, 0x96, 0xb9, 0xac, 0x3c, - 0xe1, 0x6e, 0xc7, 0x3d, 0xd5, 0x7e, 0x10, 0xbd, 0x21, 0x0e, 0xfe, 0xbc, - 0x09, 0xb9, 0x60, 0xbd, 0xfd, 0xd5, 0x42, 0xbe, 0x28, 0xa1, 0x1e, 0x3d, - 0xfc, 0x72, 0x48, 0x3e, 0x5e, 0xca, 0xf5, 0x3d, 0x33, 0x7a, 0xd1, 0x3d, - 0x50, 0x29, 0xc4, 0x3b, 0x05, 0x59, 0xed, 0xbc, 0xa5, 0xf1, 0x84, 0xbd, - 0x12, 0x18, 0xa6, 0x3d, 0x62, 0xd5, 0x47, 0xbe, 0xef, 0x48, 0xb1, 0x3c, - 0x26, 0x74, 0x40, 0x39, 0xbe, 0x73, 0x3e, 0x3d, 0x3e, 0xaa, 0x8a, 0xbc, - 0x50, 0x94, 0x83, 0xbe, 0x48, 0x6c, 0xa4, 0x3d, 0xf3, 0x42, 0x35, 0xbe, - 0x64, 0xd1, 0xd8, 0x3c, 0xb3, 0x43, 0x66, 0x3e, 0x1f, 0xd1, 0xde, 0x3d, - 0xd6, 0x0a, 0x02, 0x3e, 0x6e, 0x7f, 0xf0, 0xbd, 0xb7, 0xc5, 0x8f, 0x3d, - 0x64, 0x71, 0xb0, 0xbd, 0x89, 0xc7, 0xb4, 0x3d, 0xd7, 0x54, 0xa7, 0xbd, - 0x68, 0xca, 0x11, 0xbd, 0x17, 0xa1, 0x1f, 0xbe, 0xc8, 0xb8, 0x20, 0x3e, - 0x01, 0x2f, 0xd6, 0x3c, 0x47, 0xd6, 0x44, 0xbe, 0x85, 0x0f, 0x88, 0x3a, - 0x81, 0xb9, 0x06, 0xbe, 0xd0, 0x5c, 0x37, 0xbe, 0xe0, 0xa5, 0x40, 0x3e, - 0xc0, 0x6d, 0xff, 0x3d, 0xce, 0x49, 0x9f, 0x3e, 0x3c, 0xf0, 0x5b, 0xbe, - 0xea, 0x3e, 0x4c, 0xbe, 0x23, 0x3e, 0xaa, 0x3d, 0xeb, 0xd9, 0xd3, 0x3c, - 0xc4, 0x42, 0x19, 0xbe, 0x25, 0x3c, 0xb7, 0x3d, 0x4b, 0x31, 0xe2, 0xbe, - 0xcb, 0xe8, 0x79, 0xbe, 0x66, 0xab, 0x79, 0xbd, 0xb3, 0xd9, 0xb9, 0xbd, - 0xf8, 0x6d, 0x6b, 0x3d, 0x0e, 0x54, 0xe6, 0xbb, 0x42, 0x0a, 0xbd, 0xbe, - 0x2c, 0xbb, 0x16, 0x3e, 0x04, 0xec, 0x7c, 0xbe, 0x89, 0x5b, 0x38, 0x3e, - 0xc3, 0x0a, 0x06, 0xbe, 0x0b, 0xd8, 0x53, 0xbc, 0x0e, 0xb3, 0xa4, 0x3d, - 0x73, 0x1f, 0x43, 0x3d, 0x86, 0xcc, 0xee, 0xbd, 0x7d, 0x44, 0x21, 0x3e, - 0x50, 0xf0, 0x3a, 0xbe, 0x28, 0xab, 0x25, 0xbe, 0x26, 0x80, 0x13, 0x3d, - 0xc8, 0xf3, 0x5d, 0xbe, 0x13, 0xcc, 0x35, 0x3d, 0x8d, 0xfb, 0xc4, 0xbc, - 0xe1, 0x74, 0xfd, 0xbd, 0x74, 0x3d, 0x45, 0xbe, 0x1f, 0xe9, 0x89, 0x3d, - 0x37, 0x6b, 0x87, 0xbc, 0x38, 0xc1, 0xa5, 0xbe, 0x8d, 0x87, 0x96, 0x3d, - 0xd7, 0x71, 0x4b, 0x3d, 0xc0, 0x06, 0xaf, 0xbc, 0xc1, 0x01, 0x82, 0xbc, - 0xc6, 0x74, 0x25, 0x3e, 0xd4, 0x7f, 0xdc, 0xbe, 0xc7, 0x90, 0x58, 0xbe, - 0xfd, 0x6e, 0x44, 0x3d, 0x1f, 0x60, 0xa1, 0xbe, 0x5a, 0x32, 0x0b, 0x3e, - 0x13, 0xed, 0xe6, 0xbe, 0x03, 0x2b, 0x70, 0xbe, 0xcf, 0xb2, 0xf2, 0xbe, - 0x4e, 0x04, 0x81, 0xbd, 0xd4, 0x71, 0xa0, 0xbe, 0x72, 0xef, 0x2c, 0xbe, - 0x4b, 0xaa, 0xa8, 0xbd, 0xfe, 0xe6, 0xee, 0x3d, 0x6f, 0xf3, 0xf1, 0xbd, - 0x50, 0x70, 0xd7, 0x3d, 0x51, 0xed, 0xb3, 0x3d, 0xc2, 0xf7, 0xed, 0xbd, - 0x70, 0x9f, 0x2e, 0xbe, 0xf7, 0xaf, 0x02, 0x3e, 0x46, 0x28, 0x8c, 0xbe, - 0xec, 0x68, 0x00, 0xbe, 0xf3, 0x92, 0x1c, 0xbf, 0x33, 0x34, 0x78, 0xbe, - 0x8b, 0xf4, 0x45, 0xbf, 0x25, 0xc3, 0xb6, 0xbd, 0x36, 0xa3, 0xc4, 0xbe, - 0x72, 0xc4, 0xd5, 0xbd, 0xd6, 0x1e, 0xc9, 0x3d, 0x95, 0xca, 0x45, 0x3d, - 0x07, 0x09, 0xc5, 0xbd, 0xef, 0x4a, 0x1b, 0xbd, 0x3b, 0xf1, 0x07, 0xbe, - 0x07, 0x95, 0x93, 0xba, 0x89, 0xf0, 0x71, 0xbc, 0x73, 0x86, 0x3f, 0x3e, - 0xba, 0xcf, 0x30, 0x3c, 0xf2, 0x57, 0x64, 0xbe, 0x56, 0x43, 0xd7, 0xbb, - 0xc0, 0xf1, 0xa6, 0xbc, 0xa5, 0x1d, 0xca, 0xbd, 0xea, 0xf1, 0x78, 0xbd, - 0x72, 0x47, 0x7e, 0xbd, 0x30, 0xb4, 0x09, 0x3e, 0xfc, 0x3b, 0x01, 0x3c, - 0x13, 0x7a, 0x71, 0x3d, 0x58, 0x1e, 0xe7, 0xbd, 0xd0, 0x79, 0xc7, 0x3d, - 0x13, 0xf4, 0x32, 0xbd, 0xf0, 0x78, 0x36, 0xbd, 0xe4, 0x65, 0x0a, 0x3d, - 0xf4, 0xd3, 0x3e, 0x3c, 0xe5, 0x1e, 0x7b, 0xbe, 0xb5, 0x85, 0x22, 0xbe, - 0xd3, 0xd8, 0xc8, 0x3d, 0x50, 0x94, 0x88, 0xbb, 0x90, 0xf3, 0x1c, 0x3e, - 0xa9, 0x49, 0x39, 0x3c, 0x33, 0xb0, 0x57, 0xbe, 0x75, 0x6c, 0x98, 0x3d, - 0x73, 0x93, 0x0f, 0xbe, 0x6a, 0x9f, 0x53, 0xbd, 0xa5, 0x5b, 0xee, 0x3d, - 0x13, 0xca, 0x27, 0xbd, 0x13, 0x44, 0x2f, 0x3c, 0xa8, 0x58, 0x80, 0xbd, - 0x98, 0x53, 0x9d, 0x3b, 0x1f, 0xfe, 0x89, 0x3d, 0x4e, 0x8e, 0x8e, 0xbe, - 0x53, 0x37, 0x0e, 0xbc, 0x36, 0xac, 0x4e, 0x3e, 0x6a, 0x05, 0xeb, 0xbc, - 0x9e, 0xbe, 0x9e, 0xbe, 0x31, 0x25, 0xcb, 0xbd, 0xde, 0x47, 0x3f, 0xbe, - 0x8a, 0xfc, 0x0a, 0x3d, 0xd3, 0x47, 0xd9, 0x3d, 0x24, 0x83, 0x3c, 0xbd, - 0xfb, 0x97, 0x99, 0xbc, 0x3f, 0xa7, 0x3b, 0x3e, 0xcc, 0x40, 0xdc, 0xbc, - 0xdf, 0xab, 0x69, 0xbd, 0xee, 0x8a, 0x1e, 0xbc, 0xd9, 0x24, 0x53, 0x3d, - 0x1c, 0xc1, 0x2a, 0xbd, 0xee, 0xf3, 0x73, 0x3d, 0xb2, 0x7e, 0x86, 0x3d, - 0xe7, 0x74, 0xd1, 0xbd, 0x0e, 0x57, 0x87, 0xbe, 0xa0, 0x85, 0xf7, 0xbd, - 0xea, 0xc0, 0x93, 0xbe, 0x33, 0xa2, 0x83, 0x3d, 0xdf, 0x5c, 0x00, 0x3d, - 0x0d, 0xa4, 0x4c, 0x3d, 0x86, 0x7e, 0x11, 0xbd, 0x3b, 0x90, 0x2f, 0xbb, - 0x89, 0x6c, 0x52, 0xbd, 0xd8, 0x3a, 0x0c, 0xbc, 0xec, 0xd4, 0xb9, 0xbd, - 0x24, 0xa1, 0xb8, 0x3d, 0x8a, 0x98, 0x5b, 0xbd, 0xdb, 0x89, 0xdb, 0x3c, - 0x26, 0x16, 0xe9, 0x3d, 0x69, 0x44, 0x3b, 0xbd, 0x3a, 0x36, 0xb5, 0xbd, - 0x44, 0xc1, 0x86, 0xbd, 0x08, 0x18, 0xec, 0xbd, 0x07, 0x39, 0x85, 0x3d, - 0xc5, 0x3e, 0xcc, 0x3d, 0x49, 0xc9, 0xa7, 0xbc, 0xe9, 0x9a, 0x23, 0x3d, - 0xc8, 0xe6, 0xc6, 0xbd, 0xbd, 0x50, 0xb0, 0xba, 0x55, 0x49, 0xd1, 0xbc, - 0xb2, 0x93, 0xb5, 0xbc, 0x23, 0x5b, 0x29, 0x3d, 0x6a, 0x3d, 0x11, 0xbe, - 0xe9, 0xd6, 0xba, 0x3d, 0x78, 0x03, 0x94, 0x3d, 0x1c, 0x78, 0x8e, 0xbd, - 0x03, 0xc4, 0x87, 0xbc, 0x22, 0x61, 0xc4, 0xbd, 0xab, 0x41, 0x04, 0xbe, - 0x85, 0xb2, 0x18, 0x3d, 0x22, 0xc3, 0x40, 0x3d, 0x23, 0x28, 0x20, 0x3d, - 0xc9, 0x6c, 0xcd, 0x3c, 0x53, 0xe0, 0x49, 0x3d, 0xf5, 0x46, 0xc9, 0xbc, - 0x4e, 0xa0, 0x6e, 0x3c, 0x38, 0x20, 0x51, 0x3c, 0xd2, 0x33, 0xa2, 0x3c, - 0xf3, 0x9a, 0x00, 0xbe, 0x1f, 0x82, 0x90, 0x3d, 0xfa, 0x40, 0x23, 0x3e, - 0xfe, 0xbe, 0xa2, 0xbb, 0x18, 0xb3, 0x00, 0xbe, 0x3a, 0x7a, 0x2c, 0xbd, - 0x49, 0x85, 0x02, 0xbe, 0xe8, 0x7d, 0x9b, 0x3c, 0x3e, 0x7f, 0xb4, 0xbc, - 0xee, 0x5f, 0xeb, 0x3d, 0x2c, 0x4f, 0x91, 0x3d, 0x5b, 0xbd, 0x08, 0x3d, - 0x66, 0xb9, 0x0b, 0x3d, 0x1f, 0xbc, 0xce, 0x3c, 0x8e, 0x47, 0x80, 0xbc, - 0x87, 0xd3, 0x30, 0x3c, 0x64, 0x2c, 0x95, 0x3d, 0xe4, 0x42, 0x0d, 0x3e, - 0x5a, 0x05, 0x39, 0x3d, 0xa2, 0x5e, 0x48, 0x3d, 0x9c, 0x87, 0xd0, 0xbd, - 0x93, 0x76, 0xc3, 0xbb, 0x8c, 0xcd, 0xda, 0xbc, 0x6e, 0x25, 0x0a, 0xbd, - 0xff, 0x03, 0x9a, 0xbd, 0x40, 0x67, 0xd9, 0xbd, 0xbf, 0x7e, 0xd2, 0x3c, - 0x35, 0x27, 0xb2, 0x3c, 0x09, 0x81, 0x48, 0xbc, 0x80, 0x43, 0xab, 0x3c, - 0xcc, 0xff, 0xd2, 0xbc, 0xf8, 0x70, 0x20, 0xbe, 0xd9, 0x99, 0x2d, 0x3e, - 0x75, 0x9e, 0x77, 0x3e, 0x85, 0x2c, 0x67, 0x3d, 0xbb, 0xcd, 0x75, 0x3d, - 0xde, 0x1f, 0x07, 0xbe, 0x35, 0x92, 0x7b, 0xbc, 0x4e, 0x22, 0x4f, 0x3c, - 0xc0, 0x9f, 0x58, 0xbd, 0x78, 0x05, 0x0d, 0xbe, 0x5c, 0x89, 0x8f, 0xbf, - 0x2e, 0x4e, 0xdb, 0xbd, 0xab, 0x6a, 0xe5, 0x3d, 0x2f, 0x9f, 0x07, 0xbe, - 0x7d, 0xf6, 0x56, 0xbd, 0xb9, 0xa5, 0x69, 0xbe, 0x5d, 0xbc, 0x2f, 0xbe, - 0xb5, 0x20, 0x72, 0x3e, 0x45, 0xfc, 0x8a, 0x3e, 0x68, 0x35, 0x55, 0x3d, - 0xd4, 0x4c, 0x38, 0x3e, 0x95, 0x44, 0x92, 0xbd, 0xc9, 0xe9, 0x0b, 0xbd, - 0x3f, 0xdc, 0x1a, 0xbe, 0x0c, 0xc1, 0x54, 0x3e, 0xfb, 0xb2, 0xfd, 0xbe, - 0xe6, 0xa0, 0xba, 0xbf, 0x21, 0x2f, 0x07, 0xbe, 0xb1, 0x51, 0x86, 0x3e, - 0xb7, 0x31, 0x12, 0xbe, 0x4d, 0x5b, 0x0a, 0xbf, 0xb6, 0xec, 0xd9, 0x3d, - 0x89, 0x5b, 0x19, 0xbf, 0x1a, 0x70, 0x3f, 0x3f, 0x65, 0x22, 0xc9, 0xbe, - 0x87, 0x1c, 0x58, 0x3b, 0x1b, 0x6d, 0xa2, 0x3e, 0xf6, 0x2f, 0xc0, 0xbd, - 0x06, 0x5f, 0x0e, 0x3e, 0xf0, 0x16, 0x54, 0x3e, 0xd0, 0x79, 0x31, 0x3e, - 0xb9, 0x68, 0x6a, 0x3e, 0x26, 0x84, 0x24, 0x3d, 0x4b, 0x40, 0x5b, 0xbd, - 0xa9, 0xa8, 0x2e, 0x3e, 0x5b, 0x65, 0x43, 0x3e, 0x86, 0x43, 0x82, 0x3e, - 0x7e, 0x3c, 0x41, 0x3e, 0x29, 0x09, 0x62, 0x3c, 0x29, 0xcb, 0xb6, 0x3a, - 0xd8, 0xf4, 0x99, 0x3e, 0x27, 0x0a, 0x06, 0x3e, 0x10, 0x0e, 0xae, 0x3e, - 0x78, 0x10, 0x95, 0x3e, 0x27, 0x75, 0x31, 0x3e, 0x66, 0x54, 0xc5, 0x3d, - 0x36, 0xc8, 0x62, 0x3e, 0x34, 0xd6, 0xbc, 0x3e, 0x02, 0x85, 0x0a, 0x3d, - 0x32, 0x12, 0x23, 0xbd, 0x60, 0x5b, 0x1d, 0xbe, 0xeb, 0x9f, 0xda, 0x3d, - 0x3e, 0x15, 0x39, 0xbd, 0x61, 0x1c, 0xfc, 0xbd, 0x0e, 0x70, 0x8a, 0x3d, - 0x5b, 0x53, 0x81, 0xbc, 0x79, 0xb3, 0xce, 0x3e, 0x8e, 0xd0, 0x00, 0x3d, - 0xa8, 0x53, 0x3a, 0x3e, 0xb0, 0x90, 0x8a, 0xbc, 0x44, 0x48, 0xd7, 0xbd, - 0x83, 0xc7, 0x98, 0xbd, 0xa2, 0x56, 0xfa, 0xbd, 0x04, 0xe7, 0x84, 0x3e, - 0x1f, 0xe2, 0x09, 0x3d, 0xaf, 0xf4, 0xa4, 0xbd, 0x73, 0x9d, 0x90, 0xbe, - 0xfb, 0xc1, 0x05, 0x3e, 0x00, 0xbe, 0xe8, 0xbd, 0xa2, 0x67, 0x8b, 0xbe, - 0xab, 0xd4, 0xb3, 0x3d, 0x9d, 0x70, 0x8e, 0x3d, 0x99, 0xb3, 0xb7, 0x3d, - 0x4b, 0x5b, 0x98, 0xbd, 0x12, 0xa0, 0x94, 0x3d, 0xcb, 0xbb, 0x86, 0xbd, - 0xfb, 0x28, 0x2c, 0xbd, 0xc9, 0xb2, 0x5a, 0xbe, 0x89, 0x8e, 0x28, 0xbe, - 0xdf, 0x97, 0xad, 0x3d, 0x2e, 0x58, 0x55, 0x3c, 0xf4, 0xee, 0xf4, 0xbc, - 0x2d, 0x3f, 0x97, 0xbe, 0x47, 0x80, 0xbf, 0x3d, 0x70, 0x4f, 0x19, 0xbd, - 0x71, 0xec, 0x22, 0xbe, 0x2d, 0x78, 0xca, 0x3c, 0x8c, 0x65, 0xea, 0xbc, - 0x4c, 0xdb, 0x5b, 0xbc, 0x81, 0x89, 0x85, 0xbd, 0x5a, 0xb0, 0x7d, 0x3d, - 0x94, 0x9b, 0x42, 0xbe, 0xbe, 0x35, 0x3f, 0x3d, 0xd5, 0xa1, 0x5c, 0xbe, - 0xbf, 0x73, 0xae, 0xbe, 0x3b, 0x05, 0x03, 0x3d, 0x88, 0x3e, 0x7e, 0xba, - 0x75, 0x14, 0xf4, 0xbc, 0x3b, 0x68, 0x31, 0xbe, 0x8e, 0x1c, 0xdd, 0x3d, - 0x66, 0x27, 0x54, 0xbc, 0x0c, 0x25, 0xc9, 0xbd, 0xf6, 0xd4, 0x08, 0xbb, - 0x86, 0xcb, 0x7c, 0xbd, 0x9a, 0xad, 0x04, 0xbe, 0xe7, 0x95, 0x19, 0xbe, - 0x63, 0x65, 0x7f, 0x3b, 0xed, 0x1a, 0xba, 0xbd, 0x80, 0xfc, 0xf5, 0x3c, - 0x88, 0xe4, 0x8e, 0xbe, 0x7c, 0xfe, 0x36, 0xbe, 0x5b, 0x6a, 0x2f, 0xbd, - 0x56, 0xa0, 0xff, 0x3c, 0xff, 0x6e, 0x8d, 0x3c, 0x60, 0xd1, 0xb6, 0xbe, - 0x4b, 0xef, 0x09, 0x3e, 0x96, 0x01, 0x14, 0xbc, 0x84, 0xd1, 0x81, 0xbe, - 0xfc, 0x3e, 0x66, 0x3d, 0x09, 0x9a, 0xa5, 0x3d, 0xa7, 0x66, 0x31, 0xbe, - 0xc3, 0x77, 0x88, 0xbe, 0xd8, 0x9f, 0x26, 0x3d, 0xed, 0xd5, 0x49, 0xbe, - 0xb2, 0xfa, 0x02, 0xbe, 0x2d, 0x02, 0x7b, 0xbe, 0xbd, 0xbe, 0x6b, 0xbe, - 0xfa, 0x78, 0x0e, 0x3e, 0x77, 0xae, 0x0f, 0x3c, 0x56, 0xaf, 0xa0, 0xbd, - 0x6a, 0xe5, 0xd3, 0xbe, 0xa1, 0x98, 0xf0, 0x3d, 0xb6, 0x0c, 0xc1, 0xbd, - 0xd2, 0xf0, 0x7d, 0xbe, 0x14, 0xdb, 0x61, 0xbc, 0xf8, 0x15, 0x23, 0xbb, - 0xb0, 0x61, 0xc7, 0xbb, 0xf8, 0x55, 0xf6, 0xbd, 0x7d, 0x14, 0x9b, 0x3c, - 0xf0, 0x22, 0x1c, 0xbe, 0x3b, 0x91, 0xa3, 0xbd, 0xca, 0xcc, 0x68, 0xbe, - 0x59, 0x2e, 0x93, 0xbe, 0x92, 0xd0, 0x67, 0xbc, 0x73, 0x3d, 0x73, 0x3b, - 0xc5, 0xdb, 0x0c, 0xbe, 0xc1, 0x62, 0x6e, 0xbe, 0x79, 0xaf, 0x30, 0x3e, - 0xa6, 0x17, 0x03, 0xbe, 0x43, 0x18, 0x50, 0xbe, 0x1f, 0x1f, 0x5c, 0xbc, - 0x0a, 0x2b, 0x55, 0x3e, 0x64, 0xa9, 0x88, 0x3c, 0xc8, 0x17, 0x00, 0xbe, - 0x58, 0x18, 0x9c, 0xbc, 0x72, 0x50, 0x62, 0xbe, 0xf6, 0x19, 0xa7, 0xbd, - 0x9c, 0x40, 0x82, 0xbe, 0xf1, 0x3b, 0xf7, 0xbd, 0xbd, 0x82, 0xb2, 0x3d, - 0x8a, 0x07, 0xbd, 0xba, 0x32, 0x14, 0xa1, 0xbd, 0x85, 0x2a, 0x7b, 0xbe, - 0x1f, 0xae, 0x06, 0x3d, 0xf1, 0xdc, 0x19, 0xbe, 0xde, 0x44, 0x72, 0xbe, - 0x7d, 0x76, 0x1e, 0xbd, 0x5a, 0x2b, 0xa6, 0xbc, 0x2e, 0x67, 0x15, 0x3e, - 0x26, 0x55, 0xc0, 0xbe, 0x5d, 0x7f, 0xc9, 0xbd, 0x2b, 0xc8, 0xfd, 0xbd, - 0xc0, 0x93, 0x16, 0xbe, 0xe2, 0x34, 0x6e, 0xbe, 0xec, 0x15, 0xa5, 0xbe, - 0x9f, 0xdb, 0xf5, 0xbd, 0x1a, 0x96, 0xac, 0xbc, 0x7d, 0x85, 0x06, 0xbd, - 0x92, 0x4d, 0xc8, 0xbd, 0xd5, 0xa0, 0xc5, 0x3d, 0xd7, 0xe0, 0x12, 0xbd, - 0x71, 0xf6, 0x63, 0xbe, 0x38, 0xa0, 0x58, 0x3c, 0x28, 0x7d, 0xf0, 0xbc, - 0x5d, 0x6c, 0x14, 0xbe, 0x4d, 0xc8, 0x88, 0xbe, 0xb2, 0x45, 0xbb, 0xbd, - 0x6f, 0x69, 0x25, 0xbd, 0x5c, 0x1f, 0xc3, 0xbb, 0x73, 0xdd, 0x79, 0xbd, - 0x1e, 0x61, 0x44, 0xbc, 0xbb, 0xe8, 0x69, 0xbd, 0xc9, 0x1d, 0x6f, 0x3d, - 0x90, 0xbe, 0xb2, 0xbc, 0xa6, 0x1e, 0x9e, 0x3d, 0x79, 0x90, 0x02, 0x3d, - 0x18, 0x63, 0xfb, 0xbb, 0x17, 0xd1, 0x81, 0xbe, 0xda, 0x61, 0x86, 0x3d, - 0x18, 0xd5, 0xaa, 0xbd, 0xf8, 0x62, 0x2d, 0xbe, 0x41, 0x44, 0x0b, 0xbe, - 0x1e, 0x7e, 0xa0, 0xbd, 0x58, 0x6e, 0x56, 0x3c, 0xf3, 0xd5, 0x9e, 0xbd, - 0x5e, 0x65, 0xa4, 0xbe, 0xde, 0x9d, 0x98, 0xbe, 0xf8, 0x00, 0x56, 0xbd, - 0x06, 0x03, 0x20, 0xbd, 0x5e, 0x83, 0x55, 0xbe, 0x38, 0x80, 0x88, 0xbd, - 0x8c, 0x43, 0xa1, 0xbb, 0x1a, 0x39, 0x3d, 0xbe, 0x47, 0x59, 0xd0, 0xbe, - 0x06, 0x65, 0x8a, 0x3c, 0xc3, 0xaa, 0x7c, 0x3e, 0x84, 0x06, 0x02, 0xbe, - 0xe2, 0x0e, 0xa6, 0xbe, 0xd8, 0x7c, 0x14, 0xbb, 0xf2, 0x59, 0x89, 0xbe, - 0x64, 0xba, 0xb8, 0xbd, 0x24, 0x5b, 0x37, 0xbe, 0x73, 0xa5, 0x51, 0x3c, - 0xe4, 0xa1, 0xaf, 0xbd, 0x5b, 0xca, 0x0c, 0x3c, 0xc3, 0x6c, 0x40, 0xbc, - 0x36, 0x7b, 0x0b, 0xbe, 0x63, 0x81, 0xbf, 0xbd, 0x6a, 0xb0, 0x7a, 0xbe, - 0x89, 0x23, 0x94, 0xbe, 0x6a, 0x61, 0x01, 0x3c, 0x18, 0xf9, 0x9a, 0x3e, - 0x50, 0x93, 0xb3, 0xbc, 0x6e, 0x54, 0x17, 0xbe, 0x50, 0x11, 0x35, 0x3c, - 0xe0, 0xcb, 0xe0, 0xbd, 0x08, 0xb6, 0x4b, 0xbe, 0x20, 0x8d, 0xb9, 0xbd, - 0xc6, 0x50, 0x75, 0x3d, 0x8b, 0xbc, 0x56, 0x3e, 0xf0, 0xf7, 0x99, 0x3d, - 0x4a, 0x7e, 0x22, 0x3d, 0x5f, 0x76, 0xf8, 0x3d, 0x48, 0xc6, 0xee, 0xbc, - 0xc2, 0x94, 0x3b, 0xbd, 0x43, 0x55, 0xbb, 0x3b, 0x9c, 0xa3, 0xff, 0x3d, - 0xa4, 0x8b, 0x4e, 0x3e, 0x64, 0xb5, 0x44, 0x3d, 0x40, 0x25, 0xdf, 0x3c, - 0x92, 0x95, 0x58, 0x3e, 0xcc, 0x0e, 0xd6, 0xbd, 0x3b, 0xf3, 0xc4, 0xbc, - 0x27, 0x0a, 0x80, 0xbd, 0x0c, 0xf8, 0xff, 0xff, 0x56, 0xf8, 0xff, 0xff, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x66, 0x07, 0xd7, 0x3c, - 0x02, 0xfe, 0x8c, 0x3e, 0x2a, 0xbf, 0x8d, 0x3e, 0xa5, 0x46, 0xe6, 0xbe, - 0x9d, 0x8d, 0xaa, 0x3d, 0xd4, 0xcb, 0xe7, 0x3e, 0x93, 0xd1, 0xac, 0xbd, - 0xcc, 0xd5, 0x6c, 0xbe, 0xf7, 0xe0, 0xa1, 0xbe, 0x32, 0x17, 0x42, 0x3e, - 0x8f, 0x35, 0xf4, 0xbc, 0x00, 0xce, 0xe9, 0x3e, 0x3e, 0xe5, 0xec, 0x3e, - 0x79, 0x01, 0xcd, 0xbc, 0xae, 0x90, 0xc2, 0x3e, 0x49, 0x47, 0x88, 0x3c, - 0x58, 0x93, 0x41, 0x3d, 0xac, 0xfa, 0x8f, 0x3e, 0xf7, 0x43, 0xa8, 0x3e, - 0x59, 0x81, 0x8c, 0xbc, 0x94, 0xa7, 0x36, 0x3d, 0xe2, 0x26, 0x05, 0x3f, - 0x4b, 0x77, 0xa8, 0xbd, 0x23, 0x20, 0x9a, 0xbe, 0x19, 0x48, 0x02, 0x3f, - 0x0a, 0xfc, 0x78, 0x3e, 0x0b, 0x86, 0xa2, 0xbc, 0x0f, 0x60, 0xdb, 0x3e, - 0x5f, 0xc4, 0xd5, 0xbe, 0x7d, 0xef, 0x92, 0xbd, 0x9c, 0x52, 0xc1, 0x3e, - 0xc5, 0x54, 0x14, 0x3e, 0x79, 0x20, 0xf2, 0x3d, 0xa8, 0x96, 0x8c, 0x3e, - 0xcf, 0x38, 0x9c, 0x3e, 0x82, 0x48, 0xd0, 0xbe, 0x7a, 0x33, 0xc2, 0x3d, - 0xb1, 0xb3, 0xf8, 0x3e, 0xa4, 0x10, 0x95, 0xbd, 0xbb, 0x13, 0x0d, 0xbf, - 0x41, 0x52, 0x6c, 0x3e, 0x42, 0x9d, 0x49, 0x3e, 0xd1, 0xda, 0xfe, 0x3c, - 0xe9, 0x25, 0xcc, 0x3e, 0xe0, 0x67, 0xa4, 0xbe, 0x36, 0x77, 0xa3, 0xbd, - 0x78, 0x22, 0xe9, 0x3e, 0xaa, 0xfa, 0x74, 0x3e, 0x25, 0xf0, 0x63, 0x3d, - 0x8b, 0x69, 0xa4, 0x3e, 0x90, 0x48, 0x80, 0x3e, 0x90, 0xf8, 0xd5, 0x3e, - 0xb2, 0x76, 0x33, 0x3b, 0xa5, 0xae, 0xf4, 0x3e, 0x7e, 0xf9, 0x38, 0xbd, - 0x2c, 0x8d, 0xed, 0xbe, 0x7d, 0xf7, 0xf8, 0xbe, 0x5c, 0xba, 0x21, 0x3e, - 0xba, 0x34, 0x5f, 0xbd, 0x30, 0x07, 0xd7, 0x3e, 0x02, 0x3c, 0xe9, 0xbe, - 0x48, 0xfe, 0x7b, 0xbd, 0xcb, 0xf7, 0xdd, 0x3e, 0x3f, 0x3a, 0xbf, 0x3e, - 0x1c, 0xf9, 0xff, 0xff, 0x66, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, - 0x40, 0x00, 0x00, 0x00, 0xa3, 0x9b, 0xdd, 0xbf, 0x6d, 0x88, 0x19, 0x3d, - 0xb5, 0xbf, 0x10, 0xbf, 0x1b, 0x6c, 0xb3, 0xbf, 0x84, 0xae, 0x12, 0xbf, - 0x41, 0xd6, 0xc3, 0x3f, 0xd6, 0x2c, 0x46, 0xc0, 0x6f, 0xd0, 0x51, 0xc0, - 0x60, 0x43, 0x85, 0x3f, 0xd6, 0x91, 0xf8, 0x3e, 0x17, 0xda, 0xdc, 0xbe, - 0x2d, 0x59, 0xbf, 0xbf, 0x76, 0x42, 0x0a, 0xbe, 0x7d, 0xb1, 0x48, 0xc0, - 0xfb, 0xf5, 0x0f, 0xc0, 0xbf, 0x84, 0x3b, 0xc0, 0xb2, 0xf9, 0xff, 0xff, - 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x32, 0xda, 0xff, 0xbd, - 0x17, 0x4e, 0xb8, 0x3d, 0x5c, 0x21, 0x9a, 0xbf, 0xdc, 0x3a, 0x10, 0x3f, - 0xce, 0xf9, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x95, 0x1a, 0xe2, 0x3f, 0xe0, 0xce, 0x21, 0x40, 0xb8, 0xf4, 0xab, 0x3f, - 0x90, 0x96, 0x43, 0xbe, 0xbe, 0x61, 0x6f, 0x3f, 0x49, 0x32, 0xca, 0x3f, - 0x6c, 0x76, 0x13, 0x40, 0x03, 0x2a, 0x5a, 0xbe, 0x54, 0x18, 0x13, 0xbe, - 0x3e, 0x50, 0x3b, 0x3f, 0x35, 0x52, 0x5d, 0x3f, 0x79, 0x40, 0xe0, 0xbe, - 0xe1, 0xed, 0x47, 0xbe, 0x31, 0xa0, 0x5c, 0xbf, 0x02, 0x2a, 0x0c, 0x40, - 0x54, 0x74, 0x2d, 0x40, 0xd4, 0xf9, 0xff, 0xff, 0x0c, 0x00, 0x14, 0x00, - 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x74, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x3c, 0x04, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00, 0x28, 0x01, 0x00, 0x00, - 0xe0, 0x02, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x78, 0x02, 0x00, 0x00, - 0x58, 0x04, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x14, 0x03, 0x00, 0x00, - 0xf0, 0x04, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x68, 0x03, 0x00, 0x00, - 0xac, 0x03, 0x00, 0x00, 0x84, 0x04, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x54, 0x01, 0x00, 0x00, 0x3a, 0xfb, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, - 0x2f, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, 0x67, - 0x32, 0x64, 0x5f, 0x31, 0x30, 0x2f, 0x4d, 0x61, 0x78, 0x50, 0x6f, 0x6f, - 0x6c, 0x00, 0x00, 0x00, 0xa0, 0xfa, 0xff, 0xff, 0x92, 0xfb, 0xff, 0xff, - 0x10, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x38, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x1b, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, - 0x31, 0x31, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0xec, 0xfa, 0xff, 0xff, - 0xde, 0xfb, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x63, 0x6f, 0x6e, - 0x76, 0x32, 0x64, 0x5f, 0x31, 0x30, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, - 0x38, 0xfb, 0xff, 0xff, 0x2a, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x0d, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, - 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, 0x31, 0x30, 0x2f, 0x43, - 0x6f, 0x6e, 0x76, 0x32, 0x44, 0x2f, 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, - 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x98, 0xfb, 0xff, 0xff, 0x8a, 0xfc, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, - 0x2f, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, 0x67, - 0x32, 0x64, 0x5f, 0x31, 0x31, 0x2f, 0x4d, 0x61, 0x78, 0x50, 0x6f, 0x6f, - 0x6c, 0x00, 0x00, 0x00, 0xf0, 0xfb, 0xff, 0xff, 0xe2, 0xfc, 0xff, 0xff, - 0x10, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x0f, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, 0x31, - 0x30, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x00, 0x08, 0x00, 0x0c, 0x00, - 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x43, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0xfd, 0xff, 0xff, - 0x10, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, - 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x64, 0x65, 0x6e, - 0x73, 0x65, 0x5f, 0x31, 0x30, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, - 0x94, 0xfc, 0xff, 0xff, 0x86, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, - 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, 0x31, 0x31, 0x2f, 0x43, - 0x6f, 0x6e, 0x76, 0x32, 0x44, 0x2f, 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, - 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x00, 0x00, 0x00, 0x00, - 0xf4, 0xfc, 0xff, 0xff, 0xe6, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, - 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, 0x31, 0x30, 0x2f, 0x43, - 0x6f, 0x6e, 0x76, 0x32, 0x44, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, - 0x3c, 0xfd, 0xff, 0xff, 0x2e, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, - 0x30, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, - 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, - 0x9c, 0xfd, 0xff, 0xff, 0x8e, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x1d, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, - 0x31, 0x2f, 0x42, 0x69, 0x61, 0x73, 0x41, 0x64, 0x64, 0x00, 0x00, 0x00, - 0xe4, 0xfd, 0xff, 0xff, 0xd6, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x35, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, - 0x31, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, - 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, - 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, - 0x44, 0xfe, 0xff, 0xff, 0x36, 0xff, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x00, 0x00, 0x00, 0x00, 0x78, 0xfe, 0xff, 0xff, 0x6a, 0xff, 0xff, 0xff, - 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x22, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, - 0x31, 0x31, 0x2f, 0x43, 0x6f, 0x6e, 0x76, 0x32, 0x44, 0x5f, 0x62, 0x69, - 0x61, 0x73, 0x00, 0x00, 0xc0, 0xfe, 0xff, 0xff, 0xb2, 0xff, 0xff, 0xff, - 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x21, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x35, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, - 0x31, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, - 0x73, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, - 0x0c, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, - 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x35, - 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, 0x30, 0x2f, 0x4d, 0x61, - 0x74, 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, - 0x64, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, - 0x90, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, - 0x8c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0x30, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x80, 0x3f, 0x70, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x08, - 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x28, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0xb0, 0xfe, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, - 0x1c, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2e, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x01, - 0xf0, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, - 0x0c, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x30, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0xa8, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x05, - 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x18, 0x00, 0x07, 0x00, - 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, 0x0e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14, 0x00, 0x1c, 0x00, - 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x07, 0x00, 0x14, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x18, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, - 0x34, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, - 0x10, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, - 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, - 0xce, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x19, 0xd6, 0xff, 0xff, 0xff, - 0x00, 0x00, 0x00, 0x09, 0xde, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, - 0xe6, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x11, 0xfa, 0xff, 0xff, 0xff, - 0x00, 0x03, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x00, 0x11, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04}; -const int g_magic_wand_model_data_len = 19600; diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc b/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc deleted file mode 100644 index 455ff997bf2..00000000000 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/Makefile.inc +++ /dev/null @@ -1,282 +0,0 @@ - -INCLUDES += \ - -I$(MAKEFILE_DIR)/downloads/kissfft - -GENERATED_PROJECT_INCLUDES += \ --I./third_party/kissfft - -PROJECT_INCLUDES += \ -third_party/kissfft - -KISSFFT_LIB_SRCS := \ -$(MAKEFILE_DIR)/downloads/kissfft/kiss_fft.c \ -$(MAKEFILE_DIR)/downloads/kissfft/tools/kiss_fftr.c - -KISSFFT_LIB_HDRS := \ -$(MAKEFILE_DIR)/downloads/kissfft/COPYING \ -$(MAKEFILE_DIR)/downloads/kissfft/kiss_fft.h \ -$(MAKEFILE_DIR)/downloads/kissfft/_kiss_fft_guts.h \ -$(MAKEFILE_DIR)/downloads/kissfft/tools/kiss_fftr.h - -$(eval $(call add_third_party_download,$(KISSFFT_URL),$(KISSFFT_MD5),kissfft,patch_kissfft)) - -THIRD_PARTY_CC_HDRS += \ -third_party/kissfft/COPYING \ -third_party/kissfft/kiss_fft.h \ -third_party/kissfft/_kiss_fft_guts.h \ -third_party/kissfft/tools/kiss_fftr.h - -MICRO_SPEECH_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc - -MICRO_SPEECH_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ - -SIMPLE_FEATURES_GENERATOR_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc - -SIMPLE_FEATURES_GENERATOR_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h - -MICRO_FEATURES_LIB_SRCS := \ -tensorflow/lite/experimental/microfrontend/lib/fft.cc \ -tensorflow/lite/experimental/microfrontend/lib/fft_util.cc \ -tensorflow/lite/experimental/microfrontend/lib/filterbank.c \ -tensorflow/lite/experimental/microfrontend/lib/filterbank_util.c \ -tensorflow/lite/experimental/microfrontend/lib/frontend.c \ -tensorflow/lite/experimental/microfrontend/lib/frontend_util.c \ -tensorflow/lite/experimental/microfrontend/lib/log_lut.c \ -tensorflow/lite/experimental/microfrontend/lib/log_scale.c \ -tensorflow/lite/experimental/microfrontend/lib/log_scale_util.c \ -tensorflow/lite/experimental/microfrontend/lib/noise_reduction.c \ -tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.c \ -tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.c \ -tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.c \ -tensorflow/lite/experimental/microfrontend/lib/window.c \ -tensorflow/lite/experimental/microfrontend/lib/window_util.c \ -$(KISSFFT_LIB_SRCS) - -MICRO_FEATURES_LIB_HDRS := \ -tensorflow/lite/experimental/microfrontend/lib/bits.h \ -tensorflow/lite/experimental/microfrontend/lib/fft.h \ -tensorflow/lite/experimental/microfrontend/lib/fft_util.h \ -tensorflow/lite/experimental/microfrontend/lib/filterbank.h \ -tensorflow/lite/experimental/microfrontend/lib/filterbank_util.h \ -tensorflow/lite/experimental/microfrontend/lib/frontend.h \ -tensorflow/lite/experimental/microfrontend/lib/frontend_util.h \ -tensorflow/lite/experimental/microfrontend/lib/log_lut.h \ -tensorflow/lite/experimental/microfrontend/lib/log_scale.h \ -tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h \ -tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h \ -tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h \ -tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h \ -tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h \ -tensorflow/lite/experimental/microfrontend/lib/window.h \ -tensorflow/lite/experimental/microfrontend/lib/window_util.h \ -$(KISSFFT_LIB_HDRS) - -MICRO_FEATURES_GENERATOR_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ -$(MICRO_FEATURES_LIB_SRCS) - -MICRO_FEATURES_GENERATOR_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h \ -$(MICRO_FEATURES_LIB_HDRS) - -MICRO_FEATURES_GENERATOR_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.cc \ -$(MICRO_FEATURES_GENERATOR_SRCS) - -MICRO_FEATURES_GENERATOR_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h \ -$(MICRO_FEATURES_GENERATOR_HDRS) - -AUDIO_PROVIDER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc - -AUDIO_PROVIDER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ - -AUDIO_PROVIDER_MOCK_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock.cc - -AUDIO_PROVIDER_MOCK_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ - -FEATURE_PROVIDER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc \ -$(MICRO_FEATURES_GENERATOR_SRCS) - -FEATURE_PROVIDER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h \ -$(MICRO_FEATURES_GENERATOR_HDRS) - -FEATURE_PROVIDER_MOCK_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc \ -$(MICRO_FEATURES_GENERATOR_SRCS) - -FEATURE_PROVIDER_MOCK_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ -$(MICRO_FEATURES_GENERATOR_HDRS) - -RECOGNIZE_COMMANDS_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc - -RECOGNIZE_COMMANDS_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h - -COMMAND_RESPONDER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/command_responder_test.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.cc - -COMMAND_RESPONDER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h - -MICRO_SPEECH_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/main.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.cc \ -$(MICRO_FEATURES_GENERATOR_SRCS) - -MICRO_SPEECH_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h \ -$(MICRO_FEATURES_GENERATOR_HDRS) - -MICRO_SPEECH_MOCK_SRCS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/main.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc \ -tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.cc \ -$(MICRO_FEATURES_GENERATOR_SRCS) - -MICRO_SPEECH_MOCK_HDRS := \ -tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h \ -tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h \ -$(MICRO_FEATURES_GENERATOR_HDRS) - -# Find any platform-specific rules for this example. -include $(wildcard tensorflow/lite/experimental/micro/examples/micro_speech/*/Makefile.inc) - -# Test the code for feature generation. -$(eval $(call microlite_test,micro_features_generator_test,\ -$(MICRO_FEATURES_GENERATOR_TEST_SRCS), $(MICRO_FEATURES_GENERATOR_TEST_HDRS))) - -# Tests loading and running a speech model. -$(eval $(call microlite_test,micro_speech_test,\ -$(MICRO_SPEECH_TEST_SRCS),$(MICRO_SPEECH_TEST_HDRS))) - -# Test the code for feature generation. -$(eval $(call microlite_test,simple_features_generator_test,\ -$(SIMPLE_FEATURES_GENERATOR_TEST_SRCS), $(SIMPLE_FEATURES_GENERATOR_TEST_HDRS))) - -# Tests the audio provider module. -$(eval $(call microlite_test,audio_provider_test,\ -$(AUDIO_PROVIDER_TEST_SRCS),$(AUDIO_PROVIDER_TEST_HDRS))) - -# Tests the audio provider mock module. -$(eval $(call microlite_test,audio_provider_mock_test,\ -$(AUDIO_PROVIDER_MOCK_TEST_SRCS),$(AUDIO_PROVIDER_MOCK_TEST_HDRS))) - -# Tests the feature provider module. -$(eval $(call microlite_test,feature_provider_test,\ -$(FEATURE_PROVIDER_TEST_SRCS),$(FEATURE_PROVIDER_TEST_HDRS))) - -# Tests the feature provider module using the mock audio provider. -$(eval $(call microlite_test,feature_provider_mock_test,\ -$(FEATURE_PROVIDER_MOCK_TEST_SRCS),$(FEATURE_PROVIDER_MOCK_TEST_HDRS))) - -# Tests the command recognizer module. -$(eval $(call microlite_test,recognize_commands_test,\ -$(RECOGNIZE_COMMANDS_TEST_SRCS),$(RECOGNIZE_COMMANDS_TEST_HDRS))) - -# Tests responding to a command. -$(eval $(call microlite_test,command_responder_test,\ -$(COMMAND_RESPONDER_TEST_SRCS),$(COMMAND_RESPONDER_TEST_HDRS))) - -# Builds a standalone speech command recognizer binary. -$(eval $(call microlite_test,micro_speech,\ -$(MICRO_SPEECH_SRCS),$(MICRO_SPEECH_HDRS))) - -# Builds a standalone speech command recognizer binary using fake audio input. -$(eval $(call microlite_test,micro_speech_mock,\ -$(MICRO_SPEECH_MOCK_SRCS),$(MICRO_SPEECH_MOCK_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/Makefile.inc b/tensorflow/lite/experimental/micro/examples/person_detection/Makefile.inc deleted file mode 100644 index 9ba305996c3..00000000000 --- a/tensorflow/lite/experimental/micro/examples/person_detection/Makefile.inc +++ /dev/null @@ -1,68 +0,0 @@ -$(eval $(call add_third_party_download,$(PERSON_MODEL_URL),$(PERSON_MODEL_MD5),person_model_grayscale,)) - -person_detection_MODEL_SRCS := \ -tensorflow/lite/experimental/micro/examples/person_detection/model_settings.cc \ -$(MAKEFILE_DIR)/downloads/person_model_grayscale/person_detect_model_data.cc - -person_detection_MODEL_HDRS := \ -tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h \ -tensorflow/lite/experimental/micro/examples/person_detection/person_detect_model_data.h - -person_detection_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/person_detection/person_detection_test.cc \ -$(MAKEFILE_DIR)/downloads/person_model_grayscale/no_person_image_data.cc \ -$(MAKEFILE_DIR)/downloads/person_model_grayscale/person_image_data.cc \ -$(person_detection_MODEL_SRCS) - -person_detection_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/person_detection/no_person_image_data.h \ -tensorflow/lite/experimental/micro/examples/person_detection/person_image_data.h \ -$(person_detection_MODEL_HDRS) - -IMAGE_PROVIDER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/person_detection/image_provider.cc \ -tensorflow/lite/experimental/micro/examples/person_detection/image_provider_test.cc \ -tensorflow/lite/experimental/micro/examples/person_detection/model_settings.cc - -IMAGE_PROVIDER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h \ -tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h - -DETECTION_RESPONDER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.cc \ -tensorflow/lite/experimental/micro/examples/person_detection/detection_responder_test.cc - -DETECTION_RESPONDER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h - -person_detection_SRCS := \ -tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.cc \ -tensorflow/lite/experimental/micro/examples/person_detection/image_provider.cc \ -tensorflow/lite/experimental/micro/examples/person_detection/main.cc \ -tensorflow/lite/experimental/micro/examples/person_detection/main_functions.cc \ -$(person_detection_MODEL_SRCS) - -person_detection_HDRS := \ -tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h \ -tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h \ -tensorflow/lite/experimental/micro/examples/person_detection/main_functions.h \ -$(person_detection_MODEL_HDRS) - -# Find any platform-specific rules for this example. -include $(wildcard tensorflow/lite/experimental/micro/examples/person_detection/*/Makefile.inc) - -# Tests loading and running a vision model. -$(eval $(call microlite_test,person_detection_test,\ -$(person_detection_TEST_SRCS),$(person_detection_TEST_HDRS))) - -# Tests the image provider module. -$(eval $(call microlite_test,image_provider_test,\ -$(IMAGE_PROVIDER_TEST_SRCS),$(IMAGE_PROVIDER_TEST_HDRS))) - -# Tests the detection responder module. -$(eval $(call microlite_test,detection_responder_test,\ -$(DETECTION_RESPONDER_TEST_SRCS),$(DETECTION_RESPONDER_TEST_HDRS))) - -# Builds a standalone object recognition binary. -$(eval $(call microlite_test,person_detection,\ -$(person_detection_SRCS),$(person_detection_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/Makefile.inc b/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/Makefile.inc deleted file mode 100644 index 50bbf86f01e..00000000000 --- a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/Makefile.inc +++ /dev/null @@ -1,14 +0,0 @@ -ifeq ($(TARGET),$(filter $(TARGET),apollo3evb sparkfun_edge)) - person_detection_SRCS += \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.c \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.c \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.c - - person_detection_HDRS += \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.h \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.h \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.h \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h \ - tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h -endif diff --git a/tensorflow/lite/experimental/microfrontend/BUILD b/tensorflow/lite/experimental/microfrontend/BUILD index eb7d62081c4..aaaf864bb60 100644 --- a/tensorflow/lite/experimental/microfrontend/BUILD +++ b/tensorflow/lite/experimental/microfrontend/BUILD @@ -92,9 +92,9 @@ tf_py_test( name = "audio_microfrontend_op_test", size = "small", srcs = ["python/kernel_tests/audio_microfrontend_op_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ ":audio_microfrontend_py", "//tensorflow:tensorflow_py", ], - tags = ["no_pip"], ) diff --git a/tensorflow/lite/experimental/microfrontend/lib/BUILD b/tensorflow/lite/experimental/microfrontend/lib/BUILD index 9e8837ae28b..18bfdb24a84 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/BUILD +++ b/tensorflow/lite/experimental/microfrontend/lib/BUILD @@ -1,7 +1,7 @@ # Library for generating feature vectors from audio data load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) @@ -128,7 +128,7 @@ tflite_micro_cc_test( srcs = ["fft_test.cc"], deps = [ ":fft", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -137,7 +137,7 @@ tflite_micro_cc_test( srcs = ["filterbank_test.cc"], deps = [ ":filterbank", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -146,7 +146,7 @@ tflite_micro_cc_test( srcs = ["frontend_test.cc"], deps = [ ":frontend", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -155,7 +155,7 @@ tflite_micro_cc_test( srcs = ["log_scale_test.cc"], deps = [ ":log_scale", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -164,7 +164,7 @@ tflite_micro_cc_test( srcs = ["noise_reduction_test.cc"], deps = [ ":noise_reduction", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -173,7 +173,7 @@ tflite_micro_cc_test( srcs = ["pcan_gain_control_test.cc"], deps = [ ":pcan_gain_control", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -182,6 +182,6 @@ tflite_micro_cc_test( srcs = ["window_test.cc"], deps = [ ":window", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) diff --git a/tensorflow/lite/experimental/microfrontend/lib/fft_test.cc b/tensorflow/lite/experimental/microfrontend/lib/fft_test.cc index 958d475b3ab..cfca64c94fd 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/fft_test.cc +++ b/tensorflow/lite/experimental/microfrontend/lib/fft_test.cc @@ -14,8 +14,8 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/microfrontend/lib/fft.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" #include "tensorflow/lite/experimental/microfrontend/lib/fft_util.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace { diff --git a/tensorflow/lite/experimental/microfrontend/lib/filterbank_test.cc b/tensorflow/lite/experimental/microfrontend/lib/filterbank_test.cc index 16257aa11a5..cb5d3d806d3 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/filterbank_test.cc +++ b/tensorflow/lite/experimental/microfrontend/lib/filterbank_test.cc @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/microfrontend/lib/filterbank.h" -#include "tensorflow/lite/experimental/microfrontend/lib/filterbank_util.h" #include -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/experimental/microfrontend/lib/filterbank_util.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace { diff --git a/tensorflow/lite/experimental/microfrontend/lib/frontend_test.cc b/tensorflow/lite/experimental/microfrontend/lib/frontend_test.cc index 568484f14dd..adf59a1b8b5 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/frontend_test.cc +++ b/tensorflow/lite/experimental/microfrontend/lib/frontend_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h" -#include "tensorflow/lite/experimental/microfrontend/lib/frontend_util.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/experimental/microfrontend/lib/frontend_util.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace { diff --git a/tensorflow/lite/experimental/microfrontend/lib/log_scale_test.cc b/tensorflow/lite/experimental/microfrontend/lib/log_scale_test.cc index be52fd426a2..3f2ce200926 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/log_scale_test.cc +++ b/tensorflow/lite/experimental/microfrontend/lib/log_scale_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/microfrontend/lib/log_scale.h" -#include "tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace { diff --git a/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_test.cc b/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_test.cc index ba864c427ce..027f688a594 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_test.cc +++ b/tensorflow/lite/experimental/microfrontend/lib/noise_reduction_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h" -#include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace { diff --git a/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_test.cc b/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_test.cc index 93d7a8bcb94..f6ecd71ddb0 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_test.cc +++ b/tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h" -#include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace { diff --git a/tensorflow/lite/experimental/microfrontend/lib/window_test.cc b/tensorflow/lite/experimental/microfrontend/lib/window_test.cc index cf9df523b8f..8ed76940818 100644 --- a/tensorflow/lite/experimental/microfrontend/lib/window_test.cc +++ b/tensorflow/lite/experimental/microfrontend/lib/window_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "tensorflow/lite/experimental/microfrontend/lib/window.h" -#include "tensorflow/lite/experimental/microfrontend/lib/window_util.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/experimental/microfrontend/lib/window_util.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace { diff --git a/tensorflow/lite/experimental/objc/BUILD.apple b/tensorflow/lite/experimental/objc/BUILD.apple index 198e90c1cbc..18b53980133 100644 --- a/tensorflow/lite/experimental/objc/BUILD.apple +++ b/tensorflow/lite/experimental/objc/BUILD.apple @@ -72,7 +72,9 @@ ios_unit_test( name = "Tests", size = "medium", minimum_os_version = TFL_MINIMUM_OS_VERSION, - tags = TFL_DEFAULT_TAGS + TFL_DISABLED_SANITIZER_TAGS, + tags = TFL_DEFAULT_TAGS + TFL_DISABLED_SANITIZER_TAGS + [ + "nozapfhahn", # TODO(b/145984659): Enable after solving tool failure. + ], deps = [ ":TestsLibrary", ], diff --git a/tensorflow/lite/experimental/ruy/block_map.cc b/tensorflow/lite/experimental/ruy/block_map.cc index 292aa5186b2..f3ec73a6007 100644 --- a/tensorflow/lite/experimental/ruy/block_map.cc +++ b/tensorflow/lite/experimental/ruy/block_map.cc @@ -109,16 +109,36 @@ void GetRectangularness(int rows, int cols, int kernel_rows, int kernel_cols, int* cols_rectangularness_log2) { *rows_rectangularness_log2 = 0; *cols_rectangularness_log2 = 0; + + // In GEMV-ish cases, that is when kernel blocks are as narrow as the kernel + // itself, we risk having too small kernel blocks for good kernel + // amortization. We avoid that by limiting recangularness so that kernel + // blocks are not too tiny at least in that dimension. Specifically, we try to + // have at least (2^min_kernel_inner_loop_runs_log2) kernels fitting in each + // kernel block along the large dimension. + const int min_kernel_inner_loop_runs_log2 = 3; if (rows > cols) { + int cols_of_kernel_inner_loop_runs_log2 = + ceil_log2(cols) - pot_log2(kernel_cols); + int min_rows_of_kernel_inner_loop_runs_log2 = + std::max(0, min_kernel_inner_loop_runs_log2 - + cols_of_kernel_inner_loop_runs_log2); *rows_rectangularness_log2 = std::min(floor_log2_quotient(rows, cols), - floor_log2(rows) - pot_log2(kernel_rows)); + std::max(0, floor_log2(rows) - pot_log2(kernel_rows) - + min_rows_of_kernel_inner_loop_runs_log2)); // Sanity check that we did not over-estimate rows_rectangularness_log2. RUY_DCHECK_GE(rows >> *rows_rectangularness_log2, cols); } else if (cols > rows) { + int rows_of_kernel_inner_loop_runs_log2 = + ceil_log2(rows) - pot_log2(kernel_rows); + int min_cols_of_kernel_inner_loop_runs_log2 = + std::max(0, min_kernel_inner_loop_runs_log2 - + rows_of_kernel_inner_loop_runs_log2); *cols_rectangularness_log2 = std::min(floor_log2_quotient(cols, rows), - floor_log2(cols) - pot_log2(kernel_cols)); + std::max(0, floor_log2(cols) - pot_log2(kernel_cols) - + min_cols_of_kernel_inner_loop_runs_log2)); // Sanity check that we did not over-estimate cols_rectangularness_log2. RUY_DCHECK_GE(cols >> *cols_rectangularness_log2, rows); } @@ -166,7 +186,15 @@ int GetMultithreadingScore(int block_size_log2, int rows, int cols, // (e.g. L3) caches shared among all cores. Here we aim to fit in a fast, // local cache. int GetCacheLocalityScore(int block_size_log2, int rows, int cols, int depth, + int kernel_rows_log2, int kernel_cols_log2, int lhs_scalar_size, int rhs_scalar_size, Path path) { + // In the narrow case (e.g. matrix*vector), each byte of the big operand + // matrix (either LHS or RHS) is traversed only once, so any notion of data + // locality is irrelevant. Ignore the 'cache locality score' by forcing it to + // be 0 in that case. + if (rows <= (1 << kernel_rows_log2) || cols <= (1 << kernel_cols_log2)) { + return 0; + } const int block_rows = std::min(1 << block_size_log2, rows); const int block_cols = std::min(1 << block_size_log2, cols); #if RUY_PLATFORM(ARM_64) @@ -304,9 +332,9 @@ void MakeBlockMap(int rows, int cols, int depth, int kernel_rows, block_size_log2 <= max_block_size_log2; block_size_log2++) { const int multithreading_score = GetMultithreadingScore( block_size_log2, rows, cols, tentative_thread_count); - const int cache_locality_score = - GetCacheLocalityScore(block_size_log2, rows, cols, depth, - lhs_scalar_size, rhs_scalar_size, path); + const int cache_locality_score = GetCacheLocalityScore( + block_size_log2, rows, cols, depth, kernel_rows_log2, kernel_cols_log2, + lhs_scalar_size, rhs_scalar_size, path); const int kernel_amortization_score = GetKernelAmortizationScore( block_size_log2, rows, cols, kernel_rows_log2, kernel_cols_log2); const int score = diff --git a/tensorflow/lite/experimental/ruy/block_map_test.cc b/tensorflow/lite/experimental/ruy/block_map_test.cc index 6fc7a24762a..e51d959d35d 100644 --- a/tensorflow/lite/experimental/ruy/block_map_test.cc +++ b/tensorflow/lite/experimental/ruy/block_map_test.cc @@ -126,7 +126,7 @@ TEST(BlockMapTest, MakeBlockMapTuningTestRectangular) { MakeBlockMapTuningTest(256, 16, 256, 8, 8, 1, 1, /* tentative_thread_count */ 1, Path::kNeonDotprod, /* expected_num_blocks_base_log2 */ 0, - /* expected_rectangularness_log2 */ 4); + /* expected_rectangularness_log2 */ 3); MakeBlockMapTuningTest(24, 2400, 256, 8, 8, 1, 1, /* tentative_thread_count */ 1, Path::kNeonDotprod, /* expected_num_blocks_base_log2 */ 0, diff --git a/tensorflow/lite/experimental/ruy/dispatch.h b/tensorflow/lite/experimental/ruy/dispatch.h index 3b2113eb651..0aaaccafb2e 100644 --- a/tensorflow/lite/experimental/ruy/dispatch.h +++ b/tensorflow/lite/experimental/ruy/dispatch.h @@ -465,9 +465,7 @@ void DispatchMul(const Matrix& lhs, const Matrix& rhs, TrMulParams params; CreateTrMulParams(transposed_lhs, rhs, spec, context, dst, the_path, ¶ms); -#ifdef RUY_ENABLE_PREPACKED_CACHE HandlePrepackedCaching(¶ms, context); -#endif TrMul(¶ms, context); } diff --git a/tensorflow/lite/g3doc/microcontrollers/build_convert.md b/tensorflow/lite/g3doc/microcontrollers/build_convert.md index 42a35d62b85..b2bd2ce6ac8 100644 --- a/tensorflow/lite/g3doc/microcontrollers/build_convert.md +++ b/tensorflow/lite/g3doc/microcontrollers/build_convert.md @@ -12,7 +12,7 @@ guidance on designing and training a model to fit in limited memory. For an end-to-end, runnable example of building and converting a model, see the following Colab which is part of the *Hello World* example: -create_sine_model.ipynb +create_sine_model.ipynb ## Model conversion @@ -71,7 +71,7 @@ important to change the array declaration to `const` for better memory efficiency on embedded platforms. For an example of how to include and use a model in your program, see -[`sine_model_data.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.cc) +[`sine_model_data.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/sine_model_data.cc) in the *Hello World* example. ## Model architecture and training @@ -109,4 +109,4 @@ to run. We are working on expanding operation support, both in terms of reference implementations and optimizations for specific architectures. The supported operations can be seen in the file -[`all_ops_resolver.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.cc) +[`all_ops_resolver.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/kernels/all_ops_resolver.cc) diff --git a/tensorflow/lite/g3doc/microcontrollers/get_started.md b/tensorflow/lite/g3doc/microcontrollers/get_started.md index 2bcfa557058..0674ada8d28 100644 --- a/tensorflow/lite/g3doc/microcontrollers/get_started.md +++ b/tensorflow/lite/g3doc/microcontrollers/get_started.md @@ -30,15 +30,15 @@ TensorFlow Lite for Microcontrollers comes with several example applications that demonstrate its use for various tasks. At the time of writing, the following are available: -* [Hello World](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/hello_world) - +* [Hello World](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/hello_world) - Demonstrates the absolute basics of using TensorFlow Lite for Microcontrollers -* [Micro speech](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech) - +* [Micro speech](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/micro_speech) - Captures audio with a microphone in order to detect the words "yes" and "no" -* [Person detection](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/person_detection) - +* [Person detection](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/person_detection) - Captures camera data with an image sensor in order to detect the presence or absence of a person -* [Magic wand](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/magic_wand) - +* [Magic wand](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/magic_wand) - Captures accelerometer data in order to classify three different physical gestures @@ -46,7 +46,7 @@ Each example application has a `README.md` file that explains how it can be deployed to its supported platforms. The rest of this guide walks through the -[Hello World](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/hello_world) +[Hello World](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/hello_world) example application. ## The Hello World example @@ -73,13 +73,13 @@ The example includes the following: To run the example on your device, walk through the instructions in the `README.md`: -Hello +Hello World README.md ## How to run inference The following section walks through the *Hello World* example's -[`hello_world_test.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/hello_world_test.cc), +[`hello_world_test.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/hello_world_test.cc), which demonstrates how to run inference using TensorFlow Lite for Microcontrollers. @@ -91,18 +91,18 @@ To use the TensorFlow Lite for Microcontrollers library, we must include the following header files: ```C++ -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" ``` -- [`all_ops_resolver.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h) +- [`all_ops_resolver.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/kernels/all_ops_resolver.h) provides the operations used by the interpreter to run the model. -- [`micro_error_reporter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/micro_error_reporter.h) +- [`micro_error_reporter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/micro_error_reporter.h) outputs debug information. -- [`micro_interpreter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/micro_interpreter.h) +- [`micro_interpreter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/micro_interpreter.h) contains code to load and run models. - [`schema_generated.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema_generated.h) contains the schema for the TensorFlow Lite @@ -118,7 +118,7 @@ provided as a C++ array. In the *Hello World* example, the model is defined in following line: ```C++ -#include "tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h" +#include "tensorflow/lite/micro/examples/hello_world/sine_model_data.h" ``` ### Set up the unit test @@ -128,7 +128,7 @@ Microcontrollers unit test framework. To load the framework, we include the following file: ```C++ -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/micro_test.h" ``` The test is defined using the following macros: @@ -175,7 +175,7 @@ if (model->version() != TFLITE_SCHEMA_VERSION) { ### Instantiate operations resolver An -[`AllOpsResolver`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h) +[`AllOpsResolver`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/kernels/all_ops_resolver.h) instance is declared. This will be used by the interpreter to access the operations that are used by the model: @@ -190,7 +190,7 @@ load only the operations that are needed. This is done using a different class, `MicroMutableOpResolver`. You can see how to use it in the *Micro speech* example's -[`micro_speech_test.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc). +[`micro_speech_test.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/micro_speech/micro_speech_test.cc). ### Allocate memory @@ -348,7 +348,7 @@ TF_LITE_MICRO_EXPECT_NEAR(-0.959, value, 0.05); Once you have walked through this unit test, you should be able to understand the example's application code, located in -[`main_functions.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/main_functions.cc). +[`main_functions.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/main_functions.cc). It follows a similar process, but generates an input value based on how many inferences have been run, and calls a device-specific function that displays the model's output to the user. @@ -359,7 +359,7 @@ To understand how the library can be used with a variety of models and applications, we recommend deploying the other examples and walking through their code. -Example +Example applications on GitHub To learn how to use the library in your own project, read diff --git a/tensorflow/lite/g3doc/microcontrollers/library.md b/tensorflow/lite/g3doc/microcontrollers/library.md index 17b7b69db32..b99a72fb0dd 100644 --- a/tensorflow/lite/g3doc/microcontrollers/library.md +++ b/tensorflow/lite/g3doc/microcontrollers/library.md @@ -22,17 +22,17 @@ within various embedded development environments. The most important files for using the TensorFlow Lite for Microcontrollers interpreter are located in the root of the project, accompanied by tests: -- [`all_ops_resolver.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h) +- [`all_ops_resolver.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/kernels/all_ops_resolver.h) or - [`micro_mutable_op_resolver.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h) + [`micro_mutable_op_resolver.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/micro_mutable_op_resolver.h) can be used to provide the operations used by the interpreter to run the model. Since `all_ops_resolver.h` pulls in every available operation, it uses a lot of memory. In production applications, you should use `micro_mutable_op_resolver.h` to pull in only the operations your model needs. -- [`micro_error_reporter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/micro_error_reporter.h) +- [`micro_error_reporter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/micro_error_reporter.h) outputs debug information. -- [`micro_interpreter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/micro_interpreter.h) +- [`micro_interpreter.h`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/micro_interpreter.h) contains code to handle and run models. See [Get started with microcontrollers](get_started.md) for a walkthrough of @@ -40,15 +40,15 @@ typical usage. The build system provides for platform-specific implementations of certain files. These are located in a directory with the platform name, for example -[`sparkfun_edge`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/sparkfun_edge). +[`sparkfun_edge`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/sparkfun_edge). Several other directories exist, including: -- [`kernel`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/kernels), +- [`kernel`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/kernels), which contains operation implementations and the associated code. -- [`tools`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/tools), +- [`tools`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/tools), which contains build tools and their output. -- [`examples`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples), +- [`examples`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples), which contains sample code. ## Start a new project @@ -79,33 +79,32 @@ To generate these projects with Make, clone the following command: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile generate_projects +make -f tensorflow/lite/micro/tools/make/Makefile generate_projects ``` This will take a few minutes, since it has to download some large toolchains for the dependencies. Once it has finished, you should see some folders created -inside a path like -`tensorflow/lite/experimental/micro/tools/make/gen/linux_x86_64/prj/` (the exact -path depends on your host operating system). These folders contain the generated -project and source files. +inside a path like `tensorflow/lite/micro/tools/make/gen/linux_x86_64/prj/` (the +exact path depends on your host operating system). These folders contain the +generated project and source files. After running the command, you'll be able to find the *Hello World* projects in -`tensorflow/lite/experimental/micro/tools/make/gen/linux_x86_64/prj/hello_world`. -For example, `hello_world/keil` will contain the Keil project. +`tensorflow/lite/micro/tools/make/gen/linux_x86_64/prj/hello_world`. For +example, `hello_world/keil` will contain the Keil project. ## Run the tests To build the library and run all of its unit tests, use the following command: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile test +make -f tensorflow/lite/micro/tools/make/Makefile test ``` To run an individual test, use the following command, replacing `` with the name of the test: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile test_ +make -f tensorflow/lite/micro/tools/make/Makefile test_ ``` You can find the test names in the project's Makefiles. For example, @@ -119,14 +118,14 @@ use the following command, replacing `` with the project you wish to build: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile _bin +make -f tensorflow/lite/micro/tools/make/Makefile _bin ``` For example, the following command will build a binary for the *Hello World* application: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile hello_world_bin +make -f tensorflow/lite/micro/tools/make/Makefile hello_world_bin ``` By default, the project will be compiled for the host operating system. To @@ -134,7 +133,7 @@ specify a different target architecture, use `TARGET=`. The following example shows how to build the *Hello World* example for the SparkFun Edge: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=sparkfun_edge hello_world_bin +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=sparkfun_edge hello_world_bin ``` When a target is specified, any available target-specific source files will be @@ -149,9 +148,9 @@ World* example. ## Optimized kernels -The reference kernels in the root of -`tensorflow/lite/experimental/micro/kernels` are implemented in pure C/C++, and -do not include platform-specific hardware optimizations. +The reference kernels in the root of `tensorflow/lite/micro/kernels` are +implemented in pure C/C++, and do not include platform-specific hardware +optimizations. Optimized versions of kernels are provided in subdirectories. For example, `kernels/cmsis-nn` contains several optimized kernels that make use of Arm's @@ -162,7 +161,7 @@ replacing `` with the name of the subdirectory containing the optimizations: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TAGS= generate_projects +make -f tensorflow/lite/micro/tools/make/Makefile TAGS= generate_projects ``` You can add your own optimizations by creating a new subfolder for them. We @@ -177,14 +176,14 @@ If you need to generate a new build of the library, you can run the following script from the TensorFlow repository: ```bash -./tensorflow/lite/experimental/micro/tools/ci_build/test_arduino.sh +./tensorflow/lite/micro/tools/ci_build/test_arduino.sh ``` The resulting library can be found in -`tensorflow/lite/experimental/micro/tools/make/gen/arduino_x86_64/prj/tensorflow_lite.zip`. +`tensorflow/lite/micro/tools/make/gen/arduino_x86_64/prj/tensorflow_lite.zip`. ## Port to new devices Guidance on porting TensorFlow Lite for Microcontrollers to new platforms and devices can be found in -[`micro/README.md`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/README.md). +[`micro/README.md`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/README.md). diff --git a/tensorflow/lite/g3doc/performance/hexagon_delegate.md b/tensorflow/lite/g3doc/performance/hexagon_delegate.md new file mode 100644 index 00000000000..e0c841facb3 --- /dev/null +++ b/tensorflow/lite/g3doc/performance/hexagon_delegate.md @@ -0,0 +1,272 @@ +## Tensorflow Lite Hexagon Delegate Quick Guide + +[TOC] + +This document explains how to use the Tensorflow Lite Hexagon Delegate in your +application using the Java and/or C API. The delegate leverages the Qualcomm +Hexagon library to execute quantized kernels on the DSP. Note that the delegate +is intended to *complement* NNAPI functionality, particularly for devices where +NNAPI DSP acceleration is unavailable (e.g., on older devices, or devices that +don’t yet have a DSP NNAPI driver). Note: This delegate is in experimental +(beta) phase. + +**Supported devices:** + +Currently most +[Qualcomm SoCs](https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems-on-chip) +are supported, including: + +* Snapdragon 835 (682 DSP) +* Snapdragon 660/820/821 (680 DSP) +* Snapdragon 710/845 (685 DSP) +* Snapdragon 8150/855 (690 DSP) + + +**Supported models:** + +The Hexagon delegate currently supports quantized models generated using +[quantization-aware training](https://github.com/tensorflow/tensorflow/tree/r1.13/tensorflow/contrib/quantize), +e.g., +[these quantized models](https://www.tensorflow.org/lite/guide/hosted_models#quantized_models) +hosted on the TensorFlow Lite repo. It does not (yet) support models with +[8-bit symmetric quantization spec](https://www.tensorflow.org/lite/performance/quantization_spec). +Sample models include +[MobileNet V1](https://storage.googleapis.com/download.tensorflow.org/models/mobilenet_v1_2018_08_02/mobilenet_v1_1.0_224_quant.tgz), +[SSD Mobilenet](https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip). + +## Hexagon Delegate Java API {#hexagon-delegate-java-api} + +``` +public class HexagonDelegate implements Delegate, Closeable { + + /* + * Creates a new HexagonDelegate object given the current 'context'. + * Throws UnsupportedOperationException if Hexagon DSP delegation is not + * available on this device. + */ + public HexagonDelegate(Context context) throws UnsupportedOperationException + + + /** + * Frees TFLite resources in C runtime. + * + * User is expected to call this method explicitly. + */ + @Override + public void close(); +} +``` + +## Example Usage from Java {#example-usage-from-java} + +1. Add the ‘tensorflow-lite-hexagon.aar’ to your app - this is in addition to + the standard tensorflow-lite AAR (nightly or release). + [Relevant instructions](https://stackoverflow.com/questions/16682847/how-to-manually-include-external-aar-package-using-new-gradle-android-build-syst). + You can do this by running bazel command like example below for arm64. + We will provide a version hosted on JCenter soon. + +``` +bazel build -c opt --config=android_arm64 tensorflow/lite/experimental/delegates/hexagon/java:tensorflow-lite-hexagon +``` + +1. Run “hexagon_nn_skel.run” - Note: you will need to accept the license + agreement. It should provide 3 different shared libraries + “libhexagon_nn_skel.so”, “libhexagon_nn_skel_v65.so”, + “libhexagon_nn_skel_v66.so” \ + Include all 3 in your app with other shared libraries. See + [How to add shared library to your app](#how-to-add-shared-library-to-your-app) + \ + The delegate will automatically pick the one with best performance depending + on the device. \ + Note: If your app will be built for both 32 and 64-bit ARM devices, then you + will need to add the hexagon shared libs to both 32 and 64-bit lib folders. + +1. Create a delegate, example: + +``` +import org.tensorflow.lite.experimental.HexagonDelegate; + +// Create the Delegate instance. +try { + hexagonDelegate = new HexagonDelegate(activity); + tfliteOptions.addDelegate(hexagonDelegate); +} catch (UnsupportedOperationException e) { + // Hexagon delegate is not supported on this device. +} + +tfliteInterpreter = new Interpreter(tfliteModel, tfliteOptions); + +// Dispose after finished with inference. +tfliteInterpreter.close(); +if (hexagonDelegate != null) { + hexagonDelegate.close(); +} +``` + +## Hexagon Delegate C API {#hexagon-delegate-c-api} + +``` +struct TfLiteHexagonDelegateOptions { + // This corresponds to the debug level in the hexagon SDK. 0 (default) + // means no debug. + int debug_level; + // This corresponds to powersave_level in the hexagon SDK. + // where 0 (default) means high performance which means more power + // consumption. + int powersave_level; + // If set to true, performance information about the graph will be dumped + // to Standard output, this includes cpu cycles. + // WARNING: Experimental and subject to change anytime. + bool print_graph_profile; + // If set to true, graph structure will be dumped to Standard output. + // This is usually beneficial to see what actual nodes executed on + // the DSP. Combining with 'debug_level' more information will be printed. + // WARNING: Experimental and subject to change anytime. + bool print_graph_debug; +}; + +// Return a delegate that uses Hexagon SDK for ops execution. +// Must outlive the interpreter. +TfLiteDelegate* +TfLiteHexagonDelegateCreate(const TfLiteHexagonDelegateOptions* options); + +// Do any needed cleanup and delete 'delegate'. +void TfLiteHexagonDelegateDelete(TfLiteDelegate* delegate); + +// Initializes the DSP connection. +// This should be called before doing any usage of the delegate. +// "lib_directory_path": Path to the directory which holds the +// shared libraries for the Hexagon NN libraries on the device. +void TfLiteHexagonInitWithPath(const char* lib_directory_path); + +// Same as above method but doesn't accept the path params. +// Assumes the environment setup is already done. Only initialize Hexagon. +Void TfLiteHexagonInit(); + +// Clean up and switch off the DSP connection. +// This should be called after all processing is done and delegate is deleted. +Void TfLiteHexagonTearDown(); +``` + +## Example Usage from C {#example-usage-from-c} + +1. Add the ‘tensorflow-lite-hexagon.aar’ to your app - this is in addition to + the standard tensorflow-lite AAR (nightly or release). + [Relevant instructions](https://stackoverflow.com/questions/16682847/how-to-manually-include-external-aar-package-using-new-gradle-android-build-syst). +1. Include the provided hexagon_delegate.h +1. Run “hexagon_nn_skel.run” - Note: you will need to accept the license + agreement. It should provide 3 different shared libraries \ + “libhexagon_nn_skel.so”, “libhexagon_nn_skel_v65.so”, + “libhexagon_nn_skel_v66.so” \ + Include all 3 in your app with other shared libraries. See How to add shared + library to your app. \ + The delegate will automatically pick the one with best performance depending + on the device. \ + Note: If your app will be built for both 32 and 64-bit ARM devices, then you + will need to add the hexagon shared libs to both 32 and 64-bit lib folders. + +1. In your code, ensure the native Hexagon library is loaded. This can be done + by calling `System.loadLibrary("tensorflowlite_hexagon_jni");` \ + in your Activity or Java entry-point. + +1. Create a delegate, example: + + ``` + #include "tensorflow/lite/experimental/delegates/hexagon/hexagon_delegate.h" + + // Assuming shared libraries are under "/data/local/tmp/" + // If files are packaged with native lib in android App then it + // will typically be equivalent to the path provided by + // "getContext().getApplicationInfo().nativeLibraryDir" + const char[] library_directory_path = "/data/local/tmp/"; + TfLiteHexagonInitWithPath(library_directory_path); // Needed once at startup. + ::tflite::TfLiteHexagonDelegateOptions params = {0}; + // 'delegate_ptr' Need to outlive the interpreter. For example, + // If use case will need to resize input or anything that can trigger + // re-applying delegates then 'delegate_ptr' need to outlive the interpreter. + auto* delegate_ptr = ::tflite::TfLiteHexagonDelegateCreate(¶ms); + Interpreter::TfLiteDelegatePtr delegate(delegate_ptr, + [](TfLiteDelegate* delegate) { + ::tflite::TfLiteHexagonDelegateDelete(delegate); + }); + interpreter->ModifyGraphWithDelegate(delegate.get()); + // After usage of delegate. + TfLiteHexagonTearDown(); // Needed once at end of app/DSP usage. + ``` + +## How to add shared library to your app {#how-to-add-shared-library-to-your-app} + +Create folder “app/src/main/jniLibs”, then for each target architecture create a +directory. + +For example, + +Arm64 bit: “app/src/main/jniLibs/arm64-v8a” + +Arm32 bit: “app/src/main/jniLibs/armeabi-v7a” + +Put your .so in the directory that match the architecture. + +## Feedback {#feedback} + +For issues, please create a +[github](https://github.com/tensorflow/tensorflow/issues/new?template=50-other-issues.md) +issue with all the necessary repro details, including the phone model and board +used (`adb shell getprop ro.product.device` and `adb shell getprop +ro.board.platform`). + +## FAQ {#faq} + +* Will the delegate support models created using + [post-training quantization](https://www.tensorflow.org/lite/performance/post_training_quantization)? + * This is tentatively planned for a future release, though there is no + concrete timeline. +* Which ops are supported by the delegate? + * Initial Dogfood list of supported ops: + * Add + * ArgMax + * ArgMin + * AveragePool2D: + * Constraints: + * No Activation + * Concat + * Conv2D: + * Constraints: + * stride width/height <= 3 + * DepthwiseConv2D: + * Constraints: + * Filter width == 3 + * depth_multiplier == 1 + * dilation only supported when stride == 1 + * Otherwise, stride height/width <= 3 + * FullyConnected (without any activation) + * L2Normalization (without any activation) + * Logistic (aka Sigmoid) + * MaxPool2D (without any activation) + * Mul (without any activation) + * Neg + * Pad: Only supports 0 padding + * Relu + * Relu6 + * Reshape + * Resize Bilinear: + * Constraints: + * Requested size <= 65 + * Resize Nearest Neighbor + * SoftMax + * Split + * Sub + * Tanh + * Transpose + * TransposeConv2D: + * Constraints: + * stride height/width <= 3 + * dilation height/width == 1 +* How can I tell that the model is using the DSP when I enable the delegate? + * A log message will be printed whether delegate created or not, and + another one with how many nodes are running using the delegate. \ + "Created TensorFlow Lite delegate for Hexagon." \ + "Hexagon delegate: X nodes delegated out of Y nodes." +* Do I need all Ops in the model to be supported to run the delegate ? + * No, the Model will be partitioned into subgraphs based on the supported + ops. Any unsupported ops will run on the CPU. diff --git a/tensorflow/lite/g3doc/performance/post_training_integer_quant.ipynb b/tensorflow/lite/g3doc/performance/post_training_integer_quant.ipynb index b269990c065..a684d24a479 100644 --- a/tensorflow/lite/g3doc/performance/post_training_integer_quant.ipynb +++ b/tensorflow/lite/g3doc/performance/post_training_integer_quant.ipynb @@ -83,7 +83,9 @@ "accuracy of the converted model and compare it to the original float model.\n", "\n", "The training script, `mnist.py`, is available from the\n", - "[TensorFlow official MNIST tutorial](https://github.com/tensorflow/models/tree/master/official/mnist).\n" + "[TensorFlow official MNIST tutorial](https://github.com/tensorflow/models/tree/master/official/mnist).\n", + "\n", + "**Note:** Currently, TensorFlow 2.x does not allow you to specify the model's input/output type when using post-training quantization. So this tutorial uses TensorFlow 1.x in order to use the ```inference_input_type``` and ```inference_output_type``` options with the TFLiteConverter—allowing for complete quantization end-to-end. Work is ongoing to bring this functionality to TensorFlow 2.x.\n" ] }, { @@ -106,20 +108,6 @@ "### Setup" ] }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "gyqAw1M9lyab" - }, - "outputs": [], - "source": [ - "! pip uninstall -y tensorflow\n", - "! pip install -U tf-nightly" - ] - }, { "cell_type": "code", "execution_count": 0, @@ -130,7 +118,13 @@ }, "outputs": [], "source": [ + "try:\n", + " # %tensorflow_version only exists in Colab.\n", + " %tensorflow_version 1.x\n", + "except Exception:\n", + " pass\n", "import tensorflow as tf\n", + "\n", "tf.enable_eager_execution()" ] }, @@ -205,7 +199,7 @@ "source": [ "# The above path addition is not visible to subprocesses, add the path for the subprocess as well.\n", "# Note: channels_last is required here or the conversion may fail. \n", - "!PYTHONPATH={models_path} python models/official/mnist/mnist.py --train_epochs=1 --export_dir {saved_models_root} --data_format=channels_last" + "!PYTHONPATH={models_path} python models/official/r1/mnist/mnist.py --train_epochs=1 --export_dir {saved_models_root} --data_format=channels_last" ] }, { diff --git a/tensorflow/lite/kernels/BUILD b/tensorflow/lite/kernels/BUILD index b3657228e63..c61e866a790 100644 --- a/tensorflow/lite/kernels/BUILD +++ b/tensorflow/lite/kernels/BUILD @@ -1,4 +1,5 @@ load("//tensorflow/lite:build_def.bzl", "tflite_copts") +load("//tensorflow/lite/micro:build_def.bzl", "cc_library") load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite_combined") load("//tensorflow:tensorflow.bzl", "tf_opts_nortti_if_android") @@ -359,6 +360,7 @@ cc_library( hdrs = [ "op_macros.h", ], + build_for_embedded = True, copts = tflite_copts(), ) @@ -461,6 +463,7 @@ cc_library( "mul.cc", "neg.cc", "non_max_suppression.cc", + "numeric_verify.cc", "one_hot.cc", "pack.cc", "pad.cc", @@ -504,11 +507,13 @@ cc_library( "zeros_like.cc", ], hdrs = [ + "dequantize.h", ], copts = tflite_copts() + tf_opts_nortti_if_android() + EXTRA_EIGEN_COPTS, visibility = ["//visibility:private"], deps = [ ":cpu_backend_context", + ":cpu_backend_threadpool", ":eigen_support", ":kernel_util", ":lstm_eval", @@ -517,7 +522,6 @@ cc_library( "//tensorflow/lite:framework", "//tensorflow/lite:string_util", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/resource", "//tensorflow/lite/kernels/internal:audio_utils", "//tensorflow/lite/kernels/internal:common", "//tensorflow/lite/kernels/internal:compatibility", @@ -884,6 +888,23 @@ cc_test( ], ) +cc_test( + name = "numeric_verify_test", + size = "small", + srcs = ["numeric_verify_test.cc"], + tags = ["tflite_nnapi"], + deps = [ + ":builtin_ops", + ":test_main", + ":test_util", + "//tensorflow/lite:framework", + "//tensorflow/lite/kernels/internal:types", + "//third_party/eigen3", + "@com_google_absl//absl/memory", + "@com_google_googletest//:gtest", + ], +) + cc_test( name = "basic_rnn_test", size = "small", diff --git a/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc b/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc index 1108eaee409..4b2b582877b 100644 --- a/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc +++ b/tensorflow/lite/kernels/bidirectional_sequence_lstm.cc @@ -488,8 +488,10 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* fw_output = GetOutput(context, node, kFwOutputTensor); TfLiteTensor* fw_activation_state = GetVariableInput(context, node, kFwInputActivationStateTensor); + TF_LITE_ENSURE(context, fw_activation_state != nullptr); TfLiteTensor* fw_cell_state = GetVariableInput(context, node, kFwInputCellStateTensor); + TF_LITE_ENSURE(context, fw_cell_state != nullptr); // Check the shape of input state tensors. // These tensor may be 1D or 2D. It's fine as long as the total size is @@ -552,8 +554,10 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { // Get the pointer to activation_state and cell_state buffer tensors. TfLiteTensor* bw_activation_state = GetVariableInput(context, node, kBwInputActivationStateTensor); + TF_LITE_ENSURE(context, bw_activation_state != nullptr); TfLiteTensor* bw_cell_state = GetVariableInput(context, node, kBwInputCellStateTensor); + TF_LITE_ENSURE(context, bw_cell_state != nullptr); // Resize the output tensors. if (!params->merge_outputs) { @@ -793,8 +797,10 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* fw_activation_state = GetVariableInput(context, node, kFwInputActivationStateTensor); + TF_LITE_ENSURE(context, fw_activation_state != nullptr); TfLiteTensor* fw_cell_state = GetVariableInput(context, node, kFwInputCellStateTensor); + TF_LITE_ENSURE(context, fw_cell_state != nullptr); TfLiteTensor* fw_output = GetOutput(context, node, kFwOutputTensor); // Tensors for the backward cell. @@ -840,8 +846,10 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { // State tensors. TfLiteTensor* bw_activation_state = GetVariableInput(context, node, kBwInputActivationStateTensor); + TF_LITE_ENSURE(context, bw_activation_state != nullptr); TfLiteTensor* bw_cell_state = GetVariableInput(context, node, kBwInputCellStateTensor); + TF_LITE_ENSURE(context, bw_cell_state != nullptr); TfLiteTensor* bw_output = params->merge_outputs ? nullptr : GetOutput(context, node, kBwOutputTensor); diff --git a/tensorflow/lite/kernels/bidirectional_sequence_rnn.cc b/tensorflow/lite/kernels/bidirectional_sequence_rnn.cc index fdc8add52a2..db456d539b9 100644 --- a/tensorflow/lite/kernels/bidirectional_sequence_rnn.cc +++ b/tensorflow/lite/kernels/bidirectional_sequence_rnn.cc @@ -607,8 +607,10 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* fw_hidden_state = GetVariableInput(context, node, kFwHiddenStateTensor); + TF_LITE_ENSURE(context, fw_hidden_state != nullptr); TfLiteTensor* bw_hidden_state = GetVariableInput(context, node, kBwHiddenStateTensor); + TF_LITE_ENSURE(context, bw_hidden_state != nullptr); TfLiteTensor* fw_output = GetOutput(context, node, kFwOutputTensor); TfLiteTensor* bw_output = params->merge_outputs diff --git a/tensorflow/lite/kernels/builtin_op_kernels.h b/tensorflow/lite/kernels/builtin_op_kernels.h index 47ff2b50e72..cb0676c58e9 100644 --- a/tensorflow/lite/kernels/builtin_op_kernels.h +++ b/tensorflow/lite/kernels/builtin_op_kernels.h @@ -30,124 +30,125 @@ namespace builtin { // Selective registration in turn allows the linker to strip unused kernels. TfLiteRegistration* Register_ABS(); -TfLiteRegistration* Register_RELU(); -TfLiteRegistration* Register_RELU_N1_TO_1(); -TfLiteRegistration* Register_RELU6(); -TfLiteRegistration* Register_TANH(); -TfLiteRegistration* Register_LOGISTIC(); +TfLiteRegistration* Register_ADD(); +TfLiteRegistration* Register_ADD_N(); +TfLiteRegistration* Register_ARG_MAX(); +TfLiteRegistration* Register_ARG_MIN(); TfLiteRegistration* Register_AVERAGE_POOL_2D(); -TfLiteRegistration* Register_MAX_POOL_2D(); -TfLiteRegistration* Register_L2_POOL_2D(); -TfLiteRegistration* Register_CONV_2D(); -TfLiteRegistration* Register_DEPTHWISE_CONV_2D(); -TfLiteRegistration* Register_SVDF(); -TfLiteRegistration* Register_RNN(); +TfLiteRegistration* Register_BATCH_TO_SPACE_ND(); +TfLiteRegistration* Register_BIDIRECTIONAL_SEQUENCE_LSTM(); TfLiteRegistration* Register_BIDIRECTIONAL_SEQUENCE_RNN(); -TfLiteRegistration* Register_UNIDIRECTIONAL_SEQUENCE_RNN(); +TfLiteRegistration* Register_CAST(); +TfLiteRegistration* Register_CEIL(); +TfLiteRegistration* Register_CONCATENATION(); +TfLiteRegistration* Register_CONV_2D(); +TfLiteRegistration* Register_COS(); +TfLiteRegistration* Register_DEPTH_TO_SPACE(); +TfLiteRegistration* Register_DEPTHWISE_CONV_2D(); +TfLiteRegistration* Register_DEQUANTIZE(); +TfLiteRegistration* Register_DIV(); +TfLiteRegistration* Register_ELU(); TfLiteRegistration* Register_EMBEDDING_LOOKUP(); TfLiteRegistration* Register_EMBEDDING_LOOKUP_SPARSE(); +TfLiteRegistration* Register_EQUAL(); +TfLiteRegistration* Register_EXP(); +TfLiteRegistration* Register_EXPAND_DIMS(); +TfLiteRegistration* Register_FAKE_QUANT(); +TfLiteRegistration* Register_FILL(); +TfLiteRegistration* Register_FLOOR(); +TfLiteRegistration* Register_FLOOR_DIV(); +TfLiteRegistration* Register_FLOOR_MOD(); TfLiteRegistration* Register_FULLY_CONNECTED(); -TfLiteRegistration* Register_LSH_PROJECTION(); +TfLiteRegistration* Register_GATHER(); +TfLiteRegistration* Register_GATHER_ND(); +TfLiteRegistration* Register_GREATER(); +TfLiteRegistration* Register_GREATER_EQUAL(); +TfLiteRegistration* Register_HARD_SWISH(); TfLiteRegistration* Register_HASHTABLE_LOOKUP(); -TfLiteRegistration* Register_SOFTMAX(); -TfLiteRegistration* Register_CONCATENATION(); -TfLiteRegistration* Register_ADD(); -TfLiteRegistration* Register_SPACE_TO_BATCH_ND(); -TfLiteRegistration* Register_DIV(); -TfLiteRegistration* Register_SUB(); -TfLiteRegistration* Register_BATCH_TO_SPACE_ND(); -TfLiteRegistration* Register_MUL(); +TfLiteRegistration* Register_IF(); TfLiteRegistration* Register_L2_NORMALIZATION(); +TfLiteRegistration* Register_L2_POOL_2D(); +TfLiteRegistration* Register_LEAKY_RELU(); +TfLiteRegistration* Register_LESS(); +TfLiteRegistration* Register_LESS_EQUAL(); TfLiteRegistration* Register_LOCAL_RESPONSE_NORMALIZATION(); +TfLiteRegistration* Register_LOG(); +TfLiteRegistration* Register_LOGICAL_AND(); +TfLiteRegistration* Register_LOGICAL_NOT(); +TfLiteRegistration* Register_LOGICAL_OR(); +TfLiteRegistration* Register_LOGISTIC(); +TfLiteRegistration* Register_LOG_SOFTMAX(); +TfLiteRegistration* Register_LSH_PROJECTION(); TfLiteRegistration* Register_LSTM(); -TfLiteRegistration* Register_BIDIRECTIONAL_SEQUENCE_LSTM(); -TfLiteRegistration* Register_UNIDIRECTIONAL_SEQUENCE_LSTM(); +TfLiteRegistration* Register_MATRIX_DIAG(); +TfLiteRegistration* Register_MATRIX_SET_DIAG(); +TfLiteRegistration* Register_MAXIMUM(); +TfLiteRegistration* Register_MAX_POOL_2D(); +TfLiteRegistration* Register_MEAN(); +TfLiteRegistration* Register_MINIMUM(); +TfLiteRegistration* Register_MIRROR_PAD(); +TfLiteRegistration* Register_MUL(); +TfLiteRegistration* Register_NEG(); +TfLiteRegistration* Register_NON_MAX_SUPPRESSION_V4(); +TfLiteRegistration* Register_NON_MAX_SUPPRESSION_V5(); +TfLiteRegistration* Register_NOT_EQUAL(); +TfLiteRegistration* Register_ONE_HOT(); +TfLiteRegistration* Register_PACK(); TfLiteRegistration* Register_PAD(); TfLiteRegistration* Register_PADV2(); +TfLiteRegistration* Register_POW(); +TfLiteRegistration* Register_PRELU(); +TfLiteRegistration* Register_QUANTIZE(); +TfLiteRegistration* Register_RANGE(); +TfLiteRegistration* Register_RANK(); +TfLiteRegistration* Register_REDUCE_ANY(); +TfLiteRegistration* Register_REDUCE_MAX(); +TfLiteRegistration* Register_REDUCE_MIN(); +TfLiteRegistration* Register_REDUCE_PROD(); +TfLiteRegistration* Register_RELU(); +TfLiteRegistration* Register_RELU6(); +TfLiteRegistration* Register_RELU_N1_TO_1(); TfLiteRegistration* Register_RESHAPE(); TfLiteRegistration* Register_RESIZE_BILINEAR(); TfLiteRegistration* Register_RESIZE_NEAREST_NEIGHBOR(); +TfLiteRegistration* Register_REVERSE_SEQUENCE(); +TfLiteRegistration* Register_REVERSE_V2(); +TfLiteRegistration* Register_RNN(); +TfLiteRegistration* Register_ROUND(); +TfLiteRegistration* Register_RSQRT(); +TfLiteRegistration* Register_SCATTER_ND(); +TfLiteRegistration* Register_SELECT(); +TfLiteRegistration* Register_SELECT_V2(); +TfLiteRegistration* Register_SHAPE(); +TfLiteRegistration* Register_SIN(); TfLiteRegistration* Register_SKIP_GRAM(); +TfLiteRegistration* Register_SLICE(); +TfLiteRegistration* Register_SOFTMAX(); +TfLiteRegistration* Register_SPACE_TO_BATCH_ND(); TfLiteRegistration* Register_SPACE_TO_DEPTH(); -TfLiteRegistration* Register_DEPTH_TO_SPACE(); -TfLiteRegistration* Register_GATHER(); -TfLiteRegistration* Register_TRANSPOSE(); -TfLiteRegistration* Register_MEAN(); +TfLiteRegistration* Register_SPARSE_TO_DENSE(); TfLiteRegistration* Register_SPLIT(); TfLiteRegistration* Register_SPLIT_V(); +TfLiteRegistration* Register_SQRT(); +TfLiteRegistration* Register_SQUARE(); +TfLiteRegistration* Register_SQUARED_DIFFERENCE(); TfLiteRegistration* Register_SQUEEZE(); TfLiteRegistration* Register_STRIDED_SLICE(); -TfLiteRegistration* Register_EXP(); -TfLiteRegistration* Register_TOPK_V2(); -TfLiteRegistration* Register_LOG(); -TfLiteRegistration* Register_LOG_SOFTMAX(); -TfLiteRegistration* Register_CAST(); -TfLiteRegistration* Register_DEQUANTIZE(); -TfLiteRegistration* Register_PRELU(); -TfLiteRegistration* Register_MAXIMUM(); -TfLiteRegistration* Register_MINIMUM(); -TfLiteRegistration* Register_ARG_MAX(); -TfLiteRegistration* Register_ARG_MIN(); -TfLiteRegistration* Register_GREATER(); -TfLiteRegistration* Register_GREATER_EQUAL(); -TfLiteRegistration* Register_LESS(); -TfLiteRegistration* Register_LESS_EQUAL(); -TfLiteRegistration* Register_FLOOR(); -TfLiteRegistration* Register_CEIL(); -TfLiteRegistration* Register_ROUND(); -TfLiteRegistration* Register_TILE(); -TfLiteRegistration* Register_NEG(); +TfLiteRegistration* Register_SUB(); TfLiteRegistration* Register_SUM(); -TfLiteRegistration* Register_REDUCE_PROD(); -TfLiteRegistration* Register_REDUCE_MAX(); -TfLiteRegistration* Register_REDUCE_MIN(); -TfLiteRegistration* Register_REDUCE_ANY(); -TfLiteRegistration* Register_SELECT(); -TfLiteRegistration* Register_SLICE(); -TfLiteRegistration* Register_SIN(); -TfLiteRegistration* Register_COS(); +TfLiteRegistration* Register_SVDF(); +TfLiteRegistration* Register_TANH(); +TfLiteRegistration* Register_TILE(); +TfLiteRegistration* Register_TOPK_V2(); +TfLiteRegistration* Register_TRANSPOSE(); TfLiteRegistration* Register_TRANSPOSE_CONV(); -TfLiteRegistration* Register_EXPAND_DIMS(); -TfLiteRegistration* Register_SPARSE_TO_DENSE(); -TfLiteRegistration* Register_EQUAL(); -TfLiteRegistration* Register_NOT_EQUAL(); -TfLiteRegistration* Register_SQRT(); -TfLiteRegistration* Register_RSQRT(); -TfLiteRegistration* Register_SHAPE(); -TfLiteRegistration* Register_RANK(); -TfLiteRegistration* Register_POW(); -TfLiteRegistration* Register_FAKE_QUANT(); -TfLiteRegistration* Register_PACK(); -TfLiteRegistration* Register_ONE_HOT(); -TfLiteRegistration* Register_LOGICAL_OR(); -TfLiteRegistration* Register_LOGICAL_AND(); -TfLiteRegistration* Register_LOGICAL_NOT(); -TfLiteRegistration* Register_UNPACK(); -TfLiteRegistration* Register_FLOOR_DIV(); -TfLiteRegistration* Register_SQUARE(); -TfLiteRegistration* Register_ZEROS_LIKE(); -TfLiteRegistration* Register_FLOOR_MOD(); -TfLiteRegistration* Register_RANGE(); -TfLiteRegistration* Register_LEAKY_RELU(); -TfLiteRegistration* Register_SQUARED_DIFFERENCE(); -TfLiteRegistration* Register_FILL(); -TfLiteRegistration* Register_MIRROR_PAD(); +TfLiteRegistration* Register_UNIDIRECTIONAL_SEQUENCE_LSTM(); +TfLiteRegistration* Register_UNIDIRECTIONAL_SEQUENCE_RNN(); TfLiteRegistration* Register_UNIQUE(); -TfLiteRegistration* Register_REVERSE_V2(); -TfLiteRegistration* Register_ADD_N(); -TfLiteRegistration* Register_GATHER_ND(); +TfLiteRegistration* Register_UNPACK(); TfLiteRegistration* Register_WHERE(); -TfLiteRegistration* Register_ELU(); -TfLiteRegistration* Register_REVERSE_SEQUENCE(); -TfLiteRegistration* Register_MATRIX_DIAG(); -TfLiteRegistration* Register_QUANTIZE(); -TfLiteRegistration* Register_MATRIX_SET_DIAG(); -TfLiteRegistration* Register_HARD_SWISH(); -TfLiteRegistration* Register_IF(); TfLiteRegistration* Register_WHILE(); -TfLiteRegistration* Register_NON_MAX_SUPPRESSION_V4(); -TfLiteRegistration* Register_NON_MAX_SUPPRESSION_V5(); -TfLiteRegistration* Register_SCATTER_ND(); +TfLiteRegistration* Register_ZEROS_LIKE(); } // namespace builtin } // namespace ops diff --git a/tensorflow/lite/kernels/dequantize.cc b/tensorflow/lite/kernels/dequantize.cc index d857a412205..272662d9c48 100644 --- a/tensorflow/lite/kernels/dequantize.cc +++ b/tensorflow/lite/kernels/dequantize.cc @@ -12,19 +12,15 @@ 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/lite/kernels/internal/reference/integer_ops/dequantize.h" +#include "tensorflow/lite/kernels/dequantize.h" #include #include #include -#include "third_party/eigen3/Eigen/Core" #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" -#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" -#include "tensorflow/lite/kernels/internal/tensor.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/kernels/op_macros.h" @@ -33,12 +29,6 @@ namespace ops { namespace builtin { namespace dequantize { -// This file has two implementation of Dequantize. -enum KernelType { - kReference, - kGenericOptimized, -}; - struct OpContext { OpContext(TfLiteContext* context, TfLiteNode* node) { input = GetInput(context, node, 0); @@ -93,69 +83,15 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { return kTfLiteOk; } - tflite::DequantizationParams op_params; - op_params.zero_point = op_context.input->params.zero_point; - op_params.scale = op_context.input->params.scale; - switch (op_context.input->type) { - case kTfLiteUInt8: - if (kernel_type == kReference) { - reference_ops::Dequantize(op_params, GetTensorShape(op_context.input), - GetTensorData(op_context.input), - GetTensorShape(op_context.output), - GetTensorData(op_context.output)); - } else { - optimized_ops::Dequantize(op_params, GetTensorShape(op_context.input), - GetTensorData(op_context.input), - GetTensorShape(op_context.output), - GetTensorData(op_context.output)); - } - break; - case kTfLiteInt8: - if (kernel_type == kReference) { - reference_integer_ops::Dequantize( - op_params, GetTensorShape(op_context.input), - GetTensorData(op_context.input), - GetTensorShape(op_context.output), - GetTensorData(op_context.output)); - } else { - optimized_ops::Dequantize(op_params, GetTensorShape(op_context.input), - GetTensorData(op_context.input), - GetTensorShape(op_context.output), - GetTensorData(op_context.output)); - } - break; - case kTfLiteInt16: - if (kernel_type == kReference) { - reference_integer_ops::Dequantize( - op_params, GetTensorShape(op_context.input), - GetTensorData(op_context.input), - GetTensorShape(op_context.output), - GetTensorData(op_context.output)); - } else { - optimized_ops::Dequantize(op_params, GetTensorShape(op_context.input), - GetTensorData(op_context.input), - GetTensorShape(op_context.output), - GetTensorData(op_context.output)); - } - break; - case kTfLiteFloat16: { - const Eigen::half* half_data = reinterpret_cast( - GetTensorData(op_context.input)); - reference_ops::Dequantize(GetTensorShape(op_context.input), half_data, - GetTensorShape(op_context.output), - GetTensorData(op_context.output)); - break; - } - default: - context->ReportError(context, "Type %d not supported.", - op_context.input->type); - return kTfLiteError; + auto status = DequantizeImpl(context, node, op_context.input, + op_context.output); + if (status != kTfLiteOk) { + return status; } if (IsConstantTensor(op_context.input)) { op_data->float_dequantized_weights_initialized = true; } - return kTfLiteOk; } diff --git a/tensorflow/lite/kernels/dequantize.h b/tensorflow/lite/kernels/dequantize.h new file mode 100644 index 00000000000..3d9e7ccb135 --- /dev/null +++ b/tensorflow/lite/kernels/dequantize.h @@ -0,0 +1,101 @@ +/* Copyright 2019 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_LITE_KERNELS_DEQUANTIZE_H_ +#define TENSORFLOW_LITE_KERNELS_DEQUANTIZE_H_ + +#include "third_party/eigen3/Eigen/Core" +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" +#include "tensorflow/lite/kernels/internal/reference/integer_ops/dequantize.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/internal/tensor.h" +#include "tensorflow/lite/kernels/internal/tensor_ctypes.h" +#include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/kernels/op_macros.h" + +namespace tflite { +namespace ops { +namespace builtin { +namespace dequantize { + +// This file has two implementation of Dequantize. +enum KernelType { + kReference, + kGenericOptimized, +}; + +template +TfLiteStatus DequantizeImpl(TfLiteContext* context, TfLiteNode* node, + const TfLiteTensor* input, TfLiteTensor* output) { + DequantizationParams op_params; + op_params.zero_point = input->params.zero_point; + op_params.scale = input->params.scale; + switch (input->type) { + case kTfLiteUInt8: + if (kernel_type == kReference) { + reference_ops::Dequantize( + op_params, GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + } else { + optimized_ops::Dequantize( + op_params, GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + } + break; + case kTfLiteInt8: + if (kernel_type == kReference) { + reference_integer_ops::Dequantize( + op_params, GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + } else { + optimized_ops::Dequantize( + op_params, GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + } + break; + case kTfLiteInt16: + if (kernel_type == kReference) { + reference_integer_ops::Dequantize( + op_params, GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + } else { + optimized_ops::Dequantize( + op_params, GetTensorShape(input), GetTensorData(input), + GetTensorShape(output), GetTensorData(output)); + } + break; + case kTfLiteFloat16: { + const Eigen::half* half_data = reinterpret_cast( + GetTensorData(input)); + reference_ops::Dequantize(GetTensorShape(input), half_data, + GetTensorShape(output), + GetTensorData(output)); + break; + } + default: + context->ReportError(context, "Type %d not supported.", input->type); + return kTfLiteError; + } + + return kTfLiteOk; +} + +} // namespace dequantize +} // namespace builtin +} // namespace ops +} // namespace tflite + +#endif // TENSORFLOW_LITE_KERNELS_DEQUANTIZE_H_ diff --git a/tensorflow/lite/kernels/internal/BUILD b/tensorflow/lite/kernels/internal/BUILD index e82d3c16b31..2367174c1b3 100644 --- a/tensorflow/lite/kernels/internal/BUILD +++ b/tensorflow/lite/kernels/internal/BUILD @@ -1,5 +1,6 @@ load("//tensorflow:tensorflow.bzl", "transitive_hdrs") load("//tensorflow/lite:build_def.bzl", "tflite_copts") +load("//tensorflow/lite/micro:build_def.bzl", "cc_library") load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite_combined") package( @@ -41,6 +42,7 @@ NEON_FLAGS_IF_APPLICABLE = select({ cc_library( name = "compatibility", hdrs = ["compatibility.h"], + build_for_embedded = True, copts = tflite_copts(), deps = [ "//tensorflow/lite/kernels:op_macros", @@ -51,14 +53,16 @@ cc_library( name = "scoped_profiling_label_wrapper", hdrs = ["scoped_profiling_label_wrapper.h"], copts = tflite_copts(), - deps = [ - "@gemmlowp//:profiler", - ], + deps = select({ + "//tensorflow/lite:gemmlowp_profiling": ["@gemmlowp//:profiler"], + "//conditions:default": [], + }), ) cc_library( name = "types", hdrs = ["types.h"], + build_for_embedded = True, copts = tflite_copts(), deps = [ ":compatibility", @@ -439,10 +443,12 @@ cc_library( "reference/mul.h", "reference/neg.h", "reference/non_max_suppression.h", + "reference/pad.h", "reference/pooling.h", "reference/prelu.h", "reference/process_broadcast_shapes.h", "reference/quantize.h", + "reference/reduce.h", "reference/reference_ops.h", "reference/round.h", "reference/softmax.h", @@ -498,10 +504,12 @@ cc_library( "reference/maximum_minimum.h", "reference/mul.h", "reference/neg.h", + "reference/pad.h", "reference/pooling.h", "reference/prelu.h", "reference/process_broadcast_shapes.h", "reference/quantize.h", + "reference/reduce.h", "reference/reference_ops.h", "reference/round.h", "reference/softmax.h", @@ -541,6 +549,7 @@ cc_library( "tensor.h", "tensor_ctypes.h", ], + build_for_embedded = True, copts = tflite_copts(), deps = [ ":types", diff --git a/tensorflow/lite/kernels/internal/common.h b/tensorflow/lite/kernels/internal/common.h index 5e4ba25b711..8b4ecfa0f42 100644 --- a/tensorflow/lite/kernels/internal/common.h +++ b/tensorflow/lite/kernels/internal/common.h @@ -581,6 +581,18 @@ inline void NdArrayDescsForElementwiseBroadcast(const Dims& input0_dims, } } +// Copies dims to desc, calculating strides. +template +inline void CopyDimsToDesc(const RuntimeShape& input_shape, + NdArrayDesc* desc_out) { + int desc_stride = 1; + for (int i = N - 1; i >= 0; --i) { + desc_out->extents[i] = input_shape.Dims(i); + desc_out->strides[i] = desc_stride; + desc_stride *= input_shape.Dims(i); + } +} + template inline void NdArrayDescsForElementwiseBroadcast( const RuntimeShape& input0_shape, const RuntimeShape& input1_shape, @@ -592,16 +604,8 @@ inline void NdArrayDescsForElementwiseBroadcast( auto extended_input1_shape = RuntimeShape::ExtendedShape(N, input1_shape); // Copy dims to desc, calculating strides. - int desc0_stride = 1; - int desc1_stride = 1; - for (int i = N - 1; i >= 0; --i) { - desc0_out->extents[i] = extended_input0_shape.Dims(i); - desc0_out->strides[i] = desc0_stride; - desc0_stride *= extended_input0_shape.Dims(i); - desc1_out->extents[i] = extended_input1_shape.Dims(i); - desc1_out->strides[i] = desc1_stride; - desc1_stride *= extended_input1_shape.Dims(i); - } + CopyDimsToDesc(extended_input0_shape, desc0_out); + CopyDimsToDesc(extended_input1_shape, desc1_out); // Walk over each dimension. If the extents are equal do nothing. // Otherwise, set the desc with extent 1 to have extent equal to the other and @@ -622,6 +626,57 @@ inline void NdArrayDescsForElementwiseBroadcast( } } +template +inline void NdArrayDescsForElementwiseBroadcast( + const RuntimeShape& input0_shape, const RuntimeShape& input1_shape, + const RuntimeShape& input2_shape, NdArrayDesc* desc0_out, + NdArrayDesc* desc1_out, NdArrayDesc* desc2_out) { + TFLITE_DCHECK(desc0_out != nullptr); + TFLITE_DCHECK(desc1_out != nullptr); + TFLITE_DCHECK(desc2_out != nullptr); + + auto extended_input0_shape = RuntimeShape::ExtendedShape(N, input0_shape); + auto extended_input1_shape = RuntimeShape::ExtendedShape(N, input1_shape); + auto extended_input2_shape = RuntimeShape::ExtendedShape(N, input2_shape); + + // Copy dims to desc, calculating strides. + CopyDimsToDesc(extended_input0_shape, desc0_out); + CopyDimsToDesc(extended_input1_shape, desc1_out); + CopyDimsToDesc(extended_input2_shape, desc2_out); + + // Walk over each dimension. If the extents are equal do nothing. + // Otherwise, set the desc with extent 1 to have extent equal to the other and + // stride 0. + for (int i = 0; i < N; ++i) { + const int extent0 = extended_input0_shape.Dims(i); + const int extent1 = extended_input1_shape.Dims(i); + const int extent2 = extended_input2_shape.Dims(i); + + int extent = extent0; + if (extent1 != 1) extent = extent1; + if (extent2 != 1) extent = extent2; + + TFLITE_DCHECK(extent0 == 1 || extent0 == extent); + TFLITE_DCHECK(extent1 == 1 || extent1 == extent); + TFLITE_DCHECK(extent2 == 1 || extent2 == extent); + + if (!(extent0 == extent1 && extent1 == extent2)) { + if (extent0 == 1) { + desc0_out->strides[i] = 0; + desc0_out->extents[i] = extent; + } + if (extent1 == 1) { + desc1_out->strides[i] = 0; + desc1_out->extents[i] = extent; + } + if (extent2 == 1) { + desc2_out->strides[i] = 0; + desc2_out->extents[i] = extent; + } + } + } +} + // Copied from gemmlowp::RoundDown when we dropped direct dependency on // gemmlowp. // diff --git a/tensorflow/lite/kernels/internal/depthwiseconv_per_channel_quantized_test.cc b/tensorflow/lite/kernels/internal/depthwiseconv_per_channel_quantized_test.cc index eec483d44fd..3c809560c35 100644 --- a/tensorflow/lite/kernels/internal/depthwiseconv_per_channel_quantized_test.cc +++ b/tensorflow/lite/kernels/internal/depthwiseconv_per_channel_quantized_test.cc @@ -139,7 +139,6 @@ void PickReasonableMultiplier( } } -#if defined(__aarch64__) && !defined(GOOGLE_L4T) // The reference implementation & the fast kernel have different rounding // mechanism, so we loosely compare the difference. void CompareRoundingResults(int flat_size, const int depth_multiplier, @@ -187,7 +186,6 @@ void CompareRoundingResults(int flat_size, const int depth_multiplier, std::abs(min_diff) <= diff_mean_tolerance && std::abs(max_diff) <= diff_mean_tolerance); } -#endif bool GenerateValidShapeConfigurations( int filter_width, int filter_height, int depth_multiplier, @@ -325,7 +323,11 @@ void TryTestOneDepthwiseConv3x3Filter() { /*thread_start=*/0, /*thread_end=*/output_shape_inference.Dims(1), /*thread_dim=*/1); - EXPECT_EQ(reference_output_data, neon_output_data); + // We have changed our rounding strategy to the ARM rounding-right-shift + // instruction: breaking tie upward as it's much simpler. + // So we allow some difference for the neon output VS. the reference output. + CompareRoundingResults(output_buffer_size, depth_multiplier, + reference_output_data.data(), neon_output_data.data()); #if defined(__aarch64__) && !defined(GOOGLE_L4T) std::vector fast_kernel_output_data(output_buffer_size); diff --git a/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h b/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h index ec4f664b9fe..2c3de35135b 100644 --- a/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h +++ b/tensorflow/lite/kernels/internal/optimized/depthwiseconv_uint8_3x3_filter.h @@ -7212,7 +7212,6 @@ struct KernelMacroBlock= 0) && - (vgetq_lane_s32(out_shift, 1) >= 0) && - (vgetq_lane_s32(out_shift, 2) >= 0) && - (vgetq_lane_s32(out_shift, 3) >= 0); - if (!out_shift_all_less_than_zero && - !out_shift_all_greater_equal_than_zero) { - // Fallback to general path. - // Then go ahead for next 4. - target_output_depth = c + 4; - break; - } - int32x4_t out_mul = vld1q_s32(multiplier + c); + for (; c <= channel_size - 8; c += 8) { + int32x4_t out_shift_1 = vld1q_s32(shift + c); + int32x4_t out_shift_2 = vld1q_s32(shift + c + 4); + int32x4_t left_shift_1 = vmaxq_s32(out_shift_1, zeros); + int32x4_t left_shift_2 = vmaxq_s32(out_shift_2, zeros); + + // Right shift will be performed as left shift with negative values. + int32x4_t right_shift_1 = vminq_s32(out_shift_1, zeros); + int32x4_t right_shift_2 = vminq_s32(out_shift_2, zeros); + + int32x4_t out_mul_1 = vld1q_s32(multiplier + c); + int32x4_t out_mul_2 = vld1q_s32(multiplier + c + 4); for (int n = 0; n < rows; ++n) { int loc = n * channel_size + c; - int32x4_t acc = vld1q_s32(scratch + loc); - if (out_shift_all_less_than_zero) { // output_shift all < 0 case. - acc = vqrdmulhq_s32(acc, out_mul); - int32x4_t negative_out_shift = vmulq_n_s32(out_shift, -1); - int32x4_t mask = - vaddq_s32(vshlq_s32(ones, negative_out_shift), minus_ones); - int32x4_t remainder = vandq_s32(acc, mask); - int32x4_t shifted_right_mask = vshlq_s32(mask, minus_ones); - int32x4_t temp = - vandq_s32(vreinterpretq_s32_u32(vcltq_s32(acc, zeros)), ones); - int32x4_t threshold = vaddq_s32(shifted_right_mask, temp); - temp = vandq_s32( - vreinterpretq_s32_u32(vcgtq_s32(remainder, threshold)), ones); - int32x4_t shifted_right_acc = vshlq_s32(acc, out_shift); - acc = vaddq_s32(shifted_right_acc, temp); - } else { // output_shift all > 0 case. - int32x4_t multiplier_power_of_two = vshlq_s32(ones, out_shift); - acc = vmulq_s32(acc, multiplier_power_of_two); - acc = vqrdmulhq_s32(acc, out_mul); - } + int32x4_t acc_1 = vld1q_s32(scratch + loc); + int32x4_t acc_2 = vld1q_s32(scratch + loc + 4); + + // Saturating Rounding Doubling High Mul. + acc_1 = vshlq_s32(acc_1, left_shift_1); + acc_1 = vqrdmulhq_s32(acc_1, out_mul_1); + acc_2 = vshlq_s32(acc_2, left_shift_2); + acc_2 = vqrdmulhq_s32(acc_2, out_mul_2); + + // Rounding Dividing By POT. + acc_1 = vrshlq_s32(acc_1, right_shift_1); + acc_2 = vrshlq_s32(acc_2, right_shift_2); + // Add the output offset. - acc = vaddq_s32(acc, output_offset_vec); + acc_1 = vaddq_s32(acc_1, output_offset_vec); + acc_2 = vaddq_s32(acc_2, output_offset_vec); + // Apply the activation function. - acc = vmaxq_s32(acc, output_activation_min_vec); - acc = vminq_s32(acc, output_activation_max_vec); + acc_1 = vmaxq_s32(acc_1, output_activation_min_vec); + acc_1 = vminq_s32(acc_1, output_activation_max_vec); + acc_2 = vmaxq_s32(acc_2, output_activation_min_vec); + acc_2 = vminq_s32(acc_2, output_activation_max_vec); + // Saturating cast to int8 and store to destination. - const int16x4_t acc_s16 = vqmovn_s32(acc); - const int16x8_t res_s16 = vcombine_s16(acc_s16, acc_s16); + const int16x4_t acc_s16_1 = vqmovn_s32(acc_1); + const int16x4_t acc_s16_2 = vqmovn_s32(acc_2); + const int16x8_t res_s16 = vcombine_s16(acc_s16_1, acc_s16_2); const int8x8_t res_s8 = vqmovn_s16(res_s16); - vst1_lane_s8(output + loc + 0, res_s8, 0); - vst1_lane_s8(output + loc + 1, res_s8, 1); - vst1_lane_s8(output + loc + 2, res_s8, 2); - vst1_lane_s8(output + loc + 3, res_s8, 3); + vst1_s8(output + loc, res_s8); } } #endif // USE_NEON // Handle leftover values, one by one. This is very slow. - for (; c < target_output_depth; c++) { + for (; c < channel_size; c++) { for (int n = 0; n < rows; ++n) { int loc = n * channel_size + c; int32 acc = scratch[loc]; @@ -5722,7 +5706,6 @@ inline void Quantize(const int32_t* multiplier, const int32_t* shift, output[loc] = static_cast(acc); } } - } } // TransposeConvV2 expect the weights in HWOI order. diff --git a/tensorflow/lite/kernels/internal/reference/pad.h b/tensorflow/lite/kernels/internal/reference/pad.h new file mode 100644 index 00000000000..e20aa5e4b2a --- /dev/null +++ b/tensorflow/lite/kernels/internal/reference/pad.h @@ -0,0 +1,184 @@ +/* Copyright 2019 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_LITE_KERNELS_INTERNAL_REFERENCE_PAD_H_ +#define TENSORFLOW_LITE_KERNELS_INTERNAL_REFERENCE_PAD_H_ + +#include + +#include "tensorflow/lite/kernels/internal/types.h" + +namespace tflite { + +namespace reference_ops { + +// TFLite Pad supports activation tensors with up to 4 dimensions. +constexpr int PadKernelMaxDimensionCount() { return 4; } + +// There are two versions of pad: Pad and PadV2. In PadV2 there is a second +// scalar input that provides the padding value. Therefore pad_value_ptr can be +// equivalent to a simple input1_data. For Pad, it should point to a zero +// value. +// +// Note that two typenames are required, so that T=P=int32 is considered a +// specialization distinct from P=int32. +template +inline void PadImpl(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, const T* input_data, + const P* pad_value_ptr, const RuntimeShape& output_shape, + T* output_data) { + const RuntimeShape ext_input_shape = + RuntimeShape::ExtendedShape(PadKernelMaxDimensionCount(), input_shape); + const RuntimeShape ext_output_shape = + RuntimeShape::ExtendedShape(PadKernelMaxDimensionCount(), output_shape); + TFLITE_DCHECK_LE(op_params.left_padding_count, PadKernelMaxDimensionCount()); + TFLITE_DCHECK_LE(op_params.right_padding_count, PadKernelMaxDimensionCount()); + + // Runtime calls are currently fixed at 4 dimensions. Copy inputs so we can + // pad them to 4 dims (yes, we are "padding the padding"). + int left_padding_copy[PadKernelMaxDimensionCount()]; + for (int i = 0; i < PadKernelMaxDimensionCount(); i++) { + left_padding_copy[i] = 0; + } + for (int i = 0; i < op_params.left_padding_count; ++i) { + left_padding_copy[i + PadKernelMaxDimensionCount() - + op_params.left_padding_count] = op_params.left_padding[i]; + } + int right_padding_copy[PadKernelMaxDimensionCount()]; + for (int i = 0; i < PadKernelMaxDimensionCount(); i++) { + right_padding_copy[i] = 0; + } + for (int i = 0; i < op_params.right_padding_count; ++i) { + right_padding_copy[i + PadKernelMaxDimensionCount() - + op_params.right_padding_count] = + op_params.right_padding[i]; + } + + const int output_batch = ext_output_shape.Dims(0); + const int output_height = ext_output_shape.Dims(1); + const int output_width = ext_output_shape.Dims(2); + const int output_depth = ext_output_shape.Dims(3); + + const int left_b_padding = left_padding_copy[0]; + const int left_h_padding = left_padding_copy[1]; + const int left_w_padding = left_padding_copy[2]; + const int left_d_padding = left_padding_copy[3]; + + const int right_b_padding = right_padding_copy[0]; + const int right_h_padding = right_padding_copy[1]; + const int right_w_padding = right_padding_copy[2]; + const int right_d_padding = right_padding_copy[3]; + + const T pad_value = *pad_value_ptr; + + const T* in_ptr = input_data; + T* out_ptr = output_data; + for (int out_b = 0; out_b < output_batch; ++out_b) { + for (int out_h = 0; out_h < output_height; ++out_h) { + for (int out_w = 0; out_w < output_width; ++out_w) { + for (int out_d = 0; out_d < output_depth; ++out_d) { + if (out_b < left_b_padding || + out_b >= output_batch - right_b_padding || + out_h < left_h_padding || + out_h >= output_height - right_h_padding || + out_w < left_w_padding || + out_w >= output_width - right_w_padding || + out_d < left_d_padding || + out_d >= output_depth - right_d_padding) { + *out_ptr++ = pad_value; + } else { + *out_ptr++ = *in_ptr++; + } + } + } + } + } +} + +template +inline void Pad(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, const T* input_data, + const P* pad_value_ptr, const RuntimeShape& output_shape, + T* output_data) { + PadImpl(op_params, input_shape, input_data, pad_value_ptr, output_shape, + output_data); +} + +// The second (pad-value) input can be int32 when, say, the first is uint8. +template +inline void Pad(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, const T* input_data, + const int32* pad_value_ptr, const RuntimeShape& output_shape, + T* output_data) { + const T converted_pad_value = static_cast(*pad_value_ptr); + PadImpl(op_params, input_shape, input_data, &converted_pad_value, + output_shape, output_data); +} + +// This version avoids conflicting template matching. +template <> +inline void Pad(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, const int32* input_data, + const int32* pad_value_ptr, const RuntimeShape& output_shape, + int32* output_data) { + PadImpl(op_params, input_shape, input_data, pad_value_ptr, output_shape, + output_data); +} + +// One could make all PadImageStyle calls simply delegate the work to the +// ordinary Pad. However, it is better that the reference code asserts false in +// similar cases. +template +inline void PadImageStyle(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, const T* input_data, + const P* pad_value_ptr, + const RuntimeShape& output_shape, T* output_data) { + TFLITE_ASSERT_FALSE; +} + +template +inline void PadImageStyle(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, + const uint8* input_data, const P* pad_value_ptr, + const RuntimeShape& output_shape, + uint8* output_data) { + Pad(op_params, input_shape, input_data, pad_value_ptr, output_shape, + output_data); +} + +template +inline void PadImageStyle(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, + const int8_t* input_data, const P* pad_value_ptr, + const RuntimeShape& output_shape, + int8_t* output_data) { + Pad(op_params, input_shape, input_data, pad_value_ptr, output_shape, + output_data); +} + +template +inline void PadImageStyle(const tflite::PadParams& op_params, + const RuntimeShape& input_shape, + const float* input_data, const P* pad_value_ptr, + const RuntimeShape& output_shape, + float* output_data) { + Pad(op_params, input_shape, input_data, pad_value_ptr, output_shape, + output_data); +} + +} // namespace reference_ops +} // namespace tflite + +#endif // TENSORFLOW_LITE_KERNELS_INTERNAL_REFERENCE_PAD_H_ diff --git a/tensorflow/lite/kernels/internal/reference/reduce.h b/tensorflow/lite/kernels/internal/reference/reduce.h new file mode 100644 index 00000000000..77e22dd16b6 --- /dev/null +++ b/tensorflow/lite/kernels/internal/reference/reduce.h @@ -0,0 +1,396 @@ +/* Copyright 2019 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_LITE_KERNELS_INTERNAL_REFERENCE_REDUCE_H_ +#define TENSORFLOW_LITE_KERNELS_INTERNAL_REFERENCE_REDUCE_H_ + +#include "tensorflow/lite/kernels/internal/common.h" +#include "tensorflow/lite/kernels/internal/quantization_util.h" +#include "tensorflow/lite/kernels/internal/scoped_profiling_label_wrapper.h" +#include "tensorflow/lite/kernels/internal/types.h" + +namespace tflite { + +namespace reference_ops { + +// A generic reduce method that can be used for reduce_sum, reduce_mean, etc. +// This method iterates through input data and reduce elements along the +// dimensions given in axis. +template +inline bool Reduce(const In* input_data, const int* input_dims, + const int* output_dims, const int input_num_dims, + const int output_num_dims, const int* axis, + const int num_axis, int* input_iter, + Out reducer(const Out current, const In in), + Out* output_data) { + // Reset input iterator. + for (int idx = 0; idx < input_num_dims; ++idx) { + input_iter[idx] = 0; + } + // Iterate through input_data. + do { + size_t input_offset = + ReducedOutputOffset(input_num_dims, input_dims, input_iter, 0, nullptr); + size_t output_offset = ReducedOutputOffset(input_num_dims, input_dims, + input_iter, num_axis, axis); + output_data[output_offset] = + reducer(output_data[output_offset], input_data[input_offset]); + } while (NextIndex(input_num_dims, input_dims, input_iter)); + return true; +} + +// This method parses the input 'axis' to remove duplicates and handle negative +// values, and returns a valid 'out_axis' +inline bool ResolveAxis(const int num_dims, const int* axis, + const int64_t num_axis, int* out_axis, + int* out_num_axis) { + *out_num_axis = 0; // Just in case. + // Short-circuit axis resolution for scalars; the axis will go unused. + if (num_dims == 0) { + return true; + } + // o(n^2) is fine since out_num_axis should be really small, mostly <= 4 + for (int64_t idx = 0; idx < num_axis; ++idx) { + // Handle negative index. A positive index 'p_idx' can be represented as a + // negative index 'n_idx' as: n_idx = p_idx-num_dims + // eg: For num_dims=3, [0, 1, 2] is the same as [-3, -2, -1] */ + int current = axis[idx] < 0 ? (axis[idx] + num_dims) : axis[idx]; + TFLITE_DCHECK(current >= 0 && current < num_dims); + bool is_dup = false; + for (int j = 0; j < *out_num_axis; ++j) { + if (out_axis[j] == current) { + is_dup = true; + break; + } + } + if (!is_dup) { + out_axis[*out_num_axis] = current; + *out_num_axis += 1; + } + } + return true; +} + +// This method expects that output_data has been initialized. +template +inline bool ReduceSumImpl(const In* input_data, const int* input_dims, + const int* output_dims, const int input_num_dims, + const int output_num_dims, const int* axis, + const int num_axis, int* input_iter, + Out* output_data) { + auto reducer = [](const Out current, const In in) -> Out { + const Out actual_in = static_cast(in); + return current + actual_in; + }; + return Reduce(input_data, input_dims, output_dims, input_num_dims, + output_num_dims, axis, num_axis, input_iter, reducer, + output_data); +} + +template +inline bool InitTensorDataForReduce(const int* dims, const int num_dims, + const T init_value, T* data) { + size_t num_elements = 1; + for (int idx = 0; idx < num_dims; ++idx) { + size_t current = static_cast(dims[idx]); + // Overflow prevention. + if (num_elements > std::numeric_limits::max() / current) { + return false; + } + num_elements *= current; + } + for (size_t idx = 0; idx < num_elements; ++idx) { + data[idx] = init_value; + } + return true; +} + +// Computes the generic value (i.e., sum/max/min/prod) of elements across +// dimensions given in axis. It needs to pass in init_value and reducer. +template +inline bool ReduceGeneric(const T* input_data, const int* input_dims, + const int input_num_dims, T* output_data, + const int* output_dims, const int output_num_dims, + const int* axis, const int64_t num_axis_dimensions, + bool keep_dims, int* temp_index, int* resolved_axis, + T init_value, + T reducer(const T current, const T in)) { + // Reset output data. + if (!InitTensorDataForReduce(output_dims, output_num_dims, init_value, + output_data)) { + return false; + } + + // Resolve axis. + int num_resolved_axis = 0; + if (!ResolveAxis(input_num_dims, axis, num_axis_dimensions, resolved_axis, + &num_resolved_axis)) { + return false; + } + + return Reduce(input_data, input_dims, output_dims, input_num_dims, + output_num_dims, resolved_axis, num_resolved_axis, + temp_index, reducer, output_data); +} + +// Computes the mean of elements across dimensions given in axis. +// It does so in two stages, first calculates the sum of elements along the axis +// then divides it by the number of element in axis. +template +inline bool Mean(const T* input_data, const int* input_dims, + const int input_num_dims, T* output_data, + const int* output_dims, const int output_num_dims, + const int* axis, const int num_axis_dimensions, bool keep_dims, + int* temp_index, int* resolved_axis, U* temp_sum) { + ScopedProfilingLabelWrapper label("Mean"); + // Reset output data. + size_t num_outputs = 1; + for (int idx = 0; idx < output_num_dims; ++idx) { + size_t current = static_cast(output_dims[idx]); + // Overflow prevention. + if (num_outputs > std::numeric_limits::max() / current) { + return false; + } + num_outputs *= current; + } + for (size_t idx = 0; idx < num_outputs; ++idx) { + output_data[idx] = T(); + temp_sum[idx] = U(); + } + + // Resolve axis. + int num_resolved_axis = 0; + if (!ResolveAxis(input_num_dims, axis, num_axis_dimensions, resolved_axis, + &num_resolved_axis)) { + return false; + } + + if (!ReduceSumImpl(input_data, input_dims, output_dims, input_num_dims, + output_num_dims, resolved_axis, num_resolved_axis, + temp_index, temp_sum)) { + return false; + } + + // Calculate mean by dividing output_data by num of aggregated element. + U num_elements_in_axis = 1; + for (int idx = 0; idx < num_resolved_axis; ++idx) { + size_t current = static_cast(input_dims[resolved_axis[idx]]); + // Overflow prevention. + if (current > (std::numeric_limits::max() / num_elements_in_axis)) { + return false; + } + num_elements_in_axis *= current; + } + + if (num_elements_in_axis > 0) { + for (size_t idx = 0; idx < num_outputs; ++idx) { + output_data[idx] = + static_cast(temp_sum[idx] / static_cast(num_elements_in_axis)); + } + } + return true; +} + +template +inline void Mean(const tflite::MeanParams& op_params, + const RuntimeShape& unextended_input_shape, + const T* input_data, + const RuntimeShape& unextended_output_shape, T* output_data) { + ScopedProfilingLabelWrapper label("Mean4D"); + + // Current implementation only supports dimension equals 4 and simultaneous + // reduction over width and height. + TFLITE_CHECK_EQ(unextended_input_shape.DimensionsCount(), 4); + TFLITE_CHECK_LE(unextended_output_shape.DimensionsCount(), 4); + const RuntimeShape input_shape = + RuntimeShape::ExtendedShape(4, unextended_input_shape); + const RuntimeShape output_shape = + RuntimeShape::ExtendedShape(4, unextended_output_shape); + + const int output_batch = output_shape.Dims(0); + const int output_height = output_shape.Dims(1); + const int output_width = output_shape.Dims(2); + const int output_depth = output_shape.Dims(3); + + const int input_height = input_shape.Dims(1); + const int input_width = input_shape.Dims(2); + + TFLITE_CHECK_EQ(op_params.axis_count, 2); + TFLITE_CHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || + (op_params.axis[0] == 2 && op_params.axis[1] == 1)); + TFLITE_CHECK_EQ(output_height, 1); + TFLITE_CHECK_EQ(output_width, 1); + + for (int out_b = 0; out_b < output_batch; ++out_b) { + for (int out_d = 0; out_d < output_depth; ++out_d) { + float value = 0; + for (int in_h = 0; in_h < input_height; ++in_h) { + for (int in_w = 0; in_w < input_width; ++in_w) { + value += input_data[Offset(input_shape, out_b, in_h, in_w, out_d)]; + } + } + output_data[Offset(output_shape, out_b, 0, 0, out_d)] = + value / (input_width * input_height); + } + } +} + +inline void Mean(const tflite::MeanParams& op_params, + const RuntimeShape& unextended_input_shape, + const uint8_t* input_data, int32 input_zero_point, + float input_scale, const RuntimeShape& unextended_output_shape, + uint8_t* output_data, int32 output_zero_point, + float output_scale) { + ScopedProfilingLabelWrapper label("Mean4D/Uint8"); + + // Current implementation only supports dimension equals 4 and simultaneous + // reduction over width and height. + TFLITE_CHECK_EQ(unextended_input_shape.DimensionsCount(), 4); + TFLITE_CHECK_LE(unextended_output_shape.DimensionsCount(), 4); + const RuntimeShape input_shape = + RuntimeShape::ExtendedShape(4, unextended_input_shape); + const RuntimeShape output_shape = + RuntimeShape::ExtendedShape(4, unextended_output_shape); + const int output_batch = output_shape.Dims(0); + const int output_height = output_shape.Dims(1); + const int output_width = output_shape.Dims(2); + const int output_depth = output_shape.Dims(3); + const int input_height = input_shape.Dims(1); + const int input_width = input_shape.Dims(2); + const float num_elements_in_axis = input_width * input_height; + + TFLITE_CHECK_EQ(op_params.axis_count, 2); + TFLITE_CHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || + (op_params.axis[0] == 2 && op_params.axis[1] == 1)); + TFLITE_CHECK_EQ(output_height, 1); + TFLITE_CHECK_EQ(output_width, 1); + + constexpr int32_t kMinValue = std::numeric_limits::min(); + constexpr int32_t kMaxValue = std::numeric_limits::max(); + + int32 bias = + output_zero_point - + static_cast(input_zero_point * input_scale / output_scale); + float real_scale = input_scale / (num_elements_in_axis * output_scale); + + int32 multiplier, shift; + QuantizeMultiplier(real_scale, &multiplier, &shift); + for (int out_b = 0; out_b < output_batch; ++out_b) { + for (int out_d = 0; out_d < output_depth; ++out_d) { + int acc = 0; + for (int in_h = 0; in_h < input_height; ++in_h) { + for (int in_w = 0; in_w < input_width; ++in_w) { + acc += input_data[Offset(input_shape, out_b, in_h, in_w, out_d)]; + } + } + acc = MultiplyByQuantizedMultiplier(acc, multiplier, shift); + acc += bias; + acc = std::min(std::max(acc, kMinValue), kMaxValue); + output_data[Offset(output_shape, out_b, 0, 0, out_d)] = + static_cast(acc); + } + } +} + +// Computes the mean of elements across dimensions given in axis. +// It does so in two stages, first calculates the sum of elements along the axis +// then divides it by the number of element in axis for quantized values. +template +inline bool QuantizedMeanOrSum(const T* input_data, int32 input_zero_point, + float input_scale, const int* input_dims, + const int input_num_dims, T* output_data, + int32 output_zero_point, float output_scale, + const int* output_dims, + const int output_num_dims, const int* axis, + const int num_axis_dimensions, bool keep_dims, + int* temp_index, int* resolved_axis, U* temp_sum, + bool compute_sum) { + const bool uint8_case = std::is_same::value; + if (uint8_case) { + ScopedProfilingLabelWrapper label(compute_sum ? "Sum/Uint8" : "Mean/Uint8"); + } else { + ScopedProfilingLabelWrapper label(compute_sum ? "Sum/Int8" : "Mean/Int8"); + } + // Reset output data. + size_t num_outputs = 1; + for (int idx = 0; idx < output_num_dims; ++idx) { + size_t current = static_cast(output_dims[idx]); + // Overflow prevention. + if (num_outputs > std::numeric_limits::max() / current) { + return false; + } + num_outputs *= current; + } + for (size_t idx = 0; idx < num_outputs; ++idx) { + output_data[idx] = T(); + temp_sum[idx] = U(); + } + + // Resolve axis. + int num_resolved_axis = 0; + if (!ResolveAxis(input_num_dims, axis, num_axis_dimensions, resolved_axis, + &num_resolved_axis)) { + return false; + } + + if (!ReduceSumImpl(input_data, input_dims, output_dims, input_num_dims, + output_num_dims, resolved_axis, num_resolved_axis, + temp_index, temp_sum)) { + return false; + } + + // Calculate mean by dividing output_data by num of aggregated element. + U num_elements_in_axis = 1; + for (int idx = 0; idx < num_resolved_axis; ++idx) { + size_t current = static_cast(input_dims[resolved_axis[idx]]); + // Overflow prevention. + if (current > (std::numeric_limits::max() / num_elements_in_axis)) { + return false; + } + num_elements_in_axis *= current; + } + + if (num_elements_in_axis > 0) { + const float scale = input_scale / output_scale; + if (compute_sum) { + // TODO(b/116341117): Eliminate float and do this completely in 8bit. + const float bias = -input_zero_point * scale * num_elements_in_axis + 0.5; + for (size_t idx = 0; idx < num_outputs; ++idx) { + const U value = + static_cast(std::round(temp_sum[idx] * scale + bias)) + + output_zero_point; + output_data[idx] = static_cast(value); + } + } else { + const float bias = -input_zero_point * scale + 0.5; + for (size_t idx = 0; idx < num_outputs; ++idx) { + float float_mean = static_cast(temp_sum[idx]) / + static_cast(num_elements_in_axis); + float result = + std::min(std::round(float_mean * scale + bias) + output_zero_point, + static_cast(std::numeric_limits::max())); + result = + std::max(result, static_cast(std::numeric_limits::min())); + output_data[idx] = static_cast(result); + } + } + } + return true; +} + +} // namespace reference_ops + +} // namespace tflite + +#endif // TENSORFLOW_LITE_KERNELS_INTERNAL_REFERENCE_REDUCE_H_ diff --git a/tensorflow/lite/kernels/internal/reference/reference_ops.h b/tensorflow/lite/kernels/internal/reference/reference_ops.h index 53b2049d74a..b3969d24381 100644 --- a/tensorflow/lite/kernels/internal/reference/reference_ops.h +++ b/tensorflow/lite/kernels/internal/reference/reference_ops.h @@ -45,10 +45,12 @@ limitations under the License. #include "tensorflow/lite/kernels/internal/reference/maximum_minimum.h" #include "tensorflow/lite/kernels/internal/reference/mul.h" #include "tensorflow/lite/kernels/internal/reference/neg.h" +#include "tensorflow/lite/kernels/internal/reference/pad.h" #include "tensorflow/lite/kernels/internal/reference/pooling.h" #include "tensorflow/lite/kernels/internal/reference/prelu.h" #include "tensorflow/lite/kernels/internal/reference/process_broadcast_shapes.h" #include "tensorflow/lite/kernels/internal/reference/quantize.h" +#include "tensorflow/lite/kernels/internal/reference/reduce.h" #include "tensorflow/lite/kernels/internal/reference/round.h" #include "tensorflow/lite/kernels/internal/reference/softmax.h" #include "tensorflow/lite/kernels/internal/reference/strided_slice.h" @@ -2147,150 +2149,6 @@ inline void BatchToSpaceND( } } -// There are two versions of pad: Pad and PadV2. In PadV2 there is a second -// scalar input that provides the padding value. Therefore pad_value_ptr can be -// equivalent to a simple input1_data. For Pad, it should point to a zero -// value. -// -// Note that two typenames are required, so that T=P=int32 is considered a -// specialization distinct from P=int32. -template -inline void PadImpl(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, const T* input_data, - const P* pad_value_ptr, const RuntimeShape& output_shape, - T* output_data) { - const RuntimeShape ext_input_shape = - RuntimeShape::ExtendedShape(4, input_shape); - const RuntimeShape ext_output_shape = - RuntimeShape::ExtendedShape(4, output_shape); - TFLITE_DCHECK_LE(op_params.left_padding_count, 4); - TFLITE_DCHECK_LE(op_params.right_padding_count, 4); - - // Runtime calls are currently fixed at 4 dimensions. Copy inputs so - // we can pad them to 4 dims (yes, we are "padding the padding"). - std::vector left_padding_copy(4, 0); - for (int i = 0; i < op_params.left_padding_count; ++i) { - left_padding_copy[i + 4 - op_params.left_padding_count] = - op_params.left_padding[i]; - } - std::vector right_padding_copy(4, 0); - for (int i = 0; i < op_params.right_padding_count; ++i) { - right_padding_copy[i + 4 - op_params.right_padding_count] = - op_params.right_padding[i]; - } - - const int output_batch = ext_output_shape.Dims(0); - const int output_height = ext_output_shape.Dims(1); - const int output_width = ext_output_shape.Dims(2); - const int output_depth = ext_output_shape.Dims(3); - - const int left_b_padding = left_padding_copy[0]; - const int left_h_padding = left_padding_copy[1]; - const int left_w_padding = left_padding_copy[2]; - const int left_d_padding = left_padding_copy[3]; - - const int right_b_padding = right_padding_copy[0]; - const int right_h_padding = right_padding_copy[1]; - const int right_w_padding = right_padding_copy[2]; - const int right_d_padding = right_padding_copy[3]; - - const T pad_value = *pad_value_ptr; - - const T* in_ptr = input_data; - T* out_ptr = output_data; - for (int out_b = 0; out_b < output_batch; ++out_b) { - for (int out_h = 0; out_h < output_height; ++out_h) { - for (int out_w = 0; out_w < output_width; ++out_w) { - for (int out_d = 0; out_d < output_depth; ++out_d) { - if (out_b < left_b_padding || - out_b >= output_batch - right_b_padding || - out_h < left_h_padding || - out_h >= output_height - right_h_padding || - out_w < left_w_padding || - out_w >= output_width - right_w_padding || - out_d < left_d_padding || - out_d >= output_depth - right_d_padding) { - *out_ptr++ = pad_value; - } else { - *out_ptr++ = *in_ptr++; - } - } - } - } - } -} - -template -inline void Pad(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, const T* input_data, - const P* pad_value_ptr, const RuntimeShape& output_shape, - T* output_data) { - PadImpl(op_params, input_shape, input_data, pad_value_ptr, output_shape, - output_data); -} - -// The second (pad-value) input can be int32 when, say, the first is uint8. -template -inline void Pad(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, const T* input_data, - const int32* pad_value_ptr, const RuntimeShape& output_shape, - T* output_data) { - const T converted_pad_value = static_cast(*pad_value_ptr); - PadImpl(op_params, input_shape, input_data, &converted_pad_value, - output_shape, output_data); -} - -// This version avoids conflicting template matching. -template <> -inline void Pad(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, const int32* input_data, - const int32* pad_value_ptr, const RuntimeShape& output_shape, - int32* output_data) { - PadImpl(op_params, input_shape, input_data, pad_value_ptr, output_shape, - output_data); -} - -// One could make all PadImageStyle calls simply delegate the work to the -// ordinary Pad. However, it is better that the reference code asserts false in -// similar cases. -template -inline void PadImageStyle(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, const T* input_data, - const P* pad_value_ptr, - const RuntimeShape& output_shape, T* output_data) { - TFLITE_ASSERT_FALSE; -} - -template -inline void PadImageStyle(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, - const uint8* input_data, const P* pad_value_ptr, - const RuntimeShape& output_shape, - uint8* output_data) { - Pad(op_params, input_shape, input_data, pad_value_ptr, output_shape, - output_data); -} - -template -inline void PadImageStyle(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, - const int8_t* input_data, const P* pad_value_ptr, - const RuntimeShape& output_shape, - int8_t* output_data) { - Pad(op_params, input_shape, input_data, pad_value_ptr, output_shape, - output_data); -} - -template -inline void PadImageStyle(const tflite::PadParams& op_params, - const RuntimeShape& input_shape, - const float* input_data, const P* pad_value_ptr, - const RuntimeShape& output_shape, - float* output_data) { - Pad(op_params, input_shape, input_data, pad_value_ptr, output_shape, - output_data); -} - template inline void Slice(const tflite::SliceParams& op_params, const RuntimeShape& input_shape, @@ -2356,367 +2214,6 @@ inline void Exp(const T* input_data, const size_t num_elements, } } -// A generic reduce method that can be used for reduce_sum, reduce_mean, etc. -// This method iterates through input data and reduce elements along the -// dimensions given in axis. -template -inline bool Reduce(const In* input_data, const int* input_dims, - const int* output_dims, const int input_num_dims, - const int output_num_dims, const int* axis, - const int num_axis, int* input_iter, - Out reducer(const Out current, const In in), - Out* output_data) { - // Reset input iterator. - for (int idx = 0; idx < input_num_dims; ++idx) { - input_iter[idx] = 0; - } - // Iterate through input_data. - do { - size_t input_offset = - ReducedOutputOffset(input_num_dims, input_dims, input_iter, 0, nullptr); - size_t output_offset = ReducedOutputOffset(input_num_dims, input_dims, - input_iter, num_axis, axis); - output_data[output_offset] = - reducer(output_data[output_offset], input_data[input_offset]); - } while (NextIndex(input_num_dims, input_dims, input_iter)); - return true; -} - -inline bool ResolveAxis(const int num_dims, const int* axis, - const int64_t num_axis, int* out_axis, - int* out_num_axis) { - *out_num_axis = 0; // Just in case. - // Short-circuit axis resolution for scalars; the axis will go unused. - if (num_dims == 0) { - return true; - } - // o(n^2) is fine since out_num_axis should be really small, mostly <= 4 - for (int64_t idx = 0; idx < num_axis; ++idx) { - // Handle negative index. - int current = axis[idx] < 0 ? (axis[idx] + num_dims) : axis[idx]; - TFLITE_DCHECK(current >= 0 && current < num_dims); - bool is_dup = false; - for (int j = 0; j < *out_num_axis; ++j) { - if (out_axis[j] == current) { - is_dup = true; - break; - } - } - if (!is_dup) { - out_axis[*out_num_axis] = current; - *out_num_axis += 1; - } - } - return true; -} - -// This method expects that output_data has been initialized. -template -inline bool ReduceSumImpl(const In* input_data, const int* input_dims, - const int* output_dims, const int input_num_dims, - const int output_num_dims, const int* axis, - const int num_axis, int* input_iter, - Out* output_data) { - auto reducer = [](const Out current, const In in) -> Out { - const Out actual_in = static_cast(in); - return current + actual_in; - }; - return Reduce(input_data, input_dims, output_dims, input_num_dims, - output_num_dims, axis, num_axis, input_iter, reducer, - output_data); -} - -template -inline bool InitTensorDataForReduce(const int* dims, const int num_dims, - const T init_value, T* data) { - size_t num_elements = 1; - for (int idx = 0; idx < num_dims; ++idx) { - size_t current = static_cast(dims[idx]); - // Overflow prevention. - if (num_elements > std::numeric_limits::max() / current) { - return false; - } - num_elements *= current; - } - for (size_t idx = 0; idx < num_elements; ++idx) { - data[idx] = init_value; - } - return true; -} - -// Computes the generic value (i.e., sum/max/min/prod) of elements across -// dimensions given in axis. It needs to pass in init_value and reducer. -template -inline bool ReduceGeneric(const T* input_data, const int* input_dims, - const int input_num_dims, T* output_data, - const int* output_dims, const int output_num_dims, - const int* axis, const int64_t num_axis_dimensions, - bool keep_dims, int* temp_index, int* resolved_axis, - T init_value, - T reducer(const T current, const T in)) { - // Reset output data. - if (!InitTensorDataForReduce(output_dims, output_num_dims, init_value, - output_data)) { - return false; - } - - // Resolve axis. - int num_resolved_axis = 0; - if (!ResolveAxis(input_num_dims, axis, num_axis_dimensions, resolved_axis, - &num_resolved_axis)) { - return false; - } - - return Reduce(input_data, input_dims, output_dims, input_num_dims, - output_num_dims, resolved_axis, num_resolved_axis, - temp_index, reducer, output_data); -} - -// Computes the mean of elements across dimensions given in axis. -// It does so in two stages, first calculates the sum of elements along the axis -// then divides it by the number of element in axis. -template -inline bool Mean(const T* input_data, const int* input_dims, - const int input_num_dims, T* output_data, - const int* output_dims, const int output_num_dims, - const int* axis, const int num_axis_dimensions, bool keep_dims, - int* temp_index, int* resolved_axis, U* temp_sum) { - ScopedProfilingLabelWrapper label("Mean"); - // Reset output data. - size_t num_outputs = 1; - for (int idx = 0; idx < output_num_dims; ++idx) { - size_t current = static_cast(output_dims[idx]); - // Overflow prevention. - if (num_outputs > std::numeric_limits::max() / current) { - return false; - } - num_outputs *= current; - } - for (size_t idx = 0; idx < num_outputs; ++idx) { - output_data[idx] = T(); - temp_sum[idx] = U(); - } - - // Resolve axis. - int num_resolved_axis = 0; - if (!ResolveAxis(input_num_dims, axis, num_axis_dimensions, resolved_axis, - &num_resolved_axis)) { - return false; - } - - if (!ReduceSumImpl(input_data, input_dims, output_dims, input_num_dims, - output_num_dims, resolved_axis, num_resolved_axis, - temp_index, temp_sum)) { - return false; - } - - // Calculate mean by dividing output_data by num of aggregated element. - U num_elements_in_axis = 1; - for (int idx = 0; idx < num_resolved_axis; ++idx) { - size_t current = static_cast(input_dims[resolved_axis[idx]]); - // Overflow prevention. - if (current > (std::numeric_limits::max() / num_elements_in_axis)) { - return false; - } - num_elements_in_axis *= current; - } - - if (num_elements_in_axis > 0) { - for (size_t idx = 0; idx < num_outputs; ++idx) { - output_data[idx] = - static_cast(temp_sum[idx] / static_cast(num_elements_in_axis)); - } - } - return true; -} - -template -inline void Mean(const tflite::MeanParams& op_params, - const RuntimeShape& unextended_input_shape, - const T* input_data, - const RuntimeShape& unextended_output_shape, T* output_data) { - ScopedProfilingLabelWrapper label("Mean4D"); - - // Current implementation only supports dimension equals 4 and simultaneous - // reduction over width and height. - TFLITE_CHECK_EQ(unextended_input_shape.DimensionsCount(), 4); - TFLITE_CHECK_LE(unextended_output_shape.DimensionsCount(), 4); - const RuntimeShape input_shape = - RuntimeShape::ExtendedShape(4, unextended_input_shape); - const RuntimeShape output_shape = - RuntimeShape::ExtendedShape(4, unextended_output_shape); - - const int output_batch = output_shape.Dims(0); - const int output_height = output_shape.Dims(1); - const int output_width = output_shape.Dims(2); - const int output_depth = output_shape.Dims(3); - - const int input_height = input_shape.Dims(1); - const int input_width = input_shape.Dims(2); - - TFLITE_CHECK_EQ(op_params.axis_count, 2); - TFLITE_CHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || - (op_params.axis[0] == 2 && op_params.axis[1] == 1)); - TFLITE_CHECK_EQ(output_height, 1); - TFLITE_CHECK_EQ(output_width, 1); - - for (int out_b = 0; out_b < output_batch; ++out_b) { - for (int out_d = 0; out_d < output_depth; ++out_d) { - float value = 0; - for (int in_h = 0; in_h < input_height; ++in_h) { - for (int in_w = 0; in_w < input_width; ++in_w) { - value += input_data[Offset(input_shape, out_b, in_h, in_w, out_d)]; - } - } - output_data[Offset(output_shape, out_b, 0, 0, out_d)] = - value / (input_width * input_height); - } - } -} - -inline void Mean(const tflite::MeanParams& op_params, - const RuntimeShape& unextended_input_shape, - const uint8_t* input_data, int32 input_zero_point, - float input_scale, const RuntimeShape& unextended_output_shape, - uint8_t* output_data, int32 output_zero_point, - float output_scale) { - ScopedProfilingLabelWrapper label("Mean4D/Uint8"); - - // Current implementation only supports dimension equals 4 and simultaneous - // reduction over width and height. - TFLITE_CHECK_EQ(unextended_input_shape.DimensionsCount(), 4); - TFLITE_CHECK_LE(unextended_output_shape.DimensionsCount(), 4); - const RuntimeShape input_shape = - RuntimeShape::ExtendedShape(4, unextended_input_shape); - const RuntimeShape output_shape = - RuntimeShape::ExtendedShape(4, unextended_output_shape); - const int output_batch = output_shape.Dims(0); - const int output_height = output_shape.Dims(1); - const int output_width = output_shape.Dims(2); - const int output_depth = output_shape.Dims(3); - const int input_height = input_shape.Dims(1); - const int input_width = input_shape.Dims(2); - const float num_elements_in_axis = input_width * input_height; - - TFLITE_CHECK_EQ(op_params.axis_count, 2); - TFLITE_CHECK((op_params.axis[0] == 1 && op_params.axis[1] == 2) || - (op_params.axis[0] == 2 && op_params.axis[1] == 1)); - TFLITE_CHECK_EQ(output_height, 1); - TFLITE_CHECK_EQ(output_width, 1); - - constexpr int32_t kMinValue = std::numeric_limits::min(); - constexpr int32_t kMaxValue = std::numeric_limits::max(); - - int32 bias = - output_zero_point - - static_cast(input_zero_point * input_scale / output_scale); - float real_scale = input_scale / (num_elements_in_axis * output_scale); - - int32 multiplier, shift; - QuantizeMultiplier(real_scale, &multiplier, &shift); - for (int out_b = 0; out_b < output_batch; ++out_b) { - for (int out_d = 0; out_d < output_depth; ++out_d) { - int acc = 0; - for (int in_h = 0; in_h < input_height; ++in_h) { - for (int in_w = 0; in_w < input_width; ++in_w) { - acc += input_data[Offset(input_shape, out_b, in_h, in_w, out_d)]; - } - } - acc = MultiplyByQuantizedMultiplier(acc, multiplier, shift); - acc += bias; - acc = std::min(std::max(acc, kMinValue), kMaxValue); - output_data[Offset(output_shape, out_b, 0, 0, out_d)] = - static_cast(acc); - } - } -} - -// Computes the mean of elements across dimensions given in axis. -// It does so in two stages, first calculates the sum of elements along the axis -// then divides it by the number of element in axis for quantized values. -template -inline bool QuantizedMeanOrSum(const T* input_data, int32 input_zero_point, - float input_scale, const int* input_dims, - const int input_num_dims, T* output_data, - int32 output_zero_point, float output_scale, - const int* output_dims, - const int output_num_dims, const int* axis, - const int num_axis_dimensions, bool keep_dims, - int* temp_index, int* resolved_axis, U* temp_sum, - bool compute_sum) { - const bool uint8_case = std::is_same::value; - if (uint8_case) { - ScopedProfilingLabelWrapper label(compute_sum ? "Sum/Uint8" : "Mean/Uint8"); - } else { - ScopedProfilingLabelWrapper label(compute_sum ? "Sum/Int8" : "Mean/Int8"); - } - // Reset output data. - size_t num_outputs = 1; - for (int idx = 0; idx < output_num_dims; ++idx) { - size_t current = static_cast(output_dims[idx]); - // Overflow prevention. - if (num_outputs > std::numeric_limits::max() / current) { - return false; - } - num_outputs *= current; - } - for (size_t idx = 0; idx < num_outputs; ++idx) { - output_data[idx] = T(); - temp_sum[idx] = U(); - } - - // Resolve axis. - int num_resolved_axis = 0; - if (!ResolveAxis(input_num_dims, axis, num_axis_dimensions, resolved_axis, - &num_resolved_axis)) { - return false; - } - - if (!ReduceSumImpl(input_data, input_dims, output_dims, input_num_dims, - output_num_dims, resolved_axis, num_resolved_axis, - temp_index, temp_sum)) { - return false; - } - - // Calculate mean by dividing output_data by num of aggregated element. - U num_elements_in_axis = 1; - for (int idx = 0; idx < num_resolved_axis; ++idx) { - size_t current = static_cast(input_dims[resolved_axis[idx]]); - // Overflow prevention. - if (current > (std::numeric_limits::max() / num_elements_in_axis)) { - return false; - } - num_elements_in_axis *= current; - } - - if (num_elements_in_axis > 0) { - const float scale = input_scale / output_scale; - if (compute_sum) { - // TODO(b/116341117): Eliminate float and do this completely in 8bit. - const float bias = -input_zero_point * scale * num_elements_in_axis + 0.5; - for (size_t idx = 0; idx < num_outputs; ++idx) { - const U value = - static_cast(std::round(temp_sum[idx] * scale + bias)) + - output_zero_point; - output_data[idx] = static_cast(value); - } - } else { - const float bias = -input_zero_point * scale + 0.5; - for (size_t idx = 0; idx < num_outputs; ++idx) { - float float_mean = static_cast(temp_sum[idx]) / - static_cast(num_elements_in_axis); - float result = - std::min(std::round(float_mean * scale + bias) + output_zero_point, - static_cast(std::numeric_limits::max())); - result = - std::max(result, static_cast(std::numeric_limits::min())); - output_data[idx] = static_cast(result); - } - } - } - return true; -} - template void Minimum(const RuntimeShape& input1_shape, const T* input1_data, const T* input2_data, const RuntimeShape& output_shape, @@ -3063,6 +2560,57 @@ void RankOneSelect(const RuntimeShape& input_condition_shape, } } +template +void BroadcastSelect4DSlow(const RuntimeShape& input_condition_shape, + const D* input_condition_data, + const RuntimeShape& input_x_shape, + const T* input_x_data, + const RuntimeShape& input_y_shape, + const T* input_y_data, + const RuntimeShape& output_shape, T* output_data) { + TFLITE_DCHECK_LE(input_condition_shape.DimensionsCount(), 4); + TFLITE_DCHECK_LE(input_x_shape.DimensionsCount(), 4); + TFLITE_DCHECK_LE(input_y_shape.DimensionsCount(), 4); + TFLITE_DCHECK_LE(output_shape.DimensionsCount(), 4); + + const RuntimeShape extended_output_shape = + RuntimeShape::ExtendedShape(4, output_shape); + + NdArrayDesc<4> desc_condition; + NdArrayDesc<4> desc_x; + NdArrayDesc<4> desc_y; + NdArrayDescsForElementwiseBroadcast(input_condition_shape, input_x_shape, + input_y_shape, &desc_condition, &desc_x, + &desc_y); + + // In Tensorflow, the dimensions are canonically named (batch_number, row, + // col, channel), with extents (batches, height, width, depth), with the + // trailing dimension changing most rapidly (channels has the smallest + // stride, typically 1 element). + // + // In generated C code, we store arrays with the dimensions reversed. The + // first dimension has smallest stride. + // + // We name our variables by their Tensorflow convention, but generate C code + // nesting loops such that the innermost loop has the smallest stride for + // the best cache behavior. + for (int b = 0; b < extended_output_shape.Dims(0); ++b) { + for (int y = 0; y < extended_output_shape.Dims(1); ++y) { + for (int x = 0; x < extended_output_shape.Dims(2); ++x) { + for (int c = 0; c < extended_output_shape.Dims(3); ++c) { + const int condition_index = + SubscriptToIndex(desc_condition, b, y, x, c); + const int x_index = SubscriptToIndex(desc_x, b, y, x, c); + const int y_index = SubscriptToIndex(desc_y, b, y, x, c); + output_data[Offset(extended_output_shape, b, y, x, c)] = + input_condition_data[condition_index] ? input_x_data[x_index] + : input_y_data[y_index]; + } + } + } + } +} + template void SelectTrueCoords(const RuntimeShape& input_condition_shape, const D* input_condition_data, T* output_data) { diff --git a/tensorflow/lite/kernels/internal/scoped_profiling_label_wrapper.h b/tensorflow/lite/kernels/internal/scoped_profiling_label_wrapper.h index 34e41d31f8a..ed883b1c0b5 100644 --- a/tensorflow/lite/kernels/internal/scoped_profiling_label_wrapper.h +++ b/tensorflow/lite/kernels/internal/scoped_profiling_label_wrapper.h @@ -15,25 +15,19 @@ limitations under the License. #ifndef TENSORFLOW_LITE_KERNELS_INTERNAL_SCOPED_PROFILING_LABEL_WRAPPER_H_ #define TENSORFLOW_LITE_KERNELS_INTERNAL_SCOPED_PROFILING_LABEL_WRAPPER_H_ -// We are using TF_LITE_STATIC_MEMORY to inform if we are building for micro or -// not. This is set for all micro builds (host and target) via the Makefile but -// not so for a bazel build. +// gemmlowp itself defines an empty class for ScopedProfilingLabel when +// GEMMLOWP_PROFILING is not defined. However, that does not work for embedded +// builds because instrumentation.h depends on pthread and defines a few Mutex +// classes independent of GEMMLOWP_PROFILING. // -// TODO(b/142948705): Ideally we would have a micro-specific bazel build too. +// As a result, we are using GEMMLOWP_PROFILING to either pull in the +// gemmlowp implementation or use our own empty class. // -// We need to additionally check for ARDUINO because library specific defines -// are not supported by the Aruino IDE. See b/145161069 for more details. +// The downside with this approach is that we are using a gemmlowp macro from +// the TFLite codebase. The upside is that it is much simpler than the +// alternatives (see history of this file). -#if defined(TF_LITE_STATIC_MEMORY) || defined(ARDUINO) - -namespace tflite { -class ScopedProfilingLabelWrapper { - public: - explicit ScopedProfilingLabelWrapper(const char* label) {} -}; -} // namespace tflite - -#else +#ifdef GEMMLOWP_PROFILING #include "profiling/instrumentation.h" @@ -48,6 +42,15 @@ class ScopedProfilingLabelWrapper { }; } // namespace tflite -#endif +#else // GEMMLOWP_PROFILING + +namespace tflite { +class ScopedProfilingLabelWrapper { + public: + explicit ScopedProfilingLabelWrapper(const char* label) {} +}; +} // namespace tflite + +#endif // GEMMLOWP_PROFILING #endif // TENSORFLOW_LITE_KERNELS_INTERNAL_SCOPED_PROFILING_LABEL_WRAPPER_H_ diff --git a/tensorflow/lite/kernels/kernel_util.cc b/tensorflow/lite/kernels/kernel_util.cc index 5fdb301b20d..202140dea4a 100644 --- a/tensorflow/lite/kernels/kernel_util.cc +++ b/tensorflow/lite/kernels/kernel_util.cc @@ -205,9 +205,9 @@ TfLiteStatus CalculateShapeForBroadcast(TfLiteContext* context, const TfLiteTensor* input1, const TfLiteTensor* input2, TfLiteIntArray** output_shape) { - int64_t dims1 = NumDimensions(input1); - int64_t dims2 = NumDimensions(input2); - int64_t out_dims = std::max(dims1, dims2); + int dims1 = NumDimensions(input1); + int dims2 = NumDimensions(input2); + int out_dims = std::max(dims1, dims2); if (NumElements(input1) == 0) { *output_shape = TfLiteIntArrayCopy(input1->dims); return kTfLiteOk; @@ -215,14 +215,39 @@ TfLiteStatus CalculateShapeForBroadcast(TfLiteContext* context, std::unique_ptr shape( TfLiteIntArrayCreate(out_dims), TfLiteIntArrayFree); for (int i = 0; i < out_dims; ++i) { - int64_t d1 = i >= dims1 ? 1 : SizeOfDimension(input1, dims1 - i - 1); - int64_t d2 = i >= dims2 ? 1 : SizeOfDimension(input2, dims2 - i - 1); + int d1 = i >= dims1 ? 1 : SizeOfDimension(input1, dims1 - i - 1); + int d2 = i >= dims2 ? 1 : SizeOfDimension(input2, dims2 - i - 1); TF_LITE_ENSURE(context, d1 == d2 || d1 == 1 || d2 == 1); shape->data[out_dims - i - 1] = std::max(d1, d2); } *output_shape = shape.release(); return kTfLiteOk; } + +TfLiteStatus CalculateShapeForBroadcast(TfLiteContext* context, + const TfLiteTensor* input1, + const TfLiteTensor* input2, + const TfLiteTensor* input3, + TfLiteIntArray** output_shape) { + int dims1 = NumDimensions(input1); + int dims2 = NumDimensions(input2); + int dims3 = NumDimensions(input3); + int out_dims = std::max(std::max(dims1, dims2), dims3); + std::unique_ptr shape( + TfLiteIntArrayCreate(out_dims), TfLiteIntArrayFree); + for (int i = 0; i < out_dims; ++i) { + int d1 = i >= dims1 ? 1 : SizeOfDimension(input1, dims1 - i - 1); + int d2 = i >= dims2 ? 1 : SizeOfDimension(input2, dims2 - i - 1); + int d3 = i >= dims3 ? 1 : SizeOfDimension(input3, dims3 - i - 1); + int max_value = std::max(std::max(d1, d2), d3); + TF_LITE_ENSURE(context, d1 == 1 || d1 == max_value); + TF_LITE_ENSURE(context, d2 == 1 || d2 == max_value); + TF_LITE_ENSURE(context, d3 == 1 || d3 == max_value); + shape->data[out_dims - i - 1] = max_value; + } + *output_shape = shape.release(); + return kTfLiteOk; +} #endif // TF_LITE_STATIC_MEMORY } // namespace tflite diff --git a/tensorflow/lite/kernels/kernel_util.h b/tensorflow/lite/kernels/kernel_util.h index b343ec459e8..6155ed6a862 100644 --- a/tensorflow/lite/kernels/kernel_util.h +++ b/tensorflow/lite/kernels/kernel_util.h @@ -33,6 +33,9 @@ inline const TfLiteTensor* GetInput(TfLiteContext* context, return &context ->tensors[flatbuffers::EndianScalar(node->inputs->data[index])]; } +// Note: You must check if result is not null: +// TfLiteTensor* my_tensor = GetVariableInput(context, node, kMyTensorIdx); +// TF_LITE_ENSURE(context, my_tensor != nullptr); inline TfLiteTensor* GetVariableInput(TfLiteContext* context, const TfLiteNode* node, int index) { TfLiteTensor* tensor = @@ -74,7 +77,8 @@ inline int64_t NumElements(const TfLiteTensor* t) { inline const TfLiteTensor* GetOptionalInputTensor(TfLiteContext* context, const TfLiteNode* node, int index) { - const bool use_tensor = node->inputs->data[index] != kTfLiteOptionalTensor; + const bool use_tensor = index < node->inputs->size && + node->inputs->data[index] != kTfLiteOptionalTensor; if (use_tensor) { return &context ->tensors[flatbuffers::EndianScalar(node->inputs->data[index])]; @@ -168,12 +172,20 @@ void CalculateActivationRange(TfLiteFusedActivation activation, // Return true if the given tensors have the same shape. bool HaveSameShapes(const TfLiteTensor* input1, const TfLiteTensor* input2); -// Calculate the output_shape that is necessary for element-wise operations +// Calculates the output_shape that is necessary for element-wise operations // with broadcasting involving the two input tensors. TfLiteStatus CalculateShapeForBroadcast(TfLiteContext* context, const TfLiteTensor* input1, const TfLiteTensor* input2, TfLiteIntArray** output_shape); + +// Calculates the output_shape that is necessary for element-wise operations +// with broadcasting involving the three input tensors. +TfLiteStatus CalculateShapeForBroadcast(TfLiteContext* context, + const TfLiteTensor* input1, + const TfLiteTensor* input2, + const TfLiteTensor* input3, + TfLiteIntArray** output_shape); } // namespace tflite #endif // TENSORFLOW_LITE_KERNELS_KERNEL_UTIL_H_ diff --git a/tensorflow/lite/kernels/kernel_util_test.cc b/tensorflow/lite/kernels/kernel_util_test.cc index 79e19eb523a..55b52a4fc14 100644 --- a/tensorflow/lite/kernels/kernel_util_test.cc +++ b/tensorflow/lite/kernels/kernel_util_test.cc @@ -30,14 +30,18 @@ class KernelUtilTest : public ::testing::Test { memset(&tensor1_, 0, sizeof(TfLiteTensor)); memset(&tensor2_, 0, sizeof(TfLiteTensor)); + memset(&tensor3_, 0, sizeof(TfLiteTensor)); tensor1_.dims = nullptr; tensor2_.dims = nullptr; + tensor3_.dims = nullptr; tensor1_.allocation_type = kTfLiteMmapRo; tensor2_.allocation_type = kTfLiteMmapRo; + tensor3_.allocation_type = kTfLiteMmapRo; } ~KernelUtilTest() override { TfLiteTensorFree(&tensor1_); TfLiteTensorFree(&tensor2_); + TfLiteTensorFree(&tensor3_); } void SetShape(TfLiteTensor* tensor, std::initializer_list dims) { @@ -62,6 +66,7 @@ class KernelUtilTest : public ::testing::Test { TfLiteContext context_; TfLiteTensor tensor1_; TfLiteTensor tensor2_; + TfLiteTensor tensor3_; }; TEST_F(KernelUtilTest, SameShapeEmpty) { @@ -144,6 +149,95 @@ TEST_F(KernelUtilTest, BroadcastShapeDifferentSizes) { TfLiteIntArrayFree(output); } +TEST_F(KernelUtilTest, BroadcastShapeIncompatibleDimOnThreeTensors) { + TfLiteIntArray* output = nullptr; + SetShape(&tensor1_, {1, 2}); + SetShape(&tensor2_, {1, 3}); + SetShape(&tensor3_, {1, 4}); + EXPECT_NE(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + EXPECT_EQ(output, nullptr); +} + +TEST_F(KernelUtilTest, BroadcastShapeOnesOnThreeTensors) { + TfLiteIntArray* output = nullptr; + SetShape(&tensor1_, {1, 1}); + SetShape(&tensor2_, {1, 1}); + SetShape(&tensor3_, {1, 3}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + TfLiteIntArrayFree(output); + + SetShape(&tensor1_, {1, 2}); + SetShape(&tensor2_, {1, 1}); + SetShape(&tensor3_, {1, 1}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + TfLiteIntArrayFree(output); + + SetShape(&tensor1_, {1, 1}); + SetShape(&tensor2_, {1, 4}); + SetShape(&tensor3_, {1, 1}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + TfLiteIntArrayFree(output); +} + +TEST_F(KernelUtilTest, BroadcastShapeScalarsOnThreeTensors) { + TfLiteIntArray* output = nullptr; + SetShape(&tensor1_, {1, 2}); + SetShape(&tensor2_, {}); + SetShape(&tensor3_, {}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + EXPECT_THAT(GetShape(output), ::testing::ElementsAre(1, 2)); + TfLiteIntArrayFree(output); + + SetShape(&tensor1_, {}); + SetShape(&tensor2_, {2}); + SetShape(&tensor3_, {}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + EXPECT_THAT(GetShape(output), ::testing::ElementsAre(2)); + TfLiteIntArrayFree(output); + + SetShape(&tensor1_, {}); + SetShape(&tensor2_, {}); + SetShape(&tensor3_, {3, 2, 1}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + EXPECT_THAT(GetShape(output), ::testing::ElementsAre(3, 2, 1)); + TfLiteIntArrayFree(output); +} + +TEST_F(KernelUtilTest, BroadcastShapeDifferentSizesOnThreeTensors) { + TfLiteIntArray* output = nullptr; + SetShape(&tensor1_, {1, 2}); + SetShape(&tensor2_, {3, 1, 1}); + SetShape(&tensor3_, {3, 1}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + EXPECT_THAT(GetShape(output), ::testing::ElementsAre(3, 3, 2)); + TfLiteIntArrayFree(output); + + SetShape(&tensor1_, {3, 4}); + SetShape(&tensor2_, {1, 3, 1}); + SetShape(&tensor3_, {1, 2, 1, 1}); + EXPECT_EQ(kTfLiteOk, + CalculateShapeForBroadcast(&context_, &tensor1_, &tensor2_, + &tensor3_, &output)); + EXPECT_THAT(GetShape(output), ::testing::ElementsAre(1, 2, 3, 4)); + TfLiteIntArrayFree(output); +} + TEST_F(KernelUtilTest, CheckAndPopulate) { // Create input. TfLiteTensor input = {}; diff --git a/tensorflow/lite/kernels/lstm.cc b/tensorflow/lite/kernels/lstm.cc index 678864cec60..bbb9e173a4d 100644 --- a/tensorflow/lite/kernels/lstm.cc +++ b/tensorflow/lite/kernels/lstm.cc @@ -52,8 +52,6 @@ struct OpData { bool is_layer_norm_lstm; // These fields are only used by full kernel. - int activation_state_tensor_index; - int cell_state_tensor_index; int scratch_tensor_index; lstm_eval::QuantizedLstmParameter quantized_lstm_param; }; @@ -113,30 +111,19 @@ namespace { TfLiteStatus PopulateQuantizedLstmParams( TfLiteContext* context, TfLiteNode* node, lstm_eval::QuantizedLstmParameter* quantized_lstm_param) { - std::vector intermediate_scale; - std::vector intermediate_zp; - for (int i = 0; i < 5; ++i) { - // Calculate intermediate tensors. - TfLiteTensor* intermediate = - &context->tensors[node->intermediates->data[i]]; - auto* params = reinterpret_cast( - intermediate->quantization.params); - intermediate_scale.push_back(params->scale->data[0]); - intermediate_zp.push_back(params->zero_point->data[0]); - } - // Calculate quantized clip for projection and cell. - const auto* params = reinterpret_cast(node->builtin_data); + const auto* params = static_cast(node->builtin_data); const float cell_clip = params->cell_clip; const float proj_clip = params->proj_clip; const TfLiteTensor* cell_tensor = - GetInput(context, node, kInputCellStateTensor); + GetVariableInput(context, node, kInputCellStateTensor); + TF_LITE_ENSURE(context, cell_tensor != nullptr); const TfLiteTensor* output_tensor = GetOutput(context, node, kOutputTensor); - auto* cell_params = reinterpret_cast( - cell_tensor->quantization.params); - auto* proj_params = reinterpret_cast( + auto* cell_params = + static_cast(cell_tensor->quantization.params); + auto* proj_params = static_cast( output_tensor->quantization.params); if (cell_clip > 0.0) { quantized_lstm_param->quantized_cell_clip = static_cast( @@ -153,7 +140,7 @@ TfLiteStatus PopulateQuantizedLstmParams( } // Calculate effective scales. - OpData* op_data = reinterpret_cast(node->user_data); + OpData* op_data = static_cast(node->user_data); const bool is_layer_norm_lstm = op_data->is_layer_norm_lstm; const TfLiteTensor* input = GetInput(context, node, kInputTensor); @@ -184,27 +171,20 @@ TfLiteStatus PopulateQuantizedLstmParams( GetOptionalInputTensor(context, node, kCellToOutputWeightsTensor); const TfLiteTensor* input_layer_norm_coefficients = - is_layer_norm_lstm ? GetOptionalInputTensor( - context, node, kInputLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kInputLayerNormCoefficientsTensor); const TfLiteTensor* forget_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kForgetLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kForgetLayerNormCoefficientsTensor); const TfLiteTensor* cell_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kCellLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kCellLayerNormCoefficientsTensor); const TfLiteTensor* output_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kOutputLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kOutputLayerNormCoefficientsTensor); const TfLiteTensor* projection_weights = GetOptionalInputTensor(context, node, kProjectionWeightsTensor); TfLiteTensor* activation_state = - &context->tensors[op_data->activation_state_tensor_index]; + GetVariableInput(context, node, kInputActivationStateTensor); + TF_LITE_ENSURE(context, activation_state != nullptr); // Since we have already checked that weights are all there or none, we can // check the existence of only one to get the condition. @@ -212,6 +192,30 @@ TfLiteStatus PopulateQuantizedLstmParams( const bool use_peephole = (cell_to_output_weights != nullptr); const bool use_projection = (projection_weights != nullptr); + // Get intermediate scales and zero points. + std::vector intermediate_scale; + std::vector intermediate_zp; + for (int i = 0; i < 4; ++i) { + if (is_layer_norm_lstm) { + const TfLiteTensor* intermediate = GetIntermediates(context, node, i); + auto* params = static_cast( + intermediate->quantization.params); + intermediate_scale.push_back(params->scale->data[0]); + intermediate_zp.push_back(params->zero_point->data[0]); + } else { + // Q3.12 for activation functions. + intermediate_scale.push_back(std::pow(2, -12)); + intermediate_zp.push_back(0); + } + } + // In the absense of projection, hidden becomes otuput and this intermediate + // is ignored. + const TfLiteTensor* hidden = GetIntermediates(context, node, 4); + auto* hidden_params = + static_cast(hidden->quantization.params); + intermediate_scale.push_back(hidden_params->scale->data[0]); + intermediate_zp.push_back(hidden_params->zero_point->data[0]); + // Scales. const float default_scale = 1.0; float input_scale = default_scale; @@ -286,7 +290,8 @@ TfLiteStatus PopulateQuantizedLstmParams( // Get cell state. TfLiteTensor* cell_state = - &context->tensors[op_data->cell_state_tensor_index]; + GetVariableInput(context, node, kInputCellStateTensor); + TF_LITE_ENSURE(context, cell_state != nullptr); TF_LITE_ENSURE(context, CheckedLog2(cell_state->params.scale, &cell_scale)); TF_LITE_ENSURE(context, cell_scale <= -9); @@ -428,7 +433,7 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, int n_output, int n_cell, bool is_layer_norm_lstm, bool is_fully_quantized) { - const auto* params = reinterpret_cast(node->builtin_data); + const auto* params = static_cast(node->builtin_data); // Making sure clipping parameters have valid values. // == 0 means no clipping @@ -509,8 +514,9 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, if (cell_to_input_weights) { TF_LITE_ENSURE_EQ(context, cell_to_input_weights->dims->size, 1); TF_LITE_ENSURE_EQ(context, cell_to_input_weights->dims->data[0], n_cell); - TF_LITE_ENSURE_EQ(context, cell_to_input_weights->type, - input_to_forget_weights->type); + TF_LITE_ENSURE_EQ( + context, cell_to_input_weights->type, + is_fully_quantized ? kTfLiteInt16 : input_to_forget_weights->type); } const TfLiteTensor* cell_to_forget_weights = @@ -518,8 +524,9 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, if (cell_to_forget_weights) { TF_LITE_ENSURE_EQ(context, cell_to_forget_weights->dims->size, 1); TF_LITE_ENSURE_EQ(context, cell_to_forget_weights->dims->data[0], n_cell); - TF_LITE_ENSURE_EQ(context, cell_to_forget_weights->type, - input_to_forget_weights->type); + TF_LITE_ENSURE_EQ( + context, cell_to_forget_weights->type, + is_fully_quantized ? kTfLiteInt16 : input_to_forget_weights->type); } const TfLiteTensor* cell_to_output_weights = @@ -527,8 +534,9 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, if (cell_to_output_weights) { TF_LITE_ENSURE_EQ(context, cell_to_output_weights->dims->size, 1); TF_LITE_ENSURE_EQ(context, cell_to_output_weights->dims->data[0], n_cell); - TF_LITE_ENSURE_EQ(context, cell_to_output_weights->type, - input_to_forget_weights->type); + TF_LITE_ENSURE_EQ( + context, cell_to_output_weights->type, + is_fully_quantized ? kTfLiteInt16 : input_to_forget_weights->type); } // Making sure the peephole weights are there all or none. @@ -635,8 +643,8 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, } } - const TfLiteTensor* forget_layer_norm_coefficients = - GetInput(context, node, kForgetLayerNormCoefficientsTensor); + const TfLiteTensor* forget_layer_norm_coefficients = GetOptionalInputTensor( + context, node, kForgetLayerNormCoefficientsTensor); TF_LITE_ENSURE(context, forget_layer_norm_coefficients != nullptr); TF_LITE_ENSURE_EQ(context, forget_layer_norm_coefficients->dims->size, 1); TF_LITE_ENSURE_EQ(context, forget_layer_norm_coefficients->dims->data[0], @@ -650,7 +658,7 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, } const TfLiteTensor* cell_layer_norm_coefficients = - GetInput(context, node, kCellLayerNormCoefficientsTensor); + GetOptionalInputTensor(context, node, kCellLayerNormCoefficientsTensor); TF_LITE_ENSURE(context, cell_layer_norm_coefficients != nullptr); TF_LITE_ENSURE_EQ(context, cell_layer_norm_coefficients->dims->size, 1); TF_LITE_ENSURE_EQ(context, cell_layer_norm_coefficients->dims->data[0], @@ -663,8 +671,8 @@ TfLiteStatus CheckInputTensorDimensions(TfLiteContext* context, kTfLiteFloat32); } - const TfLiteTensor* output_layer_norm_coefficients = - GetInput(context, node, kOutputLayerNormCoefficientsTensor); + const TfLiteTensor* output_layer_norm_coefficients = GetOptionalInputTensor( + context, node, kOutputLayerNormCoefficientsTensor); TF_LITE_ENSURE(context, output_layer_norm_coefficients != nullptr); TF_LITE_ENSURE_EQ(context, output_layer_norm_coefficients->dims->size, 1); TF_LITE_ENSURE_EQ(context, output_layer_norm_coefficients->dims->data[0], @@ -713,7 +721,8 @@ TfLiteStatus PopulatePrecomputedZPTimesWeightsWithBias(TfLiteContext* context, TfLiteNode* node) { const TfLiteTensor* input = GetInput(context, node, kInputTensor); const TfLiteTensor* activation_state = - &context->tensors[op_data->activation_state_tensor_index]; + GetVariableInput(context, node, kInputActivationStateTensor); + TF_LITE_ENSURE(context, activation_state != nullptr); const int32_t input_zero_point = -input->params.zero_point; const int32_t activation_zero_point = -activation_state->params.zero_point; @@ -746,52 +755,75 @@ TfLiteStatus PopulatePrecomputedZPTimesWeightsWithBias(TfLiteContext* context, const TfLiteTensor* intermediate = &context->tensors[node->intermediates->data[4]]; - const auto* params = reinterpret_cast( - intermediate->quantization.params); + const auto* params = + static_cast(intermediate->quantization.params); const int32_t hidden_zp = params->zero_point->data[0]; + // Get bias and perform zero point calculation. + // When there is layer normalization, the gate bias does not apply to matmul + // directly: + // y = ln(w * x + w * r + w * c) + b. + const bool is_layer_norm = op_data->is_layer_norm_lstm; + // Forget gate. + const TfLiteTensor* forget_gate_bias = + is_layer_norm ? nullptr : GetInput(context, node, kForgetGateBiasTensor); TF_LITE_ENSURE_OK( - context, PrecomputeZeroPointTimesWeightWithBias( - context, input_zero_point, input_to_forget_weights, nullptr, - &(quantized_lstm_params->input_to_forget_effective_bias))); + context, + PrecomputeZeroPointTimesWeightWithBias( + context, input_zero_point, input_to_forget_weights, forget_gate_bias, + &(quantized_lstm_params->input_to_forget_effective_bias))); + TF_LITE_ENSURE_OK( context, PrecomputeZeroPointTimesWeightWithBias( context, activation_zero_point, recurrent_to_forget_weights, nullptr, &(quantized_lstm_params->recurrent_to_forget_effective_bias))); + // Modulation gate. + const TfLiteTensor* cell_gate_bias = + is_layer_norm ? nullptr : GetInput(context, node, kCellGateBiasTensor); TF_LITE_ENSURE_OK( - context, PrecomputeZeroPointTimesWeightWithBias( - context, input_zero_point, input_to_cell_weights, nullptr, - &(quantized_lstm_params->input_to_cell_effective_bias))); + context, + PrecomputeZeroPointTimesWeightWithBias( + context, input_zero_point, input_to_cell_weights, cell_gate_bias, + &(quantized_lstm_params->input_to_cell_effective_bias))); TF_LITE_ENSURE_OK( context, PrecomputeZeroPointTimesWeightWithBias( context, activation_zero_point, recurrent_to_cell_weights, nullptr, &(quantized_lstm_params->recurrent_to_cell_effective_bias))); + // Output gate. + const TfLiteTensor* output_gate_bias = + is_layer_norm ? nullptr : GetInput(context, node, kOutputGateBiasTensor); TF_LITE_ENSURE_OK( - context, PrecomputeZeroPointTimesWeightWithBias( - context, input_zero_point, input_to_output_weights, nullptr, - &(quantized_lstm_params->input_to_output_effective_bias))); + context, + PrecomputeZeroPointTimesWeightWithBias( + context, input_zero_point, input_to_output_weights, output_gate_bias, + &(quantized_lstm_params->input_to_output_effective_bias))); + TF_LITE_ENSURE_OK( context, PrecomputeZeroPointTimesWeightWithBias( context, activation_zero_point, recurrent_to_output_weights, nullptr, &(quantized_lstm_params->recurrent_to_output_effective_bias))); - // Input gate. + + // Input gate. The calculation is only meaningful for non-cifg case. + const TfLiteTensor* input_gate_bias = + is_layer_norm ? nullptr : GetInput(context, node, kInputGateBiasTensor); TF_LITE_ENSURE_OK( - context, PrecomputeZeroPointTimesWeightWithBias( - context, input_zero_point, input_to_input_weights, nullptr, - &(quantized_lstm_params->input_to_input_effective_bias))); + context, + PrecomputeZeroPointTimesWeightWithBias( + context, input_zero_point, input_to_input_weights, input_gate_bias, + &(quantized_lstm_params->input_to_input_effective_bias))); TF_LITE_ENSURE_OK( context, PrecomputeZeroPointTimesWeightWithBias( context, activation_zero_point, recurrent_to_input_weights, nullptr, &(quantized_lstm_params->recurrent_to_input_effective_bias))); - // Projection bias. + // Projection bias. The calculation is only meaningful for with projection. TF_LITE_ENSURE_OK(context, PrecomputeZeroPointTimesWeightWithBias( context, hidden_zp, projection_weights, projection_bias, @@ -803,7 +835,7 @@ TfLiteStatus PopulatePrecomputedZPTimesWeightsWithBias(TfLiteContext* context, // Allocate a temporary scratch tensor. Also check that the sizes of the input // tensors match each other. TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { - OpData* op_data = reinterpret_cast(node->user_data); + OpData* op_data = static_cast(node->user_data); TF_LITE_ENSURE_EQ(context, node->outputs->size, 1); // Logic for determining regular lstm and layer norm lstm: @@ -832,9 +864,6 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } const bool is_layer_norm_lstm = op_data->is_layer_norm_lstm; - op_data->activation_state_tensor_index = - node->inputs->data[kInputActivationStateTensor]; - op_data->cell_state_tensor_index = node->inputs->data[kInputCellStateTensor]; // Inferring batch size, number of outputs and number of cells from the // input tensors. @@ -866,9 +895,11 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* output = GetOutput(context, node, kOutputTensor); TfLiteTensor* activation_state = - &context->tensors[op_data->activation_state_tensor_index]; + GetVariableInput(context, node, kInputActivationStateTensor); + TF_LITE_ENSURE(context, activation_state != nullptr); TfLiteTensor* cell_state = - &context->tensors[op_data->cell_state_tensor_index]; + GetVariableInput(context, node, kInputCellStateTensor); + TF_LITE_ENSURE(context, cell_state != nullptr); // Check the shape of input state tensors. // These tensor may be 1D or 2D. It's fine as long as the total size is @@ -1048,9 +1079,8 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { - const auto* params = reinterpret_cast(node->builtin_data); - OpData* op_data = reinterpret_cast(node->user_data); - const bool is_layer_norm_lstm = op_data->is_layer_norm_lstm; + const auto* params = static_cast(node->builtin_data); + OpData* op_data = static_cast(node->user_data); const TfLiteTensor* input = GetInput(context, node, kInputTensor); @@ -1080,21 +1110,13 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { GetOptionalInputTensor(context, node, kCellToOutputWeightsTensor); const TfLiteTensor* input_layer_norm_coefficients = - is_layer_norm_lstm ? GetOptionalInputTensor( - context, node, kInputLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kInputLayerNormCoefficientsTensor); const TfLiteTensor* forget_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kForgetLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kForgetLayerNormCoefficientsTensor); const TfLiteTensor* cell_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kCellLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kCellLayerNormCoefficientsTensor); const TfLiteTensor* output_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kOutputLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kOutputLayerNormCoefficientsTensor); const TfLiteTensor* input_gate_bias = GetOptionalInputTensor(context, node, kInputGateBiasTensor); @@ -1110,9 +1132,11 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { GetOptionalInputTensor(context, node, kProjectionBiasTensor); TfLiteTensor* activation_state = - &context->tensors[op_data->activation_state_tensor_index]; + GetVariableInput(context, node, kInputActivationStateTensor); + TF_LITE_ENSURE(context, activation_state != nullptr); TfLiteTensor* cell_state = - &context->tensors[op_data->cell_state_tensor_index]; + GetVariableInput(context, node, kInputCellStateTensor); + TF_LITE_ENSURE(context, cell_state != nullptr); TfLiteTensor* output = GetOutput(context, node, kOutputTensor); @@ -1185,7 +1209,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* scratch3 = GetTemporary(context, node, /*index=*/3); TfLiteTensor* scratch4 = GetTemporary(context, node, /*index=*/4); TfLiteTensor* scratch5 = GetTemporary(context, node, /*index=*/5); - return lstm_eval::EvalQuantized( + return lstm_eval::EvalInteger( input, input_to_input_weights, input_to_forget_weights, input_to_cell_weights, input_to_output_weights, recurrent_to_input_weights, recurrent_to_forget_weights, @@ -1417,11 +1441,11 @@ void* Init(TfLiteContext* context, const char* buffer, size_t length) { return nullptr; } void Free(TfLiteContext* context, void* buffer) { - delete reinterpret_cast(buffer); + delete static_cast(buffer); } TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { - const auto* op_data = reinterpret_cast(node->user_data); + const auto* op_data = static_cast(node->user_data); switch (op_data->kernel_type) { case kTfLiteLSTMFullKernel: return full::Prepare(context, node); @@ -1434,7 +1458,7 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { - const auto* op_data = reinterpret_cast(node->user_data); + const auto* op_data = static_cast(node->user_data); switch (op_data->kernel_type) { case kTfLiteLSTMFullKernel: return full::Eval(context, node); diff --git a/tensorflow/lite/kernels/lstm_eval.cc b/tensorflow/lite/kernels/lstm_eval.cc index a18af287b7d..0c2a05e7a18 100644 --- a/tensorflow/lite/kernels/lstm_eval.cc +++ b/tensorflow/lite/kernels/lstm_eval.cc @@ -36,8 +36,12 @@ namespace ops { namespace builtin { namespace lstm_eval { +inline float GetTensorScale(const TfLiteTensor* tensor) { + return tensor == nullptr ? 1.0f : tensor->params.scale; +} + namespace { -// Performs an LSTM batch inference step for input specified by input_ptr_batch. +// Performs an LSTM batch inference step for input specified by input_ptr. // The LSTM cell is specified by the pointers to its weights (*_weights_ptr) and // biases (*_bias_ptr), and buffers (*_scratch), along with additional // parameters: @@ -85,9 +89,9 @@ namespace { // // The pointers to the cell and output state and the output are updated. // -// The pointers with the suffix "_batch" point to data aligned in batch_major -// order, and each step processes batch_size many inputs from input_ptr_batch, -// and updates batch_size many cell and output states. +// The pointers input_ptr, aux_input_ptr, and output_ptr point to data aligned +// in batch_major order, and each step processes batch_size many inputs from +// input_ptr, and updates batch_size many cell and output states. // // The output_batch_dim is output.shape[-1], i.e. the outermost dimension of the // output tensor, and in most cases will be equal to n_output. It is usually not @@ -96,10 +100,10 @@ namespace { // operations cannot be used since they assume that the batched outputs are // contiguous, and we manually loop over the batched outputs. inline void LstmStepWithAuxInput( - const float* input_ptr_batch, const float* input_to_input_weights_ptr, + const float* input_ptr, const float* input_to_input_weights_ptr, const float* input_to_forget_weights_ptr, const float* input_to_cell_weights_ptr, - const float* input_to_output_weights_ptr, const float* aux_input_ptr_batch, + const float* input_to_output_weights_ptr, const float* aux_input_ptr, const float* aux_input_to_input_weights_ptr, const float* aux_input_to_forget_weights_ptr, const float* aux_input_to_cell_weights_ptr, @@ -122,7 +126,7 @@ inline void LstmStepWithAuxInput( int n_aux_input, int n_output, int output_batch_leading_dim, float* output_state_ptr, float* cell_state_ptr, float* input_gate_scratch, float* forget_gate_scratch, float* cell_scratch, float* output_gate_scratch, - float* output_ptr_batch) { + float* output_ptr) { #ifdef GEMMLOWP_PROFILING gemmlowp::ScopedProfilingLabel label("LstmStepWithAuxInputFloat"); #endif @@ -158,38 +162,38 @@ inline void LstmStepWithAuxInput( // For each batch and cell: compute input_weight * input. if (!use_cifg) { tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_input_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_input_weights_ptr, n_cell, n_input, input_ptr, n_batch, input_gate_scratch, /*result_stride=*/1); } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_forget_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_forget_weights_ptr, n_cell, n_input, input_ptr, n_batch, forget_gate_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_cell_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_cell_weights_ptr, n_cell, n_input, input_ptr, n_batch, cell_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_output_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_output_weights_ptr, n_cell, n_input, input_ptr, n_batch, output_gate_scratch, /*result_stride=*/1); // If auxiliary input is available then compute aux_input_weight * aux_input - if (aux_input_ptr_batch != nullptr) { + if (aux_input_ptr != nullptr) { if (!use_cifg) { tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_input_weights_ptr, n_cell, n_aux_input, - aux_input_ptr_batch, n_batch, input_gate_scratch, + aux_input_to_input_weights_ptr, n_cell, n_aux_input, aux_input_ptr, + n_batch, input_gate_scratch, /*result_stride=*/1); } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_forget_weights_ptr, n_cell, n_aux_input, - aux_input_ptr_batch, n_batch, forget_gate_scratch, /*result_stride=*/1); + aux_input_to_forget_weights_ptr, n_cell, n_aux_input, aux_input_ptr, + n_batch, forget_gate_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_cell_weights_ptr, n_cell, n_aux_input, aux_input_ptr_batch, + aux_input_to_cell_weights_ptr, n_cell, n_aux_input, aux_input_ptr, n_batch, cell_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_output_weights_ptr, n_cell, n_aux_input, - aux_input_ptr_batch, n_batch, output_gate_scratch, /*result_stride=*/1); + aux_input_to_output_weights_ptr, n_cell, n_aux_input, aux_input_ptr, + n_batch, output_gate_scratch, /*result_stride=*/1); } // For each batch and cell: compute recurrent_weight * output_state. @@ -303,68 +307,45 @@ inline void LstmStepWithAuxInput( // For each batch: update the projection and output_state. Note that since // the output batch rows may not be contiguous (output_batch_leading_dim != - // n_output), we unroll the batched operations where this is the case. - if (output_batch_leading_dim == n_output) { - if (use_projection_weight) { - if (use_projection_bias) { - tensor_utils::VectorBatchVectorAssign(projection_bias_ptr, n_output, - n_batch, output_ptr_batch); - } else { - std::fill_n(output_ptr_batch, n_batch * n_output, 0.0f); - } - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - projection_weights_ptr, n_output, n_cell, output_gate_scratch, - n_batch, output_ptr_batch, /*result_stride=*/1); - if (params->proj_clip > 0.0) { - tensor_utils::ClipVector(output_ptr_batch, n_batch * n_output, - params->proj_clip, output_ptr_batch); - } - } else { - std::copy_n(output_gate_scratch, n_batch * n_output, output_ptr_batch); - } - std::copy_n(output_ptr_batch, n_batch * n_output, output_state_ptr); - } else { - if (use_projection_weight) { - if (use_projection_bias) { - for (int k = 0; k < n_batch; k++) { - std::copy_n(projection_bias_ptr, n_output, - output_ptr_batch + k * output_batch_leading_dim); - } - } else { - for (int k = 0; k < n_batch; k++) { - std::fill_n(output_ptr_batch + k * output_batch_leading_dim, n_output, - 0.0f); - } - } + // n_output), we unroll batched operations. + if (use_projection_weight) { + if (use_projection_bias) { for (int k = 0; k < n_batch; k++) { - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - projection_weights_ptr, n_output, n_cell, - output_gate_scratch + k * n_cell, - /*n_batch=*/1, output_ptr_batch + k * output_batch_leading_dim, - /*result_stride=*/1); - if (params->proj_clip > 0.0) { - tensor_utils::ClipVector( - output_ptr_batch + k * output_batch_leading_dim, n_output, - params->proj_clip, - output_ptr_batch + k * output_batch_leading_dim); - } + std::copy_n(projection_bias_ptr, n_output, + output_ptr + k * output_batch_leading_dim); } } else { for (int k = 0; k < n_batch; k++) { - std::copy_n(output_gate_scratch + k * n_output, n_output, - output_ptr_batch + k * output_batch_leading_dim); + std::fill_n(output_ptr + k * output_batch_leading_dim, n_output, 0.0f); } } for (int k = 0; k < n_batch; k++) { - std::copy_n(output_ptr_batch + k * output_batch_leading_dim, n_output, - output_state_ptr + k * n_output); + tensor_utils::MatrixBatchVectorMultiplyAccumulate( + projection_weights_ptr, n_output, n_cell, + output_gate_scratch + k * n_cell, + /*n_batch=*/1, output_ptr + k * output_batch_leading_dim, + /*result_stride=*/1); + if (params->proj_clip > 0.0) { + tensor_utils::ClipVector(output_ptr + k * output_batch_leading_dim, + n_output, params->proj_clip, + output_ptr + k * output_batch_leading_dim); + } } + } else { + for (int k = 0; k < n_batch; k++) { + std::copy_n(output_gate_scratch + k * n_output, n_output, + output_ptr + k * output_batch_leading_dim); + } + } + for (int k = 0; k < n_batch; k++) { + std::copy_n(output_ptr + k * output_batch_leading_dim, n_output, + output_state_ptr + k * n_output); } } // Same as above but with quantized weight matrices. In detail: // Input of size 'n_batch * n_input': -// input_ptr_batch +// input_ptr // // LSTM weights: // Quantized input weights of size 'n_cell * n_input': @@ -418,7 +399,7 @@ inline void LstmStepWithAuxInput( // output_layer_norm_coefficients_ptr - optional // // Temporary pre-allocated storage for quantized values: -// quantized_input_ptr_batch (same size as input_ptr_batch) +// quantized_input_ptr (same size as input_ptr) // quantized_output_state_ptr (same size as output_state_ptr) // quantized_cell_state_ptr (same size as cell_state_ptr) // Temporary pre-allocated storage for recovered values: @@ -427,15 +408,15 @@ inline void LstmStepWithAuxInput( // Outputs: // output_state_ptr - size 'n_batch * n_output' // cell_state_ptr - size 'n_batch * n_cell' -// output_ptr_batch - size 'n_batch * output_batch_leading_dim' +// output_ptr - size 'n_batch * output_batch_leading_dim' inline void LstmStepWithAuxInput( - const float* input_ptr_batch, const int8_t* input_to_input_weights_ptr, + const float* input_ptr, const int8_t* input_to_input_weights_ptr, float input_to_input_weights_scale, const int8_t* input_to_forget_weights_ptr, float input_to_forget_weights_scale, const int8_t* input_to_cell_weights_ptr, float input_to_cell_weights_scale, const int8_t* input_to_output_weights_ptr, - float input_to_output_weights_scale, const float* aux_input_ptr_batch, + float input_to_output_weights_scale, const float* aux_input_ptr, const int8_t* aux_input_to_input_weights_ptr, float aux_input_to_input_weights_scale, const int8_t* aux_input_to_forget_weights_ptr, @@ -469,10 +450,10 @@ inline void LstmStepWithAuxInput( int output_batch_leading_dim, float* input_gate_scratch, float* forget_gate_scratch, float* cell_scratch, float* output_gate_scratch, float* scaling_factors, float* product_scaling_factors, - float* recovered_cell_weights, int8_t* quantized_input_ptr_batch, - int8_t* quantized_aux_input_ptr_batch, int8_t* quantized_output_state_ptr, + float* recovered_cell_weights, int8_t* quantized_input_ptr, + int8_t* quantized_aux_input_ptr, int8_t* quantized_output_state_ptr, int8_t* quantized_cell_state_ptr, float* output_state_ptr, - float* cell_state_ptr, float* output_ptr_batch) { + float* cell_state_ptr, float* output_ptr) { #ifdef GEMMLOWP_PROFILING gemmlowp::ScopedProfilingLabel label("LstmStepWithAuxInputHybrid"); #endif @@ -504,13 +485,13 @@ inline void LstmStepWithAuxInput( output_gate_scratch); } - if (!tensor_utils::IsZeroVector(input_ptr_batch, n_batch * n_input)) { + if (!tensor_utils::IsZeroVector(input_ptr, n_batch * n_input)) { // Save quantization and matmul computation for all zero input. float unused_min, unused_max; for (int b = 0; b < n_batch; ++b) { const int offset = b * n_input; tensor_utils::SymmetricQuantizeFloats( - input_ptr_batch + offset, n_input, quantized_input_ptr_batch + offset, + input_ptr + offset, n_input, quantized_input_ptr + offset, &unused_min, &unused_max, &scaling_factors[b]); } // For each batch and cell: compute input_weight * input. @@ -520,9 +501,9 @@ inline void LstmStepWithAuxInput( scaling_factors[b] * input_to_input_weights_scale; } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_input_weights_ptr, n_cell, n_input, - quantized_input_ptr_batch, product_scaling_factors, n_batch, - input_gate_scratch, /*result_stride=*/1); + input_to_input_weights_ptr, n_cell, n_input, quantized_input_ptr, + product_scaling_factors, n_batch, input_gate_scratch, + /*result_stride=*/1); } for (int b = 0; b < n_batch; ++b) { @@ -530,7 +511,7 @@ inline void LstmStepWithAuxInput( scaling_factors[b] * input_to_forget_weights_scale; } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_forget_weights_ptr, n_cell, n_input, quantized_input_ptr_batch, + input_to_forget_weights_ptr, n_cell, n_input, quantized_input_ptr, product_scaling_factors, n_batch, forget_gate_scratch, /*result_stride=*/1); @@ -539,7 +520,7 @@ inline void LstmStepWithAuxInput( scaling_factors[b] * input_to_cell_weights_scale; } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_cell_weights_ptr, n_cell, n_input, quantized_input_ptr_batch, + input_to_cell_weights_ptr, n_cell, n_input, quantized_input_ptr, product_scaling_factors, n_batch, cell_scratch, /*result_stride=*/1); for (int b = 0; b < n_batch; ++b) { @@ -547,21 +528,20 @@ inline void LstmStepWithAuxInput( scaling_factors[b] * input_to_output_weights_scale; } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_output_weights_ptr, n_cell, n_input, quantized_input_ptr_batch, + input_to_output_weights_ptr, n_cell, n_input, quantized_input_ptr, product_scaling_factors, n_batch, output_gate_scratch, /*result_stride=*/1); } - if (aux_input_ptr_batch != nullptr && - !tensor_utils::IsZeroVector(aux_input_ptr_batch, n_batch * n_input)) { + if (aux_input_ptr != nullptr && + !tensor_utils::IsZeroVector(aux_input_ptr, n_batch * n_input)) { // Save quantization and matmul computation for all zero input. float unused_min, unused_max; for (int b = 0; b < n_batch; ++b) { const int offset = b * n_input; tensor_utils::SymmetricQuantizeFloats( - aux_input_ptr_batch + offset, n_input, - quantized_aux_input_ptr_batch + offset, &unused_min, &unused_max, - &scaling_factors[b]); + aux_input_ptr + offset, n_input, quantized_aux_input_ptr + offset, + &unused_min, &unused_max, &scaling_factors[b]); } // For each batch and cell: compute input_weight * input. if (!use_cifg) { @@ -571,7 +551,7 @@ inline void LstmStepWithAuxInput( } tensor_utils::MatrixBatchVectorMultiplyAccumulate( aux_input_to_input_weights_ptr, n_cell, n_input, - quantized_aux_input_ptr_batch, product_scaling_factors, n_batch, + quantized_aux_input_ptr, product_scaling_factors, n_batch, input_gate_scratch, /*result_stride=*/1); } @@ -581,7 +561,7 @@ inline void LstmStepWithAuxInput( } tensor_utils::MatrixBatchVectorMultiplyAccumulate( aux_input_to_forget_weights_ptr, n_cell, n_input, - quantized_aux_input_ptr_batch, product_scaling_factors, n_batch, + quantized_aux_input_ptr, product_scaling_factors, n_batch, forget_gate_scratch, /*result_stride=*/1); for (int b = 0; b < n_batch; ++b) { @@ -589,9 +569,8 @@ inline void LstmStepWithAuxInput( scaling_factors[b] * aux_input_to_cell_weights_scale; } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_cell_weights_ptr, n_cell, n_input, - quantized_aux_input_ptr_batch, product_scaling_factors, n_batch, - cell_scratch, /*result_stride=*/1); + aux_input_to_cell_weights_ptr, n_cell, n_input, quantized_aux_input_ptr, + product_scaling_factors, n_batch, cell_scratch, /*result_stride=*/1); for (int b = 0; b < n_batch; ++b) { product_scaling_factors[b] = @@ -599,7 +578,7 @@ inline void LstmStepWithAuxInput( } tensor_utils::MatrixBatchVectorMultiplyAccumulate( aux_input_to_output_weights_ptr, n_cell, n_input, - quantized_aux_input_ptr_batch, product_scaling_factors, n_batch, + quantized_aux_input_ptr, product_scaling_factors, n_batch, output_gate_scratch, /*result_stride=*/1); } @@ -763,96 +742,56 @@ inline void LstmStepWithAuxInput( // For each batch: update the projection and output_state. Note that since // the output batch rows may not be contiguous (output_batch_leading_dim != - // n_output), we unroll the batched operations where this is the case. - if (output_batch_leading_dim == n_output) { - if (use_projection_weight) { - if (use_projection_bias) { - tensor_utils::VectorBatchVectorAssign(projection_bias_ptr, n_output, - n_batch, output_ptr_batch); - } else { - std::fill_n(output_ptr_batch, n_batch * n_output, 0.0f); - } - if (!tensor_utils::IsZeroVector(output_gate_scratch, n_batch * n_cell)) { - // Save quantization and matmul computation for all zero input. - float unused_min, unused_max; - for (int b = 0; b < n_batch; ++b) { - const int offset = b * n_cell; - tensor_utils::SymmetricQuantizeFloats( - output_gate_scratch + offset, n_cell, - quantized_cell_state_ptr + offset, &unused_min, &unused_max, - &scaling_factors[b]); - } - for (int b = 0; b < n_batch; ++b) { - product_scaling_factors[b] = - scaling_factors[b] * projection_weights_scale; - } - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - projection_weights_ptr, n_output, n_cell, quantized_cell_state_ptr, - product_scaling_factors, n_batch, output_ptr_batch, - /*result_stride=*/1); - } - if (params->proj_clip > 0.0) { - tensor_utils::ClipVector(output_ptr_batch, n_batch * n_output, - params->proj_clip, output_ptr_batch); - } - } else { - std::copy_n(output_gate_scratch, n_batch * n_output, output_ptr_batch); - } - std::copy_n(output_ptr_batch, n_batch * n_output, output_state_ptr); - } else { - if (use_projection_weight) { - if (use_projection_bias) { - for (int k = 0; k < n_batch; k++) { - std::copy_n(projection_bias_ptr, n_output, - output_ptr_batch + k * output_batch_leading_dim); - } - } else { - for (int k = 0; k < n_batch; k++) { - std::fill_n(output_ptr_batch + k * output_batch_leading_dim, n_output, - 0.0f); - } - } - if (!tensor_utils::IsZeroVector(output_gate_scratch, n_batch * n_cell)) { - // Save quantization and matmul computation for all zero input. - float unused_min, unused_max; - for (int b = 0; b < n_batch; ++b) { - const int offset = b * n_cell; - tensor_utils::SymmetricQuantizeFloats( - output_gate_scratch + offset, n_cell, - quantized_cell_state_ptr + offset, &unused_min, &unused_max, - &scaling_factors[b]); - } - for (int b = 0; b < n_batch; ++b) { - product_scaling_factors[b] = - scaling_factors[b] * projection_weights_scale; - } - for (int k = 0; k < n_batch; k++) { - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - projection_weights_ptr, n_output, n_cell, - quantized_cell_state_ptr + k * n_cell, - &product_scaling_factors[k], - /*n_batch=*/1, output_ptr_batch + k * output_batch_leading_dim, - /*result_stride=*/1); - } - } - if (params->proj_clip > 0.0) { - for (int k = 0; k < n_batch; k++) { - tensor_utils::ClipVector( - output_ptr_batch + k * output_batch_leading_dim, n_output, - params->proj_clip, - output_ptr_batch + k * output_batch_leading_dim); - } + // n_output), we unroll the batched operations. + if (use_projection_weight) { + if (use_projection_bias) { + for (int k = 0; k < n_batch; k++) { + std::copy_n(projection_bias_ptr, n_output, + output_ptr + k * output_batch_leading_dim); } } else { for (int k = 0; k < n_batch; k++) { - std::copy_n(output_gate_scratch + k * n_output, n_output, - output_ptr_batch + k * output_batch_leading_dim); + std::fill_n(output_ptr + k * output_batch_leading_dim, n_output, 0.0f); } } - for (int k = 0; k < n_batch; k++) { - std::copy_n(output_ptr_batch + k * output_batch_leading_dim, n_output, - output_state_ptr + k * n_output); + if (!tensor_utils::IsZeroVector(output_gate_scratch, n_batch * n_cell)) { + // Save quantization and matmul computation for all zero input. + float unused_min, unused_max; + for (int b = 0; b < n_batch; ++b) { + const int offset = b * n_cell; + tensor_utils::SymmetricQuantizeFloats( + output_gate_scratch + offset, n_cell, + quantized_cell_state_ptr + offset, &unused_min, &unused_max, + &scaling_factors[b]); + } + for (int b = 0; b < n_batch; ++b) { + product_scaling_factors[b] = + scaling_factors[b] * projection_weights_scale; + } + for (int k = 0; k < n_batch; k++) { + tensor_utils::MatrixBatchVectorMultiplyAccumulate( + projection_weights_ptr, n_output, n_cell, + quantized_cell_state_ptr + k * n_cell, &product_scaling_factors[k], + /*n_batch=*/1, output_ptr + k * output_batch_leading_dim, + /*result_stride=*/1); + } } + if (params->proj_clip > 0.0) { + for (int k = 0; k < n_batch; k++) { + tensor_utils::ClipVector(output_ptr + k * output_batch_leading_dim, + n_output, params->proj_clip, + output_ptr + k * output_batch_leading_dim); + } + } + } else { + for (int k = 0; k < n_batch; k++) { + std::copy_n(output_gate_scratch + k * n_output, n_output, + output_ptr + k * output_batch_leading_dim); + } + } + for (int k = 0; k < n_batch; k++) { + std::copy_n(output_ptr + k * output_batch_leading_dim, n_output, + output_state_ptr + k * n_output); } } @@ -947,7 +886,7 @@ inline void LstmStepWithAuxInput( // output_state_ptr - size 'n_batch * n_output' // cell_state_ptr - size 'n_batch * n_cell' // output_ptr - size 'n_batch * n_output' -inline void LstmStepQuantized( +inline void LstmStepInteger( const int8_t* input_ptr, const int8_t* input_to_input_weight_ptr, int32_t effective_input_to_input_scale_a, int32_t effective_input_to_input_scale_b, @@ -972,13 +911,13 @@ inline void LstmStepQuantized( const int8_t* recurrent_to_output_weight_ptr, int32_t effective_recurrent_to_output_scale_a, int32_t effective_recurrent_to_output_scale_b, - const int8_t* cell_to_input_weight_ptr, + const int16_t* cell_to_input_weight_ptr, int32_t effective_cell_to_input_scale_a, int32_t effective_cell_to_input_scale_b, - const int8_t* cell_to_forget_weight_ptr, + const int16_t* cell_to_forget_weight_ptr, int32_t effective_cell_to_forget_scale_a, int32_t effective_cell_to_forget_scale_b, - const int8_t* cell_to_output_weight_ptr, + const int16_t* cell_to_output_weight_ptr, int32_t effective_cell_to_output_scale_a, int32_t effective_cell_to_output_scale_b, const int8_t* proj_weight_ptr, int32_t effective_proj_scale_a, int32_t effective_proj_scale_b, @@ -1005,13 +944,16 @@ inline void LstmStepQuantized( const int32_t* input_to_input_effective_bias, const int32_t* recurrent_to_input_effective_bias, const int32_t* projection_effective_bias, int32 n_batch, int32 n_cell, - int32 n_input, int32 n_output, int32 output_batch_leading_dim, - int8_t* activation_ptr, int32_t activation_zp, int16_t* cell_ptr, - int8_t* output_ptr, int16_t* scratch_0_ptr, int16_t* scratch_1_ptr, - int16_t* scratch_2_ptr, int16_t* scratch_3_ptr, int8_t* scratch_4_ptr, - int32_t* scratch_5_ptr, CpuBackendContext* context) { + int32 n_input, int32 n_output, int8_t* activation_ptr, + int32_t activation_zp, int16_t* cell_ptr, int8_t* output_ptr, + int16_t* scratch_0_ptr, int16_t* scratch_1_ptr, int16_t* scratch_2_ptr, + int16_t* scratch_3_ptr, int8_t* scratch_4_ptr, int32_t* scratch_5_ptr, + CpuBackendContext* context) { // Get hyper parameters. const bool use_cifg = (input_to_input_weight_ptr == nullptr); + const bool use_peephole = (cell_to_output_weight_ptr != nullptr); + const bool use_layer_norm_lstm = (layer_norm_forget_weight_ptr != nullptr); + const bool use_projection = (proj_weight_ptr != nullptr); // Check for nullptrs. TFLITE_DCHECK(input_to_forget_effective_bias); @@ -1026,11 +968,6 @@ inline void LstmStepQuantized( } TFLITE_DCHECK(projection_effective_bias); - // TODO(renjieliu): Handle optional arguments processing here: - // case not peephole: cell_to_forget_weight should be nullptr - // cell_to_output_weight should be nullptr - // cifg: cell_to_input_weight should be nullptr - // Set scratch to 0. if (!use_cifg) { memset(scratch_0_ptr, 0, n_batch * n_cell * sizeof(int16_t)); @@ -1050,8 +987,14 @@ inline void LstmStepQuantized( recurrent_to_forget_weight_ptr, effective_recurrent_to_forget_scale_a, effective_recurrent_to_forget_scale_b, n_batch, n_output, n_cell, 0, scratch_5_ptr, scratch_1_ptr, context); + if (use_peephole) { + tensor_utils::VectorBatchVectorCwiseProductAccumulate( + cell_to_forget_weight_ptr, n_output, cell_ptr, n_batch, + effective_cell_to_forget_scale_a, effective_cell_to_forget_scale_b, + scratch_1_ptr); + } - if (layer_norm_forget_weight_ptr != nullptr) { + if (use_layer_norm_lstm) { tensor_utils::ApplyLayerNorm(scratch_1_ptr, layer_norm_forget_weight_ptr, forget_bias_ptr, layer_norm_forget_scale_a, layer_norm_forget_scale_b, inv_large_value[1], @@ -1072,7 +1015,7 @@ inline void LstmStepQuantized( effective_recurrent_to_cell_scale_b, n_batch, n_output, n_cell, 0, scratch_5_ptr, scratch_2_ptr, context); - if (layer_norm_cell_weight_ptr != nullptr) { + if (use_layer_norm_lstm) { tensor_utils::ApplyLayerNorm(scratch_2_ptr, layer_norm_cell_weight_ptr, cell_bias_ptr, layer_norm_cell_scale_a, layer_norm_cell_scale_b, inv_large_value[2], @@ -1081,27 +1024,6 @@ inline void LstmStepQuantized( tensor_utils::ApplyTanh(3, scratch_2_ptr, n_batch, n_cell, scratch_2_ptr); - // Ouptut gate. - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_ptr, input_to_output_effective_bias, input_to_output_weight_ptr, - effective_input_to_output_scale_a, effective_input_to_output_scale_b, - n_batch, n_input, n_cell, 0, scratch_5_ptr, scratch_3_ptr, context); - - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - activation_ptr, recurrent_to_output_effective_bias, - recurrent_to_output_weight_ptr, effective_recurrent_to_output_scale_a, - effective_recurrent_to_output_scale_b, n_batch, n_output, n_cell, 0, - scratch_5_ptr, scratch_3_ptr, context); - - if (layer_norm_output_weight_ptr != nullptr) { - tensor_utils::ApplyLayerNorm(scratch_3_ptr, layer_norm_output_weight_ptr, - output_bias_ptr, layer_norm_output_scale_a, - layer_norm_output_scale_b, inv_large_value[3], - n_batch, n_cell, scratch_3_ptr); - } - - tensor_utils::ApplySigmoid(scratch_3_ptr, n_batch, n_cell, scratch_3_ptr); - // Input gate. if (use_cifg) { tensor_utils::Sub1Vector(scratch_1_ptr, n_batch * n_cell, scratch_0_ptr); @@ -1116,18 +1038,23 @@ inline void LstmStepQuantized( recurrent_to_input_weight_ptr, effective_recurrent_to_input_scale_a, effective_recurrent_to_input_scale_b, n_batch, n_output, n_cell, 0, scratch_5_ptr, scratch_0_ptr, context); + if (use_peephole) { + tensor_utils::VectorBatchVectorCwiseProductAccumulate( + cell_to_input_weight_ptr, n_output, cell_ptr, n_batch, + effective_cell_to_input_scale_a, effective_cell_to_input_scale_b, + scratch_0_ptr); + } - if (layer_norm_input_weight_ptr != nullptr) { + if (use_layer_norm_lstm) { tensor_utils::ApplyLayerNorm(scratch_0_ptr, layer_norm_input_weight_ptr, input_bias_ptr, layer_norm_input_scale_a, layer_norm_input_scale_b, inv_large_value[0], n_batch, n_cell, scratch_0_ptr); } - tensor_utils::ApplySigmoid(scratch_0_ptr, n_batch, n_cell, scratch_0_ptr); } - // Cell and hidden. + // New cell. tensor_utils::CwiseMul(scratch_1_ptr, cell_ptr, n_batch, n_cell, 15, scratch_1_ptr); @@ -1141,28 +1068,55 @@ inline void LstmStepQuantized( tensor_utils::CwiseClipping(cell_ptr, quantized_cell_clip, n_batch, n_cell); } + // Ouptut gate. + tensor_utils::MatrixBatchVectorMultiplyAccumulate( + input_ptr, input_to_output_effective_bias, input_to_output_weight_ptr, + effective_input_to_output_scale_a, effective_input_to_output_scale_b, + n_batch, n_input, n_cell, 0, scratch_5_ptr, scratch_3_ptr, context); + + tensor_utils::MatrixBatchVectorMultiplyAccumulate( + activation_ptr, recurrent_to_output_effective_bias, + recurrent_to_output_weight_ptr, effective_recurrent_to_output_scale_a, + effective_recurrent_to_output_scale_b, n_batch, n_output, n_cell, 0, + scratch_5_ptr, scratch_3_ptr, context); + if (use_peephole) { + tensor_utils::VectorBatchVectorCwiseProductAccumulate( + cell_to_output_weight_ptr, n_output, cell_ptr, n_batch, + effective_cell_to_output_scale_a, effective_cell_to_output_scale_b, + scratch_3_ptr); + } + + if (use_layer_norm_lstm) { + tensor_utils::ApplyLayerNorm(scratch_3_ptr, layer_norm_output_weight_ptr, + output_bias_ptr, layer_norm_output_scale_a, + layer_norm_output_scale_b, inv_large_value[3], + n_batch, n_cell, scratch_3_ptr); + } + + tensor_utils::ApplySigmoid(scratch_3_ptr, n_batch, n_cell, scratch_3_ptr); + + // Hidden. tensor_utils::ApplyTanh(15 + cell_scale, cell_ptr, n_batch, n_cell, scratch_0_ptr); tensor_utils::CwiseMul(scratch_3_ptr, scratch_0_ptr, effective_hidden_scale_a, effective_hidden_scale_b, n_batch, n_cell, hidden_zp, scratch_4_ptr); - // Projection. - if (proj_weight_ptr != nullptr) { + if (use_projection) { memset(output_ptr, 0, n_batch * n_output * sizeof(int8_t)); tensor_utils::MatrixBatchVectorMultiplyAccumulate( scratch_4_ptr, projection_effective_bias, proj_weight_ptr, effective_proj_scale_a, effective_proj_scale_b, n_batch, n_cell, n_output, activation_zp, scratch_5_ptr, output_ptr, context); + if (quantized_proj_clip > 0) { + tensor_utils::CwiseClipping(output_ptr, quantized_proj_clip, n_batch, + n_output); + } + } else { + std::copy_n(scratch_4_ptr, n_batch * n_output, output_ptr); } - - if (quantized_proj_clip > 0) { - tensor_utils::CwiseClipping(output_ptr, quantized_proj_clip, n_batch, - n_output); - } - - memcpy(activation_ptr, output_ptr, n_batch * n_output * sizeof(int8_t)); + std::copy_n(output_ptr, n_batch * n_output, activation_ptr); } } // namespace @@ -1215,80 +1169,22 @@ TfLiteStatus EvalFloat( // Since we have already checked that weights are all there or none, we can // check the existence of only one to the get the condition. const bool use_cifg = (input_to_input_weights == nullptr); - const bool use_peephole = (cell_to_output_weights != nullptr); - const bool is_layer_norm_lstm = (forget_layer_norm_coefficients != nullptr); // Index the scratch buffers pointers to the global scratch buffer. + float* scratch_buffer_ptr = GetTensorData(scratch_buffer); float* input_gate_scratch = nullptr; float* cell_scratch = nullptr; float* forget_gate_scratch = nullptr; float* output_gate_scratch = nullptr; if (use_cifg) { - cell_scratch = GetTensorData(scratch_buffer); - forget_gate_scratch = - GetTensorData(scratch_buffer) + n_cell * n_batch; - output_gate_scratch = - GetTensorData(scratch_buffer) + 2 * n_cell * n_batch; + cell_scratch = scratch_buffer_ptr; + forget_gate_scratch = scratch_buffer_ptr + n_cell * n_batch; + output_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch; } else { - input_gate_scratch = GetTensorData(scratch_buffer); - cell_scratch = GetTensorData(scratch_buffer) + n_cell * n_batch; - forget_gate_scratch = - GetTensorData(scratch_buffer) + 2 * n_cell * n_batch; - output_gate_scratch = - GetTensorData(scratch_buffer) + 3 * n_cell * n_batch; - } - - // Check optional tensors, the respective pointers can be null. - const float* input_to_input_weights_ptr = - (use_cifg) ? nullptr : GetTensorData(input_to_input_weights); - const float* recurrent_to_input_weights_ptr = - (use_cifg) ? nullptr : GetTensorData(recurrent_to_input_weights); - const float* input_gate_bias_ptr = - (use_cifg) ? nullptr : GetTensorData(input_gate_bias); - const float* cell_to_input_weights_ptr = - (use_peephole && !use_cifg) ? GetTensorData(cell_to_input_weights) - : nullptr; - const float* cell_to_forget_weights_ptr = - (use_peephole) ? GetTensorData(cell_to_forget_weights) : nullptr; - const float* cell_to_output_weights_ptr = - (use_peephole) ? GetTensorData(cell_to_output_weights) : nullptr; - const float* input_layer_norm_coefficients_ptr = - (is_layer_norm_lstm && !use_cifg) - ? GetTensorData(input_layer_norm_coefficients) - : nullptr; - const float* forget_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? GetTensorData(forget_layer_norm_coefficients) - : nullptr; - const float* cell_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? GetTensorData(cell_layer_norm_coefficients) - : nullptr; - const float* output_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? GetTensorData(output_layer_norm_coefficients) - : nullptr; - const float* projection_weights_ptr = - (projection_weights == nullptr) - ? nullptr - : GetTensorData(projection_weights); - const float* projection_bias_ptr = - (projection_bias == nullptr) ? nullptr - : GetTensorData(projection_bias); - - const float* aux_input_ptr = nullptr; - const float* aux_input_to_input_weights_ptr = nullptr; - const float* aux_input_to_forget_weights_ptr = nullptr; - const float* aux_input_to_cell_weights_ptr = nullptr; - const float* aux_input_to_output_weights_ptr = nullptr; - if (aux_input_size > 0) { - if (!use_cifg) { - aux_input_to_input_weights_ptr = - GetTensorData(aux_input_to_input_weights); - } - aux_input_to_forget_weights_ptr = - GetTensorData(aux_input_to_forget_weights); - aux_input_to_cell_weights_ptr = - GetTensorData(aux_input_to_cell_weights); - aux_input_to_output_weights_ptr = - GetTensorData(aux_input_to_output_weights); + input_gate_scratch = scratch_buffer_ptr; + cell_scratch = scratch_buffer_ptr + n_cell * n_batch; + forget_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch; + output_gate_scratch = scratch_buffer_ptr + 3 * n_cell * n_batch; } const int output_batch_leading_dim = @@ -1301,38 +1197,44 @@ TfLiteStatus EvalFloat( // If this is the forward_sequence, step forward, otherwise step // backwards. const int t_rel = forward_sequence ? t : max_time - t - 1; - const float* input_ptr_batch = - GetTensorData(input) + t_rel * input_step; + const float* input_ptr = GetTensorData(input) + t_rel * input_step; + const float* aux_input_ptr = nullptr; if (aux_input) { aux_input_ptr = GetTensorData(aux_input) + t_rel * input_step; } - float* output_ptr_time = + float* output_ptr = GetTensorData(output) + t_rel * output_step + output_offset; LstmStepWithAuxInput( - input_ptr_batch, input_to_input_weights_ptr, + input_ptr, GetTensorData(input_to_input_weights), GetTensorData(input_to_forget_weights), GetTensorData(input_to_cell_weights), GetTensorData(input_to_output_weights), aux_input_ptr, - aux_input_to_input_weights_ptr, aux_input_to_forget_weights_ptr, - aux_input_to_cell_weights_ptr, aux_input_to_output_weights_ptr, - recurrent_to_input_weights_ptr, + GetTensorData(aux_input_to_input_weights), + GetTensorData(aux_input_to_forget_weights), + GetTensorData(aux_input_to_cell_weights), + GetTensorData(aux_input_to_output_weights), + GetTensorData(recurrent_to_input_weights), GetTensorData(recurrent_to_forget_weights), GetTensorData(recurrent_to_cell_weights), GetTensorData(recurrent_to_output_weights), - cell_to_input_weights_ptr, cell_to_forget_weights_ptr, - cell_to_output_weights_ptr, input_layer_norm_coefficients_ptr, - forget_layer_norm_coefficients_ptr, cell_layer_norm_coefficients_ptr, - output_layer_norm_coefficients_ptr, input_gate_bias_ptr, + GetTensorData(cell_to_input_weights), + GetTensorData(cell_to_forget_weights), + GetTensorData(cell_to_output_weights), + GetTensorData(input_layer_norm_coefficients), + GetTensorData(forget_layer_norm_coefficients), + GetTensorData(cell_layer_norm_coefficients), + GetTensorData(output_layer_norm_coefficients), + GetTensorData(input_gate_bias), GetTensorData(forget_gate_bias), GetTensorData(cell_bias), - GetTensorData(output_gate_bias), projection_weights_ptr, - projection_bias_ptr, params, n_batch, n_cell, n_input, aux_input_size, - n_output, output_batch_leading_dim, + GetTensorData(output_gate_bias), + GetTensorData(projection_weights), + GetTensorData(projection_bias), params, n_batch, n_cell, + n_input, aux_input_size, n_output, output_batch_leading_dim, GetTensorData(activation_state), GetTensorData(cell_state), input_gate_scratch, - forget_gate_scratch, cell_scratch, output_gate_scratch, - output_ptr_time); + forget_gate_scratch, cell_scratch, output_gate_scratch, output_ptr); } } else { for (int b = 0; b < n_batch; b++) { @@ -1345,6 +1247,7 @@ TfLiteStatus EvalFloat( const int time_offset = b * max_time + t_rel; const float* input_ptr = GetTensorData(input) + time_offset * input_step; + const float* aux_input_ptr = nullptr; if (aux_input) { aux_input_ptr = GetTensorData(aux_input) + time_offset * input_step; @@ -1364,26 +1267,32 @@ TfLiteStatus EvalFloat( float* output_gate_scratch_ptr = output_gate_scratch + b * n_cell; LstmStepWithAuxInput( - input_ptr, input_to_input_weights_ptr, + input_ptr, GetTensorData(input_to_input_weights), GetTensorData(input_to_forget_weights), GetTensorData(input_to_cell_weights), GetTensorData(input_to_output_weights), aux_input_ptr, - aux_input_to_input_weights_ptr, aux_input_to_forget_weights_ptr, - aux_input_to_cell_weights_ptr, aux_input_to_output_weights_ptr, - recurrent_to_input_weights_ptr, + GetTensorData(aux_input_to_input_weights), + GetTensorData(aux_input_to_forget_weights), + GetTensorData(aux_input_to_cell_weights), + GetTensorData(aux_input_to_output_weights), + GetTensorData(recurrent_to_input_weights), GetTensorData(recurrent_to_forget_weights), GetTensorData(recurrent_to_cell_weights), GetTensorData(recurrent_to_output_weights), - cell_to_input_weights_ptr, cell_to_forget_weights_ptr, - cell_to_output_weights_ptr, input_layer_norm_coefficients_ptr, - forget_layer_norm_coefficients_ptr, - cell_layer_norm_coefficients_ptr, - output_layer_norm_coefficients_ptr, input_gate_bias_ptr, + GetTensorData(cell_to_input_weights), + GetTensorData(cell_to_forget_weights), + GetTensorData(cell_to_output_weights), + GetTensorData(input_layer_norm_coefficients), + GetTensorData(forget_layer_norm_coefficients), + GetTensorData(cell_layer_norm_coefficients), + GetTensorData(output_layer_norm_coefficients), + GetTensorData(input_gate_bias), GetTensorData(forget_gate_bias), GetTensorData(cell_bias), - GetTensorData(output_gate_bias), projection_weights_ptr, - projection_bias_ptr, params, /*n_batch=*/1, n_cell, n_input, - aux_input_size, n_output, output_batch_leading_dim, + GetTensorData(output_gate_bias), + GetTensorData(projection_weights), + GetTensorData(projection_bias), params, /*n_batch=*/1, + n_cell, n_input, aux_input_size, n_output, output_batch_leading_dim, activation_state_ptr, cell_state_ptr, input_gate_scratch_ptr, forget_gate_scratch_ptr, cell_scratch_ptr, output_gate_scratch_ptr, output_ptr); @@ -1443,157 +1352,21 @@ TfLiteStatus EvalHybrid( // Since we have already checked that weights are all there or none, we can // check the existence of only one to get the condition. const bool use_cifg = (input_to_input_weights == nullptr); - const bool use_peephole = (cell_to_output_weights != nullptr); - const bool is_layer_norm_lstm = (forget_layer_norm_coefficients != nullptr); + float* scratch_buffer_ptr = GetTensorData(scratch_buffer); float* input_gate_scratch = nullptr; float* cell_scratch = nullptr; float* forget_gate_scratch = nullptr; float* output_gate_scratch = nullptr; if (use_cifg) { - cell_scratch = GetTensorData(scratch_buffer); - forget_gate_scratch = - GetTensorData(scratch_buffer) + n_cell * n_batch; - output_gate_scratch = - GetTensorData(scratch_buffer) + 2 * n_cell * n_batch; + cell_scratch = scratch_buffer_ptr; + forget_gate_scratch = scratch_buffer_ptr + n_cell * n_batch; + output_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch; } else { - input_gate_scratch = GetTensorData(scratch_buffer); - cell_scratch = GetTensorData(scratch_buffer) + n_cell * n_batch; - forget_gate_scratch = - GetTensorData(scratch_buffer) + 2 * n_cell * n_batch; - output_gate_scratch = - GetTensorData(scratch_buffer) + 3 * n_cell * n_batch; - } - - // Check optional tensors, the respective pointers can be null. - const int8_t* input_to_input_weights_ptr = nullptr; - float input_to_input_weights_scale = 1.0f; - const int8_t* recurrent_to_input_weights_ptr = nullptr; - float recurrent_to_input_weights_scale = 1.0f; - const float* input_gate_bias_ptr = nullptr; - if (!use_cifg) { - input_to_input_weights_ptr = GetTensorData(input_to_input_weights); - recurrent_to_input_weights_ptr = - GetTensorData(recurrent_to_input_weights); - input_gate_bias_ptr = GetTensorData(input_gate_bias); - input_to_input_weights_scale = input_to_input_weights->params.scale; - recurrent_to_input_weights_scale = recurrent_to_input_weights->params.scale; - } - - const int8_t* cell_to_input_weights_ptr = nullptr; - const int8_t* cell_to_forget_weights_ptr = nullptr; - const int8_t* cell_to_output_weights_ptr = nullptr; - float cell_to_input_weights_scale = 1.0f; - float cell_to_forget_weights_scale = 1.0f; - float cell_to_output_weights_scale = 1.0f; - if (use_peephole) { - if (!use_cifg) { - cell_to_input_weights_ptr = GetTensorData(cell_to_input_weights); - cell_to_input_weights_scale = cell_to_input_weights->params.scale; - } - cell_to_forget_weights_ptr = GetTensorData(cell_to_forget_weights); - cell_to_output_weights_ptr = GetTensorData(cell_to_output_weights); - cell_to_forget_weights_scale = cell_to_forget_weights->params.scale; - cell_to_output_weights_scale = cell_to_output_weights->params.scale; - } - - const float* input_layer_norm_coefficients_ptr = - (is_layer_norm_lstm && !use_cifg) - ? GetTensorData(input_layer_norm_coefficients) - : nullptr; - const float* forget_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? GetTensorData(forget_layer_norm_coefficients) - : nullptr; - const float* cell_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? GetTensorData(cell_layer_norm_coefficients) - : nullptr; - const float* output_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? GetTensorData(output_layer_norm_coefficients) - : nullptr; - - const int8_t* projection_weights_ptr = - (projection_weights == nullptr) - ? nullptr - : GetTensorData(projection_weights); - const float projection_weights_scale = - (projection_weights == nullptr) ? 1.0f : projection_weights->params.scale; - const float* projection_bias_ptr = - (projection_bias == nullptr) ? nullptr - : GetTensorData(projection_bias); - - // Required tensors, pointers are non-null. - const int8_t* input_to_forget_weights_ptr = - GetTensorData(input_to_forget_weights); - const float input_to_forget_weights_scale = - input_to_forget_weights->params.scale; - const int8_t* input_to_cell_weights_ptr = - GetTensorData(input_to_cell_weights); - const float input_to_cell_weights_scale = input_to_cell_weights->params.scale; - const int8_t* input_to_output_weights_ptr = - GetTensorData(input_to_output_weights); - const float input_to_output_weights_scale = - input_to_output_weights->params.scale; - const int8_t* recurrent_to_forget_weights_ptr = - GetTensorData(recurrent_to_forget_weights); - const float recurrent_to_forget_weights_scale = - recurrent_to_forget_weights->params.scale; - const int8_t* recurrent_to_cell_weights_ptr = - GetTensorData(recurrent_to_cell_weights); - const float recurrent_to_cell_weights_scale = - recurrent_to_cell_weights->params.scale; - const int8_t* recurrent_to_output_weights_ptr = - GetTensorData(recurrent_to_output_weights); - const float recurrent_to_output_weights_scale = - recurrent_to_output_weights->params.scale; - const float* forget_gate_bias_ptr = GetTensorData(forget_gate_bias); - const float* cell_bias_ptr = GetTensorData(cell_bias); - const float* output_gate_bias_ptr = GetTensorData(output_gate_bias); - - // Temporary storage for quantized values and scaling factors. - int8_t* quantized_input_ptr = GetTensorData(input_quantized); - int8_t* quantized_aux_input_ptr = - (aux_input_quantized == nullptr) - ? nullptr - : GetTensorData(aux_input_quantized); - int8_t* quantized_output_state_ptr = - GetTensorData(output_state_quantized); - int8_t* quantized_cell_state_ptr = - GetTensorData(cell_state_quantized); - float* scaling_factors_ptr = GetTensorData(scaling_factors); - float* prod_scaling_factors_ptr = GetTensorData(prod_scaling_factors); - float* recovered_cell_weights_ptr = - GetTensorData(recovered_cell_weights); - - // Auxiliary input and weights. - const float* aux_input_ptr = nullptr; - const int8_t* aux_input_to_input_weights_ptr = nullptr; - const int8_t* aux_input_to_forget_weights_ptr = nullptr; - const int8_t* aux_input_to_cell_weights_ptr = nullptr; - const int8_t* aux_input_to_output_weights_ptr = nullptr; - float aux_input_to_input_weights_scale = 0.0f; - float aux_input_to_forget_weights_scale = 0.0f; - float aux_input_to_cell_weights_scale = 0.0f; - float aux_input_to_output_weights_scale = 0.0f; - if (aux_input_size > 0) { - if (!use_cifg) { - aux_input_to_input_weights_ptr = - GetTensorData(aux_input_to_input_weights); - } - aux_input_to_forget_weights_ptr = - GetTensorData(aux_input_to_forget_weights); - aux_input_to_cell_weights_ptr = - GetTensorData(aux_input_to_cell_weights); - aux_input_to_output_weights_ptr = - GetTensorData(aux_input_to_output_weights); - if (!use_cifg) { - aux_input_to_input_weights_scale = - aux_input_to_input_weights->params.scale; - } - aux_input_to_forget_weights_scale = - aux_input_to_forget_weights->params.scale; - aux_input_to_cell_weights_scale = aux_input_to_cell_weights->params.scale; - aux_input_to_output_weights_scale = - aux_input_to_output_weights->params.scale; + input_gate_scratch = scratch_buffer_ptr; + cell_scratch = scratch_buffer_ptr + n_cell * n_batch; + forget_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch; + output_gate_scratch = scratch_buffer_ptr + 3 * n_cell * n_batch; } const int output_batch_leading_dim = @@ -1606,43 +1379,67 @@ TfLiteStatus EvalHybrid( // If this is the forward_sequence, step forward, otherwise step // backwards. const int t_rel = forward_sequence ? t : max_time - t - 1; - const float* input_ptr_batch = - GetTensorData(input) + t_rel * input_step; + const float* input_ptr = GetTensorData(input) + t_rel * input_step; + const float* aux_input_ptr = nullptr; if (aux_input) { aux_input_ptr = GetTensorData(aux_input) + t_rel * input_step; } - float* output_ptr_batch = + float* output_ptr = GetTensorData(output) + t_rel * output_step + output_offset; LstmStepWithAuxInput( - input_ptr_batch, input_to_input_weights_ptr, - input_to_input_weights_scale, input_to_forget_weights_ptr, - input_to_forget_weights_scale, input_to_cell_weights_ptr, - input_to_cell_weights_scale, input_to_output_weights_ptr, - input_to_output_weights_scale, aux_input_ptr, - aux_input_to_input_weights_ptr, aux_input_to_input_weights_scale, - aux_input_to_forget_weights_ptr, aux_input_to_forget_weights_scale, - aux_input_to_cell_weights_ptr, aux_input_to_cell_weights_scale, - aux_input_to_output_weights_ptr, aux_input_to_output_weights_scale, - recurrent_to_input_weights_ptr, recurrent_to_input_weights_scale, - recurrent_to_forget_weights_ptr, recurrent_to_forget_weights_scale, - recurrent_to_cell_weights_ptr, recurrent_to_cell_weights_scale, - recurrent_to_output_weights_ptr, recurrent_to_output_weights_scale, - cell_to_input_weights_ptr, cell_to_input_weights_scale, - cell_to_forget_weights_ptr, cell_to_forget_weights_scale, - cell_to_output_weights_ptr, cell_to_output_weights_scale, - input_layer_norm_coefficients_ptr, forget_layer_norm_coefficients_ptr, - cell_layer_norm_coefficients_ptr, output_layer_norm_coefficients_ptr, - input_gate_bias_ptr, forget_gate_bias_ptr, cell_bias_ptr, - output_gate_bias_ptr, projection_weights_ptr, - projection_weights_scale, projection_bias_ptr, params, n_batch, - n_cell, n_input, aux_input_size, n_output, output_batch_leading_dim, + input_ptr, GetTensorData(input_to_input_weights), + GetTensorScale(input_to_input_weights), + GetTensorData(input_to_forget_weights), + GetTensorScale(input_to_forget_weights), + GetTensorData(input_to_cell_weights), + GetTensorScale(input_to_cell_weights), + GetTensorData(input_to_output_weights), + GetTensorScale(input_to_output_weights), aux_input_ptr, + GetTensorData(aux_input_to_input_weights), + GetTensorScale(aux_input_to_input_weights), + GetTensorData(aux_input_to_forget_weights), + GetTensorScale(aux_input_to_forget_weights), + GetTensorData(aux_input_to_cell_weights), + GetTensorScale(aux_input_to_cell_weights), + GetTensorData(aux_input_to_output_weights), + GetTensorScale(aux_input_to_output_weights), + GetTensorData(recurrent_to_input_weights), + GetTensorScale(recurrent_to_input_weights), + GetTensorData(recurrent_to_forget_weights), + GetTensorScale(recurrent_to_forget_weights), + GetTensorData(recurrent_to_cell_weights), + GetTensorScale(recurrent_to_cell_weights), + GetTensorData(recurrent_to_output_weights), + GetTensorScale(recurrent_to_output_weights), + GetTensorData(cell_to_input_weights), + GetTensorScale(cell_to_input_weights), + GetTensorData(cell_to_forget_weights), + GetTensorScale(cell_to_forget_weights), + GetTensorData(cell_to_output_weights), + GetTensorScale(cell_to_output_weights), + GetTensorData(input_layer_norm_coefficients), + GetTensorData(forget_layer_norm_coefficients), + GetTensorData(cell_layer_norm_coefficients), + GetTensorData(output_layer_norm_coefficients), + GetTensorData(input_gate_bias), + GetTensorData(forget_gate_bias), + GetTensorData(cell_bias), + GetTensorData(output_gate_bias), + GetTensorData(projection_weights), + GetTensorScale(projection_weights), + GetTensorData(projection_bias), params, n_batch, n_cell, + n_input, aux_input_size, n_output, output_batch_leading_dim, input_gate_scratch, forget_gate_scratch, cell_scratch, - output_gate_scratch, scaling_factors_ptr, prod_scaling_factors_ptr, - recovered_cell_weights_ptr, quantized_input_ptr, - quantized_aux_input_ptr, quantized_output_state_ptr, - quantized_cell_state_ptr, GetTensorData(output_state), - GetTensorData(cell_state), output_ptr_batch); + output_gate_scratch, GetTensorData(scaling_factors), + GetTensorData(prod_scaling_factors), + GetTensorData(recovered_cell_weights), + GetTensorData(input_quantized), + GetTensorData(aux_input_quantized), + GetTensorData(output_state_quantized), + GetTensorData(cell_state_quantized), + GetTensorData(output_state), GetTensorData(cell_state), + output_ptr); } } else { for (int b = 0; b < n_batch; b++) { @@ -1655,6 +1452,7 @@ TfLiteStatus EvalHybrid( const int time_offset = b * max_time + t_rel; const float* input_ptr = GetTensorData(input) + time_offset * input_step; + const float* aux_input_ptr = nullptr; if (aux_input) { aux_input_ptr = GetTensorData(aux_input) + time_offset * input_step; @@ -1674,36 +1472,58 @@ TfLiteStatus EvalHybrid( float* output_gate_scratch_ptr = output_gate_scratch + b * n_cell; LstmStepWithAuxInput( - input_ptr, input_to_input_weights_ptr, input_to_input_weights_scale, - input_to_forget_weights_ptr, input_to_forget_weights_scale, - input_to_cell_weights_ptr, input_to_cell_weights_scale, - input_to_output_weights_ptr, input_to_output_weights_scale, - aux_input_ptr, aux_input_to_input_weights_ptr, - aux_input_to_input_weights_scale, aux_input_to_forget_weights_ptr, - aux_input_to_forget_weights_scale, aux_input_to_cell_weights_ptr, - aux_input_to_cell_weights_scale, aux_input_to_output_weights_ptr, - aux_input_to_output_weights_scale, recurrent_to_input_weights_ptr, - recurrent_to_input_weights_scale, recurrent_to_forget_weights_ptr, - recurrent_to_forget_weights_scale, recurrent_to_cell_weights_ptr, - recurrent_to_cell_weights_scale, recurrent_to_output_weights_ptr, - recurrent_to_output_weights_scale, cell_to_input_weights_ptr, - cell_to_input_weights_scale, cell_to_forget_weights_ptr, - cell_to_forget_weights_scale, cell_to_output_weights_ptr, - cell_to_output_weights_scale, input_layer_norm_coefficients_ptr, - forget_layer_norm_coefficients_ptr, - cell_layer_norm_coefficients_ptr, - output_layer_norm_coefficients_ptr, input_gate_bias_ptr, - forget_gate_bias_ptr, cell_bias_ptr, output_gate_bias_ptr, - projection_weights_ptr, projection_weights_scale, - projection_bias_ptr, params, + input_ptr, GetTensorData(input_to_input_weights), + GetTensorScale(input_to_input_weights), + GetTensorData(input_to_forget_weights), + GetTensorScale(input_to_forget_weights), + GetTensorData(input_to_cell_weights), + GetTensorScale(input_to_cell_weights), + GetTensorData(input_to_output_weights), + GetTensorScale(input_to_output_weights), aux_input_ptr, + GetTensorData(aux_input_to_input_weights), + GetTensorScale(aux_input_to_input_weights), + GetTensorData(aux_input_to_forget_weights), + GetTensorScale(aux_input_to_forget_weights), + GetTensorData(aux_input_to_cell_weights), + GetTensorScale(aux_input_to_cell_weights), + GetTensorData(aux_input_to_output_weights), + GetTensorScale(aux_input_to_output_weights), + GetTensorData(recurrent_to_input_weights), + GetTensorScale(recurrent_to_input_weights), + GetTensorData(recurrent_to_forget_weights), + GetTensorScale(recurrent_to_forget_weights), + GetTensorData(recurrent_to_cell_weights), + GetTensorScale(recurrent_to_cell_weights), + GetTensorData(recurrent_to_output_weights), + GetTensorScale(recurrent_to_output_weights), + GetTensorData(cell_to_input_weights), + GetTensorScale(cell_to_input_weights), + GetTensorData(cell_to_forget_weights), + GetTensorScale(cell_to_forget_weights), + GetTensorData(cell_to_output_weights), + GetTensorScale(cell_to_output_weights), + GetTensorData(input_layer_norm_coefficients), + GetTensorData(forget_layer_norm_coefficients), + GetTensorData(cell_layer_norm_coefficients), + GetTensorData(output_layer_norm_coefficients), + GetTensorData(input_gate_bias), + GetTensorData(forget_gate_bias), + GetTensorData(cell_bias), + GetTensorData(output_gate_bias), + GetTensorData(projection_weights), + GetTensorScale(projection_weights), + GetTensorData(projection_bias), params, /*n_batch=*/1, n_cell, n_input, aux_input_size, n_output, output_batch_leading_dim, input_gate_scratch_ptr, forget_gate_scratch_ptr, cell_scratch_ptr, output_gate_scratch_ptr, - scaling_factors_ptr, prod_scaling_factors_ptr, - recovered_cell_weights_ptr, quantized_input_ptr, - quantized_aux_input_ptr, quantized_output_state_ptr, - quantized_cell_state_ptr, output_state_ptr, cell_state_ptr, - output_ptr); + GetTensorData(scaling_factors), + GetTensorData(prod_scaling_factors), + GetTensorData(recovered_cell_weights), + GetTensorData(input_quantized), + GetTensorData(aux_input_quantized), + GetTensorData(output_state_quantized), + GetTensorData(cell_state_quantized), output_state_ptr, + cell_state_ptr, output_ptr); } } } @@ -1711,7 +1531,7 @@ TfLiteStatus EvalHybrid( return kTfLiteOk; } -TfLiteStatus EvalQuantized( +TfLiteStatus EvalInteger( const TfLiteTensor* input, const TfLiteTensor* input_to_input_weights, const TfLiteTensor* input_to_forget_weights, const TfLiteTensor* input_to_cell_weights, @@ -1751,53 +1571,8 @@ TfLiteStatus EvalQuantized( const int n_cell = input_to_output_weights->dims->data[0]; const int n_output = recurrent_to_output_weights->dims->data[1]; - // Weights and states. - const int8_t* input_to_input_weight_ptr = - GetTensorData(input_to_input_weights); - const int8_t* recurrent_to_input_weight_ptr = - GetTensorData(recurrent_to_input_weights); - const int8_t* cell_to_input_weight_ptr = - GetTensorData(cell_to_input_weights); - const int8_t* input_to_forget_weight_ptr = - GetTensorData(input_to_forget_weights); - const int8_t* recurrent_to_forget_weight_ptr = - GetTensorData(recurrent_to_forget_weights); - const int8_t* cell_to_forget_weight_ptr = - GetTensorData(cell_to_forget_weights); - const int8_t* input_to_cell_weight_ptr = - GetTensorData(input_to_cell_weights); - const int8_t* recurrent_to_cell_weight_ptr = - GetTensorData(recurrent_to_cell_weights); - const int8_t* input_to_output_weight_ptr = - GetTensorData(input_to_output_weights); - const int8_t* recurrent_to_output_weight_ptr = - GetTensorData(recurrent_to_output_weights); - const int8_t* cell_to_output_weight_ptr = - GetTensorData(cell_to_output_weights); - const int8_t* proj_weight_ptr = GetTensorData(projection_weights); - const int16_t* layer_norm_input_weight_ptr = - GetTensorData(input_layer_norm_coefficients); - const int16_t* layer_norm_forget_weight_ptr = - GetTensorData(forget_layer_norm_coefficients); - const int16_t* layer_norm_cell_weight_ptr = - GetTensorData(cell_layer_norm_coefficients); - const int16_t* layer_norm_output_weight_ptr = - GetTensorData(output_layer_norm_coefficients); - const int32_t* input_bias_ptr = GetTensorData(input_gate_bias); - const int32_t* forget_bias_ptr = GetTensorData(forget_gate_bias); - const int32_t* cell_bias_ptr = GetTensorData(cell_bias); - const int32_t* output_bias_ptr = GetTensorData(output_gate_bias); - - int16_t* cell_ptr = GetTensorData(cell_state); - int8_t* activation_ptr = GetTensorData(activation_state); - int8_t* output_ptr = GetTensorData(output); - - // Zero points - int input_zp = 0; - int activation_zp = 0; - - input_zp = input->params.zero_point; - activation_zp = activation_state->params.zero_point; + // Activation zero point + int activation_zp = activation_state->params.zero_point; // Get params for time/batch/sequence. const int output_batch_leading_dim = @@ -1807,62 +1582,64 @@ TfLiteStatus EvalQuantized( for (int t = 0; t < max_time; t++) { const int t_rel = t; - output_ptr = output_ptr + t_rel * output_step; - - // Input can be int8 asymmetric or int16 symmetric. + int8_t* output_ptr = GetTensorData(output) + t_rel * output_step; const int8_t* input_ptr = GetTensorData(input) + t_rel * input_step; - LstmStepQuantized( - input_ptr, input_to_input_weight_ptr, + LstmStepInteger( + input_ptr, GetTensorData(input_to_input_weights), quantized_lstm_param->effective_input_to_input_scale_a, quantized_lstm_param->effective_input_to_input_scale_b, - input_to_forget_weight_ptr, + GetTensorData(input_to_forget_weights), quantized_lstm_param->effective_input_to_forget_scale_a, quantized_lstm_param->effective_input_to_forget_scale_b, - input_to_cell_weight_ptr, + GetTensorData(input_to_cell_weights), quantized_lstm_param->effective_input_to_cell_scale_a, quantized_lstm_param->effective_input_to_cell_scale_b, - input_to_output_weight_ptr, + GetTensorData(input_to_output_weights), quantized_lstm_param->effective_input_to_output_scale_a, quantized_lstm_param->effective_input_to_output_scale_b, - recurrent_to_input_weight_ptr, + GetTensorData(recurrent_to_input_weights), quantized_lstm_param->effective_recurrent_to_input_scale_a, quantized_lstm_param->effective_recurrent_to_input_scale_b, - recurrent_to_forget_weight_ptr, + GetTensorData(recurrent_to_forget_weights), quantized_lstm_param->effective_recurrent_to_forget_scale_a, quantized_lstm_param->effective_recurrent_to_forget_scale_b, - recurrent_to_cell_weight_ptr, + GetTensorData(recurrent_to_cell_weights), quantized_lstm_param->effective_recurrent_to_cell_scale_a, quantized_lstm_param->effective_recurrent_to_cell_scale_b, - recurrent_to_output_weight_ptr, + GetTensorData(recurrent_to_output_weights), quantized_lstm_param->effective_recurrent_to_output_scale_a, quantized_lstm_param->effective_recurrent_to_output_scale_b, - cell_to_input_weight_ptr, + GetTensorData(cell_to_input_weights), quantized_lstm_param->effective_cell_to_input_scale_a, quantized_lstm_param->effective_cell_to_input_scale_b, - cell_to_forget_weight_ptr, + GetTensorData(cell_to_forget_weights), quantized_lstm_param->effective_cell_to_forget_scale_a, quantized_lstm_param->effective_cell_to_forget_scale_b, - cell_to_output_weight_ptr, + GetTensorData(cell_to_output_weights), quantized_lstm_param->effective_cell_to_output_scale_a, - quantized_lstm_param->effective_cell_to_output_scale_b, proj_weight_ptr, + quantized_lstm_param->effective_cell_to_output_scale_b, + GetTensorData(projection_weights), quantized_lstm_param->effective_proj_scale_a, quantized_lstm_param->effective_proj_scale_b, quantized_lstm_param->hidden_zp, quantized_lstm_param->effective_hidden_scale_a, quantized_lstm_param->effective_hidden_scale_b, - layer_norm_input_weight_ptr, + GetTensorData(input_layer_norm_coefficients), quantized_lstm_param->layer_norm_input_scale_a, quantized_lstm_param->layer_norm_input_scale_b, - layer_norm_forget_weight_ptr, + GetTensorData(forget_layer_norm_coefficients), quantized_lstm_param->layer_norm_forget_scale_a, quantized_lstm_param->layer_norm_forget_scale_b, - layer_norm_cell_weight_ptr, + GetTensorData(cell_layer_norm_coefficients), quantized_lstm_param->layer_norm_cell_scale_a, quantized_lstm_param->layer_norm_cell_scale_b, - layer_norm_output_weight_ptr, + GetTensorData(output_layer_norm_coefficients), quantized_lstm_param->layer_norm_output_scale_a, - quantized_lstm_param->layer_norm_output_scale_b, input_bias_ptr, - forget_bias_ptr, cell_bias_ptr, output_bias_ptr, + quantized_lstm_param->layer_norm_output_scale_b, + GetTensorData(input_gate_bias), + GetTensorData(forget_gate_bias), + GetTensorData(cell_bias), + GetTensorData(output_gate_bias), quantized_lstm_param->quantized_cell_clip, quantized_lstm_param->quantized_proj_clip, quantized_lstm_param->cell_scale, @@ -1876,11 +1653,12 @@ TfLiteStatus EvalQuantized( quantized_lstm_param->input_to_input_effective_bias.get(), quantized_lstm_param->recurrent_to_input_effective_bias.get(), quantized_lstm_param->projection_effective_bias.get(), n_batch, n_cell, - n_input, n_output, output_batch_leading_dim, activation_ptr, - activation_zp, cell_ptr, output_ptr, GetTensorData(scratch0), - GetTensorData(scratch1), GetTensorData(scratch2), - GetTensorData(scratch3), GetTensorData(scratch4), - GetTensorData(scratch5), context); + n_input, n_output, GetTensorData(activation_state), + activation_zp, GetTensorData(cell_state), output_ptr, + GetTensorData(scratch0), GetTensorData(scratch1), + GetTensorData(scratch2), GetTensorData(scratch3), + GetTensorData(scratch4), GetTensorData(scratch5), + context); } return kTfLiteOk; diff --git a/tensorflow/lite/kernels/lstm_eval.h b/tensorflow/lite/kernels/lstm_eval.h index 967a1ee105d..4b9832cf03d 100644 --- a/tensorflow/lite/kernels/lstm_eval.h +++ b/tensorflow/lite/kernels/lstm_eval.h @@ -153,7 +153,7 @@ TfLiteStatus EvalHybrid( TfLiteTensor* cell_state_quantized, TfLiteTensor* output_state, TfLiteTensor* cell_state, TfLiteTensor* output); -TfLiteStatus EvalQuantized( +TfLiteStatus EvalInteger( const TfLiteTensor* input, const TfLiteTensor* input_to_input_weights, const TfLiteTensor* input_to_forget_weights, const TfLiteTensor* input_to_cell_weights, diff --git a/tensorflow/lite/kernels/lstm_eval_test.cc b/tensorflow/lite/kernels/lstm_eval_test.cc index 9a5a87bd592..2cbf83a0b77 100644 --- a/tensorflow/lite/kernels/lstm_eval_test.cc +++ b/tensorflow/lite/kernels/lstm_eval_test.cc @@ -570,7 +570,7 @@ void TestOneFullyQuantizedLSTM() { auto output = one_parameter.GetOutput(); auto cell = one_parameter.GetCell(); auto param = one_parameter.GetQuantParam(); - ops::builtin::lstm_eval::EvalQuantized( + ops::builtin::lstm_eval::EvalInteger( one_parameter.GetInput(), one_parameter.Geti2i(), one_parameter.Geti2f(), one_parameter.Geti2c(), one_parameter.Geti2o(), one_parameter.Getr2i(), one_parameter.Getr2f(), one_parameter.Getr2c(), one_parameter.Getr2o(), diff --git a/tensorflow/lite/kernels/lstm_test.cc b/tensorflow/lite/kernels/lstm_test.cc index ac2f28cf278..4b6c76ca9c5 100644 --- a/tensorflow/lite/kernels/lstm_test.cc +++ b/tensorflow/lite/kernels/lstm_test.cc @@ -2133,7 +2133,7 @@ class LSTMIntegerOpModel : public SingleOpModel { } cell_to_forget_weights_ = AddInput({TensorType_INT16, input_shapes[10], ranges[10].first, ranges[10].second}); - cell_to_output_weights_ = AddInput({TensorType_INT8, input_shapes[11], + cell_to_output_weights_ = AddInput({TensorType_INT16, input_shapes[11], ranges[11].first, ranges[11].second}); } else { cell_to_input_weights_ = AddNullInput(); @@ -2548,7 +2548,212 @@ TEST(LSTMIntegerOpModel, NoCifgYesLayerNormNoYesProjectionNoPeephole) { for (int i = 0; i < input_sequence_size; ++i) { lstm.SetInput(lstm_input[i]); lstm.Invoke(); - const auto x = lstm.GetOutput(); + EXPECT_THAT(lstm.GetOutput(), ElementsAreArray(expected_output[i])); + } +} + +TEST(LSTMIntegerOpModel, NoCifgYesLayerNormNoYesProjectionYesPeephole) { + // Hyper parameters. + const int n_batch = 2; + const int n_input = 5; + const int n_cell = 4; + const int n_output = 3; + const float cell_clip = 0.0; + const float proj_clip = 0.0; + + // Model related weights. + const std::vector input_to_input_weights = { + 0.5, 0.6, 0.7, -0.8, -0.9, 0.1, 0.2, 0.3, -0.4, 0.5, + -0.8, 0.7, -0.6, 0.5, -0.4, -0.5, -0.4, -0.3, -0.2, -0.1}; + + const std::vector input_to_forget_weights = { + -0.6, -0.1, 0.3, 0.2, 0.9, -0.5, -0.2, -0.4, 0.3, -0.8, + -0.4, 0.3, -0.5, -0.4, -0.6, 0.3, -0.4, -0.6, -0.5, -0.5}; + + const std::vector input_to_cell_weights = { + -0.4, -0.3, -0.2, -0.1, -0.5, 0.5, -0.2, -0.3, -0.2, -0.6, + 0.6, -0.1, -0.4, -0.3, -0.7, 0.7, -0.9, -0.5, 0.8, 0.6}; + + const std::vector input_to_output_weights = { + -0.8, -0.4, -0.2, -0.9, -0.1, -0.7, 0.3, -0.3, -0.8, -0.2, + 0.6, -0.2, 0.4, -0.7, -0.3, -0.5, 0.1, 0.5, -0.6, -0.4}; + + const std::vector input_gate_bias = {0.03, 0.15, 0.22, 0.38}; + + const std::vector forget_gate_bias = {0.1, -0.3, -0.2, 0.1}; + + const std::vector cell_gate_bias = {-0.05, 0.72, 0.25, 0.08}; + + const std::vector output_gate_bias = {0.05, -0.01, 0.2, 0.1}; + + const std::vector recurrent_to_input_weights = { + -0.2, -0.3, 0.4, 0.1, -0.5, 0.9, -0.2, -0.3, -0.7, 0.05, -0.2, -0.6}; + + const std::vector recurrent_to_cell_weights = { + -0.3, 0.2, 0.1, -0.3, 0.8, -0.08, -0.2, 0.3, 0.8, -0.6, -0.1, 0.2}; + + const std::vector recurrent_to_forget_weights = { + -0.5, -0.3, -0.5, -0.2, 0.6, 0.4, 0.9, 0.3, -0.1, 0.2, 0.5, 0.2}; + + const std::vector recurrent_to_output_weights = { + 0.3, -0.1, 0.1, -0.2, -0.5, -0.7, -0.2, -0.6, -0.1, -0.4, -0.7, -0.2}; + + const std::vector cell_to_input_weights = {0.3, -0.1, 0.1, -0.2}; + + const std::vector cell_to_forget_weights = {0.2, -0.1, 0.1, -0.2}; + + const std::vector cell_to_output_weights = {0.3, -0.1, 0.1, -0.3}; + + const std::vector input_layer_norm_coefficients = {0.1, 0.2, 0.3, 0.5}; + const std::vector forget_layer_norm_coefficients = {0.2, 0.2, 0.4, + 0.3}; + const std::vector cell_layer_norm_coefficients = {0.7, 0.2, 0.3, 0.8}; + const std::vector output_layer_norm_coefficients = {0.6, 0.2, 0.2, + 0.5}; + + const std::vector projection_weights = { + -0.1, 0.2, 0.01, -0.2, 0.1, 0.5, 0.3, 0.08, 0.07, 0.2, -0.4, 0.2}; + + // Input shapes. + const std::vector> inputs = { + {n_batch, n_input}, // input tensor + + {n_cell, n_input}, // input_to_input_weight tensor + {n_cell, n_input}, // input_to_forget_weight tensor + {n_cell, n_input}, // input_to_cell_weight tensor + {n_cell, n_input}, // input_to_output_weight tensor + + {n_cell, n_output}, // recurrent_to_input_weight tensor + {n_cell, n_output}, // recurrent_to_forget_weight tensor + {n_cell, n_output}, // recurrent_to_cell_weight tensor + {n_cell, n_output}, // recurrent_to_output_weight tensor + + {n_cell}, // cell_to_input_weight tensor + {n_cell}, // cell_to_forget_weight tensor + {n_cell}, // cell_to_output_weight tensor + + {n_cell}, // input_gate_bias tensor + {n_cell}, // forget_gate_bias tensor + {n_cell}, // cell_bias tensor + {n_cell}, // output_gate_bias tensor + + {n_output, n_cell}, // projection_weight tensor + {0}, // projection_bias tensor + + {n_batch, n_output}, // activation_state tensor + {n_batch, n_cell}, // cell_state tensor + + {n_cell}, // input_layer_norm_coefficient tensor + {n_cell}, // forget_layer_norm_coefficient tensor + {n_cell}, // cell_layer_norm_coefficient tensor + {n_cell}, // output_layer_norm_coefficient tensor + }; + + // Input ranges. + const std::vector> ranges = { + {-1.0, 127.0 / 128}, // input tensor + {-1.0, 1.0}, // input_to_input_weight tensor + {-1.0, 1.0}, // input_to_forget_weight tensor + {-1.0, 1.0}, // input_to_cell_weight tensor + {-1.0, 1.0}, // input_to_output_weight tensor + + {-1.0, 1.0}, // recurrent_to_input_weight tensor + {-0.9, 0.9}, // recurrent_to_forget_weight tensor + {-1.0, 1.0}, // recurrent_to_cell_weight tensor + {-1.0, 1.0}, // recurrent_to_output_weight tensor + + {-0.3, 0.3}, // cell_to_input_weight tensor + {-0.3, 0.3}, // cell_to_forget_weight tensor + {-0.3, 0.3}, // cell_to_output_weight tensor + + {-100, 100}, // input_gate_bias tensor + {-100, 80}, // forget_gate_bias tensor + {-100, 100}, // cell_bias tensor + {-100, 100}, // output_gate_bias tensor + + {-0.5, 0.5}, // projection_weight tensor + {-1, 1}, // projection_bias tensor + + {-1.0, 32767.0 / 32768}, // activation_state tensor + {-1, 1}, // cell_state tensor + + {-0.5, 0.5}, // input_layer_norm_coefficient tensor + {-0.5, 0.5}, // forget_layer_norm_coefficient tensor + {-1.0, 1.0}, // cell_layer_norm_coefficient tensor + {-1.0, 1.0}, // output_layer_norm_coefficient tensor + // Output scale is the same as input activation scale and only activation + // scale is used in the op, so this is only provided for clarity. + {-1.0, 32767.0 / 32768}, // output tensor. + }; + + // The scale and zero point of intermediate tensors. + std::vector> intermediates = { + {0.007059, 0}, {0.007812, 0}, {0.007059, 0}, {0.007812, 0}, {0.007, 0}}; + + // Create model. + LSTMIntegerOpModel lstm(n_batch, n_input, n_cell, n_output, + /*use_cifg=*/false, /*use_peephole=*/true, + /*use_projection_weights=*/true, + /*use_projection_bias=*/false, + /*use_layer_norm=*/true, cell_clip, proj_clip, inputs, + ranges, intermediates); + + // Set weights. + lstm.SetInputToInputWeights(input_to_input_weights); + lstm.SetInputToCellWeights(input_to_cell_weights); + lstm.SetInputToForgetWeights(input_to_forget_weights); + lstm.SetInputToOutputWeights(input_to_output_weights); + + lstm.SetInputGateBias(input_gate_bias); + lstm.SetCellBias(cell_gate_bias); + lstm.SetForgetGateBias(forget_gate_bias); + lstm.SetOutputGateBias(output_gate_bias); + + lstm.SetRecurrentToInputWeights(recurrent_to_input_weights); + lstm.SetRecurrentToCellWeights(recurrent_to_cell_weights); + lstm.SetRecurrentToForgetWeights(recurrent_to_forget_weights); + lstm.SetRecurrentToOutputWeights(recurrent_to_output_weights); + + lstm.SetCellToInputWeights(cell_to_input_weights); + lstm.SetCellToForgetWeights(cell_to_forget_weights); + lstm.SetCellToOutputWeights(cell_to_output_weights); + + lstm.SetProjectionWeights(projection_weights); + + lstm.SetInputLayerNormCoefficients(input_layer_norm_coefficients); + lstm.SetForgetLayerNormCoefficients(forget_layer_norm_coefficients); + lstm.SetCellLayerNormCoefficients(cell_layer_norm_coefficients); + lstm.SetOutputLayerNormCoefficients(output_layer_norm_coefficients); + + // Model inputs. sequence -batch - input + const std::vector> lstm_input = { + { + 0.7, 0.8, 0.1, 0.2, 0.3, // + 0.8, 0.1, 0.2, 0.4, 0.5, // + }, + { + 0.2, 0.7, 0.7, 0.1, 0.7, // + 0.3, 0.2, 0.9, 0.8, 0.1, // + }, + { + 0.7, 0.8, 0.1, 0.2, 0.3, // + 0.3, 0.2, 0.9, 0.8, 0.1, // + }, + }; + + // Expected outputs. + const std::vector> expected_output = { + {127, 127, -16, -21, 127, 127}, + {23, 127, 127, -128, 127, 127}, + {127, 127, 127, -128, 127, 127}, + }; + + // Invoke and verify the result. + const int input_sequence_size = lstm_input.size(); + EXPECT_GT(input_sequence_size, 0); + for (int i = 0; i < input_sequence_size; ++i) { + lstm.SetInput(lstm_input[i]); + lstm.Invoke(); EXPECT_THAT(lstm.GetOutput(), ElementsAreArray(expected_output[i])); } } diff --git a/tensorflow/lite/kernels/mirror_pad.cc b/tensorflow/lite/kernels/mirror_pad.cc index fc788f87eb5..a69451fb770 100644 --- a/tensorflow/lite/kernels/mirror_pad.cc +++ b/tensorflow/lite/kernels/mirror_pad.cc @@ -18,6 +18,8 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/cpu_backend_context.h" +#include "tensorflow/lite/kernels/cpu_backend_threadpool.h" #include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/reference/reference_ops.h" @@ -35,39 +37,19 @@ namespace { // Nil value for paddingMode/offset. const int kUnsetOffset = -1; -const int kTensorNotAllocated = -1; - -// Holds computed value (memoized value) of an internal fill state of a -// subarray. -// State is (Dimension to fill, index in tensor as flattened array) -// The value is start and end in the output array which has the padded result. -struct CacheElement { - int start; - int end; -}; -static_assert(sizeof(CacheElement) == sizeof(int64_t), - "CacheElement must be 8 bytes."); - -// Wrapper for data used by the op. -struct OpData { - int cache_tensor_index = kTensorNotAllocated; -}; - // Wrapper for params passed to the Eval function. template struct EvalData { - CacheElement* cache; const TfLiteTensor* padding_matrix = nullptr; const TfLiteIntArray* input_dims = nullptr; // Holds number of elements at the nth dimension. // value at last dimension = 1, at second to last = sizeof last dimension. - const std::vector* dimension_num_elements = nullptr; + const std::vector* output_dims_num_elements = nullptr; + const std::vector* input_dims_num_elements = nullptr; const T* input_data = nullptr; int offset = kUnsetOffset; T* output_data = nullptr; - int input_size = 0; - int output_size = 0; int num_dims = 0; }; @@ -93,57 +75,6 @@ inline void GetPadding(const TfLiteTensor* padding_matrix, int dimension, } } -template -int Eval(EvalData* eval_data, int current_dim, int flat_index, - int output_index) { - if (current_dim == eval_data->num_dims) { - // Base case if we finished evaluating. - if (output_index >= eval_data->output_size) { - return output_index; - } - eval_data->output_data[output_index] = eval_data->input_data[flat_index]; - return output_index + 1; - } - // Check if the value is computed already. - const int cache_index = current_dim * eval_data->input_size + flat_index; - auto& cache_entry = eval_data->cache[cache_index]; - if (cache_entry.start != -1) { - // Cache value is (start, end) interval. We can just copy the interval - // directly. - const int count = cache_entry.end - cache_entry.start; - memcpy(eval_data->output_data + output_index, - eval_data->output_data + cache_entry.start, count * sizeof(T)); - return output_index + count; - } - cache_entry.start = output_index; - int64_t left_pad = 0, right_pad = 0; - const int multiplier = (*eval_data->dimension_num_elements)[current_dim]; - const TfLiteTensor* padding_matrix = eval_data->padding_matrix; - const auto offset = eval_data->offset; - auto* dims = eval_data->input_dims; - - GetPadding(padding_matrix, current_dim, &left_pad, &right_pad); - // Left padding - for (int i = left_pad + offset - 1; i >= offset && left_pad > 0; - --i, --left_pad) { - output_index = Eval(eval_data, current_dim + 1, flat_index + i * multiplier, - output_index); - } - // Original values. - for (int i = 0; i < dims->data[current_dim]; ++i) { - output_index = Eval(eval_data, current_dim + 1, flat_index + i * multiplier, - output_index); - } - // Right padding. - for (int i = dims->data[current_dim] - (1 + offset); i >= 0 && right_pad > 0; - --i, --right_pad) { - output_index = Eval(eval_data, current_dim + 1, flat_index + i * multiplier, - output_index); - } - cache_entry.end = output_index; - return output_index; -} - // Returns the shape of the final output after padding. std::unique_ptr GetPaddedOutputShape( const TfLiteTensor* input, const TfLiteTensor* padding_matrix) { @@ -159,6 +90,70 @@ std::unique_ptr GetPaddedOutputShape( return shape; } +// Given dimension index and the left/right padding. +// Returns the corresponding dimension in the input array. +inline int GetInputDimension(int padded_dimension, int left_pad, int right_pad, + int input_dim_size, int offset) { + if (padded_dimension < left_pad) { + const int original_ind = left_pad + offset - 1; + return original_ind - (std::min(padded_dimension, original_ind - offset)); + } + padded_dimension -= left_pad; + if (padded_dimension >= input_dim_size) { + padded_dimension -= input_dim_size; + const int original_ind = input_dim_size - (1 + offset); + return original_ind - std::min(padded_dimension, original_ind); + } + return padded_dimension; +} + +// Given and index in output array, returns the index of the value +// in input array. +template +int GetFlatIndex(int index, EvalData* eval_data) { + int flat_index = 0; + int64_t left_pad = 0, right_pad = 0, dimension_index, index_in_input; + for (int i = 0; i < eval_data->num_dims; ++i) { + switch (eval_data->padding_matrix->type) { + case kTfLiteInt32: + GetPadding(eval_data->padding_matrix->data.i32, i, &left_pad, + &right_pad); + break; + case kTfLiteInt64: + GetPadding(eval_data->padding_matrix->data.i64, i, &left_pad, + &right_pad); + break; + default: + break; + } + dimension_index = index / (*eval_data->output_dims_num_elements)[i]; + index_in_input = + GetInputDimension(dimension_index, left_pad, right_pad, + eval_data->input_dims->data[i], eval_data->offset); + flat_index += index_in_input * (*eval_data->input_dims_num_elements)[i]; + index %= (*eval_data->output_dims_num_elements)[i]; + } + return flat_index; +} + +template +struct MirrorPadWorkerTask : cpu_backend_threadpool::Task { + MirrorPadWorkerTask(EvalData* eval_data, int start, int end) + : eval_data(eval_data), start(start), end(end) {} + void Run() override { + auto* input_data = eval_data->input_data; + auto* output_data = eval_data->output_data; + for (int i = start; i < end; ++i) { + output_data[i] = input_data[GetFlatIndex(i, eval_data)]; + } + } + + private: + EvalData* eval_data; + int start; + int end; +}; + } // namespace TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { @@ -183,36 +178,45 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { context->ResizeTensor(context, output_tensor, output_size.release())); } - std::vector dimension_num_elements(input_dims, 1); + std::vector output_dims_num_elements(input_dims, 1); + std::vector input_dims_num_elements(input_dims, 1); for (int i = input_dims - 2; i >= 0; i--) { - dimension_num_elements[i] = - dimension_num_elements[i + 1] * input_tensor->dims->data[i + 1]; + output_dims_num_elements[i] = + output_dims_num_elements[i + 1] * output_tensor->dims->data[i + 1]; + input_dims_num_elements[i] = + input_dims_num_elements[i + 1] * input_tensor->dims->data[i + 1]; } - const int input_size = NumElements(input_tensor); const int offset = params->mode != TfLiteMirrorPaddingMode::kTfLiteMirrorPaddingReflect ? 0 : 1; + + CpuBackendContext* cpu_backend_context = + CpuBackendContext::GetFromContext(context); + const int thread_count = cpu_backend_context->max_num_threads(); TfLiteStatus status = kTfLiteOk; - int output_index = 0; - // Reset cache array. - TfLiteTensor* cache = GetTemporary(context, node, /*index=*/0); - CacheElement* cache_data = reinterpret_cast(cache->data.raw); - std::fill(cache_data, cache_data + cache->dims->data[0], - CacheElement{-1, -1}); -#define TF_LITE_MIRROR_PAD(type) \ - EvalData eval_data; \ - eval_data.input_data = GetTensorData(input_tensor); \ - eval_data.input_dims = input_tensor->dims; \ - eval_data.input_size = input_size; \ - eval_data.dimension_num_elements = &dimension_num_elements; \ - eval_data.num_dims = input_dims; \ - eval_data.offset = offset; \ - eval_data.cache = cache_data; \ - eval_data.output_data = GetTensorData(output_tensor); \ - eval_data.output_size = NumElements(output_tensor); \ - eval_data.padding_matrix = padding_matrix; \ - Eval(&eval_data, 0, 0, output_index); + const int output_size = NumElements(output_tensor); +#define TF_LITE_MIRROR_PAD(type) \ + EvalData eval_data; \ + eval_data.input_data = GetTensorData(input_tensor); \ + eval_data.input_dims = input_tensor->dims; \ + eval_data.input_dims = input_tensor->dims; \ + eval_data.output_dims_num_elements = &output_dims_num_elements; \ + eval_data.input_dims_num_elements = &input_dims_num_elements; \ + eval_data.num_dims = input_dims; \ + eval_data.offset = offset; \ + eval_data.output_data = GetTensorData(output_tensor); \ + eval_data.padding_matrix = padding_matrix; \ + std::vector> tasks; \ + tasks.reserve(thread_count); \ + int start = 0; \ + for (int i = 0; i < thread_count; ++i) { \ + int end = start + (output_size - start) / (thread_count - i); \ + tasks.emplace_back(MirrorPadWorkerTask(&eval_data, start, end)); \ + start = end; \ + } \ + cpu_backend_threadpool::Execute(tasks.size(), tasks.data(), \ + cpu_backend_context); switch (output_tensor->type) { case kTfLiteFloat32: { @@ -240,44 +244,25 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { } void* Init(TfLiteContext* context, const char* buffer, size_t length) { - auto* op_data = new OpData; - context->AddTensors(context, 1, &op_data->cache_tensor_index); - return op_data; + return nullptr; } -void Free(TfLiteContext* context, void* buffer) { - delete reinterpret_cast(buffer); -} +void Free(TfLiteContext* context, void* buffer) {} TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { const TfLiteTensor* input_tensor = GetInput(context, node, 0); const TfLiteTensor* padding_matrix = GetInput(context, node, 1); TfLiteTensor* output_tensor = GetOutput(context, node, 0); - OpData* op_data = reinterpret_cast(node->user_data); TF_LITE_ENSURE_EQ(context, NumDimensions(padding_matrix), 2); TF_LITE_ENSURE_EQ(context, SizeOfDimension(padding_matrix, 0), NumDimensions(input_tensor)); - TfLiteIntArrayFree(node->temporaries); - node->temporaries = TfLiteIntArrayCreate(1); - node->temporaries->data[0] = op_data->cache_tensor_index; - - int num_elements = NumElements(input_tensor) * NumDimensions(input_tensor); - TfLiteIntArray* cache_dims = TfLiteIntArrayCreate(1); - cache_dims->data[0] = (num_elements + 1); - - TfLiteTensor* cache = &context->tensors[op_data->cache_tensor_index]; - cache->type = kTfLiteInt64; - cache->allocation_type = kTfLiteArenaRw; - TF_LITE_ENSURE_OK(context, context->ResizeTensor(context, cache, cache_dims)); - if (!IsConstantTensor(padding_matrix)) { SetTensorToDynamic(output_tensor); return kTfLiteOk; } // We have constant padding, so we can infer output size. - auto output_size = GetPaddedOutputShape(input_tensor, padding_matrix); if (output_size == nullptr) { return kTfLiteError; diff --git a/tensorflow/lite/kernels/numeric_verify.cc b/tensorflow/lite/kernels/numeric_verify.cc new file mode 100644 index 00000000000..798e2b4b847 --- /dev/null +++ b/tensorflow/lite/kernels/numeric_verify.cc @@ -0,0 +1,188 @@ +/* Copyright 2019 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 + +#include +#include +#include + +#include "third_party/eigen3/Eigen/Core" +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/dequantize.h" +#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h" +#include "tensorflow/lite/kernels/internal/reference/integer_ops/dequantize.h" +#include "tensorflow/lite/kernels/internal/reference/reference_ops.h" +#include "tensorflow/lite/kernels/internal/tensor.h" +#include "tensorflow/lite/kernels/internal/tensor_ctypes.h" +#include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/kernels/op_macros.h" + +namespace tflite { +namespace ops { +namespace custom { +namespace numeric_verify { + +struct OpContext { + OpContext(TfLiteContext* context, TfLiteNode* node) { + input = GetInput(context, node, 0); + ref = GetInput(context, node, 1); + } + const TfLiteTensor* input; + const TfLiteTensor* ref; +}; + +const int kTensorNotAllocated = -1; + +struct OpData { + float tolerance; + // This boolean value is only used when the input tensor is constant. + bool float_input_initialized; + int cache_tensor_id = kTensorNotAllocated; +}; + +void* Init(TfLiteContext* context, const char* buffer, size_t length) { + auto* op_data = new OpData(); + op_data->float_input_initialized = false; + + // Get the tolerance parameter from the buffer. Use flexbuffers asMap if there + // multiple custom options. + const float* buffer_t = reinterpret_cast(buffer); + op_data->tolerance = *buffer_t; + + return op_data; +} + +void Free(TfLiteContext* context, void* buffer) { + delete reinterpret_cast(buffer); +} + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + TF_LITE_ENSURE_EQ(context, NumInputs(node), 2); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 0); + OpData* op_data = reinterpret_cast(node->user_data); + + OpContext op_context(context, node); + + TF_LITE_ENSURE(context, op_context.input->type == kTfLiteUInt8 || + op_context.input->type == kTfLiteInt8 || + op_context.input->type == kTfLiteInt16 || + op_context.input->type == kTfLiteFloat16); + TF_LITE_ENSURE(context, op_context.ref->type == kTfLiteFloat32); + + // Allocate tensor to store the dequantized inputs. + if (op_data->cache_tensor_id == kTensorNotAllocated) { + TF_LITE_ENSURE_OK( + context, context->AddTensors(context, 1, &op_data->cache_tensor_id)); + } + + TfLiteIntArrayFree(node->temporaries); + node->temporaries = TfLiteIntArrayCreate(1); + node->temporaries->data[0] = op_data->cache_tensor_id; + + TfLiteTensor* dequantized = GetTemporary(context, node, /*index=*/0); + dequantized->type = op_context.ref->type; + dequantized->allocation_type = kTfLiteDynamic; + + TF_LITE_ENSURE_OK(context, context->ResizeTensor( + context, dequantized, + TfLiteIntArrayCopy(op_context.input->dims))); + + return kTfLiteOk; +} + +template +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + OpData* op_data = reinterpret_cast(node->user_data); + OpContext op_context(context, node); + if (IsConstantTensor(op_context.input) && op_data->float_input_initialized) { + return kTfLiteOk; + } + + // Dequantize the input + TfLiteTensor* dequantized = GetTemporary(context, node, /*index=*/0); + auto status = builtin::dequantize::DequantizeImpl( + context, node, op_context.input, dequantized); + if (status != kTfLiteOk) { + return status; + } + + if (IsConstantTensor(op_context.input)) { + op_data->float_input_initialized = true; + } + + // Verify the dequantized output + for (int i = 0; i < NumElements(op_context.ref); ++i) { + int32_t value; + switch (op_context.input->type) { + case kTfLiteUInt8: + value = GetTensorData(op_context.input)[i]; + break; + case kTfLiteInt8: + value = GetTensorData(op_context.input)[i]; + + break; + case kTfLiteInt16: + value = GetTensorData(op_context.input)[i]; + break; + default: + value = 0; + } + float dequant = GetTensorData(dequantized)[i]; + float reference = GetTensorData(op_context.ref)[i]; + float diff = std::abs(reference - dequant); + float error = diff / (reference + 1e-8); + // It is fine if the error is introduced by rounding so the diff will be + // smaller than `scale`. + if (diff > op_context.input->params.scale && error > op_data->tolerance) { + context->ReportError(context, + "Mismatch: %f is quantized to %d with (%f, %d). " + "abs((%f - %f) / %f) = %f > %f (tolerance).\n", + reference, value, op_context.input->params.scale, + op_context.input->params.zero_point, reference, + dequant, reference, error, op_data->tolerance); + return kTfLiteError; + } + } + return kTfLiteOk; +} + +} // namespace numeric_verify + +TfLiteRegistration* Register_NUMERIC_VERIFY_OPT() { + static TfLiteRegistration r = { + numeric_verify::Init, numeric_verify::Free, numeric_verify::Prepare, + numeric_verify::Eval}; + return &r; +} + +TfLiteRegistration* Register_NUMERIC_VERIFY_REF() { + static TfLiteRegistration r = { + numeric_verify::Init, numeric_verify::Free, numeric_verify::Prepare, + numeric_verify::Eval}; + return &r; +} + +TfLiteRegistration* Register_NUMERIC_VERIFY() { +#ifdef USE_NEON + return Register_NUMERIC_VERIFY_OPT(); +#else + return Register_NUMERIC_VERIFY_REF(); +#endif +} + +} // namespace custom +} // namespace ops +} // namespace tflite diff --git a/tensorflow/lite/kernels/numeric_verify_test.cc b/tensorflow/lite/kernels/numeric_verify_test.cc new file mode 100644 index 00000000000..820c0d9ac32 --- /dev/null +++ b/tensorflow/lite/kernels/numeric_verify_test.cc @@ -0,0 +1,119 @@ +/* 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 +#include + +#include +#include "absl/memory/memory.h" +#include "third_party/eigen3/Eigen/Core" +#include "tensorflow/lite/interpreter.h" +#include "tensorflow/lite/kernels/internal/types.h" +#include "tensorflow/lite/kernels/register.h" +#include "tensorflow/lite/kernels/test_util.h" +#include "tensorflow/lite/model.h" + +namespace tflite { + +namespace ops { +namespace custom { + +TfLiteRegistration* Register_NUMERIC_VERIFY(); + +} // namespace custom +} // namespace ops + +namespace { + +class NumericVerifyOpModel : public SingleOpModel { + public: + NumericVerifyOpModel(TensorType type, std::initializer_list shape, + float scale, int32_t zero_point, int version, + float tolerance = 1e-5) { + const TensorData input_tensor_data = {type, shape, 0, 0, scale, zero_point}; + input_ = AddInput(input_tensor_data); + ref_ = AddInput({TensorType_FLOAT32, shape}); + + std::vector custom_options(sizeof(float)); + memcpy(custom_options.data(), &tolerance, sizeof(float)); + + SetCustomOp("NUMERIC_VERIFY", custom_options, + ops::custom::Register_NUMERIC_VERIFY); + + BuildInterpreter({GetShape(input_), GetShape(ref_)}); + } + + template + void SetInputs(std::initializer_list data, + std::initializer_list ref_data) { + PopulateTensor(input_, data); + PopulateTensor(ref_, ref_data); + } + + private: + int input_; + int ref_; +}; + +TEST(NumericVerifyOpTest, Uint8) { + // [-63.5, 64] -> scale=0.5 zero_point=127 for UINT8 + NumericVerifyOpModel m(TensorType_UINT8, {2, 5}, 0.5, 127, 1); + + m.SetInputs({0, 1, 2, 3, 4, 251, 252, 253, 254, 255}, + {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64}); + EXPECT_EQ(m.InvokeUnchecked(), kTfLiteOk); +} + +TEST(NumericVerifyOpTest, Int8) { + // [-63.5, 64] -> scale=0.5, zero_point=1 for INT8 + NumericVerifyOpModel m(TensorType_INT8, {2, 5}, 0.5, -1, 2); + + m.SetInputs({-128, -127, -126, -125, -124, 123, 124, 125, 126, 127}, + {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64}); + EXPECT_EQ(m.InvokeUnchecked(), kTfLiteOk); +} + +TEST(NumericVerifyOpTest, Float16) { + NumericVerifyOpModel m(TensorType_FLOAT16, {2, 3}, 1.0f, 0, 3, + /*tolerance=*/0.1f); + + std::vector half{Eigen::half{-535.54f}, Eigen::half{-100.0f}, + Eigen::half{-1.0f}, Eigen::half{0.f}, + Eigen::half{1.0f}, Eigen::half{100.32f}}; + m.PopulateTensor(0, 0, reinterpret_cast(half.data()), + reinterpret_cast(half.data()) + half.size()); + m.PopulateTensor(1, {-535.54f, -100.0f, -1.0f, 0.f, 1.0f, 100.32f}); + EXPECT_EQ(m.InvokeUnchecked(), kTfLiteOk); +} + +TEST(NumericVerifyOpTest, Int16) { + NumericVerifyOpModel m(TensorType_INT16, {2, 5}, 0.5, -1, 4); + m.SetInputs( + {-130, -127, -126, -125, -124, 123, 124, 125, 126, 130}, + {-64.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 65.5}); + EXPECT_EQ(m.InvokeUnchecked(), kTfLiteOk); +} + +TEST(NumericVerifyOpFailedTest, Int8) { + // [-63.5, 64] -> scale=0.5, zero_point=1 for INT8 + NumericVerifyOpModel m(TensorType_INT8, {2, 5}, 0.5, -1, 2); + + // The 5th element is set to 0. + m.SetInputs({-128, -127, -126, -125, -124, 0, 124, 125, 126, 127}, + {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64}); + EXPECT_EQ(m.InvokeUnchecked(), kTfLiteError); +} + +} // namespace +} // namespace tflite diff --git a/tensorflow/lite/kernels/op_macros.h b/tensorflow/lite/kernels/op_macros.h index b1d8059c51c..44208007b8a 100644 --- a/tensorflow/lite/kernels/op_macros.h +++ b/tensorflow/lite/kernels/op_macros.h @@ -19,7 +19,7 @@ limitations under the License. // non-portable function. #ifdef TF_LITE_MCU_DEBUG_LOG -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" #define DEBUG_LOG(x) \ do { \ diff --git a/tensorflow/lite/kernels/pad.cc b/tensorflow/lite/kernels/pad.cc index f62621201c3..1bd4f65a043 100644 --- a/tensorflow/lite/kernels/pad.cc +++ b/tensorflow/lite/kernels/pad.cc @@ -107,7 +107,8 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { } // TODO(nupurgarg): Current implementations rely on the inputs being <= 4D. - TF_LITE_ENSURE(context, op_context.dims <= 4); + TF_LITE_ENSURE( + context, op_context.dims <= reference_ops::PadKernelMaxDimensionCount()); // Exit early if paddings is a non-const tensor. Set output tensor to // dynamic so output size can be determined in Eval. @@ -132,32 +133,22 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_OK(context, ResizeOutputTensor(context, &op_context)); } - // TODO(nupurgarg): Change kernel implementation to take in int* instead of - // vector to remove malloc from Eval(). // Create before and after padding arrays that are accepted by the kernel. - std::vector before_padding; - std::vector after_padding; const int32* paddings_data = GetTensorData(op_context.paddings); - // TODO(nupurgarg): Change kernel implementation to use padding arrays in - // forward order (depth, width, height, batch). - // Build paddings in order of int[] = {batch, height, width, depth} to match - // kernel implementation of Pad in reference_ops.h and optimized_ops.h. + TF_LITE_ENSURE( + context, op_context.dims <= reference_ops::PadKernelMaxDimensionCount()); + + tflite::PadParams op_params; + op_params.left_padding_count = op_context.dims; + op_params.right_padding_count = op_context.dims; + for (int idx = op_context.dims - 1; idx >= 0; --idx) { - before_padding.push_back(paddings_data[idx * 2]); - after_padding.push_back(paddings_data[idx * 2 + 1]); + op_params.left_padding[idx] = paddings_data[idx * 2]; + op_params.right_padding[idx] = paddings_data[idx * 2 + 1]; } #define TF_LITE_PAD(type, op_name, scalar, pad_value) \ - TF_LITE_ENSURE(context, before_padding.size() <= 4); \ - TF_LITE_ENSURE(context, after_padding.size() <= 4); \ - tflite::PadParams op_params; \ - op_params.left_padding_count = before_padding.size(); \ - op_params.right_padding_count = after_padding.size(); \ - for (int i = 0; i < op_context.dims; ++i) { \ - op_params.left_padding[i] = before_padding[op_context.dims - 1 - i]; \ - op_params.right_padding[i] = after_padding[op_context.dims - 1 - i]; \ - } \ const scalar pad_value_copy = pad_value; \ \ type::op_name(op_params, GetTensorShape(op_context.input), \ diff --git a/tensorflow/lite/kernels/pad_test.cc b/tensorflow/lite/kernels/pad_test.cc index 2dc2d92f5d3..96500a5bf4c 100644 --- a/tensorflow/lite/kernels/pad_test.cc +++ b/tensorflow/lite/kernels/pad_test.cc @@ -189,7 +189,7 @@ TEST(PadOpTest, TooManyDimensions) { PadOpConstModel({TensorType_FLOAT32, {1, 2, 3, 4, 5, 6, 7, 8, 9}}, {9, 2}, {1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, {TensorType_FLOAT32}), - "dims <= 4"); + "dims <= reference_ops::PadKernelMaxDimensionCount()"); } TEST(PadOpTest, UnequalDimensions) { @@ -426,7 +426,7 @@ TEST(PadV2OpTest, TooManyDimensions) { EXPECT_DEATH(f({TensorType_FLOAT32, {1, 2, 3, 4, 5, 6, 7, 8, 9}}, {9, 2}, {1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, 0.0, {TensorType_FLOAT32}), - "dims <= 4"); + "dims <= reference_ops::PadKernelMaxDimensionCount()"); } TEST(PadV2OpTest, UnequalDimensions) { diff --git a/tensorflow/lite/kernels/register.cc b/tensorflow/lite/kernels/register.cc index 68e102511eb..3c747488eff 100644 --- a/tensorflow/lite/kernels/register.cc +++ b/tensorflow/lite/kernels/register.cc @@ -21,6 +21,7 @@ namespace tflite { namespace ops { namespace custom { +TfLiteRegistration* Register_NUMERIC_VERIFY(); TfLiteRegistration* Register_AUDIO_SPECTROGRAM(); TfLiteRegistration* Register_MFCC(); TfLiteRegistration* Register_DETECTION_POSTPROCESS(); @@ -200,6 +201,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_SELECT, Register_SELECT(), /* min_version */ 1, /* max_version */ 2); + AddBuiltin(BuiltinOperator_SELECT_V2, Register_SELECT_V2()); AddBuiltin(BuiltinOperator_SLICE, Register_SLICE(), /* min_version */ 1, /* max_version */ 3); @@ -276,7 +278,7 @@ BuiltinOpResolver::BuiltinOpResolver() { AddBuiltin(BuiltinOperator_NON_MAX_SUPPRESSION_V5, Register_NON_MAX_SUPPRESSION_V5()); AddBuiltin(BuiltinOperator_SCATTER_ND, Register_SCATTER_ND()); - + AddCustom("NumericVerify", tflite::ops::custom::Register_NUMERIC_VERIFY()); // TODO(andrewharp, ahentz): Move these somewhere more appropriate so that // custom ops aren't always included by default. AddCustom("Mfcc", tflite::ops::custom::Register_MFCC()); diff --git a/tensorflow/lite/kernels/register_ref.cc b/tensorflow/lite/kernels/register_ref.cc index 6d10828869f..e40ba896e7a 100644 --- a/tensorflow/lite/kernels/register_ref.cc +++ b/tensorflow/lite/kernels/register_ref.cc @@ -132,6 +132,7 @@ TfLiteRegistration* Register_MIRROR_PAD(); TfLiteRegistration* Register_QUANTIZE(); TfLiteRegistration* Register_HARD_SWISH_REF(); TfLiteRegistration* Register_DEPTH_TO_SPACE_REF(); +TfLiteRegistration* Register_SELECT_V2(); namespace { @@ -284,6 +285,7 @@ BuiltinRefOpResolver::BuiltinRefOpResolver() { AddBuiltin(BuiltinOperator_MIRROR_PAD, Register_MIRROR_PAD()); AddBuiltin(BuiltinOperator_QUANTIZE, Register_QUANTIZE()); AddBuiltin(BuiltinOperator_HARD_SWISH, Register_HARD_SWISH_REF()); + AddBuiltin(BuiltinOperator_SELECT_V2, Register_SELECT_V2()); // TODO(andrewharp, ahentz): Move these somewhere more appropriate so that // custom ops aren't always included by default. diff --git a/tensorflow/lite/kernels/select.cc b/tensorflow/lite/kernels/select.cc index bff730a8087..89fac10c869 100644 --- a/tensorflow/lite/kernels/select.cc +++ b/tensorflow/lite/kernels/select.cc @@ -29,7 +29,31 @@ constexpr int kInputTensorX = 1; constexpr int kInputTensorY = 2; constexpr int kOutputTensor = 0; +enum KernelType { + kVersionOne, + kVersionTwo, +}; + +struct OpData { + bool requires_broadcast; + bool has_rank_one_input_condition; +}; + +void* SelectInit(TfLiteContext* context, const char* buffer, size_t length) { + auto* data = new OpData; + data->requires_broadcast = false; + data->has_rank_one_input_condition = false; + return data; +} + +void SelectFree(TfLiteContext* context, void* buffer) { + delete reinterpret_cast(buffer); +} + +template TfLiteStatus SelectPrepare(TfLiteContext* context, TfLiteNode* node) { + OpData* data = reinterpret_cast(node->user_data); + TF_LITE_ENSURE_EQ(context, NumInputs(node), 3); TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); @@ -41,35 +65,51 @@ TfLiteStatus SelectPrepare(TfLiteContext* context, TfLiteNode* node) { // Input must be bool. TF_LITE_ENSURE(context, input_condition->type == kTfLiteBool); - - // Input tensors must have the same type and size TF_LITE_ENSURE_EQ(context, input_x->type, input_y->type); - TF_LITE_ENSURE(context, HaveSameShapes(input_x, input_y)); output->type = input_x->type; - // Either the same shape, or input_condition must be Rank 1 and match over the - // first dimension. - bool same_shape = HaveSameShapes(input_condition, input_x); - if (!same_shape && NumDimensions(input_condition) == 1) { - same_shape = - SizeOfDimension(input_condition, 0) == SizeOfDimension(input_x, 0); + bool same_shape = HaveSameShapes(input_condition, input_x) && + HaveSameShapes(input_x, input_y); + TfLiteIntArray* output_size; + if (!same_shape) { + switch (kernel_type) { + case kVersionOne: { + data->has_rank_one_input_condition = + NumDimensions(input_condition) == 1 && + SizeOfDimension(input_condition, 0) == SizeOfDimension(input_x, 0); + TF_LITE_ENSURE(context, data->has_rank_one_input_condition); + + output_size = TfLiteIntArrayCopy(input_x->dims); + + // Input tensors must have the same type and size + TF_LITE_ENSURE(context, HaveSameShapes(input_x, input_y)); + break; + } + case kVersionTwo: { + TF_LITE_ENSURE_OK(context, CalculateShapeForBroadcast( + context, input_condition, input_x, + input_y, &output_size)); + data->requires_broadcast = true; + break; + } + default: + return kTfLiteError; + } + } else { + output_size = TfLiteIntArrayCopy(input_x->dims); } - TF_LITE_ENSURE(context, same_shape); - - TfLiteIntArray* output_size = TfLiteIntArrayCopy(input_x->dims); return context->ResizeTensor(context, output, output_size); } TfLiteStatus SelectEval(TfLiteContext* context, TfLiteNode* node) { + OpData* data = reinterpret_cast(node->user_data); const TfLiteTensor* input_condition = GetInput(context, node, kInputTensorCondition); const TfLiteTensor* input_x = GetInput(context, node, kInputTensorX); const TfLiteTensor* input_y = GetInput(context, node, kInputTensorY); TfLiteTensor* output = GetOutput(context, node, kOutputTensor); - bool is_rank_one = !HaveSameShapes(input_condition, input_x); - #define TF_LITE_SELECT(type, op) \ reference_ops::op(GetTensorShape(input_condition), \ GetTensorData(input_condition), \ @@ -109,8 +149,10 @@ TfLiteStatus SelectEval(TfLiteContext* context, TfLiteNode* node) { return kTfLiteError; \ } - if (is_rank_one) { + if (data->has_rank_one_input_condition) { TF_LITE_SWITCH(input_x->type, RankOneSelect); + } else if (data->requires_broadcast) { + TF_LITE_SWITCH(input_x->type, BroadcastSelect4DSlow); } else { TF_LITE_SWITCH(input_x->type, Select); } @@ -122,8 +164,26 @@ TfLiteStatus SelectEval(TfLiteContext* context, TfLiteNode* node) { } // namespace select +// Select op selects values of 'x' if the corresponding value of 'condition' is +// true or the value of 'y' if false. There are valid condition input sizes: +// +// 1. Either the same shape (in which case the select is elementwise), or +// 2. condition must be Rank 1 and match over the first dimension. TfLiteRegistration* Register_SELECT() { - static TfLiteRegistration r = {nullptr, nullptr, select::SelectPrepare, + static TfLiteRegistration r = {select::SelectInit, select::SelectFree, + select::SelectPrepare, + select::SelectEval}; + return &r; +} + +// SelectV2 op selects values of 'x' if the corresponding value of 'condition' +// is true or the value of 'y' if false. There are valid condition input sizes: +// +// 1. Either the same shape (in which case the select is elementwise), or +// 2. Broadcastable shapes between 'condition', 'x' and 'y'. +TfLiteRegistration* Register_SELECT_V2() { + static TfLiteRegistration r = {select::SelectInit, select::SelectFree, + select::SelectPrepare, select::SelectEval}; return &r; } diff --git a/tensorflow/lite/kernels/select_test.cc b/tensorflow/lite/kernels/select_test.cc index c1602abf942..36935b0b6dc 100644 --- a/tensorflow/lite/kernels/select_test.cc +++ b/tensorflow/lite/kernels/select_test.cc @@ -23,21 +23,17 @@ namespace { using ::testing::ElementsAreArray; -class SelectOpModel : public SingleOpModel { +class BaseSelectOpModel : public SingleOpModel { public: - SelectOpModel(std::initializer_list input1_shape, - std::initializer_list input2_shape, - std::initializer_list input3_shape, - TensorType input_type) { + BaseSelectOpModel(std::initializer_list input1_shape, + std::initializer_list input2_shape, + std::initializer_list input3_shape, + TensorType input_type) { input1_ = AddInput(TensorType_BOOL); input2_ = AddInput(input_type); input3_ = AddInput(input_type); output_ = AddOutput(input_type); - SetBuiltinOp(BuiltinOperator_SELECT, BuiltinOptions_SelectOptions, - CreateSelectOptions(builder_).Union()); - BuildInterpreter({input1_shape, input2_shape, input3_shape}); } - int input1() { return input1_; } int input2() { return input2_; } int input3() { return input3_; } @@ -49,13 +45,40 @@ class SelectOpModel : public SingleOpModel { std::vector GetOutputShape() { return GetTensorShape(output_); } - private: + protected: int input1_; int input2_; int input3_; int output_; }; +class SelectOpModel : public BaseSelectOpModel { + public: + SelectOpModel(std::initializer_list input1_shape, + std::initializer_list input2_shape, + std::initializer_list input3_shape, TensorType input_type) + : BaseSelectOpModel(input1_shape, input2_shape, input3_shape, + input_type) { + SetBuiltinOp(BuiltinOperator_SELECT, BuiltinOptions_SelectOptions, + CreateSelectOptions(builder_).Union()); + BuildInterpreter({input1_shape, input2_shape, input3_shape}); + } +}; + +class SelectV2OpModel : public BaseSelectOpModel { + public: + SelectV2OpModel(std::initializer_list input1_shape, + std::initializer_list input2_shape, + std::initializer_list input3_shape, + TensorType input_type) + : BaseSelectOpModel(input1_shape, input2_shape, input3_shape, + input_type) { + SetBuiltinOp(BuiltinOperator_SELECT_V2, BuiltinOptions_SelectV2Options, + CreateSelectV2Options(builder_).Union()); + BuildInterpreter({input1_shape, input2_shape, input3_shape}); + } +}; + TEST(SelectOpTest, SelectBool) { SelectOpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, {1, 1, 1, 4}, TensorType_BOOL); @@ -159,5 +182,145 @@ TEST(SelectOpTest, RankZeroSelectInt32) { EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2, 1})); } +TEST(SelectV2OpTest, SelectBool) { + SelectV2OpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, {1, 1, 1, 4}, + TensorType_BOOL); + + model.PopulateTensor(model.input1(), {true, false, true, false}); + model.PopulateTensor(model.input2(), {false, false, false, false}); + model.PopulateTensor(model.input3(), {true, true, true, true}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({false, true, false, true})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 1, 4})); +} + +TEST(SelectV2OpTest, SelectFloat) { + SelectV2OpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, {1, 1, 1, 4}, + TensorType_FLOAT32); + + model.PopulateTensor(model.input1(), {true, false, true, false}); + model.PopulateTensor(model.input2(), {0.1, 0.2, 0.3, 0.4}); + model.PopulateTensor(model.input3(), {0.5, 0.6, 0.7, 0.8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({0.1, 0.6, 0.3, 0.8})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 1, 4})); +} + +TEST(SelectV2OpTest, SelectUInt8) { + SelectV2OpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, {1, 1, 1, 4}, + TensorType_UINT8); + + model.PopulateTensor(model.input1(), {false, true, false, false}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 2, 7, 8})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 1, 4})); +} + +TEST(SelectV2OpTest, SelectInt8) { + SelectV2OpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, {1, 1, 1, 4}, + TensorType_INT8); + + model.PopulateTensor(model.input1(), {false, true, false, false}); + model.PopulateTensor(model.input2(), {1, -2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, -8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, -2, 7, -8})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 1, 4})); +} + +TEST(SelectV2OpTest, SelectInt16) { + SelectV2OpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, {1, 1, 1, 4}, + TensorType_INT16); + + model.PopulateTensor(model.input1(), {false, true, false, false}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 2, 7, 8})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 1, 4})); +} + +TEST(SelectV2OpTest, SelectInt32) { + SelectV2OpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, {1, 1, 1, 4}, + TensorType_INT32); + + model.PopulateTensor(model.input1(), {false, true, false, false}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 2, 7, 8})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 1, 4})); +} + +TEST(SelectV2OpTest, BroadcastSelectInt32OneDimensionConditionWithSingleValue) { + SelectV2OpModel model({1}, {1, 2, 2, 1}, {1, 2, 2, 1}, TensorType_INT32); + + model.PopulateTensor(model.input1(), {false}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 6, 7, 8})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2, 1})); +} + +TEST(SelectV2OpTest, BroadcastSelectInt32LesserThan4D) { + SelectV2OpModel model({1, 2}, {1, 2, 2}, {1, 2, 2}, TensorType_INT32); + + model.PopulateTensor(model.input1(), {false, true}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 2, 7, 4})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2})); +} + +TEST(SelectV2OpTest, BroadcastSelectInt32OnFalseValue) { + SelectV2OpModel model({1, 1}, {1, 1, 2, 2}, {1, 1, 2, 2}, TensorType_INT32); + + model.PopulateTensor(model.input1(), {false}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 6, 7, 8})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 1, 2, 2})); +} + +TEST(SelectV2OpTest, BroadcastSelectInt32) { + SelectV2OpModel model({1, 2}, {1, 2, 2}, {1, 2, 2}, TensorType_INT32); + + model.PopulateTensor(model.input1(), {false, true}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), ElementsAreArray({5, 2, 7, 4})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({1, 2, 2})); +} + +TEST(SelectV2OpTest, BroadcastSelectInt32OneDimensionConditionWithTwoValues) { + SelectV2OpModel model({2}, {2, 1, 2, 1}, {2, 1, 2, 1}, TensorType_INT32); + + model.PopulateTensor(model.input1(), {false, true}); + model.PopulateTensor(model.input2(), {1, 2, 3, 4}); + model.PopulateTensor(model.input3(), {5, 6, 7, 8}); + model.Invoke(); + + EXPECT_THAT(model.GetOutput(), + ElementsAreArray({5, 1, 6, 2, 7, 3, 8, 4})); + EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 2, 2})); +} + } // namespace } // namespace tflite diff --git a/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc b/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc index 6b7b907a30a..470f8aec42b 100644 --- a/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc +++ b/tensorflow/lite/kernels/unidirectional_sequence_lstm.cc @@ -360,8 +360,10 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* activation_state = GetVariableInput(context, node, kInputActivationStateTensor); + TF_LITE_ENSURE(context, activation_state != nullptr); TfLiteTensor* cell_state = GetVariableInput(context, node, kInputCellStateTensor); + TF_LITE_ENSURE(context, cell_state != nullptr); // Check the shape of input state tensors. // These tensor may be 1D or 2D. It's fine as long as the total size is @@ -551,8 +553,10 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TfLiteTensor* activation_state = GetVariableInput(context, node, kInputActivationStateTensor); + TF_LITE_ENSURE(context, activation_state != nullptr); TfLiteTensor* cell_state = GetVariableInput(context, node, kInputCellStateTensor); + TF_LITE_ENSURE(context, cell_state != nullptr); const TfLiteTensor* input_layer_norm_coefficients = is_layer_norm_lstm ? GetOptionalInputTensor( diff --git a/tensorflow/lite/experimental/micro/BUILD b/tensorflow/lite/micro/BUILD similarity index 80% rename from tensorflow/lite/experimental/micro/BUILD rename to tensorflow/lite/micro/BUILD index 7b3320a41e7..412dc9fcbc2 100644 --- a/tensorflow/lite/experimental/micro/BUILD +++ b/tensorflow/lite/micro/BUILD @@ -1,7 +1,11 @@ load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) +load( + "//tensorflow/lite/micro:build_def.bzl", + "cc_library", +) package( default_visibility = ["//visibility:public"], @@ -35,6 +39,7 @@ cc_library( "simple_memory_allocator.h", "test_helpers.h", ], + build_for_embedded = True, copts = [ "-Werror", "-Wdouble-promotion", @@ -45,8 +50,8 @@ cc_library( "//tensorflow/lite:type_to_tflitetype", "//tensorflow/lite/c:common", "//tensorflow/lite/core/api", - "//tensorflow/lite/experimental/micro/memory_planner:greedy_memory_planner", "//tensorflow/lite/kernels/internal:tensor", + "//tensorflow/lite/micro/memory_planner:greedy_memory_planner", "//tensorflow/lite/schema:schema_fbs", ], ) @@ -59,8 +64,10 @@ cc_library( hdrs = [ "micro_utils.h", ], + build_for_embedded = True, deps = [ "//tensorflow/lite/c:common", + "//tensorflow/lite/kernels:op_macros", ], ) @@ -81,7 +88,7 @@ tflite_micro_cc_test( ], deps = [ ":micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -92,7 +99,7 @@ tflite_micro_cc_test( ], deps = [ ":micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -103,7 +110,7 @@ tflite_micro_cc_test( ], deps = [ ":micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -114,7 +121,7 @@ tflite_micro_cc_test( ], deps = [ ":micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -125,7 +132,7 @@ tflite_micro_cc_test( ], deps = [ ":micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -136,7 +143,7 @@ tflite_micro_cc_test( ], deps = [ ":micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -147,6 +154,6 @@ tflite_micro_cc_test( ], deps = [ ":micro_utils", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) diff --git a/tensorflow/lite/experimental/micro/README.md b/tensorflow/lite/micro/README.md similarity index 94% rename from tensorflow/lite/experimental/micro/README.md rename to tensorflow/lite/micro/README.md index 39c4c027680..ccf438ea385 100644 --- a/tensorflow/lite/experimental/micro/README.md +++ b/tensorflow/lite/micro/README.md @@ -1,8 +1,8 @@ # TensorFlow Lite for Microcontrollers -TensorFlow Lite for Microcontrollers is an experimental port of TensorFlow Lite -designed to run machine learning models on microcontrollers and other devices -with only kilobytes of memory. +TensorFlow Lite for Microcontrollers is a port of TensorFlow Lite designed to run +machine learning models on microcontrollers and other devices with only kilobytes +of memory. To learn how to use the framework, visit the developer documentation at [tensorflow.org/lite/microcontrollers](https://www.tensorflow.org/lite/microcontrollers). @@ -88,13 +88,13 @@ As mentioned above, the one function you will need to implement for a completely new platform is debug logging. If your device is just a variation on an existing platform you may be able to reuse code that's already been written. To understand what's available, begin with the default reference implementation at -[tensorflow/lite/experimental/micro/debug_log.cc](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/debug_log.cc), +[tensorflow/lite/micro/debug_log.cc](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/debug_log.cc), which uses fprintf and stderr. If your platform has this level of support for the C standard library in its toolchain, then you can just reuse this. Otherwise, you'll need to do some research into how your platform and device can communicate logging statements to the outside world. As another example, take a look at -[the Mbed version of `DebugLog()`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/mbed/debug_log.cc), +[the Mbed version of `DebugLog()`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/mbed/debug_log.cc), which creates a UART object and uses it to output strings to the host's console if it's connected. @@ -115,7 +115,7 @@ make sure that C++11 compatibility is turned on, and that the right include paths (as mentioned in the makefile) have been added. You'll see the default `DebugLog()` implementation in -'tensorflow/lite/experimental/micro/debug_log.cc' inside the +'tensorflow/lite/micro/debug_log.cc' inside the micro_error_reporter_test folder. Modify that file to add the right implementation for your platform, and then you should be able to build the set of files into an executable. Transfer that executable to your target device (for @@ -174,13 +174,13 @@ specialized implementation, you can create a folder in the same directory as the header and reference source, name it after your platform, and put your implementation in a `.cc` file inside that folder. We've already seen one example of this, where the Mbed and Bluepill versions of `DebugLog()` are inside -[mbed](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/mbed) +[mbed](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/mbed) and -[bluepill](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/bluepill) +[bluepill](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/bluepill) folders, children of the -[same directory](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) +[same directory](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite//micro) where the stdio-based -[`debug_log.cc`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/debug_log.cc) +[`debug_log.cc`](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/debug_log.cc) reference implementation is found. The advantage of this approach is that we can automatically pick specialized @@ -266,7 +266,7 @@ kernel implementations, but with some specific conventions: - No platform-specific macros or #ifdef’s should be used in any portable code. The implementation of these rules is handled inside the Makefile, with a -[`specialize` function](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/tools/make/helper_functions.inc#L42) +[`specialize` function](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/tools/make/helper_functions.inc#L42) that takes a list of reference source file paths as an input, and returns the equivalent list with specialized versions of those files swapped in if they exist. @@ -283,9 +283,9 @@ bottlenecks, and then add specialized implementations in their own folders. These don't need to be platform specific, they can also be broken out by which library they rely on for example. [Here's where we do that for the CMSIS implementation of integer fast-fourier -transforms](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.cc). +transforms](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.cc). This more complex case shows that you can also add helper source files alongside the main implementation, as long as you -[mention them in the platform-specific makefile](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc). +[mention them in the platform-specific makefile](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/micro_speech/CMSIS/Makefile.inc). You can also do things like update the list of libraries that need to be linked in, or add include paths to required headers. diff --git a/tensorflow/lite/experimental/micro/apollo3evb/debug_log.cc b/tensorflow/lite/micro/apollo3evb/debug_log.cc similarity index 92% rename from tensorflow/lite/experimental/micro/apollo3evb/debug_log.cc rename to tensorflow/lite/micro/apollo3evb/debug_log.cc index b68af81c5f5..2779d941784 100644 --- a/tensorflow/lite/experimental/micro/apollo3evb/debug_log.cc +++ b/tensorflow/lite/micro/apollo3evb/debug_log.cc @@ -31,10 +31,10 @@ limitations under the License. // To add an equivalent function for your own platform, create your own // implementation file, and place it in a subfolder with named after the OS // you're targeting. For example, see the Cortex M bare metal version in -// tensorflow/lite/experimental/micro/bluepill/debug_log.cc or the mbed one on -// tensorflow/lite/experimental/micro/mbed/debug_log.cc. +// tensorflow/lite/micro/bluepill/debug_log.cc or the mbed one on +// tensorflow/lite/micro/mbed/debug_log.cc. -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" // These are headers from Ambiq's Apollo3 SDK. #include "am_bsp.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/arduino/debug_log.cc b/tensorflow/lite/micro/arduino/debug_log.cc similarity index 95% rename from tensorflow/lite/experimental/micro/arduino/debug_log.cc rename to tensorflow/lite/micro/arduino/debug_log.cc index 3cdd006f047..da39c769e0d 100644 --- a/tensorflow/lite/experimental/micro/arduino/debug_log.cc +++ b/tensorflow/lite/micro/arduino/debug_log.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" #include "Arduino.h" diff --git a/tensorflow/lite/experimental/micro/bluepill/debug_log.cc b/tensorflow/lite/micro/bluepill/debug_log.cc similarity index 94% rename from tensorflow/lite/experimental/micro/bluepill/debug_log.cc rename to tensorflow/lite/micro/bluepill/debug_log.cc index 4812a918498..dd8a3b3e4f5 100644 --- a/tensorflow/lite/experimental/micro/bluepill/debug_log.cc +++ b/tensorflow/lite/micro/bluepill/debug_log.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" // For Arm Cortex-M devices, calling SYS_WRITE0 will output the zero-terminated // string pointed to by R1 to any debug console that's attached to the system. diff --git a/tensorflow/lite/micro/build_def.bzl b/tensorflow/lite/micro/build_def.bzl new file mode 100644 index 00000000000..eb44b701408 --- /dev/null +++ b/tensorflow/lite/micro/build_def.bzl @@ -0,0 +1,16 @@ +load( + "@rules_cc//cc:defs.bzl", + _cc_library = "cc_library", +) +load( + "@flatbuffers//:build_defs.bzl", + _flatbuffer_cc_library = "flatbuffer_cc_library", +) + +def cc_library(**kwargs): + kwargs.pop("build_for_embedded", False) + _cc_library(**kwargs) + +def flatbuffer_cc_library(**kwargs): + kwargs.pop("build_for_embedded", False) + _flatbuffer_cc_library(**kwargs) diff --git a/tensorflow/lite/experimental/micro/chre/debug_log.cc b/tensorflow/lite/micro/chre/debug_log.cc similarity index 93% rename from tensorflow/lite/experimental/micro/chre/debug_log.cc rename to tensorflow/lite/micro/chre/debug_log.cc index c794bce59bb..23bb82eb7b6 100644 --- a/tensorflow/lite/experimental/micro/chre/debug_log.cc +++ b/tensorflow/lite/micro/chre/debug_log.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" #include diff --git a/tensorflow/lite/experimental/micro/compatibility.h b/tensorflow/lite/micro/compatibility.h similarity index 87% rename from tensorflow/lite/experimental/micro/compatibility.h rename to tensorflow/lite/micro/compatibility.h index 3fa91644bdd..49acb28f946 100644 --- a/tensorflow/lite/experimental/micro/compatibility.h +++ b/tensorflow/lite/micro/compatibility.h @@ -12,8 +12,8 @@ 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_LITE_EXPERIMENTAL_MICRO_COMPATIBILITY_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_COMPATIBILITY_H_ +#ifndef TENSORFLOW_LITE_MICRO_COMPATIBILITY_H_ +#define TENSORFLOW_LITE_MICRO_COMPATIBILITY_H_ // C++ will automatically create class-specific delete operators for virtual // objects, which by default call the global delete function. For embedded @@ -29,4 +29,4 @@ limitations under the License. #define TF_LITE_REMOVE_VIRTUAL_DELETE #endif -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_COMPATIBILITY_H_ +#endif // TENSORFLOW_LITE_MICRO_COMPATIBILITY_H_ diff --git a/tensorflow/lite/experimental/micro/debug_log.cc b/tensorflow/lite/micro/debug_log.cc similarity index 91% rename from tensorflow/lite/experimental/micro/debug_log.cc rename to tensorflow/lite/micro/debug_log.cc index 3d4ca44d76b..7ef582bd376 100644 --- a/tensorflow/lite/experimental/micro/debug_log.cc +++ b/tensorflow/lite/micro/debug_log.cc @@ -31,10 +31,10 @@ limitations under the License. // To add an equivalent function for your own platform, create your own // implementation file, and place it in a subfolder with named after the OS // you're targeting. For example, see the Cortex M bare metal version in -// tensorflow/lite/experimental/micro/bluepill/debug_log.cc or the mbed one on -// tensorflow/lite/experimental/micro/mbed/debug_log.cc. +// tensorflow/lite/micro/bluepill/debug_log.cc or the mbed one on +// tensorflow/lite/micro/mbed/debug_log.cc. -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" #include diff --git a/tensorflow/lite/experimental/micro/debug_log.h b/tensorflow/lite/micro/debug_log.h similarity index 79% rename from tensorflow/lite/experimental/micro/debug_log.h rename to tensorflow/lite/micro/debug_log.h index c0e395c3760..1004ab9f5db 100644 --- a/tensorflow/lite/experimental/micro/debug_log.h +++ b/tensorflow/lite/micro/debug_log.h @@ -12,12 +12,12 @@ 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_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_H_ +#ifndef TENSORFLOW_LITE_MICRO_DEBUG_LOG_H_ +#define TENSORFLOW_LITE_MICRO_DEBUG_LOG_H_ // This function should be implemented by each target platform, and provide a // way for strings to be output to some text stream. For more information, see -// tensorflow/lite/experimental/micro/debug_log.cc. +// tensorflow/lite/micro/debug_log.cc. extern "C" void DebugLog(const char* s); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_H_ +#endif // TENSORFLOW_LITE_MICRO_DEBUG_LOG_H_ diff --git a/tensorflow/lite/experimental/micro/debug_log_numbers.cc b/tensorflow/lite/micro/debug_log_numbers.cc similarity index 98% rename from tensorflow/lite/experimental/micro/debug_log_numbers.cc rename to tensorflow/lite/micro/debug_log_numbers.cc index 8e867306748..bb6e1d4118e 100644 --- a/tensorflow/lite/experimental/micro/debug_log_numbers.cc +++ b/tensorflow/lite/micro/debug_log_numbers.cc @@ -19,9 +19,9 @@ limitations under the License. // of DebugLog() and then get the numerical variations without requiring any // more code. -#include "tensorflow/lite/experimental/micro/debug_log_numbers.h" +#include "tensorflow/lite/micro/debug_log_numbers.h" -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" namespace { diff --git a/tensorflow/lite/experimental/micro/debug_log_numbers.h b/tensorflow/lite/micro/debug_log_numbers.h similarity index 81% rename from tensorflow/lite/experimental/micro/debug_log_numbers.h rename to tensorflow/lite/micro/debug_log_numbers.h index d889e751730..8cd16d4dd36 100644 --- a/tensorflow/lite/experimental/micro/debug_log_numbers.h +++ b/tensorflow/lite/micro/debug_log_numbers.h @@ -12,8 +12,8 @@ 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_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_NUMBERS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_NUMBERS_H_ +#ifndef TENSORFLOW_LITE_MICRO_DEBUG_LOG_NUMBERS_H_ +#define TENSORFLOW_LITE_MICRO_DEBUG_LOG_NUMBERS_H_ #include @@ -25,4 +25,4 @@ void DebugLogHex(uint32_t i); void DebugLogFloat(float i); } -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_DEBUG_LOG_NUMBERS_H_ +#endif // TENSORFLOW_LITE_MICRO_DEBUG_LOG_NUMBERS_H_ diff --git a/tensorflow/lite/experimental/micro/ecm3531/debug_log.cc b/tensorflow/lite/micro/ecm3531/debug_log.cc similarity index 92% rename from tensorflow/lite/experimental/micro/ecm3531/debug_log.cc rename to tensorflow/lite/micro/ecm3531/debug_log.cc index 4d961963969..17e810e5353 100644 --- a/tensorflow/lite/experimental/micro/ecm3531/debug_log.cc +++ b/tensorflow/lite/micro/ecm3531/debug_log.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" #include "eta_csp_io.h" diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/BUILD b/tensorflow/lite/micro/examples/hello_world/BUILD similarity index 63% rename from tensorflow/lite/experimental/micro/examples/hello_world/BUILD rename to tensorflow/lite/micro/examples/hello_world/BUILD index a54b23b42d7..5352d098b80 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/BUILD +++ b/tensorflow/lite/micro/examples/hello_world/BUILD @@ -1,12 +1,15 @@ # Description: # TensorFlow Lite for Microcontrollers "hello world" example. -licenses(["notice"]) # Apache 2.0 load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + cc_library( name = "sine_model_data", srcs = [ @@ -24,11 +27,11 @@ tflite_micro_cc_test( ], deps = [ "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/hello_world:sine_model_data", - "//tensorflow/lite/experimental/micro/kernels:all_ops_resolver", - "//tensorflow/lite/experimental/micro/kernels:micro_ops", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/hello_world:sine_model_data", + "//tensorflow/lite/micro/kernels:all_ops_resolver", + "//tensorflow/lite/micro/kernels:micro_ops", + "//tensorflow/lite/micro/testing:micro_test", "//tensorflow/lite/schema:schema_fbs", ], ) @@ -43,7 +46,7 @@ cc_library( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -73,9 +76,9 @@ cc_binary( ":constants", ":output_handler", "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/hello_world:sine_model_data", - "//tensorflow/lite/experimental/micro/kernels:all_ops_resolver", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/hello_world:sine_model_data", + "//tensorflow/lite/micro/kernels:all_ops_resolver", "//tensorflow/lite/schema:schema_fbs", ], ) diff --git a/tensorflow/lite/micro/examples/hello_world/Makefile.inc b/tensorflow/lite/micro/examples/hello_world/Makefile.inc new file mode 100644 index 00000000000..a4d2da7d891 --- /dev/null +++ b/tensorflow/lite/micro/examples/hello_world/Makefile.inc @@ -0,0 +1,42 @@ +HELLO_WORLD_TEST_SRCS := \ +tensorflow/lite/micro/examples/hello_world/hello_world_test.cc \ +tensorflow/lite/micro/examples/hello_world/sine_model_data.cc + +HELLO_WORLD_TEST_HDRS := \ +tensorflow/lite/micro/examples/hello_world/sine_model_data.h + +OUTPUT_HANDLER_TEST_SRCS := \ +tensorflow/lite/micro/examples/hello_world/output_handler_test.cc \ +tensorflow/lite/micro/examples/hello_world/output_handler.cc + +OUTPUT_HANDLER_TEST_HDRS := \ +tensorflow/lite/micro/examples/hello_world/output_handler.h \ +tensorflow/lite/micro/examples/hello_world/constants.h + +HELLO_WORLD_SRCS := \ +tensorflow/lite/micro/examples/hello_world/main.cc \ +tensorflow/lite/micro/examples/hello_world/main_functions.cc \ +tensorflow/lite/micro/examples/hello_world/sine_model_data.cc \ +tensorflow/lite/micro/examples/hello_world/output_handler.cc \ +tensorflow/lite/micro/examples/hello_world/constants.cc + +HELLO_WORLD_HDRS := \ +tensorflow/lite/micro/examples/hello_world/sine_model_data.h \ +tensorflow/lite/micro/examples/hello_world/output_handler.h \ +tensorflow/lite/micro/examples/hello_world/constants.h \ +tensorflow/lite/micro/examples/hello_world/main_functions.h + +#Find any platform - specific rules for this example. +include $(wildcard tensorflow/lite/micro/examples/hello_world/*/Makefile.inc) + +# Tests loading and running the sine model. +$(eval $(call microlite_test,hello_world_test,\ +$(HELLO_WORLD_TEST_SRCS),$(HELLO_WORLD_TEST_HDRS))) + +# Tests producing an output. +$(eval $(call microlite_test,output_handler_test,\ +$(OUTPUT_HANDLER_TEST_SRCS),$(OUTPUT_HANDLER_TEST_HDRS))) + +# Builds a standalone binary. +$(eval $(call microlite_test,hello_world,\ +$(HELLO_WORLD_SRCS),$(HELLO_WORLD_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/README.md b/tensorflow/lite/micro/examples/hello_world/README.md similarity index 79% rename from tensorflow/lite/experimental/micro/examples/hello_world/README.md rename to tensorflow/lite/micro/examples/hello_world/README.md index b79d29a563a..bef06053d20 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/README.md +++ b/tensorflow/lite/micro/examples/hello_world/README.md @@ -10,7 +10,7 @@ contains implementations for several platforms. In each case, the model is used to generate a pattern of data that is used to either blink LEDs or control an animation. -![Animation of example running on STM32F746](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/images/STM32F746.gif) +![Animation of example running on STM32F746](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/images/STM32F746.gif) ## Table of contents @@ -18,13 +18,12 @@ animation. - [Deploy to Arduino](#deploy-to-arduino) - [Deploy to SparkFun Edge](#deploy-to-sparkfun-edge) - [Deploy to STM32F746](#deploy-to-STM32F746) -- [Deploy to Adafruit devices](#deploy-to-adafruit) - [Run the tests on a development machine](#run-the-tests-on-a-development-machine) ## Understand the model The sample comes with a pre-trained model. The code used to train and convert -the model is available as a tutorial in [create_sine_model.ipynb](https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/create_sine_model.ipynb). +the model is available as a tutorial in [create_sine_model.ipynb](https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/create_sine_model.ipynb). Walk through this tutorial to understand what the model does, how it works, and how it was converted for use with TensorFlow Lite for @@ -35,7 +34,7 @@ Microcontrollers. The following instructions will help you build and deploy this sample to [Arduino](https://www.arduino.cc/) devices. -![Animation of example running on Arduino MKRZERO](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/images/arduino_mkrzero.gif) +![Animation of example running on Arduino MKRZERO](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/images/arduino_mkrzero.gif) The sample has been tested with the following devices: @@ -93,14 +92,14 @@ The next steps assume that the ### Generate the examples The example project can be generated with the following command: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=esp generate_hello_world_esp_project +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=esp generate_hello_world_esp_project ``` ### Building the example Go the the example project directory ``` -cd tensorflow/lite/experimental/micro/tools/make/gen/esp_xtensa-esp32/prj/hello_world/esp-idf +cd tensorflow/lite/micro/tools/make/gen/esp_xtensa-esp32/prj/hello_world/esp-idf ``` Then build with `idf.py` @@ -132,7 +131,7 @@ idf.py --port /dev/ttyUSB0 flash monitor The following instructions will help you build and deploy this sample on the [SparkFun Edge development board](https://sparkfun.com/products/15170). -![Animation of example running on SparkFun Edge](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/images/sparkfun_edge.gif) +![Animation of example running on SparkFun Edge](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/images/sparkfun_edge.gif) If you're new to using this board, we recommend walking through the [AI on a microcontroller with TensorFlow Lite and SparkFun Edge](https://codelabs.developers.google.com/codelabs/sparkfun-tensorflow) @@ -144,13 +143,13 @@ The following command will download the required dependencies and then compile a binary for the SparkFun Edge: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=sparkfun_edge hello_world_bin +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=sparkfun_edge hello_world_bin ``` The binary will be created in the following location: ``` -tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/hello_world.bin +tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/hello_world.bin ``` ### Sign the binary @@ -164,15 +163,15 @@ Enter the following command to set up some dummy cryptographic keys we can use for development: ``` -cp tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ -tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py +cp tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ +tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py ``` Next, run the following command to create a signed binary: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ ---bin tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/hello_world.bin \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ +--bin tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/hello_world.bin \ --load-address 0xC000 \ --magic-num 0xCB \ -o main_nonsecure_ota \ @@ -184,7 +183,7 @@ command to create a final version of the file that can be used to flash our device with the bootloader script we will use in the next step: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ --load-address 0x20000 \ --bin main_nonsecure_ota.bin \ -i 6 \ @@ -220,7 +219,7 @@ hit the button marked `RST`. Continue holding the button marked `14` while running the following command: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ -b ${BAUD_RATE} ${DEVICENAME} \ -r 1 \ -f main_nonsecure_wire.bin \ @@ -272,7 +271,7 @@ The following instructions will help you build and deploy the sample to the [STM32F7 discovery kit](https://os.mbed.com/platforms/ST-Discovery-F746NG/) using [ARM Mbed](https://github.com/ARMmbed/mbed-cli). -![Animation of example running on STM32F746](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/hello_world/images/STM32F746.gif) +![Animation of example running on STM32F746](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/images/STM32F746.gif) Before we begin, you'll need the following: @@ -286,13 +285,13 @@ command to generate a subfolder containing the required source files in this structure: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=mbed TAGS="CMSIS disco_f746ng" generate_hello_world_mbed_project +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=mbed TAGS="CMSIS disco_f746ng" generate_hello_world_mbed_project ``` This will result in the creation of a new folder: ``` -tensorflow/lite/experimental/micro/tools/make/gen/mbed_cortex-m4/prj/hello_world/mbed +tensorflow/lite/micro/tools/make/gen/mbed_cortex-m4/prj/hello_world/mbed ``` This folder contains all of the example's dependencies structured in the correct @@ -347,6 +346,11 @@ cp ./BUILD/DISCO_F746NG/GCC_ARM/mbed.bin /Volumes/DIS_F746NG/ Copying the file will initiate the flashing process. Once this is complete, you should see an animation on the device's screen. + +``` +screen /dev/tty.usbmodem14403 9600 +``` + In addition to this animation, debug information is logged by the board while the program is running. To view it, establish a serial connection to the board using a baud rate of `9600`. On OSX and Linux, the following command should @@ -369,16 +373,6 @@ x_value: 1.1843798*2^2, y_value: -1.9542645*2^-1 To stop viewing the debug output with `screen`, hit `Ctrl+A`, immediately followed by the `K` key, then hit the `Y` key. -## Deploy to Adafruit devices - -This sample has been tested with the following Adafruit devices. To deploy to -each device, read the accompanying guide on Adafruit's website. - -| Device | Guide | -|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| -| [Adafruit EdgeBadge](https://www.adafruit.com/product/4400) | [TensorFlow Lite for EdgeBadge Kit Quickstart](https://learn.adafruit.com/tensorflow-lite-for-edgebadge-kit-quickstart?view=all) | -| [Adafruit TensorFlow Lite for Microcontrollers Kit](https://www.adafruit.com/product/4317) | [TensorFlow Lite for EdgeBadge Kit Quickstart](https://learn.adafruit.com/tensorflow-lite-for-edgebadge-kit-quickstart?view=all) | - ### Run the tests on a development machine To compile and test this example on a desktop Linux or macOS machine, first @@ -392,7 +386,7 @@ Next, `cd` into the source directory from a terminal, and then run the following command: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile test_hello_world_test +make -f tensorflow/lite/micro/tools/make/Makefile test_hello_world_test ``` This will take a few minutes, and downloads frameworks the code uses. Once the @@ -405,7 +399,7 @@ the trained TensorFlow model, runs some example inputs through it, and got the expected outputs. To understand how TensorFlow Lite does this, you can look at the source in -[hello_world_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/hello_world/hello_world_test.cc). +[hello_world_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/hello_world/hello_world_test.cc). It's a fairly small amount of code that creates an interpreter, gets a handle to a model that's been compiled into the program, and then invokes the interpreter with the model and sample inputs. diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/arduino/constants.cc b/tensorflow/lite/micro/examples/hello_world/arduino/constants.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/hello_world/arduino/constants.cc rename to tensorflow/lite/micro/examples/hello_world/arduino/constants.cc index a4aaf74bd75..e516b6cbebd 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/arduino/constants.cc +++ b/tensorflow/lite/micro/examples/hello_world/arduino/constants.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/constants.h" +#include "tensorflow/lite/micro/examples/hello_world/constants.h" // This is tuned so that a full cycle takes ~4 seconds on an Arduino MKRZERO. const int kInferencesPerCycle = 1000; diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/arduino/main.cc b/tensorflow/lite/micro/examples/hello_world/arduino/main.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/hello_world/arduino/main.cc rename to tensorflow/lite/micro/examples/hello_world/arduino/main.cc index 557885c8001..e34e8bbf509 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/arduino/main.cc +++ b/tensorflow/lite/micro/examples/hello_world/arduino/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h" +#include "tensorflow/lite/micro/examples/hello_world/main_functions.h" // Arduino automatically calls the setup() and loop() functions in a sketch, so // where other systems need their own main routine in this file, it can be left diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/arduino/output_handler.cc b/tensorflow/lite/micro/examples/hello_world/arduino/output_handler.cc similarity index 74% rename from tensorflow/lite/experimental/micro/examples/hello_world/arduino/output_handler.cc rename to tensorflow/lite/micro/examples/hello_world/arduino/output_handler.cc index bbe8c5db8ad..d08a5fcd73d 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/arduino/output_handler.cc +++ b/tensorflow/lite/micro/examples/hello_world/arduino/output_handler.cc @@ -13,22 +13,25 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h" +#include "tensorflow/lite/micro/examples/hello_world/output_handler.h" #include "Arduino.h" -#include "tensorflow/lite/experimental/micro/examples/hello_world/constants.h" +#include "tensorflow/lite/micro/examples/hello_world/constants.h" -// Adjusts brightness of an LED to represent the current y value +// The pin of the Arduino's built-in LED +int led = LED_BUILTIN; + +// Track whether the function has run at least once +bool initialized = false; + +// Animates a dot across the screen to represent the current x and y values void HandleOutput(tflite::ErrorReporter* error_reporter, float x_value, float y_value) { - // Track whether the function has run at least once - static bool is_initialized = false; - // Do this only once - if (!is_initialized) { + if (!initialized) { // Set the LED pin to output - pinMode(LED_BUILTIN, OUTPUT); - is_initialized = true; + pinMode(led, OUTPUT); + initialized = true; } // Calculate the brightness of the LED such that y=-1 is fully off @@ -37,7 +40,7 @@ void HandleOutput(tflite::ErrorReporter* error_reporter, float x_value, // Set the brightness of the LED. If the specified pin does not support PWM, // this will result in the LED being on when y > 127, off otherwise. - analogWrite(LED_BUILTIN, brightness); + analogWrite(led, brightness); // Log the current brightness value for display in the Arduino plotter error_reporter->Report("%d\n", brightness); diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/constants.cc b/tensorflow/lite/micro/examples/hello_world/constants.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/hello_world/constants.cc rename to tensorflow/lite/micro/examples/hello_world/constants.cc index 7ac490c9c94..3eccb724025 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/constants.cc +++ b/tensorflow/lite/micro/examples/hello_world/constants.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/constants.h" +#include "tensorflow/lite/micro/examples/hello_world/constants.h" // This is a small number so that it's easy to read the logs const int kInferencesPerCycle = 20; diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/constants.h b/tensorflow/lite/micro/examples/hello_world/constants.h similarity index 85% rename from tensorflow/lite/experimental/micro/examples/hello_world/constants.h rename to tensorflow/lite/micro/examples/hello_world/constants.h index 61ca7df307d..f452893209d 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/constants.h +++ b/tensorflow/lite/micro/examples/hello_world/constants.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_CONSTANTS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_CONSTANTS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_CONSTANTS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_CONSTANTS_H_ // This constant represents the range of x values our model was trained on, // which is from 0 to (2 * Pi). We approximate Pi to avoid requiring additional @@ -29,4 +29,4 @@ const float kXrange = 2.f * 3.14159265359f; // inference, this value should be defined per-device. extern const int kInferencesPerCycle; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_CONSTANTS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_CONSTANTS_H_ diff --git a/tensorflow/lite/micro/examples/hello_world/create_sine_model.ipynb b/tensorflow/lite/micro/examples/hello_world/create_sine_model.ipynb new file mode 100644 index 00000000000..002a03d9776 --- /dev/null +++ b/tensorflow/lite/micro/examples/hello_world/create_sine_model.ipynb @@ -0,0 +1,1335 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "create_sine_model.ipynb", + "version": "0.3.2", + "provenance": [], + "collapsed_sections": [], + "toc_visible": true + }, + "kernelspec": { + "name": "python2", + "display_name": "Python 2" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "sblS7n3zWCWV", + "colab_type": "text" + }, + "source": [ + "**Copyright 2019 The TensorFlow Authors.**" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "0rvUzWmoWMH5", + "colab_type": "code", + "colab": {} + }, + "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", + "# You may obtain a copy of the License at\n", + "#\n", + "# https://www.apache.org/licenses/LICENSE-2.0\n", + "#\n", + "# Unless required by applicable law or agreed to in writing, software\n", + "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", + "# 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": "aCZBFzjClURz", + "colab_type": "text" + }, + "source": [ + "# Create and convert a TensorFlow model\n", + "This notebook is designed to demonstrate the process of creating a TensorFlow model and converting it to use with TensorFlow Lite. The model created in this notebook is used in the [hello_world](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/hello_world) sample for [TensorFlow Lite for Microcontrollers](https://www.tensorflow.org/lite/microcontrollers/overview).\n", + "\n", + "\n", + " \n", + " \n", + "
\n", + " Run in Google Colab\n", + " \n", + " View source on GitHub\n", + "
\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "dh4AXGuHWeu1", + "colab_type": "text" + }, + "source": [ + "## Import dependencies\n", + "Our first task is to import the dependencies we need. Run the following cell to do so:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "53PBJBv1jEtJ", + "colab_type": "code", + "outputId": "9b035753-60e5-43db-a78d-284ea9de9513", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 479 + } + }, + "source": [ + "# TensorFlow is an open source machine learning library\n", + "# Note: The following line is temporary to use v2\n", + "!pip install tensorflow==2.0.0-beta0\n", + "import tensorflow as tf\n", + "# Numpy is a math library\n", + "import numpy as np\n", + "# Matplotlib is a graphing library\n", + "import matplotlib.pyplot as plt\n", + "# math is Python's math library\n", + "import math" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "p-PuBEb6CMeo", + "colab_type": "text" + }, + "source": [ + "## Generate data\n", + "Deep learning networks learn to model patterns in underlying data. In this notebook, we're going to train a network to model data generated by a [sine](https://en.wikipedia.org/wiki/Sine) function. This will result in a model that can take a value, `x`, and predict its sine, `y`.\n", + "\n", + "In a real world application, if you needed the sine of `x`, you could just calculate it directly. However, by training a model to do this, we can demonstrate the basic principles of machine learning.\n", + "\n", + "In the [hello_world](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/hello_world) sample for [TensorFlow Lite for Microcontrollers](https://www.tensorflow.org/lite/microcontrollers/overview), we'll use this model to control LEDs that light up in a sequence.\n", + "\n", + "The code in the following cell will generate a set of random `x` values, calculate their sine values, and display them on a graph:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "uKjg7QeMDsDx", + "colab_type": "code", + "outputId": "b17a43c6-eba1-4cc7-8807-14fcf5918d01", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 269 + } + }, + "source": [ + "# We'll generate this many sample datapoints\n", + "SAMPLES = 1000\n", + "\n", + "# Set a \"seed\" value, so we get the same random numbers each time we run this\n", + "# notebook\n", + "np.random.seed(1337)\n", + "\n", + "# Generate a uniformly distributed set of random numbers in the range from\n", + "# 0 to 2π, which covers a complete sine wave oscillation\n", + "x_values = np.random.uniform(low=0, high=2*math.pi, size=SAMPLES)\n", + "\n", + "# Shuffle the values to guarantee they're not in order\n", + "np.random.shuffle(x_values)\n", + "\n", + "# Calculate the corresponding sine values\n", + "y_values = np.sin(x_values)\n", + "\n", + "# Plot our data. The 'b.' argument tells the library to print blue dots.\n", + "plt.plot(x_values, y_values, 'b.')\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\nb+vxVQAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iWOlC7W_FYvA", + "colab_type": "text" + }, + "source": [ + "## Add some noise\n", + "Since it was generated directly by the sine function, our data fits a nice, smooth curve.\n", + "\n", + "However, machine learning models are good at extracting underlying meaning from messy, real world data. To demonstrate this, we can add some noise to our data to approximate something more life-like.\n", + "\n", + "In the following cell, we'll add some random noise to each value, then draw a new graph:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "i0FJe3Y-Gkac", + "colab_type": "code", + "outputId": "60b19cdd-c69c-469e-9446-b738a79c1f51", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 269 + } + }, + "source": [ + "# Add a small random number to each y value\n", + "y_values += 0.1 * np.random.randn(*y_values.shape)\n", + "\n", + "# Plot our data\n", + "plt.plot(x_values, y_values, 'b.')\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\nAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Up8Xk_pMH4Rt", + "colab_type": "text" + }, + "source": [ + "## Split our data\n", + "We now have a noisy dataset that approximates real world data. We'll be using this to train our model.\n", + "\n", + "To evaluate the accuracy of the model we train, we'll need to compare its predictions to real data and check how well they match up. This evaluation happens during training (where it is referred to as validation) and after training (referred to as testing) It's important in both cases that we use fresh data that was not already used to train the model.\n", + "\n", + "To ensure we have data to use for evaluation, we'll set some aside before we begin training. We'll reserve 20% of our data for validation, and another 20% for testing. The remaining 60% will be used to train the model. This is a typical split used when training models.\n", + "\n", + "The following code will split our data and then plot each set as a different color:\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "nNYko5L1keqZ", + "colab_type": "code", + "outputId": "b9f9c57b-b6aa-4817-8ab4-4a2201732b9a", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 269 + } + }, + "source": [ + "# We'll use 60% of our data for training and 20% for testing. The remaining 20%\n", + "# will be used for validation. Calculate the indices of each section.\n", + "TRAIN_SPLIT = int(0.6 * SAMPLES)\n", + "TEST_SPLIT = int(0.2 * SAMPLES + TRAIN_SPLIT)\n", + "\n", + "# Use np.split to chop our data into three parts.\n", + "# The second argument to np.split is an array of indices where the data will be\n", + "# split. We provide two indices, so the data will be divided into three chunks.\n", + "x_train, x_test, x_validate = np.split(x_values, [TRAIN_SPLIT, TEST_SPLIT])\n", + "y_train, y_test, y_validate = np.split(y_values, [TRAIN_SPLIT, TEST_SPLIT])\n", + "\n", + "# Double check that our splits add up correctly\n", + "assert (x_train.size + x_validate.size + x_test.size) == SAMPLES\n", + "\n", + "# Plot the data in each partition in different colors:\n", + "plt.plot(x_train, y_train, 'b.', label=\"Train\")\n", + "plt.plot(x_test, y_test, 'r.', label=\"Test\")\n", + "plt.plot(x_validate, y_validate, 'y.', label=\"Validate\")\n", + "plt.legend()\n", + "plt.show()\n" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAX8AAAD8CAYAAACfF6SlAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi40LCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcv7US4rQAAIABJREFUeJzsvXt8FNX9//+cmd1JEDUpUctHEbR4\ngWBCEvAyRXQwCl6r/eEV26WgpFoQsaiVfj62fIoV64VGBRWoIPl+VD7thxatN5CVEcShKBJuiwiI\nUFRaTU2ol+zszpzfH2c3uwlBbgmX5Dwfjzxwd2d2zq6zr/M+7/O+aEIIFAqFQtG+0A/2ABQKhUJx\n4FHir1AoFO0QJf4KhULRDlHir1AoFO0QJf4KhULRDlHir1AoFO0QJf4KhULRDlHir1AoFO0QJf4K\nhULRDgkd7AHsimOOOUacdNJJB3sYCoVCcVixfPnyz4UQx+7uuENW/E866STefffdgz0MhUKhOKzQ\nNG3Lnhyn3D4KhULRDlHir1AoFO0QJf4KhULRDjlkff4KhaJ9kUgk2LZtG/X19Qd7KIcFubm5dOnS\nhXA4vE/nK/FXKBSHBNu2beOoo47ipJNOQtO0gz2cQxohBDU1NWzbto2TTz55n95DuX0UCsUhQX19\nPQUFBUr49wBN0ygoKNivVZIS/3ZIXZ3Lli0TqatzD/ZQFIpGKOHfc/b3u1Jun3bGkiUu9fXlGIaH\nrpv07h0lL8/ao3Pr6lxqax3y8+09PkehUByaKMu/HeG6MH26A3iATxB41NY6e3RuXZ3LypXlbN58\nLytXlqtVg6LNUVNTQ0lJCSUlJXTu3JkTTjih4bHneXv0HsOGDWP9+vWtPNKWQVn+7QjHgeXLbW64\nwQACwCA/397pONeVx9o2WCkDv7bWIQgaTxrK+le0JQoKCqiurgZg/PjxHHnkkdx5552NjhFCIIRA\n15u3m2fOnNnq42wplOXfVnFdmDiR1dNcJk6UD20bwmHpK5TuQo01axqfNm0anH8+/Nd/QXm5PA8g\nP99G103AQNfNZicNheJAk7rNG+7T1mDjxo0UFhZy44030qtXLz799FMqKiro27cvvXr14je/+U3D\nseeeey7V1dUkk0ny8/O555576N27N5Zl8c9//rP1BrkPKMu/LeK6UF6OiHt0D0xe1qNMyLGIRuHR\nRx0SiSS6LvB9j7lzqwDo0sVh2zabkSMtkkn5NvG4XAFYFuTlWfTuHVU+f8UhQ+o2x/PANCEazaxU\nW5r333+fqqoq+vbtC8ADDzxAp06dSCaTDBgwgKuvvprCwsJG59TV1XH++efzwAMP8POf/5wZM2Zw\nzz33tM4A9wFl+R9m7JGl4zjgeWiBTxiP/oGD58GGKpfi6q0Q6AgBui4YNGg68fgANm++l/r6cnr0\nyLyxYcjVQpq8PItu3cYp4VccEqRuc3xf/us4rXet7t27Nwg/wPPPP09ZWRllZWWsW7eOWCy20zkd\nOnTgkksuAaBPnz589NFHrTfAfUBZ/ocRe2LpuC5s2GpzY8hEFx6JwGSxbnOu4XLjzHKMpMcx/y2o\n+T5oGhiGj4YPgGF49OnjEItZ6DpMntx6lpRCsb/YtvwdpH8P2YZKS9OxY8eG/96wYQOPPvooy5Yt\nIz8/nx/96EfNxtubptnw34ZhkEwvqQ8RlPgfRjRn6WSLc2ZysJhhRJlV4bCj1OayGoshWydiTJcn\n5/wL0hHCGqT2fnV03WTECJvTT2+82atQHIpYljSAmgYntDY7duzgqKOO4uijj+bTTz9l3rx5XHzx\nxQfm4i2IEv9DiN3F0e/K0klH52zdmpkc3sLiua4WdhHgwI7SzMmdFxpsvySJ0AK0JJw6WSNxUV/q\n/1nG5pq5DOwzhiMTx/PJJ5eQSNQoH7/ikMWyDryRUlZWRmFhIT169KBbt27069fvwA6ghdCEEAd7\nDM3St29f0Z6auaTj6IPg25Ov0kJ/eYFLUY3D6gKbs8dYeB6EQiCEFH/ThMpKGDNGTgjnGi7PX1rF\nf7AdOnemzjqa2jcmkb88IG9DGD8Q1J2aYPUkgcisVhHoaFoOpaV7ngymUOwL69ato2fPngd7GIcV\nzX1nmqYtF0L03cUpDSjL/xBhT+PoLQssMs7/HrpJmR9lSSCPHTECunaFggKYMwfq6+Fs4fKKX07u\n3DgQIDSdI57O4fM7ppA3pAaWLUOb+wL/LhGIMA0+ISFA0wKSSY/58x02brQaltfN5QIoFIrDByX+\nhwjpOPq05f9tyVdDtjp0S/l3QsLjAt1hqWZhmhCJyGPLy6XwCwE2DiYeOgEC0EQACY9Zk2q4ZopN\nr5f/Gw1BXjVoCRpZ/smkTjJpMmGCTSwmVxTP3eby3iSHNwK7IYRUTQgKxeGFEv9DhObi6LPFFDKR\nPvMMm2jIxMBDM02uqbTpUJMR3YkT5XFpj56DTRIDIxXVIwCfEG8ENhcsrWLrNR5mHXh58OXjJdSc\navIJx/PWhkvIz69h5UqbNWukmpfFXS5+uJzLA497MBkYj+I4VqPxfVvMtZogFIpDgxYRf03TZgCX\nA/8UQpzRzOsa8ChwKfA18BMhxHstce22RF6e1eDqaRrWOXRo483cZ0dEiXSVPv+XajLumLo6l3PP\ndSgutlm50qJHD5fupQ5/XHkpP177ApoQ+Gg8wzC+7g3cOJ2PNCFdPQHkJNby4J1vsnatDPfs2xey\nS5Wcj4MpPHR8BB625rB1q0VVVeNIpA1VLlYTlT+QSTkKheLbaSnL/xlgMlC1i9cvAU5N/Z0NPJn6\nV9EMrgvjx8sM2yCQYgmNI31OjVi4WI3E9PXXXXxfbhpPmmSyaVMlJ588Bk3z0ESIHXeGyVvlQ8jk\niGERKm+oIvB9KfwCMMAgwe1Dx/BYVSWbNlmUlUH2vvvCwCYIm2i+h9BkDsGS6aDrcHbgch4OdRRw\n48wxkGys8rsKVVWrAYXiwNMi4i+EWKRp2knfcsiVQJWQoUVLNU3L1zTtP4QQn7bE9dsSaes4Lfy6\nToMvPxJpLJITJ0L37i7FxQ6rVtls3OjQrZvcNAaPvn3n8MUXqcca1I67grw/fI0xeDCRCov166v4\nNPv/QACaDqeWLePR0gFs+2QhrmuRXTbcxWJAEOV/KhzexGbJdAvfhzN9l9cpx8Qj8HX0wAcRUNc9\nTu0H48kvHI9tWxQXu/Tq5bB2rY1tW2o1oFAcJA6Uz/8E4O9Zj7elnmsk/pqmVQAVAF27dj1AQzu0\nSFvHaeG/8EIYPDgj+uPGZY4999xpnHnmKDTNJ5HIoWPHSoTIbBofe+xg6uoWy8eEyJ/4CqzyYfFi\nKCqic2GE7dtnIgIPkYSjNgi+PB0wQA883njD4X/+R7p/giCzh/BFD3ihJ/TpA6GZ0pJPbyqH8PEJ\nCNDZfrnGxtsDAuN1jJWLOeWUSiZNGoMI4uiBQWEwmScWVXxr4ppCcaCoqamhvLwcgO3bt2MYBsce\neywAy5Yta5Sx+23MmDGDSy+9lM6dO7faWFuCQ2rDVwgxDZgGMs7/IA/noNA0kWvw4EysfigEw4bJ\nFUC3btPw/VsJhQIADCNO16415OdHWbXKobraJhy26N27SG4iv7iVvFXTwffx6z2WP+gQPWscJ5yw\nkNpah6//VsCIVbex9iGPQECAybvv2vi+FP0rr4S//hVOP93l4YfLCYc9EgmTO++M8tvfWo02lXUE\nX/SC9aNBM0DTBL4f57PP5gBxND1ABAG100dy+feLmGBajRLXlBtIcTDYk5LOe8KMGTMoKytT4p/i\nY+DErMddUs8pmtA0Zd1xMq6d6mqbqVMt/vY3l0mTRiHrMkg0Tdbmj8UsBg2ystwoFpZlQV8XPzSL\nwPdICJPb59osnQtgpf7glVAR09+qIncQPPdWhLVr5fNBAKedJnMIcnOrMM16dF0ghMegQQ6TJlks\ni1s8I4ZTIaaiI6grDkAXaJqcPILA4NgdJdQlFhBooCchf3lAt9MdolGr2agm5QZS7JYDZCnMmjWL\nKVOm4Hke3//+95k8eTJBEDBs2DCqq6sRQlBRUcF3v/tdqqurue666+jQocNerRgONAdK/F8ERmma\nNhu50Vun/P27JjtlPQhcSkszlvbYsVF69XIQItnIF69pd/DEE1ajEg9lcZf4eAfG22BZPDssyvqp\nDm8Im6Xs/EN5y7f4c0eLcf3BuQ8KC11KSuSk89ln8JOfVBGPP42miQZBr662qayEmhroVxBBHzML\nPI+8NQbJhIYuEgihs/6VO7CffJyOpwTU9ob8lZD3YQ7Ydubzui7OeIeyuM2SwFJuIMW3c4A2jNas\nWcNf/vIX3n77bUKhEBUVFcyePZvu3bvz+eefs3r1agBqa2vJz8/n8ccfZ/LkyZSUlLT4WFqSlgr1\nfB6wgWM0TdsG/BoIAwghngJeQYZ5bkSGeg5rieu2B7p0cdj8YRy0AEScsjK5WappISCROkpjxox8\n/ud/ZBnmUAjOES7zg3I6LPDw3zR5dliU+lKL3+dafPNN89cSQmYGA9x4o8txx8lJx/dDhEKCIEgQ\nCklrPgg0XnppOI8+apGbC7fdBr/fANf8fijf96BT3wjbN8DatQ6vvWZzo1/F1sH1dKqGbs8hNzSe\nrATLkjWNVlWRf/sMzl/pMz8wGahHec+0WrVSo+IwZ3eVDluIBQsW8M477zSUdP7mm2848cQTGTRo\nEOvXr2f06NFcdtllDBw4sMWv3Zq0VLTPDbt5XQAjW+Ja7YbUcjb/hFr04wKCEIREwFXFtdxyC+Tl\nXcbnn/8VEPh+DsuXS/98z54uI0Y4FFdvpcMsWdM/iHusn+rw+1yLykpYsQKefhqSSVJCLi+p69KC\nBzj5ZIdEwkPXfTQt7V6S2zBBoOF5ucyfH0EImUn80ksujzwiJ4t3MelbGCFiWUycaMnVy6Sn2RIS\nbE1CyR2Q9z5QU5OpaeTXo98v6D0Wjn7f474LHXLGWw0rArUJoNiJA1TTWQjB8OHDmTBhwk6vrVq1\nildffZUpU6YwZ84cpk2b1ipjaA0OqQ1fBVLoqqpg5kxIJsnTdU4ZBBtuB6HDMX0eIZGo5LPPfDQt\nxH/8xzA+/zzCpk0WZ5zh8tBD5eTmeujFIXZUGxxZDQlh8oawqa+Xwv/AAy5Dhkh3Tk6OxZgxMrRU\n16Xl77pw++02999vEgp5BEEI8GXtf83g5Zdv5rXXIsRiGSEuKXEIhz0MwyeZ9Fi1yqF/f4vLC1xO\nHDiGUDgBGogwbB8ER2zI4f0Cm9pVDr7voWmCIAS1ZRp5m03s8bbcilCxoIpdcYBqOl944YVcffXV\n3H777RxzzDHU1NTw1Vdf0aFDB3Jzc7nmmms49dRTufnmmwE46qij+Pe//90qY2lJlPgfSqSFLl2U\nB0AIEvk6QgvAgEDISBldFySTgu3bP6SkRP4GPvjAITc3VRwOqH10BLXPdeV30wuwfQeEvMSKFeWA\nR+/esnooWIwaJVfPt90GJSWwcqXF2LFRSkoc6uoKGD16NJrmAwa9e0d47DEr1QwGrrsOVqywSSRM\nhPBIJk3WrbPpH3IpGlOOecs3jWJ639HO4ia/EmMUXH76Vno/FEIPQUgPkX/WMPhZJPNDPkBLe8Vh\nygGo6VxUVMSvf/1rLrzwQoIgIBwO89RTT2EYBjfddBNCCDRN43e/+x0Aw4YN4+abb1Ybvoq9IC10\nKeEXmkbSyGHzsbfhJ34PwscPwgghMIwkhhHg+wtYsWIxq1dHKSuz8f2s4nDFEbaug0minBAeHib3\nnTE09bpPENSzfXsVNTUWQZDJJl62TA5n3TqLWMxiyJCJGIbs+wtJzj/fYdEiq5HB9YtfWEyZUkn/\n/nNw3cHcfbcFjiwy1HmetPaFCUnf5IF5lRwVwPygHHONR81Yg8qSEfwjJ0LOCKvxb/lAtmtSKFKM\nHz++0eMhQ4YwZMiQnY5bsWLFTs9de+21XHvtta01tBZDif+hRJbQ+brB08FwZiUjLH3Uose8qxoi\nbwCGDh1Pnz4LMAxZcnnZModf/GIcr78epVs3pyHs89WRE/lVIJOvBB7fWQmJhIFp+oDg009nct55\nEUzTarTgyE4w27rVRggTkJPKtm02ixY1Xmn/8pcuK1aMQQiPs85aTGFhESA/T956j5JxBuvHDWfE\nfTKE9B4mNiSFdYoBsa7M0i1mz27i2TlY7ZoUijaOEv+DTWozc3WBjROHs2cN5fTt8EIswq3TLAIB\nCIjFrEY+9lmzxlNcvLjBzfLeezaeB4sWWdi2xZsPuBz93kT+4RfgYSLw8HUTcUqEefPg8sunpmL1\nk3TpImPts7YaME1ZX0hqrUVdnaw4um2bzUUXWTu54GtrHcBLuYZS/QiscQ3CnWfbRB2LVFQcDjYe\nJprm4Wsmi4TdsPLYybNzMNo1KRRtHCX+rcy3BqqkfPwi7nFcD4PTH9H4dzjJ8gKT7mURjKczkTjZ\nnIPLgHUOs+6qxCyTJZfXr5f1/AsKYJzt8oon6+wMxOQOrZLj9BqOusIm/xKL2U/BwIGzCIU8QiHZ\nO6BbNzm+pvWD0qQrjj73XGMX/Msvu3zwgcP3vlfQfD+CLOG2kRNGadzFxmGsUck9I2rYUWqzYoyF\noTw7CsUBQ4l/K9I0UOX11126dMnq0Zvy8WuBz9clAaEw6IYgmfQIAofJky1GjpQTgGFAIiGFP0o5\nOXgE603evy3Kl7dk/O+OA99PZOrsaJrHJWfWcF31OPy/gjkPKistNmyQm7nFxY378+7OyM52wRcV\nuZx7rgzv/Oork44dKzn55G/v+fubS1xuf7GckPAgZPLljyqhi8Prr6dXLcrIVygOBEr8W5HsQJXu\n3V3q6202b06gaWFKSqQrBNMkiHscUZ3OiE02RMtEIi49e2ZCMkePhgFxKeyG8CHpcfQKh6KKzCbp\n6tXworAbXD1ayGRHmY2/PGOt19TAuHGZsg57Q7YLPggy4Z2IOF++NIduV43H/cRq1GcY224oP31H\nvYMhPAx86k6Ns7J+FMFmH03TGTp0CscfX7HTNVWYv0LR8ijxb0WyreSLL67CMGRh/iDweOKJKpYt\ne5Kht0X55yNVHBPbzo6xsL6kM6/FIpxzDrzzTjmhkAzJNIwoQlgNvnKBrNEzdIbNxKzIyJoaWKZb\nlAdRBmgOPW6yOTViYc5quYCZ9LUmTbLp08cEESecDCh7eQHJ3y/m7iBKEMDtohyhe2g5JhuGRvE8\nizeEzX+mfP21fTQCQ2YpCxHw/vuj2Ly5iH79Mgo/bRoNq5+cHBXmr1C0FPrBHkBbJm0lT5gAV1zR\n+LV//hPmzoXZL6+m/LrpXFA4l6Gxufz6uRl0XAU7djhoWqah+8aNDr4PdYXwmyFDeaRwBOVEecuX\nVnYa25Yi+Y5hUZk7jtJSsJyJ/K3SZcQI2RGsOVxX9gdw3d1/riVLXKZOncj778PYsVE2zbyQM8bq\nfGdtgEh4nOs7nCfkCkUL5HLjfBxMU05MA/Uoz/WcwCdXXNfofYXwmTrVIZ0k6bowapTcgO7Rw+WH\nP5zI9OnuHo1RodhbBgwYwLx58xo9V1lZya233rrLc4488kgAPvnkE66++upmj7Ftm3ezOyI1Q2Vl\nJV9//fVejnj/UJZ/K5P2odfVRaiunonvy+ic+fMjFBa6jHh4FH8P+3ycgN5j4chYgv6Bw4vVmaQp\nTTM55RSbM85weeCBTJG3Z++KYG5qbMlnu2UuL5BJVngehSGTdUJOFrNmNbag9yaJdskSl6+/LufH\nP/a4/nqTu+6KEts4nrxNi/E1uRpxkAPyMDF02We4W8SmshR+9jNY4lt8ATyWdy+GQGb+ChDCYPly\nm83PuQxa4bABG9+3KCzMlI5IJExGjowyZYqlVgCKFuWGG25g9uzZDBo0qOG52bNn8+CDD+723OOP\nP57/+7//2+drV1ZW8qMf/Ygjjjhin99jb1GW/wFE04axZs1PueOOhcRiFiUlDlo4KTN3Q1BbAgnC\nOMgY/bFjo1RVTSA3N0q/fhbDh2d87KGQx+DBTrNCbVmy6UtRjdMoNKdfwmmUKJumuSTa5nBdmD7d\nQdczY+jTx+G6SgtjYZRtP53ApWYUXYMLdId3bqxEu29Cw2xSU5OJXrqipApDy7SQDAKNysrJHB2D\neX45J069lxtnltM/5FJa6hAOxzEMn3A4Tq9ezi7HqGhf1NW5bNkykbq6/V8OXn311bz88st4qb6p\nH330EZ988gmlpaWUl5dTVlZGUVERL7zwwk7nfvTRR5xxhmxf/s0333D99dfTs2dPfvjDH/JNViXF\nW2+9lb59+9KrVy9+/etfA/DYY4/xySefMGDAAAYMGADA/PnzsSyLsrIyrrnmGr788sv9/nxNUZZ/\na5G1S1lXKEsq+L7HaaeZ6HqEwkIoooBQQhAIWd/+n9vP43+veoBlL1oQyNj+006TNXu2bJlIaWkB\nX32VKaFw0UV2I+HfaWM0e9MhZLJE2Bj+zn7/3SXRpt9361ZYvtzm+uvlGIQwGTHCbsgF6GZZPF7q\n0mNUOSHfQ/uzCSMzs5Nty+Qx34ce1dvRE7IjgSbgn5X9eeWViobkLz21oT1rhMOCvgXoeoAQoOsB\nX35ZoMJBFZmigKnw4t69o7uMMtsTOnXqxFlnncWrr77KlVdeyezZs7n22mvp0KEDf/nLXzj66KP5\n/PPPOeecc/jBD36All1TPYsnn3ySI444gnXr1rFq1SrKysoaXvvtb39Lp06d8H2f8vJyVq1axejR\no5k0aRILFy7kmGOO4fPPP+e+++5jwYIFdOzYkd/97ndMmjSJX/3qV/v82ZpDif9eskeRJ038KNtf\nG0QQ1GMYsgHKZcVV9Kp2+PHXy/h4CnzeH76zSOMPiy6myxSL0CuZpu0bN7osX16OrssbvGPHSj78\nsIZTTrHp189qGE9BQabjV8Z1k/EBGbbNRKxmx/5tSbTZHyUUAiEs7rorSp8+DiNG2I02ZyG12gg8\nCHauxWNZ8MQT0vXzTawzxWOhrgTyqiEWK0QAiw0bdFO+R8pddOHxDps360CAEDo//3mNcvkoqK11\nCILMvlhtrbNf4g8Z109a/J9++mmEEPzyl79k0aJF6LrOxx9/zD/+8Y9ddupatGgRo0ePBqC4uJji\n4uKG1/74xz8ybdo0kskkn376KbFYrNHrAEuXLiUWi9GvXz8APM+Tv+UWRon/XtCcb7yw0JVtErNj\n27P8KHXd42xP/rWhAQo+3FU9nbxYQC2CTY9AEIa6YsGGLQV0qIHhw2HqVOkHLy52ECJzg598cg39\n+4/baTxpi3qnLNmswP30w+bYVXx/tksIZDevrl0tCgosFi2S192bWjwVFVBUBH+4KcKw2EyOinkk\nMKkiAsDbwuK5m6JEujoN5+a/uBW9KExAEsMwKS5u/J6K9kl+vt18YuF+cOWVV3LHHXfw3nvv8fXX\nX9OnTx+eeeYZPvvsM5YvX044HOakk06ivr5+r9978+bNPPzww7zzzjt85zvf4Sc/+Umz7yOE4KKL\nLuL555/f78/zbSjx3wua+sbffdclkShvaJDee/Uw8vpGGglgbR8NoQdoSL/2Ca8FdIoJNKTVG4QB\nA3yhkdOnpkErp8t2u1SnNn6ln13e4NlumPR4pEtE1udvySzZploekRq96w3i3dTiSVes/n8bLWIs\nxMbBIdNZTNfh1EhqJkrNbnmeR1GRwZvDRnBsn8h+W3eKtkFenkXv3tGdja/94Mgjj2TAgAEMHz6c\nG26QbUrq6uo47rjjCIfDLFy4kC1btnzre5x33nk899xzXHDBBaxZs4ZVq1YBsGPHDjp27EheXh7/\n+Mc/ePXVV7FTP9R0GehjjjmGc845h5EjR7Jx40ZOOeUUvvrqKz7++GNOO+20/f582Sjx3wuaCmFJ\niaxFDz5B0qd22VTyfpEKpUkJYP4Jtej+7/E1EJ5B5/mJ9B4nedVIv7cAQcZ/7ro0tGiMxSzGjYvy\n2GMyGzcWsxqEN921C+R40u0UWzIZqjktnzhxN1WWd7GMaFqxeikWf9MsdB10IT/P5MlZp2bNtkdW\nwztjuvL7XEvF+isaSJcdaUluuOEGfvjDHzJ79mwAbrzxRq644gqKioro27cvPXr0+Nbzb731VoYN\nG0bPnj3p2bMnffr0AaB3796UlpbSo0cPTjzxxAa3DkBFRQUXX3wxxx9/PAsXLuSZZ57hhhtuIB6P\nA3Dfffe1uPgjhDgk//r06SMORd5+W4j775f/1ta+Ld58s4NY+IYm3nwVUVuIEIYhD0gf3KGDqD1D\nF5sjITHljBtFAA1/s7hRPFB4i/jLo7eI2tq3G9581i1vC8OQwY+aJsQtt2Suf//9ouE1w5Cvpcdz\nIL+DDh3k9Tt02LNr19a+LZ555n5xxhlvi3Rgp6bJ86dObf4zrJr6tvDCHURSM8RXdBDn8Hajr1fR\ntojFYgd7CIcdzX1nwLtiDzT2oIv8rv4OVfFvSm3t2+KjRbeI2lJzJzX86Jb7ha9JpfZ1Q7zGQJFA\nFwJEAl3cw/3CNFOHZylqMqeDON98u1lx3RfhbQ2yJ8Hd0TBJLjTEq692EGec8bYwTTlx7er89Ofs\np78t/tO4X5wXznwfd98txMCBctJIv/9HH90vJ1DFYYsS/71nf8RfuX32k7w8i1jI4s2zI5x/tkO3\niA2WjMIZN8PmNREiTEAiCPF/DKY/ixvKK3/nBzaPXyK9G8dvdegalxmxuvCYVeHwXNedC50dKuXt\n96bKcnZUhml6jBjhcOaZmSStuiXTqN04h/xTBpPXT9b2SXt8lgQWSw2LETfBxV2hthbSOTfz58OK\nFS7XXSc7k7VEuJ9C0V5Q4r+fZCJuLEzTIhqRUTWOI8sSpJueg2ANRdxOJVdrczjlzsH0vyrjv39V\ns3ktMAnjkQhMdpTajNu5xhlw+JW3l1EYJsmkzE945hmb3Fz5HQ06YRpfHvdTgi6g/3s+vZdAXr8K\nLi9w+UZzeEO3ec+0iKTqF2UlXwKyDIbvy6Szlgr3Uxw8RKolomL3iHTnpX1Eif++kBXsX1WV6YCV\nnR371VcuP7lhPF+vSNIpJgjwGUoVEWaRi4f++GKW7CjC8yx8H97SLC4kyvk4LNJsjpxjMb7o8BL5\nXZGXZ7F6dZRlyxzee8/m/fdTTRpzAAAgAElEQVRlqWoh4Lgb5tD9J6T6E8NHy+fQWy+iaEw5ZwQe\n9xom71dGKUp9EYMHS4s/TToaStNk0tm2bbI3geLwIzc3l5qaGgoKCtQEsBuEENTU1JCbm7vP76HE\nf2/JCq73QyaxQFbbBBmtUlAAI0e63H9/OeEBcdb8KKDXWJ0OMZMTToAOnzYtdmY1RO68p1n8LSH7\n6eoLYPHitlPFsm9fi1/8IvNZ0zkJC6oHc2pifkOW80MzBvPbdQ7dUn0OwponE8dSoaAVqdXQ00/D\nihUyNHTBAlmtbt68CB98YDF8OA0rBcXhQ5cuXdi2bRufffbZwR7KYUFubi5dunTZ5/OV+O8t2cH+\ngUc/4bAIC02TyVk1NdCrV7oGT0BC6Py55EKe3Tiex38F2phMbeVuEZtoVucskK0TFyxoJlnrMCd7\nryI7G/mFTRX0fwHCO+awoHowc9ZXMNByiewmUQxg1app/PCHI9G0AM/L4dVXI3ieTJBrWrxOcegT\nDoc5+eSTD/Yw2g1K/PeWVLC/iHvEA5OFqQqW4XAmAepPf7Lx/RCaFiAIs+Xk8Ux0LIosoKjxbq3l\nulg4gHw8fjzEHZd+CYclho1ttx31yt6rKCrKnvQqKC+vaND6UyMWRHa9qz1tGjz6qMujj45C15No\nGoRCcUpKHGIxq5ELTom/QtE8Svz3lpQJ+8IYh98ty2SmnnIKBIFs0/joowXE43IzxvcFr7wCl12W\ndf631FK2gKhWjoaH0EwMouxLx61Dnaab1tGozPxNI6dEi4LVkGoG1lBKY+lSm5ISB12Twi8E6JoG\n2BiGfKx6ASsU344S/33BsphXZrF0WfaTLl9+Kds0go5hCDRNYBh+qgRxM/Xnd1FL2Uh6kKpq2Z7M\n11kpj9jMmVLAk0np/tJ16N3bZdIkGdL5ox+ZzJ10m6yIiswOPqkSPnwZAk3uKVRWtpuvTaHYJ1Q9\n/30kEpHWZZqBA6swTQ8Z2umjaRrJpEEyabJmjc3Wrc10yUrXizCMjKna3HPtgKbzYCKRqf0fBFBY\n6BD4ccBH1+P8uMSheCycPANKbocTXw6wcVK5w3LvRaFQ7Bpl+e8jliUFq6oKZszI1OJJU1NzBUFw\nFitXyno8q1c3swm5q4ytQyGL6wDTqPVAaGfLP7GigNCPglRUUMB3N+eSF4P8mJxuk+g42Oh6u5oz\nFYp9Ron/fpD2W0ci8O67ETRtJuDheSbjx9/Npk0WQ4dKa3aviqAdbllcLUDTeRAykUFz5kD312s4\nY6zOv0sCjl6ps7ljIcexlDAeAQYjeYKjB1pciMwFaGdfn0Kx12j7myXWWvTt21fsrulxS1JX10xd\n/r08tq7OZe5ch4cftlmzRlar7NsXqqul+O+uP66ieVwXxtkur3jlhPHQwiaTLovy4ovQP9i5JHRO\nzs49itObySr+X9HW0TRtuRCi726PU+K/d+3glixxqa8vxzA8wGTBgig1NZnyA03LFqfr6w8bpoRn\nf3Bd2FDl0mO7w92v2Lzlyy8y3WQmG8OACRNkH2PXhQEDIFUZF9NsV3voinbInop/i2z4app2saZp\n6zVN26hp2j3NvP4TTdM+0zStOvV3c0tct6Vorh1cc6QbmIM81vfjHH30eBYtchkwQL5uWalIE1zu\nYSJnC5dEArp2VYKzP1gWRJ60iJ41jrd8q8GV1pS0z7+gQPYdqKrKtMQEuZGsmr8rFC3g89c0zQCm\nABcB24B3NE17UQgRa3Lo/wohRu3v9VqDb2sH57qyY1dJiUN1td3QwBzi6HpAnz4LKC5ezJ13RtlQ\nBZbj0GVZAa+LMZh4eJgM0qJtKlnrYFJQIAU+COTKCuTjCy+Uvv6aGnnM6NGZzeNQSIo+yGQ8tRms\nULTMhu9ZwEYhxIcAmqbNBq4Emor/Icuu2sG5bqZOj+d59OhhAlHuuivK6MgYupctwzAChPC4oncV\nQ56eBYHHxWgIAgwCBB6PXOFwljL79xvXhdtuk0KupeL5QVr648dnVla33ppx8yQScNVV8r8/+QRu\nukmtwBQKaBnxPwH4e9bjbcDZzRw3WNO084APgDuEEH9veoCmaRVABUDXrl1bYGh7TnPt4Bwnu06P\njxAexcUOW2bbDFq0nM0l0voMkiHyq4GEdAfpuk5gGPiBhm6anHW3fUA/S1sl24UjhLT+r7wS7rzT\n5fjjHZYssVm0yCLWxOz417/gnXegLO6y7T2HuZts1uXv3CtBoWhPHKhQz78Czwsh4pqm/RSYBVzQ\n9CAhxDRgGsgN3wM0tl1i27JOTyJhIoSsRV9dbXNXjwf5aKSP0EETUDP5bObGItzGLDTNw8gx0VMN\ndVcX2LzkWNgoodlbsipnN/vdCQEffuiSSJTz4Yce8bjJc89FWbeu8cH19VL45wflmIGH96DJw3qU\nCTmqH7Ci/dIS4v8xcGLW4y6p5xoQQmTnW/4BeLAFrtvqWBZMmWIxaVIU05Q+/1jM4oghnxCEAQNE\nEoy8epZiUU6UC3WHayttiiqs5kr3KKHZQ5r77iIRmD698UZvUZGT2qvxCYXkymztWgvDkCuDcFi6\nera952AGHiF8BB79A4elnqUifxTtlpYQ/3eAUzVNOxkp+tcDQ7IP0DTtP4QQn6Ye/gBY1wLXPSBY\nFvz85xYDBljE49LP/OV3b6JzYllDDfrPj74JTYOlwuIdLI6ogSKaL92jhGbPaO67GzcORoyAp57K\nHLd6tY3vy836IBmiqHorWzSXyBMWNTWZVcPcTTbJh0004ZEQJot1W2UCK9o1+y3+QoikpmmjgHmA\nAcwQQqzVNO03yEbCLwKjNU37AZAE/gX8ZH+v2+LsysfguliOwzuP2TyxwmL7dnjm8SLmzAvxVUmS\nvDUhvjOyiNzcncvPZ5csUEKzd+zqu4tEZJmMeFxu+n7vexZ//nOUvB1V3FE9gwti07n5jBl81XM4\n+cURYjGLW2+FGTMsziTKBYbDd6+z6fiZxVM3yr2Cujq5yb87N5NC0ZZQSV7QvI8hO2Mr1bWrXERZ\nlLD4hZjIBO4lhI+vGRi/nYBrj2skHGkhKSigkQWq2HO+ZT5uqKnk+zKUc2xiImN7/BefDQzYfgmI\nsAZaLj//eZTqaqtRWKhhwOmnuzz0UDm5uTK81zCiXHSRpVx0isOePU3yUrV9ABwHEZdtA5PfeDx/\ns8Mpf7CwmnbtwuFNYeFg42Ei8NBTZuluyvQrIdkHdlXiKF1ULzvR64SKAtZcGRCYgAZogiDwGNSz\nikErZAmIIlYzWMxhTmIwXxXXEA6nk/U8qqudhn7KykWnaA8o8QdWF9h0D0zCeCQweSJms/x8WD7Z\npqih1KTJEmFj+LAiZDH5kijXdXboFrF3Ugnl6299mrqFjr2ghmRYR9cDaeULjaQXYkz1DArw8dEw\nSYKAgcznP1fe3SiKa+ZMu1HegHLRKdo6SvyBl2osXiSKTVaRsIR8nsooNXMcCgbbTCyystwQFrvq\nsKV8/a1P0yqg775rc9RROYSEh+8brF07nOQMuCA2nRA+OnJBALIE9M3xama8FWXLFof33rNZv95i\nxAhZhkO56BTtASX+SL/83zQLBNg4ACwPWxQUwNljLOJxCy0KV1wBd9+9e2HYVZl+RcuS7RY6cjXM\nu3so/yqG12IRbrnF4rkNLqOYhcBD0zX0IAnISeB7dw3msiKL8vJMFFdpaaY5vELR1ml3G75NyzGv\nnubyx585bPcLeBRZjyepm2x4MspLNRYv/afLeSKzIsjJgYULlaAfUqQ2WUTcI2mYvD85SlGFxS9+\nAW895HK+cHjbtHl6zGq6V8+RRYBSKj9tGowcKXMC0qWgQU3cisMXteHbDHVLprGyfhSB4aPrOfQ2\nKukxagy/9j0CNHQCQgRowqNoRRXHbq/iDjGDED4eJuVEIQ7/GONApa2U4VAhtcmiBT5hzaOoxsF1\nLSZNgqSweBsLPQl/zLcYN6+xaV9TkykV4XkyiijdS1ht1ivaMu2nh6/rUjt9JAEJICAI4tRunMNX\np8f5eIjPl4U+AQYJDAgZ1C19mvgRT1FfKLNCw3hEqCJKOZcvuxd/QHkzTXkVB4Vm+h47TqYHMMiX\nmtt7aXoq7LxZr1C0RdqN5b+lyiHv3QD9emRmrmYQ/l4Jqx6aTxAGPSH4ovJaTln7GUedV89HP16U\neh6Kx0LOOhMEmMjJwFdhPIcOzWyy2Eg3TjwuY/snT5Y9FpjoNOoTadk20WhmIx8aW/5qs17RVmkX\n4u+6MG6GzSteDr3Gxvl3X53vVEymtksNwWYdCAg0nV4F/8tJ2wRboKF2TyBgfslZVMYqEcDQ1Aai\nrpTh0KJJUsBO8wFZCXu6gQg0DJFEC4ewhg3DymqzpjbrFe2BdiH+jgNv+bLw2gXrHE4/zybSz4I6\nF13PSdWF0elU7RMiIL9aZ2vSQIiAZNLkv6sriSF78v6yb5Q7ypqP71ccOjTNDt5yq8OJ9R668MEP\nZB4YAhH30aZOleZ+ysGfnkfq6ly2bNmzvs4KxeFGuxD/tF/3Hc9ipWlx29EwaBDceCMcddRQ3noL\ntr1WyszYGGoK43xRovOXx39OTV5+QyXPdGPw6yotuinRP6RJZ1inXT7XXQdbZtvMEzKRz8dABnx6\n6Ai545ve7U3NGEsCGno1766vs0JxONIuxD/bBVBbCw8+CIWFLscdV0447HHRRSZjX4swrLCSikdG\nQdjn0sTjjB0ra8NfdRWcdZZyAxwuOI4U/iCQf88+C6RKbqcT+TTgp70e5LzSv9KpWpD3gQEzZ0Iy\niR8yefXaoQwY2rivsxJ/RVuiXYg/ZFzCgwbBObgMKxlPOBxvaMNYUuJwIlvRwkl0QyCER2mpw+bN\n1h4ldikOHWw70+c3m6VYLMVC06BXL5fvPjSPzWHBlsDgiGmXcuaf/4oW+Ajf46jlkBgiyz9oWuO+\nzgpFW6D9hHqmuLXEJUo5V1cvIJwI8JM6yaRJsrqAO6pnEEoISIKhhTj7bFvFeR+GyCY8spGL3uQO\n13VZBbR3b9meUzcCklrA3JzOfBOYJDDwMJkbizB2bJSqqgnk5iqXj6Lt0W4s/zRX5TsEmscRsYDi\nu3Q23d6XDzqWMbxkBcc855M7Fr4o0YifPIwB96kf/OFKRQUUFTUuq53971NPpdtzxhFCY1VtKRcS\n4fzs+k4xOO88i3792H1PSYXiMKNNl3doWsoBaFRvua7YYMXDGgFJfC9E8VhBp5hPApNLzSgTHUv9\nztsorgux2DS+971RBIFPIpHDuHFR1qyx6Jt0sXF4O2zzwJtWozBRlfarONRp9+Ud6upcVq4sT/V3\nzURruFh8MaiS4tDT/P3aL/HF+xhGQBCC35eMQIt1xcHmHV/1d22rpI34Tp1qECLAMAJ03eOxxxxq\nXoKLHy4nHHigmxhEVY1uRZukzYp/ba1s7J0drRGLWYyzXf73lNtY/4gnM3h18H3p939pZYS1qXj+\nHJXD1SZJL/zq66FnT5tHHjEJhaSB0HFzAd88NJ6QiGMQIBJextWTVaN7dYHNSxOVB0hxeNMmxd91\nZX33oiIT8CAIsfm/t7LsC5frE1V8U+I1ZPCKJLy34kKefXY8/ftbjB6t2i62ZdJGvBAQi1mMHRul\npMThNK+AcX8Zg54S/iQ6gWbyzXkF1B7vkP96JXmLalhdYHP2GNXuUXH40+bEP+PStygujvLbn1ZR\n9tgMCmLTOYeZ6CT5ulrW7AkEBMkw69aN58knlX+/PZA24tN5AGkGHrMCI/AahH8BF/LhiMGc4Y8h\n2JxyHf4syktPWMoDpGgTtDnxz3bPJhLwzdoP8U5O8HGJ4Ohqn7wY5Meg91ioLYGF1Zdxzu07C78K\n7mibZCf8gUvfvuWEQh4JLcSOpQZHVkMCk4nh8dw3xMH3G7sObdtSXdoUbYI2J/5py657d5eHHion\nx6xnkyYgSFXovFMjb60gLwZHx6CeznxR0/g9VAP2tk064W/LFofNm1PiDux4bAQ7nuvKm9g8ELEo\nLISVK02CwMP3TbZts+nXTxV+U7QN2pz4py27Dz5wyM31ACGbthoQ6Dp1v/4BR1//V0QQ4GEy24ww\n0W78Htmrh/p6WfJF/cjbHvn5NrpuNkSE5RdHyOtvEUm9XlcH//rXUBYvhvnzI2zaZFFZqfaEFG2D\nNhvnX1fnUl09ACHiDc9pWg4lJQvJi8n6/m9ic2qkeZePbcsJAFCtG9swzeaCpJ5fsaIc3/dIJEzG\njo0Si1mEw3KvQK0IFa3F/rqc232cf16eRefOw/j006lI01+jc+dh8gduQTcrY+E1xbJg+HCYOlVG\nhSSTamOvrZKXZzVbuiEdKmwYfkPtp1jMIpnMFAFV94SipTmQLuc2Xdvn888j1Nfn4id1/PoQX/2t\ndI/PjUQgN7dRZ0BFOyI/3yYITJJJg2TSpLraBmRdIHVPKFqLqirpaj4QbUTbrOUPsGiRxarnKplQ\nPJJO1T5HbRgDpxbt0VTaTGdARRvj25bXeXkWHTpEmTrVYfly2dPBNGHMGKiuhpKSzA9T3RuKlsB1\nYcYMubIEaWi0poHRpsXftuGbX9Vw0hpBiIBA93DGO+SM37OY/iadARVtiKbL679VuhTVOI1mgn79\nLHTdoqoKzjsPSkul+NfXw/z5oGmycujw4XKlqO4Vxf7gONLiB3lvDRvWuvdUmxZ/y4Ijp9gEPzNJ\n+h5eYPJfC2zeW6w269o72RFdZXGXHqPKIfDwQybPDos2BAJkGwATJ8rksLRllvb9N+kCqVDsE7YN\n5xou/QKHJWGbSKR1b6Y27fMH+LLI4iI9yr1MoJwoSwKr1X1pikOfdD6IYcAFukPIlzNBEPdYP9Wh\nvFyuDtLU1bmce+5EzjjD3em9sjeAFYp9xcIlqpUzgXuJauWymmwr0ibF33Wllea6cgNlUcLiAcY1\ndHEyTVnTPX2Mov2R3tOZMAGumWKj5Zj4mkECkzeE3UjM0xViff9eJk0qp7Awc9OoDWBFi+E4GEkP\nXfgYyda3Jtqc26epL/fMMxu/3qOH9Ns+9ZRLr14Of/qTzZQpqq5PeyTj0rGgKMq2KoehM2Q57wYx\nd11qPxhP0C0OBOjEebQkwv/G7mKGXsFvL3c562uHgsE2ReomUuwPTarHtrY10SLir2naxcCjgAH8\nQQjxQJPXc4AqoA9QA1wnhPioJa7dlOzm3fX18OGHjV8//3yIx13uv182b08kTN59N4qlfrjtG8ui\nm2UxMZIVAbR6GowcSX4PH/0hgR/SMJIBpdUbKeennMYmfv7q49JKW2xCUZRPuq3ms8/msGPHYN56\nq0JFiil2y+ppLjVzUgZENErdu1XUlkB+IeS14nX3W/w1TTOAKcBFwDbgHU3TXhRCxLIOuwn4Qghx\niqZp1wO/A67b32s3R0FBplqjELBtW+PXS0uhZ08Hz2ucwAPqF9quScV9WraNNc6Sj0eNgmSSvDVw\nxliNzSXfoXv1v8iPybTBq4I/oyc8CGRQ9iexB/kgPhcAIebz9tswb3wRs4Y7dIvYahZQ7MTqaS7d\nf1pOTzy8+Sbvzarky96zCHwPfeWshiZUrUFL+PzPAjYKIT4UQnjAbODKJsdcCcxK/ff/AeWapmkt\ncO2dqKmRYVIAhYUuQ4ZMbPDR6rp8vbjYxjBMhDAIhUyKi+3WGIricCHtK7z3XtI7vVuqHIKkjLsT\nwJExg/nP3UxeSvgB/sz/R9IwG5z+n53ySaO3vaL/07zilXPi1Mz7KhTZ1MxxMPEI4RPGY8eKpwmS\n9WRXkm0tWsLtcwLw96zH24Czd3WMECKpaVodUAB8nn2QpmkVQAVA165d92kwti1/i6ed5vLIIxnX\nzl13Rdm0ycK2ZQJPaWm02ZouinZIkzaNW1K+/1dEDiZxAgxGMpkZegXixO6cuXUOfxKDmRmq4PQ7\nruKqfAdsm2O7reaLD5Y1vG1y8fGYLEcXqvi/YmdcF2LfK+DEIdCpGo6I6fSav4J1gwRBCNBD5Ofb\nrXb9Q2rDVwgxDZgGsrDbvryHZcGUKfDWWw7hsHTtaJrHnXc6nHZaZmN3VzVdFO2QJhttb2Lzlm9R\nTpQBOLyp2SzVLHJyYMDzFcydW8HTD4PwYcjjFtGovK+OT7kO0z7/I7sUoeXMg6Qq/q9ojOvCyJEu\nD9w/mr+HfT5OQNGdPt9ZK3uN/KtEY0vOMPIuaD2Nagnx/xg4Metxl9RzzR2zTdO0EHIfo0kV/Zaj\nogJ69bKpr5dtHEMhk6uusslrzd0TxeFLk1oep2JhzoJlcYulgYUGhAyorJSHT5qU2VeKxzMGvdw2\nqMC2K+jfH/r3ByKqRkh7ZlclRBwHevVyCIU9WW5ewI7ego5rQxwR0wjHTL6cuqvSky1DS4j/O8Cp\nmqadjBT564EhTY55ERgKuMDVwBuilWtJ9+tnUVenXDuKPSQrlddCzgXjx8OCBVLog0DuFzlO4/aP\nmgZbt8K0aTKEeKdqjKpGSLvl2yp02jb86U82yYSJKeLoSchfF+bvdz/O36trZORPReveN/st/ikf\n/ihgHjLUc4YQYq2mab8B3hVCvAg8Dfw/TdM2Av9CThCtjnLtKPYVy5Liv3jxzmHXOTnS4gcZUTZt\nmgwmSE8Syr2vgJ22khrdE5YFt9xiMfuPC7nm7Cq+70HelAh5lkX3AzS+NtvMRaFoCZpbtqczx6dP\nzxTi6tXLpazMYcUKm02bLFXnR7GT5V9ZCStWyNdKS+G222Sf8XA4a2Jogebh7b6Zi0LREmR7bbJ/\nl127Zgq8FRa6PPxwOTk5HkOHmuTmRgGLiRN3/g23wG9bcZiQvZVUUCDFPt0dML1SBPncyy+7HJ+s\nIv/2GeSt8g9Iqzgl/grFHtDUinvuNpf/1B2iwuZ7ZQ5mOI6mBRhGnCBwuOgiaydf74Hs0qQ4NEgb\nDxMnSis/Tfa+UWGhy4DzB7DZi6PfL6N98ta3vu9Qib9CsQc0LQF92e/LuTLw+C/d5DfVtxFKBAQC\n9GTA6hcKGo6tr4cHH4SzzpIbw7vyASvaNrYt3Ttpyz+bq8qqMPR4Q9TPFyUaeZsPk9o+CkVbJzsV\nIEIVoWQ9mhAYmkf/NdWcMVbn3yUBR1brzF9fg65LkRcC5s6FF1+UFUBDqV+cCvtve3ybS8+y5GsP\nPggvvJBxGQKcsg30BCnjAdZXn0n1bZVc1cqWgRJ/hWIPSPtvN1S5DJkue+0JICFC/B+D6R9bzFEx\njwQmjm5z5ZUupulQXS1bQAaBnAxGjJD7Bcrn37bYE5eeZckV4AsvNH7+6xMinD52Bl+XJDiiOsxV\nsUqO7mJxVSuPuU2Kv9pUU7QGlgXHVzng+2iAj8ZMhvEHKlhDETYOizSb436wmp/9bBRB4JNI5DB2\nbJT335dlolW7x7ZDts58W1hn9rEFBbL8TDIpn9d1WHOUxfPvO/SPOTjYLMVi6uDWH3+bE3+1qaZo\nTd7E5mpMBNLKr0JmYS7FYikWAy9yue22kWhaEsMAXY8zfrzDxo0WBQUyRLSqSk0ChzvNhXHuqhR/\n02N/8xuXzz+vQgjYvLmUnj1reKvQ5oE14wAoLISiotb/DG1O/Hc3AysU+8OpEYtLZ0Tpl3BYpNss\n9RvfXMce6yBEJpRD1w0GDrTp0kUKQnrDb+ZMWLhQ3ZuHK011pqamUYWQnUo5pI/t3t3lrLMGoOsy\nS1BWINZ56KEc7rorypo1Fu+/LyeL1jZc21wbx+zerGpTTdHSWBZMdCyO/O04fvyERYcOcumu6/KH\nvGKFTSKRgxA6YNCp02WAFIDsUD/V8/fwpjmdsSwYN25nwc4+9srSKnQtjqZlSs9DQG6ux+DBTkP8\n/4G4P9pkhq/y+SsOFNm+3HRtn+Jil8rKKoSYiRBJdN3EMKJccIHVYPnn5CjL/3Bnb3TGdWHxgy4/\n2WCzbpKHCGf6Qmiajq7nYBjRZvND9pZ2neGramkpDhTZ99qmTfDnP8NFF1l06+aweXMS8PF9D01z\ncByLqip5rPL5H/7src58+ZJDp6RPyR3wyUCNdzgTv6PNBadWk3/KYPL6Wbt0HbUGbVL8FYoDzbRp\nMoYb5L+9etl07WqSTHokkyZjx9pMmQJPPpk5p67OVVVn2wmOA28ENr8kxJGxgJNjJo8bN/F4aIzs\nAW0uhmgRlmUdMKNAib9C0QLMmdP48bPPWlx+eZRP/1bF0SvgiPcbBx/U1bmsXFlOEHjoutmqvVoV\nB56mE7ttw7wQ4Elnj6ELfnXFCvQXZQ9oEffQDnB0ihJ/haIFGDwY5s9v/NgCuj87CxOP0cxiU4Es\n+AZQW+sQBB7ZvVqV+LcNmk7shhFl0SKLBy91MF/w0YXA0HwE8E1gEsYjEZhsKrA5ABGeDSjxVyha\ngIoK+e/TT8Pxx8s47SLHQegeWuBj6B5FNY5sZ+Q45J9XgK6bDQLRmr1aFQeW7Ind9z3+8AeHZ5+1\nmBeyiZpmQ1vP1ztHmKZH6B84LNZtLquxlPgrFIcjRUWwejUsXw7z5sHfKm2KcmTmj2aaMiQole2T\nFwrR+85LqB3UmfziiLL62xDbttn4vomue8TjJsuX2wQBLE5aPFsRJdLVYXWBzV9etViqgavL/tAP\n2Qd2nEr8FYoWomniz0s1FkXRKFuqHN7E5vwVDt3SB/g+efe/QN6kXIhG0t4gxeGM67KlyuGe6Tb/\nOj1Kaals7hOLyf+5QQBYsPhkGD0aqqvlaUaqP/SBjv5S4q9QtBC2Lat2BoH8t6AAfvigxV//aiEE\n9A9BNGRiBPXU9RTUlgjyV8XJU2nohzf/f3tnHx9Vde7779p7ZgfbSoKhFpSCgmgBQ8JLbfdBcWtU\nfK32cNvbak8QPNAqaKNolbanNz21pfU1rdIWVLjMtZyeY6lagQo4soXiVkFICAQU0YKgVJs2AV8y\ne2bvdf9YM5lJSIAYNG/r+/nwSWayZ2bt5MNvrfWs5/k9mdZuCxcyyA9YiUVpbZzf1c7JKeRS3d5O\nPrmUVMpn7lyL2bPjTS41ILsAACAASURBVKZ/dXWf/LC1+Gs0x5BMzWQYwsyZWQMvUNv+2ePjfHfs\nXbx55ROEUTCSIcXHF5LfOcPVdJSMcU9jI0iJCUTxcXB5AZvBg+Gtt9Rmb/x4F9NUZwGRiE9JiUtt\nrU002jlOBFr8NZpjhOtmPfxPP92juDhr6QxqQnhgo039iLO4Nu9PIEJCIagPN2vx765kYn3pWT8U\ngqS0cHEA+P731VmQ68Kkkwt5LzAITUkkYnHqqQ7f+U7nFfxp8ddojhEZD5dhwzzuvruUaNQnmVTb\n++3b1f/us0KPkRv3IK8xESLESEkKZj8Cv9Ylv12Jo7ZucByCiAWhj4hEMK6byqq+ZfStUrbMmSww\nGw9Ky2kYFlA/zqBgeiXOnZ3799bir9EcIzINX1591aVPH7W9N2jkjqtjfPhZmyU3eqzwS7G2+bx3\ni6RhNBRUQX5tEmIxPLT9Q1egPbbwHjZzZJwJuKwXDnPLbK6yObQRS3qHkL81JH+7gDPqYMLHfCNH\nQIu/RnMMsW0YOdKhenOEMBVgpiRfWbqQ/HllTJrm0me+jyEDCrZCwVb1moaR8OagTdxwg0dVlVKZ\nhQu1HXln0R5beNeFvwQ2z0kbM2j9Ws+DnXscrolYmLRi+N9J9DhLZ42ms8nPtymumcqpiwXFsyF/\ni1KFIWUORh9L+T+jRP+Vcqi6H961NzJ3bikjR3qAsn/Wls+dQ2t2zZ4Hc+eqr0e6NpfMLmLaQzal\nMs7u6T/pMh2m9Mpfo/kYyB9fRv7ti5u3dsrEhSoqaHhrNdV3S0ILECBE2CUyQDTZP1Mm5g9th4Fa\nXttS03N3EX/BZslgmzmdr/uAFn+N5uOhLVWwbaiooH7+s4TRVM7eW3SJDBCNIteu+frrYehQj8uL\nY5xQDTtjZdi23ayXA0AYeuze7bJ3r8Ojj6oXjxnTdnvHzkaLv0bzcdGW4bttU7dzHoSzwAgQRoQB\nA6YxYEAZjqMVvyvheeB5Hvfdcx5WNIGRhKI5C6lZ4FJabpNIwBe+4DFpUoz331/E66+nSCQs1q5V\nBVyWpZr8VFWlzf660J9Xi79G8wnjeTBnehGXDr+OA2Phkm+XccYZdtvphbo1XafhulBU5BKJ+mBC\nKOHAmUl2PeLS2GgzYoTHvfeWYlmNCCERgmbhO9+H++9XNR7r1qmc/67yJ9Tir9F8wuyMZVM+66SJ\n9zKsB+Y4cHbK5faIwy/Wppt6ZE4MEwl1UDxvXjZ5XPOx4zjw+987pJIWlkxgpOD4LVHuq3WQEkpK\nXKJRH8OQSAlhKEilLKqqHED16Q2C5n15tfhrNL2Uc3Gx8Hl/ZMAr9wYUWPNp/GARj50uKawN8FMW\n/3lHHPdim6v3uAxJJJR6hCHMmtW1lo+9gG3bbG6evYYrSmL0q4YH6stYH6rff1WVcvCU0icITFat\nmsbTT5c1VXVffbVq7alj/hqNhiFlDqlHLP5R0kgYlWBIDOnzQQl8rlYi8THWuSz5B7w/Zg+3jBD0\n2wYC1DKyKy0feziuq5wbamvtJkG//PIFzL2pgrVrJ7N8+Qxmz45TXNzcwRPURm3UKOXx1BWjdlr8\nNZpPGttmyXVx9q+LUZJchCFTCBnhU1WSJAFJLHaNKGyyiNh0tcGY2XDCDonIywPH0ccAnxCOA3l5\nKuoGcOmlC7jllm8D8MUvrmIou5j351+wfbtNEGRfZxjqdZm/T1f8G3VI/IUQJwD/DZwC/BX4upTy\nn61cFwA16Yd7pJRf6cjnajTdneFlNt9ZbDPstjLGjXOZPt3hne/C0p+4/L+9DkPTsWTTDEhJuHfM\ndC4aNBinwsHDPmr7AU3HyM3YLSyEgwdVs2YhAAnXTryHDcuvorHEbvLnNwy44AKoqOjaf5eOrvzv\nAOJSyp8LIe5IP769les+lFKWdPCzNJoeQ0ZUli+HE09Uz71XZHPzOzY+UNhQQxgKpDRIpSyW15Rx\nykwbx4bY9U0Owl3uELEnYttpYzbX5aVTS3ifVZC27u6/VnIeLj+vtvkyHg4u6w2Higq7y/9NOir+\nV0LauxQWAy6ti79Go2lBGHqcfbYK7Rw8aPHkk3GCwObrIxcwY9YshBEQygjz5lWydatNeTn06ePx\n/vsuI0ao+HIk0rUOEXsiNQs8vjCrlEjgMy5qcd9F11B69n/Rf62k//I+rMHhS9IjTikWPqG0sIjT\n1duzddTb53NSyrfT3+8HPtfGdX2EEBuFEC8IIQ4xvMsghJiRvm7ju+++28GhaTRdm9dey4Z2IhGf\n995z+Rfh8bOSGzCjSQxTYhgphg/fTBgqq+iBA0uZMuU/uPfeUkaN8pg6Va/6jxUNDR67d8+loSFr\n4FOzwGP/9RWIZAIRKqe3+mWjmHn7X/jtip8y5eQ4LwobJ53BFSEgKv1uYcx0xJW/EOIZYEArP/pB\n7gMppRRCyDbeZoiUcp8QYijwrBCiRkq5q+VFUsoFwAKA8ePHt/VeGk2P4LTTHA4eVGmCqZTF5s0O\nP+4Xo7AqYG8A0gAhJJMmLWT1anU2EIn4CBEgpc/YsS5jxtjMnasPfjtKQ4NHdXUpYehjGBbFxXHy\na+ELs0oZESYwCUlhEAiL9RGHDYFNtWVT+SNYXg5rGx18qZq2G3ldLKezDY4o/lLKC9r6mRDib0KI\ngVLKt4UQA4F32niPfemvrwshXGAMcIj4azS9iQkTbGKxOKtXqzTBbdts3iVG/rsw4M/w9hUgDIhG\nA2691eW00xySSYtUyicMI/Tvv4cHH/SabARaO/jt6VlBx+r+6utdwlD1YAhDny1bXII7YWLKx0gL\n/zNcwM+MCr71gM2kOnUAXFenmq/X1dnsKoxTVHcMBvMJ0dGwz5+AKenvpwBPtrxACNFPCJGX/r4/\nqoVBbQc/V6PpEZSV2dxwwxxOPtnGMGAxZSTI48RVYPgQpAwMw+KqqxwmTLCpqYmzYsV0pJRcdtlD\n3HVXKWec4TUd/ObieTDH8XjvB3OZ43h4Xuuhje5Kpvj5P/5DfW1pt9weCgocDMMCTMBi/o2F7Fi1\nB19GSGGSIsobDOULqRpOfGQulxd6lJerzy4vV3pfNMOGOXO6hfBDxw98fw78jxDiOmA38HUAIcR4\n4DtSyn8HRgDzhRAharL5uZRSi79GkyZt9Mm6dbDBt7nYXMPt/V1eeqSQARfU4fsOr75qU1cHhYU2\n777rEokEmKYK/5SUuLzxhn1IpKHJRgIf37f40/JKksny5qGN/O4hVK3RnqYrRyI/38Y041RVuexb\nWciC6nIsfFKY1JxyBSP+uoLpLMAkJHjJIHw5j7EyzvrQ7rYZVx0SfyllHVDayvMbgX9Pf/88UNSR\nz9Foejq5+eT19TZX3m+TSoH8g8oplzJbOPTVr6rwT+asoKHBaTXkc27OIaTEZ8SJS/lnTmijvt7t\n1uKfaaTyUawTWoaL1C7CJpGwuYO5TfYb/ygJ+az/FtHdAaYMkUCEkCD0Od90eUHYXc624WjRFb4a\nTRchI94TJ0IqlX1eplMfMuZgffva3H57nDPPdKmqcnjtNZsf/ODQ9xtS5hAssgh8H8OyOGXcZBqC\ndU0r/4IC52O/p4+TIzVSaYvcHr2RCEydqp73ffXVxaFupMkr9waEUYkQm3hvCJz4Z0G/WkkKA2FZ\nfO1XDsfVdZsQ/yFo8ddouhCuq0Q+F8NQzxmGWuGWlQHYzJ9vI6VqIZgbdsiuam3sNVl1zLdtihuK\nqK93KShwuvWqP8NHsU7IDRcFAcyfD9GomgiSSXgBm1+NncaF1nwwJFKm2H8Z/O3iKB8uvZkRFDCk\nzKHItrt1SEOLv0bThcj1kjEMuOUWKCjIZpbkrjIXLz405LFggTISC0P1PvG4zcgbVDZLQYOKbfcE\n0T8SDQ1em5NcJlyUqZKWUk0C06dnr7n0W2UEyUWEYUI56gmQkYARdxQwZMicT/RePi60+Gs0XYij\nDWW0dp3nKcfnTMgokYCNGz2SydIec8h7NLSas59zz5nfXSwGCxcq4bcsuGGM1yxVs+GBqeyp/y11\n6ZcKTP70J4fx47tnmKclWvw1mi7G0YYybDxsXGpqHOa6Nnv2cIizZEmJSxD0nEPew5KOd9Wfvacp\nZz8IfJ54wuX00w/12hk8ONti8foSj6LyFm55Y8fwz4OAACHhyQdv5lfL2q6p6G5o8ddouiPpU0uZ\n8BkWWiw34myI2JhmNjNo3jwYPdqhutpqWgVHo4Xs3j23x8T8m8g5xe07ShD+AmTEIJmyuOceh127\nsoKd2xwtDOHMMz3qTqygfliCgq0hMuHzXIWL+UMI+xhASBgKxLADxySttKugxV+j6QbkpiYCJCpc\nzk34iDAgis85ocvzSVtZDaMOgYuKsvnrb7zhMnRoIa+91nPy/JuRc4pbsAXGzIa6kgg/qKpka63d\n7FA8FsvG+0eO9NJ9ExJsSYaMvs3A2mrxw2ccDu6He+6JKEsNQzJp0iJWrSpj165Dayq6I1r8NZou\nTsvURCnhiymHVaFFnvBJSgsXp+nwErINvwAuvNBm2DC49toKxo1LoFayPSwE1OIUt18tfKZWMpQ6\nDENNhnv2qAPxhQuzv6eLLophWY0YhiQQBlWTL+CHtRWqTeMWWLFiGldcMR8hJJaV4tZbWw8hdUe0\n+Gs0XRjPU9W/uW18AdZLmwtFHEe4PCsdXmhhHyyEErtYTLmBZla3UoYIYfSIPP9m5JziikWLkMkU\nmBZfutlhxgFYtAgeekiFwzLnIqNGeVx22UKEUM3Xk6ko+4dWsCnPhg/VNatWlTFp0mKiUZ9oVNls\n5Od33m0eS7T4azRdlJaxaSGUeGUE7AVsPGnTmv1tGKr8dSHgG9/IWEeHSGnwz39ewIknVvScVX+G\nzEl5WRnCdYk6DlfZNtvnqgyoIMiehwgB48e7GEaAEBAEgpUrp/LBBzZTpkBtLaxdq3r3zp4dp6LC\n5aKLetY5iRZ/jaaLkgljZwq8IJuXLoR6PiNmppl9nLtDkBKqqprbQfzoRxXs2mX3iIyVVrHTeVCu\n6jSViQgNG+YxbpzL+ec77NtnM3Fi1iU1lbKIx8vYsUNNFJYF3/ueygSaPNnmootUrQTQYyYALf4a\nTRcl17sms9rPCDxkJ4EzzoBzz4UxY2DpUti3z6O4WFk/1NbaTavXkhKX6mplHd2yKviIdCNv6Nwz\nkkxa5urVHo2NpZimOuy+8kp12N3QEGfLFpft2x1s22br1qxRXEEBrFx55LqB7ooWf42mi9KyeXh5\nuRIl08xaE0gJ27fDK6+oit7f/tZj4InnYUZ9UkmLW25dw7ZtagLYuVNlA5lm60ZobVbFtqamORNA\nV5sXcu0bEgl1ZvLDH7qYZqbeoZH9+2NN1c7nnGNzzjnqMDgTWsv9/bT0+u8pB+Va/DWaLkxuwVdR\nUXYiuOGG5tc1mb7Vx4ienAATLJngzhkx3uljN1lDQOtCfdjV7WG8k48wL3yiZCahwkI1lsxZyerV\n8PbbDpWVJoYRAJL9+xcxYEBZ0z16nppcw1BNjpWV2fvIeP33FEO8DFr8NZpuQmYimDs3G/rJkFmt\njngH/nY6hBKMFLy/HJiseozkvg+eB3PdplngsKvbdPxJJnxShsWOQqfJ0KyjnvrHatfQchL67W89\n/v73GG++qTJ2ampsli9XaZsgCcMU1dUxhgxROx3XtZvOV4RQPkoZ8vNtiovjPcoQD7T4azTdjlzz\nN9OEm2+GQYM8iotjwH5Ouz+C/+mA4zZHub22jBdWqdfNmJF+g1aW6wUjD7O6tW1qKuM8NtPl2cBh\nU7lNvEiJdUc99Q+7a2gxMxxuoshMQmec4TFpUoxBgx5h8OAkY8bAxRcv5JZbXFatKuOSSxYDPkFg\nIsQi3ngjhWFYTJwYx7LsNu8jP1+FzpYs6TrhrY6ixV+j6Wa0NHUbOdKjquo8wjDB28C+mVHefPhK\ntpcM4ABArToIbhJ/183GRBIJcF1qmUN1tToUHj360NXtsjqbn0mbIAQzZ4XfXk/9XAE/7K4hd2Yw\nTfZfOo05K8r4S9C6t47jwOjRHj/7Wakq2hJqayQERCJJxoxx+eMf57B5cyWwlA8//BQTJjxFZqcz\nZIhLPG63eR9dKbx1rNDir9F0Q3LPAnbvdpHSz/7QSHHSdcsYZEic5GJmz44zebK6uKHBo/60lyj4\nQkh+LRCG7KovTAubjWWpFFBoLuiHW+EfrRFdSwGtrDzMrqGF6f7nnpjPChZTSpwNvn1IeMm24Ze/\ndEmlfISQICFTAGGKCF/6ksP113skk+UEgU8qZRIEkXSOv8VzzzmUlbV9H8eyZWRXQYu/RtPNKShw\nEMJS3vNAGBoYRpgu6vIZM8alqMjOHur2b8S4F4pnQ/4Ogzer6poJWyzWvFdAZjKYMkV9PZxIHo6W\nAlpXd5hdg+PQMNqkfkRAQRXk10qi+JwvXKqtQ711PA9WrnRwHIuI0YhISU54Aax6GHDqdTg32uze\nPZc33vAxzQDDgD17prNq1eCmlNjGxpzdUQs6Et7qqmjx12i6Ofn5NiUla3jssRjbtsHOnWOYNau8\nqairutrBdeGkk9KHukISRuAfJYJP78yjcLKDtS4rbMBhJwPVSewwpGM7NYUOy+psLi9UPvmXFzr8\npEVcvbVdg+fBxo1QdI/qomL4kuLbDD6z0+KMqQ7x9OfPnZsVYcdRO5fHH49z1dgYN21ayAm1AUks\nVn2vjKtonrVjmhbPPVfGkiXZD28WGmvBR20Z2ZXR4q/R9ADy821GjbKZOVO1Ijx+N5w/einx6sns\nel2tlAsKHAwihKkAIwV9qwyuT1byRexmwgbNxR7aEfJoYTX9V1HJMFmONHyK8ixerIyzrM5uU0Az\noaHJk11GjkxhmpKwj0H9rReQf3oFZemD39zw0ZQp2f67maK2ZynDwcXF4eX7bZ67Cmy7edbOl79s\ns2hR9rMnTz787/ijtIzsymjx12h6CLathHlnzOOaReUYtT7XmutYfnMRrmsDNsU1U/nnC/PpVyX5\nVC30p65pxZsrbC0ng4ULsznwjnOYFM10bCdjNf1VuRQL9Rjfp6jOpWhO2wqaOYv2NxXCNQZSSMxI\nHgVXVUD6ELpl+Gj/fvXakSM9SkpcDhwopCh/M303A7VZh1Pbbt7GMrPKX7pUCX9bq/6eihZ/jaYH\nYdtguy6kfAgDIvhsus/lZ1JlybxYWcaIxxYjkz5JlBX01FZWvLmrXM+jqU+AEFBTk602zs188TzY\nucfhmoiFIX2SocUfmcxE1mEYPsaRguWeR8nTLtPCQn5ZW07j7ICGcQYnfLuyWfaR42S9jEwTBgxQ\nDVkyzqWGESJCML4FU2cv5H+/5uI4zSecTDXzqFEOdXU2Rd25E/tHRIu/RtPTyDmdTBkWzwYOQboC\neFmdTdFzcbxYjE194fpRR47hu64yO5NSfV26tPnKe2fM46SYy5yFDqkUNIopXPEVePH0Mv7v/Tbb\nUkWUGi5fq3Qoaitu4nmkzi3lwqRPKQKDkE/VhuRvF5gj6mBC88uFyDZe79sXxo3LOpciAVMVun1Y\nkmTxRJchOZ/bdPAdJAgSJlWPPsiPfzyDNWt6VljnSGjx12h6GjmnkzsKHTbcaCOSWY//9SEE31zM\nqNDHMBazfn2ctWvbjsO3zHS55hqPgQNdXn7ZofBVuGZRKcL3eVpGAEmEALnc4lXKSKXgeWnzorQ5\nrg7aWmDvjrmcnPSJEJDCIMQkiWh1t+C66lwDlPjffz88/LADWEACRAgpVeHcb3uU/HnNX6+qmdV1\nZiTkpyUz2VNbRCzWM5q0HC1a/DWabsZRWSKk4zbvedlVciqlzMs++MBlyhQfw1AFTqsejtF3U4zl\n2yEMy5gwwT7krW68Ef74R5g2zeOUU0q5dkqCa68xGfLHyzAf8kEGRFE+0iaSMPA58JSLlOq9IpHD\nR3yew+F/YSFR4ajvUsnEkXX828PZm8yEaiZOdDAMu8m2Oghg3z6bK69Uh7nRaCHJXZsp2A758w7N\nS9271yH0TQwjVBNEVYiDSz29SPnR4q/RdCvaW2nqujB8uMfo0VmL540bHb75TUv1ppURrvvHw7x5\nT4owCokPF9HQsKZZjD0W89i718WyHN55J0aQakQYEkSIeP8pkjKCIUCaqseklAGBabEm5QBq8pk6\nFWyyfkINI2nmlTO8zOaSR+L8SzKdoRO1mfYwZPQ4azyXAASx2BXceef3qK1Vk8BLL4Hj2NgZw7nd\nsOQ95eff0jHivPNsvjr8QX5aMpN+VSHH1eaxPuLwiyOlsPYwtPhrNN2I9laaTjp5AWfdPQuiAclk\nHrfeGmfbNuXvP3asy9cb90D+fMIoYIIR+uzfH6O6Osa+fTBo0BgGDixn2jQ/XREbQrqCVgRw/MuS\nh8Op7GEwzwuHB+dBUZ3L8nqH5+9SA5MSJvXNzloNo02q7xOEpACLmpo4Th7ErnP57/0OA7C5bkDz\n+2gK1aR3FwMGPMF9963glltcamttnnhCee9nCtLamiAzIaM9tUWsrv13AJ49uYxfPNa7Qj6gxV+j\n6Va0q9LU8yh8diYH/i2lhF0kmDHD5bbbbHbsUP7+k2/26PvnhRhJn1BCgGDv3ocxjBQDBkAiYWKa\nUmXQCCW8QgAhfO5pQYoIu6+Gp6octm+3WVYHRXNU60TDUBk5Z57pUXBcBQ3DEuRvDdnvhIRSgoBU\nyuftF2MM+91ijjN8ZkctSmWcpwKbxYuzwh2NFja7NSEgGk1y0UUxSkrUruaVV+ympvUtrIuahN1x\n4GzTY2VQioWPj8XAa8p6nfCDFn+NplvRrkpT16Xg5RDjGyrzxRQmU6Y49OkDM2eq3cM3fmkzPuny\njQfvYkT5UwgjQIgwJ7UzIAyjhKFAygimqcI6ST/CMzsv4fR7/8yF0Ydwkou5/fY4e/aoIqyM82im\neTx5Caq/HHLaPMH+SUr4VcvJCH03k60FSDTyv4mRAM5rdFl1XyHJGzcj5SLI6VasuphJLrnkYUxT\nkkxaxG6rpPSlOt463SEM1S8mDJW/f+7v79HpLnm/9TEJMAyfqwpc6GXxftDir9F0O4660tRxyP9J\nHsW3JagfZ1Aw/UHy81Vjl0yvX9+H9dJmSP5ZjBRPql7BaVM0CaRSFg888AD9+tVx1lnK/Oz++10e\ne8yhpMRlRPQpTDNACJ/iYpeHHsqu2ONxePVVlz59fCAk7GPw7rShSOt1VPhGsHr1VLwdZZSzUIkx\nkut4hKks5IMRKbZeF5JKCQyjeQODTA/jSCRQP5MJ/nP0TE5ZIhltWNjE8bAxjObe/ABDyhxYrLZP\nR6w96MFo8ddoeirpbUK+65Kf3iY0NHicfbaybd6yxSYSUTuAqioHkVTLcRHA8dvh4HHH8+Cye1ix\nYgbRqOoelp8PH35oU1urPiLTGB4sNm92+GLgcd6HLuvucvje4zYjRzpUV2f7BBzofxuN75cTiSjf\noXHjxnDmQpeVCy7l8uefxERiksIEDpZIwigYhkyv9AUgmxrZBIHakUQiAaQMTqgKiBAiQ59zcXnR\nsMnLa0Xbe6JRz0egQ+IvhPgaUAGMAM6SUm5s47qLgV8CJvCwlPLnHflcjUZzlORsE3JbNd5zj8Wj\nj8b58pdtNm+G+fNtVs2+lWsvugu/H/zjSxBE3mPmzHKkhBNOqCMMHcCmoEC9dW5jeN93KNgOK0nH\n0p+wqFkQp2hGcz+dX//aZsmSIkaPVjYMN91UTjTqI/8zwt9vinJCbUBABMOQfKYqhZEMSUoDYUTo\n27eEgwc3IkRIEAiefvo61qwpY84clyd/WohdW04ynSq6VjiMHw9jx6qK5J0xj3Nx1ao/8zvppaKf\noaMr/63AvwLz27pACGEC84ALgb3ABiHEn6SUtR38bI1GcxRk8uMbG/c0a9WYTLqUl9tUVkKfPrDM\nuIrSSfcTsZJIAYaQRGSC8vJZCBGQSJi89daDOM6Mpk5iGSO1fxEeP5IVWCSIEAIJ3r+tghoqKJqh\n/HQWLIANv/K4fL+Lu9Vh6NUuhqHGI0zYdNN03r5nMCf8q8Orr0L9Ey67ZhcSKakjL8/huusASpHS\nRwiLU04pY948ld45aBA8eFcRB55ycaXDxoiNqFbuoGeFHvH0pBQssjDX9IBOLMeADom/lHI7ZLZj\nbXIW8JqU8vX0tb8HrgS0+Gs0HzO5q30hTISIEIYqlr9pk9PMV//VV10ifdIZPUAQCKQ0MM1UOvQS\nsmPHTPLyilizxiYWg02bILLBY7UsxSKBSUiAgUnI+APP4H97Hc+treS49+vY8EQhj1LelGUztaqS\nZNICmcCUBp87bgxV02aQKoQf3g9JbKUStSpzaMkSmHx6JU7RUp7bNpmZv8mmZ9o22I/beJ7Np10o\n2gMPPaTOBRxcLFT1cNBTOrEcAz6JmP/JwJs5j/cCX2rtQiHEDGAGwODBgz/+kWk0PZzcxuxSwsCB\n03nnncHMnq1SI3N99XPj80JEiEansmrVGM47bxZSJtNdr0Ieesjl29+2+c1vVNHUnye6WCmfCCEp\nDF5nKP1H7uJgScjxVY2cufkGDpSE/HykgVUrVVwen6G1dSyaXclPS2bSvzrA2lbOTUYRLwibIGh+\nH2EIYxMeD9WUY9X4XMM6/hArwm6lt2/GZG7x4nSqZ+jgp6uHe/MBb0uOKP5CiGeAAa386AdSyieP\n5WCklAuABQDjx4+XR7hco9EcgdwGJoZhMWBAGWecYTNv3qHnnfn5zePztbU2990H1dVw000zESIk\nlcrj5ZedZj18T7ylkIMr4ECxoO+2COuH/CvDvnMXYRREIEEGyAgYyYBRs0361prsvUxw6sQn+Py6\nkxj6XxJDhiTxOSd0eV60vir/6pkx/ja6kROqJJ+qVYe6nme3WtCVe6ZbWGjzh83x5jF/zZHFX0p5\nQQc/Yx/w+ZzHg9LPaTSaj5mWgp6xbchdIWc6YuX63Tc0eGzYMJehQx2WLZvBX/9a1FRMtWtXThtF\nz6P/6hupvjtQ9XoEXwAACitJREFUVcKE9K13SUUFhikJhQohYahag2XOFTAaBs94guG8BF+EvUaE\nzy8zSYYW6wyHc0yPs1Muz0qHF9L596NGeRTfvYjdUcmbSSiaYzKkzGGJ23bFc/MzXZvemMt/OD6J\nsM8GYLgQ4lSU6H8DuPoT+FyNRkPzBia5tOUT1NDg8fLLpYwapbKCZs+Os3OnzaWX2rzzjjJ5axJV\n16V+ZLLJHkLKFP36vdRUBdx0HKisgLjq6kuoSi0lDLOGc3/76gDyi8bzSvEAZlXX8LV7yjFIEAiD\nuwbP4z/enEFxsYsZVZXKoSE48Ktp9LNtHHpeb91PCqMjLxZCfFUIsRc1pS4XQqxMP3+SEGIFgJQy\nBcwCVgLbgf+RUm7r2LA1Gk1Hac0nCGDVKhcpVaPzSMSnpMTlkkvggQfg/Wc89s6cS80CT13sOHym\nOoqRRIk96nC2KQVE1XJBCANWQv7aOoYPn9wk/AI44fm9VJ/9BB98dgEDnVkcHNaIkCERmeKOvbM4\nJ+KxZYujDocxMcw+FIxWLmyZ8M5PfnJkkztNczqa7fM48Hgrz78FXJrzeAWwoiOfpdFoji0Zn5sJ\noct608Fx1OHpnXc6/OIXVlMD+JoahwkT1IHrqrAUK/QJb7CIbY4zvMxm3Wku4ewYZ1y0ln6X1apq\nnszKX4JMe+uf+KwFv3Y46SSl0O++eDefXbiLZF/ZFDJKoRrL59dKNZHIgMXTXJYMnsPxx8cZNKh5\n+Ap0yv5HRVf4ajS9FBuPuCgFfILA4pUa1Vx969Zs8VZ1tUNJiVJWR2RTJpOBzyvzXb6z2Kay0uam\nP9uMrfT43c6J7ClPIQWkklFenXcZqQJo3DyAH+0sYy42NnDSSTM4aUARxEtpGJbASIYEwiCZyuOh\nqhv5MfcTEQFGXh5DyhxU218dtz+WaPHXaHorrouR9BEyIAx8HpvpMmieskTYsUMVbwFs26ZCOWeb\nDiEWQeiTlBbPymydwJo14Lo27xWupWR7jFcGwIv7y6gdpIq7wlC9R7MU+xz7ieLjC9kS1jH7Voct\nr9h41lUsnnZods5RNbLRHBVa/DWa3orjkDItCJUlwrOhw2Xpgq+KCnjmGZq6ZYUhrBc2v5+uUian\nLHTYEDSvE7BzVudnoao7M8KfeY/C5s7MeNi42DgGnDOBnBRUu6nvbkbwCwvbbhyvJ4T2o8Vfo+mt\n2DY7Hozz2EyXZ0OHTXk2dztKQCsqYN26rC++YSjBHV6mRHlu2ZEF1/NUs/fM4W5Lh81MttHYhMeH\nhstn5jnYM5o3VcnNSDIMdTidcSPNHFCXlqpxGoaaPGbM+Fh+Wz0OLf4aTS+maIZS2vOXuhROhiI7\nWweQLZJSop0r9G3VCWTIiHYikRX+lg6brquE/5nwPKKhj7zBgqI1zd4oNyMp8z5CZNM6XTc7QYUh\nzJoFRUV6B3A0aPHXaHoznkdReXppvc6Comy+pLJ88A4pEMt5aevtEj2PRIXL2ITD+lB56l9wgdpN\n5Iqy40ChiJFHAgHIIAGxWLOLWnYuq6w8dCLKdAwDNUlo656jQ4u/RtObOUxT4FxTOMOwKC6ON5sA\nNm70mDzZZdOmbAtFGzUjnJvwWRVaXGTE2ZRnHyL8oB6fcgXwhHrcmj3kkaz3bVuFembNUrfQqn+/\nplW0+Gs0vZnDNAXONYULQ5/6erdJ/BsaPIqKShk50ueaayy+//04jmM3TSYiDDjO8LnzApe8CrvN\ng9mB3yuDFQtVV/VoFMrKDhnikfL4Z8xQoR596Ns+tPhrNL2ZwyytW5rCFRQ4TT+rr3cBVQVsGD6/\n/KWLbduA0zSZCMvCmVwI7lxqahxKy+1DQ0R2esLooHLrQq/2o8Vfo+nttKGcbZnCQfOJwTQtRo92\nsu+Ve1Kczs38gmExNoizPrQPMWDTyt05aPHXaDRt0pYp3OEmhiYxnzu36TwhIn3ON1xeELY2YOsi\naPHXaDQfiWYTQ2sB/ZzzBGFZfK3S4bg6HZfvKmjx12g0HaOtnM8W5wlFtk1RZ49V04QWf41G0zEO\nky6q4/ldlw75+Ws0Gk1TeMc0dUeVboRe+Ws0mo5xpEosTZdEi79Go+k4OrzT7dBhH41Go+mFaPHX\naDSaXogWf41Go+mFaPHXaDSaXogWf41Go+mFaPHXaDSaXoiQUnb2GFpFCPEusPsjvrw/8PdjOJzO\noLvfQ3cfP3T/e+ju44fufw+dMf4hUsrPHumiLiv+HUEIsVFKOb6zx9ERuvs9dPfxQ/e/h+4+fuj+\n99CVx6/DPhqNRtML0eKv0Wg0vZCeKv4LOnsAx4Dufg/dffzQ/e+hu48fuv89dNnx98iYv0aj0WgO\nT09d+Ws0Go3mMPQ48RdCXCyEeEUI8ZoQ4o7OHk97EUIsFEK8I4TY2tlj+SgIIT4vhFgjhKgVQmwT\nQny3s8fUXoQQfYQQLwkhqtP38OPOHtNHQQhhCiE2CyGWdfZYPgpCiL8KIWqEEFVCiI2dPZ72IoQo\nEEL8QQixQwixXQjRpWxPe1TYRwhhAq8CFwJ7gQ3AN6WUtZ06sHYghJgIvAfEpJRndvZ42osQYiAw\nUEq5SQhxPPAycFU3+xsI4NNSyveEEFHgL8B3pZQvdPLQ2oUQ4hZgPNBXSnl5Z4+nvQgh/gqMl1J2\nyzx/IcRiYJ2U8mEhhAV8SkpZ39njytDTVv5nAa9JKV+XUvrA74ErO3lM7UJKuRb4R2eP46MipXxb\nSrkp/f1BYDtwcueOqn1IxXvph9H0v261ShJCDAIuAx7u7LH0RoQQ+cBE4BEAKaXflYQfep74nwy8\nmfN4L91MeHoSQohTgDHAi507kvaTDplUAe8Aq6WU3e0eKoHvAWFnD6QDSGCVEOJlIcSMzh5MOzkV\neBdYlA69PSyE+HRnDyqXnib+mi6CEOIzwFKgXEp5oLPH016klIGUsgQYBJwlhOg2ITghxOXAO1LK\nlzt7LB3kbCnlWOASYGY6JNpdiABjgd9IKccA7wNd6gyyp4n/PuDzOY8HpZ/TfIKk4+RLgd9JKf/Y\n2ePpCOmt+hrg4s4eSzuYAHwlHTP/PXC+EOLRzh1S+5FS7kt/fQd4HBXW7S7sBfbm7Bj/gJoMugw9\nTfw3AMOFEKemD1i+Afypk8fUq0gflj4CbJdS3tfZ4/koCCE+K4QoSH9/HCqBYEfnjurokVLOkVIO\nklKegvo/8KyU8ludPKx2IYT4dDphgHS45CKg22TASSn3A28KIc5IP1UKdKmkhx7VwF1KmRJCzAJW\nAiawUEq5rZOH1S6EEP8FOEB/IcRe4P9IKR/p3FG1iwnAvwE16Zg5wPellCs6cUztZSCwOJ09ZgD/\nI6XslumS3ZjPAY+rtQQRYImU8unOHVK7uRH4XXoh+jowtZPH04weleqp0Wg0mqOjp4V9NBqNRnMU\naPHXaDSaXogWf41Go+mFaPHXaDSaXogWf41Go+mFaPHXaDSaXogWf41Go+mFaPHXaDSaXsj/B5mj\nDFi6aXY5AAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "t5McVnHmNiDw", + "colab_type": "text" + }, + "source": [ + "## Design a model\n", + "We're going to build a model that will take an input value (in this case, `x`) and use it to predict a numeric output value (the sine of `x`). This type of problem is called a _regression_.\n", + "\n", + "To achieve this, we're going to create a simple neural network. It will use _layers_ of _neurons_ to attempt to learn any patterns underlying the training data, so it can make predictions.\n", + "\n", + "To begin with, we'll define two layers. The first layer takes a single input (our `x` value) and runs it through 16 neurons. Based on this input, each neuron will become _activated_ to a certain degree based on its internal state (its _weight_ and _bias_ values). A neuron's degree of activation is expressed as a number.\n", + "\n", + "The activation numbers from our first layer will be fed as inputs to our second layer, which is a single neuron. It will apply its own weights and bias to these inputs and calculate its own activation, which will be output as our `y` value.\n", + "\n", + "**Note:** To learn more about how neural networks function, you can explore the [Learn TensorFlow](https://codelabs.developers.google.com/codelabs/tensorflow-lab1-helloworld) codelabs.\n", + "\n", + "The code in the following cell defines our model using [Keras](https://www.tensorflow.org/guide/keras), TensorFlow's high-level API for creating deep learning networks. Once the network is defined, we _compile_ it, specifying parameters that determine how it will be trained:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "gD60bE8cXQId", + "colab_type": "code", + "colab": {} + }, + "source": [ + "# We'll use Keras to create a simple model architecture\n", + "from tensorflow.keras import layers\n", + "model_1 = tf.keras.Sequential()\n", + "\n", + "# First layer takes a scalar input and feeds it through 16 \"neurons\". The\n", + "# neurons decide whether to activate based on the 'relu' activation function.\n", + "model_1.add(layers.Dense(16, activation='relu', input_shape=(1,)))\n", + "\n", + "# Final layer is a single neuron, since we want to output a single value\n", + "model_1.add(layers.Dense(1))\n", + "\n", + "# Compile the model using a standard optimizer and loss function for regression\n", + "model_1.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "O0idLyRLQeGj", + "colab_type": "text" + }, + "source": [ + "## Train the model\n", + "Once we've defined the model, we can use our data to _train_ it. Training involves passing an `x` value into the neural network, checking how far the network's output deviates from the expected `y` value, and adjusting the neurons' weights and biases so that the output is more likely to be correct the next time.\n", + "\n", + "Training runs this process on the full dataset multiple times, and each full run-through is known as an _epoch_. The number of epochs to run during training is a parameter we can set.\n", + "\n", + "During each epoch, data is run through the network in multiple _batches_. Each batch, several pieces of data are passed into the network, producing output values. These outputs' correctness is measured in aggregate and the network's weights and biases are adjusted accordingly, once per batch. The _batch size_ is also a parameter we can set.\n", + "\n", + "The code in the following cell uses the `x` and `y` values from our training data to train the model. It runs for 1000 _epochs_, with 16 pieces of data in each _batch_. We also pass in some data to use for _validation_. As you will see when you run the cell, training can take a while to complete:\n", + "\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "p8hQKr4cVOdE", + "colab_type": "code", + "outputId": "3f1a7904-ffcd-4bb7-8bbb-bcd85a132128", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "source": [ + "# Train the model on our training data while validating on our validation set\n", + "history_1 = model_1.fit(x_train, y_train, epochs=1000, batch_size=16,\n", + " validation_data=(x_validate, y_validate))" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Train on 600 samples, validate on 200 samples\n", + "Epoch 1/1000\n", + "600/600 [==============================] - 0s 412us/sample - loss: 0.5016 - mae: 0.6297 - val_loss: 0.4922 - val_mae: 0.6235\n", + "Epoch 2/1000\n", + "600/600 [==============================] - 0s 105us/sample - loss: 0.3905 - mae: 0.5436 - val_loss: 0.4262 - val_mae: 0.5641\n", + "...\n", + "Epoch 998/1000\n", + "600/600 [==============================] - 0s 109us/sample - loss: 0.1535 - mae: 0.3068 - val_loss: 0.1507 - val_mae: 0.3113\n", + "Epoch 999/1000\n", + "600/600 [==============================] - 0s 100us/sample - loss: 0.1545 - mae: 0.3077 - val_loss: 0.1499 - val_mae: 0.3103\n", + "Epoch 1000/1000\n", + "600/600 [==============================] - 0s 132us/sample - loss: 0.1530 - mae: 0.3045 - val_loss: 0.1542 - val_mae: 0.3143\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cRE8KpEqVfaS", + "colab_type": "text" + }, + "source": [ + "## Check the training metrics\n", + "During training, the model's performance is constantly being measured against both our training data and the validation data that we set aside earlier. Training produces a log of data that tells us how the model's performance changed over the course of the training process.\n", + "\n", + "The following cells will display some of that data in a graphical form:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "CmvA-ksoln8r", + "colab_type": "code", + "outputId": "1b834831-81e8-4548-dd8c-f5edf2c3ff43", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 295 + } + }, + "source": [ + "# Draw a graph of the loss, which is the distance between\n", + "# the predicted and actual values during training and validation.\n", + "loss = history_1.history['loss']\n", + "val_loss = history_1.history['val_loss']\n", + "\n", + "epochs = range(1, len(loss) + 1)\n", + "\n", + "plt.plot(epochs, loss, 'g.', label='Training loss')\n", + "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n", + "plt.title('Training and validation loss')\n", + "plt.xlabel('Epochs')\n", + "plt.ylabel('Loss')\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\nWBhjjAno/wGVkooxFkdVNgAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iOFBSbPcYCN4", + "colab_type": "text" + }, + "source": [ + "## Look closer at the data\n", + "The graph shows the _loss_ (or the difference between the model's predictions and the actual data) for each epoch. There are several ways to calculate loss, and the method we have used is _mean squared error_. There is a distinct loss value given for the training and the validation data.\n", + "\n", + "As we can see, the amount of loss rapidly decreases over the first 25 epochs, before flattening out. This means that the model is improving and producing more accurate predictions!\n", + "\n", + "Our goal is to stop training when either the model is no longer improving, or when the _training loss_ is less than the _validation loss_, which would mean that the model has learned to predict the training data so well that it can no longer generalize to new data.\n", + "\n", + "To make the flatter part of the graph more readable, let's skip the first 50 epochs:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "Zo0RYroFZYIV", + "colab_type": "code", + "outputId": "e6841332-0541-44bb-a186-ae5b46781e51", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 295 + } + }, + "source": [ + "# Exclude the first few epochs so the graph is easier to read\n", + "SKIP = 50\n", + "\n", + "plt.plot(epochs[SKIP:], loss[SKIP:], 'g.', label='Training loss')\n", + "plt.plot(epochs[SKIP:], val_loss[SKIP:], 'b.', label='Validation loss')\n", + "plt.title('Training and validation loss')\n", + "plt.xlabel('Epochs')\n", + "plt.ylabel('Loss')\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\nAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "W4EQD-Bb8hLM", + "colab_type": "text" + }, + "source": [ + "## Further metrics\n", + "From the plot, we can see that loss continues to reduce until around 600 epochs, at which point it is mostly stable. This means that there's no need to train our network beyond 600 epochs.\n", + "\n", + "However, we can also see that the lowest loss value is still around 0.155. This means that our network's predictions are off by an average of ~15%. In addition, the validation loss values jump around a lot, and is sometimes even higher.\n", + "\n", + "To gain more insight into our model's performance we can plot some more data. This time, we'll plot the _mean absolute error_, which is another way of measuring how far the network's predictions are from the actual numbers:\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "Md9E_azmpkZU", + "colab_type": "code", + "outputId": "39b97561-b01d-49f2-c35c-fbd8db663806", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 295 + } + }, + "source": [ + "plt.clf()\n", + "\n", + "# Draw a graph of mean absolute error, which is another way of\n", + "# measuring the amount of error in the prediction.\n", + "mae = history_1.history['mae']\n", + "val_mae = history_1.history['val_mae']\n", + "\n", + "plt.plot(epochs[SKIP:], mae[SKIP:], 'g.', label='Training MAE')\n", + "plt.plot(epochs[SKIP:], val_mae[SKIP:], 'b.', label='Validation MAE')\n", + "plt.title('Training and validation mean absolute error')\n", + "plt.xlabel('Epochs')\n", + "plt.ylabel('MAE')\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\nc8stt3DXXXcVY8gcddRRGbaCE088kX79+qXtu/7669lll1349NNPU/sef/xx9thjj9Q9Dh8+nCVL\nlhRlXIXAShYeRKNaojCSRTTa2SOysLBwo7KyktWrVwNQW1vLbrvtxk9/ml64TymFUopQyH89PGfO\nnJzXmTJlSvsH68Luu+/Os88+yxFHHMFHH33E+++/n9Fm3rx5HH744SxcuJD/+Z//Se0fM2YMCxcu\nLOp4CoWVLDyIRLTq6de/tiooC4tiIbYpxvSl04ltKp0h8I033mDYsGGceeaZHHzwwbz33ntMnDiR\nUaNGcfDBB/OrX/0q1faoo45i9erVtLa20q9fPy6//HIOO+wwIpEI//73vwG48sormTlzZqr95Zdf\nzujRo/n617/OM888A8Dnn3/OhAkTGDZsGKeddhqjRo1KMTIvTj/9dObPnw/AP//5T0477bS046+/\n/jqtra3U1tYyb968os9Pe2GZhQ8iEbjiCssoLCyKgdimGGPrxnLVkqsYWze2pAzj1Vdf5aKLLmLd\nunUMGDCAa6+9lhUrVrBmzRoee+wx1q1bl3HOJ598wre+9S3WrFlDJBLhzjvv9O1bKcXy5cv53e9+\nl2I8N910E/3792fdunVcddVVrFq1KnBs3/nOd3jiiSdIJBLcfffd/Pd//3fa8Xnz5nH66acTjUZ5\n6aWX+PDDD1PHlixZkqaGamhoaMPstA+WWXhgPaEsLIqL+oZ6muPNxFWc5ngz9Q31JbvW0KFDGTVq\nVGp73rx5jBw5kpEjR/LKK6/4Mos+ffpw/PHHA3D44YcHEuJTTz01o83TTz/N6aefDsBhhx3GwQcf\nHDi28vJyjjjiCObPn088HmfgwIFpx+fPn8/pp59OOBzm5JNP5p//dErVjxkzhtWrV6c+nZFh29os\nXLCeUBYWxUd0SJSKcAXN8WYqwhVEh0RLdq1dd9019Xv9+vX84Q9/YPny5fTr148f/vCHvnEGFRUV\nqd/hcJjW1lbfvnv16pWzTS6cfvrp/Od//ifXXHNN2v5Vq1bx5ptvMmbMGACampr42te+xgUXXNCm\n65QCVrJwwXpCWVgUH5GqCItrFvPrMb9mcc1iIlUdswLbtm0bu+++O3379uW9997jkUceKfo1jjzy\nSP7+978DsHbtWl/JxY1oNMrll1/uq4K65ppraGhooKGhgXfffZe33nqLzZs3F33MbYWVLFywnlAW\n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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ctawd0CXAVEw", + "colab_type": "text" + }, + "source": [ + "This graph of _mean absolute error_ tells another story. We can see that training data shows consistently lower error than validation data, which means that the network may have _overfit_, or learned the training data so rigidly that it can't make effective predictions about new data.\n", + "\n", + "In addition, the mean absolute error values are quite high, ~0.305 at best, which means some of the model's predictions are at least 30% off. A 30% error means we are very far from accurately modelling the sine wave function.\n", + "\n", + "To get more insight into what is happening, we can plot our network's predictions for the training data against the expected values:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "i13eVIT3B9Mj", + "colab_type": "code", + "outputId": "afc103e2-0beb-4a26-fe18-c0cccc6d3d2a", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 281 + } + }, + "source": [ + "# Use the model to make predictions from our validation data\n", + "predictions = model_1.predict(x_train)\n", + "\n", + "# Plot the predictions along with to the test data\n", + "plt.clf()\n", + "plt.title('Training data predicted vs actual values')\n", + "plt.plot(x_test, y_test, 'b.', label='Actual')\n", + "plt.plot(x_train, predictions, 'r.', label='Predicted')\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\n/oZhGGXI/w++6U8tCYD1ygAAAABJRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Wokallj1D21L", + "colab_type": "text" + }, + "source": [ + "Oh dear! The graph makes it clear that our network has learned to approximate the sine function in a very limited way. From `0 <= x <= 1.1` the line mostly fits, but for the rest of our `x` values it is a rough approximation at best.\n", + "\n", + "The rigidity of this fit suggests that the model does not have enough capacity to learn the full complexity of the sine wave function, so it's only able to approximate it in an overly simplistic way. By making our model bigger, we should be able to improve its performance.\n", + "\n", + "## Change our model\n", + "To make our model bigger, let's add an additional layer of neurons. The following cell redefines our model in the same way as earlier, but with an additional layer of 16 neurons in the middle:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "oW0xus6AF-4o", + "colab_type": "code", + "colab": {} + }, + "source": [ + "model_2 = tf.keras.Sequential()\n", + "\n", + "# First layer takes a scalar input and feeds it through 16 \"neurons\". The\n", + "# neurons decide whether to activate based on the 'relu' activation function.\n", + "model_2.add(layers.Dense(16, activation='relu', input_shape=(1,)))\n", + "\n", + "# The new second layer may help the network learn more complex representations\n", + "model_2.add(layers.Dense(16, activation='relu'))\n", + "\n", + "# Final layer is a single neuron, since we want to output a single value\n", + "model_2.add(layers.Dense(1))\n", + "\n", + "# Compile the model using a standard optimizer and loss function for regression\n", + "model_2.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Dv2SC409Grap", + "colab_type": "text" + }, + "source": [ + "We'll now train the new model. To save time, we'll train for only 600 epochs:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "DPAUrdkmGq1M", + "colab_type": "code", + "outputId": "34ad91e0-229b-479c-bd65-12ad1ed1c660", + "colab": { + "base_uri": "https://localhost:8080/" + } + }, + "source": [ + "history_2 = model_2.fit(x_train, y_train, epochs=600, batch_size=16,\n", + " validation_data=(x_validate, y_validate))" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Train on 600 samples, validate on 200 samples\n", + "Epoch 1/600\n", + "600/600 [==============================] - 0s 422us/sample - loss: 0.5655 - mae: 0.6259 - val_loss: 0.4104 - val_mae: 0.5509\n", + "Epoch 2/600\n", + "600/600 [==============================] - 0s 111us/sample - loss: 0.3195 - mae: 0.4902 - val_loss: 0.3341 - val_mae: 0.4927\n", + "...\n", + "Epoch 598/600\n", + "600/600 [==============================] - 0s 116us/sample - loss: 0.0124 - mae: 0.0886 - val_loss: 0.0096 - val_mae: 0.0771\n", + "Epoch 599/600\n", + "600/600 [==============================] - 0s 130us/sample - loss: 0.0125 - mae: 0.0900 - val_loss: 0.0107 - val_mae: 0.0824\n", + "Epoch 600/600\n", + "600/600 [==============================] - 0s 109us/sample - loss: 0.0124 - mae: 0.0892 - val_loss: 0.0116 - val_mae: 0.0845\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Mc_CQu2_IvOP", + "colab_type": "text" + }, + "source": [ + "## Evaluate our new model\n", + "Each training epoch, the model prints out its loss and mean absolute error for training and validation. You can read this in the output above (note that your exact numbers may differ): \n", + "\n", + "```\n", + "Epoch 600/600\n", + "600/600 [==============================] - 0s 109us/sample - loss: 0.0124 - mae: 0.0892 - val_loss: 0.0116 - val_mae: 0.0845\n", + "```\n", + "\n", + "You can see that we've already got a huge improvement - validation loss has dropped from 0.15 to 0.015, and validation MAE has dropped from 0.31 to 0.1.\n", + "\n", + "The following cell will print the same graphs we used to evaluate our original model, but showing our new training history:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "SYHGswAJJgrC", + "colab_type": "code", + "outputId": "efcc51f6-f1f1-490a-ffba-ed283586f83e", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 851 + } + }, + "source": [ + "# Draw a graph of the loss, which is the distance between\n", + "# the predicted and actual values during training and validation.\n", + "loss = history_2.history['loss']\n", + "val_loss = history_2.history['val_loss']\n", + "\n", + "epochs = range(1, len(loss) + 1)\n", + "\n", + "plt.plot(epochs, loss, 'g.', label='Training loss')\n", + "plt.plot(epochs, val_loss, 'b', label='Validation loss')\n", + "plt.title('Training and validation loss')\n", + "plt.xlabel('Epochs')\n", + "plt.ylabel('Loss')\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "# Exclude the first few epochs so the graph is easier to read\n", + "SKIP = 100\n", + "\n", + "plt.clf()\n", + "\n", + "plt.plot(epochs[SKIP:], loss[SKIP:], 'g.', label='Training loss')\n", + "plt.plot(epochs[SKIP:], val_loss[SKIP:], 'b.', label='Validation loss')\n", + "plt.title('Training and validation loss')\n", + "plt.xlabel('Epochs')\n", + "plt.ylabel('Loss')\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "plt.clf()\n", + "\n", + "# Draw a graph of mean absolute error, which is another way of\n", + "# measuring the amount of error in the prediction.\n", + "mae = history_2.history['mae']\n", + "val_mae = history_2.history['val_mae']\n", + "\n", + "plt.plot(epochs[SKIP:], mae[SKIP:], 'g.', label='Training MAE')\n", + "plt.plot(epochs[SKIP:], val_mae[SKIP:], 'b.', label='Validation MAE')\n", + "plt.title('Training and validation mean absolute error')\n", + "plt.xlabel('Epochs')\n", + "plt.ylabel('MAE')\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\nMMYY4/H/AZN6yxQ6gTLNAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + }, + { + "output_type": "display_data", + "data": { + "image/png": "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\ntueee45p06Yltf+RRx7JCy+80ODzT58+nauuuooDDjgAgNdfb9tdVot1thsMhv2LxlzS+ZJLLuG1\n114LL2JVUlLC1q1bGTx4cHheR//+/enduzf//ve/a+xfUlJCr169APjhhx+47LLL6N69OxdddBE/\n/PBDuNyECRPCKej/+Mc/AvDQQw+xdetWhg4dytChQwHIy8tjx44dAPzlL3+hV69e9OrVK5yCvqSk\nhO7du3PttdfSs2dPzjzzzKjzxGPt2rWceuqpnHTSSVx00UV8++234fN7aeW9ZJHvvfdeeGGvfv36\nsXv37ga3bVy8xV3a8t+AAQPUYDA0HR999FG9yi9dqtq+vaptu/8vXZp6Hc455xydO3euqqpOnTpV\nf/vb36qqalVVle7atUtVVbdv367HHHOMOo6jqqoHHnigqqp+8cUX2rNnT1VVfeCBB3T06NGqqrpu\n3Tq1bVtXrFihqqrl5eWqqhoIBHTIkCG6bt06VVXt2rWrbt++PVwX7/vKlSu1V69eumfPHt29e7f2\n6NFDV69erV988YXatq1r1qxRVdVf/OIX+tRTT9W4pj/+8Y/65z//WVVVe/furT6fT1VVJ0+erL/+\n9a9VVfWII47Qffv2qarqt99+q6qq5557ri5evFhVVXfv3q1VVVU1jh3vngErNYk+1mgkBoOh2UnH\nks6eeQtcs9bll18OuIPn22+/nZNOOokRI0bw5Zdf8vXXXyc8zsKFC8MLRp100kmcdNJJ4d+ef/55\n+vfvT79+/fjwww/rTMi4ePFiLrroIg488EAOOuggfv7zn7No0SIAunXrRt++fYHaU9WDuz7Kzp07\nGTJkCACjRo1i4cKF4TpeeeWVPP300+EZ9IMGDeKWW27hoYceYufOnY0+s94IEoPB0Ox4SzrbduMt\n6XzBBRcwf/58Vq9ezd69exkwYAAAzzzzDNu3b2fVqlWsXbuWn/zkJ3FTx9fFF198wf3338/8+fP5\n4IMPOOeccxp0HA8vBT2klob+tdde44YbbmD16tWcfPLJBAIBbrvtNp544gl++OEHBg0axCeffNLg\nesbDCBKDwdDspGNJ54MOOoihQ4cyZsyYsDYC7mj+sMMOIzMzkwULFrB58+Zaj3P66afz7LPPAvCf\n//yHDz74AHBT0B944IF07NiRr7/+mnnz5oX36dChQ1w/xODBg5k7dy579+7l+++/56WXXmLw4MH1\n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\nRU5ErkJggg==\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + }, + { + "output_type": "display_data", + "data": { + "image/png": "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\nQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f86dWOyZKmN9", + "colab_type": "text" + }, + "source": [ + "Great results! From these graphs, we can see several exciting things:\n", + "\n", + "* Our network has reached its peak accuracy much more quickly (within 200 epochs instead of 600)\n", + "* The overall loss and MAE are much better than our previous network\n", + "* Metrics are better for validation than training, which means the network is not overfitting\n", + "\n", + "The reason the metrics for validation are better than those for training is that validation metrics are calculated at the end of each epoch, while training metrics are calculated throughout the epoch, so validation happens on a model that has been trained slightly longer.\n", + "\n", + "This all means our network seems to be performing well! To confirm, let's check its predictions against the test dataset we set aside earlier:\n" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "lZfztKKyhLxX", + "colab_type": "code", + "outputId": "b792a12e-713d-4b07-9f8e-de0d059d5cdb", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 298 + } + }, + "source": [ + "# Calculate and print the loss on our test dataset\n", + "loss = model_2.evaluate(x_test, y_test)\n", + "\n", + "# Make predictions based on our test dataset\n", + "predictions = model_2.predict(x_test)\n", + "\n", + "# Graph the predictions against the actual values\n", + "plt.clf()\n", + "plt.title('Comparison of predictions and actual values')\n", + "plt.plot(x_test, y_test, 'b.', label='Actual')\n", + "plt.plot(x_test, predictions, 'r.', label='Predicted')\n", + "plt.legend()\n", + "plt.show()" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "200/200 [==============================] - 0s 146us/sample - loss: 0.0124 - mae: 0.0907\n" + ], + "name": "stdout" + }, + { + "output_type": "display_data", + "data": { + "image/png": "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\nVxRFKUP+P5OxXtvr2werAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3h7IcvuOOS4J", + "colab_type": "text" + }, + "source": [ + "Much better! The evaluation metrics we printed show that the model has a low loss and MAE on the test data, and the predictions line up visually with our data fairly well.\n", + "\n", + "The model isn't perfect; its predictions don't form a smooth sine curve. For instance, the line is almost straight when `x` is between 4.2 and 5.2. If we wanted to go further, we could try further increasing the capacity of the model, perhaps using some techniques to defend from overfitting.\n", + "\n", + "However, an important part of machine learning is knowing when to quit, and this model is good enough for our use case - which is to make some LEDs blink in a pleasing pattern.\n", + "\n", + "## Convert to TensorFlow Lite\n", + "We now have an acceptably accurate model in-memory. However, to use this with TensorFlow Lite for Microcontrollers, we'll need to convert it into the correct format and download it as a file. To do this, we'll use the [TensorFlow Lite Converter](https://www.tensorflow.org/lite/convert). The converter outputs a file in a special, space-efficient format for use on memory-constrained devices.\n", + "\n", + "Since this model is going to be deployed on a microcontroller, we want it to be as tiny as possible! One technique for reducing the size of models is called [quantization](https://www.tensorflow.org/lite/performance/post_training_quantization). It reduces the precision of the model's weights, which saves memory, often without much impact on accuracy. Quantized models also run faster, since the calculations required are simpler.\n", + "\n", + "The TensorFlow Lite Converter can apply quantization while it converts the model. In the following cell, we'll convert the model twice: once with quantization, once without:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "1muAoUm8lSXL", + "colab_type": "code", + "colab": {} + }, + "source": [ + "# Convert the model to the TensorFlow Lite format without quantization\n", + "converter = tf.lite.TFLiteConverter.from_keras_model(model_2)\n", + "tflite_model = converter.convert()\n", + "\n", + "# Save the model to disk\n", + "open(\"sine_model.tflite\", \"wb\").write(tflite_model)\n", + "\n", + "# Convert the model to the TensorFlow Lite format with quantization\n", + "converter = tf.lite.TFLiteConverter.from_keras_model(model_2)\n", + "converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]\n", + "tflite_model = converter.convert()\n", + "\n", + "# Save the model to disk\n", + "open(\"sine_model_quantized.tflite\", \"wb\").write(tflite_model)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "L_vE-ZDkHVxe", + "colab_type": "text" + }, + "source": [ + "## Test the converted models\n", + "To prove these models are still accurate after conversion and quantization, we'll use both of them to make predictions and compare these against our test results:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "-J7IKlXiYVPz", + "colab_type": "code", + "outputId": "0c10f56c-dbd7-4cc3-e332-30ad673769e5", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 281 + } + }, + "source": [ + "# Instantiate an interpreter for each model\n", + "sine_model = tf.lite.Interpreter('sine_model.tflite')\n", + "sine_model_quantized = tf.lite.Interpreter('sine_model_quantized.tflite')\n", + "\n", + "# Allocate memory for each model\n", + "sine_model.allocate_tensors()\n", + "sine_model_quantized.allocate_tensors()\n", + "\n", + "# Get the input and output tensors so we can feed in values and get the results\n", + "sine_model_input = sine_model.tensor(sine_model.get_input_details()[0][\"index\"])\n", + "sine_model_output = sine_model.tensor(sine_model.get_output_details()[0][\"index\"])\n", + "sine_model_quantized_input = sine_model_quantized.tensor(sine_model_quantized.get_input_details()[0][\"index\"])\n", + "sine_model_quantized_output = sine_model_quantized.tensor(sine_model_quantized.get_output_details()[0][\"index\"])\n", + "\n", + "# Create arrays to store the results\n", + "sine_model_predictions = np.empty(x_test.size)\n", + "sine_model_quantized_predictions = np.empty(x_test.size)\n", + "\n", + "# Run each model's interpreter for each value and store the results in arrays\n", + "for i in range(x_test.size):\n", + " sine_model_input().fill(x_test[i])\n", + " sine_model.invoke()\n", + " sine_model_predictions[i] = sine_model_output()[0]\n", + "\n", + " sine_model_quantized_input().fill(x_test[i])\n", + " sine_model_quantized.invoke()\n", + " sine_model_quantized_predictions[i] = sine_model_quantized_output()[0]\n", + "\n", + "# See how they line up with the data\n", + "plt.clf()\n", + "plt.title('Comparison of various models against actual values')\n", + "plt.plot(x_test, y_test, 'bo', label='Actual')\n", + "plt.plot(x_test, predictions, 'ro', label='Original predictions')\n", + "plt.plot(x_test, sine_model_predictions, 'bx', label='Lite predictions')\n", + "plt.plot(x_test, sine_model_quantized_predictions, 'gx', label='Lite quantized predictions')\n", + "plt.legend()\n", + "plt.show()\n" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\nETsI2utZSjVkezyWt5iKKG8Sml7fxNNS1kFezfT0dAYNGlRsWceOHXF1deX48eMA7N+/n3Xr1rFj\nx45i5f7+979z2223cfjwYd566y1rjJ+SHDt2jOeff5709HScnZ2tMYDGjx/Pnj17SE1NpX///vzz\nn/+ssL1Go5EffviBb775hlmzZlkDxtm28dtvv+XYsWP88MMPHDx4kH379rFz50727dvHqlWrrKP4\nPXv2lKo/Ly+PiRMn8v7775OamsqWLVto164db775JhMnTuTgwYNMnDix2DYvvPAC06dP59ChQ0yZ\nMoUXX3zRuu7cuXMkJyfz73//2/qmsGLFCkJCQjh48CCpqan4+vpWeNxNiUbl7dPSqJQsCIzGl3Si\n8r4B4UKEzGTsHa4kyIXE5v4FgJTuF+h2KqiYt4/OBJk9LtHrl84U4sAt5+549CvuwlkpXF2JE84k\n+6cRvGMEyf5p9G8XBDoHnvIMJCI9BYA4jwDevrsXN/wGwq4q7sMGJ6eyPYiajIdQXcV0roAHHnjA\nbkjn5ORkXnrpJQA8PT3x9va2u727u7tVwA0aNAij0QhAWloar732GtnZ2Vy7do2QkJAK2/Loo4+i\n0+no3bs3d955pzXtpG0bv/32W7799lv8/PwALVnNsWPHyMnJ4eGHH7aGqQ4LCytV/9GjR7njjjsY\nPHgwoD0IK+L7779n/fr1AEybNo25c4v66bhx49DpdAwYMIBff/0VgMGDBzNz5kzy8/MZN26cEv6K\n2qMyssD/nI59j3zDmKQBRIUfZunxADK8kwnbHETEuaXwdhQX3kskPt6+770l06ubGyR+VfU2xk2d\nTVTeYmLWuhBh3EFchg+REw+BvoDI/g5w3QeAyDFpINPwW+3PATv1VJRZTAjN1LBoUdlzCJrM1I+y\nsunU4AAGDBhQSod/9epVsrKyuPvuu9m/fz/t2rWrdv0ArVu3tv7W6/Xk5uYCWpC4DRs24OPjw+ef\nf8727dsrrEuUSChh+W/bRiklr776Ks8880yxsu+99151D6Ha2B67NHfU4cOHs3PnTr755htmzJhB\nREQEjz/+eL23ra5Qap96pKQNsKy8G0IUGU+3XVlKzFoXNgUfpsNvPcnwScH90DA27k6yPj0qo26p\n7qBzS1sTMa3mESGzQQgiZDax//ak36GBICByWjqR09JAFBC72p19xjn8xRBIh4cmQaCmlnJzg6++\n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\nlFJx6P8D4Obclx42P3sAAAAASUVORK5CYII=\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "tags": [] + } + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jWxvLGexKv0D", + "colab_type": "text" + }, + "source": [ + "We can see from the graph that the predictions for the original model, the converted model, and the quantized model are all close enough to be indistinguishable. This means that our quantized model is ready to use!\n", + "\n", + "We can print the difference in file size:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "6r42iBnULP4X", + "colab_type": "code", + "outputId": "afe526c9-498d-498e-d768-1edfbf21e870", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 68 + } + }, + "source": [ + "import os\n", + "basic_model_size = os.path.getsize(\"sine_model.tflite\")\n", + "print(\"Basic model is %d bytes\" % basic_model_size)\n", + "quantized_model_size = os.path.getsize(\"sine_model_quantized.tflite\")\n", + "print(\"Quantized model is %d bytes\" % quantized_model_size)\n", + "difference = basic_model_size - quantized_model_size\n", + "print(\"Difference is %d bytes\" % difference)" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Basic model is 2656 bytes\n", + "Quantized model is 2640 bytes\n", + "Difference is 16 bytes\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "C2vpZE9ZshVH", + "colab_type": "text" + }, + "source": [ + "Our quantized model is only 16 bytes smaller than the original version, which only a tiny reduction in size! At around 2.6 kilobytes, this model is already so small that the weights make up only a small fraction of the overall size, meaning quantization has little effect.\n", + "\n", + "More complex models have many more weights, meaning the space saving from quantization will be much higher, approaching 4x for most sophisticated models.\n", + "\n", + "Regardless, our quantized model will take less time to execute than the original version, which is important on a tiny microcontroller!\n", + "\n", + "## Write to a C file\n", + "The final step in preparing our model for use with TensorFlow Lite for Microcontrollers is to convert it into a C source file. You can see an example of this format in [`hello_world/sine_model_data.cc`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/hello_world/sine_model_data.cc).\n", + "\n", + "To do so, we can use a command line utility named [`xxd`](https://linux.die.net/man/1/xxd). The following cell runs `xxd` on our quantized model and prints the output:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "l4-WhtGpvb-E", + "colab_type": "code", + "outputId": "f975721f-bdd1-440a-93af-55f13c4c8690", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 3808 + } + }, + "source": [ + "# Install xxd if it is not available\n", + "!apt-get -qq install xxd\n", + "# Save the file as a C source file\n", + "!xxd -i sine_model_quantized.tflite > sine_model_quantized.cc\n", + "# Print the source file\n", + "!cat sine_model_quantized.cc" + ], + "execution_count": 0, + "outputs": [ + { + "output_type": "stream", + "text": [ + "unsigned char sine_model_quantized_tflite[] = {\n", + " 0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00,\n", + " 0x18, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00,\n", + " 0x0e, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x0a, 0x00, 0x00,\n", + " 0xb8, 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", + " 0x0b, 0x00, 0x00, 0x00, 0x90, 0x05, 0x00, 0x00, 0x7c, 0x05, 0x00, 0x00,\n", + " 0x24, 0x05, 0x00, 0x00, 0xd4, 0x04, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00,\n", + " 0x74, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00,\n", + " 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", + " 0x54, 0xf6, 0xff, 0xff, 0x58, 0xf6, 0xff, 0xff, 0x5c, 0xf6, 0xff, 0xff,\n", + " 0x60, 0xf6, 0xff, 0xff, 0xc2, 0xfa, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,\n", + " 0x40, 0x00, 0x00, 0x00, 0x7c, 0x19, 0xa7, 0x3e, 0x99, 0x81, 0xb9, 0x3e,\n", + " 0x56, 0x8b, 0x9f, 0x3e, 0x88, 0xd8, 0x12, 0xbf, 0x74, 0x10, 0x56, 0x3e,\n", + " 0xfe, 0xc6, 0xdf, 0xbe, 0xf2, 0x10, 0x5a, 0xbe, 0xf0, 0xe2, 0x0a, 0xbe,\n", + " 0x10, 0x5a, 0x98, 0xbe, 0xb9, 0x36, 0xce, 0x3d, 0x8f, 0x7f, 0x87, 0x3e,\n", + " 0x2c, 0xb1, 0xfd, 0xbd, 0xe6, 0xa6, 0x8a, 0xbe, 0xa5, 0x3e, 0xda, 0x3e,\n", + " 0x50, 0x34, 0xed, 0xbd, 0x90, 0x91, 0x69, 0xbe, 0x0e, 0xfb, 0xff, 0xff,\n", + " 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x67, 0x41, 0x48, 0xbf,\n", + " 0x24, 0xcd, 0xa0, 0xbe, 0xb7, 0x92, 0x0c, 0xbf, 0x00, 0x00, 0x00, 0x00,\n", + " 0x98, 0xfe, 0x3c, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", + " 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x17, 0x9a, 0xbe,\n", + " 0x41, 0xcb, 0xb6, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", + " 0x13, 0xd6, 0x1e, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n", + " 0x5a, 0xfb, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,\n", + " 0x4b, 0x98, 0xdd, 0xbd, 0x40, 0x6b, 0xcb, 0xbe, 0x36, 0x0c, 0xd4, 0x3c,\n", + " 0xbd, 0x44, 0xb5, 0x3e, 0x95, 0x70, 0xe3, 0x3e, 0xe7, 0xac, 0x86, 0x3e,\n", + " 0x00, 0xc4, 0x4e, 0x3d, 0x7e, 0xa6, 0x1d, 0x3e, 0xbd, 0x87, 0xbb, 0x3e,\n", + " 0xb4, 0xb8, 0x09, 0xbf, 0xa1, 0x1f, 0xf8, 0xbe, 0x8d, 0x90, 0xdd, 0x3e,\n", + " 0xde, 0xfa, 0x6f, 0xbe, 0xb2, 0x75, 0xe4, 0x3d, 0x6e, 0xfe, 0x36, 0x3e,\n", + " 0x20, 0x18, 0xc2, 0xbe, 0x39, 0xc7, 0xfb, 0xbe, 0xfe, 0xa4, 0x30, 0xbe,\n", + " 0xf7, 0x91, 0xde, 0xbe, 0xde, 0xab, 0x24, 0x3e, 0xfb, 0xbb, 0xce, 0x3e,\n", + " 0xeb, 0x23, 0x80, 0xbe, 0x7b, 0x58, 0x73, 0xbe, 0x9a, 0x2e, 0x03, 0x3e,\n", + " 0x10, 0x42, 0xa9, 0xbc, 0x10, 0x12, 0x64, 0xbd, 0xe3, 0x8d, 0x0c, 0x3d,\n", + " 0x9e, 0x48, 0x97, 0xbe, 0x34, 0x51, 0xd4, 0xbe, 0x02, 0x3b, 0x0d, 0x3e,\n", + " 0x62, 0x67, 0x89, 0xbe, 0x74, 0xdf, 0xa2, 0x3d, 0xf3, 0x25, 0xb3, 0xbe,\n", + " 0xef, 0x34, 0x7b, 0x3d, 0x61, 0x70, 0xe3, 0x3d, 0xba, 0x76, 0xc0, 0xbe,\n", + " 0x7d, 0xe9, 0xa7, 0x3e, 0xc3, 0xab, 0xd0, 0xbe, 0xcf, 0x7c, 0xdb, 0xbe,\n", + " 0x70, 0x27, 0x9a, 0xbe, 0x98, 0xf5, 0x3c, 0xbd, 0xff, 0x4b, 0x4b, 0x3e,\n", + " 0x7e, 0xa0, 0xf8, 0xbd, 0xd4, 0x6e, 0x86, 0x3d, 0x00, 0x4a, 0x07, 0x3a,\n", + " 0x4c, 0x24, 0x61, 0xbe, 0x54, 0x68, 0xf7, 0xbd, 0x02, 0x3f, 0x77, 0xbe,\n", + " 0x23, 0x79, 0xb3, 0x3e, 0x1c, 0x83, 0xad, 0xbd, 0xc8, 0x92, 0x8d, 0x3e,\n", + " 0xa8, 0xf3, 0x15, 0xbd, 0xe6, 0x4d, 0x6c, 0x3d, 0xac, 0xe7, 0x98, 0xbe,\n", + " 0x81, 0xec, 0xbd, 0x3e, 0xe2, 0x55, 0x73, 0x3e, 0xc1, 0x77, 0xc7, 0x3e,\n", + " 0x6e, 0x1b, 0x5e, 0x3d, 0x27, 0x78, 0x02, 0x3f, 0xd4, 0x21, 0x90, 0x3d,\n", + " 0x52, 0xdc, 0x1f, 0x3e, 0xbf, 0xda, 0x88, 0x3e, 0x80, 0x79, 0xe3, 0xbd,\n", + " 0x40, 0x6f, 0x10, 0xbe, 0x20, 0x43, 0x2e, 0xbd, 0xf0, 0x76, 0xc5, 0xbd,\n", + " 0xcc, 0xa0, 0x04, 0xbe, 0xf0, 0x69, 0xd7, 0xbe, 0xb1, 0xfe, 0x64, 0xbe,\n", + " 0x20, 0x41, 0x84, 0xbe, 0xb2, 0xc3, 0x26, 0xbe, 0xd8, 0xf4, 0x09, 0xbe,\n", + " 0x64, 0x44, 0xd1, 0x3d, 0xd5, 0xe1, 0xc8, 0xbe, 0x35, 0xbc, 0x3f, 0xbe,\n", + " 0xc0, 0x94, 0x82, 0x3d, 0xdc, 0x2b, 0xb1, 0xbd, 0x02, 0xdb, 0xbf, 0xbe,\n", + " 0xa5, 0x7f, 0x8a, 0x3e, 0x21, 0xb4, 0xa2, 0x3e, 0xcd, 0x86, 0x56, 0xbf,\n", + " 0x9c, 0x3b, 0x76, 0xbc, 0x85, 0x6d, 0x60, 0xbf, 0x86, 0x00, 0x3c, 0xbe,\n", + " 0xc1, 0x23, 0x7e, 0x3e, 0x96, 0xcd, 0x3f, 0x3e, 0x86, 0x91, 0x2d, 0x3e,\n", + " 0x55, 0xef, 0x87, 0x3e, 0x7e, 0x97, 0x03, 0xbe, 0x2a, 0xcd, 0x01, 0x3e,\n", + " 0x32, 0xc9, 0x8e, 0xbe, 0x72, 0x77, 0x3b, 0xbe, 0xe0, 0xa1, 0xbc, 0xbe,\n", + " 0x8d, 0xb7, 0xa7, 0x3e, 0x1c, 0x05, 0x95, 0xbe, 0xf7, 0x1f, 0xbb, 0x3e,\n", + " 0xc9, 0x3e, 0xd6, 0x3e, 0x80, 0x42, 0xe9, 0xbd, 0x27, 0x0c, 0xd2, 0xbe,\n", + " 0x5c, 0x32, 0x34, 0xbe, 0x14, 0xcb, 0xca, 0xbd, 0xdd, 0x3a, 0x67, 0xbe,\n", + " 0x1c, 0xbb, 0x8d, 0xbe, 0x91, 0xac, 0x5c, 0xbe, 0x52, 0x40, 0x6f, 0xbe,\n", + " 0xd7, 0x71, 0x94, 0x3e, 0x18, 0x71, 0x09, 0xbe, 0x9b, 0x29, 0xd9, 0xbe,\n", + " 0x7d, 0x66, 0xd2, 0xbe, 0x98, 0xd6, 0xb2, 0xbe, 0x00, 0xc9, 0x84, 0x3a,\n", + " 0xbc, 0xda, 0xc2, 0xbd, 0x1d, 0xc2, 0x1b, 0xbf, 0xd4, 0xdd, 0x92, 0x3e,\n", + " 0x07, 0x87, 0x6c, 0xbe, 0x40, 0xc2, 0x3b, 0xbe, 0xbd, 0xe2, 0x9c, 0x3e,\n", + " 0x0a, 0xb5, 0xa0, 0xbe, 0xe2, 0xd5, 0x9c, 0xbe, 0x3e, 0xbb, 0x7c, 0x3e,\n", + " 0x17, 0xb4, 0xcf, 0x3e, 0xd5, 0x8e, 0xc8, 0xbe, 0x7c, 0xf9, 0x5c, 0x3e,\n", + " 0x80, 0xfc, 0x0d, 0x3d, 0xc5, 0xd5, 0x8b, 0x3e, 0xf5, 0x17, 0xa2, 0x3e,\n", + " 0xc7, 0x60, 0x89, 0xbe, 0xec, 0x95, 0x87, 0x3d, 0x7a, 0xc2, 0x5d, 0xbf,\n", + " 0x77, 0x94, 0x98, 0x3e, 0x77, 0x39, 0x07, 0xbc, 0x42, 0x29, 0x00, 0x3e,\n", + " 0xaf, 0xd0, 0xa9, 0x3e, 0x31, 0x23, 0xc4, 0xbe, 0x95, 0x36, 0x5b, 0xbe,\n", + " 0xc7, 0xdc, 0x83, 0xbe, 0x1e, 0x6b, 0x47, 0x3e, 0x5b, 0x24, 0x99, 0x3e,\n", + " 0x99, 0x27, 0x54, 0x3e, 0xc8, 0x20, 0xdd, 0xbd, 0x5a, 0x86, 0x2f, 0x3e,\n", + " 0x80, 0xf0, 0x69, 0xbe, 0x44, 0xfc, 0x84, 0xbd, 0x82, 0xa0, 0x2a, 0xbe,\n", + " 0x87, 0xe6, 0x2a, 0x3e, 0xd8, 0x34, 0xae, 0x3d, 0x50, 0xbd, 0xb5, 0x3e,\n", + " 0xc4, 0x8c, 0x88, 0xbe, 0xe3, 0xbc, 0xa5, 0x3e, 0xa9, 0xda, 0x9e, 0x3e,\n", + " 0x3e, 0xb8, 0x23, 0xbe, 0x80, 0x90, 0x15, 0x3d, 0x97, 0x3f, 0xc3, 0x3e,\n", + " 0xca, 0x5c, 0x9d, 0x3e, 0x21, 0xe8, 0xe1, 0x3e, 0xc0, 0x49, 0x01, 0xbc,\n", + " 0x00, 0x0b, 0x88, 0xbd, 0x3f, 0xf7, 0xca, 0x3c, 0xfb, 0x5a, 0xb1, 0x3e,\n", + " 0x60, 0xd2, 0x0d, 0x3c, 0xce, 0x23, 0x78, 0xbf, 0x8f, 0x4f, 0xb9, 0xbe,\n", + " 0x69, 0x6a, 0x34, 0xbf, 0x4b, 0x5e, 0xa9, 0x3e, 0x64, 0x8c, 0xd9, 0x3e,\n", + " 0x52, 0x77, 0x36, 0x3e, 0xeb, 0xaf, 0xbe, 0x3e, 0x40, 0xbe, 0x36, 0x3c,\n", + " 0x08, 0x65, 0x3b, 0xbd, 0x55, 0xe0, 0x66, 0xbd, 0xd2, 0xe8, 0x9b, 0xbe,\n", + " 0x86, 0xe3, 0x09, 0xbe, 0x93, 0x3d, 0xdd, 0x3e, 0x0f, 0x66, 0x18, 0x3f,\n", + " 0x18, 0x05, 0x33, 0xbd, 0xde, 0x15, 0xd7, 0xbe, 0xaa, 0xcf, 0x49, 0xbe,\n", + " 0xa2, 0xa5, 0x64, 0x3e, 0xe6, 0x9c, 0x42, 0xbe, 0x54, 0x42, 0xcc, 0x3d,\n", + " 0xa0, 0xbd, 0x9d, 0xbe, 0xc2, 0x69, 0x48, 0x3e, 0x5b, 0x8b, 0xa2, 0xbe,\n", + " 0xc0, 0x13, 0x87, 0x3d, 0x36, 0xfd, 0x69, 0x3e, 0x05, 0x86, 0x40, 0xbe,\n", + " 0x1e, 0x7a, 0xce, 0xbe, 0x46, 0x13, 0xa7, 0xbe, 0x68, 0x52, 0x86, 0xbe,\n", + " 0x04, 0x9e, 0x86, 0xbd, 0x8c, 0x54, 0xc1, 0x3d, 0xe0, 0x3b, 0xad, 0x3c,\n", + " 0x42, 0x67, 0x85, 0xbd, 0xea, 0x97, 0x42, 0x3e, 0x6e, 0x13, 0x3b, 0xbf,\n", + " 0x56, 0x5b, 0x16, 0x3e, 0xaa, 0xab, 0xdf, 0x3e, 0xc8, 0x41, 0x36, 0x3d,\n", + " 0x24, 0x2d, 0x47, 0xbe, 0x77, 0xa5, 0xae, 0x3e, 0xc0, 0xc2, 0x5b, 0x3c,\n", + " 0xac, 0xac, 0x4e, 0x3e, 0x99, 0xec, 0x13, 0xbe, 0xf2, 0xab, 0x73, 0x3e,\n", + " 0xaa, 0xa1, 0x48, 0xbe, 0xe8, 0xd3, 0x01, 0xbe, 0x60, 0xb7, 0xc7, 0xbd,\n", + " 0x64, 0x72, 0xd3, 0x3d, 0x83, 0xd3, 0x99, 0x3e, 0x0c, 0x76, 0x34, 0xbe,\n", + " 0x42, 0xda, 0x0d, 0x3e, 0xfb, 0x47, 0x9a, 0x3e, 0x8b, 0xdc, 0x92, 0xbe,\n", + " 0x56, 0x7f, 0x6b, 0x3e, 0x04, 0xd4, 0x88, 0xbd, 0x11, 0x9e, 0x80, 0x3e,\n", + " 0x3c, 0x89, 0xff, 0x3d, 0xb3, 0x3e, 0x88, 0x3e, 0xf7, 0xf0, 0x88, 0x3e,\n", + " 0x28, 0xfb, 0xc9, 0xbe, 0x53, 0x3e, 0xcf, 0x3e, 0xac, 0x75, 0xdc, 0xbe,\n", + " 0xdd, 0xca, 0xd7, 0x3e, 0x01, 0x58, 0xa7, 0x3e, 0x29, 0xb8, 0x13, 0xbf,\n", + " 0x76, 0x81, 0x12, 0xbc, 0x28, 0x8b, 0x16, 0xbf, 0x0e, 0xec, 0x0e, 0x3e,\n", + " 0x40, 0x0a, 0xdb, 0xbd, 0x98, 0xec, 0xbf, 0xbd, 0x32, 0x55, 0x0c, 0xbe,\n", + " 0xfb, 0xf9, 0xc9, 0x3e, 0x83, 0x4a, 0x6d, 0xbe, 0x76, 0x59, 0xe2, 0xbe,\n", + " 0x54, 0x7d, 0x9f, 0xbb, 0x9d, 0xe8, 0x95, 0x3e, 0x5c, 0xd3, 0xd0, 0x3d,\n", + " 0x19, 0x8a, 0xb0, 0x3e, 0xde, 0x6f, 0x2e, 0xbe, 0xd0, 0x16, 0x83, 0x3d,\n", + " 0x9c, 0x7d, 0x11, 0xbf, 0x2b, 0xcc, 0x25, 0x3c, 0x2a, 0xa5, 0x27, 0xbe,\n", + " 0x22, 0x14, 0xc7, 0xbe, 0x5e, 0x7a, 0xac, 0x3e, 0x4e, 0x41, 0x94, 0xbe,\n", + " 0x5a, 0x68, 0x7b, 0x3e, 0x86, 0xfd, 0x4e, 0x3e, 0xa2, 0x56, 0x6a, 0xbe,\n", + " 0xca, 0xfe, 0x81, 0xbe, 0x43, 0xc3, 0xb1, 0xbd, 0xc5, 0xb8, 0xa7, 0x3e,\n", + " 0x55, 0x23, 0xcd, 0x3e, 0xaf, 0x2e, 0x76, 0x3e, 0x69, 0xa8, 0x90, 0xbe,\n", + " 0x0d, 0xba, 0xb9, 0x3e, 0x66, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00,\n", + " 0x40, 0x00, 0x00, 0x00, 0x53, 0xd6, 0xe2, 0x3d, 0x66, 0xb6, 0xcc, 0x3e,\n", + " 0x03, 0xe7, 0xf6, 0x3e, 0xe0, 0x28, 0x10, 0xbf, 0x00, 0x00, 0x00, 0x00,\n", + " 0x3e, 0x3d, 0xb0, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x62, 0xf0, 0x77, 0x3e,\n", + " 0xa6, 0x9d, 0xa4, 0x3e, 0x3a, 0x4b, 0xf3, 0xbe, 0x71, 0x9e, 0xa7, 0x3e,\n", + " 0x00, 0x00, 0x00, 0x00, 0x34, 0x39, 0xa2, 0x3e, 0x00, 0x00, 0x00, 0x00,\n", + " 0xcc, 0x9c, 0x4a, 0x3e, 0xab, 0x40, 0xa3, 0x3e, 0xb2, 0xff, 0xff, 0xff,\n", + " 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xb3, 0x71, 0x67, 0x3f,\n", + " 0x9a, 0x7a, 0x95, 0xbf, 0xe1, 0x48, 0xe8, 0xbe, 0x8a, 0x72, 0x96, 0x3e,\n", + " 0x00, 0xd2, 0xd3, 0xbb, 0x1a, 0xc5, 0xd7, 0x3f, 0xac, 0x7e, 0xc8, 0xbe,\n", + " 0x90, 0xa7, 0x95, 0xbe, 0x3b, 0xd7, 0xdc, 0xbe, 0x41, 0xa8, 0x16, 0x3f,\n", + " 0x50, 0x5b, 0xcb, 0x3f, 0x52, 0xb9, 0xed, 0xbe, 0x2e, 0xa7, 0xc6, 0xbe,\n", + " 0xaf, 0x0f, 0x14, 0xbf, 0xb3, 0xda, 0x59, 0x3f, 0x02, 0xec, 0xd7, 0xbe,\n", + " 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x66, 0x11, 0x1f, 0xbf,\n", + " 0xb8, 0xfb, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x54, 0x4f, 0x43, 0x4f,\n", + " 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00,\n", + " 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x14, 0x00,\n", + " 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00,\n", + " 0xf0, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00,\n", + " 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xce, 0xff, 0xff, 0xff,\n", + " 0x00, 0x00, 0x00, 0x08, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x1c, 0xfc, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00,\n", + " 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,\n", + " 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00,\n", + " 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00,\n", + " 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x00, 0x00, 0x00,\n", + " 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xba, 0xff, 0xff, 0xff,\n", + " 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,\n", + " 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,\n", + " 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x00, 0x00,\n", + " 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00,\n", + " 0x00, 0x00, 0x00, 0x08, 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,\n", + " 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00,\n", + " 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0x0a, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x00,\n", + " 0x40, 0x02, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00,\n", + " 0x48, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00,\n", + " 0x50, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x26, 0xfd, 0xff, 0xff,\n", + " 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x18, 0xfd, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00,\n", + " 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31,\n", + " 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x34, 0x2f, 0x4d, 0x61, 0x74,\n", + " 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00,\n", + " 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6e, 0xfd, 0xff, 0xff,\n", + " 0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x60, 0xfd, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00,\n", + " 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31,\n", + " 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x34, 0x2f, 0x4d, 0x61, 0x74,\n", + " 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69,\n", + " 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73,\n", + " 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,\n", + " 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xce, 0xfd, 0xff, 0xff,\n", + " 0x34, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xff, 0xff, 0x19, 0x00, 0x00, 0x00,\n", + " 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31,\n", + " 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x52, 0x65, 0x6c,\n", + " 0x75, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0x10, 0x00, 0x00, 0x00, 0x12, 0xfe, 0xff, 0xff, 0x3c, 0x00, 0x00, 0x00,\n", + " 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", + " 0x04, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75,\n", + " 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e,\n", + " 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f,\n", + " 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0x10, 0x00, 0x00, 0x00, 0x5a, 0xfe, 0xff, 0xff, 0x50, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", + " 0x4c, 0xfe, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75,\n", + " 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e,\n", + " 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f,\n", + " 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65,\n", + " 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65,\n", + " 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", + " 0x10, 0x00, 0x00, 0x00, 0xba, 0xfe, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00,\n", + " 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,\n", + " 0xac, 0xfe, 0xff, 0xff, 0x19, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75,\n", + " 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e,\n", + " 0x73, 0x65, 0x5f, 0x32, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, 0x00,\n", + " 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", + " 0xfe, 0xfe, 0xff, 0xff, 0x3c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,\n", + " 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf0, 0xfe, 0xff, 0xff,\n", + " 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69,\n", + " 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32,\n", + " 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73,\n", + " 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", + " 0x46, 0xff, 0xff, 0xff, 0x50, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,\n", + " 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x38, 0xff, 0xff, 0xff,\n", + " 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69,\n", + " 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32,\n", + " 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64,\n", + " 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74,\n", + " 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00,\n", + " 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0xa6, 0xff, 0xff, 0xff, 0x48, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,\n", + " 0x2c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00,\n", + " 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", + " 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x43,\n", + " 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,\n", + " 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, 0x5f, 0x69, 0x6e, 0x70, 0x75,\n", + " 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x04, 0x00,\n", + " 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00,\n", + " 0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", + " 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,\n", + " 0x08, 0x00, 0x00, 0x00, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79,\n", + " 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,\n", + " 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\n", + " 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00,\n", + " 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x00\n", + "};\n", + "unsigned int sine_model_quantized_tflite_len = 2640;\n" + ], + "name": "stdout" + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1sqrhBLXwILt", + "colab_type": "text" + }, + "source": [ + "We can either copy and paste this output into our project's source code, or download the file using the collapsible menu on the left hand side of this Colab.\n", + "\n" + ] + } + ] +} \ No newline at end of file diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/Makefile.inc b/tensorflow/lite/micro/examples/hello_world/disco_f746ng/Makefile.inc similarity index 100% rename from tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/Makefile.inc rename to tensorflow/lite/micro/examples/hello_world/disco_f746ng/Makefile.inc diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/constants.cc b/tensorflow/lite/micro/examples/hello_world/disco_f746ng/constants.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/constants.cc rename to tensorflow/lite/micro/examples/hello_world/disco_f746ng/constants.cc index 09d464bbfdd..8d6d07c1bfd 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/constants.cc +++ b/tensorflow/lite/micro/examples/hello_world/disco_f746ng/constants.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/constants.h" +#include "tensorflow/lite/micro/examples/hello_world/constants.h" // A larger number than the default to make the animation smoother const int kInferencesPerCycle = 70; diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/output_handler.cc b/tensorflow/lite/micro/examples/hello_world/disco_f746ng/output_handler.cc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/output_handler.cc rename to tensorflow/lite/micro/examples/hello_world/disco_f746ng/output_handler.cc index 3f642f8d6cf..ea7660467f7 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/disco_f746ng/output_handler.cc +++ b/tensorflow/lite/micro/examples/hello_world/disco_f746ng/output_handler.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h" +#include "tensorflow/lite/micro/examples/hello_world/output_handler.h" #include "LCD_DISCO_F746NG.h" -#include "tensorflow/lite/experimental/micro/examples/hello_world/constants.h" +#include "tensorflow/lite/micro/examples/hello_world/constants.h" // The LCD driver LCD_DISCO_F746NG lcd; @@ -26,6 +26,8 @@ const uint32_t background_color = 0xFFF4B400; // Yellow const uint32_t foreground_color = 0xFFDB4437; // Red // The size of the dot we'll draw const int dot_radius = 10; +// Track whether the function has run at least once +bool initialized = false; // Size of the drawable area int width; int height; @@ -37,11 +39,8 @@ int x_increment; // Animates a dot across the screen to represent the current x and y values void HandleOutput(tflite::ErrorReporter* error_reporter, float x_value, float y_value) { - // Track whether the function has run at least once - static bool is_initialized = false; - // Do this only once - if (!is_initialized) { + if (!initialized) { // Set the background and foreground colors lcd.Clear(background_color); lcd.SetTextColor(foreground_color); @@ -52,9 +51,12 @@ void HandleOutput(tflite::ErrorReporter* error_reporter, float x_value, midpoint = height / 2; // Calculate fractional pixels per unit of x_value x_increment = static_cast(width) / kXrange; - is_initialized = true; + initialized = true; } + // Log the current X and Y values + error_reporter->Report("x_value: %f, y_value: %f\n", x_value, y_value); + // Clear the previous drawing lcd.Clear(background_color); @@ -75,7 +77,4 @@ void HandleOutput(tflite::ErrorReporter* error_reporter, float x_value, // Draw the dot lcd.FillCircle(x_pos, y_pos, dot_radius); - - // Log the current X and Y values - error_reporter->Report("x_value: %f, y_value: %f\n", x_value, y_value); } diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/esp/main.cc b/tensorflow/lite/micro/examples/hello_world/esp/main.cc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/hello_world/esp/main.cc rename to tensorflow/lite/micro/examples/hello_world/esp/main.cc index 1d24a11b3f4..b68b189481f 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/esp/main.cc +++ b/tensorflow/lite/micro/examples/hello_world/esp/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h" +#include "tensorflow/lite/micro/examples/hello_world/main_functions.h" extern "C" void app_main(void) { setup(); diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/hello_world_test.cc b/tensorflow/lite/micro/examples/hello_world/hello_world_test.cc similarity index 86% rename from tensorflow/lite/experimental/micro/examples/hello_world/hello_world_test.cc rename to tensorflow/lite/micro/examples/hello_world/hello_world_test.cc index 9f8ac4de086..9cc30a45915 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/hello_world_test.cc +++ b/tensorflow/lite/micro/examples/hello_world/hello_world_test.cc @@ -14,11 +14,11 @@ limitations under the License. ==============================================================================*/ // #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/hello_world/sine_model_data.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" @@ -44,7 +44,7 @@ TF_LITE_MICRO_TEST(LoadModelAndPerformInference) { // Create an area of memory to use for input, output, and intermediate arrays. // Finding the minimum value for your model may require some trial and error. - const int tensor_arena_size = 3 * 1024; + const int tensor_arena_size = 2 * 1024; uint8_t tensor_arena[tensor_arena_size]; // Build an interpreter to run the model with @@ -88,8 +88,8 @@ TF_LITE_MICRO_TEST(LoadModelAndPerformInference) { // Obtain the output value from the tensor float value = output->data.f[0]; - // Check that the output value is within 0.07 of the expected value - TF_LITE_MICRO_EXPECT_NEAR(0., value, 0.07); + // Check that the output value is within 0.05 of the expected value + TF_LITE_MICRO_EXPECT_NEAR(0., value, 0.05); // Run inference on several more values and confirm the expected outputs input->data.f[0] = 1.; @@ -97,21 +97,21 @@ TF_LITE_MICRO_TEST(LoadModelAndPerformInference) { TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status); value = output->data.f[0]; - TF_LITE_MICRO_EXPECT_NEAR(0.841, value, 0.07); + TF_LITE_MICRO_EXPECT_NEAR(0.841, value, 0.05); input->data.f[0] = 3.; invoke_status = interpreter.Invoke(); TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status); value = output->data.f[0]; - TF_LITE_MICRO_EXPECT_NEAR(0.141, value, 0.07); + TF_LITE_MICRO_EXPECT_NEAR(0.141, value, 0.05); input->data.f[0] = 5.; invoke_status = interpreter.Invoke(); TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, invoke_status); value = output->data.f[0]; - TF_LITE_MICRO_EXPECT_NEAR(-0.959, value, 0.07); + TF_LITE_MICRO_EXPECT_NEAR(-0.959, value, 0.05); } TF_LITE_MICRO_TESTS_END diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/images/STM32F746.gif b/tensorflow/lite/micro/examples/hello_world/images/STM32F746.gif similarity index 100% rename from tensorflow/lite/experimental/micro/examples/hello_world/images/STM32F746.gif rename to tensorflow/lite/micro/examples/hello_world/images/STM32F746.gif diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/images/arduino_mkrzero.gif b/tensorflow/lite/micro/examples/hello_world/images/arduino_mkrzero.gif similarity index 100% rename from tensorflow/lite/experimental/micro/examples/hello_world/images/arduino_mkrzero.gif rename to tensorflow/lite/micro/examples/hello_world/images/arduino_mkrzero.gif diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/images/sparkfun_edge.gif b/tensorflow/lite/micro/examples/hello_world/images/sparkfun_edge.gif similarity index 100% rename from tensorflow/lite/experimental/micro/examples/hello_world/images/sparkfun_edge.gif rename to tensorflow/lite/micro/examples/hello_world/images/sparkfun_edge.gif diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/main.cc b/tensorflow/lite/micro/examples/hello_world/main.cc similarity index 92% rename from tensorflow/lite/experimental/micro/examples/magic_wand/main.cc rename to tensorflow/lite/micro/examples/hello_world/main.cc index 468eeb5591c..bdf7942abb5 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/main.cc +++ b/tensorflow/lite/micro/examples/hello_world/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.h" +#include "tensorflow/lite/micro/examples/hello_world/main_functions.h" // This is the default main used on systems that have the standard C entry // point. Other devices (for example FreeRTOS or ESP32) that have different diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/main_functions.cc b/tensorflow/lite/micro/examples/hello_world/main_functions.cc similarity index 88% rename from tensorflow/lite/experimental/micro/examples/hello_world/main_functions.cc rename to tensorflow/lite/micro/examples/hello_world/main_functions.cc index 8f4678b1ca1..d9d51624e96 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/main_functions.cc +++ b/tensorflow/lite/micro/examples/hello_world/main_functions.cc @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h" +#include "tensorflow/lite/micro/examples/hello_world/main_functions.h" -#include "tensorflow/lite/experimental/micro/examples/hello_world/constants.h" -#include "tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h" -#include "tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/examples/hello_world/constants.h" +#include "tensorflow/lite/micro/examples/hello_world/output_handler.h" +#include "tensorflow/lite/micro/examples/hello_world/sine_model_data.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.h b/tensorflow/lite/micro/examples/hello_world/main_functions.h similarity index 80% rename from tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.h rename to tensorflow/lite/micro/examples/hello_world/main_functions.h index d445f7b4633..e595cd87c8b 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.h +++ b/tensorflow/lite/micro/examples/hello_world/main_functions.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_MAIN_FUNCTIONS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_MAIN_FUNCTIONS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_MAIN_FUNCTIONS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_MAIN_FUNCTIONS_H_ // Initializes all data needed for the example. The name is important, and needs // to be setup() for Arduino compatibility. @@ -25,4 +25,4 @@ void setup(); // compatibility. void loop(); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_MAIN_FUNCTIONS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_MAIN_FUNCTIONS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/output_handler.cc b/tensorflow/lite/micro/examples/hello_world/output_handler.cc similarity index 91% rename from tensorflow/lite/experimental/micro/examples/hello_world/output_handler.cc rename to tensorflow/lite/micro/examples/hello_world/output_handler.cc index 63aee55c1af..466653c6534 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/output_handler.cc +++ b/tensorflow/lite/micro/examples/hello_world/output_handler.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h" +#include "tensorflow/lite/micro/examples/hello_world/output_handler.h" void HandleOutput(tflite::ErrorReporter* error_reporter, float x_value, float y_value) { diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h b/tensorflow/lite/micro/examples/hello_world/output_handler.h similarity index 73% rename from tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h rename to tensorflow/lite/micro/examples/hello_world/output_handler.h index 3218648c855..14e9d70760c 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h +++ b/tensorflow/lite/micro/examples/hello_world/output_handler.h @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_OUTPUT_HANDLER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_OUTPUT_HANDLER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_OUTPUT_HANDLER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_OUTPUT_HANDLER_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // Called by the main loop to produce some output based on the x and y values void HandleOutput(tflite::ErrorReporter* error_reporter, float x_value, float y_value); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_OUTPUT_HANDLER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_OUTPUT_HANDLER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/output_handler_test.cc b/tensorflow/lite/micro/examples/hello_world/output_handler_test.cc similarity index 83% rename from tensorflow/lite/experimental/micro/examples/hello_world/output_handler_test.cc rename to tensorflow/lite/micro/examples/hello_world/output_handler_test.cc index 0259370eda7..cbed83e1c75 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/output_handler_test.cc +++ b/tensorflow/lite/micro/examples/hello_world/output_handler_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h" +#include "tensorflow/lite/micro/examples/hello_world/output_handler.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/micro/examples/hello_world/sine_model_data.cc b/tensorflow/lite/micro/examples/hello_world/sine_model_data.cc new file mode 100644 index 00000000000..7252479fecd --- /dev/null +++ b/tensorflow/lite/micro/examples/hello_world/sine_model_data.cc @@ -0,0 +1,255 @@ +/* Copyright 2019 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. +==============================================================================*/ + +// Automatically created from a TensorFlow Lite flatbuffer using the command: +// xxd -i sine_model.tflite > sine_model_data.cc +// See the README for a full description of the creation process. + +#include "tensorflow/lite/micro/examples/hello_world/sine_model_data.h" + +// We need to keep the data array aligned on some architectures. +#ifdef __has_attribute +#define HAVE_ATTRIBUTE(x) __has_attribute(x) +#else +#define HAVE_ATTRIBUTE(x) 0 +#endif +#if HAVE_ATTRIBUTE(aligned) || (defined(__GNUC__) && !defined(__clang__)) +#define DATA_ALIGN_ATTRIBUTE __attribute__((aligned(4))) +#else +#define DATA_ALIGN_ATTRIBUTE +#endif + +const unsigned char g_sine_model_data[] DATA_ALIGN_ATTRIBUTE = { + 0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00, + 0x18, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x10, 0x0a, 0x00, 0x00, + 0xb8, 0x05, 0x00, 0x00, 0xa0, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x90, 0x05, 0x00, 0x00, 0x7c, 0x05, 0x00, 0x00, + 0x24, 0x05, 0x00, 0x00, 0xd4, 0x04, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, + 0x74, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x54, 0xf6, 0xff, 0xff, 0x58, 0xf6, 0xff, 0xff, 0x5c, 0xf6, 0xff, 0xff, + 0x60, 0xf6, 0xff, 0xff, 0xc2, 0xfa, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x7c, 0x19, 0xa7, 0x3e, 0x99, 0x81, 0xb9, 0x3e, + 0x56, 0x8b, 0x9f, 0x3e, 0x88, 0xd8, 0x12, 0xbf, 0x74, 0x10, 0x56, 0x3e, + 0xfe, 0xc6, 0xdf, 0xbe, 0xf2, 0x10, 0x5a, 0xbe, 0xf0, 0xe2, 0x0a, 0xbe, + 0x10, 0x5a, 0x98, 0xbe, 0xb9, 0x36, 0xce, 0x3d, 0x8f, 0x7f, 0x87, 0x3e, + 0x2c, 0xb1, 0xfd, 0xbd, 0xe6, 0xa6, 0x8a, 0xbe, 0xa5, 0x3e, 0xda, 0x3e, + 0x50, 0x34, 0xed, 0xbd, 0x90, 0x91, 0x69, 0xbe, 0x0e, 0xfb, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x67, 0x41, 0x48, 0xbf, + 0x24, 0xcd, 0xa0, 0xbe, 0xb7, 0x92, 0x0c, 0xbf, 0x00, 0x00, 0x00, 0x00, + 0x98, 0xfe, 0x3c, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x17, 0x9a, 0xbe, + 0x41, 0xcb, 0xb6, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0xd6, 0x1e, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x5a, 0xfb, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x4b, 0x98, 0xdd, 0xbd, 0x40, 0x6b, 0xcb, 0xbe, 0x36, 0x0c, 0xd4, 0x3c, + 0xbd, 0x44, 0xb5, 0x3e, 0x95, 0x70, 0xe3, 0x3e, 0xe7, 0xac, 0x86, 0x3e, + 0x00, 0xc4, 0x4e, 0x3d, 0x7e, 0xa6, 0x1d, 0x3e, 0xbd, 0x87, 0xbb, 0x3e, + 0xb4, 0xb8, 0x09, 0xbf, 0xa1, 0x1f, 0xf8, 0xbe, 0x8d, 0x90, 0xdd, 0x3e, + 0xde, 0xfa, 0x6f, 0xbe, 0xb2, 0x75, 0xe4, 0x3d, 0x6e, 0xfe, 0x36, 0x3e, + 0x20, 0x18, 0xc2, 0xbe, 0x39, 0xc7, 0xfb, 0xbe, 0xfe, 0xa4, 0x30, 0xbe, + 0xf7, 0x91, 0xde, 0xbe, 0xde, 0xab, 0x24, 0x3e, 0xfb, 0xbb, 0xce, 0x3e, + 0xeb, 0x23, 0x80, 0xbe, 0x7b, 0x58, 0x73, 0xbe, 0x9a, 0x2e, 0x03, 0x3e, + 0x10, 0x42, 0xa9, 0xbc, 0x10, 0x12, 0x64, 0xbd, 0xe3, 0x8d, 0x0c, 0x3d, + 0x9e, 0x48, 0x97, 0xbe, 0x34, 0x51, 0xd4, 0xbe, 0x02, 0x3b, 0x0d, 0x3e, + 0x62, 0x67, 0x89, 0xbe, 0x74, 0xdf, 0xa2, 0x3d, 0xf3, 0x25, 0xb3, 0xbe, + 0xef, 0x34, 0x7b, 0x3d, 0x61, 0x70, 0xe3, 0x3d, 0xba, 0x76, 0xc0, 0xbe, + 0x7d, 0xe9, 0xa7, 0x3e, 0xc3, 0xab, 0xd0, 0xbe, 0xcf, 0x7c, 0xdb, 0xbe, + 0x70, 0x27, 0x9a, 0xbe, 0x98, 0xf5, 0x3c, 0xbd, 0xff, 0x4b, 0x4b, 0x3e, + 0x7e, 0xa0, 0xf8, 0xbd, 0xd4, 0x6e, 0x86, 0x3d, 0x00, 0x4a, 0x07, 0x3a, + 0x4c, 0x24, 0x61, 0xbe, 0x54, 0x68, 0xf7, 0xbd, 0x02, 0x3f, 0x77, 0xbe, + 0x23, 0x79, 0xb3, 0x3e, 0x1c, 0x83, 0xad, 0xbd, 0xc8, 0x92, 0x8d, 0x3e, + 0xa8, 0xf3, 0x15, 0xbd, 0xe6, 0x4d, 0x6c, 0x3d, 0xac, 0xe7, 0x98, 0xbe, + 0x81, 0xec, 0xbd, 0x3e, 0xe2, 0x55, 0x73, 0x3e, 0xc1, 0x77, 0xc7, 0x3e, + 0x6e, 0x1b, 0x5e, 0x3d, 0x27, 0x78, 0x02, 0x3f, 0xd4, 0x21, 0x90, 0x3d, + 0x52, 0xdc, 0x1f, 0x3e, 0xbf, 0xda, 0x88, 0x3e, 0x80, 0x79, 0xe3, 0xbd, + 0x40, 0x6f, 0x10, 0xbe, 0x20, 0x43, 0x2e, 0xbd, 0xf0, 0x76, 0xc5, 0xbd, + 0xcc, 0xa0, 0x04, 0xbe, 0xf0, 0x69, 0xd7, 0xbe, 0xb1, 0xfe, 0x64, 0xbe, + 0x20, 0x41, 0x84, 0xbe, 0xb2, 0xc3, 0x26, 0xbe, 0xd8, 0xf4, 0x09, 0xbe, + 0x64, 0x44, 0xd1, 0x3d, 0xd5, 0xe1, 0xc8, 0xbe, 0x35, 0xbc, 0x3f, 0xbe, + 0xc0, 0x94, 0x82, 0x3d, 0xdc, 0x2b, 0xb1, 0xbd, 0x02, 0xdb, 0xbf, 0xbe, + 0xa5, 0x7f, 0x8a, 0x3e, 0x21, 0xb4, 0xa2, 0x3e, 0xcd, 0x86, 0x56, 0xbf, + 0x9c, 0x3b, 0x76, 0xbc, 0x85, 0x6d, 0x60, 0xbf, 0x86, 0x00, 0x3c, 0xbe, + 0xc1, 0x23, 0x7e, 0x3e, 0x96, 0xcd, 0x3f, 0x3e, 0x86, 0x91, 0x2d, 0x3e, + 0x55, 0xef, 0x87, 0x3e, 0x7e, 0x97, 0x03, 0xbe, 0x2a, 0xcd, 0x01, 0x3e, + 0x32, 0xc9, 0x8e, 0xbe, 0x72, 0x77, 0x3b, 0xbe, 0xe0, 0xa1, 0xbc, 0xbe, + 0x8d, 0xb7, 0xa7, 0x3e, 0x1c, 0x05, 0x95, 0xbe, 0xf7, 0x1f, 0xbb, 0x3e, + 0xc9, 0x3e, 0xd6, 0x3e, 0x80, 0x42, 0xe9, 0xbd, 0x27, 0x0c, 0xd2, 0xbe, + 0x5c, 0x32, 0x34, 0xbe, 0x14, 0xcb, 0xca, 0xbd, 0xdd, 0x3a, 0x67, 0xbe, + 0x1c, 0xbb, 0x8d, 0xbe, 0x91, 0xac, 0x5c, 0xbe, 0x52, 0x40, 0x6f, 0xbe, + 0xd7, 0x71, 0x94, 0x3e, 0x18, 0x71, 0x09, 0xbe, 0x9b, 0x29, 0xd9, 0xbe, + 0x7d, 0x66, 0xd2, 0xbe, 0x98, 0xd6, 0xb2, 0xbe, 0x00, 0xc9, 0x84, 0x3a, + 0xbc, 0xda, 0xc2, 0xbd, 0x1d, 0xc2, 0x1b, 0xbf, 0xd4, 0xdd, 0x92, 0x3e, + 0x07, 0x87, 0x6c, 0xbe, 0x40, 0xc2, 0x3b, 0xbe, 0xbd, 0xe2, 0x9c, 0x3e, + 0x0a, 0xb5, 0xa0, 0xbe, 0xe2, 0xd5, 0x9c, 0xbe, 0x3e, 0xbb, 0x7c, 0x3e, + 0x17, 0xb4, 0xcf, 0x3e, 0xd5, 0x8e, 0xc8, 0xbe, 0x7c, 0xf9, 0x5c, 0x3e, + 0x80, 0xfc, 0x0d, 0x3d, 0xc5, 0xd5, 0x8b, 0x3e, 0xf5, 0x17, 0xa2, 0x3e, + 0xc7, 0x60, 0x89, 0xbe, 0xec, 0x95, 0x87, 0x3d, 0x7a, 0xc2, 0x5d, 0xbf, + 0x77, 0x94, 0x98, 0x3e, 0x77, 0x39, 0x07, 0xbc, 0x42, 0x29, 0x00, 0x3e, + 0xaf, 0xd0, 0xa9, 0x3e, 0x31, 0x23, 0xc4, 0xbe, 0x95, 0x36, 0x5b, 0xbe, + 0xc7, 0xdc, 0x83, 0xbe, 0x1e, 0x6b, 0x47, 0x3e, 0x5b, 0x24, 0x99, 0x3e, + 0x99, 0x27, 0x54, 0x3e, 0xc8, 0x20, 0xdd, 0xbd, 0x5a, 0x86, 0x2f, 0x3e, + 0x80, 0xf0, 0x69, 0xbe, 0x44, 0xfc, 0x84, 0xbd, 0x82, 0xa0, 0x2a, 0xbe, + 0x87, 0xe6, 0x2a, 0x3e, 0xd8, 0x34, 0xae, 0x3d, 0x50, 0xbd, 0xb5, 0x3e, + 0xc4, 0x8c, 0x88, 0xbe, 0xe3, 0xbc, 0xa5, 0x3e, 0xa9, 0xda, 0x9e, 0x3e, + 0x3e, 0xb8, 0x23, 0xbe, 0x80, 0x90, 0x15, 0x3d, 0x97, 0x3f, 0xc3, 0x3e, + 0xca, 0x5c, 0x9d, 0x3e, 0x21, 0xe8, 0xe1, 0x3e, 0xc0, 0x49, 0x01, 0xbc, + 0x00, 0x0b, 0x88, 0xbd, 0x3f, 0xf7, 0xca, 0x3c, 0xfb, 0x5a, 0xb1, 0x3e, + 0x60, 0xd2, 0x0d, 0x3c, 0xce, 0x23, 0x78, 0xbf, 0x8f, 0x4f, 0xb9, 0xbe, + 0x69, 0x6a, 0x34, 0xbf, 0x4b, 0x5e, 0xa9, 0x3e, 0x64, 0x8c, 0xd9, 0x3e, + 0x52, 0x77, 0x36, 0x3e, 0xeb, 0xaf, 0xbe, 0x3e, 0x40, 0xbe, 0x36, 0x3c, + 0x08, 0x65, 0x3b, 0xbd, 0x55, 0xe0, 0x66, 0xbd, 0xd2, 0xe8, 0x9b, 0xbe, + 0x86, 0xe3, 0x09, 0xbe, 0x93, 0x3d, 0xdd, 0x3e, 0x0f, 0x66, 0x18, 0x3f, + 0x18, 0x05, 0x33, 0xbd, 0xde, 0x15, 0xd7, 0xbe, 0xaa, 0xcf, 0x49, 0xbe, + 0xa2, 0xa5, 0x64, 0x3e, 0xe6, 0x9c, 0x42, 0xbe, 0x54, 0x42, 0xcc, 0x3d, + 0xa0, 0xbd, 0x9d, 0xbe, 0xc2, 0x69, 0x48, 0x3e, 0x5b, 0x8b, 0xa2, 0xbe, + 0xc0, 0x13, 0x87, 0x3d, 0x36, 0xfd, 0x69, 0x3e, 0x05, 0x86, 0x40, 0xbe, + 0x1e, 0x7a, 0xce, 0xbe, 0x46, 0x13, 0xa7, 0xbe, 0x68, 0x52, 0x86, 0xbe, + 0x04, 0x9e, 0x86, 0xbd, 0x8c, 0x54, 0xc1, 0x3d, 0xe0, 0x3b, 0xad, 0x3c, + 0x42, 0x67, 0x85, 0xbd, 0xea, 0x97, 0x42, 0x3e, 0x6e, 0x13, 0x3b, 0xbf, + 0x56, 0x5b, 0x16, 0x3e, 0xaa, 0xab, 0xdf, 0x3e, 0xc8, 0x41, 0x36, 0x3d, + 0x24, 0x2d, 0x47, 0xbe, 0x77, 0xa5, 0xae, 0x3e, 0xc0, 0xc2, 0x5b, 0x3c, + 0xac, 0xac, 0x4e, 0x3e, 0x99, 0xec, 0x13, 0xbe, 0xf2, 0xab, 0x73, 0x3e, + 0xaa, 0xa1, 0x48, 0xbe, 0xe8, 0xd3, 0x01, 0xbe, 0x60, 0xb7, 0xc7, 0xbd, + 0x64, 0x72, 0xd3, 0x3d, 0x83, 0xd3, 0x99, 0x3e, 0x0c, 0x76, 0x34, 0xbe, + 0x42, 0xda, 0x0d, 0x3e, 0xfb, 0x47, 0x9a, 0x3e, 0x8b, 0xdc, 0x92, 0xbe, + 0x56, 0x7f, 0x6b, 0x3e, 0x04, 0xd4, 0x88, 0xbd, 0x11, 0x9e, 0x80, 0x3e, + 0x3c, 0x89, 0xff, 0x3d, 0xb3, 0x3e, 0x88, 0x3e, 0xf7, 0xf0, 0x88, 0x3e, + 0x28, 0xfb, 0xc9, 0xbe, 0x53, 0x3e, 0xcf, 0x3e, 0xac, 0x75, 0xdc, 0xbe, + 0xdd, 0xca, 0xd7, 0x3e, 0x01, 0x58, 0xa7, 0x3e, 0x29, 0xb8, 0x13, 0xbf, + 0x76, 0x81, 0x12, 0xbc, 0x28, 0x8b, 0x16, 0xbf, 0x0e, 0xec, 0x0e, 0x3e, + 0x40, 0x0a, 0xdb, 0xbd, 0x98, 0xec, 0xbf, 0xbd, 0x32, 0x55, 0x0c, 0xbe, + 0xfb, 0xf9, 0xc9, 0x3e, 0x83, 0x4a, 0x6d, 0xbe, 0x76, 0x59, 0xe2, 0xbe, + 0x54, 0x7d, 0x9f, 0xbb, 0x9d, 0xe8, 0x95, 0x3e, 0x5c, 0xd3, 0xd0, 0x3d, + 0x19, 0x8a, 0xb0, 0x3e, 0xde, 0x6f, 0x2e, 0xbe, 0xd0, 0x16, 0x83, 0x3d, + 0x9c, 0x7d, 0x11, 0xbf, 0x2b, 0xcc, 0x25, 0x3c, 0x2a, 0xa5, 0x27, 0xbe, + 0x22, 0x14, 0xc7, 0xbe, 0x5e, 0x7a, 0xac, 0x3e, 0x4e, 0x41, 0x94, 0xbe, + 0x5a, 0x68, 0x7b, 0x3e, 0x86, 0xfd, 0x4e, 0x3e, 0xa2, 0x56, 0x6a, 0xbe, + 0xca, 0xfe, 0x81, 0xbe, 0x43, 0xc3, 0xb1, 0xbd, 0xc5, 0xb8, 0xa7, 0x3e, + 0x55, 0x23, 0xcd, 0x3e, 0xaf, 0x2e, 0x76, 0x3e, 0x69, 0xa8, 0x90, 0xbe, + 0x0d, 0xba, 0xb9, 0x3e, 0x66, 0xff, 0xff, 0xff, 0x04, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x53, 0xd6, 0xe2, 0x3d, 0x66, 0xb6, 0xcc, 0x3e, + 0x03, 0xe7, 0xf6, 0x3e, 0xe0, 0x28, 0x10, 0xbf, 0x00, 0x00, 0x00, 0x00, + 0x3e, 0x3d, 0xb0, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x62, 0xf0, 0x77, 0x3e, + 0xa6, 0x9d, 0xa4, 0x3e, 0x3a, 0x4b, 0xf3, 0xbe, 0x71, 0x9e, 0xa7, 0x3e, + 0x00, 0x00, 0x00, 0x00, 0x34, 0x39, 0xa2, 0x3e, 0x00, 0x00, 0x00, 0x00, + 0xcc, 0x9c, 0x4a, 0x3e, 0xab, 0x40, 0xa3, 0x3e, 0xb2, 0xff, 0xff, 0xff, + 0x04, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xb3, 0x71, 0x67, 0x3f, + 0x9a, 0x7a, 0x95, 0xbf, 0xe1, 0x48, 0xe8, 0xbe, 0x8a, 0x72, 0x96, 0x3e, + 0x00, 0xd2, 0xd3, 0xbb, 0x1a, 0xc5, 0xd7, 0x3f, 0xac, 0x7e, 0xc8, 0xbe, + 0x90, 0xa7, 0x95, 0xbe, 0x3b, 0xd7, 0xdc, 0xbe, 0x41, 0xa8, 0x16, 0x3f, + 0x50, 0x5b, 0xcb, 0x3f, 0x52, 0xb9, 0xed, 0xbe, 0x2e, 0xa7, 0xc6, 0xbe, + 0xaf, 0x0f, 0x14, 0xbf, 0xb3, 0xda, 0x59, 0x3f, 0x02, 0xec, 0xd7, 0xbe, + 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x66, 0x11, 0x1f, 0xbf, + 0xb8, 0xfb, 0xff, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x54, 0x4f, 0x43, 0x4f, + 0x20, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x14, 0x00, + 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0xf0, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xce, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x08, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x1c, 0xfc, 0xff, 0xff, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xba, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x16, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x24, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0xa4, 0x02, 0x00, 0x00, + 0x40, 0x02, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, + 0x48, 0x01, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x26, 0xfd, 0xff, 0xff, + 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x18, 0xfd, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, + 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x34, 0x2f, 0x4d, 0x61, 0x74, + 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6e, 0xfd, 0xff, 0xff, + 0x50, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x60, 0xfd, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, + 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x34, 0x2f, 0x4d, 0x61, 0x74, + 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0xce, 0xfd, 0xff, 0xff, + 0x34, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0xc0, 0xfd, 0xff, 0xff, 0x19, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, + 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x52, 0x65, 0x6c, + 0x75, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x12, 0xfe, 0xff, 0xff, 0x3c, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x04, 0xfe, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, + 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f, + 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x5a, 0xfe, 0xff, 0xff, 0x50, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x4c, 0xfe, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, + 0x73, 0x65, 0x5f, 0x33, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, + 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, + 0x4f, 0x70, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0xba, 0xfe, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0xac, 0xfe, 0xff, 0xff, 0x19, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, + 0x73, 0x65, 0x5f, 0x32, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xfe, 0xfe, 0xff, 0xff, 0x3c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xf0, 0xfe, 0xff, 0xff, + 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, + 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x46, 0xff, 0xff, 0xff, 0x50, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x38, 0xff, 0xff, 0xff, + 0x34, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x31, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, + 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0xa6, 0xff, 0xff, 0xff, 0x48, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, + 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x43, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x64, 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x32, 0x5f, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0a, 0x00, 0x0c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x03, 0x00, 0x00, 0x00}; +const int g_sine_model_data_len = 2640; diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h b/tensorflow/lite/micro/examples/hello_world/sine_model_data.h similarity index 80% rename from tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h rename to tensorflow/lite/micro/examples/hello_world/sine_model_data.h index 7a7ce6f47ee..b7087c6bd9e 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/sine_model_data.h +++ b/tensorflow/lite/micro/examples/hello_world/sine_model_data.h @@ -18,10 +18,10 @@ limitations under the License. // don't have a file system. It was created using the command: // xxd -i sine_model.tflite > sine_model_data.cc -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_SINE_MODEL_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_SINE_MODEL_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_SINE_MODEL_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_SINE_MODEL_DATA_H_ extern const unsigned char g_sine_model_data[]; extern const int g_sine_model_data_len; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_SINE_MODEL_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_HELLO_WORLD_SINE_MODEL_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/sparkfun_edge/constants.cc b/tensorflow/lite/micro/examples/hello_world/sparkfun_edge/constants.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/hello_world/sparkfun_edge/constants.cc rename to tensorflow/lite/micro/examples/hello_world/sparkfun_edge/constants.cc index 169401dd532..1816a2f3207 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/sparkfun_edge/constants.cc +++ b/tensorflow/lite/micro/examples/hello_world/sparkfun_edge/constants.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/constants.h" +#include "tensorflow/lite/micro/examples/hello_world/constants.h" // This is tuned so that a full cycle takes ~4 seconds on a SparkFun Edge. const int kInferencesPerCycle = 1000; diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/sparkfun_edge/output_handler.cc b/tensorflow/lite/micro/examples/hello_world/sparkfun_edge/output_handler.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/hello_world/sparkfun_edge/output_handler.cc rename to tensorflow/lite/micro/examples/hello_world/sparkfun_edge/output_handler.cc index 24479eb77a6..67e36a39f0f 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/sparkfun_edge/output_handler.cc +++ b/tensorflow/lite/micro/examples/hello_world/sparkfun_edge/output_handler.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/output_handler.h" +#include "tensorflow/lite/micro/examples/hello_world/output_handler.h" #include "am_bsp.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/BUILD b/tensorflow/lite/micro/examples/magic_wand/BUILD similarity index 73% rename from tensorflow/lite/experimental/micro/examples/magic_wand/BUILD rename to tensorflow/lite/micro/examples/magic_wand/BUILD index 20eacf37dfb..ee428a8f5ba 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/BUILD +++ b/tensorflow/lite/micro/examples/magic_wand/BUILD @@ -1,15 +1,15 @@ # Description: # TensorFlow Lite for Microcontrollers "gesture recognition" example. +load( + "//tensorflow/lite/micro/testing:micro_test.bzl", + "tflite_micro_cc_test", +) + package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 -load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", - "tflite_micro_cc_test", -) - cc_library( name = "magic_wand_model_data", srcs = [ @@ -41,10 +41,10 @@ tflite_micro_cc_test( ":magic_wand_model_data", ":sample_feature_data", "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/kernels:all_ops_resolver", - "//tensorflow/lite/experimental/micro/kernels:micro_ops", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/kernels:all_ops_resolver", + "//tensorflow/lite/micro/kernels:micro_ops", + "//tensorflow/lite/micro/testing:micro_test", "//tensorflow/lite/schema:schema_fbs", ], ) @@ -69,7 +69,7 @@ cc_library( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -81,8 +81,8 @@ tflite_micro_cc_test( deps = [ ":accelerometer_handler", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -107,8 +107,8 @@ tflite_micro_cc_test( deps = [ ":constants", ":gesture_predictor", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -122,7 +122,7 @@ cc_library( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -134,8 +134,8 @@ tflite_micro_cc_test( deps = [ ":output_handler", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -158,8 +158,8 @@ cc_binary( ":magic_wand_model_data", ":output_handler", "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/kernels:micro_ops", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/kernels:micro_ops", "//tensorflow/lite/schema:schema_fbs", ], ) diff --git a/tensorflow/lite/micro/examples/magic_wand/Makefile.inc b/tensorflow/lite/micro/examples/magic_wand/Makefile.inc new file mode 100644 index 00000000000..561971f27b7 --- /dev/null +++ b/tensorflow/lite/micro/examples/magic_wand/Makefile.inc @@ -0,0 +1,89 @@ +ifeq ($(TARGET), sparkfun_edge) + INCLUDES += \ + -I$(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/ \ + -I$(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/ + + THIRD_PARTY_CC_SRCS += \ + $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/tf_accelerometer.c \ + $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/lis2dh12_reg.c \ + $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/tf_adc.c + + THIRD_PARTY_CC_HDRS += \ + $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/tf_accelerometer.h \ + $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/lis2dh12_reg.h \ + $(APOLLO3_SDK)/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/tf_adc.h +endif + +ACCELEROMETER_HANDLER_TEST_SRCS := \ +tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.cc \ +tensorflow/lite/micro/examples/magic_wand/accelerometer_handler_test.cc + +ACCELEROMETER_HANDLER_TEST_HDRS := \ +tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h + +OUTPUT_HANDLER_TEST_SRCS := \ +tensorflow/lite/micro/examples/magic_wand/output_handler.cc \ +tensorflow/lite/micro/examples/magic_wand/output_handler_test.cc + +OUTPUT_HANDLER_TEST_HDRS := \ +tensorflow/lite/micro/examples/magic_wand/output_handler.h + +GESTURE_PREDICTOR_TEST_SRCS := \ +tensorflow/lite/micro/examples/magic_wand/constants.cc \ +tensorflow/lite/micro/examples/magic_wand/gesture_predictor.cc \ +tensorflow/lite/micro/examples/magic_wand/gesture_predictor_test.cc + +GESTURE_PREDICTOR_TEST_HDRS := \ +tensorflow/lite/micro/examples/magic_wand/constants.h \ +tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h \ + +magic_wand_TEST_SRCS := \ +tensorflow/lite/micro/examples/magic_wand/magic_wand_test.cc \ +tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.cc \ +tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.cc \ +tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.cc + +magic_wand_TEST_HDRS := \ +tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h \ +tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.h \ +tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.h + +magic_wand_SRCS := \ +tensorflow/lite/micro/examples/magic_wand/main.cc \ +tensorflow/lite/micro/examples/magic_wand/main_functions.cc \ +tensorflow/lite/micro/examples/magic_wand/constants.cc \ +tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.cc \ +tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.cc \ +tensorflow/lite/micro/examples/magic_wand/gesture_predictor.cc \ +tensorflow/lite/micro/examples/magic_wand/output_handler.cc + +magic_wand_HDRS := \ +tensorflow/lite/micro/examples/magic_wand/main_functions.h \ +tensorflow/lite/micro/examples/magic_wand/constants.h \ +tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h \ +tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h \ +tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h \ +tensorflow/lite/micro/examples/magic_wand/output_handler.h + +#Find any platform - specific rules for this example. +include $(wildcard tensorflow/lite/micro/examples/magic_wand/*/Makefile.inc) + +# Tests the accelerometer handler +$(eval $(call microlite_test,gesture_accelerometer_handler_test,\ +$(ACCELEROMETER_HANDLER_TEST_SRCS),$(ACCELEROMETER_HANDLER_TEST_HDRS))) + +# Tests the output handler +$(eval $(call microlite_test,gesture_output_handler_test,\ +$(OUTPUT_HANDLER_TEST_SRCS),$(OUTPUT_HANDLER_TEST_HDRS))) + +# Tests the gesture predictor +$(eval $(call microlite_test,gesture_predictor_test,\ +$(GESTURE_PREDICTOR_TEST_SRCS),$(GESTURE_PREDICTOR_TEST_HDRS))) + +# Tests loading and running the gesture recognition model +$(eval $(call microlite_test,magic_wand_test,\ +$(magic_wand_TEST_SRCS),$(magic_wand_TEST_HDRS))) + +# Builds a standalone binary +$(eval $(call microlite_test,magic_wand,\ +$(magic_wand_SRCS),$(magic_wand_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/README.md b/tensorflow/lite/micro/examples/magic_wand/README.md similarity index 79% rename from tensorflow/lite/experimental/micro/examples/magic_wand/README.md rename to tensorflow/lite/micro/examples/magic_wand/README.md index 3f97b9d85ae..91e238a4a2c 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/README.md +++ b/tensorflow/lite/micro/examples/magic_wand/README.md @@ -13,7 +13,6 @@ then outputs the gesture to the serial port. - [Getting started](#getting-started) - [Deploy to Arduino](#deploy-to-arduino) - [Deploy to SparkFun Edge](#deploy-to-sparkfun-edge) -- [Deploy to Adafruit devices](#deploy-to-adafruit) - [Run the tests on a development machine](#run-the-tests-on-a-development-machine) - [Train your own model](#train-your-own-model) @@ -29,7 +28,7 @@ The sample has been tested with the following devices: ### Install the Arduino_TensorFlowLite library Download the current nightly build of the library: -[magic_wand.zip](https://storage.googleapis.com/tensorflow-nightly/github/tensorflow/tensorflow/lite/experimental/micro/tools/make/gen/arduino_x86_64/prj/magic_wand/magic_wand.zip) +[magic_wand.zip](https://storage.googleapis.com/tensorflow-nightly/github/tensorflow/tensorflow/lite/micro/tools/make/gen/arduino_x86_64/prj/magic_wand/magic_wand.zip) Next, import this zip file into the Arduino Desktop IDE by going to `Sketch ->Include Library -> Add .ZIP Library...`. This example application is included @@ -160,13 +159,13 @@ codelab to get an understanding of the workflow. Run the following command to build a binary for SparkFun Edge. ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=sparkfun_edge magic_wand_bin +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=sparkfun_edge magic_wand_bin ``` The binary will be created in the following location: ``` -tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/magic_wand.bin +tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/magic_wand.bin ``` ### Sign the binary @@ -180,15 +179,15 @@ Enter the following command to set up some dummy cryptographic keys we can use for development: ``` -cp tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ -tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py +cp tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ +tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py ``` Next, run the following command to create a signed binary: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ ---bin tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/magic_wand.bin \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ +--bin tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/magic_wand.bin \ --load-address 0xC000 \ --magic-num 0xCB \ -o main_nonsecure_ota \ @@ -200,7 +199,7 @@ command to create a final version of the file that can be used to flash our device with the bootloader script we will use in the next step: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ --load-address 0x20000 \ --bin main_nonsecure_ota.bin \ -i 6 \ @@ -238,7 +237,7 @@ hit the button marked `RST`. Continue holding the button marked `14` while running the following command: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ -b ${BAUD_RATE} ${DEVICENAME} \ -r 1 \ -f main_nonsecure_wire.bin \ @@ -319,16 +318,6 @@ SLOPE: To stop viewing the debug output with `screen`, hit `Ctrl+A`, immediately followed by the `K` key, then hit the `Y` key. -## Deploy to Adafruit devices - -This sample has been tested with the following Adafruit devices. To deploy to -each device, read the accompanying guide on Adafruit's website. - -| Device | Guide | -|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| -| [Adafruit EdgeBadge](https://www.adafruit.com/product/4400) | [TensorFlow Lite for EdgeBadge Kit Quickstart](https://learn.adafruit.com/tensorflow-lite-for-edgebadge-kit-quickstart?view=all) | -| [Adafruit TensorFlow Lite for Microcontrollers Kit](https://www.adafruit.com/product/4317) | [TensorFlow Lite for EdgeBadge Kit Quickstart](https://learn.adafruit.com/tensorflow-lite-for-edgebadge-kit-quickstart?view=all) | - ## Run the tests on a development machine To compile and test this example on a desktop Linux or macOS machine, first @@ -339,11 +328,11 @@ git clone --depth 1 https://github.com/tensorflow/tensorflow.git ``` Next, put this folder under the -tensorflow/tensorflow/lite/experimental/micro/examples/ folder, then `cd` into +tensorflow/tensorflow/lite/micro/examples/ folder, then `cd` into the source directory from a terminal and run the following command: ```bash -make -f tensorflow/lite/experimental/micro/tools/make/Makefile test_magic_wand_test +make -f tensorflow/lite/micro/tools/make/Makefile test_magic_wand_test ``` This will take a few minutes, and downloads frameworks the code uses like @@ -357,7 +346,7 @@ the trained TensorFlow model, runs some example inputs through it, and got the expected outputs. To understand how TensorFlow Lite does this, you can look at the source in -[hello_world_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/hello_world/hello_world_test.cc). +[hello_world_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/hello_world/hello_world_test.cc). It's a fairly small amount of code that creates an interpreter, gets a handle to a model that's been compiled into the program, and then invokes the interpreter with the model and sample inputs. @@ -365,4 +354,4 @@ with the model and sample inputs. ## Train your own model To train your own model, or create a new model for a new set of gestures, -follow the instructions in [magic_wand/train/README.md](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/magic_wand/train/README.md). +follow the instructions in [magic_wand/train/README.md](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/magic_wand/train/README.md). diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.cc b/tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.cc similarity index 93% rename from tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.cc rename to tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.cc index 3b62f6a5bc2..6211d13bd77 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.cc +++ b/tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h" int begin_index = 0; diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h b/tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h similarity index 74% rename from tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h rename to tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h index eaeec82c1cd..fa086f7f09e 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h +++ b/tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h @@ -13,17 +13,17 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_ACCELEROMETER_HANDLER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_ACCELEROMETER_HANDLER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_ACCELEROMETER_HANDLER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_ACCELEROMETER_HANDLER_H_ #define kChannelNumber 3 #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" extern int begin_index; extern TfLiteStatus SetupAccelerometer(tflite::ErrorReporter* error_reporter); extern bool ReadAccelerometer(tflite::ErrorReporter* error_reporter, float* input, int length, bool reset_buffer); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_ACCELEROMETER_HANDLER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_ACCELEROMETER_HANDLER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler_test.cc b/tensorflow/lite/micro/examples/magic_wand/accelerometer_handler_test.cc similarity index 87% rename from tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler_test.cc rename to tensorflow/lite/micro/examples/magic_wand/accelerometer_handler_test.cc index 62725722240..7d35deba6aa 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler_test.cc +++ b/tensorflow/lite/micro/examples/magic_wand/accelerometer_handler_test.cc @@ -13,13 +13,13 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h" #include #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/accelerometer_handler.cc b/tensorflow/lite/micro/examples/magic_wand/arduino/accelerometer_handler.cc similarity index 95% rename from tensorflow/lite/experimental/micro/examples/magic_wand/arduino/accelerometer_handler.cc rename to tensorflow/lite/micro/examples/magic_wand/arduino/accelerometer_handler.cc index affaa1a1386..a4b036e82f2 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/accelerometer_handler.cc +++ b/tensorflow/lite/micro/examples/magic_wand/arduino/accelerometer_handler.cc @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h" #include #include -#include "tensorflow/lite/experimental/micro/examples/magic_wand/constants.h" +#include "tensorflow/lite/micro/examples/magic_wand/constants.h" // A buffer holding the last 200 sets of 3-channel values float save_data[600] = {0.0}; diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/constants.cc b/tensorflow/lite/micro/examples/magic_wand/arduino/constants.cc similarity index 91% rename from tensorflow/lite/experimental/micro/examples/magic_wand/arduino/constants.cc rename to tensorflow/lite/micro/examples/magic_wand/arduino/constants.cc index ed26536eb33..6a0a37b6878 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/constants.cc +++ b/tensorflow/lite/micro/examples/magic_wand/arduino/constants.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/constants.h" +#include "tensorflow/lite/micro/examples/magic_wand/constants.h" // The number of expected consecutive inferences for each gesture type. // Established with the Arduino Nano 33 BLE Sense. diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/main.cc b/tensorflow/lite/micro/examples/magic_wand/arduino/main.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/magic_wand/arduino/main.cc rename to tensorflow/lite/micro/examples/magic_wand/arduino/main.cc index 557885c8001..e34e8bbf509 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/main.cc +++ b/tensorflow/lite/micro/examples/magic_wand/arduino/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h" +#include "tensorflow/lite/micro/examples/hello_world/main_functions.h" // Arduino automatically calls the setup() and loop() functions in a sketch, so // where other systems need their own main routine in this file, it can be left diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/output_handler.cc b/tensorflow/lite/micro/examples/magic_wand/arduino/output_handler.cc similarity index 95% rename from tensorflow/lite/experimental/micro/examples/magic_wand/arduino/output_handler.cc rename to tensorflow/lite/micro/examples/magic_wand/arduino/output_handler.cc index c24d0492a67..f2aecd687b6 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/arduino/output_handler.cc +++ b/tensorflow/lite/micro/examples/magic_wand/arduino/output_handler.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/output_handler.h" #include "Arduino.h" diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/constants.cc b/tensorflow/lite/micro/examples/magic_wand/constants.cc similarity index 91% rename from tensorflow/lite/experimental/micro/examples/magic_wand/constants.cc rename to tensorflow/lite/micro/examples/magic_wand/constants.cc index 2de881db88e..6866bd6f968 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/constants.cc +++ b/tensorflow/lite/micro/examples/magic_wand/constants.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/constants.h" +#include "tensorflow/lite/micro/examples/magic_wand/constants.h" // The number of expected consecutive inferences for each gesture type. // These defaults were established with the SparkFun Edge board. diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/constants.h b/tensorflow/lite/micro/examples/magic_wand/constants.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/magic_wand/constants.h rename to tensorflow/lite/micro/examples/magic_wand/constants.h index 653a0967763..9e225419f8e 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/constants.h +++ b/tensorflow/lite/micro/examples/magic_wand/constants.h @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_CONSTANTS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_CONSTANTS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_CONSTANTS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_CONSTANTS_H_ // The expected accelerometer data sample frequency const float kTargetHz = 25; // The number of expected consecutive inferences for each gesture type extern const int kConsecutiveInferenceThresholds[3]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_CONSTANTS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_CONSTANTS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.cc b/tensorflow/lite/micro/examples/magic_wand/gesture_predictor.cc similarity index 91% rename from tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.cc rename to tensorflow/lite/micro/examples/magic_wand/gesture_predictor.cc index 865016785ad..a7a71b23395 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.cc +++ b/tensorflow/lite/micro/examples/magic_wand/gesture_predictor.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h" +#include "tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/constants.h" +#include "tensorflow/lite/micro/examples/magic_wand/constants.h" // How many times the most recent gesture has been matched in a row int continuous_count = 0; diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h b/tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h similarity index 73% rename from tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h rename to tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h index 8b100631b53..713cb561035 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h +++ b/tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_GESTURE_PREDICTOR_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_GESTURE_PREDICTOR_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_GESTURE_PREDICTOR_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_GESTURE_PREDICTOR_H_ extern int PredictGesture(float* output); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_GESTURE_PREDICTOR_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_GESTURE_PREDICTOR_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor_test.cc b/tensorflow/lite/micro/examples/magic_wand/gesture_predictor_test.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor_test.cc rename to tensorflow/lite/micro/examples/magic_wand/gesture_predictor_test.cc index 65d2d90ab3e..880cf373b1c 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor_test.cc +++ b/tensorflow/lite/micro/examples/magic_wand/gesture_predictor_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h" +#include "tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/constants.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/magic_wand/constants.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.cc b/tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.cc new file mode 100644 index 00000000000..1b8dca8eb0a --- /dev/null +++ b/tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.cc @@ -0,0 +1,1670 @@ +/* Copyright 2019 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. +==============================================================================*/ + +// Automatically created from a TensorFlow Lite flatbuffer using the command: +// xxd -i magic_wand_model.tflite > magic_wand_model_data.cc +// See the README for a full description of the creation process. + +#include "tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h" + +// We need to keep the data array aligned on some architectures. +#ifdef __has_attribute +#define HAVE_ATTRIBUTE(x) __has_attribute(x) +#else +#define HAVE_ATTRIBUTE(x) 0 +#endif +#if HAVE_ATTRIBUTE(aligned) || (defined(__GNUC__) && !defined(__clang__)) +#define DATA_ALIGN_ATTRIBUTE __attribute__((aligned(4))) +#else +#define DATA_ALIGN_ATTRIBUTE +#endif + +const unsigned char g_magic_wand_model_data[] DATA_ALIGN_ATTRIBUTE = { + 0x1c, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x12, 0x00, + 0x1c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x12, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x24, 0x4c, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x14, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x00, 0x00, 0x54, 0x4f, 0x43, 0x4f, 0x20, 0x43, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x2e, 0x00, 0x12, 0x00, 0x00, 0x00, + 0xb0, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, + 0x8c, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, + 0x68, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x50, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x38, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x6e, 0xb6, 0xff, 0xff, 0x68, 0x00, 0x00, 0x00, 0x30, 0xb6, 0xff, 0xff, + 0x34, 0xb6, 0xff, 0xff, 0x7e, 0xb6, 0xff, 0xff, 0xf0, 0x01, 0x00, 0x00, + 0x86, 0xb6, 0xff, 0xff, 0xc8, 0x03, 0x00, 0x00, 0x48, 0xb6, 0xff, 0xff, + 0x4c, 0xb6, 0xff, 0xff, 0x50, 0xb6, 0xff, 0xff, 0x54, 0xb6, 0xff, 0xff, + 0x58, 0xb6, 0xff, 0xff, 0x5c, 0xb6, 0xff, 0xff, 0xa6, 0xb6, 0xff, 0xff, + 0xc0, 0x0d, 0x00, 0x00, 0xae, 0xb6, 0xff, 0xff, 0x00, 0x46, 0x00, 0x00, + 0xb6, 0xb6, 0xff, 0xff, 0x60, 0x46, 0x00, 0x00, 0xbe, 0xb6, 0xff, 0xff, + 0xe0, 0x46, 0x00, 0x00, 0xc6, 0xb6, 0xff, 0xff, 0x48, 0x47, 0x00, 0x00, + 0xce, 0xb6, 0xff, 0xff, 0x98, 0x48, 0x00, 0x00, 0x90, 0xb6, 0xff, 0xff, + 0x05, 0x00, 0x00, 0x00, 0x31, 0x2e, 0x35, 0x2e, 0x30, 0x00, 0x00, 0x00, + 0x54, 0xf4, 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, + 0x13, 0x00, 0x00, 0x00, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x75, 0x6e, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, + 0x0c, 0x00, 0x14, 0x00, 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x88, 0x48, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x9c, 0x0c, 0x00, 0x00, 0x14, 0x0b, 0x00, 0x00, + 0xd4, 0x00, 0x00, 0x00, 0x24, 0x45, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, + 0xa4, 0x02, 0x00, 0x00, 0x80, 0x45, 0x00, 0x00, 0x9c, 0x0b, 0x00, 0x00, + 0xb0, 0x0c, 0x00, 0x00, 0xc4, 0x47, 0x00, 0x00, 0x50, 0x0b, 0x00, 0x00, + 0x2c, 0x0c, 0x00, 0x00, 0x48, 0x46, 0x00, 0x00, 0xec, 0x45, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0xc8, 0x0b, 0x00, 0x00, 0x66, 0xb8, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x2a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x69, + 0x6e, 0x67, 0x32, 0x64, 0x2f, 0x4d, 0x61, 0x78, 0x50, 0x6f, 0x6f, 0x6c, + 0x00, 0x00, 0x00, 0x00, 0x88, 0xb7, 0xff, 0xff, 0xba, 0xb8, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x2f, 0x52, 0x65, + 0x6c, 0x75, 0x00, 0x00, 0xd0, 0xb7, 0xff, 0xff, 0x02, 0xb9, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x44, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x27, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x2f, 0x43, 0x6f, + 0x6e, 0x76, 0x32, 0x44, 0x2f, 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, + 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x00, 0x28, 0xb8, 0xff, 0xff, + 0x80, 0x01, 0x00, 0x00, 0x1c, 0x6a, 0xf6, 0xbd, 0xaa, 0x16, 0xfd, 0x3c, + 0xf6, 0xd9, 0x20, 0x3e, 0x64, 0xf8, 0xdd, 0x3d, 0x07, 0xc0, 0x82, 0xbe, + 0x9e, 0xa3, 0x38, 0xbd, 0x13, 0x41, 0xa1, 0x3d, 0xb0, 0x81, 0x90, 0xbd, + 0xc7, 0xdd, 0xbc, 0xbb, 0x87, 0x9c, 0x24, 0xbe, 0x72, 0x08, 0x6a, 0xbd, + 0x10, 0x1b, 0x61, 0x3e, 0x79, 0x49, 0x18, 0xbe, 0xda, 0x09, 0x88, 0xbe, + 0x2d, 0x70, 0x4d, 0x3d, 0x5c, 0x4a, 0x9e, 0xbd, 0x0f, 0xf1, 0x46, 0x3e, + 0x1c, 0xbd, 0x02, 0xbf, 0x56, 0xbc, 0x07, 0x3e, 0x63, 0x92, 0x39, 0xbe, + 0x4e, 0xde, 0x84, 0x3e, 0x64, 0x38, 0x88, 0xbd, 0xa0, 0x32, 0xc3, 0xbd, + 0x0f, 0x94, 0xb7, 0xbe, 0xd6, 0x11, 0x27, 0xbc, 0xcc, 0x7e, 0xf3, 0x3d, + 0xf3, 0x4d, 0xaa, 0x3d, 0xbc, 0x8a, 0x28, 0x3e, 0xa2, 0xb5, 0xda, 0xbd, + 0x92, 0x1a, 0xb6, 0xbd, 0x9a, 0x49, 0xb1, 0x3d, 0xfc, 0x93, 0x1c, 0x3d, + 0x74, 0xa1, 0xa1, 0xbd, 0xc7, 0x48, 0x1d, 0xbe, 0x3a, 0x53, 0xb2, 0x3b, + 0x92, 0x51, 0xa5, 0xbd, 0x6a, 0xc4, 0x3c, 0xbd, 0xdb, 0x61, 0x6d, 0xbd, + 0x78, 0x9f, 0x03, 0xbe, 0x40, 0x1f, 0x30, 0xbd, 0x17, 0xde, 0xad, 0x3d, + 0xd7, 0xee, 0x74, 0xbd, 0xb6, 0x5c, 0xc2, 0x3d, 0x1c, 0x89, 0x65, 0xbe, + 0xfd, 0xc4, 0x48, 0x3e, 0xb2, 0x29, 0x13, 0x3d, 0xcc, 0x56, 0x13, 0x3d, + 0xf8, 0xce, 0x1b, 0xbc, 0xb5, 0x4b, 0xe8, 0xbc, 0x48, 0x05, 0x5c, 0xbe, + 0xaf, 0xfa, 0x0d, 0x3e, 0x74, 0x84, 0xa4, 0x3d, 0x4c, 0x84, 0x04, 0x3e, + 0x09, 0x7a, 0xba, 0x3c, 0xb3, 0xa6, 0x07, 0x3e, 0x7d, 0xe5, 0xe5, 0x3d, + 0x7e, 0xb9, 0xa5, 0x3c, 0x4e, 0x70, 0x49, 0x3e, 0x39, 0xfe, 0x12, 0xbe, + 0xfa, 0x8b, 0x01, 0xbe, 0xb9, 0x8e, 0xe6, 0xbc, 0xc8, 0x2f, 0xb3, 0xbd, + 0x1b, 0x2b, 0x9e, 0xbd, 0xe7, 0x7f, 0x0e, 0x3d, 0x3e, 0xa3, 0x2a, 0x3d, + 0xa1, 0x73, 0x31, 0x3d, 0xc8, 0xc7, 0x03, 0xbd, 0x07, 0x71, 0xaf, 0xbd, + 0xb2, 0x6b, 0x2b, 0xbe, 0x06, 0xc2, 0x1f, 0xbe, 0x3b, 0xbf, 0x30, 0xbe, + 0x7e, 0x51, 0x22, 0x3e, 0x5a, 0xa7, 0x92, 0x3d, 0xb8, 0x60, 0x35, 0xbe, + 0xa7, 0xdf, 0x8f, 0x3d, 0xbc, 0xfc, 0x42, 0x3e, 0x42, 0x86, 0x7d, 0xbc, + 0x3a, 0xd0, 0xd8, 0x3c, 0xea, 0x45, 0x40, 0xbc, 0x04, 0xd3, 0x9d, 0xb7, + 0xe3, 0xdf, 0xae, 0xbd, 0x80, 0x5e, 0x59, 0xbe, 0x88, 0x15, 0xc0, 0xbd, + 0xea, 0x86, 0xaa, 0xbd, 0x3b, 0x4a, 0x64, 0x3d, 0x89, 0x25, 0x42, 0xbe, + 0xc2, 0x29, 0x93, 0xbe, 0x62, 0x85, 0x00, 0x3e, 0xf1, 0x0e, 0xda, 0xbd, + 0x48, 0x09, 0xb8, 0xbe, 0xad, 0xe2, 0x4d, 0xbe, 0x69, 0x26, 0x99, 0xbe, + 0x86, 0x3c, 0xcd, 0xbe, 0x05, 0xe6, 0x4e, 0xbd, 0xdb, 0x8f, 0xfb, 0x3d, + 0xc6, 0xf5, 0x97, 0x3e, 0xde, 0xba, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x63, + 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, 0x31, 0x2f, 0x43, 0x6f, 0x6e, 0x76, + 0x32, 0x44, 0x2f, 0x52, 0x65, 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, + 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x00, 0x00, 0x00, 0x08, 0xba, 0xff, 0xff, + 0x00, 0x08, 0x00, 0x00, 0x40, 0x7a, 0x10, 0xbf, 0x9b, 0xf1, 0xcc, 0xbe, + 0x78, 0x88, 0x1d, 0xbf, 0xc2, 0xb7, 0x09, 0xbf, 0xbd, 0xd6, 0xaf, 0xbe, + 0xf5, 0x29, 0xed, 0xbe, 0xad, 0x18, 0x70, 0xbe, 0x3d, 0x91, 0x40, 0x3b, + 0x2f, 0xcf, 0x55, 0xbe, 0xc6, 0x0b, 0xed, 0x3e, 0xbe, 0x7a, 0x80, 0xbe, + 0x3c, 0x3c, 0xb1, 0x3a, 0xab, 0xea, 0xa6, 0x3d, 0x88, 0x57, 0x55, 0xbe, + 0x0c, 0x62, 0xdc, 0xbe, 0x07, 0x6b, 0xbe, 0x3e, 0x9e, 0x0d, 0x28, 0x3d, + 0xd9, 0xd2, 0xb1, 0x3d, 0x4c, 0x45, 0xbd, 0xbd, 0x3c, 0xe0, 0x1f, 0x3d, + 0x76, 0xb5, 0x48, 0xbd, 0x2b, 0xe8, 0x3a, 0xbe, 0xd3, 0x21, 0x57, 0xbe, + 0xdc, 0x21, 0x70, 0x3e, 0x00, 0x71, 0xec, 0x3d, 0x9c, 0xeb, 0x61, 0x3d, + 0x0d, 0x8a, 0x9c, 0x3d, 0x2a, 0x35, 0x03, 0x3d, 0x1b, 0x1c, 0x28, 0x3b, + 0xa6, 0xf9, 0xa9, 0xbd, 0x20, 0xfb, 0x2f, 0xbe, 0x8c, 0xcb, 0x04, 0x3d, + 0x43, 0x62, 0x64, 0x3d, 0xf8, 0x65, 0xd6, 0x3d, 0xf3, 0xbe, 0x3a, 0xbd, + 0xaf, 0x2b, 0x53, 0x3d, 0x99, 0x58, 0xf8, 0xbd, 0x3a, 0xbe, 0x43, 0xbd, + 0xd0, 0xe7, 0xc0, 0xbc, 0xa4, 0x8d, 0xf3, 0x3d, 0xa2, 0xd7, 0x9c, 0xbc, + 0x87, 0x1b, 0xb0, 0x3e, 0x18, 0x20, 0x88, 0xbd, 0xee, 0x9e, 0xc5, 0x3e, + 0xc1, 0x35, 0xaf, 0x3c, 0xc0, 0x97, 0x39, 0xbd, 0xa5, 0x6f, 0x04, 0xbd, + 0x7b, 0x03, 0x1c, 0xbe, 0x0f, 0x75, 0xbb, 0xbd, 0xdf, 0x1b, 0x54, 0xbd, + 0xfb, 0xbf, 0xcc, 0xbd, 0x1b, 0x01, 0x10, 0xbd, 0x20, 0x67, 0xb4, 0xbc, + 0xdf, 0xa6, 0x40, 0x3c, 0x74, 0xb4, 0x28, 0x3d, 0x65, 0xe7, 0xc3, 0xbd, + 0x8d, 0x38, 0x91, 0xbd, 0x8f, 0xba, 0x45, 0xbc, 0x69, 0xc7, 0x49, 0xbc, + 0xa3, 0xcb, 0xf8, 0x3c, 0x69, 0xed, 0x2d, 0x3e, 0x0d, 0xf4, 0xc6, 0xbc, + 0xd6, 0xe7, 0xfe, 0xbd, 0xfa, 0xa0, 0x86, 0xbd, 0x30, 0x83, 0xf0, 0xbd, + 0xf5, 0xb9, 0xd1, 0xbd, 0x1c, 0x39, 0x90, 0xbe, 0x74, 0x62, 0xd8, 0xbd, + 0x13, 0x28, 0x07, 0xbe, 0xbf, 0xbd, 0xbf, 0xbd, 0x15, 0xcf, 0xa8, 0xbb, + 0x9c, 0x1a, 0x8f, 0xbd, 0x91, 0x23, 0x93, 0x3d, 0x79, 0x63, 0xc4, 0xbd, + 0xec, 0xdd, 0x72, 0xbe, 0xd2, 0xd8, 0xac, 0xbe, 0xbf, 0x1e, 0x21, 0xbe, + 0xae, 0xab, 0x20, 0xbd, 0x8f, 0xe9, 0x90, 0x3d, 0xbb, 0x47, 0xdd, 0x3d, + 0xb1, 0x93, 0x37, 0xbd, 0xeb, 0xcf, 0x45, 0xbc, 0x33, 0x8f, 0x15, 0xbe, + 0xeb, 0x19, 0x9f, 0xbe, 0x66, 0x21, 0xe3, 0xbd, 0x53, 0x82, 0x60, 0x3c, + 0x11, 0xdc, 0xf1, 0xbc, 0x0c, 0x01, 0x1e, 0x3d, 0xdb, 0xd5, 0x1d, 0x3f, + 0x58, 0xa3, 0x61, 0x3d, 0x0a, 0x2b, 0x16, 0xbe, 0x01, 0x9d, 0x50, 0xbe, + 0xac, 0xac, 0x63, 0x3e, 0x76, 0xdb, 0x8a, 0xbc, 0x57, 0xec, 0x8f, 0xbc, + 0xad, 0x20, 0xd6, 0x3d, 0xc2, 0x63, 0x89, 0x3d, 0xc3, 0x1e, 0xe9, 0x3e, + 0xa8, 0x41, 0x9e, 0xbd, 0xac, 0x2c, 0x2b, 0xbe, 0x98, 0x73, 0xbf, 0x3d, + 0x7a, 0x22, 0x54, 0xbd, 0x44, 0xaf, 0x2c, 0xbe, 0x05, 0x45, 0xd9, 0xbc, + 0x74, 0xaa, 0x20, 0x3e, 0x6e, 0x1e, 0x95, 0x3e, 0x54, 0x20, 0x12, 0xbc, + 0xbe, 0x20, 0xcf, 0x3d, 0xa6, 0x02, 0x28, 0xbe, 0xd1, 0xfe, 0xe8, 0xbd, + 0x1f, 0x2a, 0x83, 0xbe, 0x33, 0x44, 0xf3, 0x3d, 0xff, 0x48, 0xda, 0xbd, + 0x8d, 0x1e, 0x79, 0x3e, 0xdb, 0xee, 0xb2, 0x3d, 0xdb, 0xde, 0x5b, 0xbe, + 0x55, 0x57, 0x49, 0xbe, 0x62, 0x4b, 0x29, 0xbf, 0x93, 0xbf, 0x23, 0xbf, + 0xea, 0xa3, 0x71, 0x3d, 0xa5, 0x50, 0x00, 0xbe, 0xb6, 0xd1, 0xc0, 0xbf, + 0x1d, 0x22, 0x7d, 0xbd, 0x00, 0x09, 0x81, 0x3d, 0xde, 0x28, 0x75, 0xbe, + 0x03, 0x1b, 0x2e, 0xbf, 0x3c, 0x3a, 0x3b, 0xbf, 0x52, 0x1a, 0xe6, 0xbd, + 0x9d, 0xe9, 0xea, 0xbd, 0x49, 0x71, 0x2c, 0x3d, 0xe8, 0x8b, 0x55, 0xbb, + 0x6c, 0x97, 0x24, 0xbe, 0x44, 0xe8, 0xe0, 0xba, 0x6d, 0x45, 0x4b, 0x3f, + 0x7f, 0x26, 0x38, 0x3e, 0xab, 0x04, 0x6e, 0xbe, 0x70, 0x59, 0x0e, 0xbe, + 0xd6, 0xfb, 0x7a, 0x3d, 0x45, 0x72, 0xa2, 0xbd, 0xb5, 0x6f, 0x2e, 0xbe, + 0xda, 0xf5, 0x07, 0x3e, 0xe0, 0x2b, 0xac, 0xbd, 0xaf, 0x35, 0xf6, 0xbd, + 0xd0, 0x2b, 0xac, 0xbd, 0x26, 0x2d, 0x11, 0xbe, 0x7e, 0xfa, 0x87, 0x3d, + 0x3a, 0xb7, 0xf6, 0xbd, 0xb1, 0xd0, 0xe2, 0xbc, 0xc8, 0xa2, 0x86, 0xbd, + 0x19, 0xf5, 0xb1, 0xbd, 0xf6, 0x65, 0x4d, 0xbe, 0x23, 0x63, 0x47, 0x3e, + 0xb7, 0x26, 0xd3, 0xbd, 0x57, 0xf4, 0x12, 0xbf, 0x93, 0xd4, 0x39, 0xbe, + 0x77, 0xf2, 0x62, 0x3d, 0xf6, 0x3d, 0xc3, 0xbe, 0xb6, 0xf5, 0x2b, 0xbe, + 0xbe, 0x8a, 0x76, 0xbe, 0xb1, 0x39, 0x63, 0x3e, 0xde, 0xbe, 0x3c, 0xbe, + 0xd4, 0x01, 0x94, 0xbe, 0x19, 0x1a, 0x97, 0xbb, 0xcb, 0x83, 0x4e, 0xbe, + 0x50, 0x19, 0x94, 0xbd, 0xf8, 0x8a, 0x95, 0xbe, 0xc8, 0xab, 0x86, 0xbe, + 0x18, 0x57, 0x6d, 0x3e, 0x87, 0xad, 0x8b, 0x3c, 0x72, 0x7b, 0x8d, 0x3e, + 0x54, 0x39, 0x95, 0x3d, 0x1d, 0xfa, 0x4b, 0xbe, 0x97, 0xd2, 0x7a, 0xbe, + 0x68, 0x4a, 0xcb, 0xbe, 0xf0, 0x10, 0x04, 0xbf, 0x2b, 0xb5, 0x82, 0x3e, + 0xf8, 0x71, 0x1a, 0x3e, 0x29, 0xf0, 0x29, 0x3d, 0x74, 0x5a, 0x1a, 0x3e, + 0x58, 0x75, 0xd1, 0xbd, 0x38, 0x6c, 0x99, 0x3e, 0x6c, 0xd4, 0x63, 0xbe, + 0xc3, 0x51, 0x90, 0xbe, 0xcf, 0xff, 0xae, 0xbe, 0xfe, 0xf1, 0x00, 0x3d, + 0x52, 0x64, 0x90, 0xbd, 0x02, 0x1a, 0xce, 0xbd, 0x86, 0x74, 0x00, 0x3d, + 0x82, 0x40, 0x04, 0x3e, 0x38, 0x03, 0x82, 0x3e, 0x8f, 0x1c, 0xf4, 0x3e, + 0x6f, 0x04, 0x68, 0xbe, 0x00, 0x12, 0xe3, 0x3d, 0x01, 0xf6, 0xb5, 0x3d, + 0xd9, 0x99, 0x36, 0x3d, 0x40, 0xad, 0xde, 0x3e, 0xaf, 0x74, 0xc1, 0x3d, + 0xb7, 0x8e, 0x6f, 0xbe, 0xb3, 0xa6, 0x32, 0xbe, 0xff, 0xca, 0x23, 0x3e, + 0x0c, 0xf1, 0x42, 0x3e, 0xe3, 0x85, 0x2c, 0x3d, 0xca, 0xc8, 0xb7, 0xba, + 0x1a, 0x94, 0x53, 0xbd, 0x9a, 0x33, 0xaa, 0x3d, 0x9c, 0x7c, 0x79, 0xbe, + 0x84, 0xb2, 0x71, 0xbe, 0x48, 0xc1, 0x2b, 0xbd, 0xf4, 0x89, 0x6c, 0xbd, + 0x1f, 0xd2, 0xf2, 0xbd, 0xd2, 0x4f, 0x28, 0xbd, 0xb4, 0xbb, 0xb3, 0xbd, + 0x6f, 0x96, 0xab, 0xbc, 0x23, 0x9d, 0x82, 0xbe, 0xe6, 0x6b, 0x59, 0xbd, + 0x27, 0x09, 0x03, 0xbe, 0x42, 0xa6, 0xac, 0xbd, 0xb6, 0x12, 0x20, 0x3d, + 0x0a, 0x63, 0x24, 0xbe, 0x75, 0x27, 0x28, 0xbe, 0xa5, 0x62, 0x2b, 0xbe, + 0x1f, 0x48, 0x06, 0xbe, 0x7e, 0xd0, 0xb2, 0xbd, 0xa9, 0xd6, 0x80, 0x3a, + 0xff, 0x7a, 0x11, 0xbe, 0x76, 0x5f, 0x41, 0x3e, 0x17, 0xa9, 0xfa, 0xbd, + 0x5b, 0xd1, 0x71, 0xbd, 0xf3, 0x23, 0xaf, 0xbd, 0x63, 0x24, 0xe0, 0xbc, + 0xc6, 0x62, 0x9d, 0x3e, 0xd6, 0x19, 0x47, 0xbe, 0x92, 0x69, 0xf1, 0xbd, + 0x8a, 0x67, 0x82, 0x3d, 0x17, 0x33, 0x69, 0x3d, 0x1a, 0x91, 0x25, 0xbe, + 0xf1, 0xab, 0xae, 0x3d, 0x3a, 0x21, 0xc1, 0x3e, 0xd8, 0xc4, 0x5d, 0xbd, + 0xc7, 0x58, 0xa6, 0xbe, 0xc6, 0xb0, 0xed, 0x3b, 0x75, 0xd6, 0xa2, 0x3c, + 0x64, 0xa8, 0x1d, 0xbe, 0xe5, 0x1f, 0x3a, 0xbe, 0x7b, 0x03, 0x39, 0xbd, + 0x14, 0xa2, 0x81, 0x3d, 0xdb, 0xfd, 0xb2, 0xbc, 0xca, 0x96, 0x9a, 0xbe, + 0x7c, 0xcc, 0xc9, 0x3c, 0xb8, 0x7d, 0x88, 0x3d, 0x36, 0x39, 0x0b, 0xbd, + 0x5e, 0x1f, 0x3c, 0xbe, 0x27, 0x36, 0x83, 0x3c, 0x38, 0xa1, 0x23, 0xbd, + 0xba, 0xfa, 0xf6, 0x3b, 0x8d, 0xa9, 0xc3, 0xbe, 0x50, 0x34, 0xf0, 0xbd, + 0x92, 0x0f, 0xb3, 0xbd, 0xd9, 0xad, 0x5e, 0xbe, 0xc1, 0x27, 0xb2, 0x3c, + 0x6a, 0x29, 0x07, 0xbe, 0x0f, 0xb5, 0x26, 0xbe, 0xc8, 0xf9, 0x27, 0x3e, + 0x2a, 0x97, 0xa4, 0x3e, 0xe1, 0x45, 0x53, 0x3e, 0xec, 0xd7, 0xa0, 0x3d, + 0xfd, 0x1a, 0x8c, 0xbe, 0x1d, 0x4c, 0xd6, 0xbd, 0x4a, 0x78, 0x63, 0xbe, + 0x18, 0xa4, 0xd9, 0xbc, 0x5a, 0xaa, 0x37, 0x3d, 0xff, 0xe8, 0x3b, 0xbe, + 0x6b, 0x8c, 0x67, 0x3e, 0x13, 0xec, 0x12, 0xbc, 0xae, 0xcc, 0xab, 0xbc, + 0x2e, 0x9b, 0x72, 0xbd, 0x46, 0x3f, 0xb4, 0x3e, 0xdb, 0xba, 0xd3, 0xbd, + 0x7b, 0xdb, 0x86, 0xbe, 0x6a, 0x66, 0xd9, 0xbe, 0x8c, 0x5c, 0x80, 0x3d, + 0x60, 0x64, 0x4d, 0xbe, 0x4d, 0x91, 0x58, 0x3e, 0xa9, 0xfc, 0x0e, 0xbe, + 0x32, 0xc8, 0xce, 0x3e, 0xa8, 0xc8, 0xb3, 0xbe, 0x4d, 0x07, 0xae, 0xbe, + 0xbc, 0xa3, 0x2c, 0xbf, 0x57, 0x9c, 0x21, 0xbe, 0x0e, 0x6d, 0x6e, 0xbe, + 0x30, 0xa6, 0x15, 0xbf, 0xd6, 0x76, 0x01, 0xbf, 0x80, 0x3e, 0xab, 0xbe, + 0xbe, 0x98, 0x2d, 0xbe, 0xe2, 0x02, 0x48, 0xbe, 0xc8, 0x4b, 0x96, 0xbe, + 0x48, 0xaa, 0x2e, 0x3e, 0xa2, 0x19, 0x01, 0x3f, 0xa8, 0xec, 0x8f, 0xbe, + 0x15, 0xd2, 0x24, 0x3e, 0x5c, 0x80, 0xc2, 0xbc, 0xf0, 0x78, 0x29, 0xbe, + 0xfe, 0x1d, 0x63, 0xbe, 0x32, 0xf1, 0x22, 0xbd, 0x35, 0x8c, 0x1d, 0x3e, + 0xb9, 0x22, 0xc2, 0x3e, 0xde, 0x75, 0xc0, 0xbe, 0x27, 0x71, 0x73, 0xbb, + 0x37, 0x41, 0xde, 0x3d, 0x0a, 0x71, 0xfe, 0xbd, 0x9e, 0x66, 0xf6, 0xbd, + 0x2b, 0x93, 0x07, 0xbc, 0x75, 0x1e, 0x90, 0x3d, 0x01, 0x49, 0x59, 0x3e, + 0x0a, 0xb2, 0xbe, 0xbe, 0xd4, 0x65, 0x9f, 0xbc, 0x43, 0x20, 0xdd, 0x3d, + 0xef, 0x01, 0x74, 0xbd, 0xb2, 0xa4, 0xd5, 0x3b, 0xa4, 0x30, 0xf9, 0xbc, + 0xb8, 0x15, 0x68, 0xb8, 0x58, 0xa2, 0xa7, 0xbe, 0x5a, 0x25, 0xa5, 0x3d, + 0x2d, 0x86, 0xb2, 0xbe, 0xc9, 0x31, 0xc2, 0x3e, 0xd2, 0x61, 0x28, 0x3d, + 0xa6, 0xfe, 0xba, 0x3d, 0x4b, 0x6c, 0xf6, 0xbd, 0xaa, 0x14, 0xdc, 0xbc, + 0xf6, 0x7d, 0xdc, 0xbd, 0xce, 0xb6, 0x75, 0xbd, 0x0b, 0xa5, 0xa8, 0xbe, + 0x9b, 0xb5, 0x4a, 0x3e, 0xfc, 0xfa, 0x98, 0x3d, 0x27, 0xd6, 0x39, 0x3d, + 0x1a, 0xbf, 0x67, 0x3d, 0x3f, 0x04, 0x04, 0xbc, 0x07, 0x56, 0x42, 0xbd, + 0xd8, 0xe6, 0x52, 0xbe, 0x72, 0xff, 0xc7, 0xbd, 0xd8, 0x5b, 0xba, 0xbd, + 0xe9, 0xb9, 0xc8, 0xbd, 0xe2, 0x54, 0x05, 0xbe, 0xb5, 0x8f, 0xf2, 0x3e, + 0x74, 0xe9, 0x68, 0xbd, 0x6f, 0x16, 0xcd, 0xbe, 0x2a, 0x22, 0x40, 0x3c, + 0xfc, 0x03, 0xf2, 0x3d, 0x91, 0x74, 0xaa, 0x3d, 0x7d, 0xb1, 0x1f, 0xbe, + 0x95, 0xc1, 0x14, 0xbe, 0xbb, 0xe5, 0x89, 0xbe, 0xae, 0xff, 0x5a, 0x3d, + 0x31, 0x79, 0x07, 0xbe, 0x07, 0xfb, 0xba, 0x3e, 0x4e, 0xd0, 0x86, 0xbd, + 0x68, 0x36, 0x29, 0x3e, 0xec, 0x14, 0xc7, 0x3d, 0xef, 0xf6, 0x06, 0x3b, + 0x76, 0xa0, 0xe8, 0x3c, 0x97, 0x57, 0xac, 0x3c, 0xec, 0x02, 0x8d, 0xbe, + 0x43, 0xaf, 0x42, 0x3d, 0x13, 0x39, 0x0e, 0x3d, 0xf4, 0xed, 0x5a, 0x3e, + 0xbb, 0xf1, 0x18, 0xbe, 0x71, 0x1f, 0xc8, 0xbb, 0x6d, 0x8c, 0x0b, 0xbe, + 0xfe, 0x5d, 0xc3, 0xbd, 0x2c, 0xae, 0x87, 0xbe, 0x58, 0x74, 0x3e, 0x3d, + 0x14, 0x52, 0x13, 0xbe, 0x41, 0x11, 0x55, 0xbe, 0x43, 0x03, 0x0e, 0x3e, + 0xf8, 0x4c, 0x2e, 0x3e, 0x09, 0x6a, 0xea, 0xbd, 0xec, 0xe1, 0xc3, 0xbd, + 0xd5, 0xdf, 0x2a, 0xbe, 0x2e, 0xc1, 0xd9, 0xbd, 0xc1, 0x5b, 0x72, 0xbe, + 0x73, 0xe9, 0x0f, 0xbe, 0xab, 0xc3, 0x0c, 0x3e, 0x85, 0xd1, 0x5e, 0x3e, + 0x08, 0x70, 0x0d, 0xbe, 0x6f, 0xb7, 0x01, 0x3d, 0x0c, 0x0f, 0x86, 0xbd, + 0x0d, 0x23, 0x56, 0x3e, 0x16, 0x6f, 0x10, 0xbc, 0x4f, 0x98, 0x42, 0xbf, + 0x85, 0x4e, 0x44, 0xbe, 0xf0, 0x20, 0x0b, 0xbe, 0x5b, 0xa3, 0x0f, 0xbc, + 0xbd, 0x33, 0x45, 0xbd, 0x84, 0xfb, 0x48, 0xbd, 0x11, 0x99, 0x8c, 0x3c, + 0x41, 0x1e, 0x08, 0x3e, 0xe3, 0x3e, 0x6c, 0xbf, 0x97, 0x2b, 0x0c, 0xbe, + 0x94, 0xec, 0x23, 0xbb, 0x8f, 0x35, 0x4f, 0x3c, 0xea, 0xec, 0x0c, 0xbd, + 0x04, 0x13, 0x3d, 0xbe, 0x13, 0x76, 0x23, 0x3e, 0x37, 0x0d, 0x99, 0x3c, + 0xd4, 0xa3, 0xf4, 0xbe, 0x18, 0x6a, 0x6c, 0xbe, 0x3d, 0x3c, 0xf6, 0xbd, + 0xf8, 0x51, 0xaf, 0xbc, 0x1f, 0x6e, 0x8a, 0xbc, 0x55, 0xc5, 0x8c, 0xbe, + 0x9e, 0x9c, 0x79, 0xbd, 0x13, 0x14, 0xb7, 0xbd, 0x89, 0xcd, 0x1a, 0xbe, + 0x79, 0x14, 0x2e, 0x3e, 0xdd, 0xa2, 0x71, 0x3e, 0xad, 0x71, 0xbe, 0xbc, + 0xa3, 0xc9, 0x22, 0x3f, 0x66, 0x4b, 0x0f, 0x3d, 0x45, 0x1c, 0x29, 0xbe, + 0xf6, 0x79, 0x93, 0xbe, 0x71, 0x18, 0xb6, 0x3d, 0xcc, 0xcb, 0x9d, 0x3c, + 0xa1, 0xbb, 0xfd, 0xbc, 0xc9, 0x75, 0x05, 0x3e, 0x77, 0x4b, 0xad, 0xbd, + 0x81, 0x1d, 0x5c, 0x3e, 0x2d, 0xcc, 0x24, 0xbd, 0x3a, 0xce, 0x36, 0xbe, + 0xb8, 0x37, 0x27, 0xbe, 0xe6, 0x3e, 0x75, 0x3b, 0xb7, 0xb4, 0x2c, 0xbd, + 0x1f, 0x05, 0x47, 0x3c, 0x81, 0x1d, 0x33, 0x3e, 0x8a, 0xfd, 0x4f, 0x3e, + 0xaf, 0x7c, 0x3b, 0x3d, 0x00, 0xa0, 0xda, 0xbd, 0x39, 0xd1, 0x20, 0xbf, + 0xc9, 0x78, 0xf3, 0xbd, 0x9d, 0x01, 0xa3, 0xbe, 0x42, 0x44, 0xbb, 0xbc, + 0x5a, 0xc1, 0xd4, 0xbd, 0xfd, 0xe7, 0x3c, 0xbf, 0x46, 0x37, 0x85, 0x3d, + 0x79, 0x4e, 0xbc, 0x3d, 0xa4, 0xcd, 0x7f, 0xbf, 0x1d, 0xca, 0x69, 0xbf, + 0x97, 0xeb, 0x69, 0xbf, 0xaa, 0xc9, 0x9f, 0x3c, 0xb4, 0x82, 0x9d, 0x3e, + 0xf1, 0x94, 0x77, 0x3e, 0xf2, 0x74, 0x84, 0xbe, 0x88, 0x66, 0x9c, 0xbe, + 0xdf, 0x4e, 0xf1, 0xbd, 0xa2, 0x9e, 0x31, 0x3e, 0x8b, 0xc9, 0x49, 0x3d, + 0x5a, 0x63, 0x5c, 0x3e, 0xf9, 0xa5, 0x4e, 0x3d, 0x95, 0x3f, 0x8d, 0x3d, + 0x1c, 0xe0, 0x68, 0xbe, 0xb6, 0xe1, 0x7c, 0xbe, 0x82, 0x2b, 0x63, 0xbe, + 0x76, 0x6c, 0x02, 0xbe, 0xfe, 0x30, 0x36, 0xbe, 0x8f, 0x5f, 0x36, 0x3d, + 0x17, 0x52, 0x15, 0x3c, 0x1e, 0xc8, 0x88, 0xbf, 0x0a, 0xa1, 0x5d, 0x3d, + 0xe8, 0x31, 0x71, 0x3e, 0xd2, 0x45, 0x01, 0xbc, 0x41, 0x3c, 0x27, 0xbe, + 0xbb, 0xa9, 0x4d, 0xbc, 0x0f, 0xde, 0x9d, 0x3c, 0xbf, 0x35, 0xc3, 0xbd, + 0x5b, 0x0e, 0x70, 0xbf, 0xe9, 0xf4, 0xd5, 0x3b, 0x60, 0x9b, 0xec, 0x3d, + 0x8b, 0x75, 0x23, 0xbc, 0x17, 0x03, 0x84, 0xbe, 0x99, 0x04, 0xd0, 0x3c, + 0xdd, 0x01, 0x08, 0xbe, 0x82, 0xd5, 0x75, 0xbd, 0x05, 0xaa, 0xec, 0x3c, + 0xb9, 0x4d, 0x45, 0x3d, 0xa3, 0x11, 0x69, 0xbb, 0xa3, 0xb0, 0x50, 0x3e, + 0x7a, 0x5f, 0xaa, 0xbd, 0x6a, 0x73, 0xbe, 0xbd, 0x91, 0x25, 0xa9, 0xbd, + 0x0f, 0x8e, 0xe0, 0xbd, 0x50, 0x51, 0x8f, 0x3c, 0xf4, 0x7d, 0xb9, 0x3d, + 0xa2, 0x11, 0x50, 0x3d, 0x3a, 0xb5, 0x32, 0x3e, 0xe1, 0x28, 0x87, 0x3e, + 0x44, 0x83, 0x09, 0x3e, 0xc3, 0x5f, 0x0a, 0xbe, 0xc4, 0xb8, 0x0f, 0xbe, + 0xaa, 0xb2, 0xab, 0xbd, 0x93, 0x40, 0x5c, 0xbd, 0x35, 0xf0, 0x19, 0xbd, + 0x4a, 0xa8, 0x02, 0x3b, 0x3c, 0x51, 0x1a, 0x3c, 0xbe, 0x2d, 0xdd, 0xbb, + 0x55, 0x5d, 0xc3, 0x3d, 0x10, 0x6f, 0x7c, 0xbd, 0x62, 0xf7, 0x45, 0xbe, + 0xd5, 0xda, 0xe1, 0x3d, 0x25, 0xd5, 0x13, 0x3e, 0xf0, 0xd6, 0xea, 0xbd, + 0x62, 0x2b, 0x56, 0xbd, 0x80, 0x0c, 0xb1, 0x3d, 0x19, 0xbe, 0xa5, 0x3d, + 0x3e, 0xc3, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6e, 0x76, + 0x32, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x0c, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0x43, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x9e, 0xc3, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x64, + 0x65, 0x6e, 0x73, 0x65, 0x2f, 0x52, 0x65, 0x6c, 0x75, 0x00, 0x00, 0x00, + 0xac, 0xc2, 0xff, 0xff, 0xde, 0xc3, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x63, + 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x5f, 0x31, 0x2f, 0x52, 0x65, 0x6c, 0x75, + 0x00, 0x00, 0x00, 0x00, 0xf8, 0xc2, 0xff, 0xff, 0x2a, 0xc4, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x22, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x2f, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x69, + 0x6e, 0x67, 0x32, 0x64, 0x5f, 0x31, 0x2f, 0x4d, 0x61, 0x78, 0x50, 0x6f, + 0x6f, 0x6c, 0x00, 0x00, 0x4c, 0xc3, 0xff, 0xff, 0x7e, 0xc4, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, + 0x5f, 0x31, 0x2f, 0x42, 0x69, 0x61, 0x73, 0x41, 0x64, 0x64, 0x00, 0x00, + 0x90, 0xc3, 0xff, 0xff, 0xc2, 0xc4, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x00, 0x00, 0x00, 0x00, 0xc4, 0xc3, 0xff, 0xff, 0xf6, 0xc4, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0xe0, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, + 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, 0x61, 0x64, + 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, 0x2f, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, 0x00, 0x00, + 0x20, 0xc4, 0xff, 0xff, 0x00, 0x38, 0x00, 0x00, 0x91, 0x78, 0x9e, 0x3d, + 0x02, 0x6e, 0x72, 0x3e, 0xaf, 0x5f, 0x65, 0xbc, 0x83, 0x89, 0xa5, 0x3e, + 0x99, 0x1e, 0xf5, 0x3d, 0xb4, 0x02, 0x92, 0x3d, 0xaf, 0x31, 0x96, 0x3d, + 0x44, 0x77, 0xa2, 0xbe, 0xf0, 0x3c, 0x73, 0xbe, 0x0f, 0xec, 0x35, 0x3f, + 0x47, 0xd1, 0x20, 0xbc, 0xae, 0x8d, 0x48, 0xbe, 0xce, 0xcc, 0x3d, 0x3e, + 0xad, 0x49, 0x78, 0x3e, 0x11, 0x2e, 0x82, 0xbd, 0xa7, 0xf3, 0x7e, 0x3d, + 0x7b, 0xea, 0x7a, 0x3d, 0xd1, 0xe5, 0x1f, 0x3e, 0x92, 0x8c, 0x7a, 0x3d, + 0xe8, 0x22, 0x46, 0xbe, 0xe4, 0x5c, 0x24, 0x3e, 0xa2, 0x0d, 0x6b, 0x3c, + 0xfb, 0x04, 0x21, 0xbd, 0x1c, 0x6e, 0xd1, 0xbe, 0xd5, 0xc6, 0xd9, 0xbc, + 0xb6, 0xe8, 0xdf, 0x3d, 0xd8, 0x73, 0x09, 0x3d, 0xcb, 0x45, 0xb1, 0xbe, + 0xda, 0x6a, 0x0e, 0x3d, 0x40, 0xbe, 0xef, 0xbc, 0xe4, 0xbb, 0xcb, 0xbd, + 0xf6, 0x35, 0x30, 0x3d, 0x25, 0x3a, 0x56, 0xbe, 0x1f, 0x35, 0x0a, 0x3d, + 0x95, 0x31, 0x21, 0x3d, 0xde, 0xaa, 0x54, 0xbe, 0x8d, 0x0a, 0x6b, 0x3e, + 0xd5, 0x70, 0x02, 0xbd, 0xdc, 0x18, 0xaa, 0x3c, 0x2a, 0x0c, 0x79, 0xbe, + 0xee, 0xc5, 0x04, 0x3b, 0x2c, 0xb9, 0xbe, 0x3d, 0x0f, 0x55, 0x82, 0xbc, + 0x94, 0xf6, 0x00, 0xbf, 0x0a, 0xa2, 0x02, 0xbe, 0xa3, 0x2b, 0x58, 0xbd, + 0x09, 0x4f, 0xd3, 0xbd, 0x57, 0x98, 0x36, 0xbe, 0xcd, 0xed, 0x81, 0xbe, + 0x78, 0x4d, 0x3b, 0xbd, 0xa1, 0xf9, 0xdc, 0xbd, 0x18, 0xc4, 0x29, 0xbd, + 0xf5, 0x6d, 0xb2, 0x3e, 0x43, 0x7b, 0x53, 0x3e, 0x2b, 0x6a, 0x69, 0x3c, + 0xec, 0x2e, 0x13, 0xbf, 0x6a, 0x0d, 0x2c, 0xbe, 0x3d, 0xe3, 0x32, 0x3e, + 0xf4, 0x41, 0x39, 0x3d, 0x48, 0xd3, 0x49, 0xbe, 0x7f, 0x25, 0x9a, 0xbe, + 0xd3, 0x36, 0x0b, 0xbe, 0xa5, 0xa3, 0x89, 0xbd, 0x09, 0x30, 0xe5, 0xbd, + 0x13, 0x17, 0x83, 0xbe, 0x1a, 0x4c, 0xc4, 0xbc, 0x81, 0x1e, 0x67, 0xbe, + 0x82, 0x77, 0xdf, 0xbd, 0x02, 0x7d, 0x33, 0x3e, 0xd3, 0x35, 0x02, 0x3e, + 0x8a, 0xc0, 0x90, 0x3c, 0x8b, 0xd0, 0x95, 0xbe, 0x2a, 0x67, 0x6f, 0xbe, + 0xf3, 0xf4, 0x20, 0x3e, 0x01, 0x28, 0xba, 0x3c, 0x55, 0x65, 0xaa, 0xbd, + 0x76, 0x1d, 0x90, 0xbd, 0xa5, 0x37, 0xce, 0x3d, 0x8f, 0xd7, 0x80, 0xbd, + 0x02, 0xea, 0x7d, 0xbc, 0xd1, 0xff, 0xe6, 0xbd, 0x96, 0xb5, 0xa0, 0x3d, + 0xea, 0xb0, 0x90, 0xbe, 0x9e, 0xed, 0x99, 0xbd, 0x2e, 0xee, 0xd9, 0x3d, + 0xe9, 0xf4, 0xb5, 0x3e, 0xa2, 0xb4, 0xfe, 0x3d, 0xe5, 0x4b, 0x30, 0x3d, + 0x07, 0xf3, 0x58, 0xbe, 0x29, 0xa8, 0x2a, 0x3b, 0xf6, 0x0c, 0x40, 0x3e, + 0xb9, 0x87, 0xc0, 0xbc, 0xf3, 0x12, 0x67, 0x3d, 0xd7, 0x33, 0x82, 0xbd, + 0xba, 0x47, 0x15, 0xbd, 0x64, 0xca, 0x29, 0x3e, 0xca, 0x70, 0x5d, 0x3e, + 0x9c, 0xa8, 0xcb, 0x3d, 0xbe, 0xe3, 0xaf, 0xbd, 0xaf, 0x93, 0x2c, 0xbe, + 0x07, 0x36, 0xb5, 0x3e, 0xfc, 0xff, 0x34, 0x3e, 0x71, 0x9a, 0xbb, 0xbd, + 0xa8, 0x92, 0x4b, 0x3e, 0xb1, 0x22, 0x29, 0xbe, 0xce, 0x5d, 0x3e, 0x3e, + 0xda, 0xca, 0x42, 0x3e, 0x20, 0x1a, 0x58, 0x3d, 0x5c, 0x0a, 0x0c, 0x3d, + 0xe2, 0xff, 0xf0, 0x3d, 0x79, 0xfd, 0x0c, 0x3e, 0x69, 0x5f, 0x03, 0x3e, + 0x66, 0xd3, 0x2e, 0xbb, 0x3a, 0x63, 0x64, 0x3c, 0x10, 0x2f, 0x48, 0xbe, + 0xc5, 0xa7, 0x47, 0xbe, 0xda, 0x5a, 0x97, 0x3e, 0xc1, 0x6e, 0xcd, 0xbc, + 0x9a, 0x9c, 0x51, 0xbd, 0x31, 0x27, 0xdd, 0x3d, 0x0b, 0xb2, 0x80, 0xbd, + 0xbf, 0x75, 0xa7, 0xbc, 0xd5, 0x65, 0x2f, 0x3e, 0xc4, 0x0d, 0x1b, 0x3e, + 0xcf, 0x7f, 0xf2, 0x3d, 0x73, 0xc7, 0xf2, 0x3d, 0x69, 0x2e, 0x98, 0xbb, + 0xa8, 0x5b, 0xa8, 0x3d, 0xfd, 0xb0, 0xbf, 0xbd, 0xa3, 0x49, 0xfc, 0xbd, + 0xad, 0xf5, 0x02, 0xbe, 0x60, 0x1e, 0x26, 0xbe, 0x1d, 0x96, 0x3d, 0x3e, + 0xf7, 0x23, 0x2c, 0x3e, 0x44, 0x1b, 0x86, 0x3d, 0x88, 0x56, 0x48, 0xbd, + 0xad, 0xf6, 0xee, 0x3c, 0x0d, 0x81, 0x13, 0x3d, 0xd0, 0x76, 0x09, 0x3e, + 0x49, 0x83, 0x83, 0xbd, 0x50, 0xd6, 0x79, 0xbe, 0x8c, 0x17, 0x4f, 0x3d, + 0xec, 0xe5, 0x90, 0x3d, 0x1e, 0x19, 0x4f, 0x3d, 0x1f, 0x3c, 0x9f, 0xbd, + 0xe5, 0x47, 0x4b, 0xbe, 0x33, 0xf0, 0x14, 0xbe, 0x58, 0xbf, 0x21, 0x3d, + 0xd2, 0x8c, 0x42, 0x3e, 0x31, 0xe6, 0x9a, 0x3d, 0xf9, 0x4e, 0xab, 0xbd, + 0x6f, 0x46, 0x1f, 0xbe, 0x9e, 0xf1, 0x21, 0x3d, 0x04, 0x72, 0xfb, 0x3d, + 0x29, 0xca, 0x24, 0x3e, 0x32, 0x01, 0xa1, 0xbe, 0x07, 0x9b, 0x45, 0xbe, + 0xf9, 0x09, 0xc5, 0x3d, 0xc9, 0x84, 0x44, 0xbd, 0xde, 0xb5, 0x68, 0xbd, + 0x0a, 0xf6, 0x3e, 0xbe, 0x78, 0x6e, 0xbc, 0xbd, 0x03, 0xf8, 0x38, 0xbd, + 0xe9, 0xf6, 0x17, 0xbd, 0x1a, 0x19, 0x3b, 0x3e, 0x43, 0xb1, 0xdb, 0x3c, + 0xc5, 0x5b, 0x1e, 0xbb, 0xcc, 0x9b, 0x00, 0xbe, 0x01, 0xe4, 0xe4, 0xba, + 0xe5, 0x8d, 0x26, 0x3e, 0x4b, 0x09, 0x0a, 0xbc, 0x50, 0x4e, 0xe0, 0xbe, + 0xe3, 0x93, 0xf3, 0xbc, 0xe8, 0xe9, 0x20, 0x3d, 0x23, 0xa7, 0xe2, 0x3c, + 0xe2, 0x05, 0xa7, 0x3d, 0xd4, 0xda, 0x29, 0xbd, 0xb3, 0x43, 0xa7, 0xbc, + 0x28, 0x61, 0x0d, 0xbd, 0x7e, 0x55, 0xa7, 0x3d, 0x5f, 0x27, 0x3f, 0x3e, + 0x12, 0x19, 0xca, 0x3b, 0xc9, 0x89, 0x0b, 0xbd, 0x57, 0x99, 0x33, 0xbd, + 0x61, 0x8f, 0xda, 0xbc, 0x6a, 0x54, 0x5a, 0x3e, 0x31, 0xeb, 0x2b, 0x3d, + 0x8c, 0x95, 0x97, 0xbe, 0x5b, 0x2d, 0x85, 0x3e, 0x49, 0x3f, 0xf4, 0xbc, + 0x20, 0xbb, 0x62, 0x3c, 0x01, 0x69, 0xae, 0xbd, 0xe1, 0x2c, 0x43, 0xbe, + 0xe9, 0x5d, 0x84, 0x3d, 0xb3, 0x61, 0x17, 0x3e, 0x47, 0x07, 0x95, 0xbc, + 0xcd, 0x7c, 0x87, 0x3e, 0xd9, 0xb3, 0x03, 0x3d, 0x1c, 0x7e, 0x15, 0x3d, + 0xe1, 0x0b, 0xb0, 0xbd, 0x23, 0xfe, 0x94, 0x3c, 0xf0, 0x36, 0xd7, 0x3d, + 0x9e, 0x2b, 0x82, 0x3c, 0x78, 0x43, 0x9b, 0xbe, 0xf9, 0x5d, 0x0c, 0xbe, + 0x07, 0x45, 0xda, 0x3d, 0x79, 0x36, 0x19, 0x3d, 0x49, 0xff, 0xbc, 0x3c, + 0xd6, 0x6e, 0xec, 0xbe, 0x6c, 0xb2, 0xd5, 0x3c, 0x2a, 0xb0, 0x92, 0x3b, + 0x45, 0x87, 0x3a, 0x3e, 0xd3, 0xe5, 0xb8, 0xbd, 0x92, 0x1a, 0x2e, 0x3c, + 0x9b, 0x33, 0x3c, 0x3e, 0x0f, 0x3d, 0xa8, 0xbe, 0x53, 0x7c, 0xa6, 0x3d, + 0x3b, 0x9e, 0x98, 0xbe, 0x96, 0x91, 0xd6, 0x3c, 0x71, 0x5b, 0x99, 0xbe, + 0x73, 0x0b, 0x04, 0x3e, 0xfa, 0x8a, 0xc0, 0x3d, 0x7f, 0x1b, 0xdd, 0x3d, + 0xe4, 0x01, 0x84, 0xbd, 0xcf, 0x63, 0xdb, 0xbd, 0xda, 0x5d, 0x8d, 0xbd, + 0x44, 0xe1, 0x46, 0xbd, 0x65, 0x6c, 0x05, 0xbe, 0x0a, 0x83, 0xb1, 0xbd, + 0x97, 0x4a, 0x59, 0xbe, 0x77, 0x26, 0xa7, 0x3d, 0x0d, 0x22, 0xea, 0xbd, + 0x70, 0x48, 0x14, 0xbe, 0x01, 0x31, 0x04, 0x3e, 0xe0, 0x5e, 0xb8, 0xbd, + 0xd3, 0xe3, 0xee, 0xbd, 0x4e, 0x6f, 0xc4, 0xbc, 0x2f, 0xab, 0x53, 0x3d, + 0xd2, 0x79, 0x2c, 0xbe, 0xea, 0x5e, 0xdb, 0xbd, 0x02, 0x40, 0x4d, 0xbd, + 0xcf, 0x47, 0x5d, 0xbd, 0x1e, 0x48, 0x97, 0xbd, 0x7c, 0x3b, 0xca, 0x3d, + 0x75, 0x1d, 0x43, 0xbe, 0xb7, 0xab, 0x86, 0x3b, 0xfa, 0x51, 0xe2, 0x3b, + 0xcc, 0x0c, 0x1a, 0xbe, 0xda, 0x56, 0xc0, 0x3d, 0xd2, 0xa5, 0x6b, 0xbd, + 0x46, 0xe8, 0x27, 0xbe, 0x95, 0x71, 0x4e, 0xbe, 0x78, 0xda, 0xb0, 0x3d, + 0xec, 0xfd, 0x31, 0xbe, 0x5f, 0xb5, 0x44, 0xbe, 0x2b, 0x48, 0x06, 0xbe, + 0x28, 0x5c, 0xf4, 0xbd, 0x1a, 0xb1, 0xa3, 0x3c, 0x77, 0xd6, 0xef, 0xbd, + 0xec, 0xe1, 0x93, 0xbd, 0x85, 0xb2, 0xcd, 0xbd, 0xf8, 0x0b, 0x52, 0xbd, + 0x16, 0x95, 0xd7, 0x3c, 0xb2, 0x00, 0x29, 0x3c, 0x42, 0x8c, 0xb6, 0x3d, + 0xa8, 0x79, 0x1f, 0xbe, 0xa5, 0xfe, 0xe8, 0xbd, 0x28, 0x30, 0xb8, 0x3d, + 0xb8, 0x23, 0x9e, 0x3d, 0x7f, 0xe1, 0x33, 0xbe, 0x2b, 0xf8, 0x3f, 0xbe, + 0x05, 0x8c, 0x70, 0xbd, 0x30, 0x32, 0xe0, 0xbd, 0xff, 0xd3, 0x45, 0xbe, + 0x29, 0x82, 0x33, 0xbc, 0x2b, 0x86, 0x13, 0xbe, 0x0b, 0x81, 0x07, 0xbd, + 0xb1, 0xd4, 0xa8, 0x3c, 0x42, 0xd6, 0x2d, 0xbc, 0xfc, 0x19, 0x33, 0xba, + 0xd5, 0xf7, 0x29, 0xbe, 0xff, 0xb9, 0x18, 0xbe, 0x34, 0x96, 0x36, 0xbe, + 0x8d, 0x80, 0xcc, 0xbd, 0x55, 0x1f, 0xe9, 0xbc, 0xa1, 0xdd, 0x69, 0xbe, + 0xd3, 0x86, 0xb4, 0x3c, 0x3a, 0xc2, 0x0f, 0xbe, 0xc0, 0x63, 0xcd, 0xbc, + 0xcb, 0xf8, 0xcf, 0xbd, 0x45, 0x7f, 0x5f, 0x3d, 0x95, 0x59, 0xbd, 0x3d, + 0x7b, 0x9c, 0xf0, 0xbd, 0x57, 0xaf, 0xfb, 0x3c, 0xad, 0x44, 0xaf, 0xbd, + 0xa5, 0xf3, 0xbc, 0xbd, 0xb4, 0xe1, 0x59, 0xbd, 0xa6, 0x28, 0x29, 0x3d, + 0xcb, 0x8b, 0x50, 0xbe, 0x20, 0x85, 0x95, 0xbd, 0x33, 0xcf, 0xfa, 0xbb, + 0xde, 0xfc, 0x1c, 0x3d, 0x91, 0xb6, 0x43, 0xbe, 0x54, 0x84, 0xaf, 0xbd, + 0xdc, 0xde, 0x04, 0xbe, 0x69, 0xc6, 0x19, 0xbe, 0x43, 0xcf, 0x23, 0xbd, + 0x77, 0x3b, 0x58, 0xbe, 0x50, 0x09, 0x50, 0xbd, 0x17, 0xa2, 0x2d, 0xbd, + 0xe0, 0xad, 0xb5, 0xba, 0x47, 0x9d, 0xcc, 0x3d, 0x06, 0x72, 0xe3, 0x3d, + 0x92, 0x81, 0x9f, 0x3c, 0x55, 0x1d, 0x06, 0xbe, 0xa0, 0x79, 0x9c, 0x3c, + 0xe1, 0xec, 0xe6, 0xbd, 0x63, 0x0c, 0x9a, 0xbd, 0xc1, 0x82, 0x5e, 0xbe, + 0x01, 0x4c, 0x38, 0xbe, 0x64, 0x06, 0x52, 0xbd, 0xd1, 0x54, 0x08, 0xbe, + 0x0c, 0xb8, 0xc2, 0x3d, 0x5a, 0xd2, 0xb4, 0x3d, 0x84, 0xcb, 0x24, 0xbe, + 0x80, 0xb4, 0x8f, 0x3c, 0x84, 0x69, 0x0c, 0xbe, 0x3d, 0xda, 0x05, 0xbe, + 0x4c, 0x48, 0x0c, 0x3e, 0xdc, 0x35, 0xcf, 0xbc, 0x80, 0x1b, 0x0b, 0xbe, + 0xaa, 0x3a, 0x9c, 0xbc, 0x21, 0xd4, 0x83, 0x3d, 0x26, 0x6e, 0xe8, 0x3d, + 0xe4, 0x41, 0x28, 0x3d, 0x88, 0x35, 0x1d, 0xbe, 0x2c, 0xfb, 0xb2, 0x3d, + 0xe4, 0xbb, 0x0a, 0xbe, 0x95, 0x00, 0xfe, 0xbd, 0x3d, 0x87, 0x89, 0x3c, + 0x19, 0x9f, 0xa0, 0xbc, 0xac, 0xce, 0x6f, 0xbd, 0x16, 0x48, 0x21, 0xbe, + 0xd8, 0x21, 0x13, 0xbe, 0x15, 0x49, 0xfc, 0xbd, 0x6c, 0x10, 0x31, 0x3e, + 0x93, 0x04, 0xa2, 0xbd, 0xbc, 0xce, 0xbe, 0xbd, 0x59, 0xce, 0x51, 0xbd, + 0xd6, 0xf1, 0x60, 0x3d, 0x3a, 0x92, 0x76, 0xbd, 0xb8, 0xef, 0x66, 0x3b, + 0x26, 0x2c, 0x8e, 0x3d, 0xf1, 0xff, 0x1e, 0xbe, 0xc2, 0x6f, 0x26, 0x3d, + 0xe7, 0xb2, 0x4e, 0xbe, 0x31, 0x1e, 0xc4, 0xbd, 0x2f, 0x0a, 0x81, 0x3c, + 0xb9, 0x73, 0xea, 0xbd, 0x41, 0xdf, 0x4b, 0xbd, 0x9d, 0x47, 0x88, 0xbd, + 0xab, 0x21, 0x68, 0xbd, 0x77, 0x20, 0xf9, 0xbc, 0x40, 0x55, 0xaa, 0x3d, + 0x26, 0xe1, 0xf9, 0xbd, 0x97, 0xbe, 0xdd, 0x3d, 0x57, 0xe8, 0x91, 0xbd, + 0x00, 0xbb, 0x3e, 0xbe, 0x22, 0x51, 0x91, 0xbd, 0x6b, 0xe6, 0xa1, 0x3d, + 0x7d, 0xf1, 0xa6, 0x3d, 0xa9, 0x89, 0x86, 0x3d, 0x57, 0x91, 0xef, 0xbd, + 0xcb, 0x06, 0x01, 0xba, 0x9d, 0xc0, 0x4a, 0x3d, 0x71, 0xaf, 0x35, 0xbe, + 0x01, 0x3d, 0x16, 0xbc, 0x01, 0xa4, 0x81, 0xbd, 0xa4, 0xf6, 0x2e, 0xbd, + 0xb7, 0xe9, 0x0d, 0xbd, 0x94, 0xef, 0x26, 0xbe, 0xee, 0x31, 0x20, 0xbe, + 0x43, 0x8a, 0x30, 0x3d, 0x09, 0xa3, 0xb1, 0xbd, 0x20, 0xb8, 0x11, 0x3c, + 0x55, 0x7c, 0x1e, 0x3d, 0xbd, 0x60, 0x4f, 0xbe, 0x05, 0x06, 0xa3, 0x3c, + 0x92, 0x48, 0xfa, 0xbb, 0x6a, 0x53, 0x0a, 0xbe, 0xd5, 0x01, 0x19, 0xbc, + 0x69, 0xf6, 0x2a, 0xbe, 0xf9, 0xbe, 0x08, 0xbe, 0x4b, 0x17, 0x49, 0x3c, + 0xb4, 0x10, 0x79, 0x3d, 0x4f, 0xb1, 0xf2, 0xbc, 0xc4, 0x6b, 0x8a, 0x3c, + 0x6c, 0xa7, 0x35, 0xbe, 0xe2, 0xfb, 0xe0, 0xbd, 0xf3, 0xc0, 0x2a, 0xbd, + 0xe6, 0x47, 0xbd, 0xbd, 0xc3, 0x30, 0x66, 0xbe, 0xfb, 0x2d, 0x35, 0x3d, + 0x13, 0xd6, 0xad, 0xbd, 0x7f, 0xd2, 0x01, 0xbe, 0x9e, 0xe1, 0x57, 0xbd, + 0x8c, 0x02, 0xe2, 0x3c, 0x21, 0x90, 0x11, 0xbe, 0x56, 0x8f, 0xab, 0x3d, + 0xba, 0x5b, 0xdc, 0x3d, 0xaa, 0x5e, 0x77, 0xbe, 0x1c, 0xc9, 0x64, 0x3d, + 0xfa, 0xf6, 0xd4, 0x3b, 0x72, 0x3d, 0x4a, 0x3d, 0x8c, 0xd5, 0x34, 0xbe, + 0x32, 0x30, 0xa8, 0x3b, 0x60, 0x0c, 0x8e, 0x3c, 0x7b, 0xc7, 0x30, 0x3d, + 0x86, 0x51, 0xb9, 0xbb, 0xed, 0x50, 0x0e, 0x3e, 0xb3, 0x70, 0x8a, 0xbc, + 0xc6, 0x3a, 0x1d, 0xbe, 0x77, 0x4d, 0x09, 0xbe, 0xb7, 0x5b, 0x39, 0xbd, + 0x23, 0xc9, 0x94, 0x3d, 0x8c, 0x6b, 0x7d, 0xbd, 0xc7, 0x7e, 0x45, 0xbe, + 0xf7, 0x39, 0xb8, 0xbd, 0x22, 0x46, 0x41, 0xbe, 0x9c, 0xcc, 0x64, 0x3c, + 0x97, 0xae, 0x94, 0xbd, 0xf9, 0x00, 0x8e, 0xbd, 0x34, 0xd3, 0xae, 0xbd, + 0x95, 0x7e, 0x4c, 0x3d, 0x16, 0x3f, 0x81, 0x3d, 0x77, 0x7e, 0x9b, 0xbc, + 0x47, 0x7b, 0x87, 0xbc, 0xb8, 0xc1, 0x14, 0xbe, 0x48, 0x64, 0xff, 0xbd, + 0x41, 0x09, 0xe2, 0xbc, 0xcb, 0x02, 0x2d, 0xbd, 0x52, 0x57, 0x26, 0xba, + 0x2b, 0x92, 0x83, 0xbd, 0x12, 0x88, 0x81, 0xbe, 0x11, 0x87, 0xe4, 0x3d, + 0xf6, 0x25, 0x51, 0xbe, 0xd5, 0x2d, 0xe9, 0xbd, 0xca, 0xc7, 0x6b, 0xbe, + 0x20, 0x33, 0x42, 0x3c, 0xfd, 0x3b, 0x54, 0xbe, 0xcc, 0x6d, 0x18, 0xbd, + 0x50, 0x31, 0x1f, 0xbe, 0x15, 0x5a, 0x48, 0x3e, 0x6a, 0xa8, 0x1e, 0x3e, + 0x1c, 0x72, 0x9d, 0xbe, 0xc2, 0xcf, 0x19, 0x3e, 0xda, 0x99, 0x3e, 0x3d, + 0x33, 0x9c, 0x84, 0xbf, 0xc3, 0xf1, 0x19, 0x3d, 0x3f, 0xf7, 0x24, 0xbd, + 0x29, 0x2a, 0xf7, 0x3e, 0x48, 0xf5, 0x48, 0xbe, 0xf4, 0xbc, 0xf4, 0xbd, + 0xed, 0x44, 0x7f, 0xbd, 0x3b, 0x94, 0x8a, 0x3e, 0xcd, 0x23, 0x5d, 0xbc, + 0x58, 0xdb, 0x8b, 0x3e, 0xe7, 0x74, 0xac, 0xbe, 0x6e, 0x53, 0x84, 0xbd, + 0x18, 0x4a, 0x4a, 0x3e, 0x96, 0x8c, 0xf1, 0x3d, 0xae, 0xc0, 0x5b, 0xbc, + 0x92, 0x87, 0x52, 0x3e, 0x51, 0xcd, 0x55, 0xbd, 0x2d, 0x96, 0x0f, 0x3d, + 0xba, 0xee, 0x95, 0xbc, 0x79, 0xf2, 0x32, 0x3e, 0x9a, 0x6c, 0xd8, 0xbe, + 0x67, 0xfd, 0x06, 0xbe, 0x47, 0x20, 0xd5, 0xbc, 0x67, 0x20, 0xf5, 0x3d, + 0xb6, 0x84, 0xbb, 0x3b, 0x20, 0x91, 0x3a, 0x3e, 0x86, 0x8c, 0x2b, 0xbe, + 0x94, 0x2b, 0xeb, 0x3d, 0x57, 0xbd, 0x17, 0x3e, 0x8f, 0x5f, 0x18, 0xbc, + 0x7d, 0x8f, 0x65, 0xbd, 0x99, 0x37, 0xc3, 0x3e, 0x04, 0x8c, 0xa8, 0xbd, + 0x8a, 0x8d, 0xd3, 0x3d, 0xdc, 0x19, 0xa9, 0xbd, 0x92, 0x13, 0x84, 0x3d, + 0x91, 0xb8, 0xa3, 0xbe, 0x7b, 0x31, 0x87, 0xbd, 0x5b, 0xf4, 0x29, 0xbb, + 0x99, 0x9a, 0x44, 0x3e, 0x7a, 0x99, 0x26, 0xbd, 0xe1, 0xd1, 0x03, 0x3e, + 0x37, 0xac, 0xa1, 0x3c, 0x46, 0xe3, 0x0d, 0x3e, 0xcc, 0xad, 0x96, 0x3d, + 0x34, 0xab, 0xf9, 0xbd, 0xcb, 0x7e, 0x36, 0xbe, 0x75, 0xa7, 0x8c, 0x3e, + 0x65, 0x58, 0x41, 0xbe, 0x12, 0x45, 0xa3, 0xba, 0xd5, 0x51, 0xe2, 0xbc, + 0xac, 0x2c, 0xc8, 0xbc, 0x8a, 0x1d, 0x70, 0xbe, 0x58, 0xb0, 0x65, 0x3c, + 0x00, 0x29, 0xdc, 0x3d, 0xf7, 0x94, 0x9d, 0x3e, 0x24, 0xfa, 0x84, 0xbd, + 0xa0, 0x06, 0xfe, 0x3d, 0x84, 0x08, 0x10, 0xbd, 0xf0, 0x0a, 0xc5, 0x3d, + 0xd4, 0xf2, 0xd3, 0x3c, 0xfd, 0xa3, 0xd5, 0xbd, 0xac, 0x95, 0x4e, 0xbb, + 0x0a, 0x6d, 0x99, 0x3e, 0x5a, 0x84, 0x1d, 0x3c, 0x56, 0x76, 0x8c, 0xbc, + 0xa3, 0xff, 0xa8, 0xbc, 0xb2, 0x9d, 0x4b, 0x3c, 0xe3, 0x87, 0x8b, 0xbe, + 0x30, 0xe9, 0xe6, 0xbd, 0x97, 0xf3, 0xef, 0xbc, 0x67, 0x40, 0x9f, 0x3e, + 0x7e, 0x95, 0x9c, 0xbd, 0xa1, 0xd7, 0xf4, 0x3d, 0x14, 0x05, 0x44, 0xbd, + 0x32, 0x50, 0x40, 0x3e, 0x7f, 0x4f, 0x0e, 0xbe, 0x24, 0xb4, 0x35, 0xbd, + 0xbb, 0x01, 0x13, 0xbe, 0x75, 0x97, 0x72, 0x3e, 0x72, 0xb5, 0xc4, 0xbc, + 0x2d, 0x03, 0xa3, 0xbe, 0x30, 0x9c, 0x85, 0xbd, 0xe9, 0x8a, 0xdd, 0x3d, + 0x66, 0x85, 0xe1, 0xbd, 0x00, 0x78, 0x16, 0xbe, 0xa6, 0xe0, 0x5d, 0xbd, + 0x39, 0xa7, 0x61, 0x3e, 0x40, 0xe9, 0xfa, 0xbd, 0x03, 0x1a, 0x78, 0x3e, + 0xae, 0x8a, 0x10, 0xbe, 0xff, 0x69, 0x73, 0x3d, 0x83, 0xc1, 0xd1, 0xbd, + 0xe9, 0xdc, 0x01, 0xbe, 0xef, 0xa7, 0x5f, 0x3d, 0x1d, 0xe3, 0x3f, 0x3e, + 0xe2, 0x74, 0x36, 0x3d, 0xda, 0xb4, 0x5d, 0xbe, 0xdf, 0x67, 0x56, 0xbd, + 0x3b, 0xe8, 0xca, 0x3d, 0xdb, 0x14, 0x21, 0xbe, 0x26, 0x0e, 0x21, 0xbe, + 0x70, 0xee, 0xce, 0xbd, 0xce, 0xd1, 0x8d, 0x3e, 0xf7, 0x98, 0xdb, 0xbd, + 0x76, 0xd8, 0x78, 0x3d, 0xd9, 0xc5, 0x25, 0xbe, 0x7b, 0x1e, 0x97, 0x3d, + 0x36, 0x31, 0x11, 0xbe, 0x1b, 0x15, 0x09, 0xbe, 0x20, 0xa6, 0x0b, 0xbb, + 0x25, 0xa1, 0xa0, 0x3e, 0x1b, 0xb2, 0xbb, 0xbd, 0x6d, 0x78, 0x9f, 0xbe, + 0xdf, 0xfb, 0x6a, 0xbd, 0xae, 0xdc, 0xc3, 0xbd, 0xb2, 0xe1, 0x8a, 0xbe, + 0x5a, 0x86, 0x90, 0xbd, 0x36, 0x2c, 0x91, 0xbd, 0xa1, 0x4f, 0x8b, 0x3e, + 0xef, 0x10, 0x11, 0xbd, 0x4e, 0xcc, 0xa8, 0x3d, 0x54, 0xf0, 0x7b, 0xbe, + 0x28, 0x40, 0x60, 0x3d, 0xa8, 0x9b, 0x00, 0x3d, 0xcf, 0xce, 0x28, 0xbd, + 0x3c, 0x31, 0x48, 0x3d, 0x41, 0xa1, 0xa4, 0x3e, 0xa7, 0x2a, 0x8b, 0xbe, + 0xdf, 0xd5, 0xf4, 0xbd, 0xac, 0xc1, 0xa2, 0x3b, 0xc4, 0x44, 0xcf, 0xbc, + 0x4f, 0x37, 0x5b, 0xbe, 0x67, 0xc2, 0x4c, 0xbd, 0x8b, 0x10, 0xb2, 0x3c, + 0x50, 0xe3, 0x26, 0x3e, 0xf8, 0x5e, 0xc2, 0x3d, 0x03, 0x12, 0x05, 0x3e, + 0x62, 0xbd, 0x1f, 0xbe, 0x1c, 0xec, 0xfe, 0x3d, 0x6e, 0x47, 0x50, 0x3d, + 0x60, 0x32, 0x89, 0x3d, 0xac, 0x39, 0x92, 0xbd, 0x23, 0x38, 0x3c, 0x3e, + 0x5f, 0x1e, 0x76, 0x3c, 0x94, 0xe2, 0x19, 0xbd, 0x7a, 0xd7, 0xc8, 0xbc, + 0xd8, 0xe3, 0x91, 0x3b, 0x0d, 0x26, 0x99, 0xbe, 0x2a, 0xad, 0x5d, 0xbe, + 0x94, 0x8e, 0x89, 0x3c, 0x4f, 0x99, 0xe6, 0x3d, 0x38, 0xd7, 0x98, 0x3b, + 0xe2, 0x9d, 0x12, 0x3e, 0xe8, 0xb1, 0x8e, 0xbe, 0x2c, 0x89, 0x28, 0x3e, + 0x8f, 0xd0, 0x3e, 0x3e, 0x22, 0x07, 0x50, 0xbd, 0x01, 0x49, 0xb5, 0xbd, + 0x06, 0x7e, 0x76, 0x3e, 0xaa, 0x8b, 0xa0, 0xbd, 0xa5, 0x43, 0x4a, 0xbc, + 0xe5, 0x64, 0x84, 0xbc, 0x7c, 0xb1, 0x36, 0x3d, 0xae, 0x00, 0xc6, 0xbe, + 0x7f, 0x17, 0x0a, 0xbe, 0xa0, 0x53, 0x3c, 0xbd, 0xb2, 0x43, 0xab, 0x3d, + 0xf2, 0xff, 0xcd, 0x3d, 0x42, 0xde, 0xb6, 0x3e, 0x36, 0x05, 0x07, 0xbf, + 0x2d, 0x47, 0x54, 0xbd, 0x73, 0xa0, 0x21, 0x3e, 0xc1, 0x61, 0x05, 0x3e, + 0x27, 0x6b, 0xcb, 0xbd, 0x9a, 0xe1, 0x5b, 0x3e, 0xfe, 0xdc, 0x33, 0xbd, + 0x12, 0x05, 0x2f, 0x3e, 0x84, 0x79, 0x28, 0xbc, 0x3e, 0x68, 0x66, 0xbe, + 0x7a, 0x0f, 0xb7, 0xbe, 0xcb, 0xff, 0xa6, 0xbe, 0xe1, 0x4c, 0x6c, 0x3d, + 0x10, 0x0f, 0xbe, 0x3e, 0x90, 0xef, 0xd3, 0xbd, 0x20, 0x98, 0x8e, 0x3e, + 0x3f, 0x83, 0xbb, 0xbe, 0x22, 0x0e, 0xc8, 0xbd, 0x5b, 0x94, 0x08, 0x3e, + 0xf7, 0x96, 0x9b, 0x3d, 0x53, 0x44, 0x46, 0xbd, 0x31, 0x47, 0xbc, 0x3d, + 0x4b, 0x9a, 0xee, 0xbd, 0x38, 0xf9, 0x35, 0x3d, 0xce, 0xb3, 0x3e, 0x3d, + 0x9b, 0x95, 0x95, 0x3d, 0xcd, 0x6f, 0xbf, 0xbe, 0x13, 0x43, 0x6a, 0xbe, + 0xa7, 0x4d, 0xe2, 0x3d, 0x76, 0xb4, 0x0f, 0x3f, 0x8e, 0x98, 0xa0, 0xbc, + 0x26, 0x91, 0x33, 0xbe, 0xc6, 0x43, 0x7c, 0xbe, 0xbe, 0x98, 0xd5, 0xbc, + 0x48, 0x72, 0x43, 0x3e, 0xf7, 0x74, 0x88, 0xbc, 0xc2, 0x58, 0xec, 0x3c, + 0xb6, 0x16, 0xa7, 0x3d, 0x17, 0x8c, 0x39, 0x3e, 0x84, 0xf5, 0x00, 0xbe, + 0xc4, 0xa8, 0xe2, 0x3d, 0x56, 0xc9, 0x22, 0x3e, 0xa1, 0x57, 0x96, 0xbe, + 0x06, 0x1c, 0x97, 0xbd, 0xda, 0x24, 0x82, 0x3d, 0xb1, 0xaf, 0x1e, 0xbc, + 0x4c, 0x3d, 0x7e, 0xbe, 0xca, 0xe6, 0xbc, 0x3d, 0xec, 0xd0, 0x93, 0xbd, + 0x13, 0x35, 0xeb, 0xbd, 0xbb, 0x88, 0x0e, 0xbd, 0xeb, 0x2b, 0xf4, 0x3c, + 0x13, 0x20, 0x46, 0x3e, 0x54, 0x9f, 0x78, 0xbd, 0x3d, 0x11, 0x44, 0xbd, + 0xb2, 0x3d, 0xe3, 0xbd, 0x3d, 0x61, 0xa8, 0x3d, 0xa9, 0xf8, 0x10, 0xbd, + 0xe2, 0xdc, 0x94, 0x3d, 0x14, 0x39, 0xde, 0xbd, 0x65, 0xa4, 0xde, 0xbd, + 0x72, 0xdd, 0x92, 0xbd, 0xb3, 0x05, 0x53, 0xbd, 0xcf, 0x8d, 0x1d, 0x3d, + 0x0a, 0x84, 0xa3, 0xbe, 0x5c, 0x03, 0x86, 0xbe, 0x16, 0xb6, 0xcb, 0xbe, + 0x30, 0x14, 0xfd, 0xbe, 0xe8, 0xfe, 0x3f, 0x3d, 0xec, 0x02, 0xc4, 0xbd, + 0x55, 0x4b, 0x99, 0x3c, 0xcb, 0xa5, 0x1e, 0xbd, 0xb4, 0x06, 0x82, 0x3d, + 0x2a, 0xd8, 0x92, 0xbe, 0x49, 0xea, 0xa1, 0xbd, 0x25, 0x78, 0x7b, 0xbe, + 0x34, 0xd6, 0xe8, 0xbd, 0xf3, 0x6a, 0x95, 0xbe, 0xde, 0x0c, 0xa1, 0xbd, + 0x81, 0xac, 0x78, 0x3b, 0x55, 0xa5, 0xd6, 0xbe, 0xfb, 0xfa, 0x65, 0xbe, + 0xd2, 0xa0, 0xb2, 0xbe, 0x01, 0x33, 0x4f, 0xbe, 0xc6, 0xa4, 0x8f, 0x3d, + 0xb4, 0x35, 0x72, 0x3d, 0xfd, 0xfb, 0xc4, 0xbc, 0x8a, 0xc5, 0x02, 0xbc, + 0x2e, 0x81, 0x1b, 0x3d, 0xf1, 0x88, 0x71, 0xbe, 0x10, 0xe7, 0x23, 0xbe, + 0xe5, 0xd4, 0x9b, 0xbd, 0x7e, 0x92, 0x9c, 0xbd, 0x32, 0x5d, 0xa5, 0xbe, + 0x3f, 0x12, 0x10, 0xbd, 0x06, 0xa8, 0xa4, 0xbc, 0x81, 0xe0, 0xb0, 0xbe, + 0x1a, 0xef, 0x5d, 0xbe, 0x15, 0x9b, 0xca, 0xbe, 0xa3, 0xb4, 0x36, 0xbe, + 0x80, 0x96, 0x54, 0xbd, 0x27, 0x41, 0xd4, 0xbc, 0xe1, 0x96, 0x7a, 0x3d, + 0xb0, 0xf1, 0xa4, 0x3d, 0x2e, 0x9f, 0x37, 0x3d, 0x69, 0x19, 0xa0, 0xbe, + 0xb0, 0xd8, 0xae, 0x3d, 0x42, 0x80, 0x0e, 0xbd, 0xbe, 0xbb, 0x8d, 0x3c, + 0x1f, 0x71, 0x93, 0xbe, 0xe5, 0x13, 0xa0, 0xbc, 0x55, 0xb8, 0xcd, 0xbc, + 0x55, 0xf0, 0xc0, 0x3b, 0xba, 0xe0, 0x2c, 0xbe, 0xa5, 0x38, 0xfc, 0xbe, + 0x03, 0x08, 0xdc, 0xbd, 0x8c, 0x72, 0x14, 0xbd, 0xef, 0xd9, 0x67, 0x3c, + 0xc9, 0x03, 0xe0, 0x3d, 0x69, 0xe2, 0xa0, 0x3d, 0xe1, 0x86, 0x06, 0x3d, + 0xa4, 0x52, 0x90, 0xbe, 0xda, 0x56, 0x29, 0xbc, 0x2b, 0x9c, 0xbd, 0x3d, + 0x12, 0xf7, 0xf3, 0xbc, 0x96, 0xad, 0x41, 0xbd, 0xb6, 0x4a, 0x10, 0xbd, + 0x7a, 0xee, 0xb5, 0xbd, 0x88, 0x83, 0xaa, 0x3d, 0xae, 0x03, 0xbd, 0xbe, + 0x4d, 0xaf, 0xe1, 0xbe, 0x32, 0x22, 0x4a, 0x3c, 0x6b, 0xa2, 0x90, 0xbd, + 0x7e, 0x81, 0x95, 0xbd, 0xc7, 0xe1, 0xbc, 0x3d, 0x56, 0x42, 0x7e, 0xbd, + 0xb4, 0xdb, 0xcb, 0x3d, 0xfe, 0x8e, 0x0e, 0xbf, 0x68, 0xe9, 0x60, 0x3e, + 0xea, 0x83, 0xce, 0x3c, 0x04, 0x08, 0x6d, 0xbb, 0xff, 0xb2, 0x38, 0x3d, + 0x26, 0xe2, 0x82, 0x3c, 0x71, 0x20, 0x10, 0xbe, 0x82, 0x64, 0x13, 0x3e, + 0xa7, 0x1a, 0xc6, 0xbe, 0x3e, 0xe8, 0xc7, 0xbe, 0x30, 0x1e, 0xd8, 0x3d, + 0x66, 0x87, 0x50, 0xbe, 0x5d, 0xbf, 0x4b, 0xbe, 0xf9, 0x9e, 0xb8, 0xbd, + 0x22, 0x9e, 0x04, 0x3d, 0x89, 0x8f, 0x7a, 0x3d, 0x4a, 0xd9, 0x15, 0xbe, + 0x4f, 0x77, 0x5e, 0x3e, 0xc0, 0x19, 0x08, 0x3d, 0xe0, 0xd6, 0x47, 0xbd, + 0xfb, 0x2b, 0xb6, 0x3d, 0x64, 0xa3, 0xf1, 0x3c, 0x36, 0xee, 0xd1, 0xbd, + 0x3c, 0x60, 0x60, 0x3d, 0x23, 0xae, 0x75, 0xbe, 0xc8, 0x00, 0x89, 0xbe, + 0xc4, 0x9c, 0x22, 0x3e, 0xc9, 0x29, 0x88, 0xbe, 0xd5, 0x6a, 0xc2, 0xbe, + 0x87, 0x71, 0xca, 0xbd, 0x76, 0x80, 0xa3, 0xbc, 0x84, 0xcf, 0xbc, 0xbd, + 0x4c, 0xac, 0x17, 0xbe, 0xaa, 0xd8, 0x91, 0x3e, 0xa9, 0x44, 0x52, 0x3c, + 0xc0, 0xee, 0xfa, 0xbd, 0x2c, 0x3b, 0x24, 0x3d, 0xc8, 0x0a, 0x8c, 0x3d, + 0x37, 0x10, 0x07, 0x3d, 0x98, 0x78, 0xdf, 0x3d, 0x0c, 0xe2, 0xe5, 0xbd, + 0x2c, 0x38, 0x34, 0xbe, 0xe5, 0x49, 0xb7, 0xbd, 0xc7, 0xcf, 0xd8, 0xbe, + 0x54, 0xf3, 0x6e, 0xbe, 0x2d, 0xbc, 0x19, 0xbe, 0xe4, 0x0f, 0x8d, 0x3d, + 0xf1, 0x48, 0xdc, 0xbd, 0xa2, 0x21, 0xdc, 0x3c, 0x86, 0x4c, 0x9d, 0x3e, + 0x93, 0xcd, 0xe7, 0x3d, 0x30, 0x77, 0xbf, 0xbd, 0xe0, 0xd2, 0x9f, 0xbc, + 0x55, 0x3a, 0x8e, 0x3d, 0xf2, 0x3f, 0x6e, 0xbe, 0xfc, 0xb4, 0x96, 0x3d, + 0xf3, 0xfe, 0xa1, 0xbe, 0x80, 0xf7, 0xfc, 0xbd, 0x34, 0xd4, 0x6c, 0x3d, + 0x31, 0x5b, 0x44, 0xbe, 0xcc, 0x50, 0x45, 0xbe, 0xd0, 0x2b, 0xf4, 0xbd, + 0xf7, 0x13, 0x02, 0xbe, 0x60, 0x08, 0xd7, 0xbd, 0xe5, 0x7e, 0x95, 0x3e, + 0xed, 0x1e, 0x7e, 0x3e, 0x30, 0x5f, 0x10, 0x3d, 0xc7, 0xf2, 0x47, 0xbe, + 0x69, 0x3c, 0x8e, 0x3b, 0xf1, 0xee, 0x51, 0xbd, 0x8e, 0x09, 0x41, 0xbe, + 0x6f, 0x3e, 0xbf, 0x3d, 0x30, 0x8d, 0x09, 0xbe, 0xc1, 0xa9, 0x19, 0xbe, + 0xa7, 0xb8, 0x96, 0xbd, 0x6c, 0xd8, 0x82, 0x3c, 0x45, 0x45, 0x3d, 0xbd, + 0x81, 0xb8, 0x4f, 0xbe, 0xd5, 0xe1, 0x32, 0x3d, 0x26, 0x85, 0x51, 0xbc, + 0x86, 0x09, 0x8b, 0x3e, 0xa3, 0x45, 0x87, 0x3e, 0x50, 0xeb, 0x52, 0x3e, + 0x17, 0xe7, 0x97, 0x3c, 0xc6, 0x63, 0x18, 0xbe, 0x34, 0xff, 0xd4, 0xbc, + 0xf9, 0xdc, 0xe5, 0xbe, 0x4c, 0x05, 0x86, 0x3d, 0xee, 0x91, 0xf2, 0xbc, + 0x9f, 0x83, 0xfa, 0xbc, 0x02, 0x38, 0x9e, 0xbd, 0x7c, 0x8a, 0xfe, 0xbc, + 0x9a, 0x13, 0x02, 0xbe, 0xc3, 0xcf, 0x08, 0xbe, 0x49, 0xfb, 0x0d, 0x3d, + 0x17, 0xf6, 0x29, 0xbd, 0x88, 0x7a, 0xdd, 0x3e, 0x6f, 0x3b, 0x01, 0x3e, + 0x19, 0xdd, 0x5b, 0x3e, 0x47, 0xbc, 0x19, 0xbd, 0x43, 0xc4, 0x9a, 0xbd, + 0xdd, 0x16, 0x82, 0x3d, 0xd4, 0x08, 0x89, 0xbe, 0x1f, 0xb5, 0xd8, 0xbe, + 0x5a, 0x44, 0x69, 0x3d, 0x35, 0x15, 0x60, 0xbe, 0x06, 0x34, 0x9e, 0xbe, + 0xff, 0x5a, 0xa1, 0x3e, 0x61, 0x69, 0x86, 0xbe, 0x90, 0xee, 0x8a, 0xbd, + 0x4a, 0x55, 0x36, 0xbe, 0x43, 0x71, 0x57, 0x3d, 0xaa, 0xcb, 0x0e, 0xbf, + 0xb4, 0x91, 0x35, 0xbf, 0x6f, 0x5b, 0x0f, 0xbf, 0x40, 0x82, 0x03, 0x3e, + 0x07, 0x78, 0x98, 0xbe, 0xc2, 0x15, 0x90, 0xbe, 0xf9, 0x72, 0xaa, 0xbe, + 0xe2, 0x17, 0x9c, 0xbe, 0x08, 0x3e, 0xa9, 0x3d, 0x9f, 0xae, 0x1a, 0xbf, + 0xf0, 0xf7, 0x28, 0xbf, 0xe1, 0x8b, 0xfc, 0x3d, 0x5b, 0xb5, 0xec, 0xbe, + 0x64, 0x14, 0xa9, 0x3c, 0x5f, 0x3a, 0xdb, 0xbc, 0xc2, 0xc8, 0xd3, 0xbd, + 0x1d, 0xa2, 0x52, 0xbf, 0x8d, 0x06, 0x85, 0x3d, 0x59, 0xfc, 0xaf, 0xbe, + 0xab, 0xa1, 0xb0, 0x3c, 0x4d, 0xa0, 0x2f, 0xbe, 0x04, 0x1f, 0xd3, 0xbb, + 0x20, 0x49, 0xe2, 0xbc, 0x12, 0xc7, 0x02, 0x3d, 0x76, 0x89, 0x31, 0x3e, + 0xb6, 0x83, 0x48, 0x3d, 0x40, 0xc8, 0xcf, 0xbe, 0xa3, 0x9e, 0xdb, 0x3d, + 0x2f, 0x47, 0x2a, 0x3e, 0xa8, 0xf7, 0x8d, 0xbf, 0xfc, 0x3a, 0x25, 0x3d, + 0x82, 0x9c, 0xb3, 0xbd, 0xe4, 0x25, 0x05, 0x3e, 0x56, 0x7d, 0xc0, 0xbe, + 0x7c, 0xec, 0x9f, 0x3d, 0x48, 0x59, 0x8e, 0xbd, 0xcd, 0x4b, 0x91, 0x3e, + 0x53, 0x6e, 0x0d, 0x3e, 0x91, 0x99, 0x93, 0x3e, 0x54, 0x9c, 0x16, 0xbe, + 0x68, 0x8f, 0xbf, 0x3d, 0xbd, 0xef, 0xa1, 0x3d, 0xc4, 0x83, 0xcf, 0x3b, + 0xd6, 0xc4, 0xc1, 0xbe, 0x11, 0x47, 0x3e, 0x3e, 0x37, 0x5c, 0xf5, 0xbd, + 0xc2, 0x27, 0xdb, 0x3d, 0x5d, 0x79, 0x97, 0xbd, 0x98, 0xf2, 0x0c, 0x3d, + 0x6c, 0xa6, 0xaa, 0xbe, 0xd9, 0x30, 0xb5, 0xbd, 0x90, 0x05, 0x01, 0x3a, + 0xf1, 0x8f, 0x35, 0x3e, 0x82, 0x5d, 0xfd, 0xbc, 0xc7, 0xf8, 0x54, 0x3e, + 0x9e, 0xe1, 0xd8, 0xbc, 0xba, 0x9d, 0x23, 0x3e, 0xc5, 0x87, 0x06, 0x3e, + 0x6b, 0xe7, 0xee, 0x3c, 0xae, 0x75, 0x2a, 0xbe, 0x9b, 0xf5, 0x2c, 0x3e, + 0xc5, 0x58, 0x9d, 0xbc, 0x4b, 0x3b, 0xbb, 0x3c, 0x96, 0x33, 0x7a, 0x3c, + 0xd7, 0xd3, 0xab, 0xbe, 0x33, 0x03, 0x5e, 0xbe, 0x24, 0xe4, 0x2c, 0x3d, + 0xc6, 0x66, 0x09, 0xbc, 0x6f, 0x16, 0x8e, 0x3e, 0xd1, 0x41, 0x07, 0x3b, + 0x25, 0xcc, 0x40, 0x3e, 0xe2, 0x18, 0x0f, 0x3d, 0x15, 0xe8, 0x82, 0x3e, + 0x41, 0x69, 0xdb, 0x3d, 0xe1, 0x27, 0x37, 0xbe, 0x00, 0x5b, 0x9d, 0xbe, + 0xcb, 0xdb, 0x40, 0x3e, 0xe1, 0x3c, 0xea, 0xbb, 0x0e, 0x61, 0x30, 0xbc, + 0xba, 0x37, 0x95, 0x3d, 0xfd, 0x17, 0xf3, 0xbe, 0xc2, 0x74, 0x50, 0xbe, + 0x9a, 0x7a, 0x7e, 0xbd, 0x30, 0x34, 0xc3, 0x3d, 0x54, 0xe2, 0xc3, 0x3e, + 0x6d, 0xa6, 0x9a, 0x3d, 0xaf, 0xa2, 0x88, 0x3e, 0xd8, 0x11, 0x71, 0x3d, + 0x4b, 0xa8, 0x26, 0x3e, 0xb8, 0x2f, 0xeb, 0x3b, 0x65, 0x63, 0x2b, 0xbd, + 0x92, 0xd3, 0x37, 0x3c, 0x95, 0xf0, 0xa6, 0x3e, 0x00, 0x6a, 0x15, 0xbe, + 0x36, 0x55, 0x37, 0x3d, 0x1d, 0x59, 0x4d, 0x3d, 0x7c, 0xdf, 0x91, 0xbe, + 0xa2, 0x6b, 0x6f, 0xbe, 0x27, 0x4b, 0xe2, 0x3c, 0x1a, 0x00, 0x50, 0xbc, + 0xe9, 0x40, 0x9e, 0x3e, 0x99, 0xaa, 0x01, 0xbe, 0xc2, 0x58, 0x70, 0xbb, + 0x83, 0x8c, 0xd9, 0x3c, 0x0d, 0x3e, 0xe6, 0x3d, 0xf1, 0x6f, 0x81, 0x3d, + 0xcb, 0x83, 0x7b, 0x3c, 0x78, 0xff, 0x3b, 0xbd, 0xe1, 0xae, 0x79, 0x3e, + 0x27, 0x72, 0x85, 0xbc, 0x72, 0xb1, 0x0a, 0xbe, 0xda, 0xc6, 0x8a, 0x3b, + 0x6b, 0xe5, 0x14, 0xbe, 0xaf, 0x70, 0x9e, 0xbd, 0xa2, 0xba, 0xc3, 0x3d, + 0x3e, 0x64, 0x10, 0xbd, 0x6c, 0xe2, 0x94, 0x3e, 0x65, 0x6a, 0x08, 0xbd, + 0x70, 0x66, 0x9a, 0x3e, 0x9e, 0x96, 0xd4, 0xbd, 0xb3, 0xcd, 0x70, 0x3e, + 0x93, 0x7a, 0x0f, 0x3e, 0x8c, 0x94, 0xfa, 0xbd, 0xa4, 0x90, 0x63, 0xbd, + 0xcb, 0x07, 0x88, 0x3e, 0x06, 0xab, 0x48, 0x3d, 0x44, 0x5b, 0x3e, 0x3e, + 0x7f, 0xd6, 0x6c, 0xbb, 0x04, 0xed, 0xf0, 0xbc, 0xe4, 0xc3, 0x7c, 0xbe, + 0x26, 0x94, 0x2c, 0xbd, 0x4a, 0x27, 0xcc, 0x3c, 0xfc, 0xc2, 0x92, 0x3e, + 0x82, 0x1b, 0x27, 0xbd, 0x9a, 0xf4, 0x9b, 0x3e, 0x36, 0x53, 0x8b, 0xbe, + 0x0f, 0x9c, 0x8c, 0x3e, 0x2f, 0x68, 0x99, 0x3d, 0x20, 0x62, 0xaa, 0x3c, + 0xbb, 0x1e, 0x6f, 0xbc, 0x9d, 0x1f, 0x4b, 0x3e, 0x54, 0x7f, 0x45, 0xbe, + 0x9b, 0xba, 0xb5, 0x3c, 0x4b, 0x19, 0x2b, 0x3c, 0x99, 0xe6, 0xb7, 0x3c, + 0x0b, 0x45, 0x23, 0xbe, 0x78, 0xd6, 0x2a, 0x3d, 0x02, 0xe2, 0x07, 0x3e, + 0xd6, 0x26, 0x66, 0x3e, 0x3a, 0x28, 0x2b, 0xbe, 0x45, 0x77, 0x36, 0x3e, + 0x2e, 0xf3, 0xf6, 0xbd, 0xb9, 0x28, 0xea, 0x3d, 0x74, 0xdf, 0xdb, 0x3d, + 0x15, 0x69, 0x82, 0x3a, 0xe2, 0x89, 0xa1, 0x3c, 0x04, 0xfe, 0xb6, 0x3e, + 0xc2, 0x5b, 0x1c, 0x3e, 0x73, 0x7a, 0x08, 0xbd, 0x4d, 0x50, 0x0e, 0x3d, + 0xa0, 0x55, 0xb3, 0xbd, 0x18, 0x24, 0x1d, 0xbe, 0xbc, 0x5b, 0xd1, 0xbd, + 0xcc, 0xb0, 0xd9, 0x3d, 0xa1, 0x2c, 0x95, 0x3d, 0x79, 0x70, 0x08, 0xbe, + 0xeb, 0x53, 0x18, 0x3e, 0xc5, 0x8d, 0x43, 0xbd, 0x5d, 0xa3, 0x6e, 0x3d, + 0x66, 0x05, 0x0f, 0x3e, 0xa4, 0x01, 0x8b, 0xbd, 0x57, 0xb8, 0x47, 0xbd, + 0xe8, 0x97, 0x8a, 0x3e, 0x77, 0x52, 0x13, 0xbd, 0x40, 0xa2, 0xae, 0x3d, + 0x8f, 0xbc, 0xd4, 0x3a, 0x8b, 0x08, 0xd4, 0xbd, 0x56, 0x1a, 0x44, 0xbe, + 0x22, 0x87, 0x35, 0xbd, 0xb1, 0x29, 0xa9, 0xbc, 0xfe, 0x06, 0x8c, 0x3e, + 0xca, 0x88, 0x38, 0xbd, 0xba, 0xdd, 0x04, 0x3e, 0x66, 0x0b, 0xa9, 0xbe, + 0x9d, 0xa8, 0x9d, 0xbe, 0x55, 0x60, 0x32, 0x3b, 0x7d, 0x09, 0x09, 0x3e, + 0x4f, 0xb5, 0x3f, 0xbc, 0x71, 0x79, 0xde, 0x3e, 0x2d, 0xe9, 0x38, 0xbe, + 0x9a, 0x4c, 0x76, 0xbd, 0xa2, 0xe8, 0xa0, 0xbc, 0xf4, 0xc7, 0x10, 0xbd, + 0x71, 0x07, 0x20, 0x3c, 0x6d, 0xc8, 0x46, 0x3d, 0x44, 0x3c, 0x16, 0xbd, + 0xfb, 0x5a, 0x5a, 0x3e, 0x43, 0x01, 0xe0, 0xbe, 0x2e, 0xbf, 0x41, 0xbf, + 0x14, 0x7f, 0x1e, 0xbf, 0xcf, 0x88, 0x4d, 0xbf, 0x85, 0xb0, 0xa6, 0xbe, + 0x65, 0xd0, 0x9a, 0x3d, 0xad, 0xbe, 0x1c, 0xbc, 0xfa, 0xc1, 0x85, 0x3d, + 0xfc, 0x72, 0xae, 0xbd, 0x14, 0xf5, 0xc8, 0xbd, 0x0c, 0x20, 0x08, 0x3d, + 0xe8, 0x35, 0x63, 0x3e, 0xc0, 0xf5, 0x74, 0x3d, 0x8b, 0xd5, 0x0d, 0xbf, + 0x3d, 0x94, 0x4b, 0x3d, 0x69, 0xff, 0x33, 0xbe, 0x6f, 0xb7, 0x81, 0xbf, + 0x2a, 0x05, 0x5e, 0xbf, 0x21, 0x19, 0x94, 0xbe, 0x34, 0x2a, 0xed, 0xbe, + 0x10, 0xf1, 0x03, 0xbf, 0x40, 0xf0, 0x80, 0xbf, 0x08, 0x44, 0x1e, 0xbd, + 0x0d, 0x59, 0x46, 0xbf, 0x31, 0xa6, 0xb3, 0xbe, 0x58, 0x5d, 0xc4, 0xbe, + 0x1a, 0x05, 0x10, 0xbd, 0xa7, 0xe6, 0xe1, 0x3e, 0xf7, 0x06, 0xb9, 0xbe, + 0xce, 0xf0, 0xa0, 0xbf, 0xf4, 0xcf, 0x85, 0xbc, 0x3a, 0xe2, 0x82, 0xbe, + 0xfd, 0x85, 0x73, 0xbf, 0x24, 0x22, 0x55, 0xbf, 0x49, 0xad, 0x20, 0xbf, + 0x90, 0x58, 0x3f, 0xbc, 0x5d, 0xbf, 0xb4, 0xbe, 0x23, 0x4b, 0x61, 0xbf, + 0x4a, 0x9a, 0x4d, 0x3c, 0x7a, 0x20, 0x3d, 0xbf, 0xfd, 0x8c, 0xa1, 0x3b, + 0xc4, 0x15, 0x0e, 0xbe, 0x2a, 0xb8, 0x56, 0xbc, 0xb5, 0x41, 0x0c, 0x3f, + 0xf6, 0xfa, 0xcf, 0xbe, 0x3b, 0x1c, 0xfc, 0xbe, 0xb5, 0x8b, 0x24, 0xbd, + 0xca, 0xe0, 0xfd, 0x3e, 0x2a, 0xf3, 0xc9, 0x3e, 0x58, 0x8b, 0x1a, 0x3b, + 0x06, 0x37, 0x0f, 0x3f, 0xe5, 0x79, 0x5a, 0xbe, 0xcb, 0x6c, 0xa5, 0x3d, + 0x61, 0xa0, 0xc6, 0x3e, 0x86, 0x27, 0xbc, 0x3c, 0xee, 0x51, 0x4b, 0xbe, + 0x88, 0x4a, 0x0d, 0x3f, 0x16, 0x0e, 0x82, 0xbe, 0xca, 0xd9, 0xcd, 0xbd, + 0x53, 0x26, 0xec, 0x3e, 0x67, 0xa6, 0x89, 0x3e, 0x3e, 0x89, 0x21, 0x3d, + 0x89, 0x94, 0x8a, 0xba, 0x4e, 0xbc, 0x99, 0xbe, 0xb9, 0xf0, 0xdc, 0x3d, + 0x4b, 0x61, 0xa7, 0xbe, 0x5a, 0x3c, 0x2e, 0xbe, 0xa8, 0x2e, 0x82, 0x3e, + 0x68, 0x10, 0x85, 0xbd, 0x28, 0x23, 0x7c, 0xbe, 0x82, 0x7b, 0x01, 0x3e, + 0xc1, 0xc3, 0x1b, 0xbe, 0x60, 0x8b, 0xc5, 0x3e, 0xda, 0x55, 0xb1, 0xbd, + 0xd7, 0x46, 0x29, 0x3d, 0x8e, 0xff, 0xcd, 0x3d, 0xf1, 0xac, 0xe3, 0xbd, + 0x72, 0x78, 0x43, 0xbe, 0xd3, 0xed, 0x52, 0x3e, 0x6d, 0x9e, 0xe8, 0xbe, + 0x79, 0x9a, 0x9a, 0x3d, 0x98, 0xf6, 0x63, 0xbe, 0x0c, 0x7c, 0xbf, 0xbe, + 0x83, 0x45, 0x9a, 0x3e, 0x1d, 0x35, 0x0b, 0xbe, 0xc5, 0x00, 0x67, 0x3b, + 0x75, 0x3e, 0xf4, 0x3d, 0x77, 0x2b, 0x1b, 0xbe, 0xa5, 0xff, 0xeb, 0xbc, + 0xb6, 0xb3, 0x74, 0x3e, 0x52, 0xbf, 0x3d, 0xbd, 0x7a, 0xdc, 0x2d, 0xbe, + 0x8a, 0x5b, 0x3d, 0xbe, 0xfb, 0x30, 0x6d, 0xbe, 0x67, 0xd8, 0x35, 0x3e, + 0x0e, 0x72, 0xb8, 0xbe, 0xd2, 0x0a, 0x80, 0x3a, 0x43, 0x8b, 0x8d, 0x3d, + 0xd9, 0x8f, 0x9a, 0xbe, 0xba, 0x80, 0x8a, 0x3e, 0xc7, 0x1b, 0x02, 0xbd, + 0x2a, 0xae, 0x80, 0xbb, 0x31, 0x31, 0x08, 0x3e, 0x55, 0x75, 0x2f, 0xbe, + 0xad, 0xc6, 0x48, 0xbd, 0x43, 0x03, 0x7e, 0x3e, 0x48, 0xe4, 0xb8, 0xbd, + 0x75, 0xc6, 0xe8, 0xbd, 0x06, 0x3c, 0xa1, 0xbd, 0xd7, 0xa2, 0xc2, 0x3d, + 0xe5, 0x47, 0x07, 0x3e, 0xa6, 0x42, 0xdb, 0xbe, 0x6b, 0xbf, 0x05, 0xbe, + 0x84, 0x62, 0xc7, 0xbd, 0xb7, 0x2e, 0x97, 0xbe, 0xdc, 0x29, 0x9e, 0x3e, + 0x3b, 0xc5, 0x0a, 0x3d, 0x78, 0xd2, 0x28, 0xbd, 0x41, 0x00, 0x0b, 0xbc, + 0x08, 0xcc, 0xc4, 0xbc, 0xa2, 0x88, 0xe0, 0xbd, 0x23, 0x48, 0xb0, 0x3e, + 0x00, 0x0e, 0x2f, 0xbe, 0x69, 0xb0, 0x84, 0x3d, 0x20, 0x9d, 0x83, 0xbd, + 0x53, 0xe3, 0xa6, 0xbc, 0x99, 0x74, 0xd1, 0x3d, 0x36, 0x33, 0x8d, 0xbe, + 0x35, 0x2e, 0xfe, 0xbc, 0xd4, 0xea, 0xf6, 0xbc, 0xc2, 0xf9, 0x90, 0xbe, + 0x72, 0x67, 0xa7, 0x3d, 0x44, 0xae, 0x08, 0x3e, 0xed, 0x6f, 0x49, 0x3d, + 0xed, 0xe3, 0xa4, 0xbd, 0xc8, 0x11, 0x25, 0x3d, 0xc0, 0x7b, 0x30, 0xbd, + 0x80, 0x6e, 0xf7, 0x3d, 0xb9, 0xe9, 0x94, 0xbe, 0x12, 0x80, 0xba, 0x3d, + 0x03, 0xfb, 0xac, 0xbc, 0xbe, 0x53, 0xf4, 0xbc, 0x8c, 0x26, 0x27, 0xbd, + 0x71, 0x13, 0xc7, 0xbe, 0x6e, 0xc5, 0x02, 0xbe, 0x54, 0x9d, 0x23, 0x3d, + 0xa5, 0xba, 0x39, 0xbe, 0xf0, 0x26, 0x52, 0x3d, 0xfe, 0xf6, 0xc9, 0x3d, + 0x94, 0x18, 0xa8, 0x3d, 0xe6, 0xd3, 0x1c, 0xbe, 0x9a, 0xd2, 0xe6, 0xbd, + 0xbc, 0xb0, 0x1d, 0xbd, 0x22, 0x0a, 0xb0, 0x3d, 0x09, 0x0f, 0x74, 0xbe, + 0x22, 0x36, 0xde, 0x3d, 0xab, 0xc6, 0x20, 0x3d, 0x5b, 0xeb, 0xd1, 0xbd, + 0xaf, 0x09, 0x1a, 0xbd, 0xaa, 0x83, 0x31, 0xbe, 0x2d, 0x9d, 0xbd, 0xbc, + 0x8f, 0xca, 0xb6, 0xbd, 0x8c, 0xa0, 0x4a, 0xbe, 0x4b, 0x67, 0xfa, 0x3d, + 0x3f, 0x99, 0x01, 0x3e, 0x0e, 0x64, 0xa5, 0x3d, 0xe3, 0xe9, 0xd3, 0xba, + 0x9d, 0x09, 0x6a, 0xbc, 0x28, 0x4f, 0x82, 0x3e, 0xe3, 0x4c, 0x0a, 0x3e, + 0x44, 0xfd, 0x6e, 0xbd, 0x94, 0xfc, 0xa0, 0x3d, 0x8e, 0x35, 0x83, 0x3d, + 0xef, 0x14, 0xea, 0xbc, 0x4b, 0xf9, 0x10, 0xbd, 0x42, 0x43, 0x8e, 0xbd, + 0x0b, 0x78, 0x3b, 0xbd, 0x0d, 0xc8, 0x2f, 0xbd, 0x43, 0xc0, 0xb1, 0xbe, + 0xf4, 0x92, 0x85, 0x3e, 0xfd, 0x10, 0xea, 0x3d, 0x3f, 0xa1, 0x89, 0x3d, + 0xb9, 0xd2, 0x2a, 0xbd, 0x6c, 0xfd, 0x99, 0xbe, 0x68, 0xf0, 0x22, 0x3e, + 0x4e, 0x3b, 0xf8, 0xbd, 0x6a, 0xa0, 0x18, 0xbe, 0x91, 0xee, 0x1d, 0xbd, + 0x48, 0x14, 0x0e, 0x3d, 0x64, 0x2b, 0xb2, 0xbd, 0x47, 0x34, 0xad, 0x3c, + 0xb1, 0x9c, 0xe7, 0xbd, 0xd0, 0x9a, 0x8b, 0x3d, 0xc6, 0x5b, 0xe5, 0x3d, + 0xa5, 0x1f, 0x21, 0xbe, 0x0a, 0x26, 0x0b, 0x3e, 0xa4, 0x1a, 0x5a, 0xbd, + 0x3e, 0x53, 0xe8, 0xbd, 0xda, 0xb5, 0xc9, 0x3b, 0xc0, 0x68, 0x53, 0xbe, + 0x85, 0x31, 0x9a, 0x3e, 0xf9, 0x64, 0x1a, 0x3e, 0xe0, 0x4a, 0x35, 0xbd, + 0xe8, 0xaa, 0xa0, 0xbd, 0xac, 0xf7, 0xbd, 0x3d, 0xbf, 0xbd, 0x1a, 0xbe, + 0x71, 0x3e, 0x41, 0x3d, 0x5c, 0x55, 0xfb, 0xbd, 0x4b, 0x79, 0xe0, 0x3d, + 0xc1, 0x0e, 0x87, 0x3d, 0xac, 0xfa, 0x83, 0xbd, 0x1c, 0x1b, 0x1e, 0x3e, + 0xcd, 0xe6, 0xa7, 0x3d, 0xdd, 0x2a, 0xa3, 0xbd, 0xd2, 0x48, 0xc0, 0x3c, + 0x4e, 0x4a, 0xd5, 0xbe, 0x61, 0xa2, 0x16, 0x3e, 0x97, 0xe6, 0x82, 0xbc, + 0x79, 0x7e, 0xaf, 0xbd, 0x3b, 0x1a, 0xfb, 0x3d, 0xc9, 0x30, 0x3d, 0x3e, + 0x3d, 0x27, 0x86, 0xbe, 0xd1, 0x31, 0x5f, 0x3e, 0x42, 0xfc, 0x99, 0xbe, + 0xe0, 0x82, 0xc3, 0x3d, 0x91, 0x85, 0xd1, 0x3d, 0x95, 0xbf, 0xea, 0x3c, + 0x39, 0x19, 0xe4, 0x3d, 0x4f, 0x40, 0x17, 0xbe, 0x5b, 0x8f, 0xd8, 0xbd, + 0x16, 0x8b, 0x63, 0xbd, 0xd5, 0x12, 0xa1, 0x3b, 0xc0, 0xd5, 0x68, 0x3c, + 0x55, 0x96, 0xb8, 0xbd, 0xbc, 0xf3, 0x9d, 0xbe, 0xf8, 0x79, 0xd5, 0x3d, + 0xac, 0x1a, 0xd5, 0x3d, 0xd0, 0x00, 0x14, 0xbd, 0xca, 0x1d, 0xad, 0xba, + 0x79, 0xed, 0x2c, 0xbe, 0xc4, 0x05, 0xc6, 0xbc, 0x0d, 0x1c, 0xf7, 0x3d, + 0xd8, 0xd0, 0x8f, 0xbd, 0x79, 0x0e, 0xba, 0x3d, 0x3c, 0x33, 0x30, 0x3d, + 0x2c, 0x08, 0xbf, 0xbd, 0x4a, 0x7a, 0x71, 0xbd, 0xc2, 0x3b, 0x45, 0xbc, + 0x7a, 0xac, 0x8e, 0xbe, 0x13, 0x06, 0xa9, 0xbd, 0x0e, 0xd4, 0x3b, 0xbe, + 0xc5, 0x4b, 0x47, 0x3d, 0x90, 0xb5, 0x85, 0xbd, 0xe5, 0xce, 0x0f, 0xbe, + 0x02, 0xae, 0xc7, 0x3c, 0xc1, 0x9c, 0x4d, 0xbe, 0x60, 0x07, 0x8f, 0x3d, + 0xa0, 0x57, 0x78, 0xbd, 0x48, 0x93, 0x86, 0x3e, 0xe4, 0xed, 0xfb, 0xbd, + 0x6e, 0x57, 0x74, 0x3d, 0x85, 0x53, 0x01, 0xbd, 0x56, 0xaf, 0xff, 0xbd, + 0xfe, 0xe2, 0x07, 0x3e, 0x13, 0xb4, 0xdb, 0xbd, 0xfb, 0x89, 0x9f, 0xbe, + 0x74, 0xb9, 0xe2, 0xbd, 0x4b, 0xac, 0x8c, 0x3e, 0xf8, 0x2a, 0x8d, 0xbd, + 0x76, 0x7d, 0x08, 0xbe, 0x39, 0xc8, 0x84, 0x3d, 0x62, 0x02, 0xe9, 0xbd, + 0xb0, 0x1c, 0xfc, 0x3a, 0x52, 0x7e, 0x06, 0xbd, 0xec, 0xf1, 0xce, 0xbc, + 0x00, 0x3b, 0xc3, 0x3c, 0x08, 0x4e, 0x3e, 0x3d, 0x5c, 0x19, 0x45, 0xbe, + 0xbb, 0x06, 0xbd, 0xbd, 0x9d, 0xbb, 0xf7, 0xba, 0x9c, 0xa5, 0x87, 0x3b, + 0x1a, 0x83, 0x85, 0xbd, 0xa3, 0x38, 0xeb, 0xbc, 0x83, 0x65, 0xdc, 0x3d, + 0x5c, 0x8d, 0x0e, 0x3e, 0xcf, 0x7f, 0xe6, 0x3b, 0x7d, 0x2c, 0xaf, 0xbd, + 0x9c, 0xc5, 0xaf, 0x3c, 0xd7, 0x6a, 0xc7, 0xbc, 0x7a, 0xc2, 0x48, 0xbe, + 0xfc, 0x1a, 0x0e, 0x3d, 0x9e, 0x97, 0x3e, 0x3d, 0xf1, 0x34, 0xd0, 0xba, + 0xbb, 0x60, 0xee, 0xbd, 0x85, 0x2f, 0xec, 0xbd, 0x8e, 0x6f, 0x7b, 0x3c, + 0xb9, 0x28, 0xfc, 0xbd, 0x4d, 0x84, 0x6b, 0xbe, 0x26, 0x35, 0x4c, 0xbd, + 0x8a, 0xe9, 0x2f, 0xbd, 0x2a, 0x11, 0x1c, 0xbe, 0xbb, 0x7e, 0x08, 0xbe, + 0xf6, 0x73, 0x81, 0xbe, 0x36, 0xfd, 0x11, 0xbd, 0x6a, 0x85, 0xcf, 0xbd, + 0x64, 0x9f, 0x93, 0xbd, 0xb8, 0xfe, 0xff, 0xbd, 0x4f, 0xa5, 0x19, 0xbe, + 0xd9, 0x9b, 0x34, 0xbe, 0x17, 0xac, 0xdb, 0x3a, 0x2d, 0x1c, 0xa1, 0xbd, + 0x22, 0x94, 0x90, 0x3c, 0xcb, 0xa9, 0x6d, 0xbb, 0x84, 0xad, 0x9e, 0x3d, + 0xfd, 0x9a, 0x11, 0xbe, 0x93, 0xaa, 0x13, 0x3d, 0x42, 0x61, 0x8b, 0xbd, + 0x5d, 0x21, 0x28, 0xbe, 0x15, 0x7b, 0xd2, 0xbd, 0xf5, 0x7e, 0x54, 0x3d, + 0xc1, 0x76, 0x81, 0x3d, 0x45, 0x13, 0xe7, 0xbd, 0xac, 0x4f, 0x12, 0x3d, + 0x8f, 0x1c, 0x0e, 0x3c, 0xb3, 0x6d, 0x70, 0x3c, 0x6b, 0xfa, 0xdc, 0xbb, + 0xd6, 0xab, 0xf9, 0xbc, 0x49, 0x49, 0xf7, 0xbc, 0x4b, 0x50, 0x59, 0xbd, + 0x10, 0x1e, 0xb0, 0xbd, 0x43, 0x71, 0x39, 0xbd, 0xd5, 0xe0, 0xc1, 0xbd, + 0x33, 0xc2, 0x5f, 0x3d, 0x48, 0xa7, 0x4a, 0xbe, 0xef, 0x8d, 0x35, 0xbe, + 0xa1, 0x9e, 0x3c, 0xbd, 0xb0, 0x3a, 0xa5, 0xbd, 0xa8, 0x9d, 0xa1, 0x3d, + 0x61, 0xb0, 0x73, 0x3d, 0xdb, 0x36, 0xbd, 0x3d, 0xa1, 0xc1, 0x03, 0xbd, + 0xde, 0x98, 0xac, 0xbd, 0xe1, 0x5a, 0xd4, 0x3d, 0x87, 0x04, 0xa1, 0xbd, + 0x22, 0xcf, 0x2f, 0x3d, 0xae, 0xf3, 0xc2, 0xbd, 0x7d, 0xe0, 0x30, 0xbe, + 0x1d, 0x36, 0x03, 0xbe, 0xc3, 0x86, 0xb1, 0x3d, 0x00, 0xf1, 0xbb, 0xbd, + 0x5f, 0x98, 0x56, 0xbd, 0xdd, 0xcd, 0x06, 0xbe, 0xcf, 0x6e, 0xa5, 0x3b, + 0xaa, 0xa0, 0x88, 0xbd, 0x99, 0x87, 0x56, 0xbe, 0x59, 0xce, 0x37, 0x3d, + 0xc6, 0x1e, 0x9c, 0xbb, 0xf9, 0x24, 0x1f, 0xbe, 0x6e, 0xee, 0x89, 0x3d, + 0xa5, 0xd5, 0xe8, 0xbb, 0xde, 0x47, 0x7d, 0xbd, 0xe3, 0x46, 0x26, 0xbe, + 0x8b, 0x42, 0xc7, 0x3d, 0xb5, 0xc8, 0xc6, 0xbd, 0x58, 0xc1, 0xc3, 0xbd, + 0x4d, 0x67, 0x4f, 0x3d, 0x9f, 0xdd, 0x2b, 0xbe, 0x13, 0x24, 0x02, 0xbd, + 0x8c, 0x30, 0x2a, 0xbd, 0x99, 0x30, 0x54, 0x3c, 0x86, 0xce, 0xa4, 0x3c, + 0x3c, 0xea, 0x1b, 0x3c, 0x18, 0x20, 0x00, 0x3d, 0xf5, 0x1d, 0x0a, 0xbc, + 0x3f, 0xc0, 0x74, 0x3d, 0x5a, 0x4f, 0xf4, 0xbb, 0x1e, 0x77, 0xc5, 0xbd, + 0xee, 0x16, 0xe4, 0xbd, 0xb5, 0xb4, 0x60, 0x3d, 0x25, 0xa2, 0xed, 0xbd, + 0x97, 0x7d, 0x9e, 0xbd, 0xc9, 0xd4, 0x36, 0xbd, 0xbf, 0x1b, 0xa6, 0x3c, + 0x6e, 0xb9, 0x27, 0x3d, 0xe8, 0x11, 0x2e, 0x3d, 0xa5, 0xae, 0xbd, 0xbd, + 0xdd, 0xfa, 0x04, 0xbd, 0xe6, 0x01, 0xe5, 0xbd, 0x03, 0x43, 0x65, 0x3d, + 0x36, 0xf4, 0x34, 0xbd, 0xc8, 0x33, 0xd1, 0xbd, 0x06, 0x06, 0x1c, 0xbe, + 0x17, 0x94, 0xb7, 0x3b, 0x64, 0x11, 0x01, 0xbe, 0xdf, 0xb1, 0xe6, 0xbd, + 0xa5, 0x78, 0x3b, 0xbe, 0x0d, 0x16, 0x58, 0xbd, 0x4b, 0xbc, 0xd4, 0x3c, + 0x92, 0x23, 0x52, 0xbe, 0x0d, 0x3f, 0x15, 0xbe, 0x8c, 0xd3, 0x52, 0xbe, + 0x00, 0xa4, 0x35, 0x3b, 0x37, 0x84, 0xa5, 0x3d, 0xdf, 0xe3, 0x02, 0xbe, + 0xbd, 0x99, 0x0f, 0xbe, 0x9d, 0x37, 0x33, 0xbe, 0x87, 0xd1, 0xfd, 0x3d, + 0xcb, 0x26, 0x39, 0xbe, 0x5d, 0x3f, 0x0e, 0xbe, 0xe6, 0xb1, 0x2d, 0xbe, + 0x4c, 0x00, 0x7d, 0xbd, 0xf7, 0x32, 0xd8, 0x3b, 0xc5, 0xcf, 0x86, 0x3d, + 0x60, 0x62, 0xa8, 0xbd, 0x44, 0x0d, 0x2f, 0xbe, 0xed, 0x1e, 0xf9, 0x3d, + 0x4d, 0x79, 0x51, 0xbe, 0x22, 0xdd, 0x50, 0xbe, 0x74, 0x3b, 0x1e, 0xbe, + 0xe1, 0x4f, 0x77, 0xbd, 0x00, 0x02, 0x1d, 0xbe, 0x07, 0x5f, 0x44, 0xbd, + 0x44, 0x3f, 0xb8, 0x3c, 0x9d, 0x95, 0xa9, 0xbb, 0x4a, 0xe8, 0x55, 0x3d, + 0xfd, 0x6e, 0x5d, 0x3d, 0xcd, 0x7c, 0x20, 0xbe, 0xb5, 0x23, 0xa2, 0x3d, + 0x92, 0xc8, 0x84, 0x3d, 0xa4, 0x4a, 0x3f, 0xbe, 0xae, 0xd7, 0x75, 0xbd, + 0x46, 0x5e, 0xc4, 0xbd, 0xb9, 0xe0, 0x24, 0xbd, 0x2a, 0xba, 0x83, 0xbd, + 0xdd, 0xfe, 0xd9, 0x3d, 0x98, 0x72, 0x94, 0x3d, 0x6b, 0x91, 0x09, 0x3d, + 0x51, 0xae, 0x88, 0x3b, 0x65, 0x6a, 0xd8, 0xbb, 0x44, 0x15, 0x17, 0xbe, + 0xcf, 0xbb, 0xb3, 0x3d, 0xfe, 0xa1, 0x3f, 0xbe, 0x57, 0xbf, 0xd8, 0xbd, + 0xcf, 0xc5, 0x9d, 0x3d, 0xce, 0x80, 0x59, 0xbc, 0x14, 0x38, 0xdd, 0xbd, + 0x49, 0xb5, 0x56, 0xbe, 0xc5, 0xb0, 0xa5, 0x3d, 0xa9, 0x99, 0x10, 0xbd, + 0x39, 0xea, 0x95, 0xbc, 0x73, 0x55, 0x3f, 0xbe, 0xfd, 0x57, 0x85, 0xbd, + 0x1d, 0xf1, 0x65, 0x3d, 0xab, 0xe9, 0x06, 0xbd, 0x77, 0x53, 0xf8, 0xbc, + 0xfa, 0xbc, 0x75, 0xbd, 0x32, 0xc8, 0x6c, 0x3d, 0xec, 0xfb, 0x42, 0xbe, + 0x4f, 0xe3, 0x8a, 0xbd, 0xc9, 0xd4, 0x32, 0xbc, 0xe0, 0x14, 0xbe, 0x3d, + 0xdd, 0xd2, 0x2a, 0xbe, 0x2d, 0xf3, 0x42, 0xbe, 0x8a, 0x61, 0x6e, 0x3d, + 0x34, 0xed, 0x85, 0xbb, 0xcc, 0x83, 0x3c, 0xbd, 0xf4, 0xe4, 0x8f, 0xbc, + 0x6a, 0x4e, 0x2a, 0xbd, 0x32, 0xb8, 0x1b, 0xbe, 0x45, 0xd2, 0x73, 0x3d, + 0xa9, 0x63, 0x64, 0xbd, 0x34, 0x23, 0xb5, 0xbd, 0xe3, 0x16, 0x00, 0x3e, + 0xa5, 0xac, 0xf0, 0xbd, 0x1c, 0xef, 0x3d, 0x3d, 0xfc, 0xba, 0xff, 0x3c, + 0xbc, 0xfa, 0x0b, 0x3d, 0xfc, 0xd7, 0x60, 0xbd, 0x91, 0x10, 0xdd, 0xbb, + 0xe4, 0x68, 0xbe, 0x3d, 0xa5, 0x92, 0x50, 0xbe, 0x98, 0x7b, 0x27, 0xbe, + 0x78, 0xe3, 0xa2, 0xbe, 0xa6, 0x63, 0x21, 0xbd, 0x6d, 0xe4, 0xc0, 0x3d, + 0x68, 0x9c, 0xd2, 0xbd, 0x5b, 0xb4, 0x11, 0x3e, 0xa7, 0xef, 0xa9, 0xbd, + 0xf9, 0xfb, 0x83, 0x3d, 0xd8, 0x6d, 0xe5, 0x3c, 0xc2, 0x59, 0x3d, 0xbd, + 0x6f, 0x02, 0xe3, 0xbc, 0xdd, 0x52, 0xcf, 0xbb, 0x58, 0x34, 0xbc, 0xbd, + 0xa9, 0x43, 0x45, 0xbd, 0x1e, 0xf0, 0x9a, 0xbd, 0x80, 0x43, 0x61, 0x3d, + 0x77, 0xa4, 0x72, 0xbe, 0xa1, 0x17, 0xb0, 0x3e, 0x36, 0x5f, 0xd1, 0x3e, + 0x12, 0x20, 0xfe, 0xbd, 0x5d, 0x4d, 0x7c, 0xbc, 0xfe, 0x0a, 0x3d, 0x3e, + 0xd5, 0xcf, 0x22, 0x3b, 0xdc, 0x2e, 0x0f, 0x3c, 0x79, 0x3c, 0x90, 0x3d, + 0xac, 0x83, 0x2e, 0x3d, 0x75, 0xce, 0x22, 0xbf, 0x23, 0x4e, 0x35, 0xbe, + 0x92, 0x0b, 0x61, 0x3c, 0xcf, 0xdc, 0x70, 0x3e, 0x3f, 0xe4, 0xa2, 0xbe, + 0x63, 0x01, 0x9e, 0x3d, 0x61, 0x05, 0xa8, 0xbe, 0x1b, 0x7d, 0xe0, 0x3d, + 0xb5, 0x3b, 0xcc, 0x3e, 0xdc, 0x97, 0x9d, 0xbe, 0x13, 0xf1, 0x60, 0x3d, + 0xfc, 0x5b, 0x95, 0xbd, 0xd8, 0x27, 0x06, 0x3e, 0x27, 0x3e, 0x65, 0xbd, + 0x3a, 0xc3, 0x7b, 0xbd, 0x77, 0xa5, 0x37, 0x3d, 0x30, 0x67, 0x14, 0xbe, + 0xf9, 0x10, 0xbe, 0xbe, 0x4f, 0xec, 0x53, 0x3e, 0xf5, 0x26, 0xd9, 0x3d, + 0x48, 0x3b, 0x2d, 0xbf, 0xd1, 0x9b, 0x35, 0xbc, 0x7e, 0x60, 0xae, 0x3d, + 0xdf, 0x08, 0x5b, 0x3d, 0x58, 0xe9, 0x9b, 0xbe, 0xb6, 0xc6, 0xf6, 0xbe, + 0x57, 0xa3, 0x45, 0x3e, 0xa1, 0x11, 0x19, 0xbd, 0xaf, 0xb8, 0x2d, 0x3e, + 0xb8, 0x9b, 0x06, 0x3e, 0xbc, 0x03, 0x31, 0xbe, 0x49, 0xe0, 0xa4, 0x3c, + 0x67, 0x3d, 0x16, 0xbd, 0x5d, 0xa2, 0x54, 0xbe, 0xc7, 0x85, 0x1b, 0x3d, + 0xfb, 0x76, 0x05, 0xbb, 0x20, 0x1f, 0xd5, 0xbb, 0xc3, 0x12, 0x65, 0xbe, + 0x1e, 0x60, 0x9d, 0x3e, 0x74, 0x8d, 0x03, 0xbd, 0xe6, 0x87, 0x01, 0xbf, + 0x4e, 0x58, 0xc3, 0xbe, 0x0b, 0x3e, 0x09, 0x3e, 0xae, 0x0f, 0x17, 0xbd, + 0xab, 0xeb, 0x5b, 0x3d, 0x33, 0x41, 0x89, 0x3e, 0xfe, 0x66, 0x14, 0xbe, + 0x82, 0xfd, 0x63, 0xbc, 0x07, 0x1d, 0xbb, 0xbe, 0x80, 0x13, 0x7e, 0x3d, + 0x51, 0x53, 0xdf, 0xbe, 0xb3, 0x78, 0x91, 0x3d, 0x7e, 0x79, 0xac, 0xbd, + 0x2e, 0xe2, 0x3f, 0xbe, 0x46, 0x85, 0x01, 0x3d, 0xec, 0xf8, 0x64, 0x3e, + 0x06, 0x8e, 0xed, 0xbe, 0xf7, 0x7b, 0x1c, 0xbf, 0x09, 0x5b, 0x42, 0x3d, + 0x5b, 0xfd, 0x82, 0x3b, 0xde, 0xa5, 0xe4, 0xbd, 0xcb, 0x7c, 0x82, 0x3e, + 0xd6, 0xe7, 0x77, 0xbe, 0x28, 0x8e, 0xe3, 0xba, 0xf5, 0x19, 0x16, 0xbf, + 0x47, 0xf9, 0x0c, 0x3e, 0x93, 0xa4, 0xa7, 0xbe, 0xa1, 0xfd, 0x0b, 0xbd, + 0x34, 0x25, 0x2a, 0xbe, 0x79, 0xa6, 0x3e, 0xbe, 0x72, 0x46, 0x7a, 0x3e, + 0xdd, 0xb1, 0xd4, 0x3e, 0xa6, 0xae, 0xd7, 0x3d, 0xcd, 0xa3, 0xb8, 0xbe, + 0x83, 0xf2, 0xea, 0xbd, 0xda, 0x19, 0xfc, 0xbc, 0x8d, 0x76, 0xa2, 0xbc, + 0x17, 0x56, 0xa2, 0x3c, 0xf4, 0xa9, 0x00, 0xbe, 0x5b, 0xde, 0x36, 0x3d, + 0x0f, 0x78, 0x2d, 0xbf, 0x7f, 0xdc, 0x7e, 0xbd, 0x8b, 0x23, 0x93, 0xbd, + 0x0b, 0x44, 0x0e, 0x3d, 0xd1, 0xda, 0x73, 0xbe, 0xcf, 0x9f, 0x5b, 0xbe, + 0x50, 0xe9, 0x25, 0x3e, 0x83, 0x6c, 0x95, 0x3e, 0x72, 0x08, 0x76, 0x3d, + 0xae, 0xe5, 0x51, 0xbe, 0xd4, 0xf0, 0xa4, 0xbd, 0xf9, 0x06, 0xce, 0xbd, + 0x90, 0x98, 0x14, 0x3e, 0x24, 0x29, 0x20, 0x3c, 0x11, 0x75, 0x1c, 0xbe, + 0x99, 0x76, 0x2b, 0x3c, 0x9b, 0x64, 0xc1, 0xbe, 0x58, 0x5d, 0x08, 0xbe, + 0xcc, 0x09, 0x0f, 0x3e, 0x6e, 0x15, 0x1e, 0xbd, 0x17, 0x45, 0xed, 0xbd, + 0x15, 0xb4, 0x00, 0xbe, 0x71, 0x06, 0x83, 0x3d, 0xe5, 0x7e, 0xb2, 0x3e, + 0x72, 0x95, 0x57, 0x3e, 0x10, 0x2b, 0x78, 0xbe, 0x8f, 0x8b, 0x17, 0xbe, + 0x7a, 0xcb, 0x69, 0xbd, 0x93, 0xe9, 0x35, 0xbd, 0x70, 0x4a, 0x13, 0x3e, + 0x29, 0x2f, 0xde, 0xbd, 0x49, 0x2c, 0x0f, 0x3d, 0xb4, 0xd1, 0x7b, 0x3e, + 0x15, 0xff, 0x1a, 0xbd, 0x09, 0xeb, 0x16, 0x3d, 0x16, 0x20, 0x94, 0x3d, + 0x62, 0x81, 0x37, 0xbe, 0xf4, 0xb4, 0xa9, 0xbc, 0xa1, 0xe9, 0xfb, 0xbc, + 0x6a, 0xe0, 0xfc, 0x3d, 0x34, 0x3e, 0x4f, 0x3e, 0xc3, 0x45, 0x37, 0xbe, + 0xbd, 0x7c, 0x13, 0x3d, 0x80, 0xc5, 0xa1, 0x3c, 0x95, 0x2d, 0x94, 0xbd, + 0x37, 0xf2, 0xc3, 0x3d, 0xea, 0x4c, 0xb5, 0xbc, 0x3c, 0x6c, 0x0e, 0x3d, + 0x95, 0x03, 0xea, 0x3d, 0x00, 0xcb, 0xa5, 0xbd, 0xc6, 0xdb, 0x94, 0x3d, + 0xe0, 0x75, 0x9f, 0x3d, 0x35, 0x2b, 0xcd, 0xbc, 0x4b, 0xd6, 0x9d, 0xbc, + 0x19, 0xe6, 0x34, 0x3e, 0x95, 0xc7, 0xa8, 0x3d, 0x39, 0xa8, 0x4d, 0x3e, + 0x1a, 0xfe, 0x2e, 0xbe, 0x63, 0x46, 0x8c, 0x3d, 0xb2, 0x63, 0x0c, 0xbe, + 0xcb, 0x53, 0x82, 0xbc, 0x0d, 0xcc, 0x23, 0x3d, 0x93, 0xee, 0xff, 0x3c, + 0xa1, 0xba, 0x10, 0xbd, 0xf4, 0x9f, 0xfd, 0xbb, 0xe4, 0x63, 0x52, 0xbe, + 0xa0, 0x3f, 0xdb, 0x3d, 0xe3, 0x8c, 0xa1, 0xbd, 0x75, 0x4f, 0x75, 0xbd, + 0x64, 0xc3, 0xe6, 0x3c, 0x91, 0x6a, 0x96, 0x3e, 0x5b, 0x8f, 0x41, 0x3e, + 0x0b, 0xed, 0x8d, 0x3e, 0xe1, 0xfa, 0x47, 0xbd, 0xe8, 0x79, 0xa6, 0x3d, + 0x7f, 0x4e, 0x77, 0x3c, 0x3d, 0xbf, 0xf1, 0xba, 0x83, 0x2e, 0x92, 0x3d, + 0x14, 0xc8, 0xc3, 0xbd, 0x42, 0x69, 0x00, 0x3d, 0x86, 0x93, 0x8b, 0x3d, + 0xc6, 0xb3, 0x37, 0xbe, 0x5d, 0x8c, 0x27, 0xbc, 0xee, 0x70, 0xc9, 0xbc, + 0xf7, 0xf5, 0x96, 0xbe, 0x3f, 0x20, 0xe1, 0x3d, 0x07, 0x7a, 0x8b, 0x3e, + 0xc1, 0x46, 0x17, 0x3d, 0x39, 0xcc, 0x3c, 0x3e, 0xd4, 0xa0, 0x11, 0xbe, + 0x24, 0x8b, 0xe3, 0x3d, 0x9b, 0x32, 0x49, 0x3d, 0xc1, 0x6f, 0x08, 0xbe, + 0x5f, 0x21, 0x0e, 0x3d, 0xc0, 0xc1, 0x1a, 0x3d, 0x4d, 0x30, 0x8e, 0x3d, + 0x0d, 0xdf, 0x2e, 0x3e, 0x58, 0x84, 0x4b, 0xbe, 0x96, 0x08, 0x85, 0x3d, + 0x9a, 0x2e, 0x4f, 0x3c, 0x26, 0x4d, 0xc2, 0xbe, 0x78, 0x76, 0xea, 0x3d, + 0xd6, 0x01, 0x0a, 0x3d, 0xf5, 0xef, 0x78, 0xbe, 0x66, 0x9b, 0x82, 0x3e, + 0x90, 0x6b, 0x5f, 0xbe, 0x8c, 0xdf, 0xf9, 0x3d, 0x84, 0x98, 0xe9, 0xbe, + 0x38, 0x4e, 0x73, 0x3c, 0xce, 0xac, 0x2a, 0x3e, 0x1d, 0x29, 0x80, 0x3d, + 0x04, 0xd4, 0x94, 0xbe, 0x5d, 0x8b, 0x2f, 0xbe, 0x02, 0xde, 0xc5, 0xbe, + 0xbc, 0x6b, 0x0e, 0xbd, 0x0e, 0x34, 0xb2, 0xbd, 0xfc, 0xc1, 0x71, 0xbe, + 0xb3, 0x9a, 0xa4, 0xbc, 0xd2, 0x28, 0x55, 0x3e, 0x99, 0x4b, 0x15, 0x3e, + 0x6b, 0x9e, 0x8b, 0xbe, 0x0f, 0xa7, 0xa6, 0xbe, 0x9f, 0x1c, 0x7f, 0x3d, + 0x03, 0x97, 0xd6, 0xbe, 0xec, 0x2d, 0x7e, 0xbd, 0x5a, 0xa2, 0x2a, 0x3e, + 0x96, 0x01, 0xf9, 0x3c, 0x02, 0xaa, 0xd3, 0xbe, 0xb1, 0x3a, 0x1e, 0xbf, + 0x4b, 0xee, 0x50, 0x3e, 0x33, 0x84, 0x4a, 0xbe, 0xae, 0x5e, 0xca, 0xbe, + 0x6d, 0xad, 0x3d, 0xbe, 0x35, 0xdc, 0x8b, 0xbd, 0x7f, 0x3b, 0x19, 0xbe, + 0xf7, 0x3f, 0xe9, 0xbd, 0x39, 0x0d, 0xa9, 0xbe, 0x62, 0xaf, 0x2f, 0xbe, + 0x50, 0x42, 0xf2, 0xbd, 0x0b, 0xa2, 0xec, 0x3d, 0x87, 0x39, 0x6e, 0xbe, + 0xa0, 0xe2, 0x18, 0xbd, 0xd0, 0xf9, 0x9d, 0xbd, 0x44, 0xca, 0x1c, 0x3c, + 0x22, 0x53, 0xc3, 0x3e, 0x8b, 0x89, 0x93, 0xbe, 0x5b, 0xc0, 0x2a, 0xbe, + 0xc5, 0x08, 0xe9, 0xbc, 0x55, 0x7a, 0xbb, 0xbd, 0xa6, 0x59, 0x44, 0xbe, + 0x92, 0x5a, 0x9e, 0xbe, 0x44, 0x21, 0x92, 0xbe, 0x97, 0x4f, 0x57, 0xbe, + 0xe6, 0x26, 0x19, 0xbe, 0x7c, 0x1a, 0xf9, 0xbe, 0xa7, 0x2d, 0x9a, 0x3c, + 0xa1, 0x6c, 0xb9, 0xbe, 0x17, 0x1f, 0xf0, 0xbe, 0x36, 0x41, 0x63, 0xbe, + 0xe1, 0xfb, 0x72, 0xbd, 0x77, 0x52, 0xe0, 0x3e, 0xa8, 0x23, 0x01, 0xbf, + 0x34, 0x23, 0x34, 0xbe, 0x86, 0x7c, 0xbe, 0xbd, 0x95, 0x31, 0xde, 0xbe, + 0xea, 0x79, 0x04, 0xbe, 0x23, 0x82, 0xcc, 0xbe, 0xf6, 0xc9, 0x1c, 0xbf, + 0x1a, 0x7d, 0x41, 0x3c, 0xcb, 0xc1, 0x6b, 0xbe, 0xf7, 0x47, 0x2d, 0xbf, + 0xfe, 0x87, 0x32, 0xbd, 0xad, 0x73, 0x82, 0xbe, 0xa2, 0xd1, 0x3e, 0xbe, + 0x4c, 0xfa, 0x08, 0xbd, 0xef, 0xe1, 0x3d, 0xbd, 0xaf, 0x4c, 0x72, 0x3e, + 0x79, 0x46, 0xa1, 0xbe, 0x5a, 0x73, 0xe1, 0xbe, 0x0d, 0x0b, 0xc8, 0xbd, + 0x2d, 0x22, 0x91, 0x3e, 0x3f, 0x09, 0x7d, 0xbe, 0x18, 0xe7, 0x25, 0xbe, + 0x20, 0x50, 0x49, 0xbd, 0x08, 0x29, 0x4b, 0xbc, 0xce, 0xd6, 0x82, 0xbe, + 0x98, 0xe0, 0xec, 0xbe, 0xcd, 0x3d, 0x2d, 0x3d, 0xf4, 0xf8, 0xb0, 0xbd, + 0x7a, 0xee, 0xf3, 0xbd, 0x21, 0xd4, 0xd8, 0xb8, 0xf1, 0xf3, 0x81, 0xbd, + 0x2f, 0x08, 0x0c, 0x3d, 0x80, 0x46, 0x19, 0x3e, 0x80, 0x3c, 0x9c, 0xbe, + 0xc1, 0x46, 0xf2, 0xbd, 0x1d, 0x89, 0x3a, 0x3e, 0xa9, 0x64, 0x0a, 0xbe, + 0xb5, 0xe3, 0x2f, 0xbd, 0x99, 0x8d, 0x55, 0xbe, 0x19, 0x11, 0xcf, 0xbd, + 0x00, 0x5d, 0x3a, 0xbd, 0xef, 0x70, 0xb8, 0xbe, 0x2f, 0xe7, 0x4e, 0xbc, + 0x57, 0xbc, 0xa1, 0x3e, 0xbc, 0xcd, 0xd9, 0xbc, 0x7d, 0x6a, 0xbe, 0xbd, + 0x23, 0x6e, 0x3a, 0xbd, 0xc8, 0x65, 0x77, 0xbd, 0xb2, 0xe9, 0x1a, 0xbe, + 0x4f, 0xb3, 0x7a, 0xbe, 0x92, 0x29, 0x3d, 0xbd, 0xc2, 0xf1, 0xac, 0x3e, + 0x60, 0x35, 0x39, 0xbd, 0x49, 0xa6, 0xe8, 0x3c, 0x35, 0xf4, 0x04, 0x3e, + 0x41, 0x26, 0x09, 0xbc, 0x51, 0x4e, 0xa3, 0x3e, 0x56, 0x74, 0x9a, 0xbd, + 0xae, 0x10, 0x94, 0x3d, 0x01, 0xe9, 0x51, 0x3d, 0xc0, 0x1d, 0x45, 0x3d, + 0x80, 0xb1, 0xb4, 0xbc, 0x9b, 0x51, 0xcb, 0xbd, 0xa8, 0xca, 0x2d, 0xbd, + 0xa3, 0x12, 0x52, 0xbc, 0x22, 0xee, 0x05, 0xbe, 0xf0, 0x4c, 0x66, 0xbd, + 0x12, 0x0a, 0xa3, 0x3e, 0xae, 0xe7, 0x8d, 0xbc, 0xa2, 0x32, 0x48, 0x3e, + 0xd9, 0x25, 0xc6, 0xbd, 0x9f, 0xe7, 0x0e, 0x3e, 0x83, 0xd4, 0x20, 0x3e, + 0xf5, 0x11, 0xf0, 0xbc, 0xd8, 0xb1, 0x8c, 0x3d, 0xad, 0x03, 0xd5, 0x3b, + 0x87, 0x4f, 0xc0, 0x3d, 0xb5, 0x17, 0x36, 0x3d, 0x13, 0xdc, 0x08, 0xbd, + 0x6d, 0x28, 0x23, 0x3d, 0x24, 0x48, 0x94, 0xbe, 0xd9, 0xbe, 0x4f, 0xbd, + 0x0b, 0x18, 0x90, 0xbd, 0x04, 0xee, 0x91, 0x3e, 0x60, 0x12, 0x20, 0x3e, + 0xa4, 0x50, 0x31, 0x3e, 0x79, 0xb4, 0x91, 0xbd, 0xb6, 0x3b, 0xfd, 0x3d, + 0x87, 0x3a, 0xce, 0x3d, 0xab, 0x25, 0x3c, 0x3d, 0x2e, 0xba, 0xf6, 0x3b, + 0x22, 0xbe, 0x88, 0x3e, 0x41, 0x1d, 0x91, 0x3d, 0x40, 0x2e, 0x92, 0x3d, + 0x02, 0xd8, 0x90, 0x3d, 0x68, 0x3f, 0x58, 0xbe, 0x9b, 0xac, 0xd7, 0xbd, + 0x18, 0x6d, 0x8a, 0xbb, 0x1f, 0xf4, 0x7d, 0xbc, 0xe9, 0x45, 0x09, 0x3e, + 0x9f, 0xfe, 0x95, 0x3d, 0x9d, 0xf8, 0x2a, 0x3e, 0x55, 0x59, 0x18, 0xbe, + 0xb1, 0xfd, 0x36, 0x3e, 0x2a, 0xaa, 0x3b, 0x3e, 0xa6, 0xf6, 0x39, 0xba, + 0xa1, 0x4d, 0x74, 0xbd, 0xd9, 0xe6, 0x8d, 0x3e, 0x80, 0xa5, 0x8c, 0xbc, + 0x61, 0x74, 0x25, 0x3d, 0x34, 0xfe, 0x71, 0x3d, 0xbc, 0xb5, 0x8f, 0xbe, + 0x46, 0x41, 0xf4, 0xbe, 0x17, 0xf2, 0x9e, 0x3a, 0x80, 0x45, 0xce, 0x3c, + 0xa4, 0xd1, 0x8f, 0x3e, 0xe3, 0x2e, 0x43, 0xbc, 0x3d, 0x24, 0xb4, 0x3e, + 0xb1, 0x1e, 0x30, 0xbd, 0x98, 0x36, 0xcd, 0x3d, 0x0c, 0x48, 0x55, 0x3d, + 0x39, 0x6f, 0x16, 0xbe, 0x1d, 0x82, 0x34, 0xbe, 0xc0, 0x7d, 0x81, 0x3e, + 0x73, 0xe1, 0x0a, 0xbe, 0x1d, 0x95, 0x13, 0x3d, 0x52, 0x6b, 0xf5, 0xbb, + 0x42, 0x7e, 0xbc, 0xbe, 0x0c, 0x5a, 0xcc, 0xbe, 0x6b, 0xc1, 0xc3, 0xbc, + 0x85, 0xa2, 0xf7, 0x3c, 0x11, 0xef, 0x4b, 0x3e, 0xa4, 0xb9, 0x85, 0x3d, + 0x1b, 0x76, 0xbe, 0x3e, 0x9f, 0x20, 0xd4, 0xbd, 0x65, 0x15, 0x2a, 0x3e, + 0x6e, 0xdd, 0xfe, 0x3d, 0x72, 0x35, 0x21, 0xbd, 0x54, 0x0f, 0x48, 0xbe, + 0xfe, 0xe7, 0x46, 0x3e, 0x6f, 0x80, 0x80, 0xbd, 0xf4, 0x2c, 0xc8, 0x3d, + 0x45, 0x0b, 0x8e, 0xbd, 0xb3, 0xd3, 0x99, 0xbe, 0x12, 0x64, 0x19, 0xbe, + 0x8e, 0x98, 0x34, 0x3c, 0x3f, 0x82, 0x5c, 0x3d, 0x05, 0x5c, 0x8d, 0x3e, + 0x66, 0x6d, 0x1d, 0x3d, 0xe8, 0xe4, 0x79, 0x3e, 0x09, 0x06, 0x0c, 0xbd, + 0xfc, 0x25, 0x90, 0x3d, 0xd8, 0xda, 0xf1, 0x3d, 0xbb, 0x7e, 0x18, 0x3e, + 0xa7, 0x79, 0xe1, 0x3c, 0x68, 0x65, 0x31, 0x3d, 0x80, 0x16, 0x0f, 0xbe, + 0xa6, 0x2a, 0x79, 0x3c, 0x4f, 0x6a, 0xf5, 0x3d, 0x2d, 0x8e, 0x83, 0xbe, + 0xef, 0x1a, 0x86, 0xbd, 0xd7, 0x98, 0xd3, 0xbc, 0xdc, 0x99, 0xfb, 0x3d, + 0xe8, 0x2d, 0xd0, 0x3e, 0x93, 0x99, 0x23, 0xbe, 0x36, 0xdd, 0x84, 0x3e, + 0x41, 0x0f, 0x0a, 0x3e, 0x90, 0x4b, 0xd5, 0xbc, 0xd3, 0x40, 0xac, 0x3d, + 0x8e, 0x08, 0x95, 0x3c, 0xcc, 0x5b, 0x6f, 0xbe, 0x02, 0x7b, 0x89, 0x3e, + 0x6f, 0x2a, 0x55, 0xbe, 0x64, 0x57, 0xcd, 0xbd, 0x8d, 0xa2, 0xa8, 0x3d, + 0xaa, 0x1f, 0x42, 0xbd, 0x12, 0x22, 0x5c, 0xbe, 0x9a, 0xfa, 0x43, 0xbd, + 0xe0, 0x93, 0x81, 0xbd, 0xa6, 0x78, 0x74, 0xbd, 0x78, 0x8d, 0xd4, 0xbe, + 0x7e, 0xc7, 0x32, 0xbf, 0x01, 0xc0, 0x06, 0xbf, 0xd8, 0xad, 0x7f, 0xbd, + 0xd2, 0xc7, 0xa7, 0xbe, 0xbe, 0xc5, 0xff, 0xbe, 0x1e, 0xf5, 0x03, 0x3c, + 0x96, 0xae, 0x07, 0xbf, 0x43, 0xf6, 0x60, 0xbe, 0x1c, 0x7e, 0x8c, 0xbe, + 0xd4, 0x05, 0x51, 0xbd, 0x21, 0x90, 0x8c, 0x3e, 0x13, 0xcb, 0x85, 0xbe, + 0xdf, 0xb4, 0x05, 0xbf, 0xc3, 0x79, 0xa7, 0x3b, 0x60, 0x54, 0x39, 0x3c, + 0x12, 0x9a, 0x91, 0x3d, 0xe2, 0x53, 0x8d, 0x3d, 0xe8, 0x49, 0x5b, 0xbb, + 0x28, 0xcc, 0x95, 0xbe, 0x00, 0x79, 0xd7, 0xbc, 0x2a, 0x7e, 0x80, 0x3c, + 0xc9, 0x5f, 0x15, 0xbe, 0x4f, 0x7e, 0x4c, 0x3d, 0x4a, 0x6d, 0x58, 0xbc, + 0x6d, 0xa2, 0x17, 0xbe, 0x1c, 0x64, 0x11, 0xbe, 0x05, 0xf2, 0x7d, 0x3e, + 0x0f, 0x16, 0xb4, 0x3b, 0x98, 0x36, 0x88, 0x3d, 0x87, 0x91, 0xc0, 0xbe, + 0x3a, 0xbb, 0xcd, 0x3c, 0x89, 0x5d, 0xa8, 0xbe, 0x67, 0xdb, 0xe2, 0x3d, + 0xd0, 0xe3, 0x5f, 0xbd, 0x2b, 0xad, 0x0e, 0xbe, 0x4c, 0x1e, 0x24, 0xbe, + 0xe7, 0x6f, 0x8f, 0xbe, 0x0a, 0xa3, 0xb5, 0x3c, 0xfe, 0xf3, 0x8b, 0xbd, + 0x42, 0xfb, 0xc2, 0x3d, 0x7b, 0x30, 0x30, 0xbd, 0xbd, 0xbf, 0x49, 0xbe, + 0xb6, 0x92, 0x14, 0x3e, 0x97, 0x15, 0x3d, 0xbd, 0xfa, 0x58, 0x8b, 0xbd, + 0x88, 0x5d, 0x89, 0xbe, 0x8d, 0x80, 0x64, 0xbe, 0x3b, 0xb3, 0x6c, 0xbe, + 0x87, 0xd4, 0xda, 0xbb, 0x92, 0x31, 0x64, 0xbe, 0x8a, 0x85, 0xf4, 0xbd, + 0x9e, 0x89, 0xa4, 0xbd, 0x5e, 0xd7, 0x5d, 0xbe, 0xbf, 0x73, 0x45, 0xbe, + 0x77, 0xa5, 0x52, 0x3c, 0x3a, 0x4a, 0xa7, 0xbd, 0x37, 0xfb, 0x3e, 0xbc, + 0x24, 0x9c, 0x8e, 0xbd, 0x1e, 0x54, 0x91, 0xbc, 0x48, 0xaf, 0x45, 0xbd, + 0x25, 0x8c, 0x1e, 0x3d, 0xf2, 0x1a, 0x92, 0xbd, 0x7b, 0xb7, 0x3a, 0x3e, + 0x0c, 0xe9, 0x98, 0xbe, 0x70, 0x74, 0xb1, 0x3d, 0x43, 0xa9, 0x59, 0xbe, + 0xe1, 0xe3, 0x18, 0x3d, 0xc3, 0x81, 0x90, 0xbe, 0x53, 0x0c, 0x08, 0xbe, + 0x06, 0x59, 0xa5, 0xbd, 0x09, 0x71, 0xa2, 0x3c, 0x7c, 0x30, 0xa6, 0x3d, + 0xd7, 0x3a, 0xab, 0x3b, 0x00, 0xf9, 0x68, 0xbd, 0x79, 0x2a, 0x19, 0xbe, + 0x6d, 0x83, 0xb1, 0xbd, 0x20, 0xc3, 0x15, 0xbd, 0x70, 0x6e, 0x95, 0xbd, + 0xda, 0x24, 0x53, 0xbd, 0x6c, 0x88, 0xb6, 0xbe, 0x14, 0x65, 0x5f, 0x3d, + 0x80, 0xd5, 0x41, 0xbe, 0xaf, 0x31, 0x37, 0x3e, 0xa3, 0xad, 0x1c, 0xbd, + 0x2f, 0xfa, 0x0b, 0xbe, 0xed, 0x59, 0x2e, 0xbe, 0x6a, 0x9d, 0x5b, 0x3e, + 0x37, 0xde, 0x8f, 0xbd, 0x39, 0x9b, 0xb0, 0x3d, 0xaf, 0xc0, 0x03, 0xbd, + 0x85, 0xc3, 0x04, 0x3e, 0x0f, 0x4f, 0x38, 0xbc, 0x0f, 0xb3, 0x06, 0xbe, + 0x64, 0x20, 0x2b, 0xbe, 0x81, 0xb0, 0x1d, 0x3e, 0x57, 0x08, 0x8c, 0xbe, + 0x62, 0xbc, 0xe7, 0x3d, 0x45, 0xdf, 0x4e, 0x3d, 0xa0, 0x7d, 0x29, 0x3d, + 0x87, 0xd4, 0xc4, 0xbd, 0xb2, 0xa7, 0x54, 0xbd, 0x87, 0xfa, 0x8f, 0x3d, + 0x4b, 0x05, 0x34, 0x3e, 0xef, 0x5a, 0xa9, 0x3d, 0xd5, 0xd2, 0xc9, 0xbd, + 0x66, 0xbd, 0x16, 0xbd, 0x54, 0xcf, 0x08, 0x3e, 0x54, 0x8a, 0xf4, 0x3c, + 0xc6, 0xcd, 0x31, 0x3e, 0xf6, 0x6d, 0x7c, 0xbc, 0x7e, 0x1e, 0x10, 0xbd, + 0x30, 0x46, 0x1d, 0xbe, 0x5d, 0x65, 0x9c, 0xbc, 0xea, 0xbe, 0x4d, 0xbe, + 0x76, 0xc0, 0x91, 0xbc, 0x6d, 0x84, 0x33, 0x3d, 0x63, 0xc5, 0x1a, 0xbe, + 0x48, 0xa0, 0xaa, 0x3d, 0x2c, 0x55, 0x27, 0x3e, 0xee, 0x41, 0x94, 0x3d, + 0x3a, 0x2b, 0xa2, 0x3c, 0x14, 0xa0, 0x15, 0xbe, 0x33, 0x75, 0xdb, 0x3b, + 0xe2, 0x0e, 0x5d, 0xbd, 0x8d, 0x94, 0xc3, 0x3c, 0x76, 0x8e, 0xc3, 0xbe, + 0x26, 0xd0, 0x05, 0xbe, 0xb3, 0x7c, 0xc3, 0xbd, 0x95, 0xbd, 0xa3, 0x3d, + 0x98, 0x07, 0xc7, 0xbc, 0xbb, 0xc0, 0x77, 0xbd, 0x0b, 0x87, 0x80, 0x3c, + 0x3b, 0xda, 0x88, 0xbd, 0xc3, 0x53, 0x03, 0x3e, 0xe3, 0x7d, 0x00, 0xbe, + 0x57, 0xf1, 0x40, 0x3c, 0xd3, 0xf2, 0x23, 0xbd, 0xcf, 0x47, 0xcf, 0xbd, + 0x55, 0x35, 0x0c, 0xbd, 0x3a, 0x41, 0x60, 0xbd, 0xb3, 0xc1, 0x21, 0x3e, + 0x17, 0x79, 0x79, 0xbe, 0x4f, 0x9e, 0x0e, 0xbe, 0x81, 0x91, 0x00, 0xbe, + 0xaf, 0x5b, 0xbc, 0xbc, 0xe2, 0xbc, 0xd0, 0xbc, 0xa5, 0xfe, 0x9d, 0x3d, + 0x03, 0xbd, 0x93, 0xbe, 0x1e, 0x59, 0xa9, 0xbd, 0x1f, 0xea, 0xd0, 0xbd, + 0xc9, 0x61, 0x03, 0x3e, 0xe8, 0x4c, 0x16, 0x3e, 0xe5, 0x83, 0x41, 0xbb, + 0xd3, 0x77, 0xd2, 0xbd, 0x9e, 0x9e, 0x8d, 0xbc, 0x75, 0x41, 0x37, 0xbc, + 0x61, 0x40, 0x70, 0xbd, 0xbf, 0xec, 0xde, 0xbd, 0x8c, 0x63, 0xee, 0xbc, + 0xc1, 0x06, 0xe8, 0xbd, 0x15, 0x17, 0x4e, 0x3c, 0xee, 0xaa, 0xf0, 0xbd, + 0x17, 0x12, 0x02, 0xbd, 0xbb, 0xbf, 0x67, 0xbe, 0x35, 0xcb, 0x44, 0xbe, + 0x40, 0xaf, 0xa2, 0x3d, 0xc1, 0xe9, 0x9d, 0xbc, 0x84, 0x51, 0x61, 0x3e, + 0xea, 0xad, 0x7f, 0x3d, 0x5f, 0x13, 0x82, 0x3c, 0x87, 0x1c, 0xf5, 0xbc, + 0x5c, 0xc4, 0xe8, 0x3c, 0xc1, 0xa3, 0x68, 0xbd, 0x2c, 0xbf, 0x98, 0xbe, + 0xf7, 0xa1, 0xd2, 0x3b, 0x70, 0x4c, 0x24, 0x3c, 0xe2, 0x19, 0x8b, 0xbd, + 0xd0, 0x95, 0x17, 0xbe, 0xa4, 0x5e, 0x2b, 0xbe, 0x55, 0x11, 0x53, 0xbe, + 0x33, 0xdc, 0x7c, 0xbe, 0xa3, 0x5f, 0x00, 0x3e, 0x41, 0x5c, 0xf2, 0x3d, + 0x0d, 0xab, 0xe8, 0xbd, 0xdd, 0xf9, 0x24, 0x3d, 0x7f, 0x07, 0x06, 0x3e, + 0x62, 0xd0, 0x26, 0x3d, 0x72, 0x32, 0xf9, 0xbd, 0x80, 0x7a, 0xce, 0xbd, + 0xa8, 0x00, 0x1c, 0xbe, 0x28, 0x3a, 0x33, 0xbe, 0xef, 0xfc, 0xe6, 0xbc, + 0x69, 0xd4, 0xe4, 0x3d, 0x9a, 0x5d, 0x33, 0xbe, 0xb1, 0x1f, 0xd9, 0x3c, + 0xa8, 0xe8, 0x5c, 0xbd, 0xdb, 0x5e, 0x9d, 0xbe, 0x17, 0xac, 0xb8, 0xbc, + 0x5a, 0x52, 0x4d, 0x3d, 0x3d, 0x00, 0x97, 0xbc, 0x9a, 0xaa, 0x53, 0xbe, + 0xc0, 0x8c, 0x18, 0xbe, 0xdd, 0x93, 0x28, 0xbd, 0xa5, 0x6a, 0x97, 0x3d, + 0xe1, 0x09, 0x55, 0xbd, 0xea, 0xdb, 0xaa, 0xbe, 0xb1, 0x0d, 0xa0, 0xbd, + 0x33, 0xb7, 0x0c, 0xbe, 0xf3, 0x7c, 0xe5, 0x3d, 0x9e, 0x05, 0x9c, 0xbe, + 0x52, 0x7b, 0x0e, 0xbe, 0x7d, 0x50, 0x0b, 0xbe, 0x8a, 0x99, 0x1a, 0xbe, + 0x70, 0x90, 0xde, 0x3c, 0x73, 0x98, 0x7f, 0xbc, 0xf2, 0x72, 0x1b, 0x3e, + 0x64, 0x71, 0x72, 0xbd, 0xd5, 0xdb, 0x7c, 0xbe, 0xb0, 0xb4, 0xc6, 0xbc, + 0x80, 0x0a, 0x43, 0xbc, 0x7c, 0x3e, 0xa8, 0x3d, 0x9a, 0xd9, 0xda, 0xbe, + 0xe6, 0x9d, 0x21, 0xbe, 0x5e, 0x36, 0x30, 0xbe, 0xc5, 0x14, 0x38, 0x3e, + 0xd1, 0xa1, 0xa3, 0x3d, 0x22, 0xdd, 0x3d, 0xbe, 0xd6, 0x71, 0x99, 0xbd, + 0xe8, 0xab, 0x8d, 0x3e, 0x02, 0x68, 0x38, 0x3a, 0x4c, 0x4f, 0xc6, 0xbd, + 0x32, 0xb3, 0x38, 0x3e, 0x29, 0x0d, 0x23, 0xbe, 0x84, 0x39, 0xd8, 0xbd, + 0x82, 0x93, 0xaf, 0x3d, 0x5b, 0xbb, 0x98, 0x3c, 0x33, 0x97, 0xa7, 0x3d, + 0x25, 0x92, 0x96, 0xbe, 0x51, 0x1f, 0x79, 0xbe, 0x24, 0x78, 0x0b, 0xbe, + 0x2b, 0x85, 0x78, 0xbe, 0xf7, 0xd1, 0x40, 0x3f, 0xa2, 0xd5, 0x9a, 0x3e, + 0x9b, 0xff, 0x63, 0xbe, 0x0c, 0xcb, 0x08, 0xbe, 0xb8, 0x8a, 0x99, 0x3d, + 0xd1, 0xb9, 0x25, 0x3d, 0xd0, 0x91, 0x31, 0xbd, 0x3d, 0x06, 0x88, 0x3d, + 0x47, 0x65, 0xed, 0x3d, 0xaa, 0xc4, 0xd2, 0xbe, 0xd9, 0x6c, 0xc4, 0xbe, + 0xa1, 0xee, 0xc2, 0x3d, 0x56, 0xbc, 0x62, 0x3e, 0x76, 0x7d, 0x41, 0xbe, + 0x35, 0x60, 0x56, 0x3d, 0x6c, 0x50, 0x1d, 0xbe, 0xcf, 0xf4, 0x84, 0x3d, + 0x61, 0x1d, 0x4e, 0x3e, 0xea, 0xcd, 0x36, 0xbe, 0xc4, 0xc2, 0x77, 0x3c, + 0xa1, 0x6a, 0x14, 0xbd, 0xf2, 0x01, 0xcb, 0x3d, 0x73, 0xe6, 0x0d, 0x3e, + 0xf7, 0x54, 0x4d, 0xbd, 0x1b, 0x0b, 0xf0, 0x3d, 0xd8, 0x6b, 0x4c, 0x3e, + 0x05, 0xd9, 0xbf, 0xbe, 0x29, 0x5a, 0x0a, 0x3e, 0x54, 0x15, 0xaa, 0x3d, + 0x6a, 0xd0, 0xad, 0xbe, 0xb5, 0xa9, 0x7e, 0x3d, 0x5b, 0x9b, 0xa2, 0xbd, + 0x66, 0xeb, 0x1e, 0x3e, 0xdf, 0xfa, 0xb3, 0x3d, 0x01, 0xb9, 0xeb, 0xbd, + 0xed, 0x60, 0x7b, 0x3d, 0xdd, 0xc3, 0xab, 0xbc, 0x9c, 0xf9, 0xa7, 0x3d, + 0x7c, 0x05, 0xd7, 0x3d, 0xf3, 0x62, 0xb7, 0xbe, 0x9d, 0x89, 0x7b, 0x3d, + 0x2e, 0x25, 0x20, 0x3c, 0xdb, 0xe4, 0x3d, 0xbe, 0x0e, 0xee, 0x3d, 0x3d, + 0x9c, 0xd3, 0xdc, 0x3c, 0x00, 0x4d, 0x38, 0xbe, 0xd6, 0x3c, 0x1e, 0xbd, + 0x8b, 0x41, 0x14, 0x3e, 0x56, 0x6b, 0x21, 0x3e, 0xfc, 0x0b, 0xb4, 0xbe, + 0x8c, 0xee, 0x24, 0xbe, 0x92, 0xf6, 0x48, 0x3e, 0x01, 0xa4, 0x4e, 0x3c, + 0x86, 0xf3, 0x0b, 0x3d, 0xde, 0x84, 0x1d, 0x3e, 0xe1, 0xa9, 0x00, 0xbf, + 0xa5, 0x03, 0xa7, 0x3d, 0x2b, 0xf4, 0x32, 0xbd, 0x7b, 0xed, 0x30, 0xbd, + 0xd7, 0xb6, 0x2a, 0xbe, 0xa4, 0xa7, 0xaa, 0xbd, 0x76, 0xa8, 0x59, 0x3e, + 0x63, 0x2b, 0x3f, 0xbd, 0x6a, 0xb1, 0x8d, 0x3c, 0x1c, 0xf7, 0x90, 0x3e, + 0xad, 0xa8, 0xb2, 0xbd, 0x5e, 0xe2, 0x80, 0xbe, 0xb1, 0x3b, 0x67, 0xbd, + 0xaa, 0x58, 0x88, 0xbc, 0xce, 0x93, 0x66, 0xbe, 0x3d, 0xe4, 0x85, 0x3d, + 0xfc, 0x7d, 0xd8, 0xbe, 0x73, 0x8d, 0xeb, 0xbc, 0xf7, 0x65, 0x6c, 0xbe, + 0xc5, 0xd4, 0x51, 0x3d, 0x62, 0x0d, 0x4b, 0xbe, 0xb0, 0xc3, 0x1f, 0xbd, + 0x87, 0x98, 0x69, 0x3d, 0x4d, 0x80, 0xd2, 0xbc, 0x9c, 0x0c, 0x38, 0x3e, + 0x89, 0x8b, 0x81, 0x3e, 0x55, 0x79, 0xea, 0x3d, 0xd0, 0xf2, 0xc8, 0xbd, + 0xb4, 0x9c, 0xa9, 0x3d, 0x7f, 0x05, 0x83, 0xbd, 0x05, 0x77, 0xb0, 0x3b, + 0x62, 0xbd, 0x4c, 0x3d, 0x49, 0x44, 0xc9, 0xbd, 0xdc, 0x3f, 0xb9, 0x3d, + 0x56, 0xc5, 0xe3, 0xbe, 0x5e, 0x61, 0x26, 0x3d, 0x31, 0x0e, 0x12, 0xbc, + 0x2b, 0xe8, 0x93, 0x3b, 0xc0, 0x15, 0x10, 0xbe, 0xdb, 0x6b, 0xd4, 0xbd, + 0xa7, 0xf7, 0x97, 0x3d, 0x61, 0xa0, 0x08, 0x3e, 0x54, 0x8a, 0x65, 0x3e, + 0x58, 0xcc, 0x1d, 0xbe, 0x33, 0x44, 0x63, 0x3c, 0x69, 0x14, 0xf6, 0xbd, + 0xff, 0x62, 0xd4, 0x3d, 0x05, 0x2b, 0x8b, 0x3d, 0x22, 0x2c, 0x13, 0xbe, + 0x52, 0x0c, 0x0f, 0x3d, 0x48, 0x96, 0xc7, 0xbd, 0x4d, 0x35, 0xab, 0x3d, + 0x20, 0xbe, 0xeb, 0x3d, 0x64, 0xf0, 0x86, 0xbd, 0xcb, 0x71, 0xa5, 0xbd, + 0xd5, 0x2a, 0x95, 0x3c, 0x57, 0xaa, 0x06, 0x3e, 0x5e, 0x14, 0x3c, 0x3e, + 0xcb, 0xf8, 0x11, 0x3e, 0x34, 0x34, 0x6d, 0x3d, 0xa4, 0x66, 0x0b, 0x3c, + 0x52, 0x5b, 0x9d, 0xbb, 0x7a, 0x84, 0x90, 0xbd, 0x11, 0xc8, 0x4a, 0x3e, + 0x75, 0xf9, 0x8a, 0xbd, 0x83, 0x65, 0x22, 0xbd, 0x64, 0x5d, 0x15, 0xbd, + 0x91, 0x22, 0x5e, 0xbc, 0xbd, 0x09, 0x12, 0x3d, 0xfe, 0x19, 0xeb, 0x3c, + 0xa3, 0x78, 0x2e, 0xbd, 0x62, 0xf3, 0x7f, 0x3c, 0x05, 0x5e, 0x03, 0x3e, + 0x6c, 0x9e, 0xb7, 0x3d, 0xb1, 0x03, 0x4d, 0x3e, 0x66, 0x72, 0xfd, 0xbd, + 0xc5, 0x12, 0x11, 0x3d, 0x99, 0x39, 0xab, 0x3c, 0xc8, 0xf5, 0x5a, 0xbd, + 0x33, 0x84, 0x98, 0x3c, 0x0a, 0xd6, 0x99, 0x3d, 0x4f, 0xcb, 0xb5, 0xbb, + 0x00, 0x39, 0x41, 0x3e, 0xdc, 0x20, 0x27, 0xbc, 0x34, 0xdb, 0xbb, 0x3d, + 0x33, 0x3c, 0x26, 0x3d, 0x3a, 0x99, 0x2b, 0x3d, 0xa4, 0x31, 0x76, 0x3d, + 0x50, 0x79, 0x91, 0x3d, 0x80, 0x50, 0xc3, 0x3d, 0x5c, 0x3d, 0x39, 0x3e, + 0xc9, 0x7b, 0xed, 0xbc, 0xf2, 0xbb, 0x88, 0x3d, 0x76, 0xb2, 0x5e, 0xbe, + 0x0c, 0x80, 0xb9, 0xbd, 0x10, 0x91, 0x12, 0x3d, 0xca, 0xfb, 0x47, 0x3d, + 0xc4, 0x43, 0xc4, 0xbd, 0xd3, 0x36, 0x82, 0xbd, 0x03, 0x96, 0xda, 0xbd, + 0xac, 0x46, 0xbf, 0x3d, 0x26, 0x6b, 0x26, 0x3d, 0x02, 0xa1, 0x0f, 0xbe, + 0x68, 0x09, 0x42, 0x3e, 0x32, 0xf7, 0x5d, 0x3e, 0xad, 0xa5, 0xec, 0x3d, + 0x97, 0x8a, 0xb1, 0x3e, 0xc7, 0x0e, 0xee, 0xbd, 0x12, 0x2d, 0xf0, 0x3d, + 0x05, 0x02, 0xae, 0xbe, 0x63, 0x2c, 0xe3, 0x3c, 0x1c, 0x86, 0x47, 0x3e, + 0xe5, 0xa6, 0x46, 0x3e, 0x6b, 0x88, 0x8c, 0xbd, 0x6b, 0x84, 0x28, 0x3e, + 0xad, 0xc4, 0x79, 0xbe, 0x77, 0x46, 0xfc, 0xbc, 0x40, 0x27, 0x28, 0x3e, + 0x2c, 0x30, 0xe0, 0xbd, 0xe5, 0xb6, 0xd8, 0x3c, 0x68, 0xac, 0x90, 0x3e, + 0x14, 0x75, 0x1c, 0xbe, 0x0d, 0xc9, 0x9e, 0xbe, 0x4b, 0xec, 0x4f, 0xbe, + 0x1b, 0x78, 0xcb, 0x3d, 0xf6, 0xeb, 0xd3, 0xbe, 0x38, 0xab, 0xed, 0xbd, + 0x86, 0x1a, 0xf4, 0x3d, 0x43, 0x3f, 0xd2, 0x3c, 0xbf, 0x91, 0xc3, 0xbe, + 0x76, 0x19, 0x6a, 0x3d, 0xd9, 0x29, 0x35, 0xbe, 0x3b, 0x83, 0xe7, 0xba, + 0x6e, 0xcd, 0x2e, 0xbf, 0xec, 0x5f, 0x25, 0x3e, 0xb6, 0x01, 0x9f, 0x3d, + 0xd6, 0x6e, 0x37, 0x3e, 0x09, 0x82, 0x49, 0xbe, 0x31, 0x65, 0x67, 0xbf, + 0x1a, 0xf7, 0xfc, 0xbe, 0xa9, 0x80, 0x7a, 0xbc, 0x09, 0x7e, 0xa0, 0xbe, + 0x62, 0x1c, 0x38, 0xbd, 0xce, 0x50, 0x9e, 0x3d, 0x69, 0x4b, 0x47, 0xbe, + 0x53, 0x6d, 0x8f, 0xbe, 0xee, 0x17, 0xa4, 0xbe, 0x42, 0x75, 0x52, 0x3c, + 0xe5, 0x67, 0x1a, 0xbe, 0x34, 0xb7, 0x53, 0xbf, 0xc7, 0xdf, 0xb3, 0xbd, + 0x6a, 0x60, 0x00, 0xbe, 0x32, 0xd9, 0xc0, 0x3d, 0x15, 0x83, 0x9c, 0x3b, + 0x79, 0x94, 0x9a, 0xbe, 0xac, 0x0b, 0xf1, 0xbe, 0x13, 0xd8, 0x11, 0xbe, + 0x25, 0x26, 0xb3, 0xbe, 0x13, 0xce, 0xc7, 0x3d, 0x3a, 0xb6, 0x17, 0x3e, + 0xb7, 0xd8, 0x6f, 0x3d, 0x1f, 0xc1, 0xe2, 0xbe, 0x5a, 0x21, 0x26, 0xbe, + 0xbd, 0x0b, 0x39, 0x3e, 0xf3, 0xb2, 0x87, 0x3d, 0x47, 0x17, 0xf0, 0xbe, + 0x09, 0xb9, 0xd8, 0x3d, 0x3c, 0x61, 0x81, 0x3e, 0x21, 0xe5, 0xba, 0xbd, + 0x26, 0x80, 0x0c, 0x3e, 0x95, 0x3e, 0x9a, 0xbd, 0x45, 0xc1, 0x31, 0xbe, + 0x3d, 0xa0, 0x14, 0xbd, 0x02, 0x58, 0x32, 0xbd, 0x59, 0xc7, 0xff, 0x3c, + 0x15, 0x05, 0xe2, 0x3e, 0x86, 0x1d, 0x0f, 0xbe, 0xa0, 0x40, 0xbc, 0xbe, + 0x68, 0x5e, 0x44, 0x3e, 0x99, 0x1f, 0xfb, 0x3d, 0xfe, 0x0e, 0x21, 0xbe, + 0x13, 0xf4, 0x67, 0xbe, 0x39, 0x82, 0xe1, 0xbc, 0xf4, 0x0d, 0xdb, 0x3d, + 0xd6, 0x05, 0x98, 0xbe, 0xd5, 0x96, 0x43, 0xbe, 0x0d, 0x8b, 0x04, 0xbe, + 0xdb, 0x1b, 0x9b, 0xbd, 0xa7, 0xb8, 0x86, 0xbe, 0x93, 0xa7, 0x48, 0x3e, + 0xff, 0x83, 0x70, 0xbe, 0x81, 0x92, 0xb6, 0x3e, 0x00, 0xde, 0x2d, 0xbe, + 0xcb, 0xea, 0xd1, 0xbe, 0x26, 0x5f, 0x89, 0x3d, 0xa9, 0x78, 0x94, 0xbc, + 0x46, 0xe4, 0xc0, 0xbc, 0xf8, 0x7a, 0x7f, 0xbe, 0x7f, 0x6c, 0xda, 0xbd, + 0xf0, 0x26, 0x7a, 0x3d, 0xc8, 0xe1, 0x68, 0xbe, 0xf9, 0x78, 0x22, 0xbd, + 0xc6, 0xe1, 0x7e, 0xbe, 0x48, 0xa7, 0xa1, 0xbd, 0x79, 0xdc, 0x63, 0xbc, + 0x3b, 0x33, 0x25, 0x3e, 0xe9, 0x5d, 0x8a, 0xbe, 0x68, 0x6a, 0x42, 0xbe, + 0xb6, 0x15, 0x24, 0xbd, 0x8a, 0xab, 0xec, 0xbe, 0x57, 0x0d, 0xf0, 0x3d, + 0xea, 0x0e, 0xd0, 0xbd, 0x6e, 0xe6, 0xd4, 0xbd, 0xc1, 0x3c, 0x84, 0xbe, + 0xfe, 0xc7, 0x0f, 0xbe, 0xe0, 0x6e, 0x3b, 0xbd, 0xb5, 0x5b, 0x75, 0xbe, + 0x57, 0xb2, 0x70, 0xbe, 0x9a, 0xb9, 0x45, 0x3c, 0xc0, 0xe1, 0xd2, 0xbc, + 0x71, 0xc3, 0xbc, 0xbd, 0xc1, 0x3a, 0x0b, 0x3d, 0x0d, 0x92, 0xd4, 0xbc, + 0x90, 0x72, 0x14, 0xbd, 0x19, 0x0f, 0x79, 0x3c, 0x8d, 0xb0, 0xc9, 0xbe, + 0xec, 0x8b, 0xe0, 0x3d, 0x61, 0xeb, 0x92, 0xbd, 0xcc, 0x90, 0xf1, 0xbd, + 0xc4, 0x38, 0x92, 0xbd, 0x89, 0xa0, 0x33, 0xbc, 0x4e, 0xe5, 0x4d, 0xbd, + 0x7a, 0xd2, 0x83, 0xbd, 0x61, 0x9a, 0xd0, 0x3d, 0x33, 0xca, 0x94, 0xbd, + 0x8c, 0x1a, 0xb3, 0x3d, 0x4e, 0x06, 0x51, 0x3e, 0xd5, 0x7a, 0xb3, 0x3d, + 0x6a, 0x1d, 0x01, 0xbe, 0x3a, 0xc0, 0xc4, 0x3e, 0xe6, 0xf7, 0xee, 0xbd, + 0xe2, 0x8e, 0x1f, 0xbe, 0xc2, 0xfd, 0x96, 0x3e, 0x6c, 0xe1, 0x62, 0xbe, + 0xff, 0xd8, 0x13, 0xbe, 0xbd, 0xb6, 0x81, 0xbd, 0xd8, 0xdc, 0x4a, 0x3b, + 0xe5, 0xe2, 0xcb, 0x3a, 0x49, 0x4c, 0xc3, 0xbd, 0xfb, 0x38, 0xa1, 0x3d, + 0x96, 0x5e, 0xa7, 0xbc, 0xcb, 0xde, 0x23, 0x3d, 0x0f, 0x0d, 0x8f, 0x3e, + 0x9b, 0x54, 0x04, 0x3e, 0x7d, 0x1b, 0x78, 0xbd, 0x06, 0x2f, 0x2a, 0x3e, + 0x3e, 0x9a, 0xce, 0xbe, 0xa2, 0xa0, 0xa7, 0xbe, 0x58, 0x1f, 0x5e, 0x3e, + 0x23, 0xe4, 0xc5, 0x3c, 0x25, 0xc4, 0x1f, 0xbe, 0x9a, 0x80, 0x56, 0x3b, + 0x69, 0xb9, 0x17, 0x3e, 0x4d, 0xd6, 0xf1, 0x3d, 0xf3, 0x63, 0x4a, 0xbe, + 0x2b, 0xa0, 0xcd, 0x3d, 0x14, 0xc3, 0x28, 0xbe, 0x61, 0x71, 0x05, 0x3e, + 0x6a, 0xb8, 0x07, 0x3d, 0x55, 0xf9, 0x2b, 0x3e, 0x39, 0x86, 0x0f, 0xbd, + 0x38, 0xd5, 0x61, 0x3e, 0xdb, 0x24, 0xa6, 0xbe, 0x20, 0xde, 0x2c, 0xbe, + 0x15, 0x3f, 0xc8, 0x3d, 0x9e, 0x9b, 0x45, 0x3d, 0xed, 0x09, 0x22, 0xbe, + 0x2a, 0x2c, 0x33, 0xbe, 0x37, 0x8a, 0x54, 0xbd, 0x48, 0x0c, 0x84, 0xba, + 0x2f, 0x23, 0x12, 0xbe, 0xc4, 0x7c, 0x48, 0x3e, 0x82, 0xd5, 0x95, 0xbe, + 0x79, 0xda, 0x06, 0x3c, 0x2d, 0xf1, 0x33, 0x3d, 0x55, 0x60, 0x77, 0xbd, + 0x1d, 0x61, 0x05, 0xbe, 0x81, 0xcb, 0x8d, 0x3e, 0x06, 0x08, 0x01, 0xbe, + 0x95, 0xc7, 0x51, 0xbd, 0xf0, 0x72, 0x99, 0xbd, 0x06, 0xb2, 0xc6, 0x3d, + 0xfb, 0x0f, 0xca, 0x3d, 0x8e, 0x73, 0x7e, 0x3b, 0xfd, 0x34, 0x67, 0xbd, + 0x70, 0x32, 0xdd, 0xbc, 0x2d, 0x99, 0xea, 0xbe, 0xc2, 0x73, 0x64, 0xbd, + 0x00, 0x94, 0x0f, 0xbe, 0x75, 0x7a, 0xba, 0xbd, 0x85, 0xc4, 0x2a, 0x3d, + 0x76, 0x88, 0x30, 0x3d, 0x53, 0x72, 0x9d, 0xbd, 0x30, 0x57, 0x81, 0x3e, + 0x6e, 0x96, 0x95, 0xbd, 0x3c, 0x35, 0x38, 0x3d, 0x9b, 0x98, 0x6f, 0xbb, + 0xf0, 0x78, 0x27, 0x3d, 0xdc, 0xa7, 0x13, 0x3d, 0x6d, 0x26, 0x01, 0x3e, + 0x0c, 0x56, 0x0d, 0x3c, 0x34, 0x73, 0x26, 0x3d, 0x9c, 0xd1, 0xe5, 0xbe, + 0x3a, 0x3d, 0x80, 0xbd, 0x12, 0x3e, 0xf8, 0xbd, 0xaf, 0x15, 0x82, 0xbd, + 0x50, 0x20, 0x49, 0xbe, 0x69, 0x45, 0x3e, 0x3e, 0xaa, 0xf1, 0x2d, 0xbe, + 0x46, 0x98, 0x5a, 0xbe, 0x3a, 0x09, 0xb8, 0xbe, 0x10, 0xa7, 0x6b, 0xbe, + 0xf4, 0x2a, 0x71, 0x3e, 0x34, 0xde, 0x1a, 0xbd, 0x14, 0x25, 0xc5, 0x3d, + 0xac, 0x10, 0x0e, 0xbd, 0xdd, 0xd6, 0x6c, 0x3e, 0x4a, 0x10, 0x6d, 0x3d, + 0x8a, 0x02, 0x45, 0xbe, 0x79, 0x00, 0x82, 0x3d, 0xa0, 0x2d, 0x6d, 0x3e, + 0xcf, 0x3b, 0xaa, 0xbd, 0x62, 0xbc, 0x3b, 0x3d, 0x1d, 0x84, 0x85, 0x3e, + 0x74, 0x75, 0x49, 0xbe, 0x72, 0x3d, 0x81, 0xbe, 0xd5, 0xb1, 0x13, 0xbe, + 0x15, 0x6e, 0x95, 0xbe, 0x6f, 0x24, 0x95, 0x3e, 0x07, 0xc3, 0x22, 0x3e, + 0x71, 0x27, 0x1c, 0xbd, 0xf8, 0x56, 0x55, 0xbe, 0x4d, 0x4b, 0x50, 0xbe, + 0x4e, 0x36, 0x61, 0xbd, 0xb4, 0x21, 0x08, 0xbf, 0x18, 0x79, 0xe1, 0xbc, + 0x05, 0x0e, 0x58, 0x3e, 0xaf, 0xa3, 0xff, 0xbd, 0x20, 0x4e, 0x78, 0xbd, + 0xce, 0x7a, 0xfe, 0xbc, 0x8d, 0xca, 0x02, 0xbe, 0x0c, 0x26, 0xb8, 0xbe, + 0x64, 0x00, 0xab, 0xbe, 0xd2, 0x25, 0x2b, 0xbe, 0x1f, 0x46, 0x4e, 0xbd, + 0x30, 0x12, 0xa0, 0xbd, 0xea, 0xae, 0x29, 0x3e, 0x7f, 0xe0, 0x8f, 0xbe, + 0x9e, 0x18, 0x2b, 0x3d, 0xe2, 0xa8, 0x14, 0xbe, 0x23, 0xe3, 0x17, 0x3c, + 0xee, 0xc3, 0x21, 0x3e, 0x43, 0xdf, 0xce, 0x3d, 0x12, 0x1c, 0xd2, 0xbb, + 0x7f, 0xd5, 0xca, 0x3d, 0x3c, 0x5c, 0xf1, 0x3d, 0x95, 0x71, 0x44, 0x3d, + 0x93, 0xf3, 0xba, 0xbe, 0x68, 0x93, 0x5c, 0xbe, 0x10, 0x67, 0xf1, 0xbe, + 0x2a, 0x15, 0x18, 0x3d, 0x73, 0xe2, 0x82, 0x3b, 0xd6, 0x91, 0xbc, 0x3d, + 0x52, 0xcb, 0xb9, 0xbe, 0xcb, 0x4a, 0xd7, 0x3c, 0x6c, 0x72, 0xa3, 0xbe, + 0xf7, 0xb6, 0xed, 0xbc, 0xf2, 0x4b, 0x78, 0x3a, 0x22, 0x3b, 0x92, 0xbe, + 0x93, 0xd9, 0x90, 0x3e, 0x45, 0x47, 0x15, 0xbe, 0x15, 0xb5, 0x29, 0xbc, + 0x12, 0x00, 0xe3, 0xbd, 0xfb, 0xb2, 0xa7, 0xbe, 0x88, 0x19, 0x9b, 0xbe, + 0x18, 0x47, 0x29, 0xbe, 0x65, 0xe8, 0xec, 0xbb, 0xd7, 0x95, 0x5e, 0xbe, + 0x44, 0xf0, 0xae, 0xbd, 0x5e, 0xb2, 0x63, 0xbe, 0x8f, 0x8c, 0xda, 0xbd, + 0x21, 0xec, 0xce, 0x3c, 0x61, 0xec, 0xc9, 0xbd, 0xc4, 0xbc, 0xae, 0xbe, + 0x55, 0x77, 0xa7, 0xbe, 0x5b, 0x6f, 0x43, 0xbe, 0x09, 0x7c, 0x72, 0x3d, + 0x07, 0x9c, 0x9d, 0xbe, 0xa3, 0x3f, 0x50, 0x3c, 0x1c, 0xa9, 0x0c, 0xbe, + 0x67, 0x2d, 0x3b, 0xbd, 0xc4, 0xed, 0x10, 0x3d, 0x50, 0xa2, 0xd4, 0xbe, + 0x47, 0x29, 0x0c, 0xbe, 0x9d, 0x91, 0x12, 0x3d, 0x88, 0xdd, 0x67, 0xb8, + 0x38, 0x07, 0x2f, 0xbe, 0x1a, 0x06, 0x37, 0xbc, 0x62, 0x98, 0xa3, 0xbe, + 0x98, 0xcc, 0x81, 0x3e, 0x6e, 0x97, 0xd6, 0xbd, 0x95, 0x8b, 0xdd, 0xbd, + 0x79, 0xa4, 0xf3, 0x3c, 0xb5, 0xc2, 0x11, 0xbe, 0xd3, 0xec, 0x66, 0xbe, + 0xa3, 0x56, 0x80, 0xbc, 0x01, 0x00, 0x63, 0x3c, 0x56, 0x89, 0x87, 0x3b, + 0xe2, 0x59, 0x39, 0xbe, 0x4d, 0x6d, 0xfb, 0x3d, 0xa0, 0x39, 0xcc, 0x3d, + 0x69, 0x45, 0x8e, 0xbd, 0x5f, 0x84, 0xd6, 0x3d, 0xa1, 0x84, 0xbd, 0x3d, + 0x10, 0x07, 0x38, 0xbe, 0x9e, 0xfa, 0x10, 0x3e, 0xb7, 0xa7, 0x29, 0xbe, + 0x97, 0x88, 0x6a, 0xbd, 0xbe, 0x44, 0x49, 0xbc, 0x6d, 0xaf, 0x52, 0xbe, + 0x1b, 0x47, 0x60, 0xbd, 0x2d, 0x4d, 0x09, 0x3e, 0xaf, 0x9b, 0x4d, 0x3d, + 0x0e, 0x61, 0x8d, 0x3b, 0x79, 0xf0, 0x43, 0x3e, 0xf1, 0xe4, 0x6e, 0x3e, + 0x30, 0x82, 0xb4, 0x3d, 0xb1, 0x9a, 0x1f, 0xbe, 0xf0, 0x6d, 0xe7, 0x3d, + 0xa4, 0x03, 0x72, 0x3d, 0xf6, 0x2b, 0xa5, 0x3b, 0xd0, 0x1c, 0xd9, 0x3d, + 0x46, 0x22, 0xaa, 0xbe, 0xd3, 0x08, 0x54, 0xbd, 0xcd, 0xb1, 0xc2, 0xba, + 0x07, 0xf2, 0xb6, 0xbd, 0xfe, 0x75, 0x73, 0x3c, 0xe4, 0x9b, 0x0a, 0xbd, + 0xc7, 0x90, 0x9e, 0xbd, 0xf1, 0xea, 0xa7, 0x3c, 0xc8, 0x26, 0x52, 0x3e, + 0xfc, 0x27, 0xc7, 0x3e, 0x97, 0x6f, 0xa9, 0x3d, 0x7f, 0xcb, 0x24, 0xbe, + 0x7d, 0x06, 0xb8, 0xbd, 0xa3, 0x40, 0x5e, 0x3d, 0x27, 0x47, 0x2b, 0xbe, + 0x44, 0x61, 0x8d, 0x3e, 0x04, 0x26, 0x8c, 0xbe, 0x5c, 0x59, 0xfa, 0xbd, + 0xcf, 0xf0, 0xa6, 0xbd, 0xc6, 0xdd, 0x9c, 0x3d, 0x44, 0xe8, 0x70, 0xbe, + 0xda, 0x14, 0x31, 0xbe, 0x38, 0xdb, 0xd2, 0xbd, 0x50, 0xa7, 0x19, 0x3d, + 0xf0, 0x2d, 0x3b, 0x3e, 0xfd, 0x9c, 0xe7, 0x3e, 0xb3, 0x62, 0xbb, 0x3d, + 0xe6, 0xe4, 0x95, 0x3c, 0xa4, 0x26, 0x10, 0x3e, 0xf6, 0x96, 0x4d, 0x3d, + 0xd8, 0x93, 0x34, 0xbd, 0x87, 0x9d, 0x81, 0xbd, 0x4f, 0xb4, 0x85, 0xbd, + 0x7a, 0xb6, 0x55, 0xbe, 0xbd, 0xfd, 0x84, 0x3d, 0x53, 0xa2, 0x15, 0x3e, + 0x10, 0x1e, 0x93, 0xbe, 0x4e, 0xab, 0xc5, 0xbd, 0x55, 0xd9, 0xdf, 0x3d, + 0xca, 0xf4, 0x6d, 0x3d, 0xdb, 0xfe, 0x84, 0x3e, 0x95, 0x98, 0x36, 0x3e, + 0x93, 0x3e, 0xfd, 0x3c, 0x61, 0x70, 0x04, 0xbd, 0x46, 0x72, 0xe4, 0x3c, + 0xa9, 0x0e, 0x58, 0xbe, 0xe5, 0x86, 0x33, 0x3d, 0x2d, 0x46, 0x03, 0xbe, + 0x94, 0xdc, 0xf0, 0xbd, 0xaa, 0x48, 0x27, 0xbe, 0x34, 0xb0, 0x63, 0xbd, + 0x56, 0x1f, 0xb7, 0xbb, 0xcd, 0xce, 0xde, 0xbd, 0xc7, 0xb0, 0x4d, 0x3d, + 0x65, 0x11, 0x01, 0x3e, 0xf5, 0xab, 0x8a, 0x3d, 0x34, 0xe1, 0x10, 0xbe, + 0x49, 0x06, 0xff, 0xbd, 0xb6, 0xe4, 0x69, 0xbe, 0xc8, 0x48, 0x78, 0xbd, + 0x1a, 0x63, 0x2b, 0x3d, 0x7f, 0x73, 0x35, 0xbe, 0x1e, 0x01, 0xf5, 0xbd, + 0x89, 0xad, 0x6b, 0xbe, 0x6a, 0x85, 0x09, 0x3d, 0x74, 0x69, 0xf6, 0xbd, + 0x62, 0x39, 0xc9, 0xbe, 0x72, 0x00, 0x8e, 0x3d, 0xfc, 0xa5, 0x9c, 0xbe, + 0x53, 0x4e, 0x95, 0xbd, 0xc7, 0x7d, 0xae, 0xbb, 0xa7, 0x61, 0xb9, 0xba, + 0xb8, 0x9a, 0xce, 0xbe, 0x80, 0xcc, 0xd2, 0xbe, 0xcf, 0xeb, 0x5c, 0xbe, + 0x69, 0xa4, 0x0e, 0xbd, 0x12, 0xa9, 0x1e, 0xbe, 0xc9, 0x6d, 0x50, 0xbe, + 0xfc, 0xd5, 0xe6, 0xbd, 0x9f, 0x5c, 0xd0, 0xbe, 0x33, 0xcf, 0xbb, 0xbd, + 0x70, 0x66, 0xa5, 0xbe, 0x6b, 0x92, 0xfa, 0xbe, 0x27, 0xa0, 0x4b, 0x3d, + 0x5d, 0x26, 0x75, 0xbe, 0x46, 0x63, 0x8c, 0xbe, 0x57, 0x65, 0xaf, 0xbd, + 0xea, 0xd8, 0x1c, 0x3d, 0xcd, 0x4f, 0xc2, 0xbe, 0x87, 0xf0, 0x0b, 0xbf, + 0x25, 0xdf, 0xd9, 0xbe, 0x5d, 0x6a, 0x4e, 0x3d, 0xaa, 0x06, 0xf1, 0xbd, + 0x31, 0xe2, 0x60, 0xbe, 0x72, 0x6f, 0xf2, 0xbc, 0xb9, 0x4a, 0x9e, 0xbe, + 0xfb, 0xb0, 0xd7, 0xbd, 0x9f, 0xfa, 0x96, 0xbe, 0xc8, 0x14, 0x13, 0xbf, + 0xaa, 0x6a, 0x39, 0xbc, 0x7f, 0x24, 0x5a, 0xbd, 0x12, 0x36, 0xa9, 0x3d, + 0x9e, 0x86, 0x60, 0x3d, 0xbc, 0x33, 0x85, 0x3c, 0x4c, 0x72, 0xf2, 0xbe, + 0x39, 0x9f, 0xba, 0xbe, 0xc2, 0xa5, 0x68, 0xbe, 0x35, 0x06, 0xa1, 0x3c, + 0xb5, 0xad, 0x53, 0x3d, 0x3e, 0xd0, 0xa2, 0xbd, 0xfd, 0x9b, 0xaa, 0x3c, + 0x0a, 0x8f, 0xa8, 0x3e, 0x24, 0xca, 0x09, 0xbd, 0x14, 0x2c, 0x4c, 0xbe, + 0x56, 0x1b, 0xae, 0xbd, 0x9d, 0x45, 0x60, 0xbc, 0xb7, 0x16, 0x5f, 0xbe, + 0x57, 0x8f, 0x8d, 0x3e, 0x5d, 0xab, 0x50, 0xbd, 0xcc, 0x11, 0xb0, 0x3c, + 0x52, 0x21, 0xd1, 0xbe, 0xfb, 0xff, 0xb2, 0xbc, 0x9b, 0xd1, 0x14, 0xbe, + 0xfc, 0x2c, 0x7e, 0xbd, 0x39, 0x94, 0xfb, 0xb9, 0xca, 0x93, 0xca, 0xbd, + 0x0a, 0xa7, 0xfc, 0xbd, 0xf6, 0xd3, 0x7f, 0x3e, 0xd3, 0xda, 0x0b, 0xbe, + 0x8e, 0xf9, 0x99, 0xbd, 0xc5, 0xe4, 0x73, 0xbd, 0xba, 0x7d, 0xae, 0xbd, + 0x5b, 0x0e, 0x24, 0x3d, 0xcf, 0x46, 0x90, 0x3d, 0x42, 0x80, 0x88, 0xbc, + 0x93, 0x44, 0xa0, 0x3c, 0x02, 0x2c, 0x53, 0xbb, 0xb3, 0x0d, 0xdf, 0x3e, + 0xf3, 0x46, 0xc8, 0x3c, 0xc5, 0x84, 0xa4, 0xbd, 0xf8, 0x13, 0xad, 0x3b, + 0x6d, 0x41, 0xb5, 0x3c, 0xeb, 0x74, 0x5f, 0xbe, 0x67, 0x82, 0xd0, 0x3e, + 0x3c, 0xd0, 0x9c, 0xbd, 0xa0, 0x97, 0x48, 0x3e, 0x1f, 0x0a, 0x36, 0xbe, + 0xf6, 0x51, 0x4a, 0xbd, 0x4f, 0xe5, 0x20, 0xbe, 0xa1, 0x8e, 0x0d, 0xbd, + 0x70, 0x13, 0x9c, 0xbd, 0xe8, 0x43, 0xb1, 0xbb, 0xc3, 0x59, 0xa4, 0x3e, + 0x06, 0xd6, 0x10, 0x3e, 0x8e, 0x8e, 0x88, 0x3e, 0xe8, 0xa8, 0xe1, 0xbd, + 0xb3, 0x6e, 0x6b, 0xbe, 0x28, 0x96, 0x7f, 0xbe, 0xde, 0xcc, 0xab, 0xbe, + 0xb0, 0xc9, 0xa6, 0xbe, 0x60, 0x75, 0xc5, 0x3d, 0x39, 0x36, 0xea, 0x3d, + 0x53, 0x43, 0xea, 0xbe, 0xb0, 0x9d, 0x9e, 0x3d, 0x06, 0x01, 0x7c, 0xbe, + 0xb8, 0x49, 0x51, 0xbe, 0x7c, 0xad, 0xe8, 0x3c, 0xe2, 0x4e, 0xc6, 0x3c, + 0x9f, 0x1f, 0x56, 0xbe, 0x5a, 0xd7, 0x79, 0x3d, 0x62, 0x73, 0x81, 0xbe, + 0xe6, 0xc1, 0xd1, 0x3c, 0x4e, 0x1c, 0x18, 0x3f, 0x2a, 0xe7, 0xa4, 0x3e, + 0xf6, 0x80, 0x24, 0x3e, 0x06, 0xf7, 0x00, 0x3f, 0x34, 0x8e, 0x91, 0xbe, + 0x17, 0x9f, 0xf3, 0x3c, 0xc5, 0x69, 0x15, 0x3f, 0x18, 0xd8, 0xab, 0x3d, + 0x98, 0xd2, 0x55, 0x3d, 0xf1, 0x5f, 0xdb, 0x3e, 0x21, 0x48, 0x05, 0xbf, + 0xf7, 0x44, 0x65, 0xbd, 0xbd, 0x27, 0xf6, 0x3e, 0x4e, 0x58, 0xaa, 0x3e, + 0x1f, 0x87, 0xd5, 0x3b, 0x4e, 0x6f, 0xa8, 0xbd, 0x1f, 0x08, 0xd0, 0xbe, + 0x06, 0xd5, 0x76, 0x3d, 0x15, 0xf8, 0x48, 0x3e, 0x38, 0xe0, 0x0e, 0xbf, + 0xaa, 0x5c, 0xaf, 0xbd, 0xfd, 0xe2, 0xbd, 0xbe, 0x54, 0x5b, 0x2f, 0xbf, + 0x37, 0xe6, 0xd9, 0x3d, 0xd4, 0x6d, 0x89, 0xbe, 0xe4, 0xa0, 0xc4, 0x3c, + 0x76, 0x37, 0xe2, 0xbe, 0xd2, 0xf0, 0x98, 0x3c, 0xe7, 0x7a, 0xcc, 0x3e, + 0xaa, 0x3f, 0x11, 0xbf, 0x86, 0x4a, 0x3e, 0xbf, 0xae, 0xa4, 0xe5, 0x3b, + 0x62, 0x98, 0x31, 0xbe, 0xff, 0x4e, 0x80, 0x3d, 0x79, 0xe7, 0xba, 0xbd, + 0x93, 0x5b, 0xca, 0xbe, 0x3c, 0x97, 0x97, 0x3d, 0xf1, 0x41, 0x2d, 0xbf, + 0xc9, 0xa4, 0x4b, 0xbf, 0x9e, 0x2a, 0xde, 0x3d, 0xad, 0x4e, 0x84, 0xbe, + 0x01, 0x2f, 0x5c, 0x3b, 0xab, 0x1c, 0x45, 0xbe, 0x32, 0x41, 0x2b, 0x3c, + 0x73, 0x53, 0x50, 0x3e, 0xe2, 0xc4, 0x06, 0xbf, 0x62, 0xef, 0x13, 0xbf, + 0x6e, 0x3d, 0x86, 0x3c, 0x6d, 0x9a, 0xee, 0xbd, 0x49, 0xbc, 0xa8, 0x3d, + 0x68, 0xfe, 0x11, 0x3e, 0xde, 0x38, 0xfa, 0xbe, 0xbc, 0x06, 0x81, 0xbc, + 0xb7, 0x85, 0xc3, 0xbe, 0x26, 0x4f, 0x24, 0xbf, 0xe8, 0x94, 0xbb, 0xbb, + 0x16, 0x0e, 0x9b, 0xbe, 0xca, 0xe8, 0x52, 0xbe, 0xa2, 0xb7, 0x53, 0xbd, + 0x04, 0xef, 0x52, 0x3b, 0x08, 0xad, 0x1e, 0xbd, 0x72, 0xba, 0x01, 0xbf, + 0xd7, 0x49, 0x87, 0xbe, 0xf3, 0x7b, 0xf2, 0x3c, 0xfc, 0x8e, 0x84, 0x3d, + 0xcf, 0xcc, 0x3a, 0x3d, 0x35, 0xe0, 0xe4, 0xbc, 0x88, 0x88, 0x05, 0x3d, + 0x8d, 0x2f, 0xab, 0x3d, 0x33, 0x4f, 0x49, 0xbe, 0xc6, 0x68, 0xfc, 0xbe, + 0xfc, 0x90, 0x2f, 0xbb, 0x20, 0x38, 0xc1, 0xbd, 0x19, 0x53, 0x27, 0xbd, + 0xa2, 0xee, 0x09, 0xbe, 0x5e, 0x6f, 0x6b, 0x3d, 0x1e, 0xf9, 0x0d, 0x3d, + 0x76, 0xd5, 0xdd, 0xbe, 0xe3, 0xa8, 0xac, 0xbe, 0xaa, 0xad, 0x0d, 0x3d, + 0x91, 0x62, 0xce, 0xbd, 0x83, 0x52, 0xbb, 0xbc, 0xfd, 0x4e, 0x18, 0x3e, + 0x98, 0x73, 0x8f, 0xbd, 0x55, 0x1f, 0x55, 0x39, 0xb9, 0x85, 0x67, 0xbe, + 0x1a, 0xcf, 0xd3, 0xbe, 0x97, 0x08, 0x14, 0xbd, 0xca, 0xf1, 0x11, 0xbe, + 0x8b, 0x4b, 0x39, 0xbe, 0xe8, 0xe0, 0xd8, 0xbc, 0x0a, 0xf4, 0x26, 0xbc, + 0x0d, 0xda, 0x20, 0xbd, 0x52, 0xe7, 0xb1, 0xbe, 0xfc, 0x69, 0xd3, 0xbe, + 0x6f, 0x10, 0xc8, 0x3c, 0xb8, 0xb5, 0x20, 0xbe, 0xcd, 0xd6, 0x72, 0x3d, + 0xb4, 0x1b, 0xca, 0xbd, 0x1c, 0xa7, 0xfb, 0xbd, 0xf4, 0x0c, 0x91, 0xbd, + 0x95, 0xec, 0xda, 0xbc, 0x00, 0xff, 0x2b, 0xbe, 0x17, 0x4b, 0x45, 0xbd, + 0x45, 0x0c, 0x85, 0xbe, 0x97, 0x38, 0x88, 0xbd, 0x76, 0xfb, 0x0a, 0xbe, + 0xe3, 0xb5, 0xdf, 0x3c, 0x80, 0x2c, 0xd5, 0x3d, 0xc7, 0xb8, 0xb5, 0xbe, + 0x94, 0x5d, 0xba, 0xbe, 0x1e, 0x73, 0xba, 0x3c, 0xe6, 0x81, 0x25, 0xbe, + 0xff, 0x3d, 0x49, 0x3d, 0xc1, 0x10, 0x7d, 0x3e, 0x62, 0x4f, 0x55, 0xbe, + 0x3e, 0x29, 0x2f, 0xbe, 0x8c, 0x31, 0x6c, 0xbe, 0xb3, 0x0f, 0xdf, 0xbe, + 0x63, 0x87, 0xb2, 0xbc, 0xf0, 0x8a, 0x3f, 0xbe, 0x24, 0xe6, 0x92, 0x3c, + 0xb3, 0x20, 0xa7, 0xbd, 0x89, 0xa6, 0xb4, 0x3b, 0x73, 0xe7, 0xfc, 0x3d, + 0xe7, 0x9d, 0xbc, 0xbe, 0x09, 0x19, 0x97, 0xbe, 0x7c, 0xfc, 0x78, 0x3d, + 0xae, 0x7b, 0x23, 0xbe, 0xe5, 0x86, 0x3a, 0x3d, 0x07, 0x56, 0x87, 0xbd, + 0x8e, 0x90, 0x87, 0xbe, 0xc4, 0x20, 0x4e, 0xbe, 0x22, 0x0e, 0x38, 0xbc, + 0xe7, 0x44, 0xae, 0xbe, 0xa8, 0xd5, 0x1a, 0x3d, 0x93, 0x4e, 0x93, 0xbe, + 0x00, 0xb9, 0x2f, 0xbc, 0x30, 0xcf, 0xb5, 0xbe, 0xfb, 0x54, 0x8b, 0xbb, + 0x65, 0xd0, 0x00, 0x3e, 0xd5, 0x24, 0xda, 0xbe, 0xcf, 0xc4, 0xd8, 0xbe, + 0x34, 0x93, 0xb9, 0x3c, 0x02, 0x97, 0x8a, 0xbe, 0x8f, 0xd4, 0x22, 0x3d, + 0x9a, 0xfc, 0xa8, 0xbe, 0x3c, 0x29, 0x5c, 0xbd, 0xab, 0x08, 0xf5, 0x3c, + 0x45, 0x1e, 0x42, 0xbe, 0xc4, 0x6e, 0xc9, 0xbe, 0x0a, 0xb0, 0xce, 0x3c, + 0x7c, 0xf9, 0x94, 0xbe, 0x09, 0xeb, 0x1f, 0xbe, 0x82, 0x6a, 0x9e, 0xbe, + 0x29, 0x1f, 0x9e, 0x3c, 0x00, 0x19, 0x0d, 0x3d, 0x47, 0x54, 0x77, 0xbe, + 0x31, 0x68, 0x44, 0xbf, 0x0a, 0x4c, 0x14, 0x3d, 0xdd, 0x90, 0xe0, 0x3d, + 0xd4, 0xc2, 0x8e, 0x3d, 0x64, 0xfb, 0xa7, 0xbe, 0x76, 0x35, 0x63, 0xbe, + 0xdf, 0xe6, 0x18, 0xbe, 0x82, 0x50, 0x08, 0xbf, 0xd5, 0x97, 0x1b, 0xbf, + 0x28, 0x8d, 0xce, 0xbc, 0x09, 0xa0, 0xaa, 0xbe, 0xbc, 0x7f, 0xce, 0xbd, + 0xae, 0x93, 0x7a, 0xbe, 0x6c, 0x9f, 0xec, 0x3b, 0xf2, 0x7c, 0x89, 0x3d, + 0xa7, 0x8e, 0x9b, 0xbe, 0x7d, 0x7e, 0x01, 0xbf, 0x0d, 0xae, 0x7e, 0x3d, + 0xfc, 0xe0, 0x09, 0xbd, 0xf4, 0x70, 0x5a, 0x3d, 0xd3, 0x33, 0xf9, 0xbd, + 0xc5, 0xf8, 0xbb, 0xbe, 0xd5, 0xc6, 0x8d, 0xbe, 0xc9, 0xb1, 0x5e, 0xbe, + 0x93, 0x6e, 0xd6, 0xbe, 0xae, 0x9a, 0xca, 0x3c, 0x59, 0x9d, 0x62, 0xbe, + 0x95, 0x6f, 0x62, 0xbe, 0xa8, 0x20, 0xac, 0xbe, 0x40, 0xb2, 0xb0, 0x3c, + 0x08, 0xf4, 0xed, 0x3d, 0x39, 0x3c, 0x42, 0xbe, 0x31, 0x1c, 0xe3, 0xbe, + 0x1a, 0x1a, 0x0e, 0x3c, 0x59, 0x79, 0xe9, 0x3b, 0xa2, 0x22, 0x10, 0xbe, + 0x30, 0xaa, 0xa5, 0x3d, 0x71, 0x8f, 0x06, 0xbf, 0x16, 0x24, 0xa0, 0xbe, + 0x67, 0xb2, 0xc9, 0xbe, 0x52, 0x7b, 0x0f, 0xbf, 0xf9, 0xe9, 0x4f, 0x3d, + 0xac, 0x49, 0x17, 0xbe, 0xae, 0x48, 0x68, 0xbd, 0x7b, 0x00, 0xb6, 0xbe, + 0xeb, 0xb6, 0x68, 0x3c, 0xc9, 0xc1, 0xe9, 0x3d, 0xed, 0x2d, 0xc3, 0xbe, + 0xa0, 0x62, 0x41, 0xbf, 0x62, 0x09, 0x25, 0x3d, 0x9b, 0x06, 0x56, 0xbe, + 0x9f, 0x8b, 0x17, 0x3e, 0x34, 0xe7, 0x3c, 0x3c, 0x8a, 0xbb, 0x17, 0x3e, + 0x9d, 0xe9, 0x15, 0x3c, 0x24, 0x65, 0x04, 0xbf, 0x3e, 0x1f, 0x0c, 0xbe, + 0xd7, 0x33, 0x83, 0x3c, 0xc7, 0x55, 0xa0, 0xbc, 0xb4, 0xc6, 0xbf, 0xbd, + 0x5a, 0xb1, 0xfb, 0xbd, 0x64, 0x9e, 0x0b, 0x3d, 0x1a, 0xbc, 0x5f, 0x3e, + 0x3b, 0x78, 0xc8, 0x3d, 0x60, 0x10, 0xd6, 0xbd, 0x2d, 0xe4, 0x7d, 0x3d, + 0x9d, 0x40, 0x9b, 0xbe, 0xeb, 0x0c, 0xd1, 0xbc, 0x58, 0x00, 0x2e, 0xbe, + 0xb9, 0x75, 0xff, 0x3e, 0x4d, 0x20, 0xab, 0x3e, 0x26, 0x60, 0xed, 0xbd, + 0xc3, 0xb9, 0xdd, 0xbe, 0xb5, 0xda, 0xa8, 0x3e, 0x33, 0x68, 0xa2, 0x3d, + 0x43, 0xd0, 0xa9, 0x3c, 0xf1, 0x7f, 0x8b, 0x3b, 0x3f, 0x6b, 0xbf, 0x3e, + 0x6c, 0x69, 0x34, 0xbe, 0x79, 0x58, 0xf3, 0xbe, 0xf1, 0x2e, 0xdb, 0xbd, + 0x62, 0x48, 0x98, 0x3e, 0x59, 0x6d, 0x75, 0xbd, 0x37, 0x2c, 0x5a, 0x3c, + 0x13, 0xe7, 0x30, 0xbd, 0xa1, 0xac, 0xfc, 0x3c, 0xb2, 0xd8, 0x2a, 0x3e, + 0x22, 0x4b, 0xb6, 0xbd, 0xd1, 0xa3, 0x81, 0xbe, 0x73, 0x91, 0x15, 0xbd, + 0xe7, 0xfa, 0x76, 0x3d, 0x60, 0xf1, 0xdb, 0x3c, 0x07, 0xcf, 0xc2, 0x3c, + 0x7d, 0xaa, 0x8a, 0x3c, 0xa2, 0x13, 0xb1, 0x3e, 0xfa, 0x90, 0xa3, 0xbe, + 0x83, 0x37, 0x1f, 0x3e, 0xd3, 0xe8, 0xb0, 0x3d, 0xda, 0x49, 0x68, 0xbe, + 0x9d, 0x3c, 0x6b, 0x3e, 0xbc, 0x5a, 0x1d, 0xbc, 0xc9, 0x02, 0x25, 0x3d, + 0x9e, 0x16, 0x15, 0x3e, 0xb5, 0x40, 0x17, 0xbe, 0x19, 0x17, 0x1a, 0x3e, + 0x17, 0x95, 0x8c, 0xbd, 0xe5, 0x42, 0x0a, 0xbd, 0x61, 0x6f, 0xc9, 0x3d, + 0x16, 0x3c, 0x58, 0xbe, 0x19, 0x15, 0x7a, 0xbe, 0x2a, 0xb1, 0xa2, 0x3e, + 0x52, 0xa2, 0x5c, 0xbd, 0x92, 0x5f, 0x11, 0x3e, 0xb5, 0x1f, 0x2c, 0xbd, + 0xa5, 0xf8, 0x20, 0xbe, 0x0b, 0x75, 0xc9, 0x3d, 0x1e, 0x00, 0x82, 0x3e, + 0xfe, 0xa1, 0x61, 0x3d, 0xd5, 0x3f, 0x41, 0x3e, 0x09, 0x65, 0xcb, 0x3d, + 0xc5, 0x75, 0x1e, 0x3c, 0xbb, 0x0f, 0x76, 0xbe, 0xbf, 0xb6, 0xa0, 0x3d, + 0xdf, 0x04, 0xae, 0x3d, 0x9b, 0xda, 0x06, 0xbf, 0x38, 0x8c, 0x84, 0xbe, + 0x92, 0x5c, 0x0c, 0x3e, 0xb8, 0x67, 0xfc, 0xbd, 0xb7, 0xec, 0x95, 0xbd, + 0x13, 0x50, 0x15, 0xbe, 0x75, 0xb1, 0x8d, 0xbd, 0x0a, 0x71, 0x1e, 0x3e, + 0x7d, 0x79, 0xf8, 0x3d, 0x40, 0x97, 0x77, 0x3d, 0x3b, 0xe7, 0x9d, 0xbe, + 0xb2, 0x88, 0xaa, 0x3c, 0x79, 0x16, 0x44, 0x3d, 0xb9, 0xdc, 0x02, 0xbe, + 0xe1, 0xcb, 0xa3, 0xbc, 0x29, 0xda, 0xff, 0xbb, 0xb2, 0x4a, 0xb8, 0xbe, + 0xd9, 0x15, 0xfb, 0xbe, 0xf4, 0x0b, 0x43, 0xbd, 0x3e, 0xc0, 0x17, 0xbd, + 0x04, 0xa3, 0xae, 0xbc, 0xd0, 0xff, 0xdf, 0xbe, 0x72, 0xcb, 0x0f, 0x3e, + 0x03, 0x79, 0xff, 0x3d, 0x91, 0x65, 0x40, 0x3e, 0xb6, 0x77, 0xbb, 0xbd, + 0xa5, 0x8f, 0x15, 0xbf, 0x30, 0x61, 0x1e, 0xbe, 0x8b, 0xa9, 0xc2, 0x3d, + 0x6e, 0xdf, 0x06, 0xbe, 0x1e, 0x89, 0xdf, 0x3d, 0x8a, 0x8f, 0xf2, 0x3c, + 0xfd, 0xcd, 0x43, 0xbc, 0x9c, 0x4c, 0xa2, 0xbe, 0x30, 0xb4, 0x19, 0xbe, + 0xb3, 0x94, 0x45, 0x3d, 0x6c, 0xaf, 0xc1, 0xbd, 0xd4, 0x46, 0x12, 0xbf, + 0xbb, 0xde, 0x5b, 0x3e, 0x30, 0xb4, 0xb0, 0x3d, 0xce, 0x2e, 0x84, 0x3d, + 0x3e, 0x76, 0xce, 0xbd, 0x82, 0x5a, 0x93, 0xbe, 0x2d, 0x20, 0xc8, 0x3d, + 0x00, 0x41, 0x07, 0xbd, 0xb1, 0x5a, 0x36, 0xbe, 0x40, 0x9e, 0xce, 0xbc, + 0x04, 0x6a, 0x0a, 0x3e, 0x1d, 0xc6, 0x05, 0xbe, 0x48, 0xb8, 0x22, 0xbd, + 0xf7, 0x49, 0x95, 0xbe, 0xfb, 0xbd, 0xca, 0xbd, 0x25, 0x34, 0x31, 0xbe, + 0xc3, 0xe3, 0x0f, 0xbf, 0xcf, 0xb1, 0x32, 0x3d, 0x73, 0x0d, 0x86, 0x3d, + 0x05, 0xbb, 0xcf, 0xbc, 0x4a, 0xd5, 0x8d, 0xbd, 0xb3, 0x6e, 0x0d, 0xbf, + 0x64, 0xec, 0x2f, 0xbe, 0x52, 0xf8, 0x15, 0xbe, 0xd3, 0xa2, 0x80, 0x3d, + 0xac, 0x9f, 0x6a, 0xbd, 0x5e, 0xc7, 0xf9, 0xbd, 0xc8, 0x90, 0x81, 0xbd, + 0xe7, 0x9d, 0x49, 0x3d, 0xd4, 0x4a, 0x06, 0xbe, 0x43, 0x04, 0x00, 0x3d, + 0x27, 0xa5, 0xc2, 0xbe, 0x90, 0x68, 0xfa, 0xbe, 0xe9, 0xb1, 0x1c, 0x3e, + 0x6c, 0x40, 0x94, 0xbd, 0xee, 0xe3, 0x39, 0x3c, 0x40, 0xd9, 0xd7, 0xbd, + 0xe8, 0x19, 0x46, 0xbe, 0x72, 0x04, 0xaa, 0xbe, 0x80, 0xd8, 0xd3, 0xbd, + 0xe6, 0x8e, 0x04, 0xbe, 0x80, 0xea, 0xf8, 0xbc, 0xa2, 0x17, 0x51, 0x3e, + 0xd9, 0x2b, 0x59, 0x3d, 0x0c, 0x8f, 0x75, 0xbb, 0xdd, 0x53, 0x82, 0xbd, + 0x06, 0xc3, 0x50, 0x3d, 0xf2, 0x35, 0x54, 0xbe, 0xce, 0xf5, 0xea, 0xbe, + 0x22, 0x31, 0x61, 0xbc, 0x03, 0x0f, 0x4b, 0xbe, 0x1a, 0x66, 0x1e, 0x3d, + 0xb2, 0x6b, 0x22, 0xbe, 0xd7, 0x96, 0xe0, 0xbe, 0x08, 0x6f, 0xb3, 0xbe, + 0x30, 0x04, 0xf9, 0xbd, 0xea, 0x57, 0x23, 0xbe, 0x79, 0x3f, 0x8b, 0xbd, + 0xea, 0xa2, 0x62, 0x3d, 0xdc, 0x5b, 0xfb, 0x3c, 0x91, 0x51, 0x17, 0x3d, + 0xe7, 0x8d, 0x58, 0xbe, 0xab, 0x9a, 0x4f, 0xbd, 0x7c, 0xab, 0xad, 0xbe, + 0xa8, 0x66, 0x06, 0xbf, 0xbf, 0xfb, 0x9e, 0x3c, 0x48, 0xf1, 0x1e, 0xbd, + 0xad, 0xaf, 0x94, 0x3c, 0xf7, 0xdc, 0x81, 0xbe, 0xaa, 0x56, 0xda, 0xbe, + 0x41, 0xca, 0x1a, 0xbe, 0xaf, 0x06, 0xab, 0xbe, 0x9e, 0xb7, 0xa3, 0xbd, + 0x08, 0x79, 0xb6, 0x3d, 0x19, 0x31, 0x04, 0xbd, 0x90, 0xb5, 0xed, 0xbc, + 0x5d, 0xf7, 0x8a, 0x3c, 0x16, 0x86, 0x37, 0xbe, 0xa6, 0x12, 0xcd, 0x3d, + 0x82, 0x94, 0x42, 0xbe, 0xd7, 0x01, 0xad, 0xbe, 0xd1, 0xec, 0xb4, 0x3d, + 0x43, 0xf6, 0x2d, 0x3c, 0xeb, 0x80, 0xcc, 0x3d, 0x65, 0x14, 0x85, 0xbe, + 0x83, 0xc6, 0xdb, 0xbe, 0xf4, 0xd0, 0x8b, 0xbe, 0x97, 0x01, 0xfd, 0xbc, + 0x99, 0xdc, 0x9e, 0xbe, 0xf9, 0xc5, 0x09, 0x3e, 0x12, 0x1a, 0xc0, 0x3d, + 0x95, 0x7d, 0x2c, 0x3e, 0x40, 0x16, 0x88, 0xbd, 0xd0, 0x85, 0x3b, 0xbe, + 0xea, 0xef, 0x51, 0x3d, 0x63, 0xb3, 0x5b, 0xbe, 0x50, 0x49, 0xc5, 0xbe, + 0xc3, 0xa6, 0x2c, 0x3e, 0x3e, 0x64, 0x47, 0x3b, 0xfd, 0xe2, 0x22, 0x3d, + 0x1e, 0xf2, 0x52, 0xbe, 0x9d, 0x8f, 0xd6, 0xbe, 0x39, 0xf8, 0x0e, 0xbe, + 0x60, 0x67, 0x72, 0xbd, 0x05, 0x60, 0x0a, 0xbe, 0x43, 0x19, 0x8f, 0x3c, + 0x84, 0xe1, 0x3d, 0x3e, 0x7d, 0x8b, 0x24, 0x3d, 0x17, 0xf5, 0x3e, 0xbd, + 0xab, 0xf2, 0xe3, 0xbd, 0x8c, 0xc2, 0x9c, 0x3d, 0xf6, 0x55, 0x21, 0xbd, + 0x64, 0x77, 0x8a, 0xbe, 0xca, 0x5e, 0x97, 0xbb, 0x57, 0x52, 0x08, 0x3e, + 0x82, 0xb2, 0xd1, 0x3d, 0x7c, 0x53, 0x0b, 0xbd, 0x9b, 0x78, 0x9e, 0x3c, + 0xe5, 0xe2, 0xa5, 0xbd, 0x1a, 0x31, 0xbe, 0xbd, 0x37, 0x59, 0x8a, 0xbd, + 0xb5, 0x48, 0x5e, 0x3e, 0x6f, 0xd3, 0xe2, 0x3d, 0x7f, 0x74, 0x26, 0xbd, + 0x8d, 0xfe, 0x8c, 0xbe, 0x35, 0xbc, 0x75, 0xbe, 0xdf, 0xa9, 0x07, 0x3d, + 0x2c, 0x90, 0xf4, 0x3d, 0x63, 0xb0, 0x9e, 0x3d, 0x38, 0x84, 0x78, 0x3e, + 0xa4, 0x5c, 0xd1, 0x3e, 0x0c, 0x9d, 0xa8, 0xbd, 0x08, 0x4f, 0x8e, 0x3e, + 0x12, 0xc1, 0x96, 0xbe, 0x9a, 0x4c, 0xee, 0xbb, 0xe2, 0x69, 0x9a, 0x3e, + 0x71, 0x14, 0xc7, 0xbe, 0xc8, 0xaa, 0xe9, 0xbc, 0x1a, 0x81, 0xae, 0x3e, + 0x0d, 0x0f, 0x0b, 0xbf, 0x9f, 0x40, 0x15, 0xbf, 0x99, 0xec, 0x9f, 0x3e, + 0xfe, 0x81, 0x42, 0x3e, 0x74, 0xb0, 0x42, 0xbe, 0x25, 0x93, 0x96, 0xbe, + 0xee, 0x36, 0x36, 0x3d, 0xa5, 0x9f, 0xa2, 0x3d, 0x07, 0x8e, 0x3e, 0x3e, + 0x89, 0x2a, 0x9f, 0x3d, 0xe5, 0x83, 0x58, 0xbd, 0x72, 0xf7, 0x01, 0xbe, + 0x9c, 0x8b, 0x67, 0xbc, 0x28, 0xc1, 0xf7, 0xbd, 0x3b, 0x5d, 0x86, 0xbd, + 0x8c, 0x2d, 0x34, 0xbd, 0x6f, 0x8f, 0xde, 0xbe, 0x8a, 0xf3, 0xf2, 0xbe, + 0x3b, 0x49, 0xb9, 0x3e, 0xfb, 0x63, 0x83, 0xbd, 0xda, 0x2f, 0x93, 0xbe, + 0xd6, 0x44, 0xa8, 0xbe, 0x3d, 0xec, 0xff, 0x3b, 0xb5, 0xd6, 0x04, 0xbd, + 0xe3, 0xc9, 0x19, 0x3d, 0x6c, 0xdb, 0x87, 0x3d, 0x70, 0xbc, 0x9e, 0xbc, + 0x76, 0x16, 0x24, 0x3e, 0x63, 0xb6, 0x6e, 0x3d, 0xb7, 0x58, 0x9a, 0xbe, + 0x79, 0xd0, 0x21, 0x3e, 0x16, 0xe7, 0x22, 0x3b, 0x4c, 0x44, 0x4a, 0xbd, + 0x78, 0x34, 0xcc, 0xbe, 0xfd, 0x92, 0x82, 0x3d, 0x3d, 0x22, 0x9b, 0xbd, + 0x01, 0xba, 0xcb, 0xbd, 0xf0, 0xac, 0xed, 0xbc, 0x92, 0x5f, 0x09, 0x3e, + 0x76, 0x6e, 0x97, 0xbd, 0x6e, 0x7d, 0x18, 0x3d, 0xed, 0x55, 0xf8, 0x3c, + 0x01, 0xaa, 0x4a, 0x3e, 0x49, 0xf7, 0x77, 0x3e, 0xa9, 0xd2, 0xd4, 0x3d, + 0x6b, 0x7a, 0xac, 0xbd, 0xb1, 0x67, 0x90, 0xbd, 0x30, 0xf5, 0x94, 0xbd, + 0xdd, 0xb9, 0xd7, 0x3d, 0xc3, 0x2e, 0x04, 0xbe, 0xfd, 0xc1, 0xa8, 0x3d, + 0x80, 0xf0, 0x41, 0xbe, 0xae, 0x23, 0x8e, 0x3c, 0x03, 0x8a, 0x05, 0xbc, + 0x50, 0x33, 0x26, 0x3e, 0xdd, 0x04, 0xca, 0x3d, 0x11, 0xd9, 0x82, 0xbe, + 0xc9, 0x2b, 0x42, 0x3e, 0xbb, 0x94, 0x72, 0x3e, 0xdb, 0x8f, 0xa5, 0x3c, + 0xde, 0x89, 0xd0, 0x3d, 0x24, 0xb6, 0x86, 0xbe, 0x1b, 0xa7, 0x54, 0xbe, + 0x48, 0x2e, 0xd1, 0xbd, 0x5d, 0x47, 0x00, 0x3e, 0x8c, 0xd7, 0xa9, 0x3d, + 0xc9, 0x22, 0x9b, 0xbd, 0x9c, 0xa0, 0xe8, 0x3d, 0xc0, 0x7b, 0x1c, 0xbd, + 0x27, 0xd2, 0xdd, 0x3d, 0x24, 0x8e, 0xa0, 0x3d, 0x62, 0xf4, 0x53, 0xbc, + 0x16, 0x38, 0xba, 0xbe, 0x7c, 0x6e, 0xe3, 0x3d, 0x7b, 0x0d, 0x44, 0x3e, + 0xae, 0x3c, 0x8d, 0x3d, 0x1b, 0x4e, 0x4f, 0x3d, 0x73, 0x75, 0x4d, 0xbe, + 0x98, 0x9b, 0x44, 0xbe, 0xd2, 0xe8, 0x74, 0xbe, 0x8f, 0xd9, 0x56, 0x3e, + 0x74, 0x2a, 0x4e, 0x3e, 0xda, 0xb1, 0xd8, 0x3d, 0x38, 0xd7, 0x85, 0x3d, + 0x09, 0xbe, 0x3c, 0x3d, 0x24, 0x28, 0x6d, 0x3c, 0xbb, 0xcd, 0x1f, 0x3d, + 0xfb, 0x77, 0x3f, 0xbe, 0x33, 0xde, 0xbe, 0xbc, 0x5c, 0xa1, 0xb3, 0x3c, + 0x6b, 0xd1, 0x9d, 0x3d, 0xf1, 0x7b, 0x3b, 0x3e, 0x4c, 0x2c, 0x75, 0x3d, + 0x70, 0xef, 0xb2, 0xbe, 0xb5, 0x17, 0xe0, 0xbd, 0x61, 0x87, 0x65, 0xbd, + 0x3a, 0x09, 0x3e, 0x3e, 0x48, 0x49, 0xbf, 0xbb, 0x2f, 0x30, 0xaa, 0x3d, + 0x65, 0x75, 0x07, 0x3d, 0xbd, 0xc2, 0x1f, 0x3e, 0xe9, 0x8c, 0xf8, 0xbd, + 0x7c, 0x97, 0xe8, 0x3c, 0x9d, 0xc7, 0x51, 0xbb, 0x40, 0x8d, 0x89, 0xbd, + 0xd5, 0x27, 0x87, 0x3c, 0x92, 0xa1, 0x65, 0xbd, 0xf7, 0x7a, 0xa6, 0xbc, + 0xec, 0x7c, 0xd6, 0x3c, 0xf7, 0x47, 0x14, 0xbe, 0xd5, 0x0d, 0x92, 0x3c, + 0x3a, 0x11, 0x01, 0xbb, 0x9a, 0x3b, 0x03, 0x3e, 0xe0, 0xde, 0x22, 0xbe, + 0x37, 0xad, 0xe4, 0xbc, 0x4e, 0xb3, 0x03, 0xbb, 0x3f, 0xe8, 0xf6, 0xbd, + 0x87, 0x10, 0xf7, 0xbb, 0xe2, 0xc3, 0x9a, 0xbd, 0x0e, 0x42, 0x0b, 0x3e, + 0x92, 0x26, 0x9d, 0xbe, 0x4f, 0xe3, 0x32, 0xbc, 0x26, 0x0a, 0x03, 0x3e, + 0xf3, 0x60, 0xa7, 0x3c, 0x0c, 0x24, 0x05, 0x3d, 0x3e, 0xc8, 0x94, 0xbe, + 0x50, 0x31, 0x02, 0xbe, 0xff, 0xd4, 0x69, 0xbd, 0x1d, 0x42, 0x72, 0x3d, + 0xe7, 0x8c, 0x7f, 0xbe, 0x33, 0x87, 0x16, 0x3d, 0x93, 0x2c, 0xa2, 0xbd, + 0x88, 0xf0, 0xe3, 0xbb, 0xa8, 0x96, 0x84, 0x3d, 0xda, 0xc0, 0x40, 0x3e, + 0x8a, 0x68, 0x58, 0x3d, 0xad, 0xb0, 0x19, 0xbe, 0x97, 0x4e, 0x26, 0xbc, + 0x1c, 0x26, 0xe6, 0x3d, 0x48, 0x68, 0x55, 0x3d, 0xc6, 0xe3, 0xc3, 0x3d, + 0xa7, 0xe6, 0xc8, 0xbb, 0xf3, 0x00, 0x99, 0xbd, 0x2d, 0x63, 0xda, 0x3c, + 0xb4, 0xbd, 0x81, 0x3e, 0xdf, 0xb9, 0x92, 0xbe, 0x48, 0x3a, 0xb2, 0xbd, + 0x9f, 0xcb, 0xd2, 0x3d, 0x0b, 0x38, 0x97, 0x3c, 0xe2, 0x95, 0xb7, 0xbc, + 0xf6, 0x82, 0x13, 0x39, 0xc0, 0x98, 0x1a, 0x3d, 0xc2, 0xf4, 0x51, 0x3d, + 0x0d, 0xeb, 0xf5, 0x3c, 0x1d, 0xda, 0x52, 0x3d, 0xad, 0xb0, 0x77, 0x3d, + 0xca, 0x58, 0x51, 0x3d, 0x5a, 0x2d, 0xb0, 0x3c, 0xf0, 0x8b, 0xeb, 0xbd, + 0x0d, 0xd8, 0x50, 0x3e, 0x65, 0x25, 0x18, 0xbe, 0xb0, 0x18, 0xfd, 0xbe, + 0xc4, 0x02, 0x3b, 0xbd, 0xfa, 0x7b, 0x82, 0x3d, 0xce, 0xa0, 0x4d, 0xbe, + 0x0b, 0xc6, 0x32, 0xbd, 0x84, 0x62, 0xc0, 0x3d, 0x60, 0x6e, 0x1e, 0x3d, + 0x31, 0x01, 0x28, 0x3e, 0x31, 0x76, 0x04, 0x3e, 0x91, 0x6b, 0x60, 0xbd, + 0x14, 0xf5, 0x20, 0x3e, 0x5c, 0x38, 0x67, 0xbd, 0x36, 0x21, 0xc2, 0x3d, + 0x13, 0x10, 0x7e, 0xbe, 0x19, 0xd1, 0x9f, 0x3e, 0xd0, 0x1a, 0x16, 0xbe, + 0x34, 0xb5, 0xaf, 0xbe, 0x86, 0x67, 0x16, 0x3d, 0x11, 0x05, 0x58, 0xbe, + 0xfb, 0x0d, 0xd0, 0x3c, 0x90, 0x88, 0x20, 0xbd, 0xdc, 0xcc, 0x9a, 0x3d, + 0x11, 0x29, 0x7a, 0x3e, 0x05, 0x44, 0xbf, 0xbe, 0x54, 0x1a, 0x0a, 0x3e, + 0xff, 0x6f, 0xb4, 0xbd, 0xeb, 0xa4, 0x86, 0x3d, 0x5e, 0x43, 0x00, 0x3e, + 0xfa, 0x4f, 0xd9, 0xbc, 0xad, 0x7b, 0xa2, 0xbd, 0x0e, 0xf6, 0x01, 0x3e, + 0x90, 0xf0, 0xb4, 0xbd, 0xd0, 0x21, 0x9a, 0xbe, 0x74, 0x43, 0x14, 0x3e, + 0x4d, 0xe8, 0x0b, 0xbe, 0x80, 0x2b, 0x93, 0xbd, 0x87, 0x39, 0x35, 0x3e, + 0x90, 0x63, 0xb8, 0x3d, 0xcf, 0x2c, 0x83, 0x3e, 0xbd, 0xe5, 0x0b, 0xbf, + 0x5f, 0xf7, 0x90, 0x3c, 0xa0, 0x61, 0x52, 0xbc, 0x8f, 0x88, 0xac, 0xbc, + 0x0c, 0x64, 0xd3, 0x3d, 0x9c, 0xd9, 0x8b, 0x3d, 0x80, 0xb7, 0x39, 0xbe, + 0x4e, 0x30, 0x95, 0x3e, 0x73, 0x3e, 0xda, 0xbe, 0x51, 0x56, 0x84, 0x3d, + 0xe6, 0x28, 0x85, 0x3d, 0xb9, 0xe9, 0x87, 0xbe, 0x46, 0x01, 0x6c, 0xbd, + 0x4b, 0x64, 0xd5, 0x3d, 0x56, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x63, + 0x6f, 0x6e, 0x76, 0x32, 0x64, 0x2f, 0x43, 0x6f, 0x6e, 0x76, 0x32, 0x44, + 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x68, 0xfc, 0xff, 0xff, + 0x20, 0x00, 0x00, 0x00, 0xe4, 0xe5, 0x3e, 0xc0, 0x22, 0xe7, 0x92, 0x3f, + 0x57, 0x04, 0xde, 0xbf, 0xda, 0x8f, 0x1c, 0x3e, 0x47, 0xbf, 0x05, 0xc0, + 0x53, 0xab, 0xcb, 0xbf, 0x68, 0x6a, 0x6a, 0xbf, 0xd2, 0x0b, 0xe4, 0xbf, + 0xbe, 0xfd, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x63, 0x6f, 0x6e, 0x76, 0x32, + 0x64, 0x5f, 0x31, 0x2f, 0x43, 0x6f, 0x6e, 0x76, 0x32, 0x44, 0x5f, 0x62, + 0x69, 0x61, 0x73, 0x00, 0xd0, 0xfc, 0xff, 0xff, 0x40, 0x00, 0x00, 0x00, + 0xa3, 0x15, 0xb7, 0x3e, 0x29, 0x67, 0x95, 0x3f, 0x4b, 0x96, 0x62, 0xbe, + 0x61, 0x5f, 0xfc, 0x3e, 0xa2, 0xd4, 0x3e, 0xbf, 0x45, 0x1c, 0x0d, 0xbf, + 0x29, 0xdd, 0x70, 0xbe, 0x9a, 0x75, 0x97, 0xbf, 0xfc, 0x0a, 0x6f, 0xbe, + 0xcc, 0x56, 0x25, 0x3f, 0xdf, 0xac, 0x98, 0xbf, 0x0e, 0x1c, 0x8b, 0xbf, + 0xa5, 0xd8, 0x8c, 0x3f, 0xa5, 0x42, 0xd5, 0x3c, 0xa9, 0x8e, 0x7a, 0xbf, + 0x9e, 0xdb, 0x71, 0xbe, 0x46, 0xfe, 0xff, 0xff, 0x10, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x64, + 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x31, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, + 0x6c, 0x5f, 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x58, 0xfd, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0xc3, 0x99, 0xb6, 0xbf, 0xe9, 0x87, 0x8b, 0x3f, + 0xac, 0x83, 0x9a, 0xbf, 0x4c, 0x49, 0x3d, 0xbe, 0x9e, 0xfe, 0xff, 0xff, + 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x64, 0x65, 0x6e, 0x73, 0x65, + 0x5f, 0x31, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x2f, 0x52, 0x65, + 0x61, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x4f, 0x70, + 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x73, 0x65, 0x00, 0x00, + 0xc8, 0xfd, 0xff, 0xff, 0x00, 0x01, 0x00, 0x00, 0x40, 0x5c, 0x8a, 0xbe, + 0xc5, 0x88, 0xaa, 0x3e, 0xb0, 0x76, 0x1f, 0xbe, 0x35, 0x3a, 0x58, 0x3e, + 0x84, 0xa5, 0xa3, 0x3e, 0x38, 0xd9, 0x49, 0x3d, 0x58, 0x63, 0x78, 0xbc, + 0x82, 0x94, 0x2e, 0xbe, 0x97, 0xde, 0x6e, 0xbe, 0xea, 0x27, 0x9f, 0xbe, + 0x1d, 0x24, 0xc0, 0x3d, 0x21, 0x31, 0x66, 0x3c, 0x80, 0xf8, 0x88, 0xbe, + 0xdd, 0x06, 0x19, 0xbe, 0x3f, 0x4b, 0xb3, 0xbe, 0x70, 0xdc, 0x8d, 0x3e, + 0x20, 0xee, 0x93, 0xbe, 0xde, 0x7c, 0xbf, 0xbe, 0xda, 0x3a, 0x50, 0xbe, + 0x0e, 0x91, 0x6e, 0x3e, 0x18, 0xbc, 0x81, 0x3e, 0x18, 0x9c, 0xfe, 0xb9, + 0x11, 0x2d, 0x9b, 0xbe, 0xa2, 0x73, 0x3f, 0xbe, 0x0c, 0x6c, 0xa3, 0xbe, + 0x37, 0x4b, 0x8c, 0xbe, 0x91, 0x26, 0xa0, 0x3d, 0xb3, 0x04, 0xbd, 0x3e, + 0x01, 0x3e, 0x70, 0xbe, 0xd1, 0xdb, 0x69, 0xbe, 0xb4, 0xc0, 0x98, 0xbe, + 0xd4, 0xd9, 0x80, 0x3e, 0x62, 0xa9, 0x74, 0xbe, 0x8a, 0xe9, 0x83, 0xbe, + 0x7a, 0x92, 0x54, 0xbe, 0x92, 0x5d, 0x43, 0x3e, 0xe3, 0x35, 0x7b, 0x3e, + 0xee, 0x81, 0x2e, 0x3d, 0xbb, 0x68, 0xec, 0x3d, 0x70, 0x72, 0x1b, 0xbe, + 0x64, 0x20, 0xa4, 0xbe, 0x4f, 0x1f, 0x8d, 0xbd, 0xee, 0xd6, 0xf8, 0x3d, + 0xdb, 0x83, 0xb0, 0x3e, 0xd1, 0x99, 0x8c, 0xbe, 0x99, 0x21, 0x45, 0xbe, + 0x97, 0x04, 0x82, 0xbe, 0x25, 0xdf, 0x88, 0x3e, 0xe2, 0xe6, 0x5b, 0xbe, + 0xe5, 0x53, 0x68, 0x3d, 0x0b, 0xcd, 0x40, 0xbe, 0x4e, 0xea, 0x55, 0x3e, + 0x54, 0xd8, 0x85, 0x3e, 0x3c, 0xba, 0x82, 0x3d, 0x58, 0xc0, 0xe9, 0x3e, + 0x0c, 0xcc, 0x29, 0xbe, 0x3a, 0x6e, 0xa2, 0xbe, 0x84, 0x4a, 0x12, 0x3e, + 0x32, 0xd9, 0xcb, 0x3d, 0xad, 0x16, 0xd4, 0x3e, 0xf5, 0xa8, 0x85, 0xbe, + 0x5c, 0x34, 0xc8, 0xbd, 0x31, 0x02, 0xac, 0xbe, 0x3e, 0x15, 0x9e, 0x3e, + 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x0c, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x64, + 0x65, 0x6e, 0x73, 0x65, 0x2f, 0x4d, 0x61, 0x74, 0x4d, 0x75, 0x6c, 0x5f, + 0x62, 0x69, 0x61, 0x73, 0x00, 0x00, 0x00, 0x00, 0x20, 0xff, 0xff, 0xff, + 0x40, 0x00, 0x00, 0x00, 0xa9, 0xa9, 0x93, 0x3f, 0x3c, 0xda, 0x21, 0xbe, + 0x33, 0xe3, 0x3d, 0x3d, 0xd8, 0xed, 0x33, 0xbf, 0x4c, 0x9d, 0x3e, 0xbf, + 0xba, 0x40, 0xa6, 0x3f, 0x6e, 0x4e, 0x25, 0xbe, 0x6e, 0x5e, 0x93, 0x3f, + 0x48, 0xa2, 0xc5, 0xbe, 0x67, 0xaa, 0x33, 0x3e, 0xc0, 0x66, 0x20, 0x3f, + 0xb3, 0x25, 0x7e, 0xbe, 0x8a, 0x2f, 0xd9, 0xbe, 0xda, 0x64, 0xc8, 0x3f, + 0x1a, 0x14, 0x89, 0xbd, 0xea, 0x48, 0x89, 0x3e, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0x3c, 0x01, 0x00, 0x00, + 0xd0, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x30, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, + 0x04, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x70, 0xfe, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, + 0xb0, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x08, 0x02, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e, 0xfe, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0xf0, 0xfe, 0xff, 0xff, 0x00, 0x00, 0x00, 0x05, + 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x2c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x14, 0x00, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x0c, 0x00, 0x07, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x0c, 0x00, 0x07, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xa8, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, + 0x18, 0x00, 0x07, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x14, 0x00, + 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x1c, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x07, 0x00, + 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x1c, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x14, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x0c, 0x00, 0x10, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, + 0x48, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0xce, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x19, + 0xd6, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x09, 0xde, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x09, 0xe6, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x11, + 0xfa, 0xff, 0xff, 0xff, 0x00, 0x03, 0x06, 0x00, 0x06, 0x00, 0x05, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x11, 0x06, 0x00, 0x08, 0x00, 0x07, 0x00, + 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04}; +unsigned int model_tflite_len = 19616; diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h b/tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h rename to tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h index 6199c191b86..40a0b4d27f1 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h +++ b/tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h @@ -18,10 +18,10 @@ limitations under the License. // don't have a file system. It was created using the command: // xxd -i magic_wand_model.tflite > magic_wand_model_data.cc -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_MAGIC_WAND_MODEL_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_MAGIC_WAND_MODEL_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_MAGIC_WAND_MODEL_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_MAGIC_WAND_MODEL_DATA_H_ extern const unsigned char g_magic_wand_model_data[]; extern const int g_magic_wand_model_data_len; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_MAGIC_WAND_MODEL_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_MAGIC_WAND_MODEL_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_test.cc b/tensorflow/lite/micro/examples/magic_wand/magic_wand_test.cc similarity index 91% rename from tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_test.cc rename to tensorflow/lite/micro/examples/magic_wand/magic_wand_test.cc index 1bf26b4d34c..1494dbc09ab 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_test.cc +++ b/tensorflow/lite/micro/examples/magic_wand/magic_wand_test.cc @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h" +#include "tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.h" +#include "tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.h" +#include "tensorflow/lite/micro/kernels/micro_ops.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/main.cc b/tensorflow/lite/micro/examples/magic_wand/main.cc similarity index 92% rename from tensorflow/lite/experimental/micro/examples/hello_world/main.cc rename to tensorflow/lite/micro/examples/magic_wand/main.cc index a7f17441228..7dab1cd41d3 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/main.cc +++ b/tensorflow/lite/micro/examples/magic_wand/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h" +#include "tensorflow/lite/micro/examples/magic_wand/main_functions.h" // This is the default main used on systems that have the standard C entry // point. Other devices (for example FreeRTOS or ESP32) that have different diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.cc b/tensorflow/lite/micro/examples/magic_wand/main_functions.cc similarity index 87% rename from tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.cc rename to tensorflow/lite/micro/examples/magic_wand/main_functions.cc index 78570129871..ba277c10318 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.cc +++ b/tensorflow/lite/micro/examples/magic_wand/main_functions.cc @@ -13,16 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/main_functions.h" +#include "tensorflow/lite/micro/examples/magic_wand/main_functions.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/gesture_predictor.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/magic_wand_model_data.h" -#include "tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/gesture_predictor.h" +#include "tensorflow/lite/micro/examples/magic_wand/magic_wand_model_data.h" +#include "tensorflow/lite/micro/examples/magic_wand/output_handler.h" +#include "tensorflow/lite/micro/kernels/micro_ops.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h b/tensorflow/lite/micro/examples/magic_wand/main_functions.h similarity index 80% rename from tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h rename to tensorflow/lite/micro/examples/magic_wand/main_functions.h index df283b44fb3..18671538c30 100644 --- a/tensorflow/lite/experimental/micro/examples/hello_world/main_functions.h +++ b/tensorflow/lite/micro/examples/magic_wand/main_functions.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_MAIN_FUNCTIONS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_MAIN_FUNCTIONS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_MAIN_FUNCTIONS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_MAIN_FUNCTIONS_H_ // Initializes all data needed for the example. The name is important, and needs // to be setup() for Arduino compatibility. @@ -25,4 +25,4 @@ void setup(); // compatibility. void loop(); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_HELLO_WORLD_MAIN_FUNCTIONS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_MAIN_FUNCTIONS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.cc b/tensorflow/lite/micro/examples/magic_wand/output_handler.cc similarity index 94% rename from tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.cc rename to tensorflow/lite/micro/examples/magic_wand/output_handler.cc index 1e3f7a38467..d551b5b8066 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.cc +++ b/tensorflow/lite/micro/examples/magic_wand/output_handler.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/output_handler.h" void HandleOutput(tflite::ErrorReporter* error_reporter, int kind) { // light (red: wing, blue: ring, green: slope) diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h b/tensorflow/lite/micro/examples/magic_wand/output_handler.h similarity index 71% rename from tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h rename to tensorflow/lite/micro/examples/magic_wand/output_handler.h index ed323694fd8..7b85254d688 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h +++ b/tensorflow/lite/micro/examples/magic_wand/output_handler.h @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_OUTPUT_HANDLER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_OUTPUT_HANDLER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_OUTPUT_HANDLER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_OUTPUT_HANDLER_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" void HandleOutput(tflite::ErrorReporter* error_reporter, int kind); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_OUTPUT_HANDLER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_OUTPUT_HANDLER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/output_handler_test.cc b/tensorflow/lite/micro/examples/magic_wand/output_handler_test.cc similarity index 82% rename from tensorflow/lite/experimental/micro/examples/magic_wand/output_handler_test.cc rename to tensorflow/lite/micro/examples/magic_wand/output_handler_test.cc index a45767b191e..6ac5468531d 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/output_handler_test.cc +++ b/tensorflow/lite/micro/examples/magic_wand/output_handler_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/output_handler.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.cc b/tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.cc rename to tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.cc index aa579b43457..49f7d5422f3 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.cc +++ b/tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.h" +#include "tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.h" const int g_ring_micro_f9643d42_nohash_4_length = 128; const int g_ring_micro_f9643d42_nohash_4_dim = 3; diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.h b/tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.h similarity index 75% rename from tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.h rename to tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.h index d1d0b602165..9cd02cd53b0 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/ring_micro_features_data.h +++ b/tensorflow/lite/micro/examples/magic_wand/ring_micro_features_data.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_RING_MICRO_FEATURES_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_RING_MICRO_FEATURES_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_RING_MICRO_FEATURES_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_RING_MICRO_FEATURES_DATA_H_ extern const int g_ring_micro_f9643d42_nohash_4_length; extern const int g_ring_micro_f9643d42_nohash_4_dim; extern const float g_ring_micro_f9643d42_nohash_4_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_RING_MICRO_FEATURES_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_RING_MICRO_FEATURES_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.cc b/tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.cc rename to tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.cc index 68b3e40052b..3790b938e9c 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.cc +++ b/tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.h" +#include "tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.h" const int g_slope_micro_f2e59fea_nohash_1_length = 128; const int g_slope_micro_f2e59fea_nohash_1_dim = 3; diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.h b/tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.h similarity index 75% rename from tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.h rename to tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.h index ade97683d79..6ed0c3c3cdb 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/slope_micro_features_data.h +++ b/tensorflow/lite/micro/examples/magic_wand/slope_micro_features_data.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_SLOPE_MICRO_FEATURES_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_SLOPE_MICRO_FEATURES_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_SLOPE_MICRO_FEATURES_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_SLOPE_MICRO_FEATURES_DATA_H_ extern const int g_slope_micro_f2e59fea_nohash_1_length; extern const int g_slope_micro_f2e59fea_nohash_1_dim; extern const float g_slope_micro_f2e59fea_nohash_1_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MAGIC_WAND_SLOPE_MICRO_FEATURES_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MAGIC_WAND_SLOPE_MICRO_FEATURES_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/sparkfun_edge/accelerometer_handler.cc b/tensorflow/lite/micro/examples/magic_wand/sparkfun_edge/accelerometer_handler.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/magic_wand/sparkfun_edge/accelerometer_handler.cc rename to tensorflow/lite/micro/examples/magic_wand/sparkfun_edge/accelerometer_handler.cc index 4a6047c2f8d..c033bc7c437 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/sparkfun_edge/accelerometer_handler.cc +++ b/tensorflow/lite/micro/examples/magic_wand/sparkfun_edge/accelerometer_handler.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/accelerometer_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/accelerometer_handler.h" // These are headers from Ambiq's Apollo3 SDK. #include "am_bsp.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/sparkfun_edge/output_handler.cc b/tensorflow/lite/micro/examples/magic_wand/sparkfun_edge/output_handler.cc similarity index 76% rename from tensorflow/lite/experimental/micro/examples/magic_wand/sparkfun_edge/output_handler.cc rename to tensorflow/lite/micro/examples/magic_wand/sparkfun_edge/output_handler.cc index 082fb1c8952..ca388079e54 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/sparkfun_edge/output_handler.cc +++ b/tensorflow/lite/micro/examples/magic_wand/sparkfun_edge/output_handler.cc @@ -13,13 +13,13 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/magic_wand/output_handler.h" +#include "tensorflow/lite/micro/examples/magic_wand/output_handler.h" -#include "tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3_BSP/bsp/am_bsp.h" -#include "tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/tf_accelerometer.h" -#include "tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/tf_adc.h" -#include "tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/mcu/apollo3/am_mcu_apollo.h" -#include "tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/utils/am_util.h" +#include "tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3_BSP/bsp/am_bsp.h" +#include "tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_accelerometer/tf_accelerometer.h" +#include "tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3_BSP/examples/example1_edge_test/src/tf_adc/tf_adc.h" +#include "tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/mcu/apollo3/am_mcu_apollo.h" +#include "tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/utils/am_util.h" void HandleOutput(tflite::ErrorReporter* error_reporter, int kind) { // The first time this method runs, set up our LEDs correctly diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/README.md b/tensorflow/lite/micro/examples/magic_wand/train/README.md similarity index 93% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/README.md rename to tensorflow/lite/micro/examples/magic_wand/train/README.md index 6bd45375341..825b8e27887 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/train/README.md +++ b/tensorflow/lite/micro/examples/magic_wand/train/README.md @@ -37,10 +37,10 @@ started:
- Run in Google Colab + Run in Google Colab - View source on GitHub + View source on GitHub
diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_augmentation.py b/tensorflow/lite/micro/examples/magic_wand/train/data_augmentation.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_augmentation.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_augmentation.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_augmentation_test.py b/tensorflow/lite/micro/examples/magic_wand/train/data_augmentation_test.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_augmentation_test.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_augmentation_test.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_load.py b/tensorflow/lite/micro/examples/magic_wand/train/data_load.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_load.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_load.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_load_test.py b/tensorflow/lite/micro/examples/magic_wand/train/data_load_test.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_load_test.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_load_test.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_prepare.py b/tensorflow/lite/micro/examples/magic_wand/train/data_prepare.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_prepare.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_prepare.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_prepare_test.py b/tensorflow/lite/micro/examples/magic_wand/train/data_prepare_test.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_prepare_test.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_prepare_test.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split.py b/tensorflow/lite/micro/examples/magic_wand/train/data_split.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_split.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split_person.py b/tensorflow/lite/micro/examples/magic_wand/train/data_split_person.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split_person.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_split_person.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split_person_test.py b/tensorflow/lite/micro/examples/magic_wand/train/data_split_person_test.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split_person_test.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_split_person_test.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split_test.py b/tensorflow/lite/micro/examples/magic_wand/train/data_split_test.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/data_split_test.py rename to tensorflow/lite/micro/examples/magic_wand/train/data_split_test.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/netmodels/CNN/weights.h5 b/tensorflow/lite/micro/examples/magic_wand/train/netmodels/CNN/weights.h5 similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/netmodels/CNN/weights.h5 rename to tensorflow/lite/micro/examples/magic_wand/train/netmodels/CNN/weights.h5 diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/requirements.txt b/tensorflow/lite/micro/examples/magic_wand/train/requirements.txt similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/requirements.txt rename to tensorflow/lite/micro/examples/magic_wand/train/requirements.txt diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/train.py b/tensorflow/lite/micro/examples/magic_wand/train/train.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/train.py rename to tensorflow/lite/micro/examples/magic_wand/train/train.py diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/train_magic_wand_model.ipynb b/tensorflow/lite/micro/examples/magic_wand/train/train_magic_wand_model.ipynb similarity index 88% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/train_magic_wand_model.ipynb rename to tensorflow/lite/micro/examples/magic_wand/train/train_magic_wand_model.ipynb index 1995ef02dc3..65f439f9090 100644 --- a/tensorflow/lite/experimental/micro/examples/magic_wand/train/train_magic_wand_model.ipynb +++ b/tensorflow/lite/micro/examples/magic_wand/train/train_magic_wand_model.ipynb @@ -32,16 +32,16 @@ "colab_type": "text" }, "source": [ - "This notebook demonstrates how to train a 20kb gesture recognition model for [TensorFlow Lite for Microcontrollers](https://tensorflow.org/lite/microcontrollers/overview). It will produce the same model used in the [magic_wand](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/magic_wand) example application.\n", + "This notebook demonstrates how to train a 20kb gesture recognition model for [TensorFlow Lite for Microcontrollers](https://tensorflow.org/lite/microcontrollers/overview). It will produce the same model used in the [magic_wand](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/magic_wand) example application.\n", "\n", "The model is designed to be used with [Google Colaboratory](https://colab.research.google.com).\n", "\n", "\n", " \n", " \n", "
\n", - " Run in Google Colab\n", + " Run in Google Colab\n", " \n", - " View source on GitHub\n", + " View source on GitHub\n", "
\n" ] @@ -102,7 +102,7 @@ "# Clone the repository from GitHub\n", "!git clone --depth 1 -q https://github.com/tensorflow/tensorflow\n", "# Copy the training scripts into our workspace\n", - "!cp -r tensorflow/tensorflow/lite/experimental/micro/examples/magic_wand/train train" + "!cp -r tensorflow/tensorflow/lite/micro/examples/magic_wand/train train" ], "execution_count": 0, "outputs": [] @@ -224,7 +224,7 @@ "source": [ "## Create a C source file\n", "\n", - "The `train.py` script writes a quantized model, `model_quantized.tflite`, to the training scripts' directory.\n", + "The `train.py` script writes a model, `model.tflite`, to the training scripts' directory.\n", "\n", "In the following cell, we convert this model into a C++ source file we can use with TensorFlow Lite for Microcontrollers." ] @@ -240,9 +240,9 @@ "# Install xxd if it is not available\n", "!apt-get -qq install xxd\n", "# Save the file as a C source file\n", - "!xxd -i model_quantized.tflite > /content/model_quantized.cc\n", + "!xxd -i model.tflite > /content/model.cc\n", "# Print the source file\n", - "!cat /content/model_quantized.cc" + "!cat /content/model.cc" ], "execution_count": 0, "outputs": [] diff --git a/tensorflow/lite/experimental/micro/examples/magic_wand/train/train_test.py b/tensorflow/lite/micro/examples/magic_wand/train/train_test.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/magic_wand/train/train_test.py rename to tensorflow/lite/micro/examples/magic_wand/train/train_test.py diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/.gitignore b/tensorflow/lite/micro/examples/micro_speech/.gitignore similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/.gitignore rename to tensorflow/lite/micro/examples/micro_speech/.gitignore diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/BUILD b/tensorflow/lite/micro/examples/micro_speech/BUILD similarity index 62% rename from tensorflow/lite/experimental/micro/examples/micro_speech/BUILD rename to tensorflow/lite/micro/examples/micro_speech/BUILD index 1da1929f3ed..cc80ce60712 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/BUILD +++ b/tensorflow/lite/micro/examples/micro_speech/BUILD @@ -2,7 +2,7 @@ # TensorFlow Lite microcontroller example. load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) @@ -50,12 +50,12 @@ tflite_micro_cc_test( ], deps = [ "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_features_test_data", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:tiny_conv_micro_features_model_data", - "//tensorflow/lite/experimental/micro/kernels:all_ops_resolver", - "//tensorflow/lite/experimental/micro/kernels:micro_ops", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_features_test_data", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:tiny_conv_micro_features_model_data", + "//tensorflow/lite/micro/kernels:all_ops_resolver", + "//tensorflow/lite/micro/kernels:micro_ops", + "//tensorflow/lite/micro/testing:micro_test", "//tensorflow/lite/schema:schema_fbs", ], ) @@ -107,7 +107,7 @@ cc_library( deps = [ ":simple_model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -122,8 +122,8 @@ tflite_micro_cc_test( ":simple_features_generator_test_data", ":simple_model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -138,7 +138,7 @@ cc_library( deps = [ ":simple_model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -153,8 +153,8 @@ tflite_micro_cc_test( ":simple_features_generator_test_data", ":simple_model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -168,8 +168,8 @@ cc_library( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", ], ) @@ -184,8 +184,8 @@ cc_library( deps = [ ":audio_large_sample_test_data", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", ], ) @@ -197,9 +197,9 @@ tflite_micro_cc_test( deps = [ ":audio_provider", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -212,9 +212,9 @@ tflite_micro_cc_test( ":audio_large_sample_test_data", ":audio_provider_mock", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -229,9 +229,9 @@ cc_library( deps = [ ":audio_provider", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_features_generator", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_features_generator", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", ], ) @@ -244,9 +244,9 @@ tflite_micro_cc_test( ":audio_provider", ":feature_provider", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -261,9 +261,9 @@ cc_library( deps = [ ":audio_provider_mock", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_features_generator", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_features_generator", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", ], ) @@ -275,10 +275,10 @@ tflite_micro_cc_test( deps = [ ":feature_provider_mock", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_features_test_data", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_features_test_data", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -292,8 +292,8 @@ cc_library( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", ], ) @@ -308,8 +308,8 @@ tflite_micro_cc_test( deps = [ ":recognize_commands", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -323,7 +323,7 @@ cc_library( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -335,8 +335,8 @@ tflite_micro_cc_test( deps = [ ":command_responder", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -353,10 +353,10 @@ cc_binary( ":feature_provider", ":recognize_commands", "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:tiny_conv_micro_features_model_data", - "//tensorflow/lite/experimental/micro/kernels:micro_ops", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:tiny_conv_micro_features_model_data", + "//tensorflow/lite/micro/kernels:micro_ops", "//tensorflow/lite/schema:schema_fbs", ], ) @@ -374,10 +374,10 @@ cc_binary( ":feature_provider", ":recognize_commands", "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:micro_model_settings", - "//tensorflow/lite/experimental/micro/examples/micro_speech/micro_features:tiny_conv_micro_features_model_data", - "//tensorflow/lite/experimental/micro/kernels:micro_ops", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:micro_model_settings", + "//tensorflow/lite/micro/examples/micro_speech/micro_features:tiny_conv_micro_features_model_data", + "//tensorflow/lite/micro/kernels:micro_ops", "//tensorflow/lite/schema:schema_fbs", ], ) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc b/tensorflow/lite/micro/examples/micro_speech/CMSIS/Makefile.inc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc rename to tensorflow/lite/micro/examples/micro_speech/CMSIS/Makefile.inc index 22134152afb..245221aec96 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/Makefile.inc +++ b/tensorflow/lite/micro/examples/micro_speech/CMSIS/Makefile.inc @@ -11,12 +11,12 @@ ifneq ($(filter CMSIS,$(ALL_TAGS)),) -Ithird_party/CMSIS_ext/ CMSIS_PREPROCESSOR_SRCS := \ - tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.cc \ - tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.cc \ + tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.cc \ + tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.cc \ CMSIS_PREPROCESSOR_HDRS := \ - tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.h \ - tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.h \ + tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.h \ + tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.h \ third_party/CMSIS_ext/README.md \ third_party/CMSIS_ext/arm_cmplx_mag_squared_q10p6.h diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/README.md b/tensorflow/lite/micro/examples/micro_speech/CMSIS/README.md similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/README.md rename to tensorflow/lite/micro/examples/micro_speech/CMSIS/README.md diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/create_constants.py b/tensorflow/lite/micro/examples/micro_speech/CMSIS/create_constants.py similarity index 94% rename from tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/create_constants.py rename to tensorflow/lite/micro/examples/micro_speech/CMSIS/create_constants.py index 9ac29b757c5..6d0b4e2b2fe 100755 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/create_constants.py +++ b/tensorflow/lite/micro/examples/micro_speech/CMSIS/create_constants.py @@ -30,7 +30,7 @@ def to_cc(x, varname, directory='', scale_factor=1): x = x.astype(int) x = [str(v) if i % 10 != 0 else '\n ' + str(v) for i, v in enumerate(x)] - cmsis_path = 'tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS' + cmsis_path = 'tensorflow/lite/micro/examples/micro_speech/CMSIS' xstr = '#include "{}/{}.h"\n\n'.format(cmsis_path, varname) xstr += 'const int g_{}_size = {};\n'.format(varname, len(x)) xstr += 'const int16_t g_{}[{}] = {{{}}};\n'.format(varname, len(x), @@ -42,7 +42,7 @@ def to_cc(x, varname, directory='', scale_factor=1): def to_h(_, varname, directory=''): """Writes a header file for the table values.""" - tf_prepend = 'TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_' + tf_prepend = 'TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_' xstr = '#ifndef {}{}_H_\n'.format(tf_prepend, varname.upper()) xstr += '#define {}{}_H_\n\n'.format(tf_prepend, varname.upper()) xstr += '#include \n\n' diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.cc b/tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.cc rename to tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.cc index e6a11ce52c6..58c52e88f8c 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.cc +++ b/tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.h" +#include "tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.h" const int g_hanning_size = 480; const int16_t g_hanning[480] = { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.h b/tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.h similarity index 78% rename from tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.h rename to tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.h index e7d9c5c8586..eedd4c4550f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.h +++ b/tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.h @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_HANNING_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_HANNING_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_CMSIS_HANNING_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_CMSIS_HANNING_H_ #include extern const int g_hanning_size; extern const int16_t g_hanning[]; -#endif +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_CMSIS_HANNING_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.cc b/tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.cc rename to tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.cc index 45e9f798ef0..07bd80fd532 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.cc +++ b/tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.h" +#include "tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.h" const int g_sin_1k_size = 480; const int16_t g_sin_1k[480] = { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.h b/tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.h similarity index 78% rename from tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.h rename to tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.h index 653a6f58301..41551cd9d6c 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.h +++ b/tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.h @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIN_1K_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIN_1K_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_CMSIS_SIN_1K_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_CMSIS_SIN_1K_H_ #include extern const int g_sin_1k_size; extern const int16_t g_sin_1k[]; -#endif +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_CMSIS_SIN_1K_H_ diff --git a/tensorflow/lite/micro/examples/micro_speech/Makefile.inc b/tensorflow/lite/micro/examples/micro_speech/Makefile.inc new file mode 100644 index 00000000000..636cd04c449 --- /dev/null +++ b/tensorflow/lite/micro/examples/micro_speech/Makefile.inc @@ -0,0 +1,282 @@ + +INCLUDES += \ + -I$(MAKEFILE_DIR)/downloads/kissfft + +GENERATED_PROJECT_INCLUDES += \ +-I./third_party/kissfft + +PROJECT_INCLUDES += \ +third_party/kissfft + +KISSFFT_LIB_SRCS := \ +$(MAKEFILE_DIR)/downloads/kissfft/kiss_fft.c \ +$(MAKEFILE_DIR)/downloads/kissfft/tools/kiss_fftr.c + +KISSFFT_LIB_HDRS := \ +$(MAKEFILE_DIR)/downloads/kissfft/COPYING \ +$(MAKEFILE_DIR)/downloads/kissfft/kiss_fft.h \ +$(MAKEFILE_DIR)/downloads/kissfft/_kiss_fft_guts.h \ +$(MAKEFILE_DIR)/downloads/kissfft/tools/kiss_fftr.h + +$(eval $(call add_third_party_download,$(KISSFFT_URL),$(KISSFFT_MD5),kissfft,patch_kissfft)) + +THIRD_PARTY_CC_HDRS += \ +third_party/kissfft/COPYING \ +third_party/kissfft/kiss_fft.h \ +third_party/kissfft/_kiss_fft_guts.h \ +third_party/kissfft/tools/kiss_fftr.h + +MICRO_SPEECH_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/micro_speech_test.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc + +MICRO_SPEECH_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ + +SIMPLE_FEATURES_GENERATOR_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.cc \ +tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc \ +tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc \ +tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc + +SIMPLE_FEATURES_GENERATOR_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h \ +tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h \ +tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h \ +tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h + +MICRO_FEATURES_LIB_SRCS := \ +tensorflow/lite/experimental/microfrontend/lib/fft.cc \ +tensorflow/lite/experimental/microfrontend/lib/fft_util.cc \ +tensorflow/lite/experimental/microfrontend/lib/filterbank.c \ +tensorflow/lite/experimental/microfrontend/lib/filterbank_util.c \ +tensorflow/lite/experimental/microfrontend/lib/frontend.c \ +tensorflow/lite/experimental/microfrontend/lib/frontend_util.c \ +tensorflow/lite/experimental/microfrontend/lib/log_lut.c \ +tensorflow/lite/experimental/microfrontend/lib/log_scale.c \ +tensorflow/lite/experimental/microfrontend/lib/log_scale_util.c \ +tensorflow/lite/experimental/microfrontend/lib/noise_reduction.c \ +tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.c \ +tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.c \ +tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.c \ +tensorflow/lite/experimental/microfrontend/lib/window.c \ +tensorflow/lite/experimental/microfrontend/lib/window_util.c \ +$(KISSFFT_LIB_SRCS) + +MICRO_FEATURES_LIB_HDRS := \ +tensorflow/lite/experimental/microfrontend/lib/bits.h \ +tensorflow/lite/experimental/microfrontend/lib/fft.h \ +tensorflow/lite/experimental/microfrontend/lib/fft_util.h \ +tensorflow/lite/experimental/microfrontend/lib/filterbank.h \ +tensorflow/lite/experimental/microfrontend/lib/filterbank_util.h \ +tensorflow/lite/experimental/microfrontend/lib/frontend.h \ +tensorflow/lite/experimental/microfrontend/lib/frontend_util.h \ +tensorflow/lite/experimental/microfrontend/lib/log_lut.h \ +tensorflow/lite/experimental/microfrontend/lib/log_scale.h \ +tensorflow/lite/experimental/microfrontend/lib/log_scale_util.h \ +tensorflow/lite/experimental/microfrontend/lib/noise_reduction.h \ +tensorflow/lite/experimental/microfrontend/lib/noise_reduction_util.h \ +tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control.h \ +tensorflow/lite/experimental/microfrontend/lib/pcan_gain_control_util.h \ +tensorflow/lite/experimental/microfrontend/lib/window.h \ +tensorflow/lite/experimental/microfrontend/lib/window_util.h \ +$(KISSFFT_LIB_HDRS) + +MICRO_FEATURES_GENERATOR_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ +$(MICRO_FEATURES_LIB_SRCS) + +MICRO_FEATURES_GENERATOR_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h \ +$(MICRO_FEATURES_LIB_HDRS) + +MICRO_FEATURES_GENERATOR_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc \ +tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.cc \ +$(MICRO_FEATURES_GENERATOR_SRCS) + +MICRO_FEATURES_GENERATOR_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h \ +tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h \ +$(MICRO_FEATURES_GENERATOR_HDRS) + +AUDIO_PROVIDER_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/audio_provider_test.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.cc + +AUDIO_PROVIDER_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.h \ + +AUDIO_PROVIDER_MOCK_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/audio_provider_mock_test.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ +tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/audio_provider_mock.cc + +AUDIO_PROVIDER_MOCK_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h \ +tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.h \ + +FEATURE_PROVIDER_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/feature_provider_test.cc \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.cc \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.cc \ +$(MICRO_FEATURES_GENERATOR_SRCS) + +FEATURE_PROVIDER_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.h \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.h \ +$(MICRO_FEATURES_GENERATOR_HDRS) + +FEATURE_PROVIDER_MOCK_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/feature_provider_test.cc \ +tensorflow/lite/micro/examples/micro_speech/audio_provider_mock.cc \ +tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc \ +$(MICRO_FEATURES_GENERATOR_SRCS) + +FEATURE_PROVIDER_MOCK_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.h \ +tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ +$(MICRO_FEATURES_GENERATOR_HDRS) + +RECOGNIZE_COMMANDS_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/recognize_commands_test.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc \ +tensorflow/lite/micro/examples/micro_speech/recognize_commands.cc + +RECOGNIZE_COMMANDS_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h \ +tensorflow/lite/micro/examples/micro_speech/recognize_commands.h + +COMMAND_RESPONDER_TEST_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/command_responder_test.cc \ +tensorflow/lite/micro/examples/micro_speech/command_responder.cc + +COMMAND_RESPONDER_TEST_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/command_responder.h + +MICRO_SPEECH_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/main.cc \ +tensorflow/lite/micro/examples/micro_speech/main_functions.cc \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.cc \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc \ +tensorflow/lite/micro/examples/micro_speech/recognize_commands.cc \ +tensorflow/lite/micro/examples/micro_speech/command_responder.cc \ +$(MICRO_FEATURES_GENERATOR_SRCS) + +MICRO_SPEECH_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.h \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h \ +tensorflow/lite/micro/examples/micro_speech/recognize_commands.h \ +tensorflow/lite/micro/examples/micro_speech/command_responder.h \ +tensorflow/lite/micro/examples/micro_speech/main_functions.h \ +$(MICRO_FEATURES_GENERATOR_HDRS) + +MICRO_SPEECH_MOCK_SRCS := \ +tensorflow/lite/micro/examples/micro_speech/main.cc \ +tensorflow/lite/micro/examples/micro_speech/main_functions.cc \ +tensorflow/lite/micro/examples/micro_speech/audio_provider_mock.cc \ +tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.cc \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc \ +tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc \ +tensorflow/lite/micro/examples/micro_speech/recognize_commands.cc \ +tensorflow/lite/micro/examples/micro_speech/command_responder.cc \ +$(MICRO_FEATURES_GENERATOR_SRCS) + +MICRO_SPEECH_MOCK_HDRS := \ +tensorflow/lite/micro/examples/micro_speech/audio_provider.h \ +tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h \ +tensorflow/lite/micro/examples/micro_speech/feature_provider.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h \ +tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h \ +tensorflow/lite/micro/examples/micro_speech/recognize_commands.h \ +tensorflow/lite/micro/examples/micro_speech/command_responder.h \ +tensorflow/lite/micro/examples/micro_speech/main_functions.h \ +$(MICRO_FEATURES_GENERATOR_HDRS) + +#Find any platform - specific rules for this example. +include $(wildcard tensorflow/lite/micro/examples/micro_speech/*/Makefile.inc) + +# Test the code for feature generation. +$(eval $(call microlite_test,micro_features_generator_test,\ +$(MICRO_FEATURES_GENERATOR_TEST_SRCS), $(MICRO_FEATURES_GENERATOR_TEST_HDRS))) + +# Tests loading and running a speech model. +$(eval $(call microlite_test,micro_speech_test,\ +$(MICRO_SPEECH_TEST_SRCS),$(MICRO_SPEECH_TEST_HDRS))) + +# Test the code for feature generation. +$(eval $(call microlite_test,simple_features_generator_test,\ +$(SIMPLE_FEATURES_GENERATOR_TEST_SRCS), $(SIMPLE_FEATURES_GENERATOR_TEST_HDRS))) + +# Tests the audio provider module. +$(eval $(call microlite_test,audio_provider_test,\ +$(AUDIO_PROVIDER_TEST_SRCS),$(AUDIO_PROVIDER_TEST_HDRS))) + +# Tests the audio provider mock module. +$(eval $(call microlite_test,audio_provider_mock_test,\ +$(AUDIO_PROVIDER_MOCK_TEST_SRCS),$(AUDIO_PROVIDER_MOCK_TEST_HDRS))) + +# Tests the feature provider module. +$(eval $(call microlite_test,feature_provider_test,\ +$(FEATURE_PROVIDER_TEST_SRCS),$(FEATURE_PROVIDER_TEST_HDRS))) + +# Tests the feature provider module using the mock audio provider. +$(eval $(call microlite_test,feature_provider_mock_test,\ +$(FEATURE_PROVIDER_MOCK_TEST_SRCS),$(FEATURE_PROVIDER_MOCK_TEST_HDRS))) + +# Tests the command recognizer module. +$(eval $(call microlite_test,recognize_commands_test,\ +$(RECOGNIZE_COMMANDS_TEST_SRCS),$(RECOGNIZE_COMMANDS_TEST_HDRS))) + +# Tests responding to a command. +$(eval $(call microlite_test,command_responder_test,\ +$(COMMAND_RESPONDER_TEST_SRCS),$(COMMAND_RESPONDER_TEST_HDRS))) + +# Builds a standalone speech command recognizer binary. +$(eval $(call microlite_test,micro_speech,\ +$(MICRO_SPEECH_SRCS),$(MICRO_SPEECH_HDRS))) + +# Builds a standalone speech command recognizer binary using fake audio input. +$(eval $(call microlite_test,micro_speech_mock,\ +$(MICRO_SPEECH_MOCK_SRCS),$(MICRO_SPEECH_MOCK_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md b/tensorflow/lite/micro/examples/micro_speech/README.md similarity index 87% rename from tensorflow/lite/experimental/micro/examples/micro_speech/README.md rename to tensorflow/lite/micro/examples/micro_speech/README.md index 94a05eb9cc7..9724c68f32a 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/README.md +++ b/tensorflow/lite/micro/examples/micro_speech/README.md @@ -20,7 +20,6 @@ kilobytes of Flash. - [Deploy to SparkFun Edge](#deploy-to-sparkfun-edge) - [Deploy to STM32F746](#deploy-to-STM32F746) - [Deploy to NXP FRDM K66F](#deploy-to-nxp-frdm-k66f) -- [Deploy to Adafruit devices](#deploy-to-adafruit) - [Run on macOS](#run-on-macos) - [Run the tests on a development machine](#run-the-tests-on-a-development-machine) - [Calculating the input to the neural network](#calculating-the-input-to-the-neural-network) @@ -102,13 +101,13 @@ The following command will download the required dependencies and then compile a binary for the SparkFun Edge: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=sparkfun_edge TAGS="cmsis-nn" micro_speech_bin +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=sparkfun_edge TAGS="cmsis-nn" micro_speech_bin ``` The binary will be created in the following location: ``` -tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech.bin +tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech.bin ``` ### Sign the binary @@ -122,15 +121,15 @@ Enter the following command to set up some dummy cryptographic keys we can use for development: ``` -cp tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ -tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py +cp tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ +tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py ``` Next, run the following command to create a signed binary: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ ---bin tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech.bin \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ +--bin tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/micro_speech.bin \ --load-address 0xC000 \ --magic-num 0xCB \ -o main_nonsecure_ota \ @@ -142,7 +141,7 @@ command to create a final version of the file that can be used to flash our device with the bootloader script we will use in the next step: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ --load-address 0x20000 \ --bin main_nonsecure_ota.bin \ -i 6 \ @@ -178,7 +177,7 @@ hit the button marked `RST`. Continue holding the button marked `14` while running the following command: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ -b ${BAUD_RATE} ${DEVICENAME} \ -r 1 \ -f main_nonsecure_wire.bin \ @@ -250,13 +249,13 @@ command to generate a subfolder containing the required source files in this structure: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=mbed TAGS="CMSIS disco_f746ng" generate_micro_speech_mbed_project +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=mbed TAGS="CMSIS disco_f746ng" generate_micro_speech_mbed_project ``` Running the make command will result in the creation of a new folder: ``` -tensorflow/lite/experimental/micro/tools/make/gen/mbed_cortex-m4/prj/micro_speech/mbed +tensorflow/lite/micro/tools/make/gen/mbed_cortex-m4/prj/micro_speech/mbed ``` This folder contains all of the example's dependencies structured in the correct @@ -347,10 +346,10 @@ using [ARM Mbed](https://github.com/ARMmbed/mbed-cli). 3. Compile TensorFlow with the following command to generate mbed project: ``` - make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=mbed TAGS="nxp_k66f" generate_micro_speech_mbed_project + make -f tensorflow/lite/micro/tools/make/Makefile TARGET=mbed TAGS="nxp_k66f" generate_micro_speech_mbed_project ``` 4. Go to the location of the generated project. The generated project is usually - in `tensorflow/lite/experimental/micro/tools/make/gen/mbed_cortex-m4/prj/micro_speech/mbed` + in `tensorflow/lite/micro/tools/make/gen/mbed_cortex-m4/prj/micro_speech/mbed` 5. Create a mbed project using the generated files: `mbed new .` 6. Change the project setting to use C++ 11 rather than C++ 14 using: @@ -399,16 +398,6 @@ using [ARM Mbed](https://github.com/ARMmbed/mbed-cli). in black color. If there is no output on the serial port, you can connect headphone to headphone port to check if audio loopback path is working. -## Deploy to Adafruit devices - -This sample has been tested with the following Adafruit devices. To deploy to -each device, read the accompanying guide on Adafruit's website. - -| Device | Guide | -|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| -| [Adafruit EdgeBadge](https://www.adafruit.com/product/4400) | [TensorFlow Lite for EdgeBadge Kit Quickstart](https://learn.adafruit.com/tensorflow-lite-for-edgebadge-kit-quickstart?view=all) | -| [Adafruit TensorFlow Lite for Microcontrollers Kit](https://www.adafruit.com/product/4317) | [TensorFlow Lite for EdgeBadge Kit Quickstart](https://learn.adafruit.com/tensorflow-lite-for-edgebadge-kit-quickstart?view=all) | - ## Run on macOS The example contains an audio provider compatible with macOS. If you have access @@ -417,13 +406,13 @@ to a Mac, you can run the example on your development machine. First, use the following command to build it: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile micro_speech +make -f tensorflow/lite/micro/tools/make/Makefile micro_speech ``` Once the build completes, you can run the example with the following command: ``` -tensorflow/lite/experimental/micro/tools/make/gen/osx_x86_64/bin/micro_speech +tensorflow/lite/micro/tools/make/gen/osx_x86_64/bin/micro_speech ``` You might see a pop-up asking for microphone access. If so, grant it, and the @@ -459,7 +448,7 @@ To compile and test this example on a desktop Linux or macOS machine, download into the source directory from a terminal, and then run the following command: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile test_micro_speech_test +make -f tensorflow/lite/micro/tools/make/Makefile test_micro_speech_test ``` This will take a few minutes, and downloads frameworks the code uses like @@ -473,7 +462,7 @@ the trained TensorFlow model, runs some example inputs through it, and got the expected outputs. To understand how TensorFlow Lite does this, you can look at the source in -[micro_speech_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc). +[micro_speech_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/micro_speech/micro_speech_test.cc). It's a fairly small amount of code that creates an interpreter, gets a handle to a model that's been compiled into the program, and then invokes the interpreter with the model and sample inputs. @@ -546,7 +535,7 @@ go ### Use Google Colaboratory -The easiest way to train your own speech model is by running [`train_speech_model.ipynb`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/experimental/micro/examples/micro_speech/train_speech_model.ipynb) +The easiest way to train your own speech model is by running [`train_speech_model.ipynb`](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/micro/examples/micro_speech/train_speech_model.ipynb) in Google Colaboratory. This avoids the need to install dependencies, and allows the use of GPUs for training. Total training time will be 1.5-2hrs. diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/.gitignore b/tensorflow/lite/micro/examples/micro_speech/apollo3/.gitignore similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/.gitignore rename to tensorflow/lite/micro/examples/micro_speech/apollo3/.gitignore diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/Makefile.inc b/tensorflow/lite/micro/examples/micro_speech/apollo3/Makefile.inc similarity index 96% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/Makefile.inc rename to tensorflow/lite/micro/examples/micro_speech/apollo3/Makefile.inc index c83090344ba..21e167e9290 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/Makefile.inc +++ b/tensorflow/lite/micro/examples/micro_speech/apollo3/Makefile.inc @@ -46,8 +46,8 @@ ifeq ($(TARGET), apollo3evb) $(TEST_SCRIPT) $(PUSHBUTTON_CMSIS_SPEECH_TEST_BINARY) '~~~ALL TESTS PASSED~~~' PREPROCESSOR_1K_SRCS := \ - tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k.cc \ - tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.cc + tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k.cc \ + tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.cc PREPROCESSOR_1K_MICRO_TEST_SRCS := \ $(PREPROCESSOR_1K_SRCS) \ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/README.md b/tensorflow/lite/micro/examples/micro_speech/apollo3/README.md similarity index 96% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/README.md rename to tensorflow/lite/micro/examples/micro_speech/apollo3/README.md index dbdf2a31e02..57c1173d9e8 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/README.md +++ b/tensorflow/lite/micro/examples/micro_speech/apollo3/README.md @@ -24,14 +24,14 @@ * **This test should be compiled with the -O0 option.** Otherwise, the breakpoints will not be reached * In - tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc + tensorflow/lite/micro/tools/make/targets/apollo3evb_makefile.inc change "-O3" to "-O0" on line 47 * **DO NOT FORGET TO REVERT CHANGE AFTER EXPERIMENT** * In future, enhance scripts to handle automatically, NOT manually! * Clean project by running "make -f - tensorflow/lite/experimental/micro/tools/make/Makefile clean" + tensorflow/lite/micro/tools/make/Makefile clean" * Compile BIN by running "make -f - tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=apollo3evb + tensorflow/lite/micro/tools/make/Makefile TARGET=apollo3evb preprocessor_1k_cmsis_test_bin" * Run with the preprocessor\_1k\_cmsis\_test.cmd GDB command file * Produces four text files corresponding to outputs from the CMSIS diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/_main.c b/tensorflow/lite/micro/examples/micro_speech/apollo3/_main.c similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/_main.c rename to tensorflow/lite/micro/examples/micro_speech/apollo3/_main.c diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/captured_data_to_wav.py b/tensorflow/lite/micro/examples/micro_speech/apollo3/captured_data_to_wav.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/captured_data_to_wav.py rename to tensorflow/lite/micro/examples/micro_speech/apollo3/captured_data_to_wav.py diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/compare_1k.py b/tensorflow/lite/micro/examples/micro_speech/apollo3/compare_1k.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/compare_1k.py rename to tensorflow/lite/micro/examples/micro_speech/apollo3/compare_1k.py diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k.cc b/tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k.cc similarity index 88% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k.cc rename to tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k.cc index 92a98a5a94c..a0a96c93cfc 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k.cc +++ b/tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k.cc @@ -17,9 +17,9 @@ limitations under the License. */ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/sin_1k.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/CMSIS/sin_1k.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" extern "C" { #include "apollo3.h" diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k_cmsis_test.cmd b/tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k_cmsis_test.cmd similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k_cmsis_test.cmd rename to tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k_cmsis_test.cmd diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k_micro_test.cmd b/tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k_micro_test.cmd similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_1k_micro_test.cmd rename to tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_1k_micro_test.cmd diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_test.cmd b/tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_test.cmd similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/preprocessor_test.cmd rename to tensorflow/lite/micro/examples/micro_speech/apollo3/preprocessor_test.cmd diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_cmsis_scores.cmd b/tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_cmsis_scores.cmd similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_cmsis_scores.cmd rename to tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_cmsis_scores.cmd diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_cmsis_voice.cmd b/tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_cmsis_voice.cmd similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_cmsis_voice.cmd rename to tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_cmsis_voice.cmd diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_main.c b/tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_main.c similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_main.c rename to tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_main.c diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_test.cc b/tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_test.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_test.cc rename to tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_test.cc index ac426e690cc..012e0f1d7e6 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3/pushbutton_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/apollo3/pushbutton_test.cc @@ -17,12 +17,12 @@ limitations under the License. * micro_speech_test.cc */ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/audio_provider.cc b/tensorflow/lite/micro/examples/micro_speech/apollo3evb/audio_provider.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/audio_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/apollo3evb/audio_provider.cc index b89af68869f..0f9a91a9dba 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/audio_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/apollo3evb/audio_provider.cc @@ -21,7 +21,7 @@ limitations under the License. // application) USE_MAYA : Enable specific pin configuration and features for // AP3B "quarter" sized board -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" #include @@ -29,7 +29,7 @@ limitations under the License. #include "am_bsp.h" // NOLINT #include "am_mcu_apollo.h" // NOLINT #include "am_util.h" // NOLINT -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" namespace { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/command_responder.cc b/tensorflow/lite/micro/examples/micro_speech/apollo3evb/command_responder.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/command_responder.cc rename to tensorflow/lite/micro/examples/micro_speech/apollo3evb/command_responder.cc index 66237b62547..4dd7dcb929c 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/command_responder.cc +++ b/tensorflow/lite/micro/examples/micro_speech/apollo3evb/command_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h" +#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h" #include "am_bsp.h" // NOLINT #include "am_util.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/micro_speech.cmd b/tensorflow/lite/micro/examples/micro_speech/apollo3evb/micro_speech.cmd similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/apollo3evb/micro_speech.cmd rename to tensorflow/lite/micro/examples/micro_speech/apollo3evb/micro_speech.cmd diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/arduino/audio_provider.cc b/tensorflow/lite/micro/examples/micro_speech/arduino/audio_provider.cc similarity index 96% rename from tensorflow/lite/experimental/micro/examples/micro_speech/arduino/audio_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/arduino/audio_provider.cc index e8c27c897eb..c783aea034e 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/arduino/audio_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/arduino/audio_provider.cc @@ -28,10 +28,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" #include "PDM.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" namespace { bool g_is_audio_initialized = false; diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/arduino/command_responder.cc b/tensorflow/lite/micro/examples/micro_speech/arduino/command_responder.cc similarity index 96% rename from tensorflow/lite/experimental/micro/examples/micro_speech/arduino/command_responder.cc rename to tensorflow/lite/micro/examples/micro_speech/arduino/command_responder.cc index 5085c8b9ad5..04c92c87dbc 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/arduino/command_responder.cc +++ b/tensorflow/lite/micro/examples/micro_speech/arduino/command_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h" +#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h" #include "Arduino.h" diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/arduino/main.cc b/tensorflow/lite/micro/examples/micro_speech/arduino/main.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/micro_speech/arduino/main.cc rename to tensorflow/lite/micro/examples/micro_speech/arduino/main.cc index 7cfb048ad5b..e9e3edbfb30 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/arduino/main.cc +++ b/tensorflow/lite/micro/examples/micro_speech/arduino/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h" +#include "tensorflow/lite/micro/examples/micro_speech/main_functions.h" // Arduino automatically calls the setup() and loop() functions in a sketch, so // where other systems need their own main routine in this file, it can be left diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc b/tensorflow/lite/micro/examples/micro_speech/audio_provider.cc similarity index 87% rename from tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/audio_provider.cc index 08811c83b43..57755925548 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/audio_provider.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" namespace { int16_t g_dummy_audio_data[kMaxAudioSampleSize]; diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h b/tensorflow/lite/micro/examples/micro_speech/audio_provider.h similarity index 87% rename from tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h rename to tensorflow/lite/micro/examples/micro_speech/audio_provider.h index 20dfbd76b71..c51cc5f3fa9 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h +++ b/tensorflow/lite/micro/examples/micro_speech/audio_provider.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_AUDIO_PROVIDER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_AUDIO_PROVIDER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_AUDIO_PROVIDER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_AUDIO_PROVIDER_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // This is an abstraction around an audio source like a microphone, and is // expected to return 16-bit PCM sample data for a given point in time. The @@ -43,4 +43,4 @@ TfLiteStatus GetAudioSamples(tflite::ErrorReporter* error_reporter, // your own platform-specific implementation. int32_t LatestAudioTimestamp(); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_AUDIO_PROVIDER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_AUDIO_PROVIDER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock.cc b/tensorflow/lite/micro/examples/micro_speech/audio_provider_mock.cc similarity index 84% rename from tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock.cc rename to tensorflow/lite/micro/examples/micro_speech/audio_provider_mock.cc index 9c9792510b0..c67dd4d8c27 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock.cc +++ b/tensorflow/lite/micro/examples/micro_speech/audio_provider_mock.cc @@ -13,12 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" - -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" - -#include "tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h" namespace { int16_t g_dummy_audio_data[kMaxAudioSampleSize]; diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock_test.cc b/tensorflow/lite/micro/examples/micro_speech/audio_provider_mock_test.cc similarity index 84% rename from tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock_test.cc rename to tensorflow/lite/micro/examples/micro_speech/audio_provider_mock_test.cc index 1c95f7b3d1f..d874210ccea 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_mock_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/audio_provider_mock_test.cc @@ -16,12 +16,12 @@ limitations under the License. #include #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc b/tensorflow/lite/micro/examples/micro_speech/audio_provider_test.cc similarity index 88% rename from tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc rename to tensorflow/lite/micro/examples/micro_speech/audio_provider_test.cc index 76762202b34..065f0f6f996 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/audio_provider_test.cc @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" #include #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.cc b/tensorflow/lite/micro/examples/micro_speech/command_responder.cc similarity index 93% rename from tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.cc rename to tensorflow/lite/micro/examples/micro_speech/command_responder.cc index afff5109d9d..fd6b017a24d 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.cc +++ b/tensorflow/lite/micro/examples/micro_speech/command_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h" +#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h" // The default implementation writes out the name of the recognized command // to the error console. Real applications will want to take some custom diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h b/tensorflow/lite/micro/examples/micro_speech/command_responder.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h rename to tensorflow/lite/micro/examples/micro_speech/command_responder.h index e9277122752..ac3f448ee41 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h +++ b/tensorflow/lite/micro/examples/micro_speech/command_responder.h @@ -15,11 +15,11 @@ limitations under the License. // Provides an interface to take an action based on an audio command. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_COMMAND_RESPONDER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_COMMAND_RESPONDER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_COMMAND_RESPONDER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_COMMAND_RESPONDER_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // Called every time the results of an audio recognition run are available. The // human-readable name of any recognized command is in the `found_command` @@ -29,4 +29,4 @@ void RespondToCommand(tflite::ErrorReporter* error_reporter, int32_t current_time, const char* found_command, uint8_t score, bool is_new_command); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_COMMAND_RESPONDER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_COMMAND_RESPONDER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/command_responder_test.cc b/tensorflow/lite/micro/examples/micro_speech/command_responder_test.cc similarity index 83% rename from tensorflow/lite/experimental/micro/examples/micro_speech/command_responder_test.cc rename to tensorflow/lite/micro/examples/micro_speech/command_responder_test.cc index 8acf4552f59..fe811ea52bc 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/command_responder_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/command_responder_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h" +#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/Makefile.inc b/tensorflow/lite/micro/examples/micro_speech/disco_f746ng/Makefile.inc similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/Makefile.inc rename to tensorflow/lite/micro/examples/micro_speech/disco_f746ng/Makefile.inc diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/audio_provider.cc b/tensorflow/lite/micro/examples/micro_speech/disco_f746ng/audio_provider.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/audio_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/disco_f746ng/audio_provider.cc index 49fea826759..035d243a12f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/audio_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/disco_f746ng/audio_provider.cc @@ -13,13 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" - -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" #include "AUDIO_DISCO_F746NG.h" #include "SDRAM_DISCO_F746NG.h" #include "mbed.h" // NOLINT +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" namespace { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/command_responder.cc b/tensorflow/lite/micro/examples/micro_speech/disco_f746ng/command_responder.cc similarity index 95% rename from tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/command_responder.cc rename to tensorflow/lite/micro/examples/micro_speech/disco_f746ng/command_responder.cc index a7f12eab1ab..d607f288088 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/command_responder.cc +++ b/tensorflow/lite/micro/examples/micro_speech/disco_f746ng/command_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h" +#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h" #include "LCD_DISCO_F746NG.h" diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/timer.cc b/tensorflow/lite/micro/examples/micro_speech/disco_f746ng/timer.cc similarity index 91% rename from tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/timer.cc rename to tensorflow/lite/micro/examples/micro_speech/disco_f746ng/timer.cc index a8f0fe4bd50..dcc4db35f5d 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/disco_f746ng/timer.cc +++ b/tensorflow/lite/micro/examples/micro_speech/disco_f746ng/timer.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/timer.h" +#include "tensorflow/lite/micro/examples/micro_speech/timer.h" namespace { int32_t g_current_time = 0; diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc b/tensorflow/lite/micro/examples/micro_speech/feature_provider.cc similarity index 92% rename from tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/feature_provider.cc index ebb02076436..05ae8dfbc31 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/feature_provider.cc @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/feature_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" FeatureProvider::FeatureProvider(int feature_size, uint8_t* feature_data) : feature_size_(feature_size), diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h b/tensorflow/lite/micro/examples/micro_speech/feature_provider.h similarity index 86% rename from tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h rename to tensorflow/lite/micro/examples/micro_speech/feature_provider.h index 9ffe3be1905..fc634ec108d 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h +++ b/tensorflow/lite/micro/examples/micro_speech/feature_provider.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_FEATURE_PROVIDER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_FEATURE_PROVIDER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_FEATURE_PROVIDER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_FEATURE_PROVIDER_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // Binds itself to an area of memory intended to hold the input features for an // audio-recognition neural network model, and fills that data area with the @@ -49,4 +49,4 @@ class FeatureProvider { bool is_first_run_; }; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_FEATURE_PROVIDER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_FEATURE_PROVIDER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_mock_test.cc b/tensorflow/lite/micro/examples/micro_speech/feature_provider_mock_test.cc similarity index 80% rename from tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_mock_test.cc rename to tensorflow/lite/micro/examples/micro_speech/feature_provider_mock_test.cc index eb7f6cf9b70..6dcf3da9a3f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_mock_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/feature_provider_mock_test.cc @@ -14,12 +14,12 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/feature_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_test.cc b/tensorflow/lite/micro/examples/micro_speech/feature_provider_test.cc similarity index 80% rename from tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_test.cc rename to tensorflow/lite/micro/examples/micro_speech/feature_provider_test.cc index 81595986ae7..8e0e1f47d15 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/feature_provider_test.cc @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/feature_provider.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/main.cc b/tensorflow/lite/micro/examples/micro_speech/main.cc similarity index 92% rename from tensorflow/lite/experimental/micro/examples/micro_speech/main.cc rename to tensorflow/lite/micro/examples/micro_speech/main.cc index 8b98634e45e..f35c4726a27 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/main.cc +++ b/tensorflow/lite/micro/examples/micro_speech/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h" +#include "tensorflow/lite/micro/examples/micro_speech/main_functions.h" // This is the default main used on systems that have the standard C entry // point. Other devices (for example FreeRTOS or ESP32) that have different diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.cc b/tensorflow/lite/micro/examples/micro_speech/main_functions.cc similarity index 87% rename from tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.cc rename to tensorflow/lite/micro/examples/micro_speech/main_functions.cc index 703e120a307..6ccf56a306b 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.cc +++ b/tensorflow/lite/micro/examples/micro_speech/main_functions.cc @@ -13,18 +13,18 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h" +#include "tensorflow/lite/micro/examples/micro_speech/main_functions.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/feature_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h" +#include "tensorflow/lite/micro/examples/micro_speech/feature_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/recognize_commands.h" +#include "tensorflow/lite/micro/kernels/micro_ops.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h b/tensorflow/lite/micro/examples/micro_speech/main_functions.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h rename to tensorflow/lite/micro/examples/micro_speech/main_functions.h index 4012fc94bef..19599343652 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/main_functions.h +++ b/tensorflow/lite/micro/examples/micro_speech/main_functions.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MAIN_FUNCTIONS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MAIN_FUNCTIONS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MAIN_FUNCTIONS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MAIN_FUNCTIONS_H_ // Initializes all data needed for the example. The name is important, and needs // to be setup() for Arduino compatibility. @@ -25,4 +25,4 @@ void setup(); // compatibility. void loop(); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MAIN_FUNCTIONS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MAIN_FUNCTIONS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/BUILD b/tensorflow/lite/micro/examples/micro_speech/micro_features/BUILD similarity index 83% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/BUILD rename to tensorflow/lite/micro/examples/micro_speech/micro_features/BUILD index 6580b8bdf3a..da9f500cabd 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/BUILD +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/BUILD @@ -1,7 +1,7 @@ # Library for generating feature vectors from audio data load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) @@ -58,8 +58,8 @@ cc_library( deps = [ ":micro_model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", "//tensorflow/lite/experimental/microfrontend/lib:frontend", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -85,8 +85,8 @@ tflite_micro_cc_test( ":micro_features_generator_test_data", ":micro_model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/examples/micro_speech:audio_sample_test_data", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/examples/micro_speech:audio_sample_test_data", + "//tensorflow/lite/micro/testing:micro_test", ], ) diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.cc similarity index 94% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.cc index 3adf5022059..d439ef2dea5 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.cc @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h" #include #include -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" #include "tensorflow/lite/experimental/microfrontend/lib/frontend.h" #include "tensorflow/lite/experimental/microfrontend/lib/frontend_util.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" // Configure FFT to output 16 bit fixed point. #define FIXED_POINT 16 diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h similarity index 75% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h index 37a10aff494..7b9bc5faec8 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_FEATURES_GENERATOR_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_FEATURES_GENERATOR_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_FEATURES_GENERATOR_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_FEATURES_GENERATOR_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // Sets up any resources needed for the feature generation pipeline. TfLiteStatus InitializeMicroFeatures(tflite::ErrorReporter* error_reporter); @@ -29,4 +29,4 @@ TfLiteStatus GenerateMicroFeatures(tflite::ErrorReporter* error_reporter, int output_size, uint8_t* output, size_t* num_samples_read); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_FEATURES_GENERATOR_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_FEATURES_GENERATOR_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc similarity index 86% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc index cd11a66d2b1..16ed7c71b1e 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator_test.cc @@ -13,15 +13,15 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_features_generator.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h" +#include "tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" // This is a test-only API, not exposed in any public headers, so declare it. void SetMicroFeaturesNoiseEstimates(const uint32_t* estimate_presets); diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc similarity index 87% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc index 09f65ca24b3..47d12baf707 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" const char* kCategoryLabels[kCategoryCount] = { "silence", diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h similarity index 84% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h index b74a4d01ca4..270c5f3cce6 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_MODEL_SETTINGS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_MODEL_SETTINGS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_MODEL_SETTINGS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_MODEL_SETTINGS_H_ // Keeping these as constant expressions allow us to allocate fixed-sized arrays // on the stack for our working memory. @@ -38,4 +38,4 @@ constexpr int kSilenceIndex = 0; constexpr int kUnknownIndex = 1; extern const char* kCategoryLabels[kCategoryCount]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_MODEL_SETTINGS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_MICRO_MODEL_SETTINGS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc index 1dbb606e184..b523a8185d4 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.h" const uint8_t g_no_feature_data_slice[g_no_feature_data_slice_size] = { 216, 195, 223, 211, 238, 223, 243, 215, 226, 204, 232, 211, 232, 213, diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.h similarity index 77% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.h index 72ea2bf6a23..234e7efc388 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_feature_data_slice.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_feature_data_slice.h @@ -18,12 +18,12 @@ limitations under the License. // This is the expected result of running the sample data in // no_30ms_sample_data.cc through through the preprocessing pipeline. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_FEATURE_DATA_SLICE_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_FEATURE_DATA_SLICE_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_FEATURE_DATA_SLICE_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_FEATURE_DATA_SLICE_H_ #include constexpr int g_no_feature_data_slice_size = 40; extern const uint8_t g_no_feature_data_slice[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_FEATURE_DATA_SLICE_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_FEATURE_DATA_SLICE_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.cc index 865209b01df..d7a923364a7 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h" /* File automatically created by * tensorflow/examples/speech_commands/wav_to_features.py \ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h similarity index 72% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h index 178323eeba6..dc4d45b237e 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_MICRO_FEATURES_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_MICRO_FEATURES_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_MICRO_FEATURES_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_MICRO_FEATURES_DATA_H_ extern const int g_no_micro_f9643d42_nohash_4_width; extern const int g_no_micro_f9643d42_nohash_4_height; extern const unsigned char g_no_micro_f9643d42_nohash_4_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_MICRO_FEATURES_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_NO_MICRO_FEATURES_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/static_alloc.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/static_alloc.h similarity index 83% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/static_alloc.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/static_alloc.h index e2af862de75..92434641700 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/static_alloc.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/static_alloc.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_STATIC_ALLOC_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_STATIC_ALLOC_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_STATIC_ALLOC_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_STATIC_ALLOC_H_ // Checks to ensure that the C-style array passed in has a compile-time size of // at least the number of bytes requested. This doesn't work with raw pointers @@ -29,4 +29,4 @@ limitations under the License. } \ } while (0) -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_STATIC_ALLOC_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_STATIC_ALLOC_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc index 3e8938041f7..16052bac540 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.cc @@ -17,7 +17,7 @@ limitations under the License. // xxd -i tiny_conv.tflite > tiny_conv_simple_features_model_data.cc // See the README for a full description of the creation process. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h" // We need to keep the data array aligned on some architectures. #ifdef __has_attribute diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h similarity index 75% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h index 22c0a970b77..b14f4641eee 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h @@ -18,10 +18,10 @@ limitations under the License. // don't have a file system. It was created using the command: // xxd -i tiny_conv.tflite > tiny_conv_simple_features_model_data.cc -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_ extern const unsigned char g_tiny_conv_micro_features_model_data[]; extern const int g_tiny_conv_micro_features_model_data_len; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_TINY_CONV_MICRO_FEATURES_MODEL_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc index 48535d12d5d..7597b043d9b 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h" const uint8_t g_yes_feature_data_slice[g_yes_feature_data_slice_size] = { 214, 215, 236, 202, 235, 203, 225, 191, 203, 188, 199, 194, 212, 127, diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h similarity index 76% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h index e73a13153b6..1515449b2c2 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_feature_data_slice.h @@ -18,12 +18,12 @@ limitations under the License. // values. This is the expected result of running the sample data in // yes_30ms_sample_data.cc through through the preprocessing pipeline. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_FEATURE_DATA_SLICE_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_FEATURE_DATA_SLICE_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_FEATURE_DATA_SLICE_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_FEATURE_DATA_SLICE_H_ #include constexpr int g_yes_feature_data_slice_size = 40; extern const uint8_t g_yes_feature_data_slice[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_FEATURE_DATA_SLICE_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_FEATURE_DATA_SLICE_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc index 2c2ee0995c0..9c1fb8be0bb 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h" /* File automatically created by * tensorflow/examples/speech_commands/wav_to_features.py \ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h similarity index 72% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h rename to tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h index d19bf8f067d..07eccc35f4e 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_MICRO_FEATURES_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_MICRO_FEATURES_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_MICRO_FEATURES_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_MICRO_FEATURES_DATA_H_ extern const int g_yes_micro_f2e59fea_nohash_1_width; extern const int g_yes_micro_f2e59fea_nohash_1_height; extern const unsigned char g_yes_micro_f2e59fea_nohash_1_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_MICRO_FEATURES_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_MICRO_FEATURES_YES_MICRO_FEATURES_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc b/tensorflow/lite/micro/examples/micro_speech/micro_speech_test.cc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc rename to tensorflow/lite/micro/examples/micro_speech/micro_speech_test.cc index c85dc6782ec..460d9fdf5b9 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/micro_speech_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/micro_speech_test.cc @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/no_micro_features_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/yes_micro_features_data.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/no_micro_features_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/tiny_conv_micro_features_model_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/yes_micro_features_data.h" +#include "tensorflow/lite/micro/kernels/micro_ops.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.cc b/tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.cc rename to tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.cc index 85113a90dcf..7212bec4879 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h" const int g_no_1000ms_sample_data_size = 16000; const int16_t g_no_1000ms_sample_data[16000] = { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h b/tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h rename to tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h index 4cc8030cdac..ab2d67b499f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/no_1000ms_sample_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/no_1000ms_sample_data.h @@ -18,12 +18,12 @@ limitations under the License. // speech_commands_test_set_v0.02/no/f9643d42_nohash_4.wav // This should contain all 16,000 samples from the one-second file. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_NO_1000MS_SAMPLE_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_NO_1000MS_SAMPLE_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_NO_1000MS_SAMPLE_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_NO_1000MS_SAMPLE_DATA_H_ #include extern const int g_no_1000ms_sample_data_size; extern const int16_t g_no_1000ms_sample_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_NO_1000MS_SAMPLE_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_NO_1000MS_SAMPLE_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.cc b/tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.cc rename to tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.cc index 6eaa5c2fed6..6b993ab47c6 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h" const int g_no_30ms_sample_data_size = 480; const int16_t g_no_30ms_sample_data[480] = { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h b/tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h similarity index 82% rename from tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h rename to tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h index ff6b8740899..1eecca2370a 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h @@ -21,12 +21,12 @@ limitations under the License. // preprocessing pipeline, to ensure that the expected spectrogram slice is // produced given this input. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_NO_30MS_SAMPLE_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_NO_30MS_SAMPLE_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_NO_30MS_SAMPLE_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_NO_30MS_SAMPLE_DATA_H_ #include extern const int g_no_30ms_sample_data_size; extern const int16_t g_no_30ms_sample_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_NO_30MS_SAMPLE_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_NO_30MS_SAMPLE_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/nxp_k66f/audio_provider.cc b/tensorflow/lite/micro/examples/micro_speech/nxp_k66f/audio_provider.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/nxp_k66f/audio_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/nxp_k66f/audio_provider.cc index 55267e5ad50..8fe4a88c368 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/nxp_k66f/audio_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/nxp_k66f/audio_provider.cc @@ -11,9 +11,9 @@ limitations under the License. ==============================================================================*/ // TensorFlow Headers -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" // mbed and NXP FRDM-K66F Headers #include "fsl_clock_config.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc b/tensorflow/lite/micro/examples/micro_speech/osx/Makefile.inc similarity index 100% rename from tensorflow/lite/experimental/micro/examples/micro_speech/osx/Makefile.inc rename to tensorflow/lite/micro/examples/micro_speech/osx/Makefile.inc diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc b/tensorflow/lite/micro/examples/micro_speech/osx/audio_provider.cc similarity index 96% rename from tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/osx/audio_provider.cc index 6468c1a95a9..ba133eec99a 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/osx/audio_provider.cc @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" #include -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h" namespace { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc b/tensorflow/lite/micro/examples/micro_speech/recognize_commands.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc rename to tensorflow/lite/micro/examples/micro_speech/recognize_commands.cc index 8cc7e2eeb9a..5fd1454b49f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.cc +++ b/tensorflow/lite/micro/examples/micro_speech/recognize_commands.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h" +#include "tensorflow/lite/micro/examples/micro_speech/recognize_commands.h" #include diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h b/tensorflow/lite/micro/examples/micro_speech/recognize_commands.h similarity index 92% rename from tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h rename to tensorflow/lite/micro/examples/micro_speech/recognize_commands.h index 4c85a294f2f..57a09194b35 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h +++ b/tensorflow/lite/micro/examples/micro_speech/recognize_commands.h @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_RECOGNIZE_COMMANDS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_RECOGNIZE_COMMANDS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_RECOGNIZE_COMMANDS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_RECOGNIZE_COMMANDS_H_ #include #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // Partial implementation of std::dequeue, just providing the functionality // that's needed to keep a record of previous neural network results over a @@ -153,4 +153,4 @@ class RecognizeCommands { int32_t previous_top_label_time_; }; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_RECOGNIZE_COMMANDS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_RECOGNIZE_COMMANDS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands_test.cc b/tensorflow/lite/micro/examples/micro_speech/recognize_commands_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands_test.cc rename to tensorflow/lite/micro/examples/micro_speech/recognize_commands_test.cc index 875fface496..70911a81776 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/recognize_commands_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/recognize_commands.h" +#include "tensorflow/lite/micro/examples/micro_speech/recognize_commands.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/CMSIS/simple_features_generator.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/CMSIS/simple_features_generator.cc similarity index 93% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/CMSIS/simple_features_generator.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/CMSIS/simple_features_generator.cc index 403976e222f..f2b227e7ede 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/CMSIS/simple_features_generator.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/CMSIS/simple_features_generator.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h" extern "C" { #define IFFT_FLAG_R 0 @@ -21,8 +21,9 @@ extern "C" { #define FFT_SIZE 512 #define FFT_SIZE_DIV2 256 #include + #include "arm_cmplx_mag_squared_q10p6.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS/hanning.h" +#include "tensorflow/lite/micro/examples/micro_speech/CMSIS/hanning.h" } void quantize(q15_t* bufA, q15_t* bufB, uint8_t* output); diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/fixed_point/simple_features_generator.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/fixed_point/simple_features_generator.cc similarity index 97% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/fixed_point/simple_features_generator.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/fixed_point/simple_features_generator.cc index ad11684b0a9..009be5d788a 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/fixed_point/simple_features_generator.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/fixed_point/simple_features_generator.cc @@ -27,11 +27,11 @@ limitations under the License. // instead of floating point, to help show how this can work on platforms that // don't have good float support. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h" #include -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h" namespace { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc index 0b20f2f86fb..aff02429a21 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h" const uint8_t g_no_power_spectrum_data[g_no_power_spectrum_data_size] = { 255, 7, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h similarity index 76% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h rename to tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h index 9693950fb5e..463a4951cf1 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h @@ -18,12 +18,12 @@ limitations under the License. // This is the expected result of running the sample data in // no_30ms_sample_data.cc through through the preprocessing pipeline. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_POWER_SPECTRUM_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_POWER_SPECTRUM_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_POWER_SPECTRUM_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_POWER_SPECTRUM_DATA_H_ #include constexpr int g_no_power_spectrum_data_size = 43; extern const uint8_t g_no_power_spectrum_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_POWER_SPECTRUM_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_POWER_SPECTRUM_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_simple_features_data.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_simple_features_data.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_simple_features_data.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/no_simple_features_data.cc index 3d3a9538fb5..2d7ae623010 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_simple_features_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_simple_features_data.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_simple_features_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/no_simple_features_data.h" /* File automatically created by * tensorflow/examples/speech_commands/wav_to_features.py \ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_simple_features_data.h b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_simple_features_data.h similarity index 72% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_simple_features_data.h rename to tensorflow/lite/micro/examples/micro_speech/simple_features/no_simple_features_data.h index 30332b30c5c..ff461348d52 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_simple_features_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/no_simple_features_data.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_SIMPLE_FEATURES_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_SIMPLE_FEATURES_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_SIMPLE_FEATURES_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_SIMPLE_FEATURES_DATA_H_ extern const int g_no_simple_f9643d42_nohash_4_width; extern const int g_no_simple_f9643d42_nohash_4_height; extern const unsigned char g_no_simple_f9643d42_nohash_4_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_SIMPLE_FEATURES_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_NO_SIMPLE_FEATURES_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.cc similarity index 96% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.cc index 06683a14872..7d584afc5a8 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.cc @@ -24,11 +24,11 @@ limitations under the License. // functions used here, for example replacing the DFT with an FFT, so this // version shouldn't be used where performance is critical. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h" #include -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h" namespace { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h similarity index 76% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h rename to tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h index 8e3389e9fb0..6d47bc49c44 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_FEATURES_GENERATOR_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_FEATURES_GENERATOR_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_FEATURES_GENERATOR_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_FEATURES_GENERATOR_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // Converts audio sample data into a more compact form that's appropriate for // feeding into a neural network. There are reference implementations that use @@ -28,4 +28,4 @@ TfLiteStatus GenerateSimpleFeatures(tflite::ErrorReporter* error_reporter, const int16_t* input, int input_size, int output_size, uint8_t* output); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_FEATURES_GENERATOR_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_FEATURES_GENERATOR_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc similarity index 78% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc index 2509d1e241c..3a650f981aa 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator_test.cc @@ -13,15 +13,15 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_features_generator.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_features_generator.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/no_30ms_sample_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h" -#include "tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/micro_speech/no_30ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/no_power_spectrum_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.cc similarity index 87% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.cc index 4842f8dbd90..e2cf661c014 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h" const char* kCategoryLabels[kCategoryCount] = { "silence", diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h similarity index 84% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h rename to tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h index d31d6b33622..9d129c8a86f 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/simple_model_settings.h +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/simple_model_settings.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_MODEL_SETTINGS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_MODEL_SETTINGS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_MODEL_SETTINGS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_MODEL_SETTINGS_H_ // Keeping these as constant expressions allow us to allocate fixed-sized arrays // on the stack for our working memory. @@ -40,4 +40,4 @@ constexpr int kSilenceIndex = 0; constexpr int kUnknownIndex = 1; extern const char* kCategoryLabels[kCategoryCount]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_MODEL_SETTINGS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_SIMPLE_MODEL_SETTINGS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.cc index a14412edc94..2c66e3904b6 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.cc @@ -17,7 +17,7 @@ limitations under the License. // xxd -i tiny_conv.tflite > tiny_conv_simple_features_model_data.cc // See the README for a full description of the creation process. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h" const unsigned char g_tiny_conv_simple_features_model_data[] = { 0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00, diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h b/tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h similarity index 74% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h rename to tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h index cadf7d0de75..a97d79033b8 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/tiny_conv_simple_features_model_data.h @@ -18,10 +18,10 @@ limitations under the License. // don't have a file system. It was created using the command: // xxd -i tiny_conv.tflite > tiny_conv_simple_features_model_data.cc -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_TINY_CONV_SIMPLE_FEATURES_MODEL_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_TINY_CONV_SIMPLE_FEATURES_MODEL_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_TINY_CONV_SIMPLE_FEATURES_MODEL_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_TINY_CONV_SIMPLE_FEATURES_MODEL_DATA_H_ extern const unsigned char g_tiny_conv_simple_features_model_data[]; extern const int g_tiny_conv_simple_features_model_data_len; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_TINY_CONV_SIMPLE_FEATURES_MODEL_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_TINY_CONV_SIMPLE_FEATURES_MODEL_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc index cd46408c0fb..96a7c9ac288 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h" const uint8_t g_yes_power_spectrum_data[g_yes_power_spectrum_data_size] = { 8, 89, 8, 0, 0, 0, 0, 0, 0, 0, 0, 4, 13, 1, 6, 23, 20, 6, 4, 0, 0, 0, diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h similarity index 76% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h rename to tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h index 77e52d58b54..7e0c146ace0 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_power_spectrum_data.h @@ -18,12 +18,12 @@ limitations under the License. // This is the expected result of running the sample data in // yes_30ms_sample_data.cc through through the preprocessing pipeline. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_POWER_SPECTRUM_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_POWER_SPECTRUM_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_POWER_SPECTRUM_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_POWER_SPECTRUM_DATA_H_ #include constexpr int g_yes_power_spectrum_data_size = 43; extern const uint8_t g_yes_power_spectrum_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_POWER_SPECTRUM_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_POWER_SPECTRUM_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_simple_features_data.cc b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_simple_features_data.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_simple_features_data.cc rename to tensorflow/lite/micro/examples/micro_speech/simple_features/yes_simple_features_data.cc index 2d660bb8b5c..078f78d5428 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_simple_features_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_simple_features_data.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_simple_features_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/simple_features/yes_simple_features_data.h" /* File automatically created by * tensorflow/examples/speech_commands/wav_to_features.py \ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_simple_features_data.h b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_simple_features_data.h similarity index 72% rename from tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_simple_features_data.h rename to tensorflow/lite/micro/examples/micro_speech/simple_features/yes_simple_features_data.h index 87ea4a4aea8..98c7e429fee 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/simple_features/yes_simple_features_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/simple_features/yes_simple_features_data.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_SIMPLE_FEATURES_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_SIMPLE_FEATURES_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_SIMPLE_FEATURES_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_SIMPLE_FEATURES_DATA_H_ extern const int g_yes_simple_f2e59fea_nohash_1_width; extern const int g_yes_simple_f2e59fea_nohash_1_height; extern const unsigned char g_yes_simple_f2e59fea_nohash_1_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_SIMPLE_FEATURES_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_SIMPLE_FEATURES_YES_SIMPLE_FEATURES_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc b/tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc rename to tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc index 520a46ef598..9f0cc41a618 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc +++ b/tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/audio_provider.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.h" +#include "tensorflow/lite/micro/examples/micro_speech/audio_provider.h" #include @@ -21,8 +21,7 @@ limitations under the License. #include "am_bsp.h" // NOLINT #include "am_mcu_apollo.h" // NOLINT #include "am_util.h" // NOLINT - -#include "tensorflow/lite/experimental/micro/examples/micro_speech/micro_features/micro_model_settings.h" +#include "tensorflow/lite/micro/examples/micro_speech/micro_features/micro_model_settings.h" namespace { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/command_responder.cc b/tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/command_responder.cc similarity index 96% rename from tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/command_responder.cc rename to tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/command_responder.cc index 78469f2b7d7..84d87e4cba4 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/sparkfun_edge/command_responder.cc +++ b/tensorflow/lite/micro/examples/micro_speech/sparkfun_edge/command_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/micro_speech/command_responder.h" +#include "tensorflow/lite/micro/examples/micro_speech/command_responder.h" #include "am_bsp.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/train_speech_model.ipynb b/tensorflow/lite/micro/examples/micro_speech/train_speech_model.ipynb similarity index 95% rename from tensorflow/lite/experimental/micro/examples/micro_speech/train_speech_model.ipynb rename to tensorflow/lite/micro/examples/micro_speech/train_speech_model.ipynb index 14a1e810cc0..c528ea16098 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/train_speech_model.ipynb +++ b/tensorflow/lite/micro/examples/micro_speech/train_speech_model.ipynb @@ -32,16 +32,16 @@ "colab_type": "text" }, "source": [ - "This notebook demonstrates how to train a 20kb [Simple Audio Recognition](https://www.tensorflow.org/tutorials/sequences/audio_recognition) model for [TensorFlow Lite for Microcontrollers](https://tensorflow.org/lite/microcontrollers/overview). It will produce the same model used in the [micro_speech](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/micro_speech) example application.\n", + "This notebook demonstrates how to train a 20kb [Simple Audio Recognition](https://www.tensorflow.org/tutorials/sequences/audio_recognition) model for [TensorFlow Lite for Microcontrollers](https://tensorflow.org/lite/microcontrollers/overview). It will produce the same model used in the [micro_speech](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/micro_speech) example application.\n", "\n", "The model is designed to be used with [Google Colaboratory](https://colab.research.google.com).\n", "\n", "\n", " \n", " \n", "
\n", - " Run in Google Colab\n", + " Run in Google Colab\n", " \n", - " View source on GitHub\n", + " View source on GitHub\n", "
\n" ] diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.cc b/tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.cc rename to tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.cc index e5f6ceb3f0b..d988d0c36ca 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h" const int g_yes_1000ms_sample_data_size = 16000; const int16_t g_yes_1000ms_sample_data[16000] = { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h b/tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h rename to tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h index 33aeea516fb..5d09866ae32 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_1000ms_sample_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/yes_1000ms_sample_data.h @@ -18,12 +18,12 @@ limitations under the License. // speech_commands_test_set_v0.02/yes/f2e59fea_nohash_1.wav // This should contain all 16,000 samples from the one-second file. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_YES_1000MS_SAMPLE_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_YES_1000MS_SAMPLE_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_YES_1000MS_SAMPLE_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_YES_1000MS_SAMPLE_DATA_H_ #include extern const int g_yes_1000ms_sample_data_size; extern const int16_t g_yes_1000ms_sample_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_YES_1000MS_SAMPLE_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_YES_1000MS_SAMPLE_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.cc b/tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.cc similarity index 98% rename from tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.cc rename to tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.cc index f089ef82f3a..32436dc6612 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.cc +++ b/tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.cc @@ -15,7 +15,7 @@ limitations under the License. // See the header for documentation on the meaning of this data. -#include "tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h" +#include "tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h" const int g_yes_30ms_sample_data_size = 480; const int16_t g_yes_30ms_sample_data[480] = { diff --git a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h b/tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h similarity index 81% rename from tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h rename to tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h index daaeb514a80..cfe201ad7a0 100644 --- a/tensorflow/lite/experimental/micro/examples/micro_speech/yes_30ms_sample_data.h +++ b/tensorflow/lite/micro/examples/micro_speech/yes_30ms_sample_data.h @@ -21,12 +21,12 @@ limitations under the License. // preprocessing pipeline, to ensure that the expected spectrogram slice is // produced given this input. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_YES_30MS_SAMPLE_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_YES_30MS_SAMPLE_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_YES_30MS_SAMPLE_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_YES_30MS_SAMPLE_DATA_H_ #include extern const int g_yes_30ms_sample_data_size; extern const int16_t g_yes_30ms_sample_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_MICRO_SPEECH_YES_30MS_SAMPLE_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_MICRO_SPEECH_YES_30MS_SAMPLE_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/network_tester/.gitignore b/tensorflow/lite/micro/examples/network_tester/.gitignore similarity index 100% rename from tensorflow/lite/experimental/micro/examples/network_tester/.gitignore rename to tensorflow/lite/micro/examples/network_tester/.gitignore diff --git a/tensorflow/lite/experimental/micro/examples/network_tester/Makefile.inc b/tensorflow/lite/micro/examples/network_tester/Makefile.inc similarity index 58% rename from tensorflow/lite/experimental/micro/examples/network_tester/Makefile.inc rename to tensorflow/lite/micro/examples/network_tester/Makefile.inc index 9dbcbe4c55b..27f54a66763 100644 --- a/tensorflow/lite/experimental/micro/examples/network_tester/Makefile.inc +++ b/tensorflow/lite/micro/examples/network_tester/Makefile.inc @@ -1,13 +1,13 @@ NETWORK_TESTER_TEST_SRCS := \ -tensorflow/lite/experimental/micro/examples/network_tester/network_tester_test.cc +tensorflow/lite/micro/examples/network_tester/network_tester_test.cc NETWORK_TESTER_TEST_HDRS := \ -tensorflow/lite/experimental/micro/examples/network_tester/network_model.h \ -tensorflow/lite/experimental/micro/examples/network_tester/input_data.h \ -tensorflow/lite/experimental/micro/examples/network_tester/expected_output_data.h +tensorflow/lite/micro/examples/network_tester/network_model.h \ +tensorflow/lite/micro/examples/network_tester/input_data.h \ +tensorflow/lite/micro/examples/network_tester/expected_output_data.h -# Find any platform-specific rules for this example. -include $(wildcard tensorflow/lite/experimental/micro/examples/network_tester/*/Makefile.inc) +#Find any platform - specific rules for this example. +include $(wildcard tensorflow/lite/micro/examples/network_tester/*/Makefile.inc) ifdef NETWORK_MODEL INCLUDES += -include $(NETWORK_MODEL) diff --git a/tensorflow/lite/experimental/micro/examples/network_tester/README.md b/tensorflow/lite/micro/examples/network_tester/README.md similarity index 96% rename from tensorflow/lite/experimental/micro/examples/network_tester/README.md rename to tensorflow/lite/micro/examples/network_tester/README.md index 5c2034e888c..5d6629a83f2 100644 --- a/tensorflow/lite/experimental/micro/examples/network_tester/README.md +++ b/tensorflow/lite/micro/examples/network_tester/README.md @@ -19,7 +19,7 @@ the new model. This is done by using the `ARENA_SIZE` option when running `make`. ``` -make -f tensorflow/lite/experimental/micro/example/network_tester_test \ +make -f tensorflow/lite/micro/example/network_tester_test \ NETWORK_MODEL=path/to/network_model.h \ INPUT_DATA=path/to/input_data.h \ OUTPUT_DATA=path/to/expected_output_data.h \ diff --git a/tensorflow/lite/experimental/micro/examples/network_tester/expected_output_data.h b/tensorflow/lite/micro/examples/network_tester/expected_output_data.h similarity index 73% rename from tensorflow/lite/experimental/micro/examples/network_tester/expected_output_data.h rename to tensorflow/lite/micro/examples/network_tester/expected_output_data.h index 4347f169ff5..03e21954b7f 100644 --- a/tensorflow/lite/experimental/micro/examples/network_tester/expected_output_data.h +++ b/tensorflow/lite/micro/examples/network_tester/expected_output_data.h @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_EXPECTED_OUTPUT_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_EXPECTED_OUTPUT_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_EXPECTED_OUTPUT_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_EXPECTED_OUTPUT_DATA_H_ static unsigned int expected_output_data_len = 4; static unsigned char expected_output_data[] = {6, 8, 14, 16}; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_EXPECTED_OUTPUT_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_EXPECTED_OUTPUT_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/network_tester/input_data.h b/tensorflow/lite/micro/examples/network_tester/input_data.h similarity index 77% rename from tensorflow/lite/experimental/micro/examples/network_tester/input_data.h rename to tensorflow/lite/micro/examples/network_tester/input_data.h index dd8646176c1..b47277cca93 100644 --- a/tensorflow/lite/experimental/micro/examples/network_tester/input_data.h +++ b/tensorflow/lite/micro/examples/network_tester/input_data.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_INPUT_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_INPUT_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_INPUT_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_INPUT_DATA_H_ static const int input_data_len = 16; static const unsigned char input_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_INPUT_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_INPUT_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/network_tester/network_model.h b/tensorflow/lite/micro/examples/network_tester/network_model.h similarity index 95% rename from tensorflow/lite/experimental/micro/examples/network_tester/network_model.h rename to tensorflow/lite/micro/examples/network_tester/network_model.h index dc23a226b08..4c275dbfbba 100644 --- a/tensorflow/lite/experimental/micro/examples/network_tester/network_model.h +++ b/tensorflow/lite/micro/examples/network_tester/network_model.h @@ -10,8 +10,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_NETWORK_MODEL_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_NETWORK_TESTER_NETWORK_MODEL_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_NETWORK_MODEL_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_NETWORK_MODEL_H_ const unsigned char network_model[] = { 0x18, 0x00, 0x00, 0x00, 0x54, 0x46, 0x4c, 0x33, 0x00, 0x00, 0x0e, 0x00, @@ -64,4 +64,4 @@ const unsigned char network_model[] = { 0x08, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11}; const unsigned int network_model_len = 576; -#endif +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_NETWORK_TESTER_NETWORK_MODEL_H_ diff --git a/tensorflow/lite/experimental/micro/examples/network_tester/network_tester_test.cc b/tensorflow/lite/micro/examples/network_tester/network_tester_test.cc similarity index 82% rename from tensorflow/lite/experimental/micro/examples/network_tester/network_tester_test.cc rename to tensorflow/lite/micro/examples/network_tester/network_tester_test.cc index c6fba1cb9df..cebaae77486 100644 --- a/tensorflow/lite/experimental/micro/examples/network_tester/network_tester_test.cc +++ b/tensorflow/lite/micro/examples/network_tester/network_tester_test.cc @@ -10,14 +10,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/network_tester/expected_output_data.h" -#include "tensorflow/lite/experimental/micro/examples/network_tester/input_data.h" -#include "tensorflow/lite/experimental/micro/examples/network_tester/network_model.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/examples/network_tester/expected_output_data.h" +#include "tensorflow/lite/micro/examples/network_tester/input_data.h" +#include "tensorflow/lite/micro/examples/network_tester/network_model.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/BUILD b/tensorflow/lite/micro/examples/person_detection/BUILD similarity index 79% rename from tensorflow/lite/experimental/micro/examples/person_detection/BUILD rename to tensorflow/lite/micro/examples/person_detection/BUILD index 817b48e89db..cb9fdb80c33 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/BUILD +++ b/tensorflow/lite/micro/examples/person_detection/BUILD @@ -2,7 +2,7 @@ # TensorFlow Lite for Microcontrollers Vision Example. load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) @@ -56,7 +56,7 @@ cc_library( deps = [ ":model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -69,8 +69,8 @@ tflite_micro_cc_test( ":image_provider", ":model_settings", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -84,7 +84,7 @@ cc_library( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -95,7 +95,7 @@ tflite_micro_cc_test( ], deps = [ ":detection_responder", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -112,8 +112,8 @@ cc_binary( ":model_settings", ":person_detect_model_data", "//tensorflow/lite:schema_fbs_version", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/kernels:micro_ops", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/kernels:micro_ops", "//tensorflow/lite/schema:schema_fbs", ], ) diff --git a/tensorflow/lite/micro/examples/person_detection/Makefile.inc b/tensorflow/lite/micro/examples/person_detection/Makefile.inc new file mode 100644 index 00000000000..ca95f736cd4 --- /dev/null +++ b/tensorflow/lite/micro/examples/person_detection/Makefile.inc @@ -0,0 +1,68 @@ +$(eval $(call add_third_party_download,$(PERSON_MODEL_URL),$(PERSON_MODEL_MD5),person_model_grayscale,)) + +person_detection_MODEL_SRCS := \ +tensorflow/lite/micro/examples/person_detection/model_settings.cc \ +$(MAKEFILE_DIR)/downloads/person_model_grayscale/person_detect_model_data.cc + +person_detection_MODEL_HDRS := \ +tensorflow/lite/micro/examples/person_detection/model_settings.h \ +tensorflow/lite/micro/examples/person_detection/person_detect_model_data.h + +person_detection_TEST_SRCS := \ +tensorflow/lite/micro/examples/person_detection/person_detection_test.cc \ +$(MAKEFILE_DIR)/downloads/person_model_grayscale/no_person_image_data.cc \ +$(MAKEFILE_DIR)/downloads/person_model_grayscale/person_image_data.cc \ +$(person_detection_MODEL_SRCS) + +person_detection_TEST_HDRS := \ +tensorflow/lite/micro/examples/person_detection/no_person_image_data.h \ +tensorflow/lite/micro/examples/person_detection/person_image_data.h \ +$(person_detection_MODEL_HDRS) + +IMAGE_PROVIDER_TEST_SRCS := \ +tensorflow/lite/micro/examples/person_detection/image_provider.cc \ +tensorflow/lite/micro/examples/person_detection/image_provider_test.cc \ +tensorflow/lite/micro/examples/person_detection/model_settings.cc + +IMAGE_PROVIDER_TEST_HDRS := \ +tensorflow/lite/micro/examples/person_detection/image_provider.h \ +tensorflow/lite/micro/examples/person_detection/model_settings.h + +DETECTION_RESPONDER_TEST_SRCS := \ +tensorflow/lite/micro/examples/person_detection/detection_responder.cc \ +tensorflow/lite/micro/examples/person_detection/detection_responder_test.cc + +DETECTION_RESPONDER_TEST_HDRS := \ +tensorflow/lite/micro/examples/person_detection/detection_responder.h + +person_detection_SRCS := \ +tensorflow/lite/micro/examples/person_detection/detection_responder.cc \ +tensorflow/lite/micro/examples/person_detection/image_provider.cc \ +tensorflow/lite/micro/examples/person_detection/main.cc \ +tensorflow/lite/micro/examples/person_detection/main_functions.cc \ +$(person_detection_MODEL_SRCS) + +person_detection_HDRS := \ +tensorflow/lite/micro/examples/person_detection/detection_responder.h \ +tensorflow/lite/micro/examples/person_detection/image_provider.h \ +tensorflow/lite/micro/examples/person_detection/main_functions.h \ +$(person_detection_MODEL_HDRS) + +#Find any platform - specific rules for this example. +include $(wildcard tensorflow/lite/micro/examples/person_detection/*/Makefile.inc) + +# Tests loading and running a vision model. +$(eval $(call microlite_test,person_detection_test,\ +$(person_detection_TEST_SRCS),$(person_detection_TEST_HDRS))) + +# Tests the image provider module. +$(eval $(call microlite_test,image_provider_test,\ +$(IMAGE_PROVIDER_TEST_SRCS),$(IMAGE_PROVIDER_TEST_HDRS))) + +# Tests the detection responder module. +$(eval $(call microlite_test,detection_responder_test,\ +$(DETECTION_RESPONDER_TEST_SRCS),$(DETECTION_RESPONDER_TEST_HDRS))) + +# Builds a standalone object recognition binary. +$(eval $(call microlite_test,person_detection,\ +$(person_detection_SRCS),$(person_detection_HDRS))) diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/README.md b/tensorflow/lite/micro/examples/person_detection/README.md similarity index 90% rename from tensorflow/lite/experimental/micro/examples/person_detection/README.md rename to tensorflow/lite/micro/examples/person_detection/README.md index 08ba4ad35bb..4e02fdbd080 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/README.md +++ b/tensorflow/lite/micro/examples/person_detection/README.md @@ -43,7 +43,7 @@ Connect the Arducam pins as follows: ### Install the Arduino_TensorFlowLite library Download the current nightly build of the library: -[person_detection.zip](https://storage.googleapis.com/tensorflow-nightly/github/tensorflow/tensorflow/lite/experimental/micro/tools/make/gen/arduino_x86_64/prj/person_detection/tensorflow_lite.zip) +[person_detection.zip](https://storage.googleapis.com/tensorflow-nightly/github/tensorflow/tensorflow/lite/micro/tools/make/gen/arduino_x86_64/prj/person_detection/tensorflow_lite.zip) This example application is included as part of the official TensorFlow Lite Arduino library. To install it, open the Arduino library manager in @@ -188,13 +188,13 @@ The following command will download the required dependencies and then compile a binary for the SparkFun Edge: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=sparkfun_edge person_detection_bin +make -f tensorflow/lite/micro/tools/make/Makefile TARGET=sparkfun_edge person_detection_bin ``` The binary will be created in the following location: ``` -tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/person_detection.bin +tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/person_detection.bin ``` ### Sign the binary @@ -208,15 +208,15 @@ Enter the following command to set up some dummy cryptographic keys we can use for development: ``` -cp tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ -tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py +cp tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info0.py \ +tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/keys_info.py ``` Next, run the following command to create a signed binary: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ ---bin tensorflow/lite/experimental/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/person_detection.bin \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_image_blob.py \ +--bin tensorflow/lite/micro/tools/make/gen/sparkfun_edge_cortex-m4/bin/person_detection.bin \ --load-address 0xC000 \ --magic-num 0xCB \ -o main_nonsecure_ota \ @@ -228,7 +228,7 @@ command to create a final version of the file that can be used to flash our device with the bootloader script we will use in the next step: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/create_cust_wireupdate_blob.py \ --load-address 0x20000 \ --bin main_nonsecure_ota.bin \ -i 6 \ @@ -264,7 +264,7 @@ hit the button marked `RST`. Continue holding the button marked `14` while running the following command: ``` -python3 tensorflow/lite/experimental/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ +python3 tensorflow/lite/micro/tools/make/downloads/AmbiqSuite-Rel2.0.0/tools/apollo3_scripts/uart_wired_update.py \ -b ${BAUD_RATE} ${DEVICENAME} \ -r 1 \ -f main_nonsecure_wire.bin \ @@ -307,7 +307,7 @@ To compile and test this example on a desktop Linux or MacOS machine, download into the source directory from a terminal, and then run the following command: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile +make -f tensorflow/lite/micro/tools/make/Makefile ``` This will take a few minutes, and downloads frameworks the code uses like @@ -316,7 +316,7 @@ This will take a few minutes, and downloads frameworks the code uses like finished, run: ``` -make -f tensorflow/lite/experimental/micro/tools/make/Makefile test_person_detection_test +make -f tensorflow/lite/micro/tools/make/Makefile test_person_detection_test ``` You should see a series of files get compiled, followed by some logging output @@ -328,7 +328,7 @@ and checks that the network correctly identifies them. To understand how TensorFlow Lite does this, you can look at the `TestInvoke()` function in -[person_detection_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro/examples/person_detection/person_detection_test.cc). +[person_detection_test.cc](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro/examples/person_detection/person_detection_test.cc). It's a fairly small amount of code, creating an interpreter, getting a handle to a model that's been compiled into the program, and then invoking the interpreter with the model and sample inputs. diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/apollo3evb/image_provider.cc b/tensorflow/lite/micro/examples/person_detection/apollo3evb/image_provider.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/person_detection/apollo3evb/image_provider.cc rename to tensorflow/lite/micro/examples/person_detection/apollo3evb/image_provider.cc index b6a39083870..64cf9871d5a 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/apollo3evb/image_provider.cc +++ b/tensorflow/lite/micro/examples/person_detection/apollo3evb/image_provider.cc @@ -13,13 +13,13 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h" +#include "tensorflow/lite/micro/examples/person_detection/image_provider.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h" // These are headers from Ambiq's Apollo3 SDK. #include "am_bsp.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/arduino/detection_responder.cc b/tensorflow/lite/micro/examples/person_detection/arduino/detection_responder.cc similarity index 95% rename from tensorflow/lite/experimental/micro/examples/person_detection/arduino/detection_responder.cc rename to tensorflow/lite/micro/examples/person_detection/arduino/detection_responder.cc index 2763e454c09..5c832530e4f 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/arduino/detection_responder.cc +++ b/tensorflow/lite/micro/examples/person_detection/arduino/detection_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h" +#include "tensorflow/lite/micro/examples/person_detection/detection_responder.h" #include "Arduino.h" diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/arduino/image_provider.cc b/tensorflow/lite/micro/examples/person_detection/arduino/image_provider.cc similarity index 99% rename from tensorflow/lite/experimental/micro/examples/person_detection/arduino/image_provider.cc rename to tensorflow/lite/micro/examples/person_detection/arduino/image_provider.cc index 23657db075b..87774ad9f79 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/arduino/image_provider.cc +++ b/tensorflow/lite/micro/examples/person_detection/arduino/image_provider.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h" +#include "tensorflow/lite/micro/examples/person_detection/image_provider.h" /* * The sample requires the following third-party libraries to be installed and diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/arduino/main.cc b/tensorflow/lite/micro/examples/person_detection/arduino/main.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/person_detection/arduino/main.cc rename to tensorflow/lite/micro/examples/person_detection/arduino/main.cc index 6d962d91240..feaf350d51f 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/arduino/main.cc +++ b/tensorflow/lite/micro/examples/person_detection/arduino/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/main_functions.h" +#include "tensorflow/lite/micro/examples/person_detection/main_functions.h" // Arduino automatically calls the setup() and loop() functions in a sketch, so // where other systems need their own main routine in this file, it can be left diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.cc b/tensorflow/lite/micro/examples/person_detection/detection_responder.cc similarity index 92% rename from tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.cc rename to tensorflow/lite/micro/examples/person_detection/detection_responder.cc index 1f39bc316a5..02af9f94c46 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.cc +++ b/tensorflow/lite/micro/examples/person_detection/detection_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h" +#include "tensorflow/lite/micro/examples/person_detection/detection_responder.h" // This dummy implementation writes person and no person scores to the error // console. Real applications will want to take some custom action instead, and diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h b/tensorflow/lite/micro/examples/person_detection/detection_responder.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h rename to tensorflow/lite/micro/examples/person_detection/detection_responder.h index 98323d5e623..a7c709daa38 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h +++ b/tensorflow/lite/micro/examples/person_detection/detection_responder.h @@ -16,11 +16,11 @@ limitations under the License. // Provides an interface to take an action based on the output from the person // detection model. -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_DETECTION_RESPONDER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_DETECTION_RESPONDER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_DETECTION_RESPONDER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_DETECTION_RESPONDER_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // Called every time the results of a person detection run are available. The // `person_score` has the numerical confidence that the captured image contains @@ -31,4 +31,4 @@ limitations under the License. void RespondToDetection(tflite::ErrorReporter* error_reporter, uint8_t person_score, uint8_t no_person_score); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_DETECTION_RESPONDER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_DETECTION_RESPONDER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/detection_responder_test.cc b/tensorflow/lite/micro/examples/person_detection/detection_responder_test.cc similarity index 83% rename from tensorflow/lite/experimental/micro/examples/person_detection/detection_responder_test.cc rename to tensorflow/lite/micro/examples/person_detection/detection_responder_test.cc index 69edc02d791..6ef17d38dc9 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/detection_responder_test.cc +++ b/tensorflow/lite/micro/examples/person_detection/detection_responder_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h" +#include "tensorflow/lite/micro/examples/person_detection/detection_responder.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.c b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.c similarity index 100% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.c rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.c diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.h b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.h similarity index 98% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.h rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.h index f3674bcd9db..718a8fe3715 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.h +++ b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_H_ #ifdef __cplusplus extern "C" { @@ -416,4 +416,4 @@ uint32_t hm01b0_single_frame_capture(hm01b0_cfg_t *psCfg); } #endif -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h similarity index 96% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h index 1c1619f38e9..32897ca678c 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h +++ b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_RAW8_QVGA_8BITS_LSB_5FPS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_RAW8_QVGA_8BITS_LSB_5FPS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_RAW8_QVGA_8BITS_LSB_5FPS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_RAW8_QVGA_8BITS_LSB_5FPS_H_ #include "HM01B0.h" @@ -507,4 +507,4 @@ const hm_script_t sHM01B0InitScript[] = { // ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; }; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_RAW8_QVGA_8BITS_LSB_5FPS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_RAW8_QVGA_8BITS_LSB_5FPS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h similarity index 80% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h index 29777236566..712b232d9b7 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h +++ b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_WALKING1S_01_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_WALKING1S_01_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_WALKING1S_01_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_WALKING1S_01_H_ #include "HM01B0.h" @@ -53,4 +53,4 @@ const hm_script_t sHM01b0TestModeScript_Walking1s[] = { }, // W 24 0104 01 2 1 ; }; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_WALKING1S_01_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_WALKING1S_01_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.txt b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.txt similarity index 100% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.txt rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.txt diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.c b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.c similarity index 100% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.c rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.c diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.h b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.h similarity index 83% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.h rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.h index 57a8d2331c4..61b3699d4b3 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.h +++ b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_DEBUG_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_DEBUG_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_DEBUG_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_DEBUG_H_ #ifdef __cplusplus extern "C" { @@ -46,4 +46,4 @@ void hm01b0_framebuffer_dump(uint8_t* frame, uint32_t len); } #endif -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_DEBUG_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_DEBUG_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.c b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.c similarity index 100% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.c rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.c diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.h b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.h similarity index 84% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.h rename to tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.h index ddb695ee9ea..0c3f9126e9d 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.h +++ b/tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_OPTIMIZED_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_OPTIMIZED_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_OPTIMIZED_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_OPTIMIZED_H_ #ifdef __cplusplus extern "C" { @@ -47,4 +47,4 @@ uint32_t hm01b0_blocking_read_oneframe_scaled(hm01b0_cfg_t* psCfg, } #endif -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_OPTIMIZED_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_HM01B0_OPTIMIZED_H_ diff --git a/tensorflow/lite/micro/examples/person_detection/himax_driver/Makefile.inc b/tensorflow/lite/micro/examples/person_detection/himax_driver/Makefile.inc new file mode 100644 index 00000000000..beab55bac0e --- /dev/null +++ b/tensorflow/lite/micro/examples/person_detection/himax_driver/Makefile.inc @@ -0,0 +1,14 @@ +ifeq ($(TARGET),$(filter $(TARGET),apollo3evb sparkfun_edge)) + person_detection_SRCS += \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.c \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.c \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.c + + person_detection_HDRS += \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.h \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.h \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.h \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h \ + tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_Walking1s_01.h \ + tensorflow/lite/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h +endif diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h b/tensorflow/lite/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h similarity index 82% rename from tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h rename to tensorflow/lite/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h index 21c100c2f57..0f0123529cc 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h +++ b/tensorflow/lite/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_PLATFORM_SPARKFUN_EDGE_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_PLATFORM_SPARKFUN_EDGE_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_PLATFORM_SPARKFUN_EDGE_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_PLATFORM_SPARKFUN_EDGE_H_ #ifdef __cplusplus extern "C" { @@ -51,4 +51,4 @@ extern "C" { } #endif -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_PLATFORM_SPARKFUN_EDGE_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_HIMAX_DRIVER_PLATFORM_SPARKFUN_EDGE_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/image_provider.cc b/tensorflow/lite/micro/examples/person_detection/image_provider.cc similarity index 84% rename from tensorflow/lite/experimental/micro/examples/person_detection/image_provider.cc rename to tensorflow/lite/micro/examples/person_detection/image_provider.cc index 517035006be..caf0faa41b4 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/image_provider.cc +++ b/tensorflow/lite/micro/examples/person_detection/image_provider.cc @@ -13,8 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h" +#include "tensorflow/lite/micro/examples/person_detection/image_provider.h" + +#include "tensorflow/lite/micro/examples/person_detection/model_settings.h" TfLiteStatus GetImage(tflite::ErrorReporter* error_reporter, int image_width, int image_height, int channels, uint8_t* image_data) { diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h b/tensorflow/lite/micro/examples/person_detection/image_provider.h similarity index 84% rename from tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h rename to tensorflow/lite/micro/examples/person_detection/image_provider.h index af738ac77c4..cb310996cac 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h +++ b/tensorflow/lite/micro/examples/person_detection/image_provider.h @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_IMAGE_PROVIDER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_IMAGE_PROVIDER_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_IMAGE_PROVIDER_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_IMAGE_PROVIDER_H_ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" // This is an abstraction around an image source like a camera, and is // expected to return 8-bit sample data. The assumption is that this will be @@ -36,4 +36,4 @@ limitations under the License. TfLiteStatus GetImage(tflite::ErrorReporter* error_reporter, int image_width, int image_height, int channels, uint8_t* image_data); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_IMAGE_PROVIDER_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_IMAGE_PROVIDER_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/image_provider_test.cc b/tensorflow/lite/micro/examples/person_detection/image_provider_test.cc similarity index 81% rename from tensorflow/lite/experimental/micro/examples/person_detection/image_provider_test.cc rename to tensorflow/lite/micro/examples/person_detection/image_provider_test.cc index 45bb5339949..73695035d14 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/image_provider_test.cc +++ b/tensorflow/lite/micro/examples/person_detection/image_provider_test.cc @@ -13,14 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h" +#include "tensorflow/lite/micro/examples/person_detection/image_provider.h" #include #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/person_detection/model_settings.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/main.cc b/tensorflow/lite/micro/examples/person_detection/main.cc similarity index 92% rename from tensorflow/lite/experimental/micro/examples/person_detection/main.cc rename to tensorflow/lite/micro/examples/person_detection/main.cc index d2c73eab583..b53d3665eb4 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/main.cc +++ b/tensorflow/lite/micro/examples/person_detection/main.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/main_functions.h" +#include "tensorflow/lite/micro/examples/person_detection/main_functions.h" // This is the default main used on systems that have the standard C entry // point. Other devices (for example FreeRTOS or ESP32) that have different diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/main_functions.cc b/tensorflow/lite/micro/examples/person_detection/main_functions.cc similarity index 85% rename from tensorflow/lite/experimental/micro/examples/person_detection/main_functions.cc rename to tensorflow/lite/micro/examples/person_detection/main_functions.cc index 90ed1328ce8..ac874ebfad4 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/main_functions.cc +++ b/tensorflow/lite/micro/examples/person_detection/main_functions.cc @@ -13,16 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/main_functions.h" +#include "tensorflow/lite/micro/examples/person_detection/main_functions.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/person_detect_model_data.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/examples/person_detection/detection_responder.h" +#include "tensorflow/lite/micro/examples/person_detection/image_provider.h" +#include "tensorflow/lite/micro/examples/person_detection/model_settings.h" +#include "tensorflow/lite/micro/examples/person_detection/person_detect_model_data.h" +#include "tensorflow/lite/micro/kernels/micro_ops.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/main_functions.h b/tensorflow/lite/micro/examples/person_detection/main_functions.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/person_detection/main_functions.h rename to tensorflow/lite/micro/examples/person_detection/main_functions.h index a1f49454326..2120ea92ddb 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/main_functions.h +++ b/tensorflow/lite/micro/examples/person_detection/main_functions.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_MAIN_FUNCTIONS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_MAIN_FUNCTIONS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_MAIN_FUNCTIONS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_MAIN_FUNCTIONS_H_ // Initializes all data needed for the example. The name is important, and needs // to be setup() for Arduino compatibility. @@ -25,4 +25,4 @@ void setup(); // compatibility. void loop(); -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_MAIN_FUNCTIONS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_MAIN_FUNCTIONS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/model_settings.cc b/tensorflow/lite/micro/examples/person_detection/model_settings.cc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/person_detection/model_settings.cc rename to tensorflow/lite/micro/examples/person_detection/model_settings.cc index 24ce3523193..99a1899e22b 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/model_settings.cc +++ b/tensorflow/lite/micro/examples/person_detection/model_settings.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h" +#include "tensorflow/lite/micro/examples/person_detection/model_settings.h" const char* kCategoryLabels[kCategoryCount] = { "unused", diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h b/tensorflow/lite/micro/examples/person_detection/model_settings.h similarity index 82% rename from tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h rename to tensorflow/lite/micro/examples/person_detection/model_settings.h index 408b59fbe8a..e666f824c6c 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h +++ b/tensorflow/lite/micro/examples/person_detection/model_settings.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_MODEL_SETTINGS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_MODEL_SETTINGS_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_MODEL_SETTINGS_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_MODEL_SETTINGS_H_ // Keeping these as constant expressions allow us to allocate fixed-sized arrays // on the stack for our working memory. @@ -32,4 +32,4 @@ constexpr int kPersonIndex = 1; constexpr int kNotAPersonIndex = 2; extern const char* kCategoryLabels[kCategoryCount]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_MODEL_SETTINGS_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_MODEL_SETTINGS_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/no_person_image_data.h b/tensorflow/lite/micro/examples/person_detection/no_person_image_data.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/person_detection/no_person_image_data.h rename to tensorflow/lite/micro/examples/person_detection/no_person_image_data.h index 64cc05bfe36..4e026af01c2 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/no_person_image_data.h +++ b/tensorflow/lite/micro/examples/person_detection/no_person_image_data.h @@ -19,12 +19,12 @@ limitations under the License. // Skip the 54 byte bmp3 header and add the reset of the bytes to a C array: // xxd -s 54 -i /tmp/noperson.bmp3 > /tmp/noperson.cc -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_NO_PERSON_IMAGE_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_NO_PERSON_IMAGE_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_NO_PERSON_IMAGE_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_NO_PERSON_IMAGE_DATA_H_ #include extern const int g_no_person_data_size; extern const uint8_t g_no_person_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_NO_PERSON_IMAGE_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_NO_PERSON_IMAGE_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/person_detect_model_data.h b/tensorflow/lite/micro/examples/person_detection/person_detect_model_data.h similarity index 78% rename from tensorflow/lite/experimental/micro/examples/person_detection/person_detect_model_data.h rename to tensorflow/lite/micro/examples/person_detection/person_detect_model_data.h index 5642203d6ac..86471b30431 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/person_detect_model_data.h +++ b/tensorflow/lite/micro/examples/person_detection/person_detect_model_data.h @@ -18,10 +18,10 @@ limitations under the License. // don't have a file system. It was created using the command: // xxd -i person_detect.tflite > person_detect_model_data.cc -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_DETECT_MODEL_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_DETECT_MODEL_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_DETECT_MODEL_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_DETECT_MODEL_DATA_H_ extern const unsigned char g_person_detect_model_data[]; extern const int g_person_detect_model_data_len; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_DETECT_MODEL_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_DETECT_MODEL_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/person_detection_test.cc b/tensorflow/lite/micro/examples/person_detection/person_detection_test.cc similarity index 89% rename from tensorflow/lite/experimental/micro/examples/person_detection/person_detection_test.cc rename to tensorflow/lite/micro/examples/person_detection/person_detection_test.cc index 12fa9b977e4..58694e9a58b 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/person_detection_test.cc +++ b/tensorflow/lite/micro/examples/person_detection/person_detection_test.cc @@ -14,15 +14,15 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/model_settings.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/no_person_image_data.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/person_detect_model_data.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/person_image_data.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/examples/person_detection/model_settings.h" +#include "tensorflow/lite/micro/examples/person_detection/no_person_image_data.h" +#include "tensorflow/lite/micro/examples/person_detection/person_detect_model_data.h" +#include "tensorflow/lite/micro/examples/person_detection/person_image_data.h" +#include "tensorflow/lite/micro/kernels/micro_ops.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/version.h" diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/person_image_data.h b/tensorflow/lite/micro/examples/person_detection/person_image_data.h similarity index 79% rename from tensorflow/lite/experimental/micro/examples/person_detection/person_image_data.h rename to tensorflow/lite/micro/examples/person_detection/person_image_data.h index 02ac2fd60c4..1e677ed45fa 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/person_image_data.h +++ b/tensorflow/lite/micro/examples/person_detection/person_image_data.h @@ -19,12 +19,12 @@ limitations under the License. // Skip the 54 byte bmp3 header and add the reset of the bytes to a C array: // xxd -s 54 -i /tmp/person.bmp3 > /tmp/person.cc -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_IMAGE_DATA_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_IMAGE_DATA_H_ +#ifndef TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_IMAGE_DATA_H_ +#define TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_IMAGE_DATA_H_ #include extern const int g_person_data_size; extern const uint8_t g_person_data[]; -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_IMAGE_DATA_H_ +#endif // TENSORFLOW_LITE_MICRO_EXAMPLES_PERSON_DETECTION_PERSON_IMAGE_DATA_H_ diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/sparkfun_edge/detection_responder.cc b/tensorflow/lite/micro/examples/person_detection/sparkfun_edge/detection_responder.cc similarity index 95% rename from tensorflow/lite/experimental/micro/examples/person_detection/sparkfun_edge/detection_responder.cc rename to tensorflow/lite/micro/examples/person_detection/sparkfun_edge/detection_responder.cc index 2f0ec69e86a..bf7f4112d48 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/sparkfun_edge/detection_responder.cc +++ b/tensorflow/lite/micro/examples/person_detection/sparkfun_edge/detection_responder.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/detection_responder.h" +#include "tensorflow/lite/micro/examples/person_detection/detection_responder.h" #include "am_bsp.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/sparkfun_edge/image_provider.cc b/tensorflow/lite/micro/examples/person_detection/sparkfun_edge/image_provider.cc similarity index 90% rename from tensorflow/lite/experimental/micro/examples/person_detection/sparkfun_edge/image_provider.cc rename to tensorflow/lite/micro/examples/person_detection/sparkfun_edge/image_provider.cc index 46001789a78..ec38d75064f 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/sparkfun_edge/image_provider.cc +++ b/tensorflow/lite/micro/examples/person_detection/sparkfun_edge/image_provider.cc @@ -13,13 +13,13 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/examples/person_detection/image_provider.h" +#include "tensorflow/lite/micro/examples/person_detection/image_provider.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_debug.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/HM01B0_optimized.h" -#include "tensorflow/lite/experimental/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_RAW8_QVGA_8bits_lsb_5fps.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_debug.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/HM01B0_optimized.h" +#include "tensorflow/lite/micro/examples/person_detection/himax_driver/platform_Sparkfun_Edge.h" // These are headers from Ambiq's Apollo3 SDK. #include "am_bsp.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/training_a_model.md b/tensorflow/lite/micro/examples/person_detection/training_a_model.md similarity index 100% rename from tensorflow/lite/experimental/micro/examples/person_detection/training_a_model.md rename to tensorflow/lite/micro/examples/person_detection/training_a_model.md diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/utils/BUILD b/tensorflow/lite/micro/examples/person_detection/utils/BUILD similarity index 100% rename from tensorflow/lite/experimental/micro/examples/person_detection/utils/BUILD rename to tensorflow/lite/micro/examples/person_detection/utils/BUILD diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/utils/raw_to_bitmap.py b/tensorflow/lite/micro/examples/person_detection/utils/raw_to_bitmap.py similarity index 100% rename from tensorflow/lite/experimental/micro/examples/person_detection/utils/raw_to_bitmap.py rename to tensorflow/lite/micro/examples/person_detection/utils/raw_to_bitmap.py diff --git a/tensorflow/lite/experimental/micro/examples/person_detection/utils/raw_to_bitmap_test.py b/tensorflow/lite/micro/examples/person_detection/utils/raw_to_bitmap_test.py similarity index 95% rename from tensorflow/lite/experimental/micro/examples/person_detection/utils/raw_to_bitmap_test.py rename to tensorflow/lite/micro/examples/person_detection/utils/raw_to_bitmap_test.py index 9dddf3f3fca..cc3af1bc1eb 100644 --- a/tensorflow/lite/experimental/micro/examples/person_detection/utils/raw_to_bitmap_test.py +++ b/tensorflow/lite/micro/examples/person_detection/utils/raw_to_bitmap_test.py @@ -22,8 +22,8 @@ import io import numpy as np -from tensorflow.lite.experimental.micro.examples.person_detection.utils.raw_to_bitmap import parse_file -from tensorflow.lite.experimental.micro.examples.person_detection.utils.raw_to_bitmap import reshape_bitmaps +from tensorflow.lite.micro.examples.person_detection.utils.raw_to_bitmap import parse_file +from tensorflow.lite.micro.examples.person_detection.utils.raw_to_bitmap import reshape_bitmaps from tensorflow.python.platform import test _RGB_RAW = u""" diff --git a/tensorflow/lite/experimental/micro/kernels/BUILD b/tensorflow/lite/micro/kernels/BUILD similarity index 67% rename from tensorflow/lite/experimental/micro/kernels/BUILD rename to tensorflow/lite/micro/kernels/BUILD index 5ce2c0741e5..8c03ca7266b 100644 --- a/tensorflow/lite/experimental/micro/kernels/BUILD +++ b/tensorflow/lite/micro/kernels/BUILD @@ -1,6 +1,6 @@ load("//tensorflow/lite:build_def.bzl", "tflite_copts") load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) @@ -11,6 +11,7 @@ package( licenses = ["notice"], # Apache 2.0 ) +# LINT.IfChange(micro_ops) cc_library( name = "micro_ops", srcs = [ @@ -32,6 +33,7 @@ cc_library( "mul.cc", "neg.cc", "pack.cc", + "pad.cc", "pooling.cc", "prelu.cc", "quantize.cc", @@ -49,7 +51,6 @@ cc_library( ":activation_utils", ":micro_utils", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_utils", "//tensorflow/lite/kernels:kernel_util", "//tensorflow/lite/kernels:op_macros", "//tensorflow/lite/kernels:padding", @@ -58,8 +59,10 @@ cc_library( "//tensorflow/lite/kernels/internal:reference_base", "//tensorflow/lite/kernels/internal:tensor", "//tensorflow/lite/kernels/internal:types", + "//tensorflow/lite/micro:micro_utils", ], ) +# LINT.ThenChange(//tensorflow/lite/micro/kernels/BUILD:portable_optimized_micro_ops) cc_library( name = "all_ops_resolver", @@ -72,10 +75,11 @@ cc_library( copts = tflite_copts(), deps = [ ":micro_ops", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) +# LINT.IfChange(portable_optimized_micro_ops) cc_library( name = "portable_optimized_micro_ops", srcs = [ @@ -96,6 +100,7 @@ cc_library( "mul.cc", "neg.cc", "pack.cc", + "pad.cc", "pooling.cc", "portable_optimized/depthwise_conv.cc", "prelu.cc", @@ -114,7 +119,6 @@ cc_library( ":activation_utils", ":micro_utils", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_utils", "//tensorflow/lite/kernels:kernel_util", "//tensorflow/lite/kernels:op_macros", "//tensorflow/lite/kernels:padding", @@ -123,9 +127,11 @@ cc_library( "//tensorflow/lite/kernels/internal:reference_base", "//tensorflow/lite/kernels/internal:tensor", "//tensorflow/lite/kernels/internal:types", + "//tensorflow/lite/micro:micro_utils", ], ) +# LINT.ThenChange(//tensorflow/lite/micro/kernels/BUILD:micro_ops) cc_library( name = "portable_optimized_ops_resolver", srcs = [ @@ -137,7 +143,7 @@ cc_library( copts = tflite_copts(), deps = [ ":portable_optimized_micro_ops", - "//tensorflow/lite/experimental/micro:micro_framework", + "//tensorflow/lite/micro:micro_framework", ], ) @@ -151,8 +157,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -164,8 +170,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -177,8 +183,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -190,8 +196,8 @@ tflite_micro_cc_test( deps = [ ":portable_optimized_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -203,8 +209,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -216,8 +222,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -229,8 +235,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -242,8 +248,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -255,9 +261,9 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro:micro_utils", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro:micro_utils", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -269,8 +275,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -282,8 +288,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -295,8 +301,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -308,8 +314,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -321,8 +327,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -334,8 +340,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -348,8 +354,8 @@ tflite_micro_cc_test( ":all_ops_resolver", ":micro_utils", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -361,9 +367,9 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/kernels:micro_utils", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/kernels:micro_utils", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -375,8 +381,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -388,8 +394,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -401,8 +407,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -414,8 +420,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -427,8 +433,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -440,8 +446,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -453,7 +459,7 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -464,8 +470,8 @@ tflite_micro_cc_test( ], deps = [ "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro/testing:micro_test", "//tensorflow/lite/kernels/internal:quantization_util", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -483,9 +489,9 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/kernels:micro_utils", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/kernels:micro_utils", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -497,9 +503,9 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/kernels:micro_utils", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/kernels:micro_utils", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -516,10 +522,10 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro:micro_utils", - "//tensorflow/lite/experimental/micro/testing:micro_test", "//tensorflow/lite/kernels/internal:tensor", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro:micro_utils", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -531,8 +537,8 @@ tflite_micro_cc_test( deps = [ ":all_ops_resolver", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -545,7 +551,20 @@ tflite_micro_cc_test( ":all_ops_resolver", ":micro_utils", "//tensorflow/lite/c:common", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", + ], +) + +tflite_micro_cc_test( + name = "pad_test", + srcs = [ + "pad_test.cc", + ], + deps = [ + ":all_ops_resolver", + "//tensorflow/lite/c:common", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro/testing:micro_test", ], ) diff --git a/tensorflow/lite/experimental/micro/kernels/activation_utils.h b/tensorflow/lite/micro/kernels/activation_utils.h similarity index 87% rename from tensorflow/lite/experimental/micro/kernels/activation_utils.h rename to tensorflow/lite/micro/kernels/activation_utils.h index c6d8c3015f7..b4cf2747370 100644 --- a/tensorflow/lite/experimental/micro/kernels/activation_utils.h +++ b/tensorflow/lite/micro/kernels/activation_utils.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_ACTIVATION_UTILS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_ACTIVATION_UTILS_H_ +#ifndef TENSORFLOW_LITE_MICRO_KERNELS_ACTIVATION_UTILS_H_ +#define TENSORFLOW_LITE_MICRO_KERNELS_ACTIVATION_UTILS_H_ #include #include @@ -52,4 +52,4 @@ inline float ActivationValFloat(TfLiteFusedActivation act, float a) { } // namespace ops } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_ACTIVATION_UTILS_H_ +#endif // TENSORFLOW_LITE_MICRO_KERNELS_ACTIVATION_UTILS_H_ diff --git a/tensorflow/lite/experimental/micro/kernels/activations.cc b/tensorflow/lite/micro/kernels/activations.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/activations.cc rename to tensorflow/lite/micro/kernels/activations.cc index 29320587fe1..5f04691b5da 100644 --- a/tensorflow/lite/experimental/micro/kernels/activations.cc +++ b/tensorflow/lite/micro/kernels/activations.cc @@ -15,12 +15,12 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/micro_utils.h" #include "tensorflow/lite/kernels/internal/common.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/kernels/op_macros.h" +#include "tensorflow/lite/micro/micro_utils.h" namespace tflite { namespace ops { diff --git a/tensorflow/lite/experimental/micro/kernels/activations_test.cc b/tensorflow/lite/micro/kernels/activations_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/activations_test.cc rename to tensorflow/lite/micro/kernels/activations_test.cc index f75c8207b65..cd375b00b9b 100644 --- a/tensorflow/lite/experimental/micro/kernels/activations_test.cc +++ b/tensorflow/lite/micro/kernels/activations_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/add.cc b/tensorflow/lite/micro/kernels/add.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/add.cc rename to tensorflow/lite/micro/kernels/add.cc diff --git a/tensorflow/lite/experimental/micro/kernels/add_test.cc b/tensorflow/lite/micro/kernels/add_test.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/add_test.cc rename to tensorflow/lite/micro/kernels/add_test.cc index 9e16c853dfc..1cb60d99df8 100644 --- a/tensorflow/lite/experimental/micro/kernels/add_test.cc +++ b/tensorflow/lite/micro/kernels/add_test.cc @@ -17,9 +17,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.cc b/tensorflow/lite/micro/kernels/all_ops_resolver.cc similarity index 94% rename from tensorflow/lite/experimental/micro/kernels/all_ops_resolver.cc rename to tensorflow/lite/micro/kernels/all_ops_resolver.cc index b1cd8b17af0..c86c3bce340 100644 --- a/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.cc +++ b/tensorflow/lite/micro/kernels/all_ops_resolver.cc @@ -10,9 +10,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_ops.h" +#include "tensorflow/lite/micro/kernels/micro_ops.h" namespace tflite { namespace ops { @@ -21,8 +21,6 @@ namespace micro { // Register each supported op with: // AddBuiltin(, , [min version], [max version]) AllOpsResolver::AllOpsResolver() { - AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(), 1, - 3); AddBuiltin(BuiltinOperator_FULLY_CONNECTED, Register_FULLY_CONNECTED(), 1, 4); AddBuiltin(BuiltinOperator_MAX_POOL_2D, Register_MAX_POOL_2D()); AddBuiltin(BuiltinOperator_SOFTMAX, Register_SOFTMAX()); @@ -30,6 +28,8 @@ AllOpsResolver::AllOpsResolver() { AddBuiltin(BuiltinOperator_SVDF, Register_SVDF()); AddBuiltin(BuiltinOperator_CONV_2D, Register_CONV_2D(), 1, 3); AddBuiltin(BuiltinOperator_CONCATENATION, Register_CONCATENATION(), 1, 3); + AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(), 1, + 3); AddBuiltin(BuiltinOperator_AVERAGE_POOL_2D, Register_AVERAGE_POOL_2D()); AddBuiltin(BuiltinOperator_ABS, Register_ABS()); AddBuiltin(BuiltinOperator_SIN, Register_SIN()); @@ -58,6 +58,8 @@ AllOpsResolver::AllOpsResolver() { AddBuiltin(BuiltinOperator_ROUND, Register_ROUND()); AddBuiltin(BuiltinOperator_STRIDED_SLICE, Register_STRIDED_SLICE()); AddBuiltin(BuiltinOperator_PACK, Register_PACK()); + AddBuiltin(BuiltinOperator_PAD, Register_PAD()); + AddBuiltin(BuiltinOperator_PADV2, Register_PADV2()); AddBuiltin(BuiltinOperator_SPLIT, Register_SPLIT(), 1, 3); AddBuiltin(BuiltinOperator_UNPACK, Register_UNPACK()); AddBuiltin(BuiltinOperator_NEG, Register_NEG()); diff --git a/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h b/tensorflow/lite/micro/kernels/all_ops_resolver.h similarity index 72% rename from tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h rename to tensorflow/lite/micro/kernels/all_ops_resolver.h index b9ba8c88262..26bb03230ed 100644 --- a/tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h +++ b/tensorflow/lite/micro/kernels/all_ops_resolver.h @@ -9,11 +9,11 @@ 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_LITE_EXPERIMENTAL_MICRO_KERNELS_ALL_OPS_RESOLVER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_ALL_OPS_RESOLVER_H_ +#ifndef TENSORFLOW_LITE_MICRO_KERNELS_ALL_OPS_RESOLVER_H_ +#define TENSORFLOW_LITE_MICRO_KERNELS_ALL_OPS_RESOLVER_H_ -#include "tensorflow/lite/experimental/micro/compatibility.h" -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/compatibility.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" namespace tflite { namespace ops { @@ -31,4 +31,4 @@ class AllOpsResolver : public MicroMutableOpResolver { } // namespace ops } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_ALL_OPS_RESOLVER_H_ +#endif // TENSORFLOW_LITE_MICRO_KERNELS_ALL_OPS_RESOLVER_H_ diff --git a/tensorflow/lite/experimental/micro/kernels/arg_min_max.cc b/tensorflow/lite/micro/kernels/arg_min_max.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/arg_min_max.cc rename to tensorflow/lite/micro/kernels/arg_min_max.cc index 4018597446a..9698576a0e9 100644 --- a/tensorflow/lite/experimental/micro/kernels/arg_min_max.cc +++ b/tensorflow/lite/micro/kernels/arg_min_max.cc @@ -17,9 +17,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/micro_utils.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/micro/kernels/micro_utils.h" namespace tflite { namespace ops { diff --git a/tensorflow/lite/experimental/micro/kernels/arg_min_max_test.cc b/tensorflow/lite/micro/kernels/arg_min_max_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/arg_min_max_test.cc rename to tensorflow/lite/micro/kernels/arg_min_max_test.cc index 8808dd6a81a..fc4110fc3fd 100644 --- a/tensorflow/lite/experimental/micro/kernels/arg_min_max_test.cc +++ b/tensorflow/lite/micro/kernels/arg_min_max_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/ceil.cc b/tensorflow/lite/micro/kernels/ceil.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/ceil.cc rename to tensorflow/lite/micro/kernels/ceil.cc diff --git a/tensorflow/lite/experimental/micro/kernels/ceil_test.cc b/tensorflow/lite/micro/kernels/ceil_test.cc similarity index 94% rename from tensorflow/lite/experimental/micro/kernels/ceil_test.cc rename to tensorflow/lite/micro/kernels/ceil_test.cc index 8aacc18493b..6802aac43c7 100644 --- a/tensorflow/lite/experimental/micro/kernels/ceil_test.cc +++ b/tensorflow/lite/micro/kernels/ceil_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/conv.cc b/tensorflow/lite/micro/kernels/cmsis-nn/conv.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/cmsis-nn/conv.cc rename to tensorflow/lite/micro/kernels/cmsis-nn/conv.cc index f639b08beb8..84146ffa177 100644 --- a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/conv.cc +++ b/tensorflow/lite/micro/kernels/cmsis-nn/conv.cc @@ -18,13 +18,13 @@ limitations under the License. #include "arm_nnfunctions.h" #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.h" #include "tensorflow/lite/kernels/internal/common.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/reference/integer_ops/conv.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/kernels/padding.h" +#include "tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.h" namespace tflite { namespace ops { diff --git a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/depthwise_conv.cc b/tensorflow/lite/micro/kernels/cmsis-nn/depthwise_conv.cc similarity index 96% rename from tensorflow/lite/experimental/micro/kernels/cmsis-nn/depthwise_conv.cc rename to tensorflow/lite/micro/kernels/cmsis-nn/depthwise_conv.cc index 74692e0ab23..850ad2388d6 100644 --- a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/cmsis-nn/depthwise_conv.cc @@ -18,7 +18,6 @@ limitations under the License. #include "arm_nnfunctions.h" #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.h" #include "tensorflow/lite/kernels/internal/common.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/reference/depthwiseconv_float.h" @@ -26,6 +25,7 @@ limitations under the License. #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/kernels/padding.h" +#include "tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.h" namespace tflite { namespace ops { @@ -307,7 +307,8 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { OpData data; - if (input->type != kTfLiteFloat32) { + // All per-channel quantized tensors need valid zero point and scale arrays. + if (input->type == kTfLiteInt8) { TF_LITE_ENSURE_EQ(context, filter->quantization.type, kTfLiteAffineQuantization); @@ -316,6 +317,13 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { filter->quantization.params); TF_LITE_ENSURE(context, affine_quantization); TF_LITE_ENSURE(context, affine_quantization->scale); + TF_LITE_ENSURE(context, affine_quantization->zero_point); + // Depthwise conv is quantized along dimension 3: + // https://www.tensorflow.org/lite/performance/quantization_spec + TF_LITE_ENSURE_EQ(context, filter->dims->data[3], + affine_quantization->scale->size); + TF_LITE_ENSURE_EQ(context, filter->dims->data[3], + affine_quantization->zero_point->size); } TF_LITE_ENSURE_STATUS(CalculateOpData(context, node, params, width, height, diff --git a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/fully_connected.cc b/tensorflow/lite/micro/kernels/cmsis-nn/fully_connected.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/cmsis-nn/fully_connected.cc rename to tensorflow/lite/micro/kernels/cmsis-nn/fully_connected.cc index 49836f36ed1..b3ae24e6e46 100644 --- a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/fully_connected.cc +++ b/tensorflow/lite/micro/kernels/cmsis-nn/fully_connected.cc @@ -18,12 +18,12 @@ limitations under the License. #include "arm_nnfunctions.h" #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.h" #include "tensorflow/lite/kernels/internal/common.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/reference/integer_ops/fully_connected.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.h" namespace tflite { namespace ops { @@ -89,7 +89,6 @@ TfLiteStatus EvalQuantizedInt8(TfLiteContext* context, TfLiteNode* node, const TfLiteTensor* input, const TfLiteTensor* filter, const TfLiteTensor* bias, TfLiteTensor* output) { -#if defined(ARM_MATH_DSP) && defined(ARM_MATH_LOOPUNROLL) RuntimeShape output_shape = GetTensorShape(output); const int batches = output_shape.Dims(0); const int output_depth = output_shape.Dims(1); @@ -97,6 +96,7 @@ TfLiteStatus EvalQuantizedInt8(TfLiteContext* context, TfLiteNode* node, const int filter_dim_count = filter_shape.DimensionsCount(); const int accum_depth = filter_shape.Dims(filter_dim_count - 1); +#if defined(ARM_MATH_DSP) && defined(ARM_MATH_LOOPUNROLL) const int32_t buf_size = arm_fully_connected_s8_get_buffer_size(accum_depth); int16_t* buf = nullptr; TF_LITE_ENSURE_OK(context, get_cmsis_scratch_buffer(context, &buf, buf_size)); @@ -129,7 +129,6 @@ TfLiteStatus EvalQuantizedInt8(TfLiteContext* context, TfLiteNode* node, GetTensorShape(filter), GetTensorData(filter), GetTensorShape(bias), GetTensorData(bias), GetTensorShape(output), GetTensorData(output)); - #endif return kTfLiteOk; } diff --git a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/pooling.cc b/tensorflow/lite/micro/kernels/cmsis-nn/pooling.cc similarity index 90% rename from tensorflow/lite/experimental/micro/kernels/cmsis-nn/pooling.cc rename to tensorflow/lite/micro/kernels/cmsis-nn/pooling.cc index 1bd3671d253..901dc65c3d0 100644 --- a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/pooling.cc +++ b/tensorflow/lite/micro/kernels/cmsis-nn/pooling.cc @@ -14,8 +14,9 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/kernels/internal/reference/pooling.h" -#include "arm_nnfunctions.h" -#include "scratch_buffer.h" +// These are headers from the ARM CMSIS-NN library. +#include "arm_nnfunctions.h" // NOLINT +#include "scratch_buffer.h" // NOLINT #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/kernels/internal/reference/integer_ops/pooling.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" @@ -97,24 +98,22 @@ void AverageEvalUint8(const TfLiteContext* context, const TfLiteNode* node, GetTensorShape(output), GetTensorData(output)); } -TfLiteStatus AverageEvalInt8(const TfLiteContext* context, - const TfLiteNode* node, +TfLiteStatus AverageEvalInt8(TfLiteContext* context, const TfLiteNode* node, const TfLitePoolParams* params, const OpData* data, - const TfLiteTensor* input, TfLiteTensor* output) { + TfLiteTensor* input, TfLiteTensor* output) { int32_t activation_min, activation_max; CalculateActivationRangeInt8(params->activation, output, &activation_min, &activation_max); - TFLITE_DCHECK_LE(params->quantized_activation_min, - params->quantized_activation_max); + TFLITE_DCHECK_LE(activation_min, activation_max); +#if defined(ARM_MATH_DSP) && defined(ARM_MATH_LOOPUNROLL) RuntimeShape input_shape = GetTensorShape(input); TFLITE_DCHECK_EQ(input_shape.DimensionsCount(), 4); RuntimeShape output_shape = GetTensorShape(output); TFLITE_DCHECK_EQ(output_shape.DimensionsCount(), 4); - const int batches = MatchingDim(input_shape, 0, output_shape, 0); const int depth = MatchingDim(input_shape, 3, output_shape, 3); const int input_height = input_shape.Dims(1); const int input_width = input_shape.Dims(2); @@ -128,10 +127,8 @@ TfLiteStatus AverageEvalInt8(const TfLiteContext* context, const int padding_height = data->padding.height; const int padding_width = data->padding.width; -#if defined(ARM_MATH_DSP) && defined(ARM_MATH_LOOPUNROLL) int16_t* scratch_buffer = nullptr; - const int32_t buffer_size = - arm_avgpool_s8_get_buffer_size(output_width, depth); + int32_t buffer_size = arm_avgpool_s8_get_buffer_size(output_width, depth); TF_LITE_ENSURE_OK( context, get_cmsis_scratch_buffer(context, &scratch_buffer, buffer_size)); @@ -145,6 +142,15 @@ TfLiteStatus AverageEvalInt8(const TfLiteContext* context, scratch_buffer, GetTensorData(output)), ARM_MATH_SUCCESS); #else + PoolParams op_params; + op_params.stride_height = params->stride_height; + op_params.stride_width = params->stride_width; + op_params.filter_height = params->filter_height; + op_params.filter_width = params->filter_width; + op_params.padding_values.height = data->padding.height; + op_params.padding_values.width = data->padding.width; + op_params.quantized_activation_min = activation_min; + op_params.quantized_activation_max = activation_max; reference_integer_ops::AveragePool( op_params, GetTensorShape(input), GetTensorData(input), GetTensorShape(output), GetTensorData(output)); @@ -211,7 +217,9 @@ TfLiteStatus AverageEval(TfLiteContext* context, TfLiteNode* node) { auto* params = reinterpret_cast(node->builtin_data); OpData data; - const TfLiteTensor* input = GetInput(context, node, kInputTensor); + // Todo: make 'input' const once CMSIS-reuse is fixed + TfLiteTensor* input = &context->tensors[flatbuffers::EndianScalar( + node->inputs->data[kInputTensor])]; TfLiteTensor* output = GetOutput(context, node, kOutputTensor); TF_LITE_ENSURE_STATUS(CalculateOpData(context, params, input, output, &data)); diff --git a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.cc b/tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.cc similarity index 83% rename from tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.cc rename to tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.cc index 87761a83467..e15a1416aeb 100644 --- a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.cc +++ b/tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.cc @@ -15,18 +15,18 @@ limitations under the License. #include "scratch_buffer.h" -#define SCRATCH_BUFFER_BYTES 13000 - // todo: remove this function once context->AllocateTemporaryTensor() is // implemented. + +// This buffer is used by CMSIS-NN optimized operator implementations. +// SCRATCH_BUFFER_BYTES bytes is chosen empirically. It needs to be large +// enough to hold the biggest buffer needed by all CMSIS-NN operators in the +// network. // note: buffer must be 32-bit aligned for SIMD +#define SCRATCH_BUFFER_BYTES 13000 + TfLiteStatus get_cmsis_scratch_buffer(TfLiteContext* context, int16_t** buf, int32_t buf_size_bytes) { - // This buffer is used by CMSIS-NN optimized operator implementations. - // SCRATCH_BUFFER_BYTES bytes is chosen empirically. It needs to be large - // enough to hold the biggest buffer needed by all CMSIS-NN operators in the - // network. - __attribute__((aligned( 4))) static int16_t cmsis_scratch_buffer[SCRATCH_BUFFER_BYTES / 2] = {0}; diff --git a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.h b/tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.h similarity index 82% rename from tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.h rename to tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.h index ee1857627ab..ba63cdfe90b 100644 --- a/tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.h +++ b/tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.h @@ -13,9 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ +#ifndef TENSORFLOW_LITE_MICRO_KERNELS_CMSIS_NN_SCRATCH_BUFFER_H_ +#define TENSORFLOW_LITE_MICRO_KERNELS_CMSIS_NN_SCRATCH_BUFFER_H_ + #include "tensorflow/lite/c/common.h" // todo: remove this function once context->AllocateTemporaryTensor() is // implemented. TfLiteStatus get_cmsis_scratch_buffer(TfLiteContext* context, int16_t** buf, int32_t buf_size); + +#endif // TENSORFLOW_LITE_MICRO_KERNELS_CMSIS_NN_SCRATCH_BUFFER_H_ diff --git a/tensorflow/lite/experimental/micro/kernels/comparisons.cc b/tensorflow/lite/micro/kernels/comparisons.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/comparisons.cc rename to tensorflow/lite/micro/kernels/comparisons.cc diff --git a/tensorflow/lite/experimental/micro/kernels/comparisons_test.cc b/tensorflow/lite/micro/kernels/comparisons_test.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/comparisons_test.cc rename to tensorflow/lite/micro/kernels/comparisons_test.cc index 5d2f726af4c..86fb9ea759b 100644 --- a/tensorflow/lite/experimental/micro/kernels/comparisons_test.cc +++ b/tensorflow/lite/micro/kernels/comparisons_test.cc @@ -17,9 +17,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/concatenation.cc b/tensorflow/lite/micro/kernels/concatenation.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/concatenation.cc rename to tensorflow/lite/micro/kernels/concatenation.cc diff --git a/tensorflow/lite/experimental/micro/kernels/concatenation_test.cc b/tensorflow/lite/micro/kernels/concatenation_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/kernels/concatenation_test.cc rename to tensorflow/lite/micro/kernels/concatenation_test.cc index 5f2bdd3be27..703ef83fe87 100644 --- a/tensorflow/lite/experimental/micro/kernels/concatenation_test.cc +++ b/tensorflow/lite/micro/kernels/concatenation_test.cc @@ -16,9 +16,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/conv.cc b/tensorflow/lite/micro/kernels/conv.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/conv.cc rename to tensorflow/lite/micro/kernels/conv.cc diff --git a/tensorflow/lite/experimental/micro/kernels/conv_test.cc b/tensorflow/lite/micro/kernels/conv_test.cc similarity index 75% rename from tensorflow/lite/experimental/micro/kernels/conv_test.cc rename to tensorflow/lite/micro/kernels/conv_test.cc index 352b10cca04..7d2a262bae6 100644 --- a/tensorflow/lite/experimental/micro/kernels/conv_test.cc +++ b/tensorflow/lite/micro/kernels/conv_test.cc @@ -15,10 +15,10 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_utils.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/micro_utils.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { @@ -103,7 +103,8 @@ TfLiteStatus ValidateConvGoldens(TfLiteTensor* tensors, int tensors_size, } for (int i = 0; i < output_length; ++i) { - TF_LITE_MICRO_EXPECT_EQ(expected_output_data[i], output_data[i]); + TF_LITE_MICRO_EXPECT_NEAR(expected_output_data[i], output_data[i], + tolerance); } return kTfLiteOk; } @@ -181,13 +182,14 @@ void TestConvQuantizedPerLayer( void TestConvQuantizedPerChannel( const int* input_dims_data, const float* input_data, - int8_t* input_quantized, float input_scale, const int* filter_dims_data, - const float* filter_data, int8_t* filter_data_quantized, - const int* bias_dims_data, const float* bias_data, - int32_t* bias_data_quantized, float* bias_scales, int* bias_zero_points, - const int* output_dims_data, const float* expected_output_data, - int8_t* expected_output_data_quantized, int8_t* output_data, - float output_scale, TfLiteConvParams* conv_params) { + int8_t* input_quantized, float input_scale, int input_zero_point, + const int* filter_dims_data, const float* filter_data, + int8_t* filter_data_quantized, const int* bias_dims_data, + const float* bias_data, int32_t* bias_data_quantized, float* bias_scales, + int* bias_zero_points, const int* output_dims_data, + const float* expected_output_data, int8_t* expected_output_data_quantized, + int8_t* output_data, float output_scale, int output_zero_point, + TfLiteConvParams* conv_params) { TfLiteIntArray* input_dims = IntArrayFromInts(input_dims_data); TfLiteIntArray* filter_dims = IntArrayFromInts(filter_dims_data); TfLiteIntArray* bias_dims = IntArrayFromInts(bias_dims_data); @@ -198,28 +200,30 @@ void TestConvQuantizedPerChannel( float filter_scales[5]; TfLiteAffineQuantization filter_quant; TfLiteAffineQuantization bias_quant; - TfLiteTensor input_tensor = CreateQuantizedTensor( - input_data, input_quantized, input_dims, input_scale, 0, "input_tensor"); + TfLiteTensor input_tensor = + CreateQuantizedTensor(input_data, input_quantized, input_dims, + input_scale, input_zero_point, "input_tensor"); TfLiteTensor filter_tensor = CreateSymmetricPerChannelQuantizedTensor( filter_data, filter_data_quantized, filter_dims, filter_scales, filter_zero_points, &filter_quant, 0 /* quantized dimension */, "filter_tensor"); TfLiteTensor bias_tensor = CreatePerChannelQuantizedBiasTensor( bias_data, bias_data_quantized, bias_dims, input_scale, &filter_scales[1], - bias_scales, bias_zero_points, &bias_quant, 0, "bias_tensor"); + bias_scales, bias_zero_points, &bias_quant, 0 /* quantized dimension */, + "bias_tensor"); TfLiteTensor output_tensor = CreateQuantizedTensor(output_data, output_dims, output_scale, - 0 /* quantized dimension */, "output_tensor"); + output_zero_point, "output_tensor"); // TODO(njeff): Affine Quantization Params should be set on tensor creation. float input_scales[] = {1, input_scale}; - int input_zero_points[] = {1, 128}; + int input_zero_points[] = {1, input_zero_point}; TfLiteAffineQuantization input_quant = {FloatArrayFromFloats(input_scales), IntArrayFromInts(input_zero_points)}; input_tensor.quantization = {kTfLiteAffineQuantization, &input_quant}; float output_scales[] = {1, output_scale}; - int output_zero_points[] = {1, 128}; + int output_zero_points[] = {1, output_zero_point}; TfLiteAffineQuantization output_quant = { FloatArrayFromFloats(output_scales), IntArrayFromInts(output_zero_points)}; @@ -237,11 +241,12 @@ void TestConvQuantizedPerChannel( tflite::AsymmetricQuantize(expected_output_data, expected_output_data_quantized, output_dims_count, - output_scale); + output_scale, output_zero_point); TF_LITE_MICRO_EXPECT_EQ( kTfLiteOk, ValidateConvGoldens(tensors, tensors_size, expected_output_data_quantized, - output_data, output_dims_count, conv_params)); + output_data, output_dims_count, conv_params, + 1.0 /* tolerance */)); } } // namespace @@ -306,8 +311,9 @@ TF_LITE_MICRO_TEST(SimpleTestQuantizedPerChannel) { int8_t output_data[output_dims_count]; const float input_scale = 0.5f; - const float bias_scale = 0.5f; const float output_scale = 1.0f; + const int input_zero_point = 0; + const int output_zero_point = 0; int8_t input_quantized[tflite::testing::kInputElements]; int8_t filter_quantized[tflite::testing::kFilterElements]; @@ -318,12 +324,44 @@ TF_LITE_MICRO_TEST(SimpleTestQuantizedPerChannel) { tflite::testing::TestConvQuantizedPerChannel( tflite::testing::kInputShape, tflite::testing::kInputData, - input_quantized, input_scale, tflite::testing::kFilterShape, - tflite::testing::kFilterData, filter_quantized, - tflite::testing::kBiasShape, tflite::testing::kBiasData, bias_quantized, - scales, zero_points, tflite::testing::kOutputShape, + input_quantized, input_scale, input_zero_point, + tflite::testing::kFilterShape, tflite::testing::kFilterData, + filter_quantized, tflite::testing::kBiasShape, tflite::testing::kBiasData, + bias_quantized, scales, zero_points, tflite::testing::kOutputShape, tflite::testing::kGoldenData, golden_quantized, output_data, output_scale, - &tflite::testing::common_conv_params); + output_zero_point, &tflite::testing::common_conv_params); +} + +TF_LITE_MICRO_TEST(SimpleTestQuantizedPerChannelRelu6) { + // conv params: + // padding, stride_, dilation_, activation + TfLiteConvParams conv_params = {kTfLitePaddingValid, 1, 1, kTfLiteActRelu6}; + const int output_dims_count = 12; + int8_t output_data[output_dims_count]; + + const float bias_values[] = {1, 2, -3}; + const float golden_data[] = {6, 2, 0, 6, 2, 0, 6, 4, 0, 6, 4, 0}; + + const float input_scale = 0.023529f; + const float output_scale = 0.023529f; + const int input_zero_point = -128; + const int output_zero_point = -128; + + int8_t input_quantized[tflite::testing::kInputElements]; + int8_t filter_quantized[tflite::testing::kFilterElements]; + int32_t bias_quantized[tflite::testing::kBiasElements]; + int8_t golden_quantized[tflite::testing::kOutputElements]; + int zero_points[tflite::testing::kBiasElements + 1]; + float scales[tflite::testing::kBiasElements + 1]; + + tflite::testing::TestConvQuantizedPerChannel( + tflite::testing::kInputShape, tflite::testing::kInputData, + input_quantized, input_scale, input_zero_point, + tflite::testing::kFilterShape, tflite::testing::kFilterData, + filter_quantized, tflite::testing::kBiasShape, bias_values, + bias_quantized, scales, zero_points, tflite::testing::kOutputShape, + golden_data, golden_quantized, output_data, output_scale, + output_zero_point, &tflite::testing::common_conv_params); } TF_LITE_MICRO_TEST(Kernel1x1QuantizedPerChannel) { @@ -351,8 +389,9 @@ TF_LITE_MICRO_TEST(Kernel1x1QuantizedPerChannel) { 31, 4, 7, 31, 4, 7}; const float input_scale = 0.5f; - const float bias_scale = 0.5f; const float output_scale = 1.0f; + const int input_zero_point = 0; + const int output_zero_point = 0; int8_t input_quantized[kInputElements]; int8_t filter_quantized[kFilterElements]; @@ -362,10 +401,54 @@ TF_LITE_MICRO_TEST(Kernel1x1QuantizedPerChannel) { float scales[kBiasElements + 1]; tflite::testing::TestConvQuantizedPerChannel( - kInputShape, kInputData, input_quantized, input_scale, kFilterShape, - kFilterData, filter_quantized, kBiasShape, kBiasData, bias_quantized, - scales, zero_points, kOutputShape, kGoldenData, golden_quantized, - output_data, output_scale, &conv_params); + kInputShape, kInputData, input_quantized, input_scale, input_zero_point, + kFilterShape, kFilterData, filter_quantized, kBiasShape, kBiasData, + bias_quantized, scales, zero_points, kOutputShape, kGoldenData, + golden_quantized, output_data, output_scale, output_zero_point, + &conv_params); +} + +TF_LITE_MICRO_TEST(Kernel1x1QuantizedPerChannelRelu6) { + // conv params: + // padding, stride_, dilation_, activation + TfLiteConvParams conv_params = {kTfLitePaddingValid, 1, 1, kTfLiteActRelu6}; + const int kInputShape[] = {4, 1, 2, 2, 4}; // [len,N,H,W,C] + const int kInputElements = + kInputShape[1] * kInputShape[2] * kInputShape[3] * kInputShape[4]; + float kInputData[/* kInputElements */] = {1, 1, 1, 1, 2, 2, 2, 2, + 1, 2, 3, 4, 1, 2, 3, 4}; + const int kFilterShape[] = {4, 3, 1, 1, 4}; + const int kFilterElements = + kFilterShape[1] * kFilterShape[2] * kFilterShape[3] * kFilterShape[4]; + float kFilterData[/* kFilterElements */] = {1, 2, 3, 4, -1, 1, + -1, 1, -1, -1, 1, 1}; + const int kBiasElements = kFilterShape[1]; + const int kBiasShape[] = {1, kBiasElements}; + float kBiasData[/* kBiasElements */] = {1, 2, -3}; + const int kOutputShape[] = {4, 1, 2, 2, kBiasElements}; + const int kOutputElements = 4 * 3; + int8_t output_data[kOutputElements]; + const float kGoldenData[/* kOutputElements */] = {6, 2, 0, 6, 2, 0, + 6, 4, 1, 6, 4, 1}; + + const float input_scale = 0.023529f; + const float output_scale = 0.023529f; + const int input_zero_point = -128; + const int output_zero_point = -128; + + int8_t input_quantized[kInputElements]; + int8_t filter_quantized[kFilterElements]; + int32_t bias_quantized[kBiasElements]; + int8_t golden_quantized[kOutputElements]; + int zero_points[kBiasElements + 1]; + float scales[kBiasElements + 1]; + + tflite::testing::TestConvQuantizedPerChannel( + kInputShape, kInputData, input_quantized, input_scale, input_zero_point, + kFilterShape, kFilterData, filter_quantized, kBiasShape, kBiasData, + bias_quantized, scales, zero_points, kOutputShape, kGoldenData, + golden_quantized, output_data, output_scale, output_zero_point, + &conv_params); } TF_LITE_MICRO_TEST(FilterDimsNotMatchingAffineQuantization) { @@ -373,7 +456,6 @@ TF_LITE_MICRO_TEST(FilterDimsNotMatchingAffineQuantization) { int8_t output_data[output_dims_count]; const float input_scale = 0.5f; - const float bias_scale = 0.5f; const float output_scale = 1.0f; int8_t input_quantized[tflite::testing::kInputElements]; diff --git a/tensorflow/lite/experimental/micro/kernels/depthwise_conv.cc b/tensorflow/lite/micro/kernels/depthwise_conv.cc similarity index 95% rename from tensorflow/lite/experimental/micro/kernels/depthwise_conv.cc rename to tensorflow/lite/micro/kernels/depthwise_conv.cc index 7f1444f9aa9..04c33de85af 100644 --- a/tensorflow/lite/experimental/micro/kernels/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/depthwise_conv.cc @@ -35,7 +35,7 @@ constexpr int kInputTensor = 0; constexpr int kFilterTensor = 1; constexpr int kBiasTensor = 2; constexpr int kOutputTensor = 0; -constexpr int kMaxChannels = 256; +constexpr int kMaxChannels = 64; struct OpData { TfLitePaddingValues padding; @@ -79,16 +79,6 @@ TfLiteStatus CalculateOpData(TfLiteContext* context, TfLiteNode* node, GetOptionalInputTensor(context, node, kBiasTensor); TfLiteTensor* output = GetOutput(context, node, kOutputTensor); - // Ensure filter and bias channel count does not exceed space reserved for - // quantization metadata. - const auto filter_quantization = - reinterpret_cast( - filter->quantization.params); - const auto bias_quantization = - reinterpret_cast(bias->quantization.params); - TF_LITE_ENSURE(context, filter_quantization->scale->size <= kMaxChannels); - TF_LITE_ENSURE(context, bias_quantization->scale->size <= kMaxChannels); - TF_LITE_ENSURE_STATUS(tflite::PopulateConvolutionQuantizationParams( context, input, filter, bias, output, params->activation, &data->output_multiplier, &data->output_shift, @@ -243,6 +233,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { TF_LITE_ENSURE_STATUS(CalculateOpData(context, node, params, width, height, filter_width, filter_height, data_type, &data)); + // TODO(aselle): Consider whether float conv and quantized conv should be // separate ops to avoid dispatch overhead here. switch (input->type) { // Already know in/out types are same. diff --git a/tensorflow/lite/experimental/micro/kernels/depthwise_conv_test.cc b/tensorflow/lite/micro/kernels/depthwise_conv_test.cc similarity index 80% rename from tensorflow/lite/experimental/micro/kernels/depthwise_conv_test.cc rename to tensorflow/lite/micro/kernels/depthwise_conv_test.cc index 866c9a346e5..3a49ff0d669 100644 --- a/tensorflow/lite/experimental/micro/kernels/depthwise_conv_test.cc +++ b/tensorflow/lite/micro/kernels/depthwise_conv_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { @@ -177,12 +177,14 @@ void TestDepthwiseConvQuantizedPerLayer( void TestDepthwiseConvQuantizedPerChannel( const int* input_dims_data, const float* input_data, - int8_t* input_quantized, float input_scale, const int* filter_dims_data, - const float* filter_data, int8_t* filter_data_quantized, - const int* bias_dims_data, const float* bias_data, - int32_t* bias_data_quantized, const int* output_dims_data, - const float* expected_output_data, int8_t* expected_output_data_quantized, - int8_t* output_data, float output_scale, TfLiteFusedActivation activation) { + int8_t* input_quantized, float input_scale, int input_zero_point, + const int* filter_dims_data, const float* filter_data, + int8_t* filter_data_quantized, const int* bias_dims_data, + const float* bias_data, int32_t* bias_data_quantized, + const int* output_dims_data, const float* expected_output_data, + int8_t* expected_output_data_quantized, int8_t* output_data, + float output_scale, int output_zero_point, + TfLiteFusedActivation activation) { TfLiteIntArray* input_dims = IntArrayFromInts(input_dims_data); TfLiteIntArray* filter_dims = IntArrayFromInts(filter_dims_data); TfLiteIntArray* bias_dims = IntArrayFromInts(bias_dims_data); @@ -195,8 +197,9 @@ void TestDepthwiseConvQuantizedPerChannel( float bias_scales[kMaxBiasChannels]; TfLiteAffineQuantization filter_quant; TfLiteAffineQuantization bias_quant; - TfLiteTensor input_tensor = CreateQuantizedTensor( - input_data, input_quantized, input_dims, input_scale, 0, "input_tensor"); + TfLiteTensor input_tensor = + CreateQuantizedTensor(input_data, input_quantized, input_dims, + input_scale, input_zero_point, "input_tensor"); TfLiteTensor filter_tensor = CreateSymmetricPerChannelQuantizedTensor( filter_data, filter_data_quantized, filter_dims, filter_scales, filter_zero_points, &filter_quant, 3 /* quantized dimension */, @@ -207,17 +210,17 @@ void TestDepthwiseConvQuantizedPerChannel( "bias_tensor"); TfLiteTensor output_tensor = CreateQuantizedTensor(output_data, output_dims, output_scale, - 0 /* zero point */, "output_tensor"); + input_zero_point, "output_tensor"); // TODO(njeff): Affine Quantization Params should be set on tensor creation. float input_scales[] = {1, input_scale}; - int input_zero_points[] = {1, 0}; + int input_zero_points[] = {1, input_zero_point}; TfLiteAffineQuantization input_quant = {FloatArrayFromFloats(input_scales), IntArrayFromInts(input_zero_points)}; input_tensor.quantization = {kTfLiteAffineQuantization, &input_quant}; float output_scales[] = {1, output_scale}; - int output_zero_points[] = {1, 0}; + int output_zero_points[] = {1, output_zero_point}; TfLiteAffineQuantization output_quant = { FloatArrayFromFloats(output_scales), IntArrayFromInts(output_zero_points)}; @@ -234,7 +237,7 @@ void TestDepthwiseConvQuantizedPerChannel( }; AsymmetricQuantize(expected_output_data, expected_output_data_quantized, - output_dims_count, output_scale, 0); + output_dims_count, output_scale, output_zero_point); TF_LITE_MICRO_EXPECT_EQ( kTfLiteOk, ValidateDepthwiseConvGoldens( @@ -428,6 +431,8 @@ TF_LITE_MICRO_TEST(SimpleTestQuantizedPerChannel) { const float input_scale = 0.5; const float output_scale = 1.0f; + const int input_zero_point = 0; + const int output_zero_point = 0; int8_t input_quantized[input_elements]; int8_t filter_quantized[filter_elements]; @@ -437,10 +442,93 @@ TF_LITE_MICRO_TEST(SimpleTestQuantizedPerChannel) { float scales[bias_elements + 1]; tflite::testing::TestDepthwiseConvQuantizedPerChannel( - input_shape, input_values, input_quantized, input_scale, filter_shape, - filter_values, filter_quantized, bias_shape, bias_values, bias_quantized, - output_shape, golden, golden_quantized, output_data, output_scale, - kTfLiteActNone); + input_shape, input_values, input_quantized, input_scale, input_zero_point, + filter_shape, filter_values, filter_quantized, bias_shape, bias_values, + bias_quantized, output_shape, golden, golden_quantized, output_data, + output_scale, output_zero_point, kTfLiteActNone); +} + +TF_LITE_MICRO_TEST(SimpleTestQuantizedPerChannelDepthMultiplier1) { + const int input_elements = 12; + const int input_shape[] = {4, 1, 3, 2, 2}; + const float input_values[] = {1, 2, 7, 8, 3, 4, 9, 10, 5, 6, 11, 12}; + const int filter_elements = 8; + const int filter_shape[] = {4, 1, 2, 2, 2}; + const float filter_values[] = {1, 2, 3, 4, -9, 10, -11, 12}; + const int bias_elements = 2; + const int bias_shape[] = {4, 1, 1, 1, 2}; + const int output_elements = 4; + const float bias_values[] = {1, 2}; + const float golden[] = { + -103, + 127, + -128, + 127, + }; + const int output_shape[] = {4, 1, 2, 1, 2}; + const int output_dims_count = 4; + int8_t output_data[output_dims_count]; + + const float input_scale = 1.0f; + const float output_scale = 1.0f; + const int input_zero_point = 0; + const int output_zero_point = 0; + + int8_t input_quantized[input_elements]; + int8_t filter_quantized[filter_elements]; + int32_t bias_quantized[bias_elements]; + int8_t golden_quantized[output_elements]; + int zero_points[bias_elements + 1]; + float scales[bias_elements + 1]; + + tflite::testing::TestDepthwiseConvQuantizedPerChannel( + input_shape, input_values, input_quantized, input_scale, input_zero_point, + filter_shape, filter_values, filter_quantized, bias_shape, bias_values, + bias_quantized, output_shape, golden, golden_quantized, output_data, + output_scale, output_zero_point, kTfLiteActNone); +} + +TF_LITE_MICRO_TEST(TestQuantizedPerChannelDepthMultiplier1Relu6) { + const int input_elements = 24; + const int input_shape[] = {4, 1, 3, 2, 4}; + const float input_values[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + const int filter_elements = 16; + const int filter_shape[] = {4, 1, 2, 2, 4}; + const float filter_values[] = {0, 1, 8, -2, -1, 2, -10, 0, + -1, 3, -18, 0, 0, 4, 20, -3}; + const int bias_elements = 4; + const int bias_shape[] = {4, 1, 1, 1, 4}; + const int output_elements = 8; + const float bias_values[] = {1, 2, 3, 4}; + const float golden[] = { + 0, 6, 3, 0, 0, 6, 3, 0, + }; + const int output_shape[] = {4, 1, 2, 1, 4}; + int8_t output_data[output_elements]; + float output_float[output_elements]; + + const float input_scale = 0.023529f; + const float output_scale = 0.023529f; + const int input_zero_point = -128; + const int output_zero_point = -128; + + int8_t input_quantized[input_elements]; + int8_t filter_quantized[filter_elements]; + int32_t bias_quantized[bias_elements]; + int8_t golden_quantized[output_elements]; + int zero_points[bias_elements + 1]; + float scales[bias_elements + 1]; + + tflite::testing::TestDepthwiseConvFloat( + input_shape, input_values, filter_shape, filter_values, bias_shape, + bias_values, golden, output_shape, kTfLiteActRelu6, output_float); + + tflite::testing::TestDepthwiseConvQuantizedPerChannel( + input_shape, input_values, input_quantized, input_scale, input_zero_point, + filter_shape, filter_values, filter_quantized, bias_shape, bias_values, + bias_quantized, output_shape, golden, golden_quantized, output_data, + output_scale, output_zero_point, kTfLiteActRelu6); } TF_LITE_MICRO_TEST(TestQuantizedPerChannelCompareWithFloat) { @@ -466,14 +554,16 @@ TF_LITE_MICRO_TEST(TestQuantizedPerChannelCompareWithFloat) { int8_t output_data[output_size]; float output_float[output_size]; - float input_scale = 0.5; - float output_scale = 1.0; + const float input_scale = 0.5; + const float output_scale = 1.0; + const int input_zero_point = 0; + const int output_zero_point = 0; tflite::testing::TestDepthwiseConvQuantizedPerChannel( - input_dims, input_data, input_quantized, input_scale, filter_dims, - filter_data, filter_quantized, bias_dims, bias_data, bias_quantized, - output_dims, golden, golden_quantized, output_data, output_scale, - kTfLiteActNone); + input_dims, input_data, input_quantized, input_scale, input_zero_point, + filter_dims, filter_data, filter_quantized, bias_dims, bias_data, + bias_quantized, output_dims, golden, golden_quantized, output_data, + output_scale, output_zero_point, kTfLiteActNone); tflite::testing::TestDepthwiseConvFloat( input_dims, input_data, filter_dims, filter_data, bias_dims, bias_data, @@ -503,8 +593,10 @@ TF_LITE_MICRO_TEST(FilterDimsNotMatchingAffineQuantization) { int8_t output_data[output_size]; float output_float[output_size]; - float input_scale = 0.5; - float output_scale = 1.0; + const float input_scale = 0.5; + const float output_scale = 1.0; + const int input_zero_point = 0; + const int output_zero_point = 0; TfLiteIntArray* input_dims = tflite::testing::IntArrayFromInts(input_shape); TfLiteIntArray* filter_dims = tflite::testing::IntArrayFromInts(filter_shape); @@ -516,7 +608,8 @@ TF_LITE_MICRO_TEST(FilterDimsNotMatchingAffineQuantization) { TfLiteAffineQuantization filter_quant; TfLiteAffineQuantization bias_quant; TfLiteTensor input_tensor = tflite::testing::CreateQuantizedTensor( - input_data, input_quantized, input_dims, input_scale, 0, "input_tensor"); + input_data, input_quantized, input_dims, input_scale, input_zero_point, + "input_tensor"); TfLiteTensor filter_tensor = tflite::testing::CreateSymmetricPerChannelQuantizedTensor( filter_data, filter_quantized, filter_dims, filter_scales, @@ -527,11 +620,11 @@ TF_LITE_MICRO_TEST(FilterDimsNotMatchingAffineQuantization) { bias_data, bias_quantized, bias_dims, input_scale, &filter_scales[1], scales, zero_points, &bias_quant, 0, "bias_tensor"); TfLiteTensor output_tensor = tflite::testing::CreateQuantizedTensor( - output_data, output_dims, output_scale, 0 /* quantized dimension */, + output_data, output_dims, output_scale, output_zero_point, "output_tensor"); float input_scales[] = {1, input_scale}; - int input_zero_points[] = {1, 128}; + int input_zero_points[] = {1, input_zero_point}; TfLiteAffineQuantization input_quant = { tflite::testing::FloatArrayFromFloats(input_scales), tflite::testing::IntArrayFromInts(input_zero_points)}; diff --git a/tensorflow/lite/experimental/micro/kernels/dequantize.cc b/tensorflow/lite/micro/kernels/dequantize.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/dequantize.cc rename to tensorflow/lite/micro/kernels/dequantize.cc diff --git a/tensorflow/lite/experimental/micro/kernels/dequantize_test.cc b/tensorflow/lite/micro/kernels/dequantize_test.cc similarity index 93% rename from tensorflow/lite/experimental/micro/kernels/dequantize_test.cc rename to tensorflow/lite/micro/kernels/dequantize_test.cc index 76a82496bc9..68114e24b58 100644 --- a/tensorflow/lite/experimental/micro/kernels/dequantize_test.cc +++ b/tensorflow/lite/micro/kernels/dequantize_test.cc @@ -15,10 +15,10 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { @@ -56,7 +56,7 @@ void TestDequantize(const int* input_dims_data, const float* input_data, TfLiteContext context; PopulateContext(tensors, tensors_size, &context); - // Version 4 ops support int8 quantization. + // Version 2 of dequantize supports int8 quantization. const TfLiteRegistration* registration = resolver.FindOp(tflite::BuiltinOperator_DEQUANTIZE, 2); diff --git a/tensorflow/lite/experimental/micro/kernels/elementwise.cc b/tensorflow/lite/micro/kernels/elementwise.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/elementwise.cc rename to tensorflow/lite/micro/kernels/elementwise.cc diff --git a/tensorflow/lite/experimental/micro/kernels/elementwise_test.cc b/tensorflow/lite/micro/kernels/elementwise_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/kernels/elementwise_test.cc rename to tensorflow/lite/micro/kernels/elementwise_test.cc index 46a07bf26b6..7c0d8a4c231 100644 --- a/tensorflow/lite/experimental/micro/kernels/elementwise_test.cc +++ b/tensorflow/lite/micro/kernels/elementwise_test.cc @@ -14,10 +14,10 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/debug_log.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/debug_log.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/floor.cc b/tensorflow/lite/micro/kernels/floor.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/floor.cc rename to tensorflow/lite/micro/kernels/floor.cc index 6ba020ae528..d593cadcd75 100644 --- a/tensorflow/lite/experimental/micro/kernels/floor.cc +++ b/tensorflow/lite/micro/kernels/floor.cc @@ -13,9 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/kernels/internal/reference/floor.h" - #include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/internal/reference/floor.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" diff --git a/tensorflow/lite/experimental/micro/kernels/floor_test.cc b/tensorflow/lite/micro/kernels/floor_test.cc similarity index 94% rename from tensorflow/lite/experimental/micro/kernels/floor_test.cc rename to tensorflow/lite/micro/kernels/floor_test.cc index cce3372086c..fdf81f55fd1 100644 --- a/tensorflow/lite/experimental/micro/kernels/floor_test.cc +++ b/tensorflow/lite/micro/kernels/floor_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/fully_connected.cc b/tensorflow/lite/micro/kernels/fully_connected.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/fully_connected.cc rename to tensorflow/lite/micro/kernels/fully_connected.cc diff --git a/tensorflow/lite/experimental/micro/kernels/fully_connected_test.cc b/tensorflow/lite/micro/kernels/fully_connected_test.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/fully_connected_test.cc rename to tensorflow/lite/micro/kernels/fully_connected_test.cc index 1fcf2de0cbb..81278ec9a2e 100644 --- a/tensorflow/lite/experimental/micro/kernels/fully_connected_test.cc +++ b/tensorflow/lite/micro/kernels/fully_connected_test.cc @@ -17,9 +17,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/logical.cc b/tensorflow/lite/micro/kernels/logical.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/logical.cc rename to tensorflow/lite/micro/kernels/logical.cc diff --git a/tensorflow/lite/experimental/micro/kernels/logical_test.cc b/tensorflow/lite/micro/kernels/logical_test.cc similarity index 95% rename from tensorflow/lite/experimental/micro/kernels/logical_test.cc rename to tensorflow/lite/micro/kernels/logical_test.cc index 74b787c19ca..bd2b10784c1 100644 --- a/tensorflow/lite/experimental/micro/kernels/logical_test.cc +++ b/tensorflow/lite/micro/kernels/logical_test.cc @@ -14,9 +14,9 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/logistic.cc b/tensorflow/lite/micro/kernels/logistic.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/logistic.cc rename to tensorflow/lite/micro/kernels/logistic.cc diff --git a/tensorflow/lite/experimental/micro/kernels/logistic_test.cc b/tensorflow/lite/micro/kernels/logistic_test.cc similarity index 95% rename from tensorflow/lite/experimental/micro/kernels/logistic_test.cc rename to tensorflow/lite/micro/kernels/logistic_test.cc index 202db5cbe0f..a68cb1a2cd6 100644 --- a/tensorflow/lite/experimental/micro/kernels/logistic_test.cc +++ b/tensorflow/lite/micro/kernels/logistic_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/maximum_minimum.cc b/tensorflow/lite/micro/kernels/maximum_minimum.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/maximum_minimum.cc rename to tensorflow/lite/micro/kernels/maximum_minimum.cc diff --git a/tensorflow/lite/experimental/micro/kernels/maximum_minimum_test.cc b/tensorflow/lite/micro/kernels/maximum_minimum_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/maximum_minimum_test.cc rename to tensorflow/lite/micro/kernels/maximum_minimum_test.cc index 45c0f770e00..3d10636c0df 100644 --- a/tensorflow/lite/experimental/micro/kernels/maximum_minimum_test.cc +++ b/tensorflow/lite/micro/kernels/maximum_minimum_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/micro_ops.h b/tensorflow/lite/micro/kernels/micro_ops.h similarity index 93% rename from tensorflow/lite/experimental/micro/kernels/micro_ops.h rename to tensorflow/lite/micro/kernels/micro_ops.h index 7dd18adc669..35422ec8af5 100644 --- a/tensorflow/lite/experimental/micro/kernels/micro_ops.h +++ b/tensorflow/lite/micro/kernels/micro_ops.h @@ -12,8 +12,8 @@ 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_LITE_EXPERIMENTAL_MICRO_KERNELS_MICRO_OPS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_MICRO_OPS_H_ +#ifndef TENSORFLOW_LITE_MICRO_KERNELS_MICRO_OPS_H_ +#define TENSORFLOW_LITE_MICRO_KERNELS_MICRO_OPS_H_ #include "tensorflow/lite/c/common.h" @@ -35,8 +35,8 @@ TfLiteRegistration* Register_ARG_MAX(); TfLiteRegistration* Register_ARG_MIN(); TfLiteRegistration* Register_AVERAGE_POOL_2D(); TfLiteRegistration* Register_CEIL(); -TfLiteRegistration* Register_CONCATENATION(); TfLiteRegistration* Register_CONV_2D(); +TfLiteRegistration* Register_CONCATENATION(); TfLiteRegistration* Register_COS(); TfLiteRegistration* Register_DEPTHWISE_CONV_2D(); TfLiteRegistration* Register_DEQUANTIZE(); @@ -59,6 +59,8 @@ TfLiteRegistration* Register_MUL(); TfLiteRegistration* Register_NEG(); TfLiteRegistration* Register_NOT_EQUAL(); TfLiteRegistration* Register_PACK(); +TfLiteRegistration* Register_PAD(); +TfLiteRegistration* Register_PADV2(); TfLiteRegistration* Register_PRELU(); TfLiteRegistration* Register_QUANTIZE(); TfLiteRegistration* Register_RELU(); @@ -79,4 +81,4 @@ TfLiteRegistration* Register_UNPACK(); } // namespace ops } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_MICRO_OPS_H_ +#endif // TENSORFLOW_LITE_MICRO_KERNELS_MICRO_OPS_H_ diff --git a/tensorflow/lite/experimental/micro/kernels/micro_utils.h b/tensorflow/lite/micro/kernels/micro_utils.h similarity index 84% rename from tensorflow/lite/experimental/micro/kernels/micro_utils.h rename to tensorflow/lite/micro/kernels/micro_utils.h index dcb691ff883..85db263eb92 100644 --- a/tensorflow/lite/experimental/micro/kernels/micro_utils.h +++ b/tensorflow/lite/micro/kernels/micro_utils.h @@ -9,8 +9,8 @@ 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_LITE_EXPERIMENTAL_MICRO_KERNELS_MICRO_UTILS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_MICRO_UTILS_H_ +#ifndef TENSORFLOW_LITE_MICRO_KERNELS_MICRO_UTILS_H_ +#define TENSORFLOW_LITE_MICRO_KERNELS_MICRO_UTILS_H_ namespace tflite { namespace ops { namespace micro { @@ -34,4 +34,4 @@ struct Less { } // namespace micro } // namespace ops } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_KERNELS_MICRO_UTILS_H_ +#endif // TENSORFLOW_LITE_MICRO_KERNELS_MICRO_UTILS_H_ diff --git a/tensorflow/lite/experimental/micro/kernels/mul.cc b/tensorflow/lite/micro/kernels/mul.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/mul.cc rename to tensorflow/lite/micro/kernels/mul.cc diff --git a/tensorflow/lite/experimental/micro/kernels/mul_test.cc b/tensorflow/lite/micro/kernels/mul_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/mul_test.cc rename to tensorflow/lite/micro/kernels/mul_test.cc index 543929ad102..9955adfd7b6 100644 --- a/tensorflow/lite/experimental/micro/kernels/mul_test.cc +++ b/tensorflow/lite/micro/kernels/mul_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/neg.cc b/tensorflow/lite/micro/kernels/neg.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/neg.cc rename to tensorflow/lite/micro/kernels/neg.cc diff --git a/tensorflow/lite/experimental/micro/kernels/neg_test.cc b/tensorflow/lite/micro/kernels/neg_test.cc similarity index 94% rename from tensorflow/lite/experimental/micro/kernels/neg_test.cc rename to tensorflow/lite/micro/kernels/neg_test.cc index a9ba1eb82c8..ea012483a23 100644 --- a/tensorflow/lite/experimental/micro/kernels/neg_test.cc +++ b/tensorflow/lite/micro/kernels/neg_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/pack.cc b/tensorflow/lite/micro/kernels/pack.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/pack.cc rename to tensorflow/lite/micro/kernels/pack.cc diff --git a/tensorflow/lite/experimental/micro/kernels/pack_test.cc b/tensorflow/lite/micro/kernels/pack_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/pack_test.cc rename to tensorflow/lite/micro/kernels/pack_test.cc index a2ac2f7234d..b218b43a894 100644 --- a/tensorflow/lite/experimental/micro/kernels/pack_test.cc +++ b/tensorflow/lite/micro/kernels/pack_test.cc @@ -15,10 +15,10 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/debug_log.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/debug_log.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/micro/kernels/pad.cc b/tensorflow/lite/micro/kernels/pad.cc new file mode 100644 index 00000000000..916725dc2a0 --- /dev/null +++ b/tensorflow/lite/micro/kernels/pad.cc @@ -0,0 +1,222 @@ +/* Copyright 2019 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/lite/kernels/internal/reference/pad.h" + +#include + +#include "tensorflow/lite/kernels/internal/types.h" + +#ifdef MEMORY_SANITIZER +#include +#else +#define __msan_check_mem_is_initialized(ptr, size) +#endif + +#include "tensorflow/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/internal/tensor.h" +#include "tensorflow/lite/kernels/kernel_util.h" +#include "tensorflow/lite/kernels/op_macros.h" + +namespace tflite { +namespace ops { +namespace micro { +namespace pad { + +struct PadContext { + PadContext(TfLiteContext* context, TfLiteNode* node) { + input = GetInput(context, node, 0); + paddings = GetInput(context, node, 1); + constant_values = nullptr; + if (NumInputs(node) == 3) { + constant_values = GetOptionalInputTensor(context, node, 2); + } else { + constant_values = nullptr; + } + output = GetOutput(context, node, 0); + dims = NumDimensions(input); + + resizing_category = ResizingCategory::kGenericResize; + const int paddings_total = GetTensorShape(paddings).FlatSize(); + const int32* paddings_data = GetTensorData(paddings); + // Paddings will be a n,2 array, and we need to detect 4D arrays with the + // pattern { {0,0}, {a, b}, {c, d}, {0,0} }. + if (IsConstantTensor(paddings) && paddings_total == 8 && + (paddings_data[0] == 0 && paddings_data[1] == 0) && + (paddings_data[6] == 0 && paddings_data[7] == 0)) { + resizing_category = ResizingCategory::kImageStyle; + } + } + const TfLiteTensor* constant_values; + const TfLiteTensor* input; + const TfLiteTensor* paddings; + TfLiteTensor* output; + int dims; + ResizingCategory resizing_category; +}; + +TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) { + TF_LITE_ENSURE(context, NumInputs(node) == 2 || NumInputs(node) == 3); + TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1); + + PadContext op_context(context, node); + TF_LITE_ENSURE_EQ(context, op_context.input->type, op_context.output->type); + if (op_context.constant_values != nullptr) { + TF_LITE_ENSURE_EQ(context, op_context.input->type, + op_context.constant_values->type); + } + + // There must be a pair of paddings for each output dimension. + TF_LITE_ENSURE_EQ(context, GetTensorShape(op_context.paddings).FlatSize(), + op_context.output->dims->size * 2); + + // On Micro, outputs must be properly sized by the converter. + const int32* paddings_data = GetTensorData(op_context.paddings); + for (int i = 0; i < op_context.output->dims->size; i++) { + int output_dim = op_context.output->dims->data[i]; + int expected_dim = op_context.input->dims->data[i] + paddings_data[i * 2] + + paddings_data[i * 2 + 1]; + TF_LITE_ENSURE_EQ(context, output_dim, expected_dim); + } + + // Current implementations rely on the inputs being <= 4D. + TF_LITE_ENSURE( + context, op_context.dims <= reference_ops::PadKernelMaxDimensionCount()); + TF_LITE_ENSURE(context, IsConstantTensor(op_context.paddings)); + return kTfLiteOk; +} + +TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) { + PadContext op_context(context, node); + + if (op_context.constant_values != nullptr) { + // Ensure that constant_values is a scalar. + TF_LITE_ENSURE_EQ(context, NumElements(op_context.constant_values), 1); + } + + // Create before and after padding arrays that are accepted by the kernel. + const int32* paddings_data = GetTensorData(op_context.paddings); + + tflite::PadParams op_params; + memset(&op_params, 0, sizeof(PadParams)); + op_params.left_padding_count = op_context.dims; + op_params.right_padding_count = op_context.dims; + + for (int idx = op_context.dims - 1; idx >= 0; --idx) { + op_params.left_padding[idx] = paddings_data[idx * 2]; + op_params.right_padding[idx] = paddings_data[idx * 2 + 1]; + } + +#define TF_LITE_PAD(type, op_name, scalar, pad_value) \ + const scalar pad_value_copy = pad_value; \ + \ + type::op_name(op_params, GetTensorShape(op_context.input), \ + GetTensorData(op_context.input), &pad_value_copy, \ + GetTensorShape(op_context.output), \ + GetTensorData(op_context.output)) + switch (op_context.input->type) { + case kTfLiteFloat32: { + float pad_value = op_context.constant_values == nullptr + ? 0.f + : *GetTensorData(op_context.constant_values); + if (op_context.resizing_category == ResizingCategory::kImageStyle) { + TF_LITE_PAD(reference_ops, PadImageStyle, float, pad_value); + } else { + TF_LITE_PAD(reference_ops, Pad, float, pad_value); + } + } break; + case kTfLiteUInt8: { + uint8_t pad_value; + if (op_context.constant_values == nullptr) { + // Quantized Pad requires that 0 is represented in the quantized + // range. + TF_LITE_ENSURE(context, op_context.output->params.zero_point >= + std::numeric_limits::min()); + TF_LITE_ENSURE(context, op_context.output->params.zero_point <= + std::numeric_limits::max()); + pad_value = static_cast(op_context.output->params.zero_point); + } else { + // Quantized Pad requires that 'constant_values' is represented in the + // same quantized range as the input and output tensors. + TF_LITE_ENSURE_EQ(context, op_context.output->params.zero_point, + op_context.constant_values->params.zero_point); + TF_LITE_ENSURE_EQ(context, op_context.output->params.scale, + op_context.constant_values->params.scale); + pad_value = *GetTensorData(op_context.constant_values); + } + if (op_context.resizing_category == ResizingCategory::kImageStyle) { + TF_LITE_PAD(reference_ops, PadImageStyle, uint8_t, pad_value); + } else { + TF_LITE_PAD(reference_ops, Pad, uint8_t, pad_value); + } + } break; + case kTfLiteInt8: { + int8_t pad_value; + if (op_context.constant_values == nullptr) { + // Quantized Pad requires that 0 is represented in the quantized + // range. + TF_LITE_ENSURE(context, op_context.output->params.zero_point >= + std::numeric_limits::min()); + TF_LITE_ENSURE(context, op_context.output->params.zero_point <= + std::numeric_limits::max()); + pad_value = static_cast(op_context.output->params.zero_point); + } else { + // Quantized Pad requires that 'constant_values' is represented in the + // same quantized range as the input and output tensors. + TF_LITE_ENSURE_EQ(context, op_context.output->params.zero_point, + op_context.constant_values->params.zero_point); + TF_LITE_ENSURE(context, op_context.output->params.scale == + op_context.constant_values->params.scale); + pad_value = *GetTensorData(op_context.constant_values); + } + if (op_context.resizing_category == ResizingCategory::kImageStyle) { + TF_LITE_PAD(reference_ops, PadImageStyle, int8_t, pad_value); + } else { + TF_LITE_PAD(reference_ops, Pad, int8_t, pad_value); + } + } break; + case kTfLiteInt32: { + int32_t pad_value = + op_context.constant_values == nullptr + ? 0 + : *GetTensorData(op_context.constant_values); + TF_LITE_PAD(reference_ops, Pad, int32_t, pad_value); + } break; + default: + + context->ReportError(context, "Type %s not currently supported by Pad.", + TfLiteTypeGetName(op_context.input->type)); + return kTfLiteError; + } +#undef TF_LITE_PAD + return kTfLiteOk; +} + +} // namespace pad + +TfLiteRegistration* Register_PAD() { + static TfLiteRegistration r = {nullptr, nullptr, pad::Prepare, pad::Eval}; + return &r; +} + +// Also register Pad as PadV2. +TfLiteRegistration* Register_PADV2() { + static TfLiteRegistration r = {nullptr, nullptr, pad::Prepare, pad::Eval}; + return &r; +} + +} // namespace micro +} // namespace ops +} // namespace tflite diff --git a/tensorflow/lite/micro/kernels/pad_test.cc b/tensorflow/lite/micro/kernels/pad_test.cc new file mode 100644 index 00000000000..f659b73c0ef --- /dev/null +++ b/tensorflow/lite/micro/kernels/pad_test.cc @@ -0,0 +1,444 @@ +/* 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/lite/c/builtin_op_data.h" +#include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" + +namespace tflite { +namespace testing { +namespace { + +template +TfLiteStatus ValidatePadGoldens(TfLiteTensor* tensors, int tensors_size, + const T* golden, T* output_data, + int output_length) { + TfLiteContext context; + PopulateContext(tensors, tensors_size, &context); + ::tflite::ops::micro::AllOpsResolver resolver; + const TfLiteRegistration* registration = + resolver.FindOp(tflite::BuiltinOperator_PAD, 1); + TF_LITE_ENSURE(&context, registration != nullptr); + + int inputs_array_data[] = {2, 0, 1}; + TfLiteIntArray* inputs_array = IntArrayFromInts(inputs_array_data); + int outputs_array_data[] = {1, 2}; + TfLiteIntArray* outputs_array = IntArrayFromInts(outputs_array_data); + int temporaries_array_data[] = {0}; + TfLiteIntArray* temporaries_array = IntArrayFromInts(temporaries_array_data); + TfLiteNode node; + node.inputs = inputs_array; + node.outputs = outputs_array; + node.temporaries = temporaries_array; + node.user_data = nullptr; + node.builtin_data = nullptr; + node.custom_initial_data = nullptr; + node.custom_initial_data_size = 0; + node.delegate = nullptr; + TF_LITE_MICRO_EXPECT_NE(nullptr, registration->prepare); + TF_LITE_ENSURE_EQ(&context, kTfLiteOk, + registration->prepare(&context, &node)); + TF_LITE_MICRO_EXPECT_NE(nullptr, registration->invoke); + TF_LITE_ENSURE_EQ(&context, kTfLiteOk, registration->invoke(&context, &node)); + for (int i = 0; i < output_length; ++i) { + TF_LITE_MICRO_EXPECT_EQ(golden[i], output_data[i]); + } + return kTfLiteOk; +} + +template +TfLiteStatus ValidatePadV2Goldens(TfLiteTensor* tensors, int tensors_size, + const T* golden, T* output_data, + int output_length) { + TfLiteContext context; + PopulateContext(tensors, tensors_size, &context); + ::tflite::ops::micro::AllOpsResolver resolver; + const TfLiteRegistration* registration = + resolver.FindOp(tflite::BuiltinOperator_PADV2, 1); + TF_LITE_ENSURE(&context, registration != nullptr); + + int inputs_array_data[] = {3, 0, 1, 2}; + TfLiteIntArray* inputs_array = IntArrayFromInts(inputs_array_data); + int outputs_array_data[] = {1, 3}; + TfLiteIntArray* outputs_array = IntArrayFromInts(outputs_array_data); + int temporaries_array_data[] = {0}; + TfLiteIntArray* temporaries_array = IntArrayFromInts(temporaries_array_data); + TfLiteNode node; + node.inputs = inputs_array; + node.outputs = outputs_array; + node.temporaries = temporaries_array; + node.user_data = nullptr; + node.builtin_data = nullptr; + node.custom_initial_data = nullptr; + node.custom_initial_data_size = 0; + node.delegate = nullptr; + TF_LITE_MICRO_EXPECT_NE(nullptr, registration->prepare); + // Prepare should catch dimension mismatches. + TfLiteStatus prepare_status = registration->prepare(&context, &node); + if (prepare_status != kTfLiteOk) { + return prepare_status; + } + + TF_LITE_MICRO_EXPECT_NE(nullptr, registration->invoke); + // Eval should catch quantization mismatches. + TfLiteStatus invoke_status = registration->invoke(&context, &node); + if (invoke_status != kTfLiteOk) { + return invoke_status; + } + + for (int i = 0; i < output_length; ++i) { + TF_LITE_MICRO_EXPECT_EQ(golden[i], output_data[i]); + } + return kTfLiteOk; +} + +// output data and golden must be shaped correctly +void TestPadFloat(const int* input_dims_data, const float* input_data, + const int* pad_dims_data, const int* pad_data, + const int* output_dims_data, const float* golden, + float* output_data, + TfLiteStatus expected_status = kTfLiteOk) { + TfLiteIntArray* input_dims = IntArrayFromInts(input_dims_data); + TfLiteIntArray* pad_dims = IntArrayFromInts(pad_dims_data); + TfLiteIntArray* output_dims = IntArrayFromInts(output_dims_data); + const int output_dims_count = ElementCount(*output_dims); + constexpr int inputs_size = 2; + constexpr int outputs_size = 1; + constexpr int tensors_size = inputs_size + outputs_size; + TfLiteTensor tensors[tensors_size] = { + CreateFloatTensor(input_data, input_dims, "input_tensor"), + CreateInt32Tensor(pad_data, pad_dims, "padding tensor"), + CreateFloatTensor(output_data, output_dims, "output_tensor")}; + + // Pad tensor must be constant. + tensors[1].allocation_type = kTfLiteMmapRo; + + TF_LITE_MICRO_EXPECT_EQ(expected_status, + ValidatePadGoldens(tensors, tensors_size, golden, + output_data, output_dims_count)); +} + +// output data and golden must be shaped correctly +void TestPadV2Float(const int* input_dims_data, const float* input_data, + const int* pad_dims_data, const int* pad_data, + const float pad_value, const int* output_dims_data, + const float* golden, float* output_data, + TfLiteStatus expected_status = kTfLiteOk) { + TfLiteIntArray* input_dims = IntArrayFromInts(input_dims_data); + TfLiteIntArray* pad_dims = IntArrayFromInts(pad_dims_data); + const int pad_value_dims_data[] = {1, 1}; // Only one padding value allowed. + TfLiteIntArray* pad_value_dims = IntArrayFromInts(pad_value_dims_data); + TfLiteIntArray* output_dims = IntArrayFromInts(output_dims_data); + const int output_dims_count = ElementCount(*output_dims); + constexpr int inputs_size = 3; + constexpr int outputs_size = 1; + constexpr int tensors_size = inputs_size + outputs_size; + TfLiteTensor tensors[tensors_size] = { + CreateFloatTensor(input_data, input_dims, "input_tensor"), + CreateInt32Tensor(pad_data, pad_dims, "padding tensor"), + CreateFloatTensor(&pad_value, pad_value_dims, "pad value tensor"), + CreateFloatTensor(output_data, output_dims, "output_tensor")}; + + // Pad tensor must be constant. + tensors[1].allocation_type = kTfLiteMmapRo; + + TF_LITE_MICRO_EXPECT_EQ(expected_status, + ValidatePadV2Goldens(tensors, tensors_size, golden, + output_data, output_dims_count)); +} + +template +void TestPadQuantized(const int* input_dims_data, const float* input_data, + T* input_quantized, float input_scale, + int input_zero_point, const int* pad_dims_data, + const int* pad_data, const int* output_dims_data, + const float* golden, T* golden_quantized, + float output_scale, int output_zero_point, T* output_data, + TfLiteStatus expected_status = kTfLiteOk) { + TfLiteIntArray* input_dims = IntArrayFromInts(input_dims_data); + TfLiteIntArray* pad_dims = IntArrayFromInts(pad_dims_data); + TfLiteIntArray* output_dims = IntArrayFromInts(output_dims_data); + const int output_dims_count = ElementCount(*output_dims); + constexpr int inputs_size = 2; + constexpr int outputs_size = 1; + constexpr int tensors_size = inputs_size + outputs_size; + TfLiteTensor tensors[tensors_size] = { + CreateQuantizedTensor(input_data, input_quantized, input_dims, + input_scale, input_zero_point, "input_tensor"), + CreateInt32Tensor(pad_data, pad_dims, "padding tensor"), + CreateQuantizedTensor(output_data, output_dims, output_scale, + output_zero_point, "output_tensor")}; + + // Pad tensor must be constant. + tensors[1].allocation_type = kTfLiteMmapRo; + + tflite::AsymmetricQuantize(golden, golden_quantized, output_dims_count, + output_scale, output_zero_point); + TF_LITE_MICRO_EXPECT_EQ( + expected_status, + ValidatePadGoldens(tensors, tensors_size, golden_quantized, output_data, + output_dims_count)); +} + +template +void TestPadV2Quantized(const int* input_dims_data, const float* input_data, + T* input_quantized, float input_scale, + int input_zero_point, const int* pad_dims_data, + const int* pad_data, const float pad_value, + const float pad_value_scale, + const int pad_value_zero_point, + const int* output_dims_data, const float* golden, + T* golden_quantized, float output_scale, + int output_zero_point, T* output_data, + TfLiteStatus expected_status = kTfLiteOk) { + TfLiteIntArray* input_dims = IntArrayFromInts(input_dims_data); + TfLiteIntArray* pad_dims = IntArrayFromInts(pad_dims_data); + const int pad_value_dims_data[] = {1, 1}; // Only one padding value allowed. + TfLiteIntArray* pad_value_dims = IntArrayFromInts(pad_value_dims_data); + TfLiteIntArray* output_dims = IntArrayFromInts(output_dims_data); + T pad_value_quantized; + const int output_dims_count = ElementCount(*output_dims); + constexpr int inputs_size = 3; + constexpr int outputs_size = 1; + constexpr int tensors_size = inputs_size + outputs_size; + TfLiteTensor tensors[tensors_size] = { + CreateQuantizedTensor(input_data, input_quantized, input_dims, + input_scale, input_zero_point, "input_tensor"), + CreateInt32Tensor(pad_data, pad_dims, "padding tensor"), + CreateQuantizedTensor(&pad_value, &pad_value_quantized, pad_value_dims, + pad_value_scale, pad_value_zero_point, + "pad value tensor"), + CreateQuantizedTensor(output_data, output_dims, output_scale, + output_zero_point, "output_tensor")}; + + // Pad tensor must be constant. + tensors[1].allocation_type = kTfLiteMmapRo; + tensors[2].params.scale = pad_value_scale; + tensors[3].params.scale = output_scale; + + tflite::AsymmetricQuantize(golden, golden_quantized, output_dims_count, + output_scale, output_zero_point); + TF_LITE_MICRO_EXPECT_EQ( + expected_status, + ValidatePadV2Goldens(tensors, tensors_size, golden_quantized, output_data, + output_dims_count)); +} + +} // namespace +} // namespace testing +} // namespace tflite + +TF_LITE_MICRO_TESTS_BEGIN + +TF_LITE_MICRO_TEST(Test2DFloat) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, + 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + float output_data[24]; + + tflite::testing::TestPadFloat(input_dims, input_values, pad_dims, pad_values, + output_dims, golden, output_data); +} + +TF_LITE_MICRO_TEST(Test4DFloat) { + const int input_dims[] = {4, 1, 1, 1, 1}; + const float input_values[] = {42}; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 1, 1, 1, 1, 1, 1}; + const int output_dims[] = {4, 3, 3, 3, 3}; + const int kOutputLen = 81; // 3 * 3 * 3 * 3 + float golden[kOutputLen]; + for (int i = 0; i < kOutputLen; i++) { + golden[i] = 0; + } + golden[40] = 42; + float output_data[kOutputLen]; + + tflite::testing::TestPadFloat(input_dims, input_values, pad_dims, pad_values, + output_dims, const_cast(golden), + output_data); +} + +TF_LITE_MICRO_TEST(Test2DFloatV2) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const float pad_value = 42; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {42, 42, 42, 42, 42, 42, 42, 42, 42, 1, 2, 42, + 42, 3, 4, 42, 42, 42, 42, 42, 42, 42, 42, 42}; + float output_data[24]; + + tflite::testing::TestPadV2Float(input_dims, input_values, pad_dims, + pad_values, pad_value, output_dims, golden, + output_data); +} + +TF_LITE_MICRO_TEST(Test2DUInt8) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const float input_scale = 1.0f; + const int input_zero_point = 127; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, + 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + const float output_scale = 1.0f; + const int output_zero_point = 127; + uint8_t output_data[24]; + uint8_t input_quantized[4]; + uint8_t golden_quantized[24]; + + tflite::testing::TestPadQuantized( + input_dims, input_values, input_quantized, input_scale, input_zero_point, + pad_dims, pad_values, output_dims, golden, golden_quantized, output_scale, + output_zero_point, output_data); +} + +TF_LITE_MICRO_TEST(Test2DUInt8V2) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const float input_scale = 1.0f; + const int input_zero_point = 127; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const float pad_value = 42; + const float pad_value_scale = 1.0; + const float pad_value_zero_point = 127; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {42, 42, 42, 42, 42, 42, 42, 42, 42, 1, 2, 42, + 42, 3, 4, 42, 42, 42, 42, 42, 42, 42, 42, 42}; + const float output_scale = 1.0f; + const int output_zero_point = 127; + uint8_t output_data[24]; + uint8_t input_quantized[4]; + uint8_t golden_quantized[24]; + + tflite::testing::TestPadV2Quantized( + input_dims, input_values, input_quantized, input_scale, input_zero_point, + pad_dims, pad_values, pad_value, pad_value_scale, pad_value_zero_point, + output_dims, golden, golden_quantized, output_scale, output_zero_point, + output_data); +} + +TF_LITE_MICRO_TEST(Test2DInt8) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const float input_scale = 1.0f; + const int input_zero_point = 0; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, + 0, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + const float output_scale = 1.0f; + const int output_zero_point = 0; + int8_t output_data[24]; + int8_t input_quantized[4]; + int8_t golden_quantized[24]; + + tflite::testing::TestPadQuantized( + input_dims, input_values, input_quantized, input_scale, input_zero_point, + pad_dims, pad_values, output_dims, golden, golden_quantized, output_scale, + output_zero_point, output_data); +} + +TF_LITE_MICRO_TEST(Test2DInt8V2) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const float input_scale = 1.0f; + const int input_zero_point = 0; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const float pad_value = 42; + const float pad_value_scale = 1.0; + const float pad_value_zero_point = 0; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {42, 42, 42, 42, 42, 42, 42, 42, 42, 1, 2, 42, + 42, 3, 4, 42, 42, 42, 42, 42, 42, 42, 42, 42}; + const float output_scale = 1.0f; + const int output_zero_point = 0; + int8_t output_data[24]; + int8_t input_quantized[4]; + int8_t golden_quantized[24]; + + tflite::testing::TestPadV2Quantized( + input_dims, input_values, input_quantized, input_scale, input_zero_point, + pad_dims, pad_values, pad_value, pad_value_scale, pad_value_zero_point, + output_dims, golden, golden_quantized, output_scale, output_zero_point, + output_data); +} + +TF_LITE_MICRO_TEST(Test2DInt8V2ExpectFailurePadValueQuantizationMismatch) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const float input_scale = 1.0f; + const int input_zero_point = 0; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const float pad_value = 42; + // Causes failure since this is in a different quantization space than input. + const float pad_value_scale = .5; + const float pad_value_zero_point = 0; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + const float output_scale = 1.0f; + const int output_zero_point = 0; + int8_t output_data[24] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int8_t input_quantized[4]; + int8_t golden_quantized[24]; + + tflite::testing::TestPadV2Quantized( + input_dims, input_values, input_quantized, input_scale, input_zero_point, + pad_dims, pad_values, pad_value, pad_value_scale, pad_value_zero_point, + output_dims, golden, golden_quantized, output_scale, output_zero_point, + output_data, kTfLiteError); +} + +TF_LITE_MICRO_TEST(Test2DInt8ExpectFailureQuantizationRangeExcludesZero) { + const int input_dims[] = {4, 1, 2, 2, 1}; + const float input_values[] = {1, 2, 3, 4}; + const float input_scale = 1.0f; + const int input_zero_point = 0; + const int pad_dims[] = {2, 4, 2}; + const int pad_values[] = {1, 1, 0, 0, 1, 1, 0, 0}; + const int output_dims[] = {4, 3, 2, 4, 1}; + const float golden[] = {42, 42, 42, 42, 42, 42, 42, 42, 42, 1, 2, 42, + 42, 3, 4, 42, 42, 42, 42, 42, 42, 42, 42, 42}; + // Causes failure since this quantization zero point excludes zero. + const float output_scale = 1.0f; + const int output_zero_point = 129; + int8_t output_data[24]; + int8_t input_quantized[4]; + int8_t golden_quantized[24]; + + tflite::testing::TestPadQuantized( + input_dims, input_values, input_quantized, input_scale, input_zero_point, + pad_dims, pad_values, output_dims, golden, golden_quantized, output_scale, + output_zero_point, output_data, kTfLiteError); +} + +TF_LITE_MICRO_TESTS_END diff --git a/tensorflow/lite/experimental/micro/kernels/pooling.cc b/tensorflow/lite/micro/kernels/pooling.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/pooling.cc rename to tensorflow/lite/micro/kernels/pooling.cc diff --git a/tensorflow/lite/experimental/micro/kernels/pooling_test.cc b/tensorflow/lite/micro/kernels/pooling_test.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/pooling_test.cc rename to tensorflow/lite/micro/kernels/pooling_test.cc index 03909b994f8..c8a8cdd8c14 100644 --- a/tensorflow/lite/experimental/micro/kernels/pooling_test.cc +++ b/tensorflow/lite/micro/kernels/pooling_test.cc @@ -17,9 +17,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/portable_optimized/depthwise_conv.cc b/tensorflow/lite/micro/kernels/portable_optimized/depthwise_conv.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/portable_optimized/depthwise_conv.cc rename to tensorflow/lite/micro/kernels/portable_optimized/depthwise_conv.cc index 3685e2a26fb..3cbddd623d7 100644 --- a/tensorflow/lite/experimental/micro/kernels/portable_optimized/depthwise_conv.cc +++ b/tensorflow/lite/micro/kernels/portable_optimized/depthwise_conv.cc @@ -35,7 +35,7 @@ constexpr int kInputTensor = 0; constexpr int kFilterTensor = 1; constexpr int kBiasTensor = 2; constexpr int kOutputTensor = 0; -constexpr int kMaxChannels = 64; +constexpr int kMaxChannels = 256; // Size of the cached buffer we'll be using to hold reordered weights. constexpr int kReshapedFilterDataSize = 1 * 1024; diff --git a/tensorflow/lite/experimental/micro/kernels/prelu.cc b/tensorflow/lite/micro/kernels/prelu.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/prelu.cc rename to tensorflow/lite/micro/kernels/prelu.cc diff --git a/tensorflow/lite/experimental/micro/kernels/prelu_test.cc b/tensorflow/lite/micro/kernels/prelu_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/kernels/prelu_test.cc rename to tensorflow/lite/micro/kernels/prelu_test.cc index 4bfb9e39433..db770fd0f27 100644 --- a/tensorflow/lite/experimental/micro/kernels/prelu_test.cc +++ b/tensorflow/lite/micro/kernels/prelu_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/quantization_util_test.cc b/tensorflow/lite/micro/kernels/quantization_util_test.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/quantization_util_test.cc rename to tensorflow/lite/micro/kernels/quantization_util_test.cc index cb5ace70003..e9b219128fe 100644 --- a/tensorflow/lite/experimental/micro/kernels/quantization_util_test.cc +++ b/tensorflow/lite/micro/kernels/quantization_util_test.cc @@ -15,7 +15,7 @@ limitations under the License. #include "tensorflow/lite/kernels/internal/quantization_util.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace tflite { namespace { diff --git a/tensorflow/lite/experimental/micro/kernels/quantize.cc b/tensorflow/lite/micro/kernels/quantize.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/quantize.cc rename to tensorflow/lite/micro/kernels/quantize.cc diff --git a/tensorflow/lite/experimental/micro/kernels/quantize_test.cc b/tensorflow/lite/micro/kernels/quantize_test.cc similarity index 94% rename from tensorflow/lite/experimental/micro/kernels/quantize_test.cc rename to tensorflow/lite/micro/kernels/quantize_test.cc index c891bd1ec26..b116eb439c6 100644 --- a/tensorflow/lite/experimental/micro/kernels/quantize_test.cc +++ b/tensorflow/lite/micro/kernels/quantize_test.cc @@ -15,10 +15,10 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { @@ -55,7 +55,7 @@ void TestQuantize(const int* input_dims_data, const float* input_data, TfLiteContext context; PopulateContext(tensors, tensors_size, &context); - // Version 4 ops support int8 quantization. + // Version 1 of quantize supports int8 and uint8 quantization. const TfLiteRegistration* registration = resolver.FindOp(tflite::BuiltinOperator_QUANTIZE, 1); diff --git a/tensorflow/lite/experimental/micro/kernels/reshape.cc b/tensorflow/lite/micro/kernels/reshape.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/reshape.cc rename to tensorflow/lite/micro/kernels/reshape.cc diff --git a/tensorflow/lite/experimental/micro/kernels/reshape_test.cc b/tensorflow/lite/micro/kernels/reshape_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/kernels/reshape_test.cc rename to tensorflow/lite/micro/kernels/reshape_test.cc index 8aa8005bdd1..e252e13fa50 100644 --- a/tensorflow/lite/experimental/micro/kernels/reshape_test.cc +++ b/tensorflow/lite/micro/kernels/reshape_test.cc @@ -18,12 +18,12 @@ limitations under the License. #include #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_utils.h" -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/micro_utils.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/round.cc b/tensorflow/lite/micro/kernels/round.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/round.cc rename to tensorflow/lite/micro/kernels/round.cc diff --git a/tensorflow/lite/experimental/micro/kernels/round_test.cc b/tensorflow/lite/micro/kernels/round_test.cc similarity index 94% rename from tensorflow/lite/experimental/micro/kernels/round_test.cc rename to tensorflow/lite/micro/kernels/round_test.cc index 98ae3748d23..f50f323a3fe 100644 --- a/tensorflow/lite/experimental/micro/kernels/round_test.cc +++ b/tensorflow/lite/micro/kernels/round_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/softmax.cc b/tensorflow/lite/micro/kernels/softmax.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/softmax.cc rename to tensorflow/lite/micro/kernels/softmax.cc diff --git a/tensorflow/lite/experimental/micro/kernels/softmax_test.cc b/tensorflow/lite/micro/kernels/softmax_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/softmax_test.cc rename to tensorflow/lite/micro/kernels/softmax_test.cc index b82581be1d6..55bd5bf5757 100644 --- a/tensorflow/lite/experimental/micro/kernels/softmax_test.cc +++ b/tensorflow/lite/micro/kernels/softmax_test.cc @@ -15,9 +15,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/split.cc b/tensorflow/lite/micro/kernels/split.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/split.cc rename to tensorflow/lite/micro/kernels/split.cc diff --git a/tensorflow/lite/experimental/micro/kernels/split_test.cc b/tensorflow/lite/micro/kernels/split_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/split_test.cc rename to tensorflow/lite/micro/kernels/split_test.cc index 486f06acc26..a9ed9347cad 100644 --- a/tensorflow/lite/experimental/micro/kernels/split_test.cc +++ b/tensorflow/lite/micro/kernels/split_test.cc @@ -15,10 +15,10 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/debug_log.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/debug_log.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/strided_slice.cc b/tensorflow/lite/micro/kernels/strided_slice.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/strided_slice.cc rename to tensorflow/lite/micro/kernels/strided_slice.cc diff --git a/tensorflow/lite/experimental/micro/kernels/strided_slice_test.cc b/tensorflow/lite/micro/kernels/strided_slice_test.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/strided_slice_test.cc rename to tensorflow/lite/micro/kernels/strided_slice_test.cc index 72312773430..899037f218a 100644 --- a/tensorflow/lite/experimental/micro/kernels/strided_slice_test.cc +++ b/tensorflow/lite/micro/kernels/strided_slice_test.cc @@ -14,9 +14,9 @@ limitations under the License. ==============================================================================*/ #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/svdf.cc b/tensorflow/lite/micro/kernels/svdf.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/svdf.cc rename to tensorflow/lite/micro/kernels/svdf.cc index 208dd83f953..dfecd44f524 100644 --- a/tensorflow/lite/experimental/micro/kernels/svdf.cc +++ b/tensorflow/lite/micro/kernels/svdf.cc @@ -17,13 +17,13 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/activation_utils.h" -#include "tensorflow/lite/experimental/micro/micro_utils.h" #include "tensorflow/lite/kernels/internal/common.h" #include "tensorflow/lite/kernels/internal/quantization_util.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" #include "tensorflow/lite/kernels/kernel_util.h" #include "tensorflow/lite/kernels/op_macros.h" +#include "tensorflow/lite/micro/kernels/activation_utils.h" +#include "tensorflow/lite/micro/micro_utils.h" namespace tflite { namespace ops { diff --git a/tensorflow/lite/experimental/micro/kernels/svdf_test.cc b/tensorflow/lite/micro/kernels/svdf_test.cc similarity index 99% rename from tensorflow/lite/experimental/micro/kernels/svdf_test.cc rename to tensorflow/lite/micro/kernels/svdf_test.cc index ac144e3e3fe..69288e15c96 100644 --- a/tensorflow/lite/experimental/micro/kernels/svdf_test.cc +++ b/tensorflow/lite/micro/kernels/svdf_test.cc @@ -17,9 +17,9 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/kernels/unpack.cc b/tensorflow/lite/micro/kernels/unpack.cc similarity index 100% rename from tensorflow/lite/experimental/micro/kernels/unpack.cc rename to tensorflow/lite/micro/kernels/unpack.cc diff --git a/tensorflow/lite/experimental/micro/kernels/unpack_test.cc b/tensorflow/lite/micro/kernels/unpack_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/kernels/unpack_test.cc rename to tensorflow/lite/micro/kernels/unpack_test.cc index 8f649995c08..8b3dd1b7299 100644 --- a/tensorflow/lite/experimental/micro/kernels/unpack_test.cc +++ b/tensorflow/lite/micro/kernels/unpack_test.cc @@ -15,10 +15,10 @@ limitations under the License. #include "tensorflow/lite/c/builtin_op_data.h" #include "tensorflow/lite/c/common.h" -#include "tensorflow/lite/experimental/micro/debug_log.h" -#include "tensorflow/lite/experimental/micro/kernels/all_ops_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/debug_log.h" +#include "tensorflow/lite/micro/kernels/all_ops_resolver.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" namespace tflite { namespace testing { diff --git a/tensorflow/lite/experimental/micro/mbed/debug_log.cc b/tensorflow/lite/micro/mbed/debug_log.cc similarity index 93% rename from tensorflow/lite/experimental/micro/mbed/debug_log.cc rename to tensorflow/lite/micro/mbed/debug_log.cc index d4a4a5a8429..57e74bfb94d 100644 --- a/tensorflow/lite/experimental/micro/mbed/debug_log.cc +++ b/tensorflow/lite/micro/mbed/debug_log.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" #include diff --git a/tensorflow/lite/experimental/micro/memory_helpers.cc b/tensorflow/lite/micro/memory_helpers.cc similarity index 97% rename from tensorflow/lite/experimental/micro/memory_helpers.cc rename to tensorflow/lite/micro/memory_helpers.cc index 9e117dfbc5e..302f160a235 100644 --- a/tensorflow/lite/experimental/micro/memory_helpers.cc +++ b/tensorflow/lite/micro/memory_helpers.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/memory_helpers.h" +#include "tensorflow/lite/micro/memory_helpers.h" #include diff --git a/tensorflow/lite/experimental/micro/memory_helpers.h b/tensorflow/lite/micro/memory_helpers.h similarity index 89% rename from tensorflow/lite/experimental/micro/memory_helpers.h rename to tensorflow/lite/micro/memory_helpers.h index a4f7ad8ce7c..ef8205c8038 100644 --- a/tensorflow/lite/experimental/micro/memory_helpers.h +++ b/tensorflow/lite/micro/memory_helpers.h @@ -12,8 +12,8 @@ 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_LITE_EXPERIMENTAL_MICRO_MEMORY_HELPERS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_HELPERS_H_ +#ifndef TENSORFLOW_LITE_MICRO_MEMORY_HELPERS_H_ +#define TENSORFLOW_LITE_MICRO_MEMORY_HELPERS_H_ #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/error_reporter.h" @@ -41,4 +41,4 @@ TfLiteStatus BytesRequiredForTensor(const tflite::Tensor& flatbuffer_tensor, } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_HELPERS_H_ +#endif // TENSORFLOW_LITE_MICRO_MEMORY_HELPERS_H_ diff --git a/tensorflow/lite/experimental/micro/memory_helpers_test.cc b/tensorflow/lite/micro/memory_helpers_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/memory_helpers_test.cc rename to tensorflow/lite/micro/memory_helpers_test.cc index a5579424d73..fbd5bea929f 100644 --- a/tensorflow/lite/experimental/micro/memory_helpers_test.cc +++ b/tensorflow/lite/micro/memory_helpers_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/memory_helpers.h" +#include "tensorflow/lite/micro/memory_helpers.h" -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/memory_planner/BUILD b/tensorflow/lite/micro/memory_planner/BUILD similarity index 85% rename from tensorflow/lite/experimental/micro/memory_planner/BUILD rename to tensorflow/lite/micro/memory_planner/BUILD index bdbd126f24d..e665c5c77c4 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/BUILD +++ b/tensorflow/lite/micro/memory_planner/BUILD @@ -1,7 +1,11 @@ load( - "//tensorflow/lite/experimental/micro/testing:micro_test.bzl", + "//tensorflow/lite/micro/testing:micro_test.bzl", "tflite_micro_cc_test", ) +load( + "//tensorflow/lite/micro:build_def.bzl", + "cc_library", +) package( default_visibility = ["//visibility:public"], @@ -13,6 +17,7 @@ cc_library( hdrs = [ "memory_planner.h", ], + build_for_embedded = True, copts = [ "-Werror", "-Wdouble-promotion", @@ -54,6 +59,7 @@ cc_library( hdrs = [ "greedy_memory_planner.h", ], + build_for_embedded = True, copts = [ "-Werror", "-Wdouble-promotion", @@ -74,7 +80,7 @@ tflite_micro_cc_test( ], deps = [ ":linear_memory_planner", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) @@ -85,6 +91,6 @@ tflite_micro_cc_test( ], deps = [ ":greedy_memory_planner", - "//tensorflow/lite/experimental/micro/testing:micro_test", + "//tensorflow/lite/micro/testing:micro_test", ], ) diff --git a/tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.cc b/tensorflow/lite/micro/memory_planner/greedy_memory_planner.cc similarity index 99% rename from tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.cc rename to tensorflow/lite/micro/memory_planner/greedy_memory_planner.cc index ab00adcea00..061ec0a7a3e 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.cc +++ b/tensorflow/lite/micro/memory_planner/greedy_memory_planner.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.h" +#include "tensorflow/lite/micro/memory_planner/greedy_memory_planner.h" namespace tflite { diff --git a/tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.h b/tensorflow/lite/micro/memory_planner/greedy_memory_planner.h similarity index 94% rename from tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.h rename to tensorflow/lite/micro/memory_planner/greedy_memory_planner.h index 2d681860529..2618f728db3 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.h +++ b/tensorflow/lite/micro/memory_planner/greedy_memory_planner.h @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_GREEDY_MEMORY_PLANNER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_GREEDY_MEMORY_PLANNER_H_ +#ifndef TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_GREEDY_MEMORY_PLANNER_H_ +#define TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_GREEDY_MEMORY_PLANNER_H_ -#include "tensorflow/lite/experimental/micro/memory_planner/memory_planner.h" +#include "tensorflow/lite/micro/memory_planner/memory_planner.h" namespace tflite { @@ -129,4 +129,4 @@ class GreedyMemoryPlanner : public MemoryPlanner { } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_GREEDY_MEMORY_PLANNER_H_ +#endif // TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_GREEDY_MEMORY_PLANNER_H_ diff --git a/tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner_test.cc b/tensorflow/lite/micro/memory_planner/greedy_memory_planner_test.cc similarity index 98% rename from tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner_test.cc rename to tensorflow/lite/micro/memory_planner/greedy_memory_planner_test.cc index c1a6c3d7239..923013845fa 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner_test.cc +++ b/tensorflow/lite/micro/memory_planner/greedy_memory_planner_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.h" +#include "tensorflow/lite/micro/memory_planner/greedy_memory_planner.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace tflite { // We don't declare this in the header since it's not a public interface, but we diff --git a/tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.cc b/tensorflow/lite/micro/memory_planner/linear_memory_planner.cc similarity index 95% rename from tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.cc rename to tensorflow/lite/micro/memory_planner/linear_memory_planner.cc index eb1eef74344..391e7ad5458 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.cc +++ b/tensorflow/lite/micro/memory_planner/linear_memory_planner.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.h" +#include "tensorflow/lite/micro/memory_planner/linear_memory_planner.h" namespace tflite { diff --git a/tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.h b/tensorflow/lite/micro/memory_planner/linear_memory_planner.h similarity index 81% rename from tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.h rename to tensorflow/lite/micro/memory_planner/linear_memory_planner.h index fe4b71ece83..cc6e18bbc02 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.h +++ b/tensorflow/lite/micro/memory_planner/linear_memory_planner.h @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_LINEAR_MEMORY_PLANNER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_LINEAR_MEMORY_PLANNER_H_ +#ifndef TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_LINEAR_MEMORY_PLANNER_H_ +#define TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_LINEAR_MEMORY_PLANNER_H_ -#include "tensorflow/lite/experimental/micro/memory_planner/memory_planner.h" +#include "tensorflow/lite/micro/memory_planner/memory_planner.h" namespace tflite { @@ -44,4 +44,4 @@ class LinearMemoryPlanner : public MemoryPlanner { } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_LINEAR_MEMORY_PLANNER_H_ +#endif // TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_LINEAR_MEMORY_PLANNER_H_ diff --git a/tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner_test.cc b/tensorflow/lite/micro/memory_planner/linear_memory_planner_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner_test.cc rename to tensorflow/lite/micro/memory_planner/linear_memory_planner_test.cc index 17cf2f3b1e0..61a914b5e91 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner_test.cc +++ b/tensorflow/lite/micro/memory_planner/linear_memory_planner_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/memory_planner/linear_memory_planner.h" +#include "tensorflow/lite/micro/memory_planner/linear_memory_planner.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/memory_planner/memory_planner.h b/tensorflow/lite/micro/memory_planner/memory_planner.h similarity index 92% rename from tensorflow/lite/experimental/micro/memory_planner/memory_planner.h rename to tensorflow/lite/micro/memory_planner/memory_planner.h index fa7fed0d152..9d5cd08468b 100644 --- a/tensorflow/lite/experimental/micro/memory_planner/memory_planner.h +++ b/tensorflow/lite/micro/memory_planner/memory_planner.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_MEMORY_PLANNER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_MEMORY_PLANNER_H_ +#ifndef TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_MEMORY_PLANNER_H_ +#define TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_MEMORY_PLANNER_H_ #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/error_reporter.h" @@ -68,4 +68,4 @@ class MemoryPlanner { } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MEMORY_PLANNER_MEMORY_PLANNER_H_ +#endif // TENSORFLOW_LITE_MICRO_MEMORY_PLANNER_MEMORY_PLANNER_H_ diff --git a/tensorflow/lite/experimental/micro/micro_allocator.cc b/tensorflow/lite/micro/micro_allocator.cc similarity index 87% rename from tensorflow/lite/experimental/micro/micro_allocator.cc rename to tensorflow/lite/micro/micro_allocator.cc index 73c2bda1d20..150c2c9eb4e 100644 --- a/tensorflow/lite/experimental/micro/micro_allocator.cc +++ b/tensorflow/lite/micro/micro_allocator.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_allocator.h" +#include "tensorflow/lite/micro/micro_allocator.h" #include @@ -21,10 +21,10 @@ limitations under the License. #include "tensorflow/lite/core/api/flatbuffer_conversions.h" #include "tensorflow/lite/core/api/op_resolver.h" #include "tensorflow/lite/core/api/tensor_utils.h" -#include "tensorflow/lite/experimental/micro/compatibility.h" -#include "tensorflow/lite/experimental/micro/memory_helpers.h" -#include "tensorflow/lite/experimental/micro/memory_planner/greedy_memory_planner.h" -#include "tensorflow/lite/experimental/micro/simple_memory_allocator.h" +#include "tensorflow/lite/micro/compatibility.h" +#include "tensorflow/lite/micro/memory_helpers.h" +#include "tensorflow/lite/micro/memory_planner/greedy_memory_planner.h" +#include "tensorflow/lite/micro/simple_memory_allocator.h" namespace tflite { @@ -107,33 +107,9 @@ MicroAllocator::MicroAllocator(TfLiteContext* context, const Model* model, "Failed to allocate memory for context->tensors, %d bytes required", sizeof(TfLiteTensor) * context_->tensors_size); } - - // Null all inputs so we can later perform a null check to avoid re-allocating - // registered pre-allocated inputs. - for (size_t i = 0; i < context_->tensors_size; ++i) { - context_->tensors[i].data.raw = nullptr; - } - active_ = true; } -TfLiteStatus MicroAllocator::RegisterPreallocatedInput(uint8_t* buffer, - size_t input_index) { - if (buffer == nullptr || input_index < 0 || - input_index >= subgraph_->inputs()->size()) { - error_reporter_->Report("Invalid pre-allocated input %d provided.", - input_index); - return kTfLiteError; - } - const flatbuffers::Vector>* buffers = - model_->buffers(); - - const int tensor_index = subgraph_->inputs()->Get(input_index); - const auto* tensor = tensors_->Get(tensor_index); - return InitializeRuntimeTensor(*tensor, buffers, error_reporter_, - &context_->tensors[tensor_index], buffer); -} - TfLiteStatus MicroAllocator::AllocateNodeAndRegistrations( const OpResolver& op_resolver, NodeAndRegistration** node_and_registrations) { @@ -224,30 +200,19 @@ TfLiteStatus MicroAllocator::FinishTensorAllocation() { return kTfLiteError; } - const size_t tensors_size = tensors_->size(); - - const flatbuffers::Vector>* buffers = - model_->buffers(); - - // Initialize runtime tensors. - for (size_t i = 0; i < tensors_size; ++i) { - auto* runtime_tensor = &context_->tensors[i]; - auto* flatbuffer_tensor = tensors_->Get(i); - - // Preallocated inputs have already been set up earlier, so skip them. - const bool is_preallocated_input = (runtime_tensor->data.raw != nullptr); - if (!is_preallocated_input) { - TF_LITE_ENSURE_STATUS(InitializeRuntimeTensor(*flatbuffer_tensor, buffers, - error_reporter_, - runtime_tensor, nullptr)); - } + // Initialize runtime tensors in context_ using the flatbuffer. + for (size_t i = 0; i < tensors_->size(); ++i) { + TF_LITE_ENSURE_STATUS( + InitializeRuntimeTensor(*tensors_->Get(i), model_->buffers(), + error_reporter_, &context_->tensors[i])); } // tensor_info is only used in this function. - auto tmp_allocator = memory_allocator_.CreateChildAllocator(); + SimpleMemoryAllocator tmp_allocator = + memory_allocator_.CreateChildAllocator(); TensorInfo* tensor_info = reinterpret_cast(tmp_allocator.AllocateFromTail( - sizeof(TensorInfo) * tensors_size, alignof(TensorInfo))); + sizeof(TensorInfo) * tensors_->size(), alignof(TensorInfo))); if (tensor_info == nullptr) { error_reporter_->Report( "Failed to allocate memory for tensor_info, %d bytes required", @@ -256,7 +221,7 @@ TfLiteStatus MicroAllocator::FinishTensorAllocation() { } // Set up the runtime data structures for all tensors. - for (size_t i = 0; i < tensors_size; ++i) { + for (size_t i = 0; i < tensors_->size(); ++i) { TensorInfo* current = &tensor_info[i]; current->flatbuffer_tensor = &(*(tensors_->Get(i))); current->runtime_tensor = &context_->tensors[i]; @@ -400,8 +365,7 @@ TfLiteStatus MicroAllocator::FinishTensorAllocation() { TfLiteStatus MicroAllocator::InitializeRuntimeTensor( const tflite::Tensor& flatbuffer_tensor, const flatbuffers::Vector>* buffers, - ErrorReporter* error_reporter, TfLiteTensor* result, - uint8_t* preallocated_buffer) { + ErrorReporter* error_reporter, TfLiteTensor* result) { if (!active_) { return kTfLiteError; } @@ -448,13 +412,6 @@ TfLiteStatus MicroAllocator::InitializeRuntimeTensor( // make a note that they will be allocated from memory. The actual // allocation won't happen until later. result->allocation_type = kTfLiteArenaRw; - if (preallocated_buffer != nullptr) { - // If the client is supplying memory for the contents of the tensor - // themselves, use it. - // TODO(petewarden): Should we store the fact this is a client-allocated - // buffer? - result->data.raw = reinterpret_cast(preallocated_buffer); - } } // Figure out what the size in bytes of the buffer is and store it. diff --git a/tensorflow/lite/experimental/micro/micro_allocator.h b/tensorflow/lite/micro/micro_allocator.h similarity index 62% rename from tensorflow/lite/experimental/micro/micro_allocator.h rename to tensorflow/lite/micro/micro_allocator.h index 9ca76222442..7f8e913f7e3 100644 --- a/tensorflow/lite/experimental/micro/micro_allocator.h +++ b/tensorflow/lite/micro/micro_allocator.h @@ -12,13 +12,13 @@ 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_LITE_EXPERIMENTAL_MICRO_MICRO_ALLOCATOR_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_ALLOCATOR_H_ +#ifndef TENSORFLOW_LITE_MICRO_MICRO_ALLOCATOR_H_ +#define TENSORFLOW_LITE_MICRO_MICRO_ALLOCATOR_H_ #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/error_reporter.h" #include "tensorflow/lite/core/api/flatbuffer_conversions.h" -#include "tensorflow/lite/experimental/micro/simple_memory_allocator.h" +#include "tensorflow/lite/micro/simple_memory_allocator.h" #include "tensorflow/lite/schema/schema_generated.h" namespace tflite { @@ -39,32 +39,18 @@ class MicroAllocator { uint8_t* tensor_arena, size_t arena_size, ErrorReporter* error_reporter); - // Specify a particular tensor as pre-allocated. This means that this tensor - // will internally point to the supplied buffer, and no new memory will be - // provided. The buffer must live at least as long as the allocator, since - // the buffer will be used every time an op is invoked which uses the - // specified tensor. Most commonly this is useful when a platform-provided - // DMA buffer is used as an input, and it is desirable to avoid unnecessarily - // allocating a new buffer and copying from the DMA buffer. The user must - // ensure the buffer is valid throughout each interpreter run, and is not - // prematurely overwritten. - TfLiteStatus RegisterPreallocatedInput(uint8_t* buffer, size_t input_index); - // Sets up all of the data structure members for a runtime tensor based on the - // contents of a serialized tensor. This method doesn't allocate any memory, - // all allocations happen subsequently in AllocateTensors. + // contents of a serialized tensor. TfLiteStatus InitializeRuntimeTensor( const tflite::Tensor& flatbuffer_tensor, const flatbuffers::Vector>* buffers, - ErrorReporter* error_reporter, TfLiteTensor* result, - uint8_t* preallocated_buffer = nullptr); + ErrorReporter* error_reporter, TfLiteTensor* result); - // Run through the model and allocate all necessary input, output and - // intermediate tensors except for those already provided via calls to - // registerPreallocatedInput. - // WARNING: doing any allocation after calling is method has the risk of - // corruption tensor data so this method is the last method to be called in - // this class. + // Runs through the model and allocates all necessary input, output and + // intermediate tensors. + // WARNING: doing any allocation after calling this method has the risk of + // corrupting tensor data so this method should be the last method to be + // called in this class. TfLiteStatus FinishTensorAllocation(); // Run through the model to allocate nodes and registrations. We need to keep @@ -90,4 +76,4 @@ class MicroAllocator { }; } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_ALLOCATOR_H_ +#endif // TENSORFLOW_LITE_MICRO_MICRO_ALLOCATOR_H_ diff --git a/tensorflow/lite/experimental/micro/micro_allocator_test.cc b/tensorflow/lite/micro/micro_allocator_test.cc similarity index 71% rename from tensorflow/lite/experimental/micro/micro_allocator_test.cc rename to tensorflow/lite/micro/micro_allocator_test.cc index b3d320ebfa9..695aff651e0 100644 --- a/tensorflow/lite/experimental/micro/micro_allocator_test.cc +++ b/tensorflow/lite/micro/micro_allocator_test.cc @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_allocator.h" +#include "tensorflow/lite/micro/micro_allocator.h" #include -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN @@ -116,44 +116,4 @@ TF_LITE_MICRO_TEST(TestFinishTensorAllocation) { context.tensors[2].data.raw); } -TF_LITE_MICRO_TEST(TestPreallocatedInput) { - const tflite::Model* model = tflite::testing::GetMockModel(); - TfLiteContext context; - constexpr size_t arena_size = 1024; - uint8_t arena[arena_size]; - tflite::MicroAllocator allocator(&context, model, arena, arena_size, - micro_test::reporter); - TF_LITE_MICRO_EXPECT_EQ(3, context.tensors_size); - - uint8_t preallocated_input_buffer[4]; - TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, allocator.RegisterPreallocatedInput( - preallocated_input_buffer, 0)); - - TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, allocator.FinishTensorAllocation()); - - constexpr int kExpectedAlignment = 4; - - TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, context.tensors[0].type); - TF_LITE_MICRO_EXPECT_EQ(1, context.tensors[0].dims->size); - TF_LITE_MICRO_EXPECT_EQ(1, context.tensors[0].dims->data[0]); - TF_LITE_MICRO_EXPECT_EQ(4, context.tensors[0].bytes); - TF_LITE_MICRO_EXPECT_EQ(preallocated_input_buffer, - context.tensors[0].data.uint8); - - TF_LITE_MICRO_EXPECT_EQ(kTfLiteUInt8, context.tensors[1].type); - TF_LITE_MICRO_EXPECT_EQ(1, context.tensors[1].dims->size); - TF_LITE_MICRO_EXPECT_EQ(1, context.tensors[1].dims->data[0]); - TF_LITE_MICRO_EXPECT_EQ(1, context.tensors[1].bytes); - TF_LITE_MICRO_EXPECT_NE(nullptr, context.tensors[1].data.raw); - - TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, context.tensors[2].type); - TF_LITE_MICRO_EXPECT_EQ(1, context.tensors[2].dims->size); - TF_LITE_MICRO_EXPECT_EQ(1, context.tensors[2].dims->data[0]); - TF_LITE_MICRO_EXPECT_EQ(4, context.tensors[2].bytes); - TF_LITE_MICRO_EXPECT_NE(nullptr, context.tensors[2].data.raw); - TF_LITE_MICRO_EXPECT_EQ( - 0, (reinterpret_cast(context.tensors[2].data.raw) % - kExpectedAlignment)); -} - TF_LITE_MICRO_TESTS_END diff --git a/tensorflow/lite/experimental/micro/micro_error_reporter.cc b/tensorflow/lite/micro/micro_error_reporter.cc similarity index 96% rename from tensorflow/lite/experimental/micro/micro_error_reporter.cc rename to tensorflow/lite/micro/micro_error_reporter.cc index 0711a05d163..e1a67856ea7 100644 --- a/tensorflow/lite/experimental/micro/micro_error_reporter.cc +++ b/tensorflow/lite/micro/micro_error_reporter.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" namespace tflite { namespace { diff --git a/tensorflow/lite/experimental/micro/micro_error_reporter.h b/tensorflow/lite/micro/micro_error_reporter.h similarity index 71% rename from tensorflow/lite/experimental/micro/micro_error_reporter.h rename to tensorflow/lite/micro/micro_error_reporter.h index 6c18367c95f..b3542e49aa2 100644 --- a/tensorflow/lite/experimental/micro/micro_error_reporter.h +++ b/tensorflow/lite/micro/micro_error_reporter.h @@ -12,13 +12,13 @@ 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_LITE_EXPERIMENTAL_MICRO_MICRO_ERROR_REPORTER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_ERROR_REPORTER_H_ +#ifndef TENSORFLOW_LITE_MICRO_MICRO_ERROR_REPORTER_H_ +#define TENSORFLOW_LITE_MICRO_MICRO_ERROR_REPORTER_H_ #include "tensorflow/lite/core/api/error_reporter.h" -#include "tensorflow/lite/experimental/micro/compatibility.h" -#include "tensorflow/lite/experimental/micro/debug_log.h" -#include "tensorflow/lite/experimental/micro/debug_log_numbers.h" +#include "tensorflow/lite/micro/compatibility.h" +#include "tensorflow/lite/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log_numbers.h" namespace tflite { @@ -33,4 +33,4 @@ class MicroErrorReporter : public ErrorReporter { } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_ERROR_REPORTER_H_ +#endif // TENSORFLOW_LITE_MICRO_MICRO_ERROR_REPORTER_H_ diff --git a/tensorflow/lite/experimental/micro/micro_error_reporter_test.cc b/tensorflow/lite/micro/micro_error_reporter_test.cc similarity index 93% rename from tensorflow/lite/experimental/micro/micro_error_reporter_test.cc rename to tensorflow/lite/micro/micro_error_reporter_test.cc index ca89de9739f..182f6e21d34 100644 --- a/tensorflow/lite/experimental/micro/micro_error_reporter_test.cc +++ b/tensorflow/lite/micro/micro_error_reporter_test.cc @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" int main(int argc, char** argv) { tflite::MicroErrorReporter micro_error_reporter; diff --git a/tensorflow/lite/experimental/micro/micro_interpreter.cc b/tensorflow/lite/micro/micro_interpreter.cc similarity index 95% rename from tensorflow/lite/experimental/micro/micro_interpreter.cc rename to tensorflow/lite/micro/micro_interpreter.cc index 7185d643514..fc91a27ee04 100644 --- a/tensorflow/lite/experimental/micro/micro_interpreter.cc +++ b/tensorflow/lite/micro/micro_interpreter.cc @@ -12,12 +12,12 @@ 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/lite/experimental/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/flatbuffer_conversions.h" -#include "tensorflow/lite/experimental/micro/compatibility.h" -#include "tensorflow/lite/experimental/micro/micro_optional_debug_tools.h" +#include "tensorflow/lite/micro/compatibility.h" +#include "tensorflow/lite/micro/micro_optional_debug_tools.h" namespace tflite { namespace { @@ -71,7 +71,8 @@ MicroInterpreter::MicroInterpreter(const Model* model, allocator_(&context_, model_, tensor_arena, tensor_arena_size, error_reporter_), tensors_allocated_(false) { - auto* subgraphs = model->subgraphs(); + const flatbuffers::Vector>* subgraphs = + model->subgraphs(); if (subgraphs->size() != 1) { error_reporter->Report("Only 1 subgraph is currently supported.\n"); initialization_status_ = kTfLiteError; @@ -138,11 +139,6 @@ void MicroInterpreter::CorrectTensorDataEndianness(T* data, int32_t size) { } } -TfLiteStatus MicroInterpreter::RegisterPreallocatedInput(uint8_t* buffer, - size_t input_index) { - return allocator_.RegisterPreallocatedInput(buffer, input_index); -} - TfLiteStatus MicroInterpreter::AllocateTensors() { TF_LITE_ENSURE_OK(&context_, allocator_.AllocateNodeAndRegistrations( op_resolver_, &node_and_registrations_)); diff --git a/tensorflow/lite/experimental/micro/micro_interpreter.h b/tensorflow/lite/micro/micro_interpreter.h similarity index 77% rename from tensorflow/lite/experimental/micro/micro_interpreter.h rename to tensorflow/lite/micro/micro_interpreter.h index f34e29e06ad..5f6a2295e9d 100644 --- a/tensorflow/lite/experimental/micro/micro_interpreter.h +++ b/tensorflow/lite/micro/micro_interpreter.h @@ -12,14 +12,14 @@ 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_LITE_EXPERIMENTAL_MICRO_MICRO_INTERPRETER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_INTERPRETER_H_ +#ifndef TENSORFLOW_LITE_MICRO_MICRO_INTERPRETER_H_ +#define TENSORFLOW_LITE_MICRO_MICRO_INTERPRETER_H_ #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/error_reporter.h" #include "tensorflow/lite/core/api/op_resolver.h" -#include "tensorflow/lite/experimental/micro/micro_allocator.h" #include "tensorflow/lite/kernels/internal/tensor_ctypes.h" +#include "tensorflow/lite/micro/micro_allocator.h" #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/type_to_tflitetype.h" @@ -39,20 +39,8 @@ class MicroInterpreter { uint8_t* tensor_arena, size_t tensor_arena_size, ErrorReporter* error_reporter); - // Specify a particular tensor as pre-allocated. This means that this tensor - // will internally point to the supplied buffer, and no new memory will be - // provided. The buffer must live at least as long as the allocator, since - // the buffer will be used every time an op is invoked which uses the - // specified tensor. Most commonly this is useful when a platform-provided - // DMA buffer is used as an input, and it is desirable to avoid unnecessarily - // allocating a new buffer and copying from the DMA buffer. The user must - // ensure the buffer is valid throughout each interpreter run, and is not - // prematurely overwritten. - TfLiteStatus RegisterPreallocatedInput(uint8_t* buffer, size_t input_index); - - // Run through the model and allocate all necessary input, output and - // intermediate tensors except for those already provided via calls to - // registerPreallocatedInput. + // Runs through the model and allocates all necessary input, output and + // intermediate tensors. TfLiteStatus AllocateTensors(); TfLiteStatus Invoke(); @@ -119,7 +107,6 @@ class MicroInterpreter { const Model* model_; const OpResolver& op_resolver_; ErrorReporter* error_reporter_; - // Explicitly initialize TfLiteContext POD struct. TfLiteContext context_ = {}; MicroAllocator allocator_; bool tensors_allocated_; @@ -133,4 +120,4 @@ class MicroInterpreter { } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_INTERPRETER_H_ +#endif // TENSORFLOW_LITE_MICRO_MICRO_INTERPRETER_H_ diff --git a/tensorflow/lite/experimental/micro/micro_interpreter_test.cc b/tensorflow/lite/micro/micro_interpreter_test.cc similarity index 65% rename from tensorflow/lite/experimental/micro/micro_interpreter_test.cc rename to tensorflow/lite/micro/micro_interpreter_test.cc index ebd12625c1b..2ee985077cc 100644 --- a/tensorflow/lite/experimental/micro/micro_interpreter_test.cc +++ b/tensorflow/lite/micro/micro_interpreter_test.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace tflite { namespace { @@ -79,7 +79,7 @@ TF_LITE_MICRO_TEST(TestInterpreter) { tflite::MicroInterpreter interpreter(model, mock_resolver, allocator_buffer, allocator_buffer_size, micro_test::reporter); - interpreter.AllocateTensors(); + TF_LITE_MICRO_EXPECT_EQ(interpreter.AllocateTensors(), kTfLiteOk); TF_LITE_MICRO_EXPECT_EQ(1, interpreter.inputs_size()); TF_LITE_MICRO_EXPECT_EQ(1, interpreter.outputs_size()); @@ -104,43 +104,4 @@ TF_LITE_MICRO_TEST(TestInterpreter) { TF_LITE_MICRO_EXPECT_EQ(42, output->data.i32[0]); } -TF_LITE_MICRO_TEST(TestInterpreterProvideInputBuffer) { - const tflite::Model* model = tflite::testing::GetMockModel(); - TF_LITE_MICRO_EXPECT_NE(nullptr, model); - tflite::MockOpResolver mock_resolver; - int32_t input_buffer = 21; - constexpr size_t allocator_buffer_size = 1024; - uint8_t allocator_buffer[allocator_buffer_size]; - tflite::MicroInterpreter interpreter(model, mock_resolver, allocator_buffer, - allocator_buffer_size, - micro_test::reporter); - interpreter.RegisterPreallocatedInput( - reinterpret_cast(&input_buffer), 0); - interpreter.AllocateTensors(); - TF_LITE_MICRO_EXPECT_EQ(1, interpreter.inputs_size()); - TF_LITE_MICRO_EXPECT_EQ(1, interpreter.outputs_size()); - - TfLiteTensor* input = interpreter.input(0); - TF_LITE_MICRO_EXPECT_NE(nullptr, input); - TF_LITE_MICRO_EXPECT_EQ(reinterpret_cast(&input_buffer), - reinterpret_cast(input->data.raw)); - TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, input->type); - TF_LITE_MICRO_EXPECT_EQ(1, input->dims->size); - TF_LITE_MICRO_EXPECT_EQ(1, input->dims->data[0]); - TF_LITE_MICRO_EXPECT_EQ(4, input->bytes); - TF_LITE_MICRO_EXPECT_NE(nullptr, input->data.i32); - TF_LITE_MICRO_EXPECT_EQ(21, *input->data.i32); - - TF_LITE_MICRO_EXPECT_EQ(kTfLiteOk, interpreter.Invoke()); - - TfLiteTensor* output = interpreter.output(0); - TF_LITE_MICRO_EXPECT_NE(nullptr, output); - TF_LITE_MICRO_EXPECT_EQ(kTfLiteInt32, output->type); - TF_LITE_MICRO_EXPECT_EQ(1, output->dims->size); - TF_LITE_MICRO_EXPECT_EQ(1, output->dims->data[0]); - TF_LITE_MICRO_EXPECT_EQ(4, output->bytes); - TF_LITE_MICRO_EXPECT_NE(nullptr, output->data.i32); - TF_LITE_MICRO_EXPECT_EQ(42, output->data.i32[0]); -} - TF_LITE_MICRO_TESTS_END diff --git a/tensorflow/lite/experimental/micro/micro_mutable_op_resolver.cc b/tensorflow/lite/micro/micro_mutable_op_resolver.cc similarity index 95% rename from tensorflow/lite/experimental/micro/micro_mutable_op_resolver.cc rename to tensorflow/lite/micro/micro_mutable_op_resolver.cc index c54e9e438d6..9b5b751d554 100644 --- a/tensorflow/lite/experimental/micro/micro_mutable_op_resolver.cc +++ b/tensorflow/lite/micro/micro_mutable_op_resolver.cc @@ -13,10 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" namespace tflite { +namespace { + +const int kDefaultOpVersions[] = {1}; + +} // namespace + const TfLiteRegistration* MicroMutableOpResolver::FindOp( tflite::BuiltinOperator op, int version) const { for (int i = 0; i < registrations_len_; ++i) { diff --git a/tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h b/tensorflow/lite/micro/micro_mutable_op_resolver.h similarity index 77% rename from tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h rename to tensorflow/lite/micro/micro_mutable_op_resolver.h index f613203909e..49761850c1d 100644 --- a/tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h +++ b/tensorflow/lite/micro/micro_mutable_op_resolver.h @@ -12,11 +12,13 @@ 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_LITE_EXPERIMENTAL_MICRO_MICRO_MUTABLE_OP_RESOLVER_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_MUTABLE_OP_RESOLVER_H_ +#ifndef TENSORFLOW_LITE_MICRO_MICRO_MUTABLE_OP_RESOLVER_H_ +#define TENSORFLOW_LITE_MICRO_MICRO_MUTABLE_OP_RESOLVER_H_ +#include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/op_resolver.h" -#include "tensorflow/lite/experimental/micro/compatibility.h" +#include "tensorflow/lite/micro/compatibility.h" +#include "tensorflow/lite/schema/schema_generated.h" #ifndef TFLITE_REGISTRATIONS_MAX #define TFLITE_REGISTRATIONS_MAX (128) @@ -24,6 +26,9 @@ limitations under the License. namespace tflite { +// Op versions discussed in this file are enumerated here: +// tensorflow/lite/tools/versioning/op_version.cc + class MicroMutableOpResolver : public OpResolver { public: const TfLiteRegistration* FindOp(tflite::BuiltinOperator op, @@ -43,4 +48,4 @@ class MicroMutableOpResolver : public OpResolver { } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_MUTABLE_OP_RESOLVER_H_ +#endif // TENSORFLOW_LITE_MICRO_MICRO_MUTABLE_OP_RESOLVER_H_ diff --git a/tensorflow/lite/experimental/micro/micro_mutable_op_resolver_test.cc b/tensorflow/lite/micro/micro_mutable_op_resolver_test.cc similarity index 95% rename from tensorflow/lite/experimental/micro/micro_mutable_op_resolver_test.cc rename to tensorflow/lite/micro/micro_mutable_op_resolver_test.cc index f551830865d..403d5dd5ce8 100644 --- a/tensorflow/lite/experimental/micro/micro_mutable_op_resolver_test.cc +++ b/tensorflow/lite/micro/micro_mutable_op_resolver_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace tflite { namespace { diff --git a/tensorflow/lite/experimental/micro/micro_optional_debug_tools.cc b/tensorflow/lite/micro/micro_optional_debug_tools.cc similarity index 98% rename from tensorflow/lite/experimental/micro/micro_optional_debug_tools.cc rename to tensorflow/lite/micro/micro_optional_debug_tools.cc index 1f6ce531f05..31a31ec90b8 100644 --- a/tensorflow/lite/experimental/micro/micro_optional_debug_tools.cc +++ b/tensorflow/lite/micro/micro_optional_debug_tools.cc @@ -12,7 +12,7 @@ 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/lite/experimental/micro/micro_optional_debug_tools.h" +#include "tensorflow/lite/micro/micro_optional_debug_tools.h" // `cinttypes` requires `__STDC_FORMAT_MACROS` to be defined to expose `PRId32`. #ifndef __STDC_FORMAT_MACROS diff --git a/tensorflow/lite/experimental/micro/micro_optional_debug_tools.h b/tensorflow/lite/micro/micro_optional_debug_tools.h similarity index 83% rename from tensorflow/lite/experimental/micro/micro_optional_debug_tools.h rename to tensorflow/lite/micro/micro_optional_debug_tools.h index 41b5a060863..70fe6f899da 100644 --- a/tensorflow/lite/experimental/micro/micro_optional_debug_tools.h +++ b/tensorflow/lite/micro/micro_optional_debug_tools.h @@ -14,10 +14,10 @@ limitations under the License. ==============================================================================*/ // Optional debugging functionality. For small sized binaries, these are not // needed. -#ifndef TENSORFLOW_LITE_MICRO_OPTIONAL_DEBUG_TOOLS_H_ -#define TENSORFLOW_LITE_MICRO_OPTIONAL_DEBUG_TOOLS_H_ +#ifndef TENSORFLOW_LITE_MICRO_MICRO_OPTIONAL_DEBUG_TOOLS_H_ +#define TENSORFLOW_LITE_MICRO_MICRO_OPTIONAL_DEBUG_TOOLS_H_ -#include "tensorflow/lite/experimental/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" namespace tflite { // Prints a dump of what tensors and what nodes are in the interpreter. @@ -37,4 +37,4 @@ struct pairTfLiteNodeAndRegistration { } // namespace tflite -#endif // TENSORFLOW_LITE_MICRO_OPTIONAL_DEBUG_TOOLS_H_ +#endif // TENSORFLOW_LITE_MICRO_MICRO_OPTIONAL_DEBUG_TOOLS_H_ diff --git a/tensorflow/lite/experimental/micro/micro_utils.cc b/tensorflow/lite/micro/micro_utils.cc similarity index 93% rename from tensorflow/lite/experimental/micro/micro_utils.cc rename to tensorflow/lite/micro/micro_utils.cc index 14e74253a35..5882eac8ce1 100644 --- a/tensorflow/lite/experimental/micro/micro_utils.cc +++ b/tensorflow/lite/micro/micro_utils.cc @@ -13,13 +13,14 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_utils.h" +#include "tensorflow/lite/micro/micro_utils.h" #include #include #include #include "tensorflow/lite/c/common.h" +#include "tensorflow/lite/kernels/op_macros.h" namespace tflite { @@ -130,15 +131,24 @@ void SignedSymmetricPerChannelQuantize(const float* values, int input_size = ElementCount(*dims); int channel_count = dims->data[quantized_dimension]; int per_channel_size = input_size / channel_count; + + int stride; + int channel_stride; + if (quantized_dimension == 0) { + stride = 1; + channel_stride = per_channel_size; + } else if (quantized_dimension == 3) { + stride = channel_count; + channel_stride = 1; + } else { + TF_LITE_FATAL("quantized dimension must be 0 or 3"); + } + + // Calculate scales for each channel. for (int channel = 0; channel < channel_count; channel++) { float min = 0; float max = 0; - int stride = 1; - for (int i = 0; i < quantized_dimension; i++) { - stride *= dims->data[i]; - } - int channel_stride = per_channel_size / stride; - // Calculate scales for each channel. + for (int i = 0; i < per_channel_size; i++) { int idx = channel * channel_stride + i * stride; min = fminf(min, values[idx]); diff --git a/tensorflow/lite/experimental/micro/micro_utils.h b/tensorflow/lite/micro/micro_utils.h similarity index 95% rename from tensorflow/lite/experimental/micro/micro_utils.h rename to tensorflow/lite/micro/micro_utils.h index 85ca07229eb..90670a2653a 100644 --- a/tensorflow/lite/experimental/micro/micro_utils.h +++ b/tensorflow/lite/micro/micro_utils.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_UTILS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_UTILS_H_ +#ifndef TENSORFLOW_LITE_MICRO_MICRO_UTILS_H_ +#define TENSORFLOW_LITE_MICRO_MICRO_UTILS_H_ #include @@ -83,4 +83,4 @@ void SymmetricDequantize(const int8_t* values, const int size, } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_MICRO_UTILS_H_ +#endif // TENSORFLOW_LITE_MICRO_MICRO_UTILS_H_ diff --git a/tensorflow/lite/experimental/micro/micro_utils_test.cc b/tensorflow/lite/micro/micro_utils_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/micro_utils_test.cc rename to tensorflow/lite/micro/micro_utils_test.cc index 5619dee2486..e33d53b1c48 100644 --- a/tensorflow/lite/experimental/micro/micro_utils_test.cc +++ b/tensorflow/lite/micro/micro_utils_test.cc @@ -13,9 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/micro_utils.h" +#include "tensorflow/lite/micro/micro_utils.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/riscv32_mcu/README.md b/tensorflow/lite/micro/riscv32_mcu/README.md similarity index 100% rename from tensorflow/lite/experimental/micro/riscv32_mcu/README.md rename to tensorflow/lite/micro/riscv32_mcu/README.md diff --git a/tensorflow/lite/experimental/micro/riscv32_mcu/debug_log.cc b/tensorflow/lite/micro/riscv32_mcu/debug_log.cc similarity index 100% rename from tensorflow/lite/experimental/micro/riscv32_mcu/debug_log.cc rename to tensorflow/lite/micro/riscv32_mcu/debug_log.cc diff --git a/tensorflow/lite/experimental/micro/simple_memory_allocator.cc b/tensorflow/lite/micro/simple_memory_allocator.cc similarity index 91% rename from tensorflow/lite/experimental/micro/simple_memory_allocator.cc rename to tensorflow/lite/micro/simple_memory_allocator.cc index 4f8a3247c61..36ceeafc9d9 100644 --- a/tensorflow/lite/experimental/micro/simple_memory_allocator.cc +++ b/tensorflow/lite/micro/simple_memory_allocator.cc @@ -13,10 +13,10 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/simple_memory_allocator.h" +#include "tensorflow/lite/micro/simple_memory_allocator.h" #include "tensorflow/lite/core/api/flatbuffer_conversions.h" -#include "tensorflow/lite/experimental/micro/memory_helpers.h" +#include "tensorflow/lite/micro/memory_helpers.h" namespace tflite { @@ -43,6 +43,7 @@ SimpleMemoryAllocator SimpleMemoryAllocator::CreateChildAllocator() { // is not what we expected. SimpleMemoryAllocator child = *this; child.parent_allocator_ = this; + // With C++ copy elision, &child should be available after return. has_child_allocator_ = true; return child; } diff --git a/tensorflow/lite/experimental/micro/simple_memory_allocator.h b/tensorflow/lite/micro/simple_memory_allocator.h similarity index 91% rename from tensorflow/lite/experimental/micro/simple_memory_allocator.h rename to tensorflow/lite/micro/simple_memory_allocator.h index bcf75a716ae..8a4f867c518 100644 --- a/tensorflow/lite/experimental/micro/simple_memory_allocator.h +++ b/tensorflow/lite/micro/simple_memory_allocator.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_SIMPLE_MEMORY_ALLOCATOR_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_SIMPLE_MEMORY_ALLOCATOR_H_ +#ifndef TENSORFLOW_LITE_MICRO_SIMPLE_MEMORY_ALLOCATOR_H_ +#define TENSORFLOW_LITE_MICRO_SIMPLE_MEMORY_ALLOCATOR_H_ #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/error_reporter.h" @@ -60,4 +60,4 @@ class SimpleMemoryAllocator { } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_SIMPLE_MEMORY_ALLOCATOR_H_ +#endif // TENSORFLOW_LITE_MICRO_SIMPLE_MEMORY_ALLOCATOR_H_ diff --git a/tensorflow/lite/experimental/micro/simple_memory_allocator_test.cc b/tensorflow/lite/micro/simple_memory_allocator_test.cc similarity index 93% rename from tensorflow/lite/experimental/micro/simple_memory_allocator_test.cc rename to tensorflow/lite/micro/simple_memory_allocator_test.cc index 152a908f227..92c63661bde 100644 --- a/tensorflow/lite/experimental/micro/simple_memory_allocator_test.cc +++ b/tensorflow/lite/micro/simple_memory_allocator_test.cc @@ -13,12 +13,12 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/simple_memory_allocator.h" +#include "tensorflow/lite/micro/simple_memory_allocator.h" #include -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/sparkfun_edge/debug_log.cc b/tensorflow/lite/micro/sparkfun_edge/debug_log.cc similarity index 95% rename from tensorflow/lite/experimental/micro/sparkfun_edge/debug_log.cc rename to tensorflow/lite/micro/sparkfun_edge/debug_log.cc index 61b82fc68f5..1dc15aba529 100644 --- a/tensorflow/lite/experimental/micro/sparkfun_edge/debug_log.cc +++ b/tensorflow/lite/micro/sparkfun_edge/debug_log.cc @@ -17,7 +17,7 @@ limitations under the License. // SparkFun Edge microcontroller. The same should work for other targets using // the Ambiq Apollo 3. -#include "tensorflow/lite/experimental/micro/debug_log.h" +#include "tensorflow/lite/micro/debug_log.h" #include "am_bsp.h" // NOLINT #include "am_util.h" // NOLINT diff --git a/tensorflow/lite/experimental/micro/test_helpers.cc b/tensorflow/lite/micro/test_helpers.cc similarity index 98% rename from tensorflow/lite/experimental/micro/test_helpers.cc rename to tensorflow/lite/micro/test_helpers.cc index a1b9801ffc9..587571ed727 100644 --- a/tensorflow/lite/experimental/micro/test_helpers.cc +++ b/tensorflow/lite/micro/test_helpers.cc @@ -13,11 +13,11 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/test_helpers.h" +#include "tensorflow/lite/micro/test_helpers.h" #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/tensor_utils.h" -#include "tensorflow/lite/experimental/micro/micro_utils.h" +#include "tensorflow/lite/micro/micro_utils.h" namespace tflite { namespace testing { @@ -385,6 +385,10 @@ TfLiteTensor CreateSymmetricPerChannelQuantizedTensor( SignedSymmetricPerChannelQuantize(input, dims, quantized_dimension, quantized, &scales[1]); + for (int i = 0; i < channel_count; i++) { + zero_points[i + 1] = 0; + } + affine_quant->scale = FloatArrayFromFloats(scales); affine_quant->zero_point = IntArrayFromInts(zero_points); affine_quant->quantized_dimension = quantized_dimension; diff --git a/tensorflow/lite/experimental/micro/test_helpers.h b/tensorflow/lite/micro/test_helpers.h similarity index 96% rename from tensorflow/lite/experimental/micro/test_helpers.h rename to tensorflow/lite/micro/test_helpers.h index 9a74d9b34ce..f41f5151bc7 100644 --- a/tensorflow/lite/experimental/micro/test_helpers.h +++ b/tensorflow/lite/micro/test_helpers.h @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TEST_HELPERS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TEST_HELPERS_H_ +#ifndef TENSORFLOW_LITE_MICRO_TEST_HELPERS_H_ +#define TENSORFLOW_LITE_MICRO_TEST_HELPERS_H_ // Useful functions for writing tests. @@ -105,4 +105,4 @@ TfLiteTensor CreateSymmetricPerChannelQuantizedTensor( } // namespace testing } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TEST_HELPERS_H_ +#endif // TENSORFLOW_LITE_MICRO_TEST_HELPERS_H_ diff --git a/tensorflow/lite/experimental/micro/testing/BUILD b/tensorflow/lite/micro/testing/BUILD similarity index 73% rename from tensorflow/lite/experimental/micro/testing/BUILD rename to tensorflow/lite/micro/testing/BUILD index 70a1bf4aba9..dec646e3347 100644 --- a/tensorflow/lite/experimental/micro/testing/BUILD +++ b/tensorflow/lite/micro/testing/BUILD @@ -14,7 +14,7 @@ cc_library( deps = [ "//tensorflow/lite/c:common", "//tensorflow/lite/core/api", - "//tensorflow/lite/experimental/micro:micro_framework", - "//tensorflow/lite/experimental/micro:micro_utils", + "//tensorflow/lite/micro:micro_framework", + "//tensorflow/lite/micro:micro_utils", ], ) diff --git a/tensorflow/lite/experimental/micro/testing/Dockerfile.bluepill b/tensorflow/lite/micro/testing/Dockerfile.bluepill similarity index 100% rename from tensorflow/lite/experimental/micro/testing/Dockerfile.bluepill rename to tensorflow/lite/micro/testing/Dockerfile.bluepill diff --git a/tensorflow/lite/experimental/micro/testing/Dockerfile.riscv b/tensorflow/lite/micro/testing/Dockerfile.riscv similarity index 100% rename from tensorflow/lite/experimental/micro/testing/Dockerfile.riscv rename to tensorflow/lite/micro/testing/Dockerfile.riscv diff --git a/tensorflow/lite/experimental/micro/testing/bluepill.resc b/tensorflow/lite/micro/testing/bluepill.resc similarity index 100% rename from tensorflow/lite/experimental/micro/testing/bluepill.resc rename to tensorflow/lite/micro/testing/bluepill.resc diff --git a/tensorflow/lite/experimental/micro/testing/bluepill.robot b/tensorflow/lite/micro/testing/bluepill.robot similarity index 100% rename from tensorflow/lite/experimental/micro/testing/bluepill.robot rename to tensorflow/lite/micro/testing/bluepill.robot diff --git a/tensorflow/lite/experimental/micro/testing/leon_commands b/tensorflow/lite/micro/testing/leon_commands similarity index 100% rename from tensorflow/lite/experimental/micro/testing/leon_commands rename to tensorflow/lite/micro/testing/leon_commands diff --git a/tensorflow/lite/experimental/micro/testing/micro_test.bzl b/tensorflow/lite/micro/testing/micro_test.bzl similarity index 96% rename from tensorflow/lite/experimental/micro/testing/micro_test.bzl rename to tensorflow/lite/micro/testing/micro_test.bzl index a19664c1b74..532a1a16ac6 100644 --- a/tensorflow/lite/experimental/micro/testing/micro_test.bzl +++ b/tensorflow/lite/micro/testing/micro_test.bzl @@ -52,7 +52,7 @@ def tflite_micro_cc_test( name = name, size = size, srcs = [ - "//tensorflow/lite/experimental/micro/testing:test_linux_binary.sh", + "//tensorflow/lite/micro/testing:test_linux_binary.sh", ], args = [ native.package_name() + "/" + name + "_binary", diff --git a/tensorflow/lite/experimental/micro/testing/micro_test.h b/tensorflow/lite/micro/testing/micro_test.h similarity index 97% rename from tensorflow/lite/experimental/micro/testing/micro_test.h rename to tensorflow/lite/micro/testing/micro_test.h index 4eddc035451..72c3400478d 100644 --- a/tensorflow/lite/experimental/micro/testing/micro_test.h +++ b/tensorflow/lite/micro/testing/micro_test.h @@ -51,10 +51,10 @@ limitations under the License. // all on systems that struggle to run more conventional approaches, so use with // caution! -#ifndef TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TESTING_MICRO_TEST_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TESTING_MICRO_TEST_H_ +#ifndef TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_ +#define TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_ -#include "tensorflow/lite/experimental/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_error_reporter.h" namespace micro_test { extern int tests_passed; @@ -207,4 +207,4 @@ extern tflite::ErrorReporter* reporter; } \ } while (false) -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TESTING_MICRO_TEST_H_ +#endif // TENSORFLOW_LITE_MICRO_TESTING_MICRO_TEST_H_ diff --git a/tensorflow/lite/experimental/micro/testing/sifive_fe310.resc b/tensorflow/lite/micro/testing/sifive_fe310.resc similarity index 80% rename from tensorflow/lite/experimental/micro/testing/sifive_fe310.resc rename to tensorflow/lite/micro/testing/sifive_fe310.resc index c84ce5091c7..b2bd20cc951 100644 --- a/tensorflow/lite/experimental/micro/testing/sifive_fe310.resc +++ b/tensorflow/lite/micro/testing/sifive_fe310.resc @@ -7,7 +7,7 @@ using sysbus mach create $name machine LoadPlatformDescription @platforms/cpus/sifive-fe310.repl -$bin?=@/workspace/tensorflow/lite/experimental/micro/tools/make/gen/riscv32_mcu_riscv32_mcu/bin/micro_speech_test +$bin?=@/workspace/tensorflow/lite/micro/tools/make/gen/riscv32_mcu_riscv32_mcu/bin/micro_speech_test showAnalyzer uart0 Antmicro.Renode.Analyzers.LoggingUartAnalyzer logFile @/tmp/renode_riscv_log.txt diff --git a/tensorflow/lite/experimental/micro/testing/test_bluepill_binary.sh b/tensorflow/lite/micro/testing/test_bluepill_binary.sh similarity index 86% rename from tensorflow/lite/experimental/micro/testing/test_bluepill_binary.sh rename to tensorflow/lite/micro/testing/test_bluepill_binary.sh index 5bb4588c463..a9608f2c4d4 100755 --- a/tensorflow/lite/experimental/micro/testing/test_bluepill_binary.sh +++ b/tensorflow/lite/micro/testing/test_bluepill_binary.sh @@ -31,8 +31,8 @@ declare -r MICRO_LOG_FILENAME=${MICRO_LOG_PATH}/logs.txt mkdir -p ${MICRO_LOG_PATH} docker build -t renode_bluepill \ - -f ${ROOT_DIR}/tensorflow/lite/experimental/micro/testing/Dockerfile.bluepill \ - ${ROOT_DIR}/tensorflow/lite/experimental/micro/testing/ + -f ${ROOT_DIR}/tensorflow/lite/micro/testing/Dockerfile.bluepill \ + ${ROOT_DIR}/tensorflow/lite/micro/testing/ exit_code=0 # running in `if` to avoid setting +e @@ -41,10 +41,10 @@ if ! docker run \ -v ${ROOT_DIR}:/workspace \ -v /tmp:/tmp \ -e BIN=/workspace/$1 \ - -e SCRIPT=/workspace/tensorflow/lite/experimental/micro/testing/bluepill.resc \ + -e SCRIPT=/workspace/tensorflow/lite/micro/testing/bluepill.resc \ -e EXPECTED="$2" \ -it renode_bluepill \ - /bin/bash -c "/opt/renode/tests/test.sh /workspace/tensorflow/lite/experimental/micro/testing/bluepill.robot 2>&1 >${MICRO_LOG_FILENAME}" + /bin/bash -c "/opt/renode/tests/test.sh /workspace/tensorflow/lite/micro/testing/bluepill.robot 2>&1 >${MICRO_LOG_FILENAME}" then exit_code=1 fi diff --git a/tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh b/tensorflow/lite/micro/testing/test_ecm3531_binary.sh similarity index 100% rename from tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh rename to tensorflow/lite/micro/testing/test_ecm3531_binary.sh diff --git a/tensorflow/lite/experimental/micro/testing/test_leon_binary.sh b/tensorflow/lite/micro/testing/test_leon_binary.sh similarity index 94% rename from tensorflow/lite/experimental/micro/testing/test_leon_binary.sh rename to tensorflow/lite/micro/testing/test_leon_binary.sh index 249ddae4857..5163c45eca1 100755 --- a/tensorflow/lite/experimental/micro/testing/test_leon_binary.sh +++ b/tensorflow/lite/micro/testing/test_leon_binary.sh @@ -32,7 +32,7 @@ mkdir -p ${MICRO_LOG_PATH} SCRIPT_PATH="`dirname \"$BASH_SOURCE\"`" SCRIPT_PATH="`( cd \"$SCRIPT_PATH\" && pwd )`" LEON_COMMANDS="$SCRIPT_PATH/leon_commands" -TSIM_PATH="tensorflow/lite/experimental/micro/tools/make/downloads/tsim/tsim/linux-x64/tsim-leon3" +TSIM_PATH="tensorflow/lite/micro/tools/make/downloads/tsim/tsim/linux-x64/tsim-leon3" ${TSIM_PATH} $1 -c ${LEON_COMMANDS} 2>&1 | tee ${MICRO_LOG_FILENAME} diff --git a/tensorflow/lite/experimental/micro/testing/test_linux_binary.sh b/tensorflow/lite/micro/testing/test_linux_binary.sh similarity index 92% rename from tensorflow/lite/experimental/micro/testing/test_linux_binary.sh rename to tensorflow/lite/micro/testing/test_linux_binary.sh index 731ca5a5ffd..1e967be1f61 100755 --- a/tensorflow/lite/experimental/micro/testing/test_linux_binary.sh +++ b/tensorflow/lite/micro/testing/test_linux_binary.sh @@ -39,7 +39,7 @@ trap 'if [[ $? -eq 139 ]]; then echo "Segmentation fault" >> ${MICRO_LOG_FILENAM # This trap statement prevents the bash script from segfaulting with a cryptic # message like: -# tensorflow/lite/experimental/micro/testing/test_linux_binary.sh: line 44: 210514 Segmentation fault $1 > ${MICRO_LOG_FILENAME} 2>&1 +# tensorflow/lite/micro/testing/test_linux_binary.sh: line 44: 210514 Segmentation fault $1 > ${MICRO_LOG_FILENAME} 2>&1 # What we get instead is purely another Segmentation fault text in the output. trap '' SEGV diff --git a/tensorflow/lite/experimental/micro/testing/test_utils.h b/tensorflow/lite/micro/testing/test_utils.h similarity index 96% rename from tensorflow/lite/experimental/micro/testing/test_utils.h rename to tensorflow/lite/micro/testing/test_utils.h index 502e06f5c05..6b75f6b9e00 100644 --- a/tensorflow/lite/experimental/micro/testing/test_utils.h +++ b/tensorflow/lite/micro/testing/test_utils.h @@ -12,8 +12,8 @@ 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_LITE_EXPERIMENTAL_MICRO_TESTING_TEST_UTILS_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TESTING_TEST_UTILS_H_ +#ifndef TENSORFLOW_LITE_MICRO_TESTING_TEST_UTILS_H_ +#define TENSORFLOW_LITE_MICRO_TESTING_TEST_UTILS_H_ #include #include @@ -22,9 +22,9 @@ limitations under the License. #include "tensorflow/lite/c/common.h" #include "tensorflow/lite/core/api/tensor_utils.h" -#include "tensorflow/lite/experimental/micro/micro_utils.h" -#include "tensorflow/lite/experimental/micro/test_helpers.h" -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/micro_utils.h" +#include "tensorflow/lite/micro/test_helpers.h" +#include "tensorflow/lite/micro/testing/micro_test.h" namespace tflite { namespace testing { @@ -270,4 +270,4 @@ inline TfLiteTensor CreateTensor(std::initializer_list data, } // namespace testing } // namespace tflite -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TESTING_TEST_UTILS_H_ +#endif // TENSORFLOW_LITE_MICRO_TESTING_TEST_UTILS_H_ diff --git a/tensorflow/lite/experimental/micro/testing/test_xtensa_xpg_binary.sh b/tensorflow/lite/micro/testing/test_xtensa_xpg_binary.sh similarity index 100% rename from tensorflow/lite/experimental/micro/testing/test_xtensa_xpg_binary.sh rename to tensorflow/lite/micro/testing/test_xtensa_xpg_binary.sh diff --git a/tensorflow/lite/experimental/micro/testing_helpers_test.cc b/tensorflow/lite/micro/testing_helpers_test.cc similarity index 97% rename from tensorflow/lite/experimental/micro/testing_helpers_test.cc rename to tensorflow/lite/micro/testing_helpers_test.cc index 48f46c64496..a7fc2996eb9 100644 --- a/tensorflow/lite/experimental/micro/testing_helpers_test.cc +++ b/tensorflow/lite/micro/testing_helpers_test.cc @@ -13,8 +13,8 @@ See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ -#include "tensorflow/lite/experimental/micro/testing/micro_test.h" -#include "tensorflow/lite/experimental/micro/testing/test_utils.h" +#include "tensorflow/lite/micro/testing/micro_test.h" +#include "tensorflow/lite/micro/testing/test_utils.h" TF_LITE_MICRO_TESTS_BEGIN diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/ci_build_micro_projects.sh b/tensorflow/lite/micro/tools/ci_build/ci_build_micro_projects.sh similarity index 86% rename from tensorflow/lite/experimental/micro/tools/ci_build/ci_build_micro_projects.sh rename to tensorflow/lite/micro/tools/ci_build/ci_build_micro_projects.sh index 32291a423a9..de5b63d964d 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/ci_build_micro_projects.sh +++ b/tensorflow/lite/micro/tools/ci_build/ci_build_micro_projects.sh @@ -25,14 +25,14 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR=${SCRIPT_DIR}/../../../../../.. +ROOT_DIR=${SCRIPT_DIR}/../../../../.. cd ${ROOT_DIR} pwd -make -f tensorflow/lite/experimental/micro/tools/make/Makefile \ +make -f tensorflow/lite/micro/tools/make/Makefile \ TARGET=${1} \ TAGS="${2}" \ generate_projects # Needed to solve CI build bug triggered by files added to source tree. -make -f tensorflow/lite/experimental/micro/tools/make/Makefile clean_downloads +make -f tensorflow/lite/micro/tools/make/Makefile clean_downloads diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/helper_functions.sh b/tensorflow/lite/micro/tools/ci_build/helper_functions.sh similarity index 100% rename from tensorflow/lite/experimental/micro/tools/ci_build/helper_functions.sh rename to tensorflow/lite/micro/tools/ci_build/helper_functions.sh diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/install_arduino_cli.sh b/tensorflow/lite/micro/tools/ci_build/install_arduino_cli.sh similarity index 100% rename from tensorflow/lite/experimental/micro/tools/ci_build/install_arduino_cli.sh rename to tensorflow/lite/micro/tools/ci_build/install_arduino_cli.sh diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/install_mbed_cli.sh b/tensorflow/lite/micro/tools/ci_build/install_mbed_cli.sh similarity index 95% rename from tensorflow/lite/experimental/micro/tools/ci_build/install_mbed_cli.sh rename to tensorflow/lite/micro/tools/ci_build/install_mbed_cli.sh index 7e6a8cdf18e..801a0f5f01c 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/install_mbed_cli.sh +++ b/tensorflow/lite/micro/tools/ci_build/install_mbed_cli.sh @@ -15,6 +15,4 @@ # ============================================================================== # # Installs the latest Mbed command-line toolchain. - pip install mbed-cli -apt install -y gcc-arm-none-eabi diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_all.sh b/tensorflow/lite/micro/tools/ci_build/test_all.sh similarity index 78% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_all.sh rename to tensorflow/lite/micro/tools/ci_build/test_all.sh index 5dbaba1920d..28358610e96 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_all.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_all.sh @@ -20,11 +20,11 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR=${SCRIPT_DIR}/../../../../../.. +ROOT_DIR=${SCRIPT_DIR}/../../../../.. cd ${ROOT_DIR} pwd -make -f tensorflow/lite/experimental/micro/tools/make/Makefile \ +make -f tensorflow/lite/micro/tools/make/Makefile \ clean clean_downloads # Add all the test scripts for the various supported platforms here. This @@ -35,15 +35,18 @@ make -f tensorflow/lite/experimental/micro/tools/make/Makefile \ echo "Starting to run micro tests at `date`" echo "Running Arduino tests at `date`" -tensorflow/lite/experimental/micro/tools/ci_build/test_arduino.sh +tensorflow/lite/micro/tools/ci_build/test_arduino.sh echo "Running bluepill tests at `date`" -tensorflow/lite/experimental/micro/tools/ci_build/test_bluepill.sh +tensorflow/lite/micro/tools/ci_build/test_bluepill.sh + +echo "Running mbed tests at `date`" +tensorflow/lite/micro/tools/ci_build/test_mbed.sh PRESUBMIT echo "Running Sparkfun tests at `date`" -tensorflow/lite/experimental/micro/tools/ci_build/test_sparkfun.sh +tensorflow/lite/micro/tools/ci_build/test_sparkfun.sh echo "Running x86 tests at `date`" -tensorflow/lite/experimental/micro/tools/ci_build/test_x86.sh +tensorflow/lite/micro/tools/ci_build/test_x86.sh echo "Finished all micro tests at `date`" diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_arduino.sh b/tensorflow/lite/micro/tools/ci_build/test_arduino.sh similarity index 66% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_arduino.sh rename to tensorflow/lite/micro/tools/ci_build/test_arduino.sh index 86f8cbcbff3..996612a977b 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_arduino.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_arduino.sh @@ -20,22 +20,22 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR=${SCRIPT_DIR}/../../../../../.. +ROOT_DIR=${SCRIPT_DIR}/../../../../.. cd ${ROOT_DIR} -source tensorflow/lite/experimental/micro/tools/ci_build/helper_functions.sh +source tensorflow/lite/micro/tools/ci_build/helper_functions.sh -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile clean +readable_run make -f tensorflow/lite/micro/tools/make/Makefile clean TARGET=arduino # TODO(b/143715361): parallel builds do not work with generated files right now. -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile \ +readable_run make -f tensorflow/lite/micro/tools/make/Makefile \ TARGET=${TARGET} \ TAGS="portable_optimized" \ generate_arduino_zip -readable_run tensorflow/lite/experimental/micro/tools/ci_build/install_arduino_cli.sh +readable_run tensorflow/lite/micro/tools/ci_build/install_arduino_cli.sh -readable_run tensorflow/lite/experimental/micro/tools/ci_build/test_arduino_library.sh \ - tensorflow/lite/experimental/micro/tools/make/gen/arduino_x86_64/prj/tensorflow_lite.zip +readable_run tensorflow/lite/micro/tools/ci_build/test_arduino_library.sh \ + tensorflow/lite/micro/tools/make/gen/arduino_x86_64/prj/tensorflow_lite.zip diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_arduino_library.sh b/tensorflow/lite/micro/tools/ci_build/test_arduino_library.sh similarity index 97% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_arduino_library.sh rename to tensorflow/lite/micro/tools/ci_build/test_arduino_library.sh index 04a5a617655..ada030bb7c8 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_arduino_library.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_arduino_library.sh @@ -23,6 +23,7 @@ set -e ARDUINO_HOME_DIR=${HOME}/Arduino ARDUINO_LIBRARIES_DIR=${ARDUINO_HOME_DIR}/libraries ARDUINO_CLI_TOOL=/tmp/arduino-cli +# Necessary due to bug in arduino-cli that allows it to build files in pwd TEMP_BUILD_DIR=/tmp/tflite-arduino-build LIBRARY_ZIP=${1} diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_bluepill.sh b/tensorflow/lite/micro/tools/ci_build/test_bluepill.sh similarity index 70% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_bluepill.sh rename to tensorflow/lite/micro/tools/ci_build/test_bluepill.sh index b20d5712df2..fc0fc18817c 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_bluepill.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_bluepill.sh @@ -19,19 +19,19 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR=${SCRIPT_DIR}/../../../../../.. +ROOT_DIR=${SCRIPT_DIR}/../../../../.. cd ${ROOT_DIR} pwd -source tensorflow/lite/experimental/micro/tools/ci_build/helper_functions.sh +source tensorflow/lite/micro/tools/ci_build/helper_functions.sh -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile clean +readable_run make -f tensorflow/lite/micro/tools/make/Makefile clean TARGET=bluepill # TODO(b/143715361): downloading first to allow for parallel builds. -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=${TARGET} third_party_downloads +readable_run make -f tensorflow/lite/micro/tools/make/Makefile TARGET=${TARGET} third_party_downloads # TODO(b/143286954): Run all the tests once they pass. -readable_run make -j8 -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=${TARGET} kernel_add_test +readable_run make -j8 -f tensorflow/lite/micro/tools/make/Makefile TARGET=${TARGET} kernel_add_test diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_mbed.sh b/tensorflow/lite/micro/tools/ci_build/test_mbed.sh similarity index 54% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_mbed.sh rename to tensorflow/lite/micro/tools/ci_build/test_mbed.sh index c4a25a4bc5f..894c85ad811 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_mbed.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_mbed.sh @@ -13,28 +13,48 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== + +# +# This script takes a single argument to differentiate between running it as +# part of presubmit checks or not. +# +# This will generate a subset of targets: +# test_mbed.sh PRESUBMIT +# +# This will run generate all the targets: +# test_mbed.sh # -# Creates the project file distributions for the TensorFlow Lite Micro test and -# example targets aimed at embedded platforms. set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR=${SCRIPT_DIR}/../../../../../.. +ROOT_DIR=${SCRIPT_DIR}/../../../../.. cd ${ROOT_DIR} pwd -make -f tensorflow/lite/experimental/micro/tools/make/Makefile \ - clean clean_downloads +source tensorflow/lite/micro/tools/ci_build/helper_functions.sh -make -f tensorflow/lite/experimental/micro/tools/make/Makefile \ - TARGET=mbed \ +readable_run make -f tensorflow/lite/micro/tools/make/Makefile clean + +TARGET=mbed + +# We limit the number of projects that we build as part of the presubmit checks +# to keep the overall time low, but build everything as part of the nightly +# builds. +if [[ ${1} == "PRESUBMIT" ]]; then + PROJECTS="generate_hello_world_mbed_project generate_micro_speech_mbed_project" +else + PROJECTS=generate_projects +fi + +make -f tensorflow/lite/micro/tools/make/Makefile \ + TARGET=${TARGET} \ TAGS="portable_optimized disco_f746ng" \ - generate_projects + ${PROJECTS} -tensorflow/lite/experimental/micro/tools/ci_build/install_mbed_cli.sh +readable_run tensorflow/lite/micro/tools/ci_build/install_mbed_cli.sh -for PROJECT_PATH in tensorflow/lite/experimental/micro/tools/make/gen/mbed_*/prj/*/mbed; do +for PROJECT_PATH in tensorflow/lite/micro/tools/make/gen/mbed_*/prj/*/mbed; do PROJECT_PARENT_DIR=$(dirname ${PROJECT_PATH}) PROJECT_NAME=$(basename ${PROJECT_PARENT_DIR}) # Don't try to build and package up test projects, because there are too many. @@ -45,8 +65,5 @@ for PROJECT_PATH in tensorflow/lite/experimental/micro/tools/make/gen/mbed_*/prj pushd ${PROJECT_PARENT_DIR} zip -q -r ${PROJECT_NAME}.zip ${PROJECT_NAME} popd - tensorflow/lite/experimental/micro/tools/ci_build/test_mbed_library.sh ${PROJECT_PATH} + readable_run tensorflow/lite/micro/tools/ci_build/test_mbed_library.sh ${PROJECT_PATH} done - -# Needed to solve CI build bug triggered by files added to source tree. -make -f tensorflow/lite/experimental/micro/tools/make/Makefile clean_downloads diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_mbed_library.sh b/tensorflow/lite/micro/tools/ci_build/test_mbed_library.sh similarity index 93% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_mbed_library.sh rename to tensorflow/lite/micro/tools/ci_build/test_mbed_library.sh index 9c2a332b522..3bf215568b9 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_mbed_library.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_mbed_library.sh @@ -20,6 +20,7 @@ set -e +PATH=`pwd`/tensorflow/lite/micro/tools/make/downloads/gcc_embedded/bin:${PATH} cd ${1} mbed config root . diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_sparkfun.sh b/tensorflow/lite/micro/tools/ci_build/test_sparkfun.sh similarity index 68% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_sparkfun.sh rename to tensorflow/lite/micro/tools/ci_build/test_sparkfun.sh index 65a2e6914a0..f4250850fdb 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_sparkfun.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_sparkfun.sh @@ -19,15 +19,15 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR=${SCRIPT_DIR}/../../../../../.. +ROOT_DIR=${SCRIPT_DIR}/../../../../.. cd ${ROOT_DIR} -source tensorflow/lite/experimental/micro/tools/ci_build/helper_functions.sh +source tensorflow/lite/micro/tools/ci_build/helper_functions.sh -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile clean +readable_run make -f tensorflow/lite/micro/tools/make/Makefile clean TARGET=sparkfun_edge # TODO(b/143715361): downloading first to allow for parallel builds. -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=${TARGET} third_party_downloads -readable_run make -j8 -f tensorflow/lite/experimental/micro/tools/make/Makefile TARGET=${TARGET} micro_speech_bin +readable_run make -f tensorflow/lite/micro/tools/make/Makefile TARGET=${TARGET} third_party_downloads +readable_run make -j8 -f tensorflow/lite/micro/tools/make/Makefile TARGET=${TARGET} micro_speech_bin diff --git a/tensorflow/lite/experimental/micro/tools/ci_build/test_x86.sh b/tensorflow/lite/micro/tools/ci_build/test_x86.sh similarity index 70% rename from tensorflow/lite/experimental/micro/tools/ci_build/test_x86.sh rename to tensorflow/lite/micro/tools/ci_build/test_x86.sh index 3df34700d87..48ef94aaa21 100755 --- a/tensorflow/lite/experimental/micro/tools/ci_build/test_x86.sh +++ b/tensorflow/lite/micro/tools/ci_build/test_x86.sh @@ -19,13 +19,13 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR=${SCRIPT_DIR}/../../../../../.. +ROOT_DIR=${SCRIPT_DIR}/../../../../.. cd ${ROOT_DIR} -source tensorflow/lite/experimental/micro/tools/ci_build/helper_functions.sh +source tensorflow/lite/micro/tools/ci_build/helper_functions.sh -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile clean +readable_run make -f tensorflow/lite/micro/tools/make/Makefile clean # TODO(b/143715361): downloading first to allow for parallel builds. -readable_run make -f tensorflow/lite/experimental/micro/tools/make/Makefile third_party_downloads -readable_run make -s -j8 -f tensorflow/lite/experimental/micro/tools/make/Makefile test +readable_run make -f tensorflow/lite/micro/tools/make/Makefile third_party_downloads +readable_run make -s -j8 -f tensorflow/lite/micro/tools/make/Makefile test diff --git a/tensorflow/lite/experimental/micro/tools/make/.gitignore b/tensorflow/lite/micro/tools/make/.gitignore similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/.gitignore rename to tensorflow/lite/micro/tools/make/.gitignore diff --git a/tensorflow/lite/experimental/micro/tools/make/Makefile b/tensorflow/lite/micro/tools/make/Makefile similarity index 90% rename from tensorflow/lite/experimental/micro/tools/make/Makefile rename to tensorflow/lite/micro/tools/make/Makefile index ee291427a5b..224ee879cb5 100644 --- a/tensorflow/lite/experimental/micro/tools/make/Makefile +++ b/tensorflow/lite/micro/tools/make/Makefile @@ -3,7 +3,7 @@ ifneq (3.82,$(firstword $(sort $(MAKE_VERSION) 3.82))) $(error "Requires make version 3.82 or later (current is $(MAKE_VERSION))") endif -MAKEFILE_DIR := tensorflow/lite/experimental/micro/tools/make +MAKEFILE_DIR := tensorflow/lite/micro/tools/make # Pull in some convenience functions. include $(MAKEFILE_DIR)/helper_functions.inc @@ -61,7 +61,7 @@ PROJECT_INCLUDES := \ third_party/gemmlowp \ third_party/flatbuffers/include -TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_linux_binary.sh +TEST_SCRIPT := tensorflow/lite/micro/testing/test_linux_binary.sh MICROLITE_LIBS := -lm @@ -82,17 +82,17 @@ CC_PREFIX := MICROLITE_LIB_NAME := libtensorflow-microlite.a MICROLITE_TEST_SRCS := \ -$(wildcard tensorflow/lite/experimental/micro/*test.cc) \ -$(wildcard tensorflow/lite/experimental/micro/kernels/*test.cc) \ -$(wildcard tensorflow/lite/experimental/micro/memory_planner/*test.cc) +$(wildcard tensorflow/lite/micro/*test.cc) \ +$(wildcard tensorflow/lite/micro/kernels/*test.cc) \ +$(wildcard tensorflow/lite/micro/memory_planner/*test.cc) MICROLITE_TEST_HDRS := \ -$(wildcard tensorflow/lite/experimental/micro/testing/*.h) +$(wildcard tensorflow/lite/micro/testing/*.h) MICROLITE_CC_BASE_SRCS := \ -$(wildcard tensorflow/lite/experimental/micro/*.cc) \ -$(wildcard tensorflow/lite/experimental/micro/kernels/*.cc) \ -$(wildcard tensorflow/lite/experimental/micro/memory_planner/*.cc) \ +$(wildcard tensorflow/lite/micro/*.cc) \ +$(wildcard tensorflow/lite/micro/kernels/*.cc) \ +$(wildcard tensorflow/lite/micro/memory_planner/*.cc) \ tensorflow/lite/c/common.c \ tensorflow/lite/core/api/error_reporter.cc \ tensorflow/lite/core/api/flatbuffer_conversions.cc \ @@ -104,9 +104,9 @@ tensorflow/lite/kernels/kernel_util.cc MICROLITE_CC_SRCS := $(filter-out $(MICROLITE_TEST_SRCS), $(MICROLITE_CC_BASE_SRCS)) MICROLITE_CC_HDRS := \ -$(wildcard tensorflow/lite/experimental/micro/*.h) \ -$(wildcard tensorflow/lite/experimental/micro/kernels/*.h) \ -$(wildcard tensorflow/lite/experimental/micro/memory_planner/*.h) \ +$(wildcard tensorflow/lite/micro/*.h) \ +$(wildcard tensorflow/lite/micro/kernels/*.h) \ +$(wildcard tensorflow/lite/micro/memory_planner/*.h) \ LICENSE \ tensorflow/core/public/version.h \ tensorflow/lite/c/builtin_op_data.h \ @@ -140,6 +140,7 @@ tensorflow/lite/kernels/internal/reference/integer_ops/softmax.h \ tensorflow/lite/kernels/internal/reference/maximum_minimum.h \ tensorflow/lite/kernels/internal/reference/mul.h \ tensorflow/lite/kernels/internal/reference/neg.h \ +tensorflow/lite/kernels/internal/reference/pad.h \ tensorflow/lite/kernels/internal/reference/pooling.h \ tensorflow/lite/kernels/internal/reference/prelu.h \ tensorflow/lite/kernels/internal/reference/process_broadcast_shapes.h \ @@ -149,8 +150,8 @@ tensorflow/lite/kernels/internal/reference/softmax.h \ tensorflow/lite/kernels/internal/reference/logistic.h \ tensorflow/lite/kernels/internal/reference/strided_slice.h \ tensorflow/lite/kernels/internal/reference/integer_ops/pooling.h \ -tensorflow/lite/kernels/internal/round.h \ tensorflow/lite/kernels/internal/scoped_profiling_label_wrapper.h \ +tensorflow/lite/kernels/internal/round.h \ tensorflow/lite/kernels/internal/strided_slice_logic.h \ tensorflow/lite/kernels/internal/tensor.h \ tensorflow/lite/kernels/internal/tensor_ctypes.h \ @@ -239,7 +240,7 @@ CC := $(CC_PREFIX)${TARGET_TOOLCHAIN_PREFIX}${CC_TOOL} AR := $(CC_PREFIX)${TARGET_TOOLCHAIN_PREFIX}${AR_TOOL} # Load the examples. -include $(wildcard tensorflow/lite/experimental/micro/examples/*/Makefile.inc) +include $(wildcard tensorflow/lite/micro/examples/*/Makefile.inc) # Create rules for downloading third-party dependencies. THIRD_PARTY_TARGETS := @@ -300,9 +301,9 @@ $(BINDIR)%.bin: $(BINDIR)% $(OBJCOPY) $< $@ -O binary # Generate standalone makefile projects for all of the test targets. -$(foreach TEST_TARGET,$(filter-out tensorflow/lite/experimental/micro/kernels/%,$(MICROLITE_TEST_SRCS)),\ +$(foreach TEST_TARGET,$(filter-out tensorflow/lite/micro/kernels/%,$(MICROLITE_TEST_SRCS)),\ $(eval $(call microlite_test,$(notdir $(basename $(TEST_TARGET))),$(TEST_TARGET)))) -$(foreach TEST_TARGET,$(filter tensorflow/lite/experimental/micro/kernels/%,$(MICROLITE_TEST_SRCS)),\ +$(foreach TEST_TARGET,$(filter tensorflow/lite/micro/kernels/%,$(MICROLITE_TEST_SRCS)),\ $(eval $(call microlite_test,kernel_$(notdir $(basename $(TEST_TARGET))),$(TEST_TARGET)))) test: $(MICROLITE_TEST_TARGETS) @@ -314,7 +315,7 @@ generate_non_kernel_projects: $(filter-out generate_kernel%,$(ALL_PROJECT_TARGET generate_non_test_projects: $(filter-out %_test%,$(ALL_PROJECT_TARGETS)) generate_arduino_zip: generate_non_kernel_projects $(ARDUINO_LIBRARY_ZIPS) - python tensorflow/lite/experimental/micro/tools/make/merge_arduino_zips.py $(PRJDIR)/tensorflow_lite.zip $(ARDUINO_LIBRARY_ZIPS) + python tensorflow/lite/micro/tools/make/merge_arduino_zips.py $(PRJDIR)/tensorflow_lite.zip $(ARDUINO_LIBRARY_ZIPS) # Gets rid of all generated files. clean: diff --git a/tensorflow/lite/experimental/micro/tools/make/download_and_extract.sh b/tensorflow/lite/micro/tools/make/download_and_extract.sh similarity index 93% rename from tensorflow/lite/experimental/micro/tools/make/download_and_extract.sh rename to tensorflow/lite/micro/tools/make/download_and_extract.sh index 0de91e9001a..8a82cc06a99 100755 --- a/tensorflow/lite/experimental/micro/tools/make/download_and_extract.sh +++ b/tensorflow/lite/micro/tools/make/download_and_extract.sh @@ -60,11 +60,11 @@ patch_am_sdk() { # Fixes issues with KissFFT. patch_kissfft() { - sed -i -E $'s@#ifdef FIXED_POINT@// Patched automatically by download_dependencies.sh so default is 16 bit.\\\n#ifndef FIXED_POINT\\\n#define FIXED_POINT (16)\\\n#endif\\\n// End patch.\\\n\\\n#ifdef FIXED_POINT@g' tensorflow/lite/experimental/micro/tools/make/downloads/kissfft/kiss_fft.h - sed -i -E "s@#define KISS_FFT_MALLOC malloc@#define KISS_FFT_MALLOC(X) (void*)(0) /* Patched. */@g" tensorflow/lite/experimental/micro/tools/make/downloads/kissfft/kiss_fft.h - sed -i -E "s@#define KISS_FFT_FREE free@#define KISS_FFT_FREE(X) /* Patched. */@g" tensorflow/lite/experimental/micro/tools/make/downloads/kissfft/kiss_fft.h - sed -ir -E "s@(fprintf.*\);)@/* \1 */@g" tensorflow/lite/experimental/micro/tools/make/downloads/kissfft/tools/kiss_fftr.c - sed -ir -E "s@(exit.*\);)@return; /* \1 */@g" tensorflow/lite/experimental/micro/tools/make/downloads/kissfft/tools/kiss_fftr.c + sed -i -E $'s@#ifdef FIXED_POINT@// Patched automatically by download_dependencies.sh so default is 16 bit.\\\n#ifndef FIXED_POINT\\\n#define FIXED_POINT (16)\\\n#endif\\\n// End patch.\\\n\\\n#ifdef FIXED_POINT@g' tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h + sed -i -E "s@#define KISS_FFT_MALLOC malloc@#define KISS_FFT_MALLOC(X) (void*)(0) /* Patched. */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h + sed -i -E "s@#define KISS_FFT_FREE free@#define KISS_FFT_FREE(X) /* Patched. */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h + sed -ir -E "s@(fprintf.*\);)@/* \1 */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/tools/kiss_fftr.c + sed -ir -E "s@(exit.*\);)@return; /* \1 */@g" tensorflow/lite/micro/tools/make/downloads/kissfft/tools/kiss_fftr.c echo "Finished patching kissfft" } diff --git a/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh b/tensorflow/lite/micro/tools/make/download_dependencies.sh similarity index 91% rename from tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh rename to tensorflow/lite/micro/tools/make/download_dependencies.sh index 3d951826520..df2caedb28d 100755 --- a/tensorflow/lite/experimental/micro/tools/make/download_dependencies.sh +++ b/tensorflow/lite/micro/tools/make/download_dependencies.sh @@ -16,5 +16,5 @@ set -e -echo "download_dependencies.sh is no longer needed, just use 'make -f tensorflow/lite/experimental/micro/tools/make/Makefile'." >&2 +echo "download_dependencies.sh is no longer needed, just use 'make -f tensorflow/lite/micro/tools/make/Makefile'." >&2 exit 1 diff --git a/tensorflow/lite/experimental/micro/tools/make/ext_libs/cmsis.inc b/tensorflow/lite/micro/tools/make/ext_libs/cmsis.inc similarity index 90% rename from tensorflow/lite/experimental/micro/tools/make/ext_libs/cmsis.inc rename to tensorflow/lite/micro/tools/make/ext_libs/cmsis.inc index a57888b88e0..49aa5ac9a5c 100644 --- a/tensorflow/lite/experimental/micro/tools/make/ext_libs/cmsis.inc +++ b/tensorflow/lite/micro/tools/make/ext_libs/cmsis.inc @@ -25,7 +25,7 @@ ifneq ($(filter cmsis-nn,$(ALL_TAGS)),) CMSIS_PATH = $(MAKEFILE_DIR)/downloads/cmsis/ # List created by running: - # find tensorflow/lite/experimental/micro/tools/make/downloads/cmsis/CMSIS/NN/Source/ -name *.c | sed -E 's#tensorflow/lite/experimental/micro/tools/make/downloads/cmsis(.*)$# ${CMSIS_PATH}\1 \\#g' + # find tensorflow/lite/micro/tools/make/downloads/cmsis/CMSIS/NN/Source/ -name *.c | sed -E 's#tensorflow/lite/micro/tools/make/downloads/cmsis(.*)$# ${CMSIS_PATH}\1 \\#g' THIRD_PARTY_CC_SRCS += \ $(CMSIS_PATH)/CMSIS/NN/Source/BasicMathFunctions/arm_elementwise_mul_s8.c \ $(CMSIS_PATH)/CMSIS/NN/Source/BasicMathFunctions/arm_elementwise_add_s8.c \ @@ -48,6 +48,8 @@ ifneq ($(filter cmsis-nn,$(ALL_TAGS)),) $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_depthwise_separable_conv_HWC_q7_nonsquare.c \ $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_convolve_HWC_q15_fast_nonsquare.c \ $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_depthwise_conv_s8_opt.c \ + $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_nn_depthwise_conv_s8_core.c \ + $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_nn_mat_mult_s8.c \ $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_nn_mat_mult_kernel_s8_s16.c \ $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_convolve_HWC_q15_basic.c \ $(CMSIS_PATH)/CMSIS/NN/Source/ConvolutionFunctions/arm_convolve_HWC_q15_fast.c \ @@ -76,7 +78,7 @@ ifneq ($(filter cmsis-nn,$(ALL_TAGS)),) $(CMSIS_PATH)/CMSIS/NN/Source/SoftmaxFunctions/arm_softmax_with_batch_q7.c # List created by running: - # find tensorflow/lite/experimental/micro/tools/make/downloads/cmsis/CMSIS/{Core,NN,DSP}/Include -name *.h | sed -E 's#tensorflow/lite/experimental/micro/tools/make/downloads/cmsis(.*)$# ${CMSIS_PATH}\1 \\#g' + # find tensorflow/lite/micro/tools/make/downloads/cmsis/CMSIS/{Core,NN,DSP}/Include -name *.h | sed -E 's#tensorflow/lite/micro/tools/make/downloads/cmsis(.*)$# ${CMSIS_PATH}\1 \\#g' THIRD_PARTY_CC_HDRS += \ ${CMSIS_PATH}/CMSIS/Core/Include/cmsis_compiler.h \ ${CMSIS_PATH}/CMSIS/Core/Include/cmsis_armclang.h \ @@ -106,8 +108,8 @@ ifneq ($(filter cmsis-nn,$(ALL_TAGS)),) ${CMSIS_PATH}/CMSIS/DSP/Include/arm_const_structs.h # todo: remove the two lines below once context->AllocateTemporaryTensor() is implemented. - MICROLITE_CC_HDRS += tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.h - MICROLITE_CC_SRCS += tensorflow/lite/experimental/micro/kernels/cmsis-nn/scratch_buffer.cc + MICROLITE_CC_HDRS += tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.h + MICROLITE_CC_SRCS += tensorflow/lite/micro/kernels/cmsis-nn/scratch_buffer.cc INCLUDES += -I$(CMSIS_PATH)/CMSIS/Core/Include \ -I$(CMSIS_PATH)/CMSIS/NN/Include \ diff --git a/tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders.py b/tensorflow/lite/micro/tools/make/fix_arduino_subfolders.py similarity index 90% rename from tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders.py rename to tensorflow/lite/micro/tools/make/fix_arduino_subfolders.py index 26d6ff700d7..246504968a9 100755 --- a/tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders.py +++ b/tensorflow/lite/micro/tools/make/fix_arduino_subfolders.py @@ -40,10 +40,8 @@ def rename_example_subfolder_files(library_dir): def move_person_data(library_dir): """Moves the downloaded person model into the examples folder.""" old_person_data_path = os.path.join( - library_dir, - 'src/tensorflow/lite/experimental/micro/tools/make/downloads/' + - 'person_model_grayscale/person_detect_model_data.cpp' - ) + library_dir, 'src/tensorflow/lite/micro/tools/make/downloads/' + + 'person_model_grayscale/person_detect_model_data.cpp') new_person_data_path = os.path.join( library_dir, 'examples/person_detection/person_detect_model_data.cpp') if os.path.exists(old_person_data_path): @@ -52,9 +50,8 @@ def move_person_data(library_dir): with open(new_person_data_path, 'r') as source_file: file_contents = source_file.read() file_contents = file_contents.replace( - six.ensure_str( - '#include "tensorflow/lite/experimental/micro/examples/' + - 'person_detection/person_detect_model_data.h"'), + six.ensure_str('#include "tensorflow/lite/micro/examples/' + + 'person_detection/person_detect_model_data.h"'), '#include "person_detect_model_data.h"') with open(new_person_data_path, 'w') as source_file: source_file.write(file_contents) diff --git a/tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders_test.sh b/tensorflow/lite/micro/tools/make/fix_arduino_subfolders_test.sh similarity index 87% rename from tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders_test.sh rename to tensorflow/lite/micro/tools/make/fix_arduino_subfolders_test.sh index 26fa03d455b..307d026bfa7 100755 --- a/tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders_test.sh +++ b/tensorflow/lite/micro/tools/make/fix_arduino_subfolders_test.sh @@ -30,16 +30,16 @@ mkdir -p `dirname ${EXAMPLES_SUBDIR_HEADER}` touch ${EXAMPLES_SUBDIR_HEADER} TENSORFLOW_SRC_DIR=${LIBRARY_DIR}/src/ -PERSON_DATA_FILE=${TENSORFLOW_SRC_DIR}tensorflow/lite/experimental/micro/tools/make/downloads/person_model_grayscale/person_detect_model_data.cpp +PERSON_DATA_FILE=${TENSORFLOW_SRC_DIR}tensorflow/lite/micro/tools/make/downloads/person_model_grayscale/person_detect_model_data.cpp mkdir -p `dirname ${PERSON_DATA_FILE}` -echo '#include "tensorflow/lite/experimental/micro/examples/person_detection/person_detect_model_data.h"' > ${PERSON_DATA_FILE} +echo '#include "tensorflow/lite/micro/examples/person_detection/person_detect_model_data.h"' > ${PERSON_DATA_FILE} mkdir -p ${LIBRARY_DIR}/examples/person_detection EXAMPLE_INO_FILE=${LIBRARY_DIR}/examples/something/main.ino mkdir -p `dirname ${EXAMPLE_INO_FILE}` touch ${EXAMPLE_INO_FILE} -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/fix_arduino_subfolders \ ${LIBRARY_DIR} EXPECTED_EXAMPLES_SUBDIR_CPP=${LIBRARY_DIR}/examples/something/foo_fish.cpp diff --git a/tensorflow/lite/experimental/micro/tools/make/generate_keil_project.py b/tensorflow/lite/micro/tools/make/generate_keil_project.py similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/generate_keil_project.py rename to tensorflow/lite/micro/tools/make/generate_keil_project.py diff --git a/tensorflow/lite/experimental/micro/tools/make/generate_keil_project_test.sh b/tensorflow/lite/micro/tools/make/generate_keil_project_test.sh similarity index 91% rename from tensorflow/lite/experimental/micro/tools/make/generate_keil_project_test.sh rename to tensorflow/lite/micro/tools/make/generate_keil_project_test.sh index 22b68e4f683..359e5a896f5 100755 --- a/tensorflow/lite/experimental/micro/tools/make/generate_keil_project_test.sh +++ b/tensorflow/lite/micro/tools/make/generate_keil_project_test.sh @@ -18,11 +18,11 @@ set -e -INPUT_TEMPLATE=${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/templates/keil_project.uvprojx.tpl +INPUT_TEMPLATE=${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/templates/keil_project.uvprojx.tpl OUTPUT_FILE=${TEST_TMPDIR}/keil_project.uvprojx EXECUTABLE=test_executable -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/generate_keil_project \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/generate_keil_project \ --input_template=${INPUT_TEMPLATE} \ --output_file=${OUTPUT_FILE} \ --executable=${EXECUTABLE} \ diff --git a/tensorflow/lite/experimental/micro/tools/make/helper_functions.inc b/tensorflow/lite/micro/tools/make/helper_functions.inc similarity index 79% rename from tensorflow/lite/experimental/micro/tools/make/helper_functions.inc rename to tensorflow/lite/micro/tools/make/helper_functions.inc index 1ac66b9c56c..cad543efe34 100644 --- a/tensorflow/lite/experimental/micro/tools/make/helper_functions.inc +++ b/tensorflow/lite/micro/tools/make/helper_functions.inc @@ -6,10 +6,10 @@ reverse = $(if $(1),$(call reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstwor # implementations with, given a tag. These are expected to occur in subfolders # of a directory where a reference implementation exists, and have the same # interface and header file. For example, -# tensorflow/lite/experimental/micro/examples/micro_speech/audio_provider.cc +# tensorflow/lite/micro/examples/micro_speech/audio_provider.cc # defines a module for supplying audio data, but since no platform or OS can be # presumed, it just always returns zeroes for its samples. The MacOS-specific -# tensorflow/lite/experimental/micro/examples/micro_speech/osx/audio_provider.cc +# tensorflow/lite/micro/examples/micro_speech/osx/audio_provider.cc # has an implementation that relies on CoreAudio, and there are equivalent # versions for other operating systems. # The specific implementation yielded by the first tag in the list that produces @@ -69,11 +69,11 @@ $(PRJDIR)$(3)/$(1)/%: % third_party_downloads @mkdir -p $$(dir $$@) @cp $$< $$@ -$(PRJDIR)$(3)/$(1)/third_party/%: tensorflow/lite/experimental/micro/tools/make/downloads/% third_party_downloads +$(PRJDIR)$(3)/$(1)/third_party/%: tensorflow/lite/micro/tools/make/downloads/% third_party_downloads @mkdir -p $$(dir $$@) @cp $$< $$@ -$(PRJDIR)$(3)/$(1)/%: tensorflow/lite/experimental/micro/tools/make/templates/%.tpl +$(PRJDIR)$(3)/$(1)/%: tensorflow/lite/micro/tools/make/templates/%.tpl @mkdir -p $$(dir $$@) @sed -E 's#\%\{SRCS\}\%#$(4)#g' $$< | \ sed -E 's#\%\{EXECUTABLE\}\%#$(3)#g' | \ @@ -81,13 +81,13 @@ $(PRJDIR)$(3)/$(1)/%: tensorflow/lite/experimental/micro/tools/make/templates/%. sed -E 's#\%\{CXX_FLAGS\}\%#$(7)#g' | \ sed -E 's#\%\{CC_FLAGS\}\%#$(8)#g' > $$@ -$(PRJDIR)$(3)/$(1)/keil_project.uvprojx: tensorflow/lite/experimental/micro/tools/make/templates/keil_project.uvprojx.tpl +$(PRJDIR)$(3)/$(1)/keil_project.uvprojx: tensorflow/lite/micro/tools/make/templates/keil_project.uvprojx.tpl @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/generate_keil_project.py \ + @python tensorflow/lite/micro/tools/make/generate_keil_project.py \ --input_template=$$< --output_file=$$@ --executable=$(3) \ --srcs="$(4)" --hdrs="$(5)" --include_paths="$$(PROJECT_INCLUDES)" -$(PRJDIR)$(3)/$(1)/.vscode/tasks.json : tensorflow/lite/experimental/micro/tools/make/templates/tasks.json.$(1).tpl +$(PRJDIR)$(3)/$(1)/.vscode/tasks.json : tensorflow/lite/micro/tools/make/templates/tasks.json.$(1).tpl @mkdir -p $$(dir $$@) @cp $$< $$@ @@ -114,25 +114,25 @@ endef # can invoke to create the standalone project. define generate_arduino_project -$(PRJDIR)$(2)/arduino/examples/%.cpp: tensorflow/lite/experimental/micro/examples/%.cc +$(PRJDIR)$(2)/arduino/examples/%.cpp: tensorflow/lite/micro/examples/%.cc @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --is_example_source \ --source_path="$$<" \ --third_party_headers="$(4)" < $$< > $$@ -$(PRJDIR)$(2)/arduino/examples/%.h: tensorflow/lite/experimental/micro/examples/%.h +$(PRJDIR)$(2)/arduino/examples/%.h: tensorflow/lite/micro/examples/%.h @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --is_example_source \ --source_path="$$<" \ --third_party_headers="$(4)" < $$< > $$@ -$(PRJDIR)$(2)/arduino/examples/%/main.ino: tensorflow/lite/experimental/micro/examples/%/main_functions.cc +$(PRJDIR)$(2)/arduino/examples/%/main.ino: tensorflow/lite/micro/examples/%/main_functions.cc @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --is_example_ino \ --source_path="$$<" \ @@ -140,13 +140,13 @@ $(PRJDIR)$(2)/arduino/examples/%/main.ino: tensorflow/lite/experimental/micro/ex $(PRJDIR)$(2)/arduino/src/%.cpp: %.cc @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --third_party_headers="$(4)" < $$< > $$@ $(PRJDIR)$(2)/arduino/src/%.h: %.h third_party_downloads @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --third_party_headers="$(4)" < $$< > $$@ @@ -156,37 +156,37 @@ $(PRJDIR)$(2)/arduino/LICENSE: LICENSE $(PRJDIR)$(2)/arduino/src/%: % third_party_downloads @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --third_party_headers="$(4)" < $$< > $$@ -$(PRJDIR)$(2)/arduino/src/third_party/%: tensorflow/lite/experimental/micro/tools/make/downloads/% third_party_downloads +$(PRJDIR)$(2)/arduino/src/third_party/%: tensorflow/lite/micro/tools/make/downloads/% third_party_downloads @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --third_party_headers="$(4)" < $$< > $$@ -$(PRJDIR)$(2)/arduino/src/third_party/%.cpp: tensorflow/lite/experimental/micro/tools/make/downloads/%.cc third_party_downloads +$(PRJDIR)$(2)/arduino/src/third_party/%.cpp: tensorflow/lite/micro/tools/make/downloads/%.cc third_party_downloads @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --third_party_headers="$(4)" < $$< > $$@ -$(PRJDIR)$(2)/arduino/src/third_party/flatbuffers/include/flatbuffers/base.h: tensorflow/lite/experimental/micro/tools/make/downloads/flatbuffers/include/flatbuffers/base.h third_party_downloads +$(PRJDIR)$(2)/arduino/src/third_party/flatbuffers/include/flatbuffers/base.h: tensorflow/lite/micro/tools/make/downloads/flatbuffers/include/flatbuffers/base.h third_party_downloads @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --third_party_headers="$(4)" < $$< | \ sed -E 's/utility\.h/utility/g' > $$@ -$(PRJDIR)$(2)/arduino/src/third_party/kissfft/kiss_fft.h: tensorflow/lite/experimental/micro/tools/make/downloads/kissfft/kiss_fft.h third_party_downloads +$(PRJDIR)$(2)/arduino/src/third_party/kissfft/kiss_fft.h: tensorflow/lite/micro/tools/make/downloads/kissfft/kiss_fft.h third_party_downloads @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=arduino \ --third_party_headers="$(4)" < $$< | \ sed -E 's@#include @//#include /* Patched by helper_functions.inc for Arduino compatibility */@g' > $$@ -$(PRJDIR)$(2)/arduino/%: tensorflow/lite/experimental/micro/tools/make/templates/% +$(PRJDIR)$(2)/arduino/%: tensorflow/lite/micro/tools/make/templates/% @mkdir -p $$(dir $$@) @sed -E 's#\%\{SRCS\}\%#$(3)#g' $$< | \ sed -E 's#\%\{EXECUTABLE\}\%#$(2)#g' | \ @@ -194,11 +194,11 @@ $(PRJDIR)$(2)/arduino/%: tensorflow/lite/experimental/micro/tools/make/templates sed -E 's#\%\{CXX_FLAGS\}\%#$(6)#g' | \ sed -E 's#\%\{CC_FLAGS\}\%#$(7)#g' > $$@ -$(PRJDIR)$(2)/arduino/examples/$(2)/$(2).ino: tensorflow/lite/experimental/micro/tools/make/templates/arduino_example.ino +$(PRJDIR)$(2)/arduino/examples/$(2)/$(2).ino: tensorflow/lite/micro/tools/make/templates/arduino_example.ino @mkdir -p $$(dir $$@) @cp $$< $$@ -$(PRJDIR)$(2)/arduino/src/TensorFlowLite.h: tensorflow/lite/experimental/micro/tools/make/templates/TensorFlowLite.h +$(PRJDIR)$(2)/arduino/src/TensorFlowLite.h: tensorflow/lite/micro/tools/make/templates/TensorFlowLite.h @mkdir -p $$(dir $$@) @cp $$< $$@ @@ -210,19 +210,19 @@ $(addprefix $(PRJDIR)$(2)/arduino/, \ $(patsubst tensorflow/%,src/tensorflow/%,\ $(patsubst examples/%/main_functions.cpp,examples/%/main.ino,\ $(patsubst examples/%_test.cpp,examples/%_test.ino,\ -$(patsubst tensorflow/lite/experimental/micro/examples/%,examples/%,\ +$(patsubst tensorflow/lite/micro/examples/%,examples/%,\ $(patsubst third_party/%,src/third_party/%,\ $(patsubst %.cc,%.cpp,$(3)))))))) \ $(addprefix $(PRJDIR)$(2)/arduino/, \ $(patsubst tensorflow/%,src/tensorflow/%,\ -$(patsubst tensorflow/lite/experimental/micro/examples/%,examples/%,\ +$(patsubst tensorflow/lite/micro/examples/%,examples/%,\ $(patsubst third_party/%,src/third_party/%,$(4))))) \ $(addprefix $(PRJDIR)$(2)/arduino/,$(1)) \ $(PRJDIR)$(2)/arduino/src/TensorFlowLite.h generate_$(2)_arduino_library_zip: generate_$(2)_arduino_project cp -r $(PRJDIR)$(2)/arduino $(PRJDIR)$(2)/tensorflow_lite - python tensorflow/lite/experimental/micro/tools/make/fix_arduino_subfolders.py $(PRJDIR)$(2)/tensorflow_lite + python tensorflow/lite/micro/tools/make/fix_arduino_subfolders.py $(PRJDIR)$(2)/tensorflow_lite @cd $(PRJDIR)$(2) && zip -q -r tensorflow_lite.zip tensorflow_lite ALL_PROJECT_TARGETS += $(if $(findstring _test,$(2)),,generate_$(2)_arduino_library_zip) @@ -250,15 +250,15 @@ $(PRJDIR)$(2)/esp-idf/LICENSE: LICENSE @mkdir -p $$(dir $$@) @cp $$< $$@ -$(PRJDIR)$(2)/esp-idf/main/%.cc: tensorflow/lite/experimental/micro/examples/$(2)/%.cc +$(PRJDIR)$(2)/esp-idf/main/%.cc: tensorflow/lite/micro/examples/$(2)/%.cc @mkdir -p $$(dir $$@) - @python tensorflow/lite/experimental/micro/tools/make/transform_source.py \ + @python tensorflow/lite/micro/tools/make/transform_source.py \ --platform=esp \ --is_example_source \ --source_path="$$<" \ < $$< > $$@ -$(PRJDIR)$(2)/esp-idf/main/%: tensorflow/lite/experimental/micro/examples/$(2)/% +$(PRJDIR)$(2)/esp-idf/main/%: tensorflow/lite/micro/examples/$(2)/% @mkdir -p $$(dir $$@) @cp $$< $$@ @@ -266,12 +266,12 @@ $(PRJDIR)$(2)/esp-idf/components/tfmicro/%: % third_party_downloads @mkdir -p $$(dir $$@) @cp $$< $$@ -$(PRJDIR)$(2)/esp-idf/components/tfmicro/third_party/%: tensorflow/lite/experimental/micro/tools/make/downloads/% third_party_downloads +$(PRJDIR)$(2)/esp-idf/components/tfmicro/third_party/%: tensorflow/lite/micro/tools/make/downloads/% third_party_downloads @mkdir -p $$(dir $$@) @cp $$< $$@ -$(PRJDIR)$(2)/esp-idf/%: tensorflow/lite/experimental/micro/tools/make/templates/esp/%.tpl - $(eval MAIN_SRCS_RELATIVE := $(patsubst tensorflow/lite/experimental/micro/examples/$(2)/%,%,$(5))) +$(PRJDIR)$(2)/esp-idf/%: tensorflow/lite/micro/tools/make/templates/esp/%.tpl + $(eval MAIN_SRCS_RELATIVE := $(patsubst tensorflow/lite/micro/examples/$(2)/%,%,$(5))) @mkdir -p $$(dir $$@) @sed -E 's#\%\{COMPONENT_SRCS\}\%#$(3)#g' $$< | \ @@ -286,7 +286,7 @@ generate_$(2)_esp_project: \ $(addprefix $(PRJDIR)$(2)/esp-idf/,\ $(patsubst tensorflow/%,components/tfmicro/tensorflow/%,\ $(patsubst third_party/%,components/tfmicro/third_party/%,\ -$(patsubst tensorflow/lite/experimental/micro/examples/$(2)/%,main/%,$(3) $(4) $(5) $(6))))) \ +$(patsubst tensorflow/lite/micro/examples/$(2)/%,main/%,$(3) $(4) $(5) $(6))))) \ $(addprefix $(PRJDIR)$(2)/esp-idf/,$(1)) ALL_PROJECT_TARGETS += generate_$(2)_esp_project @@ -350,7 +350,7 @@ endef # These arguments are packed into a single '!' separated string, so no element # can contain a '!'. define add_third_party_download -THIRD_PARTY_DOWNLOADS += $(1)!$(2)!tensorflow/lite/experimental/micro/tools/make/downloads/$(3)!$(4) +THIRD_PARTY_DOWNLOADS += $(1)!$(2)!tensorflow/lite/micro/tools/make/downloads/$(3)!$(4) endef # Unpacks an entry in a list of strings created by add_third_party_download, and @@ -359,6 +359,6 @@ endef # 1 - Information about the library, separated by '!'s. define create_download_rule $(word 3, $(subst !, ,$(1))): - tensorflow/lite/experimental/micro/tools/make/download_and_extract.sh $(subst !, ,$(1)) + tensorflow/lite/micro/tools/make/download_and_extract.sh $(subst !, ,$(1)) THIRD_PARTY_TARGETS += $(word 3, $(subst !, ,$(1))) endef diff --git a/tensorflow/lite/experimental/micro/tools/make/merge_arduino_zips.py b/tensorflow/lite/micro/tools/make/merge_arduino_zips.py similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/merge_arduino_zips.py rename to tensorflow/lite/micro/tools/make/merge_arduino_zips.py diff --git a/tensorflow/lite/experimental/micro/tools/make/merge_arduino_zips_test.sh b/tensorflow/lite/micro/tools/make/merge_arduino_zips_test.sh similarity index 95% rename from tensorflow/lite/experimental/micro/tools/make/merge_arduino_zips_test.sh rename to tensorflow/lite/micro/tools/make/merge_arduino_zips_test.sh index bb511f0be2d..7fe5663aaed 100755 --- a/tensorflow/lite/experimental/micro/tools/make/merge_arduino_zips_test.sh +++ b/tensorflow/lite/micro/tools/make/merge_arduino_zips_test.sh @@ -47,7 +47,7 @@ popd OUTPUT_DIR=${TEST_TMPDIR}/output/ OUTPUT_ZIP=${OUTPUT_DIR}/output.zip -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/merge_arduino_zips \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/merge_arduino_zips \ ${OUTPUT_ZIP} ${INPUT1_ZIP} ${INPUT2_ZIP} if [[ ! -f ${OUTPUT_ZIP} ]]; then diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb/.gitignore b/tensorflow/lite/micro/tools/make/targets/apollo3evb/.gitignore similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb/.gitignore rename to tensorflow/lite/micro/tools/make/targets/apollo3evb/.gitignore diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb/apollo3evb.ld b/tensorflow/lite/micro/tools/make/targets/apollo3evb/apollo3evb.ld similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb/apollo3evb.ld rename to tensorflow/lite/micro/tools/make/targets/apollo3evb/apollo3evb.ld diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb/prep_apollo3_files.sh b/tensorflow/lite/micro/tools/make/targets/apollo3evb/prep_apollo3_files.sh similarity index 92% rename from tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb/prep_apollo3_files.sh rename to tensorflow/lite/micro/tools/make/targets/apollo3evb/prep_apollo3_files.sh index 7ef23095022..ae764c8c32c 100755 --- a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb/prep_apollo3_files.sh +++ b/tensorflow/lite/micro/tools/make/targets/apollo3evb/prep_apollo3_files.sh @@ -14,12 +14,12 @@ # limitations under the License. # ============================================================================== -AP3_DIR="tensorflow/lite/experimental/micro/tools/make/downloads/Apollo3-SDK-2018.08.13" +AP3_DIR="tensorflow/lite/micro/tools/make/downloads/Apollo3-SDK-2018.08.13" if [ ! -d $AP3_DIR ]; then echo "Apollo 3 SDK does not exist" echo "Either the SDK has not been downloaded, or this script is not being run from the root of the repository" else - DEST_DIR="tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb" + DEST_DIR="tensorflow/lite/micro/tools/make/targets/apollo3evb" cp "$AP3_DIR/boards/apollo3_evb/examples/hello_world/gcc/startup_gcc.c" "$DEST_DIR" cp "$AP3_DIR/boards/apollo3_evb/examples/hello_world/gcc/hello_world.ld" "$DEST_DIR/apollo3evb.ld" sed -i -e '131s/1024/1024\*20/g' "$DEST_DIR/startup_gcc.c" diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc b/tensorflow/lite/micro/tools/make/targets/apollo3evb_makefile.inc similarity index 93% rename from tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/apollo3evb_makefile.inc index 34b5cdf837b..a7d0aa4870b 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/apollo3evb_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/apollo3evb_makefile.inc @@ -129,18 +129,18 @@ $(MAKEFILE_DIR)/downloads/AmbiqSuite-Rel2.0.0/boards/SparkFun_TensorFlow_Apollo3 $(CMSIS_SRC_DIR)/StatisticsFunctions/arm_max_q7.c AP3_EXT_MICRO_DIR := $(MAKEFILE_DIR)/downloads/apollo3_ext - AP3_MICRO_DIR := tensorflow/lite/experimental/micro/examples/micro_speech/apollo3 - CMSIS_DIR := tensorflow/lite/experimental/micro/examples/micro_speech/CMSIS + AP3_MICRO_DIR := tensorflow/lite/micro/examples/micro_speech/apollo3 + CMSIS_DIR := tensorflow/lite/micro/examples/micro_speech/CMSIS CMSIS_EXT_DIR := $(MAKEFILE_DIR)/downloads/CMSIS_ext MICRO_SPEECH_TEST_SRCS += \ $(AP3_MICRO_DIR)/_main.c - TEST_SCRIPT := tensorflow/lite/experimental/log_test/test_apollo3evb_binary.sh + TEST_SCRIPT := tensorflow/lite/micro/testing/test_apollo3evb_binary.sh # These are tests that don't currently work on the Apollo3 board. EXCLUDED_TESTS := \ - tensorflow/lite/experimental/micro/micro_interpreter_test.cc \ - tensorflow/lite/experimental/micro/simple_tensor_allocator_test.cc + tensorflow/lite/micro/micro_interpreter_test.cc \ + tensorflow/lite/micro/simple_tensor_allocator_test.cc MICROLITE_TEST_SRCS := $(filter-out $(EXCLUDED_TESTS), $(MICROLITE_TEST_SRCS)) endif diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/bluepill/bluepill.lds b/tensorflow/lite/micro/tools/make/targets/bluepill/bluepill.lds similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/bluepill/bluepill.lds rename to tensorflow/lite/micro/tools/make/targets/bluepill/bluepill.lds diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/bluepill_makefile.inc b/tensorflow/lite/micro/tools/make/targets/bluepill_makefile.inc similarity index 91% rename from tensorflow/lite/experimental/micro/tools/make/targets/bluepill_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/bluepill_makefile.inc index e9321a78961..edef3917cfd 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/bluepill_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/bluepill_makefile.inc @@ -55,11 +55,11 @@ ifeq ($(TARGET), bluepill) EXCLUDED_SRCS := \ $(MAKEFILE_DIR)/downloads/stm32_bare_lib/source/debug_log.c MICROLITE_CC_SRCS := $(filter-out $(EXCLUDED_SRCS), $(MICROLITE_CC_SRCS)) - TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_bluepill_binary.sh + TEST_SCRIPT := tensorflow/lite/micro/testing/test_bluepill_binary.sh # These are tests that don't currently work on the blue pill. EXCLUDED_TESTS := \ - tensorflow/lite/experimental/micro/micro_interpreter_test.cc \ - tensorflow/lite/experimental/micro/simple_tensor_allocator_test.cc + tensorflow/lite/micro/micro_interpreter_test.cc \ + tensorflow/lite/micro/simple_tensor_allocator_test.cc MICROLITE_TEST_SRCS := $(filter-out $(EXCLUDED_TESTS), $(MICROLITE_TEST_SRCS)) # These are microcontroller-specific rules for converting the ELF output diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md b/tensorflow/lite/micro/tools/make/targets/ecm3531/README.md similarity index 90% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md rename to tensorflow/lite/micro/tools/make/targets/ecm3531/README.md index a92fc8312be..14ea7c3cd3c 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/README.md +++ b/tensorflow/lite/micro/tools/make/targets/ecm3531/README.md @@ -1,5 +1,5 @@ Compiling instructions here -https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro +https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro CONTACT INFORMATION: diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/_main.c b/tensorflow/lite/micro/tools/make/targets/ecm3531/_main.c similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/_main.c rename to tensorflow/lite/micro/tools/make/targets/ecm3531/_main.c diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531.lds b/tensorflow/lite/micro/tools/make/targets/ecm3531/ecm3531.lds similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531.lds rename to tensorflow/lite/micro/tools/make/targets/ecm3531/ecm3531.lds diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531_flash.lds b/tensorflow/lite/micro/tools/make/targets/ecm3531/ecm3531_flash.lds similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/ecm3531_flash.lds rename to tensorflow/lite/micro/tools/make/targets/ecm3531/ecm3531_flash.lds diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_erase b/tensorflow/lite/micro/tools/make/targets/ecm3531/flash_erase similarity index 91% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_erase rename to tensorflow/lite/micro/tools/make/targets/ecm3531/flash_erase index 5395b3d9965..66b506e71fe 100755 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_erase +++ b/tensorflow/lite/micro/tools/make/targets/ecm3531/flash_erase @@ -1,5 +1,5 @@ #!/usr/bin/python3 -#Usage: cd to the directory tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 and type ./flash_erase to erase the flash. +#Usage: cd to the directory tensorflow/lite/micro/tools/make/targets/ecm3531 and type ./flash_erase to erase the flash. # # # Copyright 2015 The TensorFlow Authors. All Rights Reserved. diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_program b/tensorflow/lite/micro/tools/make/targets/ecm3531/flash_program similarity index 86% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_program rename to tensorflow/lite/micro/tools/make/targets/ecm3531/flash_program index bc3fe5cb21a..8f72ac36048 100755 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/flash_program +++ b/tensorflow/lite/micro/tools/make/targets/ecm3531/flash_program @@ -1,5 +1,5 @@ #!/usr/bin/python3 -#Usage: cd to the directory tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 and type ./flash_program executable_name to load an executable from the directory tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ into flash +#Usage: cd to the directory tensorflow/lite/micro/tools/make/targets/ecm3531 and type ./flash_program executable_name to load an executable from the directory tensorflow/lite/micro/tools/make/gen/ecm3531_cortex-m3/bin/ into flash # # # Copyright 2015 The TensorFlow Authors. All Rights Reserved. diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program b/tensorflow/lite/micro/tools/make/targets/ecm3531/load_program similarity index 87% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program rename to tensorflow/lite/micro/tools/make/targets/ecm3531/load_program index 781231480aa..a6bf6fef1ef 100755 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/load_program +++ b/tensorflow/lite/micro/tools/make/targets/ecm3531/load_program @@ -1,5 +1,5 @@ #!/usr/bin/python3 -#Usage: cd to the directory tensorflow/lite/experimental/micro/tools/make/targets/ecm3531 and type ./load_prgram executable_name to load an executable from the directory tensorflow/lite/experimental/micro/tools/make/gen/ecm3531_cortex-m3/bin/ +#Usage: cd to the directory tensorflow/lite/micro/tools/make/targets/ecm3531 and type ./load_prgram executable_name to load an executable from the directory tensorflow/lite/micro/tools/make/gen/ecm3531_cortex-m3/bin/ # # # Copyright 2015 The TensorFlow Authors. All Rights Reserved. diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/startup.c b/tensorflow/lite/micro/tools/make/targets/ecm3531/startup.c similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531/startup.c rename to tensorflow/lite/micro/tools/make/targets/ecm3531/startup.c diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc b/tensorflow/lite/micro/tools/make/targets/ecm3531_makefile.inc similarity index 93% rename from tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/ecm3531_makefile.inc index 06f73a8e041..63bc44b5a8c 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/ecm3531_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/ecm3531_makefile.inc @@ -84,7 +84,7 @@ ifeq ($(TARGET), ecm3531) # _main.c contains application and target specific initialization, like # setting clock speed, default uart setups, etc. and an implementation - # of the DebugLog interfaces. +#of the DebugLog interfaces. MICROLITE_CC_SRCS += \ $(MAKEFILE_DIR)/targets/ecm3531/startup.c \ $(MAKEFILE_DIR)/targets/ecm3531/_main.c \ @@ -98,11 +98,11 @@ ifeq ($(TARGET), ecm3531) MICROLITE_CC_HDRS += \ $(MAKEFILE_DIR)/targets/ecm3531/$(ETA_LDS_FILE) - TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_ecm3531_binary.sh + TEST_SCRIPT := tensorflow/lite/micro/testing/test_ecm3531_binary.sh # These are tests that don't currently work on the blue pill. EXCLUDED_TESTS := \ - tensorflow/lite/experimental/micro/micro_interpreter_test.cc \ - tensorflow/lite/experimental/micro/simple_tensor_allocator_test.cc + tensorflow/lite/micro/micro_interpreter_test.cc \ + tensorflow/lite/micro/simple_tensor_allocator_test.cc MICROLITE_TEST_SRCS := $(filter-out $(EXCLUDED_TESTS), $(MICROLITE_TEST_SRCS)) # These are microcontroller-specific rules for converting the ELF output diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/esp32_makefile.inc b/tensorflow/lite/micro/tools/make/targets/esp32_makefile.inc similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/esp32_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/esp32_makefile.inc diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/leon_makefile.inc b/tensorflow/lite/micro/tools/make/targets/leon_makefile.inc similarity index 69% rename from tensorflow/lite/experimental/micro/tools/make/targets/leon_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/leon_makefile.inc index 7d7832411b3..fce05513e26 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/leon_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/leon_makefile.inc @@ -4,8 +4,8 @@ ifeq ($(TARGET), leon) CXXFLAGS += -std=c++11 $(PLATFORM_FLAGS) CCFLAGS += $(PLATFORM_FLAGS) TARGET_ARCH := leon - TARGET_TOOLCHAIN_PREFIX := tensorflow/lite/experimental/micro/tools/make/downloads/leon_bcc2/bin/sparc-gaisler-elf- - TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_leon_binary.sh + TARGET_TOOLCHAIN_PREFIX := tensorflow/lite/micro/tools/make/downloads/leon_bcc2/bin/sparc-gaisler-elf- + TEST_SCRIPT := tensorflow/lite/micro/testing/test_leon_binary.sh GCC_LEON := $(MAKEFILE_DIR)/downloads/leon_bcc2/ $(eval $(call add_third_party_download,$(LEON_BCC2_URL),$(LEON_BCC2_MD5),leon_bcc2,)) diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/linux_x86_makefile.inc b/tensorflow/lite/micro/tools/make/targets/linux_x86_makefile.inc similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/linux_x86_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/linux_x86_makefile.inc diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/mbed_makefile.inc b/tensorflow/lite/micro/tools/make/targets/mbed_makefile.inc similarity index 70% rename from tensorflow/lite/experimental/micro/tools/make/targets/mbed_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/mbed_makefile.inc index b3210bd32a6..512769bb475 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/mbed_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/mbed_makefile.inc @@ -3,4 +3,5 @@ ifeq ($(TARGET), mbed) TARGET_ARCH := cortex-m4 $(eval $(call add_third_party_download,$(CMSIS_URL),$(CMSIS_MD5),cmsis,)) $(eval $(call add_third_party_download,$(CUST_CMSIS_URL),$(CUST_CMSIS_MD5),CMSIS_ext,)) -endif \ No newline at end of file + $(eval $(call add_third_party_download,$(GCC_EMBEDDED_URL),$(GCC_EMBEDDED_MD5),gcc_embedded,)) +endif diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/mcu_riscv_makefile.inc b/tensorflow/lite/micro/tools/make/targets/mcu_riscv_makefile.inc similarity index 97% rename from tensorflow/lite/experimental/micro/tools/make/targets/mcu_riscv_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/mcu_riscv_makefile.inc index 9eb387f4e56..5e0917e8a04 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/mcu_riscv_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/mcu_riscv_makefile.inc @@ -47,7 +47,7 @@ ifeq ($(TARGET), riscv32_mcu) -I$(MAKEFILE_DIR)/downloads/sifive_fe310_lib/bsp/env/freedom-e300-hifive1 MICROLITE_CC_SRCS += \ - $(wildcard tensorflow/lite/experimental/micro/riscv32_mcu/*.cc) + $(wildcard tensorflow/lite/micro/riscv32_mcu/*.cc) MICRO_SPEECH_TEST_SRCS += \ $(wildcard $(MAKEFILE_DIR)/downloads/sifive_fe310_lib/bsp/libwrap/sys/*.c) \ $(wildcard $(MAKEFILE_DIR)/downloads/sifive_fe310_lib/bsp/libwrap/sys/*.cc) \ diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc b/tensorflow/lite/micro/tools/make/targets/osx_makefile.inc similarity index 70% rename from tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/osx_makefile.inc index 090b4fa101d..9b1e2220575 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/osx_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/osx_makefile.inc @@ -2,7 +2,7 @@ ifeq ($(TARGET), osx) # Make sure we can find the embedded GCC compiler. - export PATH := ${PATH}:tensorflow/lite/experimental/micro/tools/make/downloads/gcc_embedded/bin/ + export PATH := ${PATH}:tensorflow/lite/micro/tools/make/downloads/gcc_embedded/bin/ PLATFORM_FLAGS = \ -DTF_LITE_DISABLE_X86_NEON diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/osx_x86_64_makefile.inc b/tensorflow/lite/micro/tools/make/targets/osx_x86_64_makefile.inc similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/targets/osx_x86_64_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/osx_x86_64_makefile.inc diff --git a/tensorflow/lite/experimental/micro/tools/make/targets/xtensa_xpg_makefile.inc b/tensorflow/lite/micro/tools/make/targets/xtensa_xpg_makefile.inc similarity index 89% rename from tensorflow/lite/experimental/micro/tools/make/targets/xtensa_xpg_makefile.inc rename to tensorflow/lite/micro/tools/make/targets/xtensa_xpg_makefile.inc index ea952bef8ae..4161882d30e 100644 --- a/tensorflow/lite/experimental/micro/tools/make/targets/xtensa_xpg_makefile.inc +++ b/tensorflow/lite/micro/tools/make/targets/xtensa_xpg_makefile.inc @@ -18,7 +18,7 @@ ifeq ($(TARGET), xtensa-xpg) CXXFLAGS = $(PLATFORM_ARGS) -std=c++11 CCFLAGS = $(PLATFORM_ARGS) -std=c11 - TEST_SCRIPT := tensorflow/lite/experimental/micro/testing/test_xtensa_xpg_binary.sh + TEST_SCRIPT := tensorflow/lite/micro/testing/test_xtensa_xpg_binary.sh # These are microcontroller-specific rules for converting the ELF output # of the linker into a binary image that can be loaded directly. diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl b/tensorflow/lite/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl rename to tensorflow/lite/micro/tools/make/templates/AUDIO_DISCO_F746NG.lib.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl b/tensorflow/lite/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl rename to tensorflow/lite/micro/tools/make/templates/BSP_DISCO_F746NG.lib.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/LCD_DISCO_F746NG.lib.tpl b/tensorflow/lite/micro/tools/make/templates/LCD_DISCO_F746NG.lib.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/LCD_DISCO_F746NG.lib.tpl rename to tensorflow/lite/micro/tools/make/templates/LCD_DISCO_F746NG.lib.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/Makefile.tpl b/tensorflow/lite/micro/tools/make/templates/Makefile.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/Makefile.tpl rename to tensorflow/lite/micro/tools/make/templates/Makefile.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/README_KEIL.md.tpl b/tensorflow/lite/micro/tools/make/templates/README_KEIL.md.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/README_KEIL.md.tpl rename to tensorflow/lite/micro/tools/make/templates/README_KEIL.md.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/README_MAKE.md.tpl b/tensorflow/lite/micro/tools/make/templates/README_MAKE.md.tpl similarity index 84% rename from tensorflow/lite/experimental/micro/tools/make/templates/README_MAKE.md.tpl rename to tensorflow/lite/micro/tools/make/templates/README_MAKE.md.tpl index 7906a3226ab..f9f6a9ce542 100644 --- a/tensorflow/lite/experimental/micro/tools/make/templates/README_MAKE.md.tpl +++ b/tensorflow/lite/micro/tools/make/templates/README_MAKE.md.tpl @@ -18,7 +18,7 @@ standard Makefile variables like CFLAGS, CC, CXX, and so on. ## Project Generation See -[tensorflow/lite/experimental/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) +[tensorflow/lite/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro) for details on how projects like this can be generated from the main source tree. diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/README_MBED.md.tpl b/tensorflow/lite/micro/tools/make/templates/README_MBED.md.tpl similarity index 91% rename from tensorflow/lite/experimental/micro/tools/make/templates/README_MBED.md.tpl rename to tensorflow/lite/micro/tools/make/templates/README_MBED.md.tpl index 2682236edf5..2685cbe2841 100644 --- a/tensorflow/lite/experimental/micro/tools/make/templates/README_MBED.md.tpl +++ b/tensorflow/lite/micro/tools/make/templates/README_MBED.md.tpl @@ -37,7 +37,7 @@ over the file. ## Project Generation See -[tensorflow/lite/experimental/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) +[tensorflow/lite/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro) for details on how projects like this can be generated from the main source tree. diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl b/tensorflow/lite/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl rename to tensorflow/lite/micro/tools/make/templates/SDRAM_DISCO_F746NG.lib.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/TensorFlowLite.h b/tensorflow/lite/micro/tools/make/templates/TensorFlowLite.h similarity index 77% rename from tensorflow/lite/experimental/micro/tools/make/templates/TensorFlowLite.h rename to tensorflow/lite/micro/tools/make/templates/TensorFlowLite.h index 4e8619fa159..3ba9a5d98dd 100644 --- a/tensorflow/lite/experimental/micro/tools/make/templates/TensorFlowLite.h +++ b/tensorflow/lite/micro/tools/make/templates/TensorFlowLite.h @@ -12,11 +12,11 @@ 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_LITE_EXPERIMENTAL_MICRO_TOOLS_MAKE_TEMPLATES_TENSORFLOWLITE_H_ -#define TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TOOLS_MAKE_TEMPLATES_TENSORFLOWLITE_H_ +#ifndef TENSORFLOW_LITE_MICRO_TOOLS_MAKE_TEMPLATES_TENSORFLOWLITE_H_ +#define TENSORFLOW_LITE_MICRO_TOOLS_MAKE_TEMPLATES_TENSORFLOWLITE_H_ // This header is deliberately empty, and is only present because including it // in a .ino sketch forces the Arduino toolchain to build the rest of the // library. -#endif // TENSORFLOW_LITE_EXPERIMENTAL_MICRO_TOOLS_MAKE_TEMPLATES_TENSORFLOWLITE_H_ +#endif // TENSORFLOW_LITE_MICRO_TOOLS_MAKE_TEMPLATES_TENSORFLOWLITE_H_ diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/arduino_example.ino b/tensorflow/lite/micro/tools/make/templates/arduino_example.ino similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/arduino_example.ino rename to tensorflow/lite/micro/tools/make/templates/arduino_example.ino diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/esp/CMakeLists.txt.tpl b/tensorflow/lite/micro/tools/make/templates/esp/CMakeLists.txt.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/esp/CMakeLists.txt.tpl rename to tensorflow/lite/micro/tools/make/templates/esp/CMakeLists.txt.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/esp/README_ESP.md.tpl b/tensorflow/lite/micro/tools/make/templates/esp/README_ESP.md.tpl similarity index 92% rename from tensorflow/lite/experimental/micro/tools/make/templates/esp/README_ESP.md.tpl rename to tensorflow/lite/micro/tools/make/templates/esp/README_ESP.md.tpl index 501a3874a9c..6847893ecc3 100644 --- a/tensorflow/lite/experimental/micro/tools/make/templates/esp/README_ESP.md.tpl +++ b/tensorflow/lite/micro/tools/make/templates/esp/README_ESP.md.tpl @@ -47,7 +47,7 @@ idf.py --port /dev/ttyUSB0 flash monitor ## Project Generation See -[tensorflow/lite/experimental/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/experimental/micro) +[tensorflow/lite/micro](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/lite/micro) for details on how projects like this can be generated from the main source tree. diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/esp/components/tfmicro/CMakeLists.txt.tpl b/tensorflow/lite/micro/tools/make/templates/esp/components/tfmicro/CMakeLists.txt.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/esp/components/tfmicro/CMakeLists.txt.tpl rename to tensorflow/lite/micro/tools/make/templates/esp/components/tfmicro/CMakeLists.txt.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/esp/main/CMakeLists.txt.tpl b/tensorflow/lite/micro/tools/make/templates/esp/main/CMakeLists.txt.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/esp/main/CMakeLists.txt.tpl rename to tensorflow/lite/micro/tools/make/templates/esp/main/CMakeLists.txt.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/keil_project.uvprojx.tpl b/tensorflow/lite/micro/tools/make/templates/keil_project.uvprojx.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/keil_project.uvprojx.tpl rename to tensorflow/lite/micro/tools/make/templates/keil_project.uvprojx.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/library.properties b/tensorflow/lite/micro/tools/make/templates/library.properties similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/library.properties rename to tensorflow/lite/micro/tools/make/templates/library.properties diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/mbed-os.lib.tpl b/tensorflow/lite/micro/tools/make/templates/mbed-os.lib.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/mbed-os.lib.tpl rename to tensorflow/lite/micro/tools/make/templates/mbed-os.lib.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/mbed_app.json.tpl b/tensorflow/lite/micro/tools/make/templates/mbed_app.json.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/mbed_app.json.tpl rename to tensorflow/lite/micro/tools/make/templates/mbed_app.json.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/tasks.json.make.tpl b/tensorflow/lite/micro/tools/make/templates/tasks.json.make.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/tasks.json.make.tpl rename to tensorflow/lite/micro/tools/make/templates/tasks.json.make.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/templates/tasks.json.mbed.tpl b/tensorflow/lite/micro/tools/make/templates/tasks.json.mbed.tpl similarity index 100% rename from tensorflow/lite/experimental/micro/tools/make/templates/tasks.json.mbed.tpl rename to tensorflow/lite/micro/tools/make/templates/tasks.json.mbed.tpl diff --git a/tensorflow/lite/experimental/micro/tools/make/third_party_downloads.inc b/tensorflow/lite/micro/tools/make/third_party_downloads.inc similarity index 90% rename from tensorflow/lite/experimental/micro/tools/make/third_party_downloads.inc rename to tensorflow/lite/micro/tools/make/third_party_downloads.inc index 5b1750180d4..49565c4c3d5 100644 --- a/tensorflow/lite/experimental/micro/tools/make/third_party_downloads.inc +++ b/tensorflow/lite/micro/tools/make/third_party_downloads.inc @@ -20,8 +20,8 @@ LEON_BCC2_MD5 := "cdf78082be4882da2a92c9baa82fe765" TSIM_URL := "https://www.gaisler.com/anonftp/tsim/tsim-eval-2.0.63.tar.gz" TSIM_MD5 := "afa0095d3ed989a949e1467f94e41d2f" -CMSIS_URL := "https://github.com/ARM-software/CMSIS_5/archive/5deff575d14ed255616d23b61d7b95f3b6dd19a8.zip" -CMSIS_MD5 := "3adb1a3d4b7aabfbb40972c609730c9e" +CMSIS_URL := "https://github.com/ARM-software/CMSIS_5/archive/d76d5e3acb87cf089daf50b31f991026149ecb6c.zip" +CMSIS_MD5 := "866f79cfb86f7aee29a320aeda530aca" AM_SDK_URL := "http://s3.asia.ambiqmicro.com/downloads/AmbiqSuite-Rel2.0.0.zip" AM_SDK_MD5 := "70332bc6968602bd85bee600ca81d06f" @@ -52,5 +52,5 @@ SIFIVE_FE310_LIB_MD5 := "06ee24c4956f8e21670ab3395861fe64" KISSFFT_URL="https://github.com/mborgerding/kissfft/archive/v130.zip" KISSFFT_MD5="438ba1fef5783cc5f5f201395cc477ca" -PERSON_MODEL_URL := "https://storage.googleapis.com/download.tensorflow.org/data/tf_lite_micro_person_data_grayscale_2019_11_07.zip" -PERSON_MODEL_MD5 := "e6430de25aa92bcb807d07278a1b5b90" +PERSON_MODEL_URL := "https://storage.googleapis.com/download.tensorflow.org/data/tf_lite_micro_person_data_grayscale_2019_11_21.zip" +PERSON_MODEL_MD5 := "fe2934bd0788f1dcc7af3f0a954542ab" diff --git a/tensorflow/lite/experimental/micro/tools/make/transform_arduino_source.py b/tensorflow/lite/micro/tools/make/transform_arduino_source.py similarity index 98% rename from tensorflow/lite/experimental/micro/tools/make/transform_arduino_source.py rename to tensorflow/lite/micro/tools/make/transform_arduino_source.py index 4b497439ce9..e6b026520de 100644 --- a/tensorflow/lite/experimental/micro/tools/make/transform_arduino_source.py +++ b/tensorflow/lite/micro/tools/make/transform_arduino_source.py @@ -72,7 +72,7 @@ def replace_example_includes(line, _): # Because the export process moves the example source and header files out of # their default locations into the top-level 'examples' folder in the Arduino # library, we have to update any include references to match. - dir_path = 'tensorflow/lite/experimental/micro/examples/' + dir_path = 'tensorflow/lite/micro/examples/' include_match = re.match( r'(.*#include.*")' + six.ensure_str(dir_path) + r'([^/]+)/(.*")', line) if include_match: diff --git a/tensorflow/lite/experimental/micro/tools/make/transform_arduino_source_test.sh b/tensorflow/lite/micro/tools/make/transform_arduino_source_test.sh similarity index 91% rename from tensorflow/lite/experimental/micro/tools/make/transform_arduino_source_test.sh rename to tensorflow/lite/micro/tools/make/transform_arduino_source_test.sh index a4b2fd6c03c..00889b2daf6 100755 --- a/tensorflow/lite/experimental/micro/tools/make/transform_arduino_source_test.sh +++ b/tensorflow/lite/micro/tools/make/transform_arduino_source_test.sh @@ -37,7 +37,7 @@ EOF OUTPUT_REGULAR_FILE=${TEST_TMPDIR}/output_regular.cc THIRD_PARTY_HEADERS="subdir/foo.h subdir_2/include/bar/fish.h subdir_3/something.h" -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/transform_source \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/transform_source \ --platform=arduino \ --third_party_headers="${THIRD_PARTY_HEADERS}" \ < ${INPUT_REGULAR_FILE} \ @@ -78,7 +78,7 @@ INPUT_EXAMPLE_INO_FILE=${TEST_TMPDIR}/input_example_ino.cc cat << EOF > ${INPUT_EXAMPLE_INO_FILE} #include #include "foo.h" -#include "tensorflow/lite/experimental/micro/examples/something/foo/fish.h" +#include "tensorflow/lite/micro/examples/something/foo/fish.h" #include "baz.h" void setup() { @@ -90,7 +90,7 @@ EOF OUTPUT_EXAMPLE_INO_FILE=${TEST_TMPDIR}/output_regular.cc -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/transform_source \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/transform_source \ --platform=arduino \ --third_party_headers="${THIRD_PARTY_HEADERS}" \ --is_example_ino \ @@ -113,7 +113,7 @@ cat << EOF > ${INPUT_EXAMPLE_SOURCE_FILE} #include "foo.h" #include "foo/fish.h" #include "baz.h" -#include "tensorflow/lite/experimental/micro/examples/something/cube/tri.h" +#include "tensorflow/lite/micro/examples/something/cube/tri.h" void setup() { } @@ -131,7 +131,7 @@ EOF OUTPUT_EXAMPLE_SOURCE_FILE=${TEST_TMPDIR}/output_example_source.h -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/transform_source \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/transform_source \ --platform=arduino \ --third_party_headers="${THIRD_PARTY_HEADERS}" \ --is_example_source \ diff --git a/tensorflow/lite/experimental/micro/tools/make/transform_esp_source_test.sh b/tensorflow/lite/micro/tools/make/transform_esp_source_test.sh similarity index 81% rename from tensorflow/lite/experimental/micro/tools/make/transform_esp_source_test.sh rename to tensorflow/lite/micro/tools/make/transform_esp_source_test.sh index 62b53d561c6..b1bbbfb7ab5 100755 --- a/tensorflow/lite/experimental/micro/tools/make/transform_esp_source_test.sh +++ b/tensorflow/lite/micro/tools/make/transform_esp_source_test.sh @@ -22,7 +22,7 @@ INPUT_EXAMPLE_FILE=${TEST_TMPDIR}/input_example.cc cat << EOF > ${INPUT_EXAMPLE_FILE} #include #include "baz.h" -#include "tensorflow/lite/experimental/micro/examples/something/foo/fish.h" +#include "tensorflow/lite/micro/examples/something/foo/fish.h" main() { fprintf(stderr, "Hello World!\n"); @@ -32,10 +32,10 @@ EOF OUTPUT_EXAMPLE_FILE=${TEST_TMPDIR}/output_example.cc -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/transform_source \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/transform_source \ --platform=esp \ --is_example_source \ - --source_path="tensorflow/lite/experimental/micro/examples/something/input_example.cc" \ + --source_path="tensorflow/lite/micro/examples/something/input_example.cc" \ < ${INPUT_EXAMPLE_FILE} \ > ${OUTPUT_EXAMPLE_FILE} @@ -64,17 +64,17 @@ INPUT_EXAMPLE_SUBDIR_FILE=${TEST_TMPDIR}/subdir/input_example.cc cat << EOF > ${INPUT_EXAMPLE_SUBDIR_FILE} #include #include "baz.h" -#include "tensorflow/lite/experimental/micro/examples/something/subdir/input_example.h" -#include "tensorflow/lite/experimental/micro/examples/something/bleh.h" -#include "tensorflow/lite/experimental/micro/examples/something/foo/fish.h" +#include "tensorflow/lite/micro/examples/something/subdir/input_example.h" +#include "tensorflow/lite/micro/examples/something/bleh.h" +#include "tensorflow/lite/micro/examples/something/foo/fish.h" EOF OUTPUT_EXAMPLE_SUBDIR_FILE=${TEST_TMPDIR}/output_example.cc -${TEST_SRCDIR}/tensorflow/lite/experimental/micro/tools/make/transform_source \ +${TEST_SRCDIR}/tensorflow/lite/micro/tools/make/transform_source \ --platform=esp \ --is_example_source \ - --source_path="tensorflow/lite/experimental/micro/examples/something/subdir/input_example.cc" \ + --source_path="tensorflow/lite/micro/examples/something/subdir/input_example.cc" \ < ${INPUT_EXAMPLE_SUBDIR_FILE} \ > ${OUTPUT_EXAMPLE_SUBDIR_FILE} diff --git a/tensorflow/lite/experimental/micro/tools/make/transform_source.py b/tensorflow/lite/micro/tools/make/transform_source.py similarity index 98% rename from tensorflow/lite/experimental/micro/tools/make/transform_source.py rename to tensorflow/lite/micro/tools/make/transform_source.py index 9c77b1dbcc3..f7eaaa08c58 100644 --- a/tensorflow/lite/experimental/micro/tools/make/transform_source.py +++ b/tensorflow/lite/micro/tools/make/transform_source.py @@ -29,7 +29,7 @@ import sys import six -EXAMPLE_DIR_PATH = 'tensorflow/lite/experimental/micro/examples/' +EXAMPLE_DIR_PATH = 'tensorflow/lite/micro/examples/' def replace_arduino_includes(line, supplied_headers_list): @@ -79,7 +79,7 @@ def replace_ardunio_example_includes(line, _): # Because the export process moves the example source and header files out of # their default locations into the top-level 'examples' folder in the Arduino # library, we have to update any include references to match. - dir_path = 'tensorflow/lite/experimental/micro/examples/' + dir_path = 'tensorflow/lite/micro/examples/' include_match = re.match( r'(.*#include.*")' + six.ensure_str(dir_path) + r'([^/]+)/(.*")', line) if include_match: diff --git a/tensorflow/lite/model.cc b/tensorflow/lite/model.cc index a4287a57ea0..d7523dbd99b 100644 --- a/tensorflow/lite/model.cc +++ b/tensorflow/lite/model.cc @@ -375,8 +375,6 @@ TfLiteStatus InterpreterBuilder::ParseQuantization( return kTfLiteError; } - // Affine-quantization. - quantization->type = kTfLiteAffineQuantization; const size_t num_scales = src_quantization->scale()->size(); // Ensure that the quantization dimension is valid. @@ -401,6 +399,8 @@ TfLiteStatus InterpreterBuilder::ParseQuantization( return kTfLiteError; } + // Affine-quantization. + quantization->type = kTfLiteAffineQuantization; auto* affine_quantization = reinterpret_cast( malloc(sizeof(TfLiteAffineQuantization))); affine_quantization->scale = TfLiteFloatArrayCreate(num_scales); @@ -424,18 +424,42 @@ TfLiteStatus InterpreterBuilder::ParseSparsity( return kTfLiteOk; } + if (src_sparsity->traversal_order() == nullptr || + src_sparsity->dim_metadata() == nullptr) { + error_reporter_->Report("Invalid sparsity parameter."); + return kTfLiteError; + } + + const size_t dim_metadata_size = src_sparsity->dim_metadata()->size(); + // Validate sparsity params before allocating the TfLiteSparsity output. + for (int i = 0; i < dim_metadata_size; i++) { + const auto* src_metadata = src_sparsity->dim_metadata()->Get(i); + if (src_metadata->format() != DimensionType_DENSE && + src_metadata->format() != DimensionType_SPARSE_CSR) { + error_reporter_->Report("The %dth dimension has unknown type: %d.", i, + src_metadata->format()); + return kTfLiteError; + } + + if (src_metadata->format() == DimensionType_SPARSE_CSR && + (src_metadata->array_indices() == nullptr || + src_metadata->array_segments() == nullptr)) { + error_reporter_->Report( + "The %dth sparse dimension has invalid parameters.", i); + return kTfLiteError; + } + } + auto* sparsity = reinterpret_cast(malloc(sizeof(TfLiteSparsity))); memset(sparsity, 0, sizeof(TfLiteSparsity)); *sparsity_ptr = sparsity; - if (src_sparsity->traversal_order()) { - const size_t traversal_order_size = src_sparsity->traversal_order()->size(); - sparsity->traversal_order = TfLiteIntArrayCreate(traversal_order_size); - for (int i = 0; i < traversal_order_size; i++) { - sparsity->traversal_order->data[i] = - src_sparsity->traversal_order()->Get(i); - } + const size_t traversal_order_size = src_sparsity->traversal_order()->size(); + sparsity->traversal_order = TfLiteIntArrayCreate(traversal_order_size); + for (int i = 0; i < traversal_order_size; i++) { + sparsity->traversal_order->data[i] = + src_sparsity->traversal_order()->Get(i); } if (src_sparsity->block_map()) { @@ -446,40 +470,33 @@ TfLiteStatus InterpreterBuilder::ParseSparsity( } } - if (src_sparsity->dim_metadata()) { - const size_t dim_metadata_size = src_sparsity->dim_metadata()->size(); - sparsity->dim_metadata_size = dim_metadata_size; - sparsity->dim_metadata = reinterpret_cast( - malloc(dim_metadata_size * sizeof(TfLiteDimensionMetadata))); - memset(sparsity->dim_metadata, 0, - dim_metadata_size * sizeof(TfLiteDimensionMetadata)); + sparsity->dim_metadata_size = dim_metadata_size; + sparsity->dim_metadata = reinterpret_cast( + malloc(dim_metadata_size * sizeof(TfLiteDimensionMetadata))); + memset(sparsity->dim_metadata, 0, + dim_metadata_size * sizeof(TfLiteDimensionMetadata)); - for (int i = 0; i < dim_metadata_size; i++) { - const auto* src_metadata = src_sparsity->dim_metadata()->Get(i); - auto* tgt_metadata = &sparsity->dim_metadata[i]; + for (int i = 0; i < dim_metadata_size; i++) { + const auto* src_metadata = src_sparsity->dim_metadata()->Get(i); + auto* tgt_metadata = &sparsity->dim_metadata[i]; - tgt_metadata->format = - static_cast(src_metadata->format()); + tgt_metadata->format = + static_cast(src_metadata->format()); - if (tgt_metadata->format == kTfLiteDimDense) { - tgt_metadata->dense_size = src_metadata->dense_size(); - } else if (tgt_metadata->format == kTfLiteDimSparseCSR) { - const int array_segments_size = src_metadata->array_segments()->size(); - tgt_metadata->array_segments = - TfLiteIntArrayCreate(array_segments_size); - for (int j = 0; j < array_segments_size; j++) { - tgt_metadata->array_segments->data[j] = - src_metadata->array_segments()->Get(j); - } - const int array_indices_size = src_metadata->array_indices()->size(); - tgt_metadata->array_indices = TfLiteIntArrayCreate(array_indices_size); - for (int j = 0; j < array_indices_size; j++) { - tgt_metadata->array_indices->data[j] = - src_metadata->array_indices()->Get(j); - } - } else { - error_reporter_->Report("Unsupported dimension type."); - return kTfLiteError; + if (tgt_metadata->format == kTfLiteDimDense) { + tgt_metadata->dense_size = src_metadata->dense_size(); + } else { + const int array_segments_size = src_metadata->array_segments()->size(); + tgt_metadata->array_segments = TfLiteIntArrayCreate(array_segments_size); + for (int j = 0; j < array_segments_size; j++) { + tgt_metadata->array_segments->data[j] = + src_metadata->array_segments()->Get(j); + } + const int array_indices_size = src_metadata->array_indices()->size(); + tgt_metadata->array_indices = TfLiteIntArrayCreate(array_indices_size); + for (int j = 0; j < array_indices_size; j++) { + tgt_metadata->array_indices->data[j] = + src_metadata->array_indices()->Get(j); } } } @@ -541,15 +558,9 @@ TfLiteStatus InterpreterBuilder::ParseTensors( const auto* src_quantization = tensor->quantization(); TfLiteQuantization quantization; if (ParseQuantization(src_quantization, &quantization, dims) != kTfLiteOk) { + error_reporter_->Report("Tensor %d has invalid quantization parameters.", + i); status = kTfLiteError; - continue; - } - - const auto* src_sparsity = tensor->sparsity(); - TfLiteSparsity* sparsity = nullptr; - if (ParseSparsity(src_sparsity, &sparsity) != kTfLiteOk) { - status = kTfLiteError; - continue; } bool is_variable = tensor->is_variable(); @@ -562,6 +573,15 @@ TfLiteStatus InterpreterBuilder::ParseTensors( status = kTfLiteError; } + // TODO(b/144999664): Only constant sparse tensor is supported now. + const auto* src_sparsity = tensor->sparsity(); + TfLiteSparsity* sparsity = nullptr; + if (ParseSparsity(src_sparsity, &sparsity) != kTfLiteOk) { + error_reporter_->Report("Tensor %d has invalid sparsity parameters.", + i); + status = kTfLiteError; + } + if (subgraph->SetTensorParametersReadOnly( i, type, get_name(tensor), dims, quantization, buffer_ptr, buffer_size, allocation_, sparsity) != kTfLiteOk) { @@ -570,7 +590,6 @@ TfLiteStatus InterpreterBuilder::ParseTensors( status = kTfLiteError; } } else { - // TODO(b/144999664): Non-constant sparse tensor is not supported now. if (subgraph->SetTensorParametersReadWrite(i, type, get_name(tensor), dims, quantization, is_variable) != kTfLiteOk) { diff --git a/tensorflow/lite/nnapi/BUILD b/tensorflow/lite/nnapi/BUILD index e26d9567337..4cd5ab3922f 100644 --- a/tensorflow/lite/nnapi/BUILD +++ b/tensorflow/lite/nnapi/BUILD @@ -57,7 +57,7 @@ cc_library( "//conditions:default": ["-lrt"], }), deps = [ - "//tensorflow/lite/nnapi:nnapi_lib", + ":nnapi_lib", ], ) @@ -76,7 +76,43 @@ cc_test( name = "nnapi_implementation_test", srcs = ["nnapi_implementation_test.cc"], deps = [ - "//tensorflow/lite/nnapi:nnapi_implementation", + ":nnapi_implementation", + "@com_google_googletest//:gtest_main", + ], +) + +# Cannot inject NNAPI instance on ios and windows +cc_library( + name = "nnapi_handler", + srcs = select({ + "//tensorflow:ios": [], + "//tensorflow:windows": [], + "//conditions:default": ["nnapi_handler.cc"], + }), + hdrs = select({ + "//tensorflow:ios": [], + "//tensorflow:windows": [], + "//conditions:default": ["nnapi_handler.h"], + }), + deps = [ + ":nnapi_implementation", + ":nnapi_lib", + "//tensorflow/core/platform:logging", + "//tensorflow/lite:framework", + ], +) + +cc_test( + name = "nnapi_handler_test", + srcs = ["nnapi_handler_test.cc"], + tags = [ + "no_mac", + "no_windows", + "tflite_not_portable_ios", + ], + deps = [ + ":nnapi_handler", + ":nnapi_implementation", "@com_google_googletest//:gtest_main", ], ) diff --git a/tensorflow/lite/nnapi/nnapi_handler.cc b/tensorflow/lite/nnapi/nnapi_handler.cc new file mode 100644 index 00000000000..354ad66463c --- /dev/null +++ b/tensorflow/lite/nnapi/nnapi_handler.cc @@ -0,0 +1,44 @@ +/* Copyright 2019 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/lite/nnapi/nnapi_handler.h" + +#include + +#include "tensorflow/lite/nnapi/nnapi_implementation.h" + +namespace tflite { +namespace nnapi { + +const NnApi* NnApiPassthroughInstance() { + static const NnApi orig_nnapi_copy = *NnApiImplementation(); + return &orig_nnapi_copy; +} + +// static +NnApiHandler* NnApiHandler::Instance() { + // Ensuring that the original copy of nnapi is saved before we return + // access to NnApiHandler + NnApiPassthroughInstance(); + static NnApiHandler handler{const_cast(NnApiImplementation())}; + return &handler; +} + +void NnApiHandler::Reset() { + // Restores global NNAPI to original value + *nnapi_ = *NnApiPassthroughInstance(); +} + +} // namespace nnapi +} // namespace tflite diff --git a/tensorflow/lite/nnapi/nnapi_handler.h b/tensorflow/lite/nnapi/nnapi_handler.h new file mode 100644 index 00000000000..70406ba2c6e --- /dev/null +++ b/tensorflow/lite/nnapi/nnapi_handler.h @@ -0,0 +1,197 @@ +/* Copyright 2019 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_LITE_NNAPI_NNAPI_HANDLER_H_ +#define TENSORFLOW_LITE_NNAPI_NNAPI_HANDLER_H_ + +#include "tensorflow/core/platform/logging.h" +#include "tensorflow/lite/nnapi/nnapi_implementation.h" + +namespace tflite { +namespace nnapi { + +// Offers an interface to alter the behaviour of the NNAPI instance. +// As for NNAPI, it is designed to be a singleton. +// It allows to change the behaviour of some of the methods with some stub +// implementation and then to reset the behavior to the original one using +// Reset(). +// +class NnApiHandler { + public: + // No destructor defined to allow this class to be used as singleton. + + // Factory method, only one instance per process/jni library. + static NnApiHandler* Instance(); + + // Makes the current object a transparent proxy again, resetting any + // applied changes to its methods. + void Reset(); + + // Using templates in the ...Returns methods because the functions need to be + // stateless and the template generated code is more readable than using a + // file-local variable in the method implementation to store the configured + // result. + + template + void GetDeviceCountReturns() { + nnapi_->ANeuralNetworks_getDeviceCount = [](uint32_t* numDevices) -> int { + *numDevices = 2; + return Value; + }; + } + + void StubGetDeviceCountWith(int(stub)(uint32_t*)) { + nnapi_->ANeuralNetworks_getDeviceCount = stub; + } + + template + void ModelCreateReturns() { + nnapi_->ANeuralNetworksModel_create = [](ANeuralNetworksModel** model) { + *model = reinterpret_cast(1); + return Value; + }; + } + + template + void AddOperandReturns() { + nnapi_->ANeuralNetworksModel_addOperand = + [](ANeuralNetworksModel* model, + const ANeuralNetworksOperandType* type) { return Value; }; + } + + template + void SetOperandValueReturns() { + nnapi_->ANeuralNetworksModel_setOperandValue = + [](ANeuralNetworksModel* model, int32_t index, const void* buffer, + size_t length) { return Value; }; + } + + template + void AddOperationReturns() { + nnapi_->ANeuralNetworksModel_addOperation = + [](ANeuralNetworksModel* model, ANeuralNetworksOperationType type, + uint32_t inputCount, const uint32_t* inputs, uint32_t outputCount, + const uint32_t* outputs) { return Value; }; + } + + template + void IdentifyInputAndOutputsReturns() { + nnapi_->ANeuralNetworksModel_identifyInputsAndOutputs = + [](ANeuralNetworksModel* model, uint32_t inputCount, + const uint32_t* inputs, uint32_t outputCount, + const uint32_t* outputs) { return Value; }; + } + + template + void RelaxComputationFloatReturns() { + nnapi_->ANeuralNetworksModel_relaxComputationFloat32toFloat16 = + [](ANeuralNetworksModel* model, bool allow) { return Value; }; + } + + template + void ModelFinishReturns() { + nnapi_->ANeuralNetworksModel_finish = [](ANeuralNetworksModel* model) { + return Value; + }; + } + + template + void MemoryCreateFromFdReturns() { + nnapi_->ANeuralNetworksMemory_createFromFd = + [](size_t size, int protect, int fd, size_t offset, + ANeuralNetworksMemory** memory) { + *memory = reinterpret_cast(2); + return Value; + }; + } + + template + void CompilationCreateReturns() { + nnapi_->ANeuralNetworksCompilation_create = + [](ANeuralNetworksModel* model, + ANeuralNetworksCompilation** compilation) { + *compilation = reinterpret_cast(3); + return Value; + }; + } + + template + void CompilationFinishReturns() { + nnapi_->ANeuralNetworksCompilation_finish = + [](ANeuralNetworksCompilation* compilation) { return Value; }; + } + + template + void ExecutionCreateReturns() { + nnapi_->ANeuralNetworksExecution_create = + [](ANeuralNetworksCompilation* compilation, + ANeuralNetworksExecution** execution) { + if (compilation == nullptr) return 1; + *execution = reinterpret_cast(4); + return Value; + }; + } + template + void ExecutionSetInputFromMemoryReturns() { + nnapi_->ANeuralNetworksExecution_setInputFromMemory = + [](ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, + const ANeuralNetworksMemory* memory, size_t offset, + size_t length) { return Value; }; + } + template + void ExecutionSetOutputFromMemoryReturns() { + nnapi_->ANeuralNetworksExecution_setOutputFromMemory = + [](ANeuralNetworksExecution* execution, int32_t index, + const ANeuralNetworksOperandType* type, + const ANeuralNetworksMemory* memory, size_t offset, + size_t length) { return Value; }; + } + + template + void ExecutionComputeReturns() { + nnapi_->ANeuralNetworksExecution_compute = + [](ANeuralNetworksExecution* execution) { return Value; }; + } + + protected: + explicit NnApiHandler(NnApi* nnapi) : nnapi_(nnapi) { DCHECK(nnapi); } + + NnApi* nnapi_; +}; + +// Returns a pointer to an unaltered instance of NNAPI. Is intended +// to be used by stub methods when wanting to pass-through to original +// implementation for example: +// +// NnApiTestUtility()->StubGetDeviceWith( +// [](uint32_t devIndex, ANeuralNetworksDevice** device) -> int { +// static int count = 0; +// if (count++ < 1) { +// NnApiPassthroughInstance()->ANeuralNetworks_getDevice( +// devIndex, device); +// } else { +// return ANEURALNETWORKS_BAD_DATA; +// } +// }); +const NnApi* NnApiPassthroughInstance(); + +// Returns an instance of NnApiProxy that can be used to alter +// the behaviour of the TFLite wide instance of NnApi. +NnApiHandler* NnApiProxyInstance(); + +} // namespace nnapi +} // namespace tflite + +#endif // TENSORFLOW_LITE_NNAPI_NNAPI_HANDLER_H_ diff --git a/tensorflow/lite/nnapi/nnapi_handler_test.cc b/tensorflow/lite/nnapi/nnapi_handler_test.cc new file mode 100644 index 00000000000..aea766ef036 --- /dev/null +++ b/tensorflow/lite/nnapi/nnapi_handler_test.cc @@ -0,0 +1,143 @@ +/* Copyright 2019 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/lite/nnapi/nnapi_handler.h" + +#include +#include + +#include +#include +#include "tensorflow/lite/nnapi/nnapi_implementation.h" + +namespace tflite { +namespace nnapi { + +using testing::Eq; +using testing::Ne; +using testing::NotNull; + +void ExpectEquals(const NnApi& left, const NnApi& right); + +class NnApiHandlerTest : public ::testing::Test { + protected: + ~NnApiHandlerTest() override { NnApiHandler::Instance()->Reset(); } +}; + +TEST_F(NnApiHandlerTest, ShouldAlterNnApiInstanceBehaviour) { + const NnApi* nnapi = NnApiImplementation(); + + const auto device_count_stub = [](uint32_t* device_count) -> int { + *device_count = 999; + return ANEURALNETWORKS_NO_ERROR; + }; + + NnApiHandler::Instance()->StubGetDeviceCountWith(device_count_stub); + + ASSERT_THAT(nnapi->ANeuralNetworks_getDeviceCount, NotNull()); + + uint32_t device_count = 0; + nnapi->ANeuralNetworks_getDeviceCount(&device_count); + EXPECT_THAT(device_count, Eq(999)); +} + +TEST_F(NnApiHandlerTest, ShouldRestoreNnApiToItsOriginalValueWithReset) { + NnApi nnapi_orig_copy = *NnApiImplementation(); + + auto device_count_override = [](uint32_t* device_count) -> int { + *device_count = 777; + return ANEURALNETWORKS_NO_ERROR; + }; + + NnApiHandler::Instance()->StubGetDeviceCountWith(device_count_override); + + EXPECT_THAT(nnapi_orig_copy.ANeuralNetworks_getDeviceCount, + Ne(NnApiImplementation()->ANeuralNetworks_getDeviceCount)); + + NnApiHandler::Instance()->Reset(); + + ExpectEquals(nnapi_orig_copy, *NnApiImplementation()); +} + +int (*device_count_ptr)(uint32_t*); +TEST_F(NnApiHandlerTest, ShouldSupportPassthroughCalls) { + const NnApi* nnapi = NnApiImplementation(); + device_count_ptr = nnapi->ANeuralNetworks_getDeviceCount; + + NnApiHandler::Instance()->StubGetDeviceCountWith( + [](uint32_t* device_count) -> int { + return NnApiPassthroughInstance()->ANeuralNetworks_getDeviceCount == + device_count_ptr; + }); + + uint32_t device_count = 0; + EXPECT_THAT(nnapi->ANeuralNetworks_getDeviceCount(&device_count), Eq(1)); +} + +void ExpectEquals(const NnApi& left, const NnApi& right) { +#define EXPECT_NNAPI_MEMBER_EQ(name) EXPECT_EQ(left.name, right.name) + + EXPECT_NNAPI_MEMBER_EQ(nnapi_exists); + EXPECT_NNAPI_MEMBER_EQ(android_sdk_version); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksMemory_createFromFd); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksMemory_free); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_create); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_free); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_finish); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_addOperand); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_setOperandValue); + EXPECT_NNAPI_MEMBER_EQ( + ANeuralNetworksModel_setOperandSymmPerChannelQuantParams); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_setOperandValueFromMemory); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_addOperation); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_identifyInputsAndOutputs); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_relaxComputationFloat32toFloat16); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksCompilation_create); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksCompilation_free); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksCompilation_setPreference); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksCompilation_finish); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_create); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_free); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_setInput); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_setInputFromMemory); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_setOutput); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_setOutputFromMemory); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_startCompute); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksEvent_wait); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksEvent_free); + EXPECT_NNAPI_MEMBER_EQ(ASharedMemory_create); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworks_getDeviceCount); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworks_getDevice); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksDevice_getName); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksDevice_getVersion); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksDevice_getFeatureLevel); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksDevice_getType); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksModel_getSupportedOperationsForDevices); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksCompilation_createForDevices); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksCompilation_setCaching); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_compute); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_getOutputOperandRank); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_getOutputOperandDimensions); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksBurst_create); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksBurst_free); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_burstCompute); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksMemory_createFromAHardwareBuffer); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_setMeasureTiming); + EXPECT_NNAPI_MEMBER_EQ(ANeuralNetworksExecution_getDuration); + +#undef EXPECT_NNAPI_MEMBER_EQ +} + +} // namespace nnapi +} // namespace tflite diff --git a/tensorflow/lite/profiling/BUILD b/tensorflow/lite/profiling/BUILD index 672de4095d5..03dd5054c17 100644 --- a/tensorflow/lite/profiling/BUILD +++ b/tensorflow/lite/profiling/BUILD @@ -95,7 +95,7 @@ cc_library( deps = [ ":memory_info", ":profile_buffer", - "//tensorflow/core:stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//tensorflow/lite:framework", "//tensorflow/lite/schema:schema_fbs", ], diff --git a/tensorflow/lite/profiling/profile_summarizer.cc b/tensorflow/lite/profiling/profile_summarizer.cc index b004bc2e361..0b51b653923 100644 --- a/tensorflow/lite/profiling/profile_summarizer.cc +++ b/tensorflow/lite/profiling/profile_summarizer.cc @@ -164,9 +164,9 @@ void ProfileSummarizer::ProcessProfiles( event->end_mem_usage - event->begin_mem_usage; std::string node_name(event->tag); node_name += "/" + std::to_string(event->event_subgraph_index); - stats_calculator->AddNodeStats(node_name, "Misc Runtime Ops", node_num, - start_us, node_exec_time, - node_mem_usage.total_allocated_bytes); + stats_calculator->AddNodeStats(node_name, event->tag, node_num, start_us, + node_exec_time, + node_mem_usage.max_rss_kb * 1000.0); } // Add total time except actual delegate ops since the elapsed time of the diff --git a/tensorflow/lite/python/convert.py b/tensorflow/lite/python/convert.py index 78bfdc82e29..bac1cb6c720 100644 --- a/tensorflow/lite/python/convert.py +++ b/tensorflow/lite/python/convert.py @@ -255,10 +255,10 @@ def build_toco_convert_protos(input_tensors, `foo.shape` and `foo.dtype`. output_tensors: List of output tensors (only .name is used from this). inference_type: Target data type of real-number arrays in the output file. - Must be `{tf.float32, tf.uint8}`. (default tf.float32) - Must be `{tf.float32, tf.uint8}`. (default `inference_type`) + Must be `{tf.float32, tf.uint8, tf.int8}`. (default tf.float32) inference_input_type: Target data type of real-number input arrays. Allows for a different type for input arrays in the case of quantization. + Must be `{tf.float32, tf.uint8, tf.int8}`. (default `inference_type`) input_format: Type of data to read Currently must be `{TENSORFLOW_GRAPHDEF}`. (default TENSORFLOW_GRAPHDEF) input_shapes: Input array shape. It needs to be a list of the same length @@ -267,7 +267,7 @@ def build_toco_convert_protos(input_tensors, GRAPHVIZ_DOT}`. (default TFLITE) quantized_input_stats: List of tuples of floats representing the mean and standard deviation. Each tuple maps to the corresponding input tensor. - Only need if `inference_input_type` is `QUANTIZED_UINT8`. + Only need if `inference_input_type` is `QUANTIZED_UINT8` or `INT8`. real_input_value = (quantized_input_value - mean_value) / std_dev_value. (default None) default_ranges_stats: Tuple of integers representing (min, max) range values @@ -363,11 +363,10 @@ def build_toco_convert_protos(input_tensors, input_array.data_type = util.convert_dtype_to_tflite_type( input_tensor.dtype) - if toco.inference_input_type in \ - [_types_pb2.QUANTIZED_UINT8, _types_pb2.INT8]: - if not quantized_input_stats: - raise ValueError("std_dev and mean must be defined when " - "inference_input_type is QUANTIZED_UINT8.") + if toco.inference_type in [_types_pb2.QUANTIZED_UINT8, _types_pb2.INT8]: + if not quantized_input_stats and not post_training_quantize: + raise ValueError("std_dev and mean must be defined when inference_type " + "is QUANTIZED_UINT8 or INT8.") input_array.mean_value, input_array.std_value = quantized_input_stats[idx] if input_shapes is None: shape = input_tensor.shape @@ -418,11 +417,13 @@ def toco_convert_graph_def(input_data, input_arrays_with_shape, output_arrays, for idx, (name, shape) in enumerate(input_arrays_with_shape): input_array = model_flags.input_arrays.add() - if toco_flags.inference_input_type == _types_pb2.QUANTIZED_UINT8: - if (("quantized_input_stats" not in kwargs) or - (not kwargs["quantized_input_stats"])): + if toco_flags.inference_type in ( + [_types_pb2.QUANTIZED_UINT8, _types_pb2.INT8]): + if ((("quantized_input_stats" not in kwargs) or + (not kwargs["quantized_input_stats"])) and + not toco_flags.post_training_quantize): raise ValueError("std_dev and mean must be defined when " - "inference_input_type is QUANTIZED_UINT8.") + "inference_type is QUANTIZED_UINT8 or INT8.") input_array.mean_value, input_array.std_value = kwargs[ "quantized_input_stats"][idx] input_array.name = name diff --git a/tensorflow/lite/python/convert_test.py b/tensorflow/lite/python/convert_test.py index 543ddda0d7c..fcd3128cbb4 100644 --- a/tensorflow/lite/python/convert_test.py +++ b/tensorflow/lite/python/convert_test.py @@ -76,8 +76,17 @@ class ConvertTest(test_util.TensorFlowTestCase): sess.graph_def, [in_tensor], [out_tensor], inference_type=lite_constants.QUANTIZED_UINT8) self.assertEqual( - "std_dev and mean must be defined when inference_input_type is " - "QUANTIZED_UINT8.", str(error.exception)) + "std_dev and mean must be defined when inference_type is " + "QUANTIZED_UINT8 or INT8.", str(error.exception)) + + with self.assertRaises(ValueError) as error: + convert.toco_convert( + sess.graph_def, [in_tensor], [out_tensor], + inference_type=lite_constants.QUANTIZED_UINT8, + inference_input_type=lite_constants.FLOAT) + self.assertEqual( + "std_dev and mean must be defined when inference_type is " + "QUANTIZED_UINT8 or INT8.", str(error.exception)) def testGraphDefBasic(self): with ops.Graph().as_default(): @@ -176,8 +185,8 @@ class ConvertTest(test_util.TensorFlowTestCase): enable_mlir_converter=False, inference_type=lite_constants.QUANTIZED_UINT8) self.assertEqual( - "std_dev and mean must be defined when inference_input_type is " - "QUANTIZED_UINT8.", str(error.exception)) + "std_dev and mean must be defined when inference_type is " + "QUANTIZED_UINT8 or INT8.", str(error.exception)) class ConvertTestOpHint(test_util.TensorFlowTestCase): diff --git a/tensorflow/lite/python/lite.py b/tensorflow/lite/python/lite.py index 52628218997..83e97f156eb 100644 --- a/tensorflow/lite/python/lite.py +++ b/tensorflow/lite/python/lite.py @@ -22,6 +22,7 @@ from __future__ import print_function import enum import warnings +from absl import logging import six from six import PY3 @@ -470,6 +471,17 @@ class TFLiteConverterV2(TFLiteConverterBase): converter_kwargs = self._get_base_converter_args() + if not self.experimental_new_converter: + logging.warning( + "Please consider switching to use new converter by setting " + "experimental_new_converter to true. " + "Old converter (TOCO) is deprecated and flow will be switched on " + "by default to use new converter soon.") + else: + logging.info("Using experimental converter: If you encountered a problem " + "please file a bug. You can opt-out " + "by setting experimental_new_converter=False") + # Converts model. result = _toco_convert_impl( input_data=graph_def, @@ -986,7 +998,12 @@ class TFLiteConverter(TFLiteConverterBase): "are not enabled.") optimized_graph = self._graph_def - if self.inference_type != constants.QUANTIZED_UINT8: + # if it is not uint8 or int8 with post-training quantization, it is not + # quantization aware training, then graph optimization is applied. + # Graph optimization is disabled for quantization aware training. + if (self.inference_type != constants.QUANTIZED_UINT8 or + (self.inference_type == constants.INT8 and + (post_training_optimize or weight_only_quantize))): try: optimized_graph = _run_graph_optimizations( self._graph_def, @@ -1014,6 +1031,17 @@ class TFLiteConverter(TFLiteConverterBase): "custom_opdefs": self._custom_opdefs, }) + if not self.experimental_new_converter: + logging.warning( + "Please consider switching to use new converter by setting " + "experimental_new_converter to true. " + "Old converter (TOCO) is deprecated and flow will be switched on " + "by default to use new converter soon.") + else: + logging.info("Using experimental converter: If you encountered a problem " + "please file a bug. You can opt-out " + "by setting experimental_new_converter=False") + # Converts model. if self._has_valid_tensors(): result = _toco_convert_impl( diff --git a/tensorflow/lite/python/lite_test.py b/tensorflow/lite/python/lite_test.py index 0a0eeb46225..96c9aa72ebc 100644 --- a/tensorflow/lite/python/lite_test.py +++ b/tensorflow/lite/python/lite_test.py @@ -19,6 +19,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import io +import logging import os import tempfile @@ -53,7 +55,25 @@ from tensorflow.python.saved_model import saved_model from tensorflow.python.training.training_util import write_graph -class TestModels(test_util.TensorFlowTestCase): +class LiteTest(test_util.TensorFlowTestCase): + """Base class of all the tests in this module.""" + + def setUp(self): + # Some cases are broken when we enable the new converter by default. + # Explicitly disabling it for now. + # TODO(b/145763157): Investigate if these are real issues. + self._original_use_experimental_new_converter = ( + lite._USE_EXPERIMENTAL_NEW_CONVERTER) + lite._USE_EXPERIMENTAL_NEW_CONVERTER = False + super(LiteTest, self).setUp() + + def tearDown(self): + super(LiteTest, self).tearDown() + lite._USE_EXPERIMENTAL_NEW_CONVERTER = ( + self._original_use_experimental_new_converter) + + +class TestModels(LiteTest): def assertValidDebugInfo(self, debug_info): """Verify the DebugInfo is valid.""" @@ -1202,7 +1222,7 @@ class FromSessionTest(TestModels, parameterized.TestCase): self.assertIn(('add@' + six.ensure_str(func)), converter._debug_info.traces) -class FromFrozenGraphFile(test_util.TensorFlowTestCase): +class FromFrozenGraphFile(LiteTest): def testFloat(self): with ops.Graph().as_default(): @@ -1388,7 +1408,7 @@ class FromFrozenGraphFile(test_util.TensorFlowTestCase): self.assertTrue(not converter._debug_info) -class FromFrozenGraphObjectDetection(test_util.TensorFlowTestCase): +class FromFrozenGraphObjectDetection(LiteTest): def _initObjectDetectionArgs(self): # Initializes the arguments required for the object detection model. @@ -1526,6 +1546,37 @@ class FromSavedModelTest(TestModels): self.assertTrue(([1, 16, 16, 3] == output_details[0]['shape']).all()) self.assertEqual((0., 0.), output_details[0]['quantization']) + def testOldConverterWarning(self): + """Test if the warning message when using TOCO is logged.""" + saved_model_dir = self._createSavedModel(shape=[1, 16, 16, 3]) + log = io.BytesIO() if six.PY2 else io.StringIO() + handler = logging.StreamHandler(log) + logging.root.addHandler(handler) + warning_message = 'Please consider switching to use new converter' + # Convert model and ensure model is not None. + converter = lite.TFLiteConverter.from_saved_model(saved_model_dir) + converter.experimental_new_converter = False + tflite_model = converter.convert() + self.assertTrue(tflite_model) + self.assertIn(warning_message, log.getvalue()) + logging.root.removeHandler(handler) + + def testNewConverterOptOut(self): + """Test if the opt out message when using New converter is logged.""" + saved_model_dir = self._createSavedModel(shape=[1, 16, 16, 3]) + log = io.BytesIO() if six.PY2 else io.StringIO() + handler = logging.StreamHandler(log) + logging.root.addHandler(handler) + optout_message = ('Using experimental converter: ' + 'If you encountered a problem') + # Convert model and ensure model is not None. + converter = lite.TFLiteConverter.from_saved_model(saved_model_dir) + converter.experimental_new_converter = True + tflite_model = converter.convert() + self.assertTrue(tflite_model) + self.assertIn(optout_message, log.getvalue()) + logging.root.removeHandler(handler) + def testNoneBatchSize(self): """Test a SavedModel, with None in input tensor's shape.""" saved_model_dir = self._createSavedModel(shape=[None, 16, 16, 3]) @@ -2082,7 +2133,7 @@ class GrapplerTest(TestModels, parameterized.TestCase): self.assertEqual('Placeholder', input_details[0]['name']) self.assertEqual('Const', input_details[1]['name']) -class ImportOpsUtilTest(test_util.TensorFlowTestCase): +class ImportOpsUtilTest(LiteTest): def testGetPotentiallySupportedOps(self): self.assertIsNotNone(lite.get_potentially_supported_ops()) diff --git a/tensorflow/lite/python/lite_v2_test.py b/tensorflow/lite/python/lite_v2_test.py index f3842ae4bdf..f4a6a4e6d19 100644 --- a/tensorflow/lite/python/lite_v2_test.py +++ b/tensorflow/lite/python/lite_v2_test.py @@ -787,6 +787,12 @@ class GrapplerTest(TestModels): actual_value = self._evaluateTFLiteModel(tflite_model, [input_data]) np.testing.assert_almost_equal(expected_value.numpy(), actual_value[0]) + # Enable hybrid quantization, same result + converter.experimental_new_converter = True + converter.optimizations = [lite.Optimize.DEFAULT] + hybrid_tflite_model = converter.convert() + actual_value = self._evaluateTFLiteModel(hybrid_tflite_model, [input_data]) + np.testing.assert_almost_equal(expected_value.numpy(), actual_value[0]) if __name__ == '__main__': test.main() diff --git a/tensorflow/lite/python/tflite_convert.py b/tensorflow/lite/python/tflite_convert.py index 5a3e9961e5a..734baecd413 100644 --- a/tensorflow/lite/python/tflite_convert.py +++ b/tensorflow/lite/python/tflite_convert.py @@ -207,8 +207,6 @@ def _convert_tf1_model(flags): if flags.experimental_new_converter: converter.experimental_new_converter = True - if flags.experimental_legacy_converter: - converter.experimental_new_converter = False # Convert model. output_data = converter.convert() @@ -234,8 +232,6 @@ def _convert_tf2_model(flags): if flags.experimental_new_converter: converter.experimental_new_converter = True - if flags.experimental_legacy_converter: - converter.experimental_new_converter = False # Convert the model. tflite_model = converter.convert() @@ -310,10 +306,6 @@ def _check_tf1_flags(flags, unparsed): "--experimental_new_converter") if flags.custom_opdefs and not flags.allow_custom_ops: raise ValueError("--custom_opdefs must be used with --allow_custom_ops") - if flags.experimental_new_converter and flags.experimental_legacy_converter: - raise ValueError( - "--experimental_new_converter and experimental_legacy_converter " - "cannot be used together") def _check_tf2_flags(flags): @@ -328,10 +320,6 @@ def _check_tf2_flags(flags): if not flags.keras_model_file and not flags.saved_model_dir: raise ValueError("one of the arguments --saved_model_dir " "--keras_model_file is required") - if flags.experimental_new_converter and flags.experimental_legacy_converter: - raise ValueError( - "--experimental_new_converter and experimental_legacy_converter " - "cannot be used together") def _get_tf1_flags(parser): @@ -542,6 +530,36 @@ def _get_tf2_flags(parser): help=("Enables the TensorFlow V1 converter in 2.0")) +class _ParseExperimentalNewConverter(argparse.Action): + """Helper class to parse --experimental_new_converter argument.""" + + def __init__(self, option_strings, dest, nargs=None, **kwargs): + if nargs != "?": + # This should never happen. This class is only used once below with + # nargs="?". + raise ValueError( + "This parser only supports nargs='?' (0 or 1 additional arguments)") + super(_ParseExperimentalNewConverter, self).__init__( + option_strings, dest, nargs=nargs, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + if values is None: + # Handling `--experimental_new_converter`. + # Without additional arguments, it implies enabling the new converter. + experimental_new_converter = True + elif values.lower() == "true": + # Handling `--experimental_new_converter=true`. + # (Case insensitive after the equal sign) + experimental_new_converter = True + elif values.lower() == "false": + # Handling `--experimental_new_converter=false`. + # (Case insensitive after the equal sign) + experimental_new_converter = False + else: + raise ValueError("Invalid --experimental_new_converter argument.") + setattr(namespace, self.dest, experimental_new_converter) + + def _get_parser(use_v2_converter): """Returns an ArgumentParser for tflite_convert. @@ -564,20 +582,12 @@ def _get_parser(use_v2_converter): else: _get_tf1_flags(parser) - # Note: When neither of the following command line argument is passed, - # it will use the default behavior defined in `lite.py`. - # Enable MLIR-based TFLite converter. parser.add_argument( "--experimental_new_converter", - action="store_true", + action=_ParseExperimentalNewConverter, + nargs="?", help=("Experimental flag, subject to change. Enables MLIR-based " "conversion instead of TOCO conversion.")) - # Explicitly disable the MLIR-based TFLite converter. - parser.add_argument( - "--experimental_legacy_converter", - action="store_true", - help=("Experimental flag, subject to change. Disable MLIR-based " - "conversion and use the legacy converter.")) return parser diff --git a/tensorflow/lite/python/tflite_convert_test.py b/tensorflow/lite/python/tflite_convert_test.py index 298b606cfe7..2033000afc3 100644 --- a/tensorflow/lite/python/tflite_convert_test.py +++ b/tensorflow/lite/python/tflite_convert_test.py @@ -21,6 +21,7 @@ from __future__ import print_function import os import numpy as np +from tensorflow.lite.python import tflite_convert from tensorflow.python import keras from tensorflow.python import tf2 from tensorflow.python.client import session @@ -116,9 +117,10 @@ class TfLiteConvertV1Test(TestModels): write_graph(sess.graph_def, '', graph_def_file, False) sess.close() - flags_str = ('--graph_def_file={0} --input_arrays={1} ' - '--output_arrays={2} --experimental_legacy_converter'.format( - graph_def_file, 'Placeholder', 'add')) + flags_str = ( + '--graph_def_file={0} --input_arrays={1} ' + '--output_arrays={2} --experimental_new_converter=false'.format( + graph_def_file, 'Placeholder', 'add')) self._run(flags_str, should_succeed=True) os.remove(graph_def_file) @@ -310,5 +312,75 @@ class TfLiteConvertV2Test(TestModels): should_succeed=False) +class ArgParserTest(test_util.TensorFlowTestCase): + + def test_without_experimental_new_converter(self): + args = [ + '--saved_model_dir=/tmp/saved_model/', + '--output_file=/tmp/output.tflite', + ] + + # V1 parser. + parser = tflite_convert._get_parser(False) + parsed_args = parser.parse_args(args) + self.assertFalse(parsed_args.experimental_new_converter) + + # V2 parser. + parser = tflite_convert._get_parser(True) + parsed_args = parser.parse_args(args) + self.assertFalse(parsed_args.experimental_new_converter) + + def test_experimental_new_converter(self): + args = [ + '--saved_model_dir=/tmp/saved_model/', + '--output_file=/tmp/output.tflite', + '--experimental_new_converter', + ] + + # V1 parser. + parser = tflite_convert._get_parser(False) + parsed_args = parser.parse_args(args) + self.assertTrue(parsed_args.experimental_new_converter) + + # V2 parser. + parser = tflite_convert._get_parser(True) + parsed_args = parser.parse_args(args) + self.assertTrue(parsed_args.experimental_new_converter) + + def test_experimental_new_converter_true(self): + args = [ + '--saved_model_dir=/tmp/saved_model/', + '--output_file=/tmp/output.tflite', + '--experimental_new_converter=true', + ] + + # V1 parser. + parser = tflite_convert._get_parser(False) + parsed_args = parser.parse_args(args) + self.assertTrue(parsed_args.experimental_new_converter) + + # V2 parser. + parser = tflite_convert._get_parser(True) + parsed_args = parser.parse_args(args) + self.assertTrue(parsed_args.experimental_new_converter) + + def test_experimental_new_converter_false(self): + args = [ + '--saved_model_dir=/tmp/saved_model/', + '--output_file=/tmp/output.tflite', + '--experimental_new_converter=false', + ] + + # V1 parser. + parser = tflite_convert._get_parser(False) + parsed_args = parser.parse_args(args) + self.assertFalse(parsed_args.experimental_new_converter) + + # V2 parser. + parser = tflite_convert._get_parser(True) + parsed_args = parser.parse_args(args) + self.assertFalse(parsed_args.experimental_new_converter) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/lite/schema/BUILD b/tensorflow/lite/schema/BUILD index 550889fb9c2..33e7eec8421 100644 --- a/tensorflow/lite/schema/BUILD +++ b/tensorflow/lite/schema/BUILD @@ -1,6 +1,6 @@ load("//tensorflow:tensorflow.bzl", "py_test") load("//tensorflow/lite:special_rules.bzl", "tflite_portable_test_suite") -load("@flatbuffers//:build_defs.bzl", "flatbuffer_cc_library") +load("//tensorflow/lite/micro:build_def.bzl", "flatbuffer_cc_library") package( default_visibility = [ @@ -61,10 +61,10 @@ exports_files([ "schema_v3.fbs", ]) -# Generic schema for inference on device. flatbuffer_cc_library( name = "schema_fbs", srcs = ["schema.fbs"], + build_for_embedded = True, ) # Generic schema for flatbuffer converter (but with mutable makes bigger). diff --git a/tensorflow/lite/schema/schema.fbs b/tensorflow/lite/schema/schema.fbs index 7e70f986998..a41ac0275d6 100644 --- a/tensorflow/lite/schema/schema.fbs +++ b/tensorflow/lite/schema/schema.fbs @@ -89,11 +89,10 @@ table QuantizationParameters { // whereas to store it in column major order, the traversal order would be // (d1, d0). If the 2-D matrix has a 2-D inner block, the traversal order // could be (d0, d1, d2, d3). -// 2. In the order of (d0, ..., dn-1, dn, ..., dn+k-1), whether each dimension -// is DENSE or SPARSE. -// 3. How each block dimension in (dn, ..., dn+k-1) maps to the original +// 2. How each block dimension in (dn, ..., dn+k-1) maps to the original // tensor dimension in (d0, ..., dn-1). -// 4. Index metadata for each dimension. For a dense dimension, this is just +// 3. In the traversal order defined above, the format (dense vs. sparse) and +// index metadata for each dimension. For a dense dimension, this is just // the size of that dimension. For a sparse dimension, it's the same as // the compressed index defined in the Compressed Sparse Row (CSR) format. // (http://scipy-lectures.org/advanced/scipy_sparse/csr_matrix.html) @@ -110,9 +109,9 @@ enum DimensionType : byte { } table DimensionMetadata { - // Whether each dimension is dense or sparse. + // Whether a dimension is dense or sparse. format:DimensionType; - // Index metadata used for each dimension. + // Index metadata used for a dimension. // - If format is DimensionType.DENSE then we use the dense_size field to // store the size of that dimension. Each index in that dimension is // stored implicitly. @@ -149,7 +148,7 @@ table SparsityParameters { // It's stored in the order of (dn, ..., dn+k-1). // If not block-sparse, this field is NULL. block_map:[int]; - // In the order of (d0, ..., dn-1, dn, ..., dn+k-1), the metadata needed for + // In the traversal order defined above, the metadata needed for // each dimension to locate the non-zero values in the original dense tensor. // The size of the dim_metadata array = the size of the traversal_order array // = n + k. @@ -316,7 +315,8 @@ enum BuiltinOperator : byte { WHILE = 119, NON_MAX_SUPPRESSION_V4 = 120, NON_MAX_SUPPRESSION_V5 = 121, - SCATTER_ND = 122 + SCATTER_ND = 122, + SELECT_V2 = 123 } @@ -418,7 +418,8 @@ union BuiltinOptions { DepthToSpaceOptions, NonMaxSuppressionV4Options, NonMaxSuppressionV5Options, - ScatterNdOptions + ScatterNdOptions, + SelectV2Options } enum Padding : byte { SAME, VALID } @@ -902,6 +903,9 @@ table NonMaxSuppressionV5Options { table ScatterNdOptions { } +table SelectV2Options { +} + // 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/lite/schema/schema_generated.h b/tensorflow/lite/schema/schema_generated.h index ae523cc7d5a..b5c80ed55b1 100755 --- a/tensorflow/lite/schema/schema_generated.h +++ b/tensorflow/lite/schema/schema_generated.h @@ -328,6 +328,9 @@ struct NonMaxSuppressionV5OptionsT; struct ScatterNdOptions; struct ScatterNdOptionsT; +struct SelectV2Options; +struct SelectV2OptionsT; + struct OperatorCode; struct OperatorCodeT; @@ -637,11 +640,12 @@ enum BuiltinOperator { BuiltinOperator_NON_MAX_SUPPRESSION_V4 = 120, BuiltinOperator_NON_MAX_SUPPRESSION_V5 = 121, BuiltinOperator_SCATTER_ND = 122, + BuiltinOperator_SELECT_V2 = 123, BuiltinOperator_MIN = BuiltinOperator_ADD, - BuiltinOperator_MAX = BuiltinOperator_SCATTER_ND + BuiltinOperator_MAX = BuiltinOperator_SELECT_V2 }; -inline const BuiltinOperator (&EnumValuesBuiltinOperator())[123] { +inline const BuiltinOperator (&EnumValuesBuiltinOperator())[124] { static const BuiltinOperator values[] = { BuiltinOperator_ADD, BuiltinOperator_AVERAGE_POOL_2D, @@ -765,7 +769,8 @@ inline const BuiltinOperator (&EnumValuesBuiltinOperator())[123] { BuiltinOperator_WHILE, BuiltinOperator_NON_MAX_SUPPRESSION_V4, BuiltinOperator_NON_MAX_SUPPRESSION_V5, - BuiltinOperator_SCATTER_ND + BuiltinOperator_SCATTER_ND, + BuiltinOperator_SELECT_V2 }; return values; } @@ -895,13 +900,14 @@ inline const char * const *EnumNamesBuiltinOperator() { "NON_MAX_SUPPRESSION_V4", "NON_MAX_SUPPRESSION_V5", "SCATTER_ND", + "SELECT_V2", nullptr }; return names; } inline const char *EnumNameBuiltinOperator(BuiltinOperator e) { - if (e < BuiltinOperator_ADD || e > BuiltinOperator_SCATTER_ND) return ""; + if (e < BuiltinOperator_ADD || e > BuiltinOperator_SELECT_V2) return ""; const size_t index = static_cast(e); return EnumNamesBuiltinOperator()[index]; } @@ -1005,11 +1011,12 @@ enum BuiltinOptions { BuiltinOptions_NonMaxSuppressionV4Options = 95, BuiltinOptions_NonMaxSuppressionV5Options = 96, BuiltinOptions_ScatterNdOptions = 97, + BuiltinOptions_SelectV2Options = 98, BuiltinOptions_MIN = BuiltinOptions_NONE, - BuiltinOptions_MAX = BuiltinOptions_ScatterNdOptions + BuiltinOptions_MAX = BuiltinOptions_SelectV2Options }; -inline const BuiltinOptions (&EnumValuesBuiltinOptions())[98] { +inline const BuiltinOptions (&EnumValuesBuiltinOptions())[99] { static const BuiltinOptions values[] = { BuiltinOptions_NONE, BuiltinOptions_Conv2DOptions, @@ -1108,7 +1115,8 @@ inline const BuiltinOptions (&EnumValuesBuiltinOptions())[98] { BuiltinOptions_DepthToSpaceOptions, BuiltinOptions_NonMaxSuppressionV4Options, BuiltinOptions_NonMaxSuppressionV5Options, - BuiltinOptions_ScatterNdOptions + BuiltinOptions_ScatterNdOptions, + BuiltinOptions_SelectV2Options }; return values; } @@ -1213,13 +1221,14 @@ inline const char * const *EnumNamesBuiltinOptions() { "NonMaxSuppressionV4Options", "NonMaxSuppressionV5Options", "ScatterNdOptions", + "SelectV2Options", nullptr }; return names; } inline const char *EnumNameBuiltinOptions(BuiltinOptions e) { - if (e < BuiltinOptions_NONE || e > BuiltinOptions_ScatterNdOptions) return ""; + if (e < BuiltinOptions_NONE || e > BuiltinOptions_SelectV2Options) return ""; const size_t index = static_cast(e); return EnumNamesBuiltinOptions()[index]; } @@ -1616,6 +1625,10 @@ template<> struct BuiltinOptionsTraits { static const BuiltinOptions enum_value = BuiltinOptions_ScatterNdOptions; }; +template<> struct BuiltinOptionsTraits { + static const BuiltinOptions enum_value = BuiltinOptions_SelectV2Options; +}; + struct BuiltinOptionsUnion { BuiltinOptions type; void *value; @@ -2424,6 +2437,14 @@ struct BuiltinOptionsUnion { return type == BuiltinOptions_ScatterNdOptions ? reinterpret_cast(value) : nullptr; } + SelectV2OptionsT *AsSelectV2Options() { + return type == BuiltinOptions_SelectV2Options ? + reinterpret_cast(value) : nullptr; + } + const SelectV2OptionsT *AsSelectV2Options() const { + return type == BuiltinOptions_SelectV2Options ? + reinterpret_cast(value) : nullptr; + } }; bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *obj, BuiltinOptions type); @@ -8537,6 +8558,46 @@ inline flatbuffers::Offset CreateScatterNdOptions( flatbuffers::Offset CreateScatterNdOptions(flatbuffers::FlatBufferBuilder &_fbb, const ScatterNdOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +struct SelectV2OptionsT : public flatbuffers::NativeTable { + typedef SelectV2Options TableType; + SelectV2OptionsT() { + } +}; + +struct SelectV2Options FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef SelectV2OptionsT NativeTableType; + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + verifier.EndTable(); + } + SelectV2OptionsT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const; + void UnPackTo(SelectV2OptionsT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const; + static flatbuffers::Offset Pack(flatbuffers::FlatBufferBuilder &_fbb, const SelectV2OptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); +}; + +struct SelectV2OptionsBuilder { + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + explicit SelectV2OptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + SelectV2OptionsBuilder &operator=(const SelectV2OptionsBuilder &); + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateSelectV2Options( + flatbuffers::FlatBufferBuilder &_fbb) { + SelectV2OptionsBuilder builder_(_fbb); + return builder_.Finish(); +} + +flatbuffers::Offset CreateSelectV2Options(flatbuffers::FlatBufferBuilder &_fbb, const SelectV2OptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr); + struct OperatorCodeT : public flatbuffers::NativeTable { typedef OperatorCode TableType; BuiltinOperator builtin_code; @@ -8964,6 +9025,9 @@ struct Operator FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const ScatterNdOptions *builtin_options_as_ScatterNdOptions() const { return builtin_options_type() == BuiltinOptions_ScatterNdOptions ? static_cast(builtin_options()) : nullptr; } + const SelectV2Options *builtin_options_as_SelectV2Options() const { + return builtin_options_type() == BuiltinOptions_SelectV2Options ? static_cast(builtin_options()) : nullptr; + } const flatbuffers::Vector *custom_options() const { return GetPointer *>(VT_CUSTOM_OPTIONS); } @@ -9388,6 +9452,10 @@ template<> inline const ScatterNdOptions *Operator::builtin_options_as inline const SelectV2Options *Operator::builtin_options_as() const { + return builtin_options_as_SelectV2Options(); +} + struct OperatorBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; @@ -12636,6 +12704,29 @@ inline flatbuffers::Offset CreateScatterNdOptions(flatbuffers: _fbb); } +inline SelectV2OptionsT *SelectV2Options::UnPack(const flatbuffers::resolver_function_t *_resolver) const { + auto _o = new SelectV2OptionsT(); + UnPackTo(_o, _resolver); + return _o; +} + +inline void SelectV2Options::UnPackTo(SelectV2OptionsT *_o, const flatbuffers::resolver_function_t *_resolver) const { + (void)_o; + (void)_resolver; +} + +inline flatbuffers::Offset SelectV2Options::Pack(flatbuffers::FlatBufferBuilder &_fbb, const SelectV2OptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher) { + return CreateSelectV2Options(_fbb, _o, _rehasher); +} + +inline flatbuffers::Offset CreateSelectV2Options(flatbuffers::FlatBufferBuilder &_fbb, const SelectV2OptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher) { + (void)_rehasher; + (void)_o; + struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const SelectV2OptionsT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va; + return tflite::CreateSelectV2Options( + _fbb); +} + inline OperatorCodeT *OperatorCode::UnPack(const flatbuffers::resolver_function_t *_resolver) const { auto _o = new OperatorCodeT(); UnPackTo(_o, _resolver); @@ -13317,6 +13408,10 @@ inline bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *ob auto ptr = reinterpret_cast(obj); return verifier.VerifyTable(ptr); } + case BuiltinOptions_SelectV2Options: { + auto ptr = reinterpret_cast(obj); + return verifier.VerifyTable(ptr); + } default: return true; } } @@ -13723,6 +13818,10 @@ inline void *BuiltinOptionsUnion::UnPack(const void *obj, BuiltinOptions type, c auto ptr = reinterpret_cast(obj); return ptr->UnPack(resolver); } + case BuiltinOptions_SelectV2Options: { + auto ptr = reinterpret_cast(obj); + return ptr->UnPack(resolver); + } default: return nullptr; } } @@ -14117,6 +14216,10 @@ inline flatbuffers::Offset BuiltinOptionsUnion::Pack(flatbuffers::FlatBuff auto ptr = reinterpret_cast(value); return CreateScatterNdOptions(_fbb, ptr, _rehasher).Union(); } + case BuiltinOptions_SelectV2Options: { + auto ptr = reinterpret_cast(value); + return CreateSelectV2Options(_fbb, ptr, _rehasher).Union(); + } default: return 0; } } @@ -14511,6 +14614,10 @@ inline BuiltinOptionsUnion::BuiltinOptionsUnion(const BuiltinOptionsUnion &u) FL value = new ScatterNdOptionsT(*reinterpret_cast(u.value)); break; } + case BuiltinOptions_SelectV2Options: { + value = new SelectV2OptionsT(*reinterpret_cast(u.value)); + break; + } default: break; } @@ -15003,6 +15110,11 @@ inline void BuiltinOptionsUnion::Reset() { delete ptr; break; } + case BuiltinOptions_SelectV2Options: { + auto ptr = reinterpret_cast(value); + delete ptr; + break; + } default: break; } value = nullptr; diff --git a/tensorflow/lite/testing/BUILD b/tensorflow/lite/testing/BUILD index c10c015c0cb..a42228313f9 100644 --- a/tensorflow/lite/testing/BUILD +++ b/tensorflow/lite/testing/BUILD @@ -131,7 +131,7 @@ py_library( py_binary( name = "generate_examples", srcs = ["generate_examples.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":generate_examples_lib", @@ -219,16 +219,18 @@ cc_library( ":join", ":split", ":test_runner", + "@com_google_absl//absl/strings", "//tensorflow/lite:builtin_op_data", "//tensorflow/lite:framework", "//tensorflow/lite:string_util", - "//tensorflow/lite/delegates/flex:delegate", "//tensorflow/lite/kernels:builtin_ops", "//tensorflow/lite/kernels:custom_ops", "//tensorflow/lite/kernels:reference_ops", "//tensorflow/lite/tools/evaluation:utils", - "@com_google_absl//absl/strings", - ], + ] + select({ + "//tensorflow:ios": [], + "//conditions:default": ["//tensorflow/lite/delegates/flex:delegate"], + }), ) tf_cc_test( @@ -355,6 +357,7 @@ cc_library( ":join", ":split", ":tf_driver", + ":tflite_driver", "//tensorflow/lite:string", ] + select({ "//conditions:default": [ @@ -403,6 +406,9 @@ cc_library( "//tensorflow:android": [ "//tensorflow/core:android_tensorflow_lib", ], + "//tensorflow:ios": [ + "//tensorflow/core:ios_tensorflow_lib", + ], }), ) @@ -435,6 +441,9 @@ cc_library( "//tensorflow:android": [ "//tensorflow/core:android_tensorflow_lib", ], + "//tensorflow:ios": [ + "//tensorflow/core:ios_tensorflow_lib", + ], }), ) diff --git a/tensorflow/lite/testing/generate_testspec.cc b/tensorflow/lite/testing/generate_testspec.cc index 99021c9f317..e7435e19f49 100644 --- a/tensorflow/lite/testing/generate_testspec.cc +++ b/tensorflow/lite/testing/generate_testspec.cc @@ -22,6 +22,7 @@ limitations under the License. #include "tensorflow/lite/testing/join.h" #include "tensorflow/lite/testing/split.h" #include "tensorflow/lite/testing/tf_driver.h" +#include "tensorflow/lite/testing/tflite_driver.h" namespace tflite { namespace testing { @@ -83,6 +84,68 @@ std::vector GenerateInputValues( return input_values; } +bool GenerateTestSpecFromRunner(std::iostream& stream, int num_invocations, + const std::vector& input_layer, + const std::vector& input_layer_type, + const std::vector& input_layer_shape, + const std::vector& output_layer, + TestRunner* runner) { + stream << "reshape {\n"; + for (const auto& shape : input_layer_shape) { + stream << " input: \"" << shape << "\"\n"; + } + stream << "}\n"; + + // Generate inputs. + std::mt19937 random_engine; + for (int i = 0; i < num_invocations; ++i) { + // Note that the input values are random, so each invocation will have a + // different set. + std::vector input_values = GenerateInputValues( + &random_engine, input_layer, input_layer_type, input_layer_shape); + if (input_values.empty()) { + std::cerr << "Unable to generate input values for the TensorFlow model. " + "Make sure the correct values are defined for " + "input_layer, input_layer_type, and input_layer_shape." + << std::endl; + return false; + } + + // Run TensorFlow. + auto inputs = runner->GetInputs(); + for (int j = 0; j < input_values.size(); j++) { + runner->SetInput(inputs[j], input_values[j]); + if (!runner->IsValid()) { + std::cerr << runner->GetErrorMessage() << std::endl; + return false; + } + } + + runner->Invoke(); + if (!runner->IsValid()) { + std::cerr << runner->GetErrorMessage() << std::endl; + return false; + } + + // Write second part of test spec, with inputs and outputs. + stream << "invoke {\n"; + for (const auto& value : input_values) { + stream << " input: \"" << value << "\"\n"; + } + auto outputs = runner->GetOutputs(); + for (int j = 0; j < output_layer.size(); j++) { + stream << " output: \"" << runner->ReadOutput(outputs[j]) << "\"\n"; + if (!runner->IsValid()) { + std::cerr << runner->GetErrorMessage() << std::endl; + return false; + } + } + stream << "}\n"; + } + + return true; +} + } // namespace bool GenerateTestSpecFromTensorflowModel( @@ -108,61 +171,29 @@ bool GenerateTestSpecFromTensorflowModel( std::cerr << runner.GetErrorMessage() << std::endl; return false; } - // Write first part of test spec, defining model and input shapes. stream << "load_model: " << tflite_model_path << "\n"; - stream << "reshape {\n"; - for (const auto& shape : input_layer_shape) { - stream << " input: \"" << shape << "\"\n"; + return GenerateTestSpecFromRunner(stream, num_invocations, input_layer, + input_layer_type, input_layer_shape, + output_layer, &runner); +} + +bool GenerateTestSpecFromTFLiteModel( + std::iostream& stream, const string& tflite_model_path, int num_invocations, + const std::vector& input_layer, + const std::vector& input_layer_type, + const std::vector& input_layer_shape, + const std::vector& output_layer) { + TfLiteDriver runner; + runner.LoadModel(tflite_model_path); + if (!runner.IsValid()) { + std::cerr << runner.GetErrorMessage() << std::endl; + return false; } - stream << "}\n"; - - // Generate inputs. - std::mt19937 random_engine; - for (int i = 0; i < num_invocations; ++i) { - // Note that the input values are random, so each invocation will have a - // different set. - std::vector input_values = GenerateInputValues( - &random_engine, input_layer, input_layer_type, input_layer_shape); - if (input_values.empty()) { - std::cerr << "Unable to generate input values for the TensorFlow model. " - "Make sure the correct values are defined for " - "input_layer, input_layer_type, and input_layer_shape." - << std::endl; - return false; - } - - // Run TensorFlow. - for (int j = 0; j < input_values.size(); j++) { - runner.SetInput(j, input_values[j]); - if (!runner.IsValid()) { - std::cerr << runner.GetErrorMessage() << std::endl; - return false; - } - } - - runner.Invoke(); - if (!runner.IsValid()) { - std::cerr << runner.GetErrorMessage() << std::endl; - return false; - } - - // Write second part of test spec, with inputs and outputs. - stream << "invoke {\n"; - for (const auto& value : input_values) { - stream << " input: \"" << value << "\"\n"; - } - for (int j = 0; j < output_layer.size(); j++) { - stream << " output: \"" << runner.ReadOutput(j) << "\"\n"; - if (!runner.IsValid()) { - std::cerr << runner.GetErrorMessage() << std::endl; - return false; - } - } - stream << "}\n"; - } - - return true; + runner.AllocateTensors(); + return GenerateTestSpecFromRunner(stream, num_invocations, input_layer, + input_layer_type, input_layer_shape, + output_layer, &runner); } } // namespace testing diff --git a/tensorflow/lite/testing/generate_testspec.h b/tensorflow/lite/testing/generate_testspec.h index 58f8065972b..79d0114ce8e 100644 --- a/tensorflow/lite/testing/generate_testspec.h +++ b/tensorflow/lite/testing/generate_testspec.h @@ -46,6 +46,14 @@ bool GenerateTestSpecFromTensorflowModel( const std::vector& input_layer_shape, const std::vector& output_layer); +// Generate test spec by executing TFLite model on random inputs. +bool GenerateTestSpecFromTFLiteModel( + std::iostream& stream, const string& tflite_model_path, int num_invocations, + const std::vector& input_layer, + const std::vector& input_layer_type, + const std::vector& input_layer_shape, + const std::vector& output_layer); + // Generates random values that are filled into the tensor. template std::vector GenerateRandomTensor(const std::vector& shape, diff --git a/tensorflow/lite/testing/model_coverage/BUILD b/tensorflow/lite/testing/model_coverage/BUILD index 39ed70c869f..7c5c221650a 100644 --- a/tensorflow/lite/testing/model_coverage/BUILD +++ b/tensorflow/lite/testing/model_coverage/BUILD @@ -18,6 +18,7 @@ py_library( py_test( name = "model_coverage_lib_test", srcs = ["model_coverage_lib_test.py"], + python_version = "PY3", srcs_version = "PY2AND3", tags = [ "manual", diff --git a/tensorflow/lite/testing/model_coverage/model_coverage_lib.py b/tensorflow/lite/testing/model_coverage/model_coverage_lib.py index 14390c03a44..6d050eb2791 100644 --- a/tensorflow/lite/testing/model_coverage/model_coverage_lib.py +++ b/tensorflow/lite/testing/model_coverage/model_coverage_lib.py @@ -96,6 +96,10 @@ def _convert(converter, **kwargs): converter.optimizations = [_lite.Optimize.DEFAULT] if kwargs.get("quantize_to_float16", False): converter.target_spec.supported_types = [constants.FLOAT16] + # Some cases are broken when we enable the new converter by default. + # Explicitly disabling it for now. + # TODO(b/145763444): Investigate if these are real issues. + converter.experimental_new_converter = False return converter.convert() diff --git a/tensorflow/lite/testing/tflite_diff_util.cc b/tensorflow/lite/testing/tflite_diff_util.cc index 721830adc4d..2e628fd710d 100644 --- a/tensorflow/lite/testing/tflite_diff_util.cc +++ b/tensorflow/lite/testing/tflite_diff_util.cc @@ -12,6 +12,8 @@ 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/lite/testing/tflite_diff_util.h" + #include #include #include @@ -19,11 +21,27 @@ limitations under the License. #include "tensorflow/lite/testing/generate_testspec.h" #include "tensorflow/lite/testing/parse_testdata.h" -#include "tensorflow/lite/testing/tflite_diff_util.h" #include "tensorflow/lite/testing/tflite_driver.h" namespace tflite { namespace testing { +namespace { +bool SingleRunDiffTestWithProvidedRunner(::tflite::testing::DiffOptions options, + int num_invocations, + TestRunner* (*runner_factory)()) { + std::stringstream tflite_stream; + if (!GenerateTestSpecFromTFLiteModel( + tflite_stream, options.tflite_model, num_invocations, + options.input_layer, options.input_layer_type, + options.input_layer_shape, options.output_layer)) { + return false; + } + + std::unique_ptr runner(runner_factory()); + runner->LoadModel(options.tflite_model); + return ParseAndRunTests(&tflite_stream, runner.get()); +} +} // namespace bool RunDiffTest(const DiffOptions& options, int num_invocations) { std::stringstream tflite_stream; @@ -35,7 +53,39 @@ bool RunDiffTest(const DiffOptions& options, int num_invocations) { } TfLiteDriver tflite_driver(options.delegate); tflite_driver.LoadModel(options.tflite_model); - return tflite::testing::ParseAndRunTests(&tflite_stream, &tflite_driver); + return ParseAndRunTests(&tflite_stream, &tflite_driver); +} + +bool RunDiffTestWithProvidedRunner(const tflite::testing::DiffOptions& options, + TestRunner* (*runner_factory)()) { + int failure_count = 0; + for (int i = 0; i < options.num_runs_per_pass; i++) { + if (!SingleRunDiffTestWithProvidedRunner(options, + /*num_invocations=*/1, + runner_factory)) { + ++failure_count; + } + } + int failures_in_first_pass = failure_count; + + if (failure_count == 0) { + // Let's try again with num_invocations > 1 to make sure we can do multiple + // invocations without resetting the interpreter. + for (int i = 0; i < options.num_runs_per_pass; i++) { + if (!SingleRunDiffTestWithProvidedRunner(options, + /*num_invocations=*/2, + runner_factory)) { + ++failure_count; + } + } + } + + fprintf(stderr, "Num errors in single-inference pass: %d\n", + failures_in_first_pass); + fprintf(stderr, "Num errors in multi-inference pass : %d\n", + failure_count - failures_in_first_pass); + + return failure_count == 0; } } // namespace testing diff --git a/tensorflow/lite/testing/tflite_diff_util.h b/tensorflow/lite/testing/tflite_diff_util.h index 362bc64a6bc..3cf4342b810 100644 --- a/tensorflow/lite/testing/tflite_diff_util.h +++ b/tensorflow/lite/testing/tflite_diff_util.h @@ -52,6 +52,14 @@ struct DiffOptions { // Run a single TensorFLow Lite diff test with a given options. bool RunDiffTest(const DiffOptions& options, int num_invocations); +// Runs diff test for custom TestRunner identified by the factory methiodd +// 'runner_factory' against TFLite CPU given 'options' 'runner_factory' should +// return instance of TestRunner, caller will take ownership of the returned +// object. +// Function returns True if test pass, false otherwise. +bool RunDiffTestWithProvidedRunner(const tflite::testing::DiffOptions& options, + TestRunner* (*runner_factory)()); + } // namespace testing } // namespace tflite diff --git a/tensorflow/lite/testing/tflite_driver.cc b/tensorflow/lite/testing/tflite_driver.cc index 3d988eb624a..9aeba87bbea 100644 --- a/tensorflow/lite/testing/tflite_driver.cc +++ b/tensorflow/lite/testing/tflite_driver.cc @@ -18,9 +18,12 @@ limitations under the License. #include #include #include + #include "absl/strings/escaping.h" #include "tensorflow/lite/builtin_op_data.h" +#if !defined(__APPLE__) #include "tensorflow/lite/delegates/flex/delegate.h" +#endif #include "tensorflow/lite/kernels/custom_ops_register.h" #include "tensorflow/lite/kernels/register.h" #include "tensorflow/lite/kernels/register_ref.h" @@ -331,10 +334,12 @@ TfLiteDriver::TfLiteDriver(DelegateType delegate_type, bool reference_kernel) delegate_ = evaluation::CreateGPUDelegate(/*model=*/nullptr); break; case DelegateType::kFlex: +#if !defined(__APPLE__) delegate_ = Interpreter::TfLiteDelegatePtr( FlexDelegate::Create().release(), [](TfLiteDelegate* delegate) { delete static_cast(delegate); }); +#endif break; } } diff --git a/tensorflow/lite/testing/tflite_driver.h b/tensorflow/lite/testing/tflite_driver.h index 258902606a5..bce3e9c4c01 100644 --- a/tensorflow/lite/testing/tflite_driver.h +++ b/tensorflow/lite/testing/tflite_driver.h @@ -18,7 +18,9 @@ limitations under the License. #include #include +#if !defined(__APPLE__) #include "tensorflow/lite/delegates/flex/delegate.h" +#endif #include "tensorflow/lite/interpreter.h" #include "tensorflow/lite/kernels/register.h" #include "tensorflow/lite/kernels/register_ref.h" diff --git a/tensorflow/lite/toco/logging/BUILD b/tensorflow/lite/toco/logging/BUILD index 7cd4253b463..c9a9591c704 100644 --- a/tensorflow/lite/toco/logging/BUILD +++ b/tensorflow/lite/toco/logging/BUILD @@ -88,7 +88,7 @@ py_test( "//tensorflow/lite/toco/logging/testdata:toco_tf_graph.dot", "//tensorflow/lite/toco/logging/testdata:toco_tflite_graph.dot", ], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":gen_html", diff --git a/tensorflow/lite/toco/model.h b/tensorflow/lite/toco/model.h index d8f4b73115c..d225915bf74 100644 --- a/tensorflow/lite/toco/model.h +++ b/tensorflow/lite/toco/model.h @@ -147,6 +147,7 @@ enum class OperatorType : uint8 { // special nodes in the graph to shuffle axes. kReorderAxes, kSelect, + kSelectV2, kSparseToDense, kEqual, kNotEqual, @@ -177,7 +178,9 @@ enum class OperatorType : uint8 { kMatrixDiagV2, kMatrixSetDiagV2, kMatrixDiagV3, - kMatrixSetDiagV3 + kMatrixSetDiagV3, + // Debugging operators. + kNumericVerify }; // Helper to deal with TensorFlow arrays using a different ordering of @@ -589,6 +592,21 @@ struct DequantizeOperator : Operator { DequantizeOperator() : Operator(OperatorType::kDequantize) {} }; +// Numeric verification operator, converting a quantized array of integers with +// quantization parameters specifying how these integers correspond to real +// numbers +// (see QuantizationParams) and verify them with an array of floating-point +// values. + +// Inputs: +// inputs[0]: required: the input quantized activations array +// inputs[1]: required: the input reference activations array +// +// TensorFlow equivalent: Dequantize +struct NumericVerifyOperator : Operator { + NumericVerifyOperator() : Operator(OperatorType::kNumericVerify) {} +}; + // Batch-normalization operator. // // We only support batch-normalization using pre-learned moments, so this is diff --git a/tensorflow/lite/toco/python/BUILD b/tensorflow/lite/toco/python/BUILD index 73b049a1861..b8a00b90a06 100644 --- a/tensorflow/lite/toco/python/BUILD +++ b/tensorflow/lite/toco/python/BUILD @@ -77,7 +77,7 @@ py_library( py_binary( name = "toco_from_protos", srcs = ["toco_from_protos.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ "//tensorflow/python:_pywrap_toco_api", @@ -89,13 +89,14 @@ py_binary( tf_py_test( name = "toco_from_protos_test", srcs = ["toco_from_protos_test.py"], - additional_deps = [ - "//tensorflow:tensorflow_py", - "//tensorflow/lite/toco:model_flags_proto_py", - "//tensorflow/lite/toco:toco_flags_proto_py", - ], + python_version = "PY3", tags = [ "no_oss", "no_pip", ], + deps = [ + "//tensorflow:tensorflow_py", + "//tensorflow/lite/toco:model_flags_proto_py", + "//tensorflow/lite/toco:toco_flags_proto_py", + ], ) diff --git a/tensorflow/lite/toco/python/toco_from_protos.py b/tensorflow/lite/toco/python/toco_from_protos.py index 4da0bb97b9c..e24af2dc115 100644 --- a/tensorflow/lite/toco/python/toco_from_protos.py +++ b/tensorflow/lite/toco/python/toco_from_protos.py @@ -40,7 +40,7 @@ def execute(unused_args): with open(FLAGS.model_input_file, "rb") as input_file: input_str = input_file.read() - debug_info_str = "" + debug_info_str = None if FLAGS.debug_proto_file: with open(FLAGS.debug_proto_file, "rb") as debug_info_file: debug_info_str = debug_info_file.read() diff --git a/tensorflow/lite/toco/tflite/op_version.cc b/tensorflow/lite/toco/tflite/op_version.cc index a7a829e77e3..241048f8797 100644 --- a/tensorflow/lite/toco/tflite/op_version.cc +++ b/tensorflow/lite/toco/tflite/op_version.cc @@ -196,6 +196,7 @@ string GetMinimumRuntimeVersionForModel(const Model& model) { {{OperatorType::kLessEqual, 2}, "1.14.0"}, {{OperatorType::kSelect, 1}, "1.14.0"}, {{OperatorType::kSelect, 2}, "1.14.0"}, + {{OperatorType::kSelectV2, 1}, kPendingReleaseOpVersion}, {{OperatorType::kFloorDiv, 1}, "1.14.0"}, {{OperatorType::kFloorDiv, 2}, "1.14.0"}, {{OperatorType::kFloor, 1}, "1.9.0"}, diff --git a/tensorflow/lite/tools/BUILD b/tensorflow/lite/tools/BUILD index e090477460c..914866b9f16 100644 --- a/tensorflow/lite/tools/BUILD +++ b/tensorflow/lite/tools/BUILD @@ -19,7 +19,7 @@ py_binary( "//tensorflow/python:platform", "@flatbuffers//:flatc", ], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", ) diff --git a/tensorflow/lite/tools/benchmark/BUILD b/tensorflow/lite/tools/benchmark/BUILD index 97d021c6326..0107d877769 100644 --- a/tensorflow/lite/tools/benchmark/BUILD +++ b/tensorflow/lite/tools/benchmark/BUILD @@ -95,6 +95,7 @@ cc_test( "tflite_not_portable_ios", ], deps = [ + ":benchmark_performance_options", ":benchmark_tflite_model_lib", "//tensorflow/lite:framework", "//tensorflow/lite/testing:util", @@ -151,7 +152,7 @@ cc_library( ":benchmark_params", ":benchmark_utils", ":logging", - "//tensorflow/core:stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//tensorflow/lite/c:common", "//tensorflow/lite/nnapi:nnapi_util", "//tensorflow/lite/profiling:time", @@ -185,7 +186,7 @@ cc_library( ":benchmark_params", ":benchmark_utils", ":logging", - "//tensorflow/core:stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//tensorflow/lite:framework", "//tensorflow/lite/c:common", "//tensorflow/lite/profiling:memory_info", diff --git a/tensorflow/lite/tools/benchmark/benchmark_model.h b/tensorflow/lite/tools/benchmark/benchmark_model.h index 77a576bd1f9..74022f3aa39 100644 --- a/tensorflow/lite/tools/benchmark/benchmark_model.h +++ b/tensorflow/lite/tools/benchmark/benchmark_model.h @@ -105,6 +105,14 @@ class BenchmarkListeners : public BenchmarkListener { listeners_.push_back(listener); } + // Remove all listeners after [index] including the one at 'index'. + void RemoveListeners(int index) { + if (index >= NumListeners()) return; + listeners_.resize(index); + } + + int NumListeners() const { return listeners_.size(); } + void OnBenchmarkStart(const BenchmarkParams& params) override { for (auto listener : listeners_) { listener->OnBenchmarkStart(params); @@ -165,6 +173,9 @@ class BenchmarkModel { void AddListener(BenchmarkListener* listener) { listeners_.AddListener(listener); } + // Remove all listeners after [index] including the one at 'index'. + void RemoveListeners(int index) { listeners_.RemoveListeners(index); } + int NumListeners() const { return listeners_.NumListeners(); } BenchmarkParams* mutable_params() { return ¶ms_; } diff --git a/tensorflow/lite/tools/benchmark/benchmark_performance_options.cc b/tensorflow/lite/tools/benchmark/benchmark_performance_options.cc index d1d124aca7f..609789aa151 100644 --- a/tensorflow/lite/tools/benchmark/benchmark_performance_options.cc +++ b/tensorflow/lite/tools/benchmark/benchmark_performance_options.cc @@ -267,6 +267,36 @@ void BenchmarkPerformanceOptions::CreatePerformanceOptions() { #endif } +void BenchmarkPerformanceOptions::Run() { + CreatePerformanceOptions(); + + if (params_.Get("random_shuffle_benchmark_runs")) { + std::random_shuffle(all_run_params_.begin(), all_run_params_.end()); + } + + // We need to clean *internally* created benchmark listeners, like the + // profiling listener etc. in each Run() invoke because such listeners may be + // reset and become invalid in the next Run(). As a result, we record the + // number of externally-added listeners here to prevent they're cleared later. + const int num_external_listners = single_option_run_->NumListeners(); + + // Now perform all runs, each with different performance-affecting parameters. + for (const auto& run_params : all_run_params_) { + // Reset all performance-related options before any runs. + ResetPerformanceOptions(); + single_option_run_params_->Set(run_params); + util::SleepForSeconds(params_.Get("option_benchmark_run_delay")); + + // Clear internally created listeners before each run but keep externally + // created ones. + single_option_run_->RemoveListeners(num_external_listners); + + single_option_run_->Run(); + } + + all_run_stats_->OutputStats(); +} + void BenchmarkPerformanceOptions::Run(int argc, char** argv) { // We first parse flags for single-option runs to get information like // parameters of the input model etc. @@ -280,22 +310,7 @@ void BenchmarkPerformanceOptions::Run(int argc, char** argv) { TFLITE_LOG(WARN) << "WARNING: unrecognized commandline flag: " << argv[i]; } - CreatePerformanceOptions(); - - if (params_.Get("random_shuffle_benchmark_runs")) { - std::random_shuffle(all_run_params_.begin(), all_run_params_.end()); - } - - // Now perform all runs, each with different performance-affecting parameters. - for (const auto& run_params : all_run_params_) { - // Reset all performance-related options before any runs. - ResetPerformanceOptions(); - single_option_run_params_->Set(run_params); - util::SleepForSeconds(params_.Get("option_benchmark_run_delay")); - single_option_run_->Run(); - } - - all_run_stats_->OutputStats(); + Run(); } } // namespace benchmark } // namespace tflite diff --git a/tensorflow/lite/tools/benchmark/benchmark_performance_options.h b/tensorflow/lite/tools/benchmark/benchmark_performance_options.h index df5aa818600..b7ce59d994f 100644 --- a/tensorflow/lite/tools/benchmark/benchmark_performance_options.h +++ b/tensorflow/lite/tools/benchmark/benchmark_performance_options.h @@ -56,6 +56,8 @@ class BenchmarkPerformanceOptions { virtual ~BenchmarkPerformanceOptions() {} + // Just run the benchmark just w/ default parameter values. + void Run(); void Run(int argc, char** argv); protected: diff --git a/tensorflow/lite/tools/benchmark/benchmark_test.cc b/tensorflow/lite/tools/benchmark/benchmark_test.cc index 883a4353b7c..18fa653d036 100644 --- a/tensorflow/lite/tools/benchmark/benchmark_test.cc +++ b/tensorflow/lite/tools/benchmark/benchmark_test.cc @@ -21,6 +21,7 @@ limitations under the License. #include "absl/algorithm/algorithm.h" #include "tensorflow/lite/interpreter.h" #include "tensorflow/lite/testing/util.h" +#include "tensorflow/lite/tools/benchmark/benchmark_performance_options.h" #include "tensorflow/lite/tools/benchmark/benchmark_tflite_model.h" #include "tensorflow/lite/tools/command_line_flags.h" @@ -111,6 +112,24 @@ TEST(BenchmarkTest, DoesntCrashInt8Model) { benchmark.Run(); } +TEST(BenchmarkTest, DoesntCrashMultiPerfOptions) { + ASSERT_THAT(g_fp32_model_path, testing::NotNull()); + + BenchmarkTfLiteModel benchmark(CreateFp32Params()); + BenchmarkPerformanceOptions all_options_benchmark(&benchmark); + all_options_benchmark.Run(); +} + +TEST(BenchmarkTest, DoesntCrashMultiPerfOptionsWithProfiling) { + ASSERT_THAT(g_fp32_model_path, testing::NotNull()); + + BenchmarkParams params = CreateFp32Params(); + params.Set("enable_op_profiling", true); + BenchmarkTfLiteModel benchmark(std::move(params)); + BenchmarkPerformanceOptions all_options_benchmark(&benchmark); + all_options_benchmark.Run(); +} + TEST(BenchmarkTest, DoesntCrashWithExplicitInputFp32Model) { ASSERT_THAT(g_fp32_model_path, testing::NotNull()); diff --git a/tensorflow/lite/tools/benchmark/experimental/c/BUILD b/tensorflow/lite/tools/benchmark/experimental/c/BUILD index 2bd26e8e127..1cb1e2751a6 100644 --- a/tensorflow/lite/tools/benchmark/experimental/c/BUILD +++ b/tensorflow/lite/tools/benchmark/experimental/c/BUILD @@ -29,7 +29,7 @@ cc_library( "benchmark", ], deps = [ - "//tensorflow/core:stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//tensorflow/lite/tools/benchmark:benchmark_tflite_model_lib", ], ) diff --git a/tensorflow/lite/tools/benchmark/experimental/c/c_api_types.h b/tensorflow/lite/tools/benchmark/experimental/c/c_api_types.h index 332b9b68881..7d728ab55b7 100644 --- a/tensorflow/lite/tools/benchmark/experimental/c/c_api_types.h +++ b/tensorflow/lite/tools/benchmark/experimental/c/c_api_types.h @@ -91,9 +91,11 @@ typedef struct { // in bytes. int TfLiteIntArrayGetSizeInBytes(int size); +#ifndef TF_LITE_STATIC_MEMORY // Create a array of a given `size` (uninitialized entries). // This returns a pointer, that you must free using TfLiteIntArrayFree(). TfLiteIntArray* TfLiteIntArrayCreate(int size); +#endif // Check if two intarrays are equal. Returns 1 if they are equal, 0 otherwise. int TfLiteIntArrayEqual(const TfLiteIntArray* a, const TfLiteIntArray* b); @@ -102,12 +104,14 @@ int TfLiteIntArrayEqual(const TfLiteIntArray* a, const TfLiteIntArray* b); int TfLiteIntArrayEqualsArray(const TfLiteIntArray* a, int b_size, const int b_data[]); +#ifndef TF_LITE_STATIC_MEMORY // Create a copy of an array passed as `src`. // You are expected to free memory with TfLiteIntArrayFree TfLiteIntArray* TfLiteIntArrayCopy(const TfLiteIntArray* src); // Free memory of array `a`. void TfLiteIntArrayFree(TfLiteIntArray* a); +#endif // TF_LITE_STATIC_MEMORY // Fixed size list of floats. Used for per-channel quantization. typedef struct { @@ -126,12 +130,14 @@ typedef struct { // in bytes. int TfLiteFloatArrayGetSizeInBytes(int size); +#ifndef TF_LITE_STATIC_MEMORY // Create a array of a given `size` (uninitialized entries). // This returns a pointer, that you must free using TfLiteFloatArrayFree(). TfLiteFloatArray* TfLiteFloatArrayCreate(int size); // Free memory of array `a`. void TfLiteFloatArrayFree(TfLiteFloatArray* a); +#endif // TF_LITE_STATIC_MEMORY // Since we must not depend on any libraries, define a minimal subset of // error macros while avoiding names that have pre-conceived meanings like @@ -387,6 +393,7 @@ typedef struct { TfLiteSparsity* sparsity; } TfLiteTensor; +#ifndef TF_LITE_STATIC_MEMORY // Free data memory of tensor `t`. void TfLiteTensorDataFree(TfLiteTensor* t); @@ -409,6 +416,7 @@ void TfLiteTensorReset(TfLiteType type, const char* name, TfLiteIntArray* dims, // Resize the allocated data of a (dynamic) tensor. Tensors with allocation // types other than kTfLiteDynamic will be ignored. void TfLiteTensorRealloc(size_t num_bytes, TfLiteTensor* tensor); +#endif // TF_LITE_STATIC_MEMORY // A structure representing an instance of a node. // This structure only exhibits the inputs, outputs and user defined data, not diff --git a/tensorflow/lite/tools/evaluation/stages/BUILD b/tensorflow/lite/tools/evaluation/stages/BUILD index 497b880926a..d0bf3a1429d 100644 --- a/tensorflow/lite/tools/evaluation/stages/BUILD +++ b/tensorflow/lite/tools/evaluation/stages/BUILD @@ -40,7 +40,7 @@ cc_library( deps = [ "@com_google_absl//absl/base", "//tensorflow/core:tflite_portable_logging", - "//tensorflow/core:stats_calculator_portable", + "//tensorflow/core/util:stats_calculator_portable", "//tensorflow/lite/profiling:time", "//tensorflow/lite/tools/evaluation:evaluation_stage", "//tensorflow/lite/kernels/internal:reference_base", @@ -103,8 +103,8 @@ cc_library( hdrs = ["tflite_inference_stage.h"], copts = tflite_copts(), deps = [ - "//tensorflow/core:stats_calculator_portable", "//tensorflow/core:tflite_portable_logging", + "//tensorflow/core/util:stats_calculator_portable", "//tensorflow/lite:framework", "//tensorflow/lite/c:common", "//tensorflow/lite/kernels:builtin_ops", @@ -158,8 +158,8 @@ cc_library( copts = tflite_copts(), deps = [ ":tflite_inference_stage", - "//tensorflow/core:stats_calculator_portable", "//tensorflow/core:tflite_portable_logging", + "//tensorflow/core/util:stats_calculator_portable", "//tensorflow/lite/tools/evaluation:evaluation_stage", "//tensorflow/lite/tools/evaluation/proto:evaluation_config_cc_proto", "//tensorflow/lite/tools/evaluation/proto:evaluation_stages_cc_proto", diff --git a/tensorflow/lite/tools/evaluation/tasks/coco_object_detection/BUILD b/tensorflow/lite/tools/evaluation/tasks/coco_object_detection/BUILD index 84656b78d49..d605b744690 100644 --- a/tensorflow/lite/tools/evaluation/tasks/coco_object_detection/BUILD +++ b/tensorflow/lite/tools/evaluation/tasks/coco_object_detection/BUILD @@ -10,7 +10,7 @@ load("//tensorflow/lite:build_def.bzl", "tflite_copts", "tflite_linkopts") py_binary( name = "preprocess_coco_minival", srcs = ["preprocess_coco_minival.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = ["//tensorflow/lite/tools/evaluation/proto:evaluation_stages_py"], diff --git a/tensorflow/lite/tools/optimize/BUILD b/tensorflow/lite/tools/optimize/BUILD index 5ff5b7884c1..2d249493aa4 100644 --- a/tensorflow/lite/tools/optimize/BUILD +++ b/tensorflow/lite/tools/optimize/BUILD @@ -237,10 +237,13 @@ tf_cc_test( "//tensorflow/lite/tools/optimize:testdata/lstm_calibrated2.bin", "//tensorflow/lite/tools/optimize:testdata/lstm_quantized.bin", "//tensorflow/lite/tools/optimize:testdata/lstm_quantized2.bin", + "//tensorflow/lite/tools/optimize:testdata/maximum.bin", + "//tensorflow/lite/tools/optimize:testdata/minimum.bin", "//tensorflow/lite/tools/optimize:testdata/mixed.bin", "//tensorflow/lite/tools/optimize:testdata/multi_input_add_reshape.bin", "//tensorflow/lite/tools/optimize:testdata/pack.bin", "//tensorflow/lite/tools/optimize:testdata/single_avg_pool_min_minus_5_max_plus_5.bin", + "//tensorflow/lite/tools/optimize:testdata/single_conv_no_bias.bin", "//tensorflow/lite/tools/optimize:testdata/single_conv_weights_min_0_max_plus_10.bin", "//tensorflow/lite/tools/optimize:testdata/single_conv_weights_min_minus_127_max_plus_127.bin", "//tensorflow/lite/tools/optimize:testdata/single_softmax_min_minus_5_max_plus_5.bin", diff --git a/tensorflow/lite/tools/optimize/calibration/builtin_logging_ops/lstm.cc b/tensorflow/lite/tools/optimize/calibration/builtin_logging_ops/lstm.cc index ab592684d23..cc35b14d7db 100644 --- a/tensorflow/lite/tools/optimize/calibration/builtin_logging_ops/lstm.cc +++ b/tensorflow/lite/tools/optimize/calibration/builtin_logging_ops/lstm.cc @@ -36,10 +36,10 @@ namespace builtin { namespace { inline void LstmStepWithAuxInput( - const float* input_ptr_batch, const float* input_to_input_weights_ptr, + const float* input_ptr, const float* input_to_input_weights_ptr, const float* input_to_forget_weights_ptr, const float* input_to_cell_weights_ptr, - const float* input_to_output_weights_ptr, const float* aux_input_ptr_batch, + const float* input_to_output_weights_ptr, const float* aux_input_ptr, const float* aux_input_to_input_weights_ptr, const float* aux_input_to_forget_weights_ptr, const float* aux_input_to_cell_weights_ptr, @@ -62,7 +62,7 @@ inline void LstmStepWithAuxInput( int n_aux_input, int n_output, int output_batch_leading_dim, float* output_state_ptr, float* cell_state_ptr, float* input_gate_scratch, float* forget_gate_scratch, float* cell_scratch, float* output_gate_scratch, - float* output_ptr_batch, Logger* logger, + float* output_ptr, Logger* logger, std::vector intemediate_tensor_indexes) { // Since we have already checked that weights are all there or none, we can // check the existence of only one to the get the condition. @@ -96,38 +96,38 @@ inline void LstmStepWithAuxInput( // For each batch and cell: compute input_weight * input. if (!use_cifg) { tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_input_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_input_weights_ptr, n_cell, n_input, input_ptr, n_batch, input_gate_scratch, /*result_stride=*/1); } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_forget_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_forget_weights_ptr, n_cell, n_input, input_ptr, n_batch, forget_gate_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_cell_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_cell_weights_ptr, n_cell, n_input, input_ptr, n_batch, cell_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - input_to_output_weights_ptr, n_cell, n_input, input_ptr_batch, n_batch, + input_to_output_weights_ptr, n_cell, n_input, input_ptr, n_batch, output_gate_scratch, /*result_stride=*/1); // If auxiliary input is available then compute aux_input_weight * aux_input - if (aux_input_ptr_batch != nullptr) { + if (aux_input_ptr != nullptr) { if (!use_cifg) { tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_input_weights_ptr, n_cell, n_aux_input, - aux_input_ptr_batch, n_batch, input_gate_scratch, + aux_input_to_input_weights_ptr, n_cell, n_aux_input, aux_input_ptr, + n_batch, input_gate_scratch, /*result_stride=*/1); } tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_forget_weights_ptr, n_cell, n_aux_input, - aux_input_ptr_batch, n_batch, forget_gate_scratch, /*result_stride=*/1); + aux_input_to_forget_weights_ptr, n_cell, n_aux_input, aux_input_ptr, + n_batch, forget_gate_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_cell_weights_ptr, n_cell, n_aux_input, aux_input_ptr_batch, + aux_input_to_cell_weights_ptr, n_cell, n_aux_input, aux_input_ptr, n_batch, cell_scratch, /*result_stride=*/1); tensor_utils::MatrixBatchVectorMultiplyAccumulate( - aux_input_to_output_weights_ptr, n_cell, n_aux_input, - aux_input_ptr_batch, n_batch, output_gate_scratch, /*result_stride=*/1); + aux_input_to_output_weights_ptr, n_cell, n_aux_input, aux_input_ptr, + n_batch, output_gate_scratch, /*result_stride=*/1); } // For each batch and cell: compute recurrent_weight * output_state. @@ -252,62 +252,39 @@ inline void LstmStepWithAuxInput( // For each batch: update the projection and output_state. Note that since // the output batch rows may not be contiguous (output_batch_leading_dim != - // n_output), we unroll the batched operations where this is the case. - if (output_batch_leading_dim == n_output) { - if (use_projection_weight) { - if (use_projection_bias) { - tensor_utils::VectorBatchVectorAssign(projection_bias_ptr, n_output, - n_batch, output_ptr_batch); - } else { - std::fill_n(output_ptr_batch, n_batch * n_output, 0.0f); - } - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - projection_weights_ptr, n_output, n_cell, output_gate_scratch, - n_batch, output_ptr_batch, /*result_stride=*/1); - if (params->proj_clip > 0.0) { - tensor_utils::ClipVector(output_ptr_batch, n_batch * n_output, - params->proj_clip, output_ptr_batch); - } - } else { - std::copy_n(output_gate_scratch, n_batch * n_output, output_ptr_batch); - } - std::copy_n(output_ptr_batch, n_batch * n_output, output_state_ptr); - } else { - if (use_projection_weight) { - if (use_projection_bias) { - for (int k = 0; k < n_batch; k++) { - std::copy_n(projection_bias_ptr, n_output, - output_ptr_batch + k * output_batch_leading_dim); - } - } else { - for (int k = 0; k < n_batch; k++) { - std::fill_n(output_ptr_batch + k * output_batch_leading_dim, n_output, - 0.0f); - } - } + // n_output), we unroll batched operations. + if (use_projection_weight) { + if (use_projection_bias) { for (int k = 0; k < n_batch; k++) { - tensor_utils::MatrixBatchVectorMultiplyAccumulate( - projection_weights_ptr, n_output, n_cell, - output_gate_scratch + k * n_cell, - /*n_batch=*/1, output_ptr_batch + k * output_batch_leading_dim, - /*result_stride=*/1); - if (params->proj_clip > 0.0) { - tensor_utils::ClipVector( - output_ptr_batch + k * output_batch_leading_dim, n_output, - params->proj_clip, - output_ptr_batch + k * output_batch_leading_dim); - } + std::copy_n(projection_bias_ptr, n_output, + output_ptr + k * output_batch_leading_dim); } } else { for (int k = 0; k < n_batch; k++) { - std::copy_n(output_gate_scratch + k * n_output, n_output, - output_ptr_batch + k * output_batch_leading_dim); + std::fill_n(output_ptr + k * output_batch_leading_dim, n_output, 0.0f); } } for (int k = 0; k < n_batch; k++) { - std::copy_n(output_ptr_batch + k * output_batch_leading_dim, n_output, - output_state_ptr + k * n_output); + tensor_utils::MatrixBatchVectorMultiplyAccumulate( + projection_weights_ptr, n_output, n_cell, + output_gate_scratch + k * n_cell, + /*n_batch=*/1, output_ptr + k * output_batch_leading_dim, + /*result_stride=*/1); + if (params->proj_clip > 0.0) { + tensor_utils::ClipVector(output_ptr + k * output_batch_leading_dim, + n_output, params->proj_clip, + output_ptr + k * output_batch_leading_dim); + } } + } else { + for (int k = 0; k < n_batch; k++) { + std::copy_n(output_gate_scratch + k * n_output, n_output, + output_ptr + k * output_batch_leading_dim); + } + } + for (int k = 0; k < n_batch; k++) { + std::copy_n(output_ptr + k * output_batch_leading_dim, n_output, + output_state_ptr + k * n_output); } } @@ -360,64 +337,22 @@ TfLiteStatus EvalFloat( // Since we have already checked that weights are all there or none, we can // check the existence of only one to the get the condition. const bool use_cifg = (input_to_input_weights == nullptr); - const bool use_peephole = (cell_to_output_weights != nullptr); - const bool is_layer_norm_lstm = (forget_layer_norm_coefficients != nullptr); // Index the scratch buffers pointers to the global scratch buffer. + float* scratch_buffer_ptr = GetTensorData(scratch_buffer); float* input_gate_scratch = nullptr; float* cell_scratch = nullptr; float* forget_gate_scratch = nullptr; float* output_gate_scratch = nullptr; if (use_cifg) { - cell_scratch = scratch_buffer->data.f; - forget_gate_scratch = scratch_buffer->data.f + n_cell * n_batch; - output_gate_scratch = scratch_buffer->data.f + 2 * n_cell * n_batch; + cell_scratch = scratch_buffer_ptr; + forget_gate_scratch = scratch_buffer_ptr + n_cell * n_batch; + output_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch; } else { - input_gate_scratch = scratch_buffer->data.f; - cell_scratch = scratch_buffer->data.f + n_cell * n_batch; - forget_gate_scratch = scratch_buffer->data.f + 2 * n_cell * n_batch; - output_gate_scratch = scratch_buffer->data.f + 3 * n_cell * n_batch; - } - - // Check optional tensors, the respective pointers can be null. - const float* input_to_input_weights_ptr = - (use_cifg) ? nullptr : input_to_input_weights->data.f; - const float* recurrent_to_input_weights_ptr = - (use_cifg) ? nullptr : recurrent_to_input_weights->data.f; - const float* input_gate_bias_ptr = - (use_cifg) ? nullptr : input_gate_bias->data.f; - const float* cell_to_input_weights_ptr = - (use_peephole && !use_cifg) ? cell_to_input_weights->data.f : nullptr; - const float* cell_to_forget_weights_ptr = - (use_peephole) ? cell_to_forget_weights->data.f : nullptr; - const float* cell_to_output_weights_ptr = - (use_peephole) ? cell_to_output_weights->data.f : nullptr; - const float* input_layer_norm_coefficients_ptr = - (is_layer_norm_lstm && !use_cifg) ? input_layer_norm_coefficients->data.f - : nullptr; - const float* forget_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? forget_layer_norm_coefficients->data.f : nullptr; - const float* cell_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? cell_layer_norm_coefficients->data.f : nullptr; - const float* output_layer_norm_coefficients_ptr = - is_layer_norm_lstm ? output_layer_norm_coefficients->data.f : nullptr; - const float* projection_weights_ptr = - (projection_weights == nullptr) ? nullptr : projection_weights->data.f; - const float* projection_bias_ptr = - (projection_bias == nullptr) ? nullptr : projection_bias->data.f; - - float* aux_input_ptr = nullptr; - float* aux_input_to_input_weights_ptr = nullptr; - float* aux_input_to_forget_weights_ptr = nullptr; - float* aux_input_to_cell_weights_ptr = nullptr; - float* aux_input_to_output_weights_ptr = nullptr; - if (aux_input_size > 0) { - if (!use_cifg) { - aux_input_to_input_weights_ptr = aux_input_to_input_weights->data.f; - } - aux_input_to_forget_weights_ptr = aux_input_to_forget_weights->data.f; - aux_input_to_cell_weights_ptr = aux_input_to_cell_weights->data.f; - aux_input_to_output_weights_ptr = aux_input_to_output_weights->data.f; + input_gate_scratch = scratch_buffer_ptr; + cell_scratch = scratch_buffer_ptr + n_cell * n_batch; + forget_gate_scratch = scratch_buffer_ptr + 2 * n_cell * n_batch; + output_gate_scratch = scratch_buffer_ptr + 3 * n_cell * n_batch; } const int output_batch_leading_dim = @@ -430,32 +365,45 @@ TfLiteStatus EvalFloat( // If this is the forward_sequence, step forward, otherwise step // backwards. const int t_rel = forward_sequence ? t : max_time - t - 1; - const float* input_ptr_batch = input->data.f + t_rel * input_step; + const float* input_ptr = GetTensorData(input) + t_rel * input_step; + const float* aux_input_ptr = nullptr; if (aux_input) { - aux_input_ptr = aux_input->data.f + t_rel * input_step; + aux_input_ptr = GetTensorData(aux_input) + t_rel * input_step; } float* output_ptr_time = - output->data.f + t_rel * output_step + output_offset; + GetTensorData(output) + t_rel * output_step + output_offset; LstmStepWithAuxInput( - input_ptr_batch, input_to_input_weights_ptr, - input_to_forget_weights->data.f, input_to_cell_weights->data.f, - input_to_output_weights->data.f, aux_input_ptr, - aux_input_to_input_weights_ptr, aux_input_to_forget_weights_ptr, - aux_input_to_cell_weights_ptr, aux_input_to_output_weights_ptr, - recurrent_to_input_weights_ptr, recurrent_to_forget_weights->data.f, - recurrent_to_cell_weights->data.f, - recurrent_to_output_weights->data.f, cell_to_input_weights_ptr, - cell_to_forget_weights_ptr, cell_to_output_weights_ptr, - input_layer_norm_coefficients_ptr, forget_layer_norm_coefficients_ptr, - cell_layer_norm_coefficients_ptr, output_layer_norm_coefficients_ptr, - input_gate_bias_ptr, forget_gate_bias->data.f, cell_bias->data.f, - output_gate_bias->data.f, projection_weights_ptr, projection_bias_ptr, - params, n_batch, n_cell, n_input, aux_input_size, n_output, - output_batch_leading_dim, activation_state->data.f, - cell_state->data.f, input_gate_scratch, forget_gate_scratch, - cell_scratch, output_gate_scratch, output_ptr_time, logger, - intemediate_tensor_indexes); + input_ptr, GetTensorData(input_to_input_weights), + GetTensorData(input_to_forget_weights), + GetTensorData(input_to_cell_weights), + GetTensorData(input_to_output_weights), aux_input_ptr, + GetTensorData(aux_input_to_input_weights), + GetTensorData(aux_input_to_forget_weights), + GetTensorData(aux_input_to_cell_weights), + GetTensorData(aux_input_to_output_weights), + GetTensorData(recurrent_to_input_weights), + GetTensorData(recurrent_to_forget_weights), + GetTensorData(recurrent_to_cell_weights), + GetTensorData(recurrent_to_output_weights), + GetTensorData(cell_to_input_weights), + GetTensorData(cell_to_forget_weights), + GetTensorData(cell_to_output_weights), + GetTensorData(input_layer_norm_coefficients), + GetTensorData(forget_layer_norm_coefficients), + GetTensorData(cell_layer_norm_coefficients), + GetTensorData(output_layer_norm_coefficients), + GetTensorData(input_gate_bias), + GetTensorData(forget_gate_bias), + GetTensorData(cell_bias), + GetTensorData(output_gate_bias), + GetTensorData(projection_weights), + GetTensorData(projection_bias), params, n_batch, n_cell, + n_input, aux_input_size, n_output, output_batch_leading_dim, + GetTensorData(activation_state), + GetTensorData(cell_state), input_gate_scratch, + forget_gate_scratch, cell_scratch, output_gate_scratch, + output_ptr_time, logger, intemediate_tensor_indexes); } } else { for (int b = 0; b < n_batch; b++) { @@ -466,17 +414,20 @@ TfLiteStatus EvalFloat( // backwards. const int t_rel = forward_sequence ? t : max_time - t - 1; const int time_offset = b * max_time + t_rel; - const float* input_ptr = input->data.f + time_offset * input_step; + const float* input_ptr = + GetTensorData(input) + time_offset * input_step; + const float* aux_input_ptr = nullptr; if (aux_input) { - aux_input_ptr = aux_input->data.f + time_offset * input_step; + aux_input_ptr = + GetTensorData(aux_input) + time_offset * input_step; } - float* output_ptr = - output->data.f + time_offset * output_step + output_offset; + float* output_ptr = GetTensorData(output) + + time_offset * output_step + output_offset; // Offset the {activation,cell}_state pointers to the right batch. - float* activation_state_ptr = - activation_state->data.f + b * output_batch_leading_dim; - float* cell_state_ptr = cell_state->data.f + b * n_cell; + float* activation_state_ptr = GetTensorData(activation_state) + + b * output_batch_leading_dim; + float* cell_state_ptr = GetTensorData(cell_state) + b * n_cell; // Offset the scratch pointers to the right batch. float* input_gate_scratch_ptr = input_gate_scratch ? input_gate_scratch + b * n_cell : nullptr; @@ -485,23 +436,32 @@ TfLiteStatus EvalFloat( float* output_gate_scratch_ptr = output_gate_scratch + b * n_cell; LstmStepWithAuxInput( - input_ptr, input_to_input_weights_ptr, - input_to_forget_weights->data.f, input_to_cell_weights->data.f, - input_to_output_weights->data.f, aux_input_ptr, - aux_input_to_input_weights_ptr, aux_input_to_forget_weights_ptr, - aux_input_to_cell_weights_ptr, aux_input_to_output_weights_ptr, - recurrent_to_input_weights_ptr, recurrent_to_forget_weights->data.f, - recurrent_to_cell_weights->data.f, - recurrent_to_output_weights->data.f, cell_to_input_weights_ptr, - cell_to_forget_weights_ptr, cell_to_output_weights_ptr, - input_layer_norm_coefficients_ptr, - forget_layer_norm_coefficients_ptr, - cell_layer_norm_coefficients_ptr, - output_layer_norm_coefficients_ptr, input_gate_bias_ptr, - forget_gate_bias->data.f, cell_bias->data.f, - output_gate_bias->data.f, projection_weights_ptr, - projection_bias_ptr, params, /*n_batch=*/1, n_cell, n_input, - aux_input_size, n_output, output_batch_leading_dim, + input_ptr, GetTensorData(input_to_input_weights), + GetTensorData(input_to_forget_weights), + GetTensorData(input_to_cell_weights), + GetTensorData(input_to_output_weights), aux_input_ptr, + GetTensorData(aux_input_to_input_weights), + GetTensorData(aux_input_to_forget_weights), + GetTensorData(aux_input_to_cell_weights), + GetTensorData(aux_input_to_output_weights), + GetTensorData(recurrent_to_input_weights), + GetTensorData(recurrent_to_forget_weights), + GetTensorData(recurrent_to_cell_weights), + GetTensorData(recurrent_to_output_weights), + GetTensorData(cell_to_input_weights), + GetTensorData(cell_to_forget_weights), + GetTensorData(cell_to_output_weights), + GetTensorData(input_layer_norm_coefficients), + GetTensorData(forget_layer_norm_coefficients), + GetTensorData(cell_layer_norm_coefficients), + GetTensorData(output_layer_norm_coefficients), + GetTensorData(input_gate_bias), + GetTensorData(forget_gate_bias), + GetTensorData(cell_bias), + GetTensorData(output_gate_bias), + GetTensorData(projection_weights), + GetTensorData(projection_bias), params, /*n_batch=*/1, + n_cell, n_input, aux_input_size, n_output, output_batch_leading_dim, activation_state_ptr, cell_state_ptr, input_gate_scratch_ptr, forget_gate_scratch_ptr, cell_scratch_ptr, output_gate_scratch_ptr, output_ptr, logger, intemediate_tensor_indexes); @@ -522,8 +482,6 @@ struct OpData { bool is_layer_norm_lstm; // These fields are only used by full kernel. - int activation_state_tensor_index; - int cell_state_tensor_index; int scratch_tensor_index; }; @@ -558,6 +516,11 @@ constexpr int kProjectionWeightsTensor = 16; // Optional // Projection bias tensor of size {n_output} constexpr int kProjectionBiasTensor = 17; // Optional +// These state tensors are defined as variable tensors, and will be modified by +// this op. +constexpr int kInputActivationStateTensor = 18; +constexpr int kInputCellStateTensor = 19; + // Layer norm coefficient tensors of size {n_cell}, representing a diagonal // matrix. constexpr int kInputLayerNormCoefficientsTensor = 20; // Optional @@ -573,9 +536,7 @@ constexpr int kOutputTensor = 0; // tensors match each other. TfLiteStatus lstm_eval(TfLiteContext* context, TfLiteNode* node, Logger* logger) { - const auto* params = reinterpret_cast(node->builtin_data); - OpData* op_data = reinterpret_cast(node->user_data); - const bool is_layer_norm_lstm = op_data->is_layer_norm_lstm; + const auto* params = static_cast(node->builtin_data); const TfLiteTensor* input = GetInput(context, node, kInputTensor); @@ -605,21 +566,13 @@ TfLiteStatus lstm_eval(TfLiteContext* context, TfLiteNode* node, GetOptionalInputTensor(context, node, kCellToOutputWeightsTensor); const TfLiteTensor* input_layer_norm_coefficients = - is_layer_norm_lstm ? GetOptionalInputTensor( - context, node, kInputLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kInputLayerNormCoefficientsTensor); const TfLiteTensor* forget_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kForgetLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kForgetLayerNormCoefficientsTensor); const TfLiteTensor* cell_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kCellLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kCellLayerNormCoefficientsTensor); const TfLiteTensor* output_layer_norm_coefficients = - is_layer_norm_lstm - ? GetInput(context, node, kOutputLayerNormCoefficientsTensor) - : nullptr; + GetOptionalInputTensor(context, node, kOutputLayerNormCoefficientsTensor); const TfLiteTensor* input_gate_bias = GetOptionalInputTensor(context, node, kInputGateBiasTensor); @@ -638,9 +591,11 @@ TfLiteStatus lstm_eval(TfLiteContext* context, TfLiteNode* node, TfLiteTensor* scratch_buffer = GetTemporary(context, node, /*index=*/0); TfLiteTensor* activation_state = - &context->tensors[op_data->activation_state_tensor_index]; + GetVariableInput(context, node, kInputActivationStateTensor); + TF_LITE_ENSURE(context, activation_state != nullptr); TfLiteTensor* cell_state = - &context->tensors[op_data->cell_state_tensor_index]; + GetVariableInput(context, node, kInputCellStateTensor); + TF_LITE_ENSURE(context, cell_state != nullptr); TfLiteTensor* output = GetOutput(context, node, kOutputTensor); diff --git a/tensorflow/lite/tools/optimize/operator_property.cc b/tensorflow/lite/tools/optimize/operator_property.cc index 2eacd62725b..9b94bd3d44f 100644 --- a/tensorflow/lite/tools/optimize/operator_property.cc +++ b/tensorflow/lite/tools/optimize/operator_property.cc @@ -193,7 +193,6 @@ OperatorProperty GetOperatorProperty(const ModelT* model, int subgraph_index, } case BuiltinOperator_LSTM: { // TODO(jianlijianli): extend LSTM op spec to inlucde input, bias etc. - // TODO(jianlijianli): extend this to other variants of LSTM. // LSTM needs 5 intermediate tensors. This agrees with the fully quantized // kernels in lstm_eval.cc if (op_variant.use_layer_norm && op_variant.use_projection && diff --git a/tensorflow/lite/tools/optimize/quantize_model.cc b/tensorflow/lite/tools/optimize/quantize_model.cc index 42db16eb965..26d595947cd 100644 --- a/tensorflow/lite/tools/optimize/quantize_model.cc +++ b/tensorflow/lite/tools/optimize/quantize_model.cc @@ -993,6 +993,9 @@ TfLiteStatus EnsureBiasScaleCompatibility( // Loop over all bias tensors. for (const int bias_idx : property.biases) { + if (op->inputs[bias_idx] == kTfLiteOptionalTensor) { + continue; + } TensorT* bias_tensor = subgraph->tensors[op->inputs[bias_idx]].get(); int32_t channel_dim_size = bias_tensor->shape[0]; if (bias_tensor->shape.size() != 1) { diff --git a/tensorflow/lite/tools/optimize/quantize_model_test.cc b/tensorflow/lite/tools/optimize/quantize_model_test.cc index 85afbee8c42..1d838a822cb 100644 --- a/tensorflow/lite/tools/optimize/quantize_model_test.cc +++ b/tensorflow/lite/tools/optimize/quantize_model_test.cc @@ -316,6 +316,24 @@ TEST_F(QuantizeConvModelTest, Uint8InputAndOutput) { } } +class QuantizeConvNoBiasModelTest : public QuantizeModelTest { + protected: + QuantizeConvNoBiasModelTest() { + input_model_ = ReadModel(internal::kConvModelWithNoBias); + readonly_model_ = input_model_->GetModel(); + readonly_model_->UnPackTo(&model_); + } +}; + +TEST_F(QuantizeConvNoBiasModelTest, QuantizationSucceeds) { + auto status = QuantizeModel(&builder_, &model_, TensorType_INT8, + TensorType_INT8, &error_reporter_); + EXPECT_EQ(status, kTfLiteOk); + const uint8_t* buffer = builder_.GetBufferPointer(); + const Model* output_model = GetModel(buffer); + ASSERT_TRUE(output_model); +} + class QuantizeConcatModelTest : public QuantizeModelTest { protected: QuantizeConcatModelTest() { @@ -1246,6 +1264,90 @@ TEST_F(QuantizePackTest, VerifyPack) { EXPECT_EQ(pack_input2->type, TensorType_INT8); } +class QuantizeMinimumMaximumTest + : public QuantizeModelTest, + public testing::WithParamInterface { + protected: + QuantizeMinimumMaximumTest() { + input_model_ = ReadModel(GetParam()); + readonly_model_ = input_model_->GetModel(); + readonly_model_->UnPackTo(&model_); + } +}; + +TEST_P(QuantizeMinimumMaximumTest, VerifyMinimumMaximum) { + auto status = QuantizeModel(&builder_, &model_, &error_reporter_); + ASSERT_EQ(kTfLiteOk, status); + const auto& subgraph = model_.subgraphs[0]; + + // Check that the first op is Quantize and the last is Dequant. + const auto& quant_op = subgraph->operators[0]; + const auto& dequant_op = subgraph->operators[subgraph->operators.size() - 1]; + const int32_t quant_idx = quant_op->opcode_index; + const int32_t dequant_idx = dequant_op->opcode_index; + EXPECT_EQ(model_.operator_codes[quant_idx]->builtin_code, + BuiltinOperator_QUANTIZE); + EXPECT_EQ(model_.operator_codes[dequant_idx]->builtin_code, + BuiltinOperator_DEQUANTIZE); + const auto& requant1 = subgraph->operators[1].get(); + // Check that we have RE operator. + auto requant1_builtin_code = + model_.operator_codes[requant1->opcode_index].get()->builtin_code; + ASSERT_TRUE(requant1_builtin_code == tflite::BuiltinOperator_QUANTIZE); + + const auto& requant2 = subgraph->operators[2].get(); + // Check that we have RE operator. + auto requant2_builtin_code = + model_.operator_codes[requant2->opcode_index].get()->builtin_code; + ASSERT_TRUE(requant2_builtin_code == tflite::BuiltinOperator_QUANTIZE); + + const auto& op = subgraph->operators[3].get(); + + // Check that we have MINIMUM or MAXIMUM operator. + auto op_builtin_code = + model_.operator_codes[op->opcode_index].get()->builtin_code; + ASSERT_TRUE(op_builtin_code == tflite::BuiltinOperator_MINIMUM || + op_builtin_code == tflite::BuiltinOperator_MAXIMUM); + + // Check that we have two inputs and one output. + ASSERT_EQ(op->inputs.size(), 2); + ASSERT_EQ(op->outputs.size(), 1); + + // Check that all is quantized. + auto output = subgraph->tensors[op->outputs[0]].get(); + auto input1 = subgraph->tensors[op->outputs[0]].get(); + auto input2 = subgraph->tensors[op->outputs[0]].get(); + + EXPECT_EQ(output->type, TensorType_INT8); + EXPECT_EQ(input1->type, TensorType_INT8); + EXPECT_EQ(input2->type, TensorType_INT8); + + // Check if the quantization params of the minimum/maximum inputs match + // after requantization + EXPECT_EQ(input1->quantization->scale, input2->quantization->scale); + EXPECT_EQ(input1->quantization->zero_point, input2->quantization->zero_point); + + // Check the input quantization params match the output ones. + EXPECT_EQ(output->quantization->scale, input1->quantization->scale); + EXPECT_EQ(output->quantization->zero_point, input1->quantization->zero_point); + EXPECT_EQ(output->quantization->scale, input2->quantization->scale); + EXPECT_EQ(output->quantization->zero_point, input2->quantization->zero_point); + + EXPECT_EQ(subgraph->tensors.size(), 7); + + EXPECT_EQ(subgraph->tensors[0]->name, "input_int8"); + EXPECT_EQ(subgraph->tensors[1]->name, "output_int8"); + EXPECT_EQ(subgraph->tensors[2]->name, "output/y"); + EXPECT_EQ(subgraph->tensors[3]->name, "input_requantized"); + EXPECT_EQ(subgraph->tensors[4]->name, "output/y_requantized"); + EXPECT_EQ(subgraph->tensors[5]->name, "input"); + EXPECT_EQ(subgraph->tensors[6]->name, "output"); +} + +INSTANTIATE_TEST_SUITE_P(MinimumMaximumTestInst, QuantizeMinimumMaximumTest, + testing::ValuesIn({internal::kModelWithMinimumOp, + internal::kModelWithMaximumOp})); + class QuantizeUnpackTest : public QuantizeModelTest { protected: QuantizeUnpackTest() { @@ -1254,7 +1356,6 @@ class QuantizeUnpackTest : public QuantizeModelTest { readonly_model_->UnPackTo(&model_); } }; - TEST_F(QuantizeUnpackTest, VerifyUnpack) { auto status = QuantizeModel(&builder_, &model_, &error_reporter_); diff --git a/tensorflow/lite/tools/optimize/test_util.cc b/tensorflow/lite/tools/optimize/test_util.cc index be54ca94c09..917e4a7e958 100644 --- a/tensorflow/lite/tools/optimize/test_util.cc +++ b/tensorflow/lite/tools/optimize/test_util.cc @@ -25,6 +25,8 @@ const char* kConvModelWithMinus128Plus127Weights = const char* kConvModelWith0Plus10Weights = "single_conv_weights_min_0_max_plus_10.bin"; +const char* kConvModelWithNoBias = "single_conv_no_bias.bin"; + const char* kSingleSoftmaxModelMinMinus5MaxPlus5 = "single_softmax_min_minus_5_max_plus_5.bin"; @@ -54,6 +56,8 @@ const char* kModelPack = "pack.bin"; const char* kLstmCalibrated = "lstm_calibrated.bin"; const char* kLstmQuantized = "lstm_quantized.bin"; +const char* kModelWithMinimumOp = "minimum.bin"; +const char* kModelWithMaximumOp = "maximum.bin"; const char* kLstmCalibrated2 = "lstm_calibrated2.bin"; const char* kLstmQuantized2 = "lstm_quantized2.bin"; diff --git a/tensorflow/lite/tools/optimize/test_util.h b/tensorflow/lite/tools/optimize/test_util.h index 8e086afbac9..af38febcf57 100644 --- a/tensorflow/lite/tools/optimize/test_util.h +++ b/tensorflow/lite/tools/optimize/test_util.h @@ -34,6 +34,9 @@ extern const char* kConvModelWithMinus128Plus127Weights; // Activations have min = 0, max = 10. extern const char* kConvModelWith0Plus10Weights; +// Test model where no bias is in the conv. +extern const char* kConvModelWithNoBias; + // A floating point model with a single softmax. The input tensor has min // and max in range [-5, 5], not necessarily -5 or +5. extern const char* kSingleSoftmaxModelMinMinus5MaxPlus5; @@ -84,6 +87,12 @@ extern const char* kModelPack; extern const char* kLstmCalibrated; extern const char* kLstmQuantized; +// Test model with a minimum op. +extern const char* kModelWithMinimumOp; + +// Test model with a maximum op. +extern const char* kModelWithMaximumOp; + // Test model with LSTM op that has peephole, without layer norm, without // projection, without cifg. extern const char* kLstmCalibrated2; diff --git a/tensorflow/lite/tools/optimize/testdata/maximum.bin b/tensorflow/lite/tools/optimize/testdata/maximum.bin new file mode 100644 index 00000000000..8e767ee792b Binary files /dev/null and b/tensorflow/lite/tools/optimize/testdata/maximum.bin differ diff --git a/tensorflow/lite/tools/optimize/testdata/minimum.bin b/tensorflow/lite/tools/optimize/testdata/minimum.bin new file mode 100644 index 00000000000..a67d2492b33 Binary files /dev/null and b/tensorflow/lite/tools/optimize/testdata/minimum.bin differ diff --git a/tensorflow/lite/tools/optimize/testdata/single_conv_no_bias.bin b/tensorflow/lite/tools/optimize/testdata/single_conv_no_bias.bin new file mode 100644 index 00000000000..d4c2d7cdd43 Binary files /dev/null and b/tensorflow/lite/tools/optimize/testdata/single_conv_no_bias.bin differ diff --git a/tensorflow/lite/tutorials/BUILD b/tensorflow/lite/tutorials/BUILD index 657fbcd19ef..277d807b2ca 100644 --- a/tensorflow/lite/tutorials/BUILD +++ b/tensorflow/lite/tutorials/BUILD @@ -13,7 +13,7 @@ py_binary( "dataset.py", "mnist_tflite.py", ], - python_version = "PY2", + python_version = "PY3", deps = [ "//tensorflow:tensorflow_py", ], diff --git a/tensorflow/opensource_only.files b/tensorflow/opensource_only.files index a3cbb1f5511..e4c0e70aeff 100644 --- a/tensorflow/opensource_only.files +++ b/tensorflow/opensource_only.files @@ -6,6 +6,7 @@ tensorflow/compat_template.__init__.py tensorflow/compat_template_v1.__init__.py tensorflow/compiler/mlir/glob_lit_test.bzl tensorflow/core/public/version.h +tensorflow/lite/micro/build_def.bzl tensorflow/python/autograph/core/config.py tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD index a25c3162752..253c69a7347 100644 --- a/tensorflow/python/BUILD +++ b/tensorflow/python/BUILD @@ -10,8 +10,8 @@ load("//tensorflow:tensorflow.bzl", "tf_py_wrap_cc") load("//tensorflow:tensorflow.bzl", "cuda_py_test") load("//tensorflow:tensorflow.bzl", "cuda_py_tests") load("//tensorflow:tensorflow.bzl", "tf_external_workspace_visible") -load("//tensorflow/core/platform:build_config.bzl", "pyx_library", "tf_additional_all_protos", "tf_additional_cupti_test_flags", "tf_additional_lib_deps", "tf_proto_library", "tf_proto_library_py", "tf_protos_grappler") # @unused -load("//tensorflow/core/platform:build_config_root.bzl", "if_static", "tf_additional_plugin_deps") +load("//tensorflow/core/platform:build_config.bzl", "pyx_library", "tf_additional_all_protos", "tf_additional_lib_deps", "tf_proto_library", "tf_proto_library_py", "tf_protos_grappler") # @unused +load("//tensorflow/core/platform:build_config_root.bzl", "if_static", "tf_additional_plugin_deps", "tf_additional_xla_deps_py") load("//tensorflow/python:build_defs.bzl", "tf_gen_op_wrapper_private_py") load( "//third_party/ngraph:build_defs.bzl", @@ -300,47 +300,47 @@ tf_py_test( name = "resource_loader_test", size = "small", srcs = ["platform/resource_loader_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":platform", ":platform_test", ], - python_version = "PY3", ) tf_py_test( name = "flags_test", size = "small", srcs = ["platform/flags_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":platform", ], - python_version = "PY3", ) tf_py_test( name = "stacktrace_handler_test", size = "small", srcs = ["platform/stacktrace_handler_test.py"], - additional_deps = [ - ":client_testlib", - ":platform", - ], python_version = "PY3", tags = [ "no_windows", "nomac", "notap", # TODO(b/137133525): enable after this is fixed. ], + deps = [ + ":client_testlib", + ":platform", + ], ) tf_py_test( name = "app_test", size = "small", srcs = ["platform/app_test.py"], - additional_deps = [":platform"], python_version = "PY3", tags = ["notap"], + deps = [":platform"], ) cc_library( @@ -594,10 +594,10 @@ tf_python_pybind_extension( tf_python_pybind_extension( name = "_pywrap_util_port", srcs = ["util/port_wrapper.cc"], - hdrs = ["//tensorflow/core:util_port_hdrs"], + hdrs = ["//tensorflow/core/util:port_hdrs"], module_name = "_pywrap_util_port", deps = [ - "//tensorflow/core:util_port", + "//tensorflow/core/util:port", "//third_party/python_runtime:headers", "@pybind11", ], @@ -885,15 +885,6 @@ tf_py_test( name = "file_system_test", size = "small", srcs = ["framework/file_system_test.py"], - additional_deps = [ - ":client_testlib", - ":data_flow_ops", - ":framework", - ":framework_for_generated_wrappers", - ":io_ops", - ":platform", - ":util", - ], data = [":framework/test_file_system.so"], main = "framework/file_system_test.py", python_version = "PY3", @@ -902,60 +893,69 @@ tf_py_test( "no_windows", "notap", ], + deps = [ + ":client_testlib", + ":data_flow_ops", + ":framework", + ":framework_for_generated_wrappers", + ":io_ops", + ":platform", + ":util", + ], ) tf_py_test( name = "decorator_utils_test", srcs = ["util/decorator_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":platform", ":util", ], - python_version = "PY3", ) tf_py_test( name = "tf_export_test", srcs = ["util/tf_export_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":platform", ":util", ], - python_version = "PY3", ) tf_py_test( name = "deprecation_test", srcs = ["util/deprecation_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":platform", ":util", ], - python_version = "PY3", ) tf_py_test( name = "dispatch_test", srcs = ["util/dispatch_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":platform", ":util", ], - python_version = "PY3", ) tf_py_test( name = "keyword_args_test", srcs = ["util/keyword_args_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":util", ], - python_version = "PY3", ) cc_library( @@ -1264,7 +1264,9 @@ tf_py_test( name = "function_def_to_graph_test", size = "small", srcs = ["framework/function_def_to_graph_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_pip"], + deps = [ ":array_ops", ":client_testlib", ":constant_op", @@ -1277,8 +1279,6 @@ tf_py_test( ":op_def_library", ":test_ops", ], - python_version = "PY3", - tags = ["no_pip"], ) py_library( @@ -1405,19 +1405,19 @@ py_library( cuda_py_test( name = "op_callbacks_test", srcs = ["framework/op_callbacks_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":control_flow_ops", ":framework_test_lib", ":script_ops", ":sparse_ops", ":sparse_tensor", - "//third_party/py/numpy", "//tensorflow/python/eager:execute", "//tensorflow/python/eager:test", "//tensorflow/python/keras", + "//third_party/py/numpy", ], - python_version = "PY3", ) py_library( @@ -1502,12 +1502,12 @@ tf_py_test( name = "auto_control_deps_test", size = "small", srcs = ["framework/auto_control_deps_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":auto_control_deps", ":client_testlib", "//tensorflow/python/keras", ], - python_version = "PY3", ) py_library( @@ -1525,16 +1525,16 @@ cuda_py_test( name = "config_test", size = "small", srcs = ["framework/config_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_pip"], # test_ops are not available in pip. + deps = [ + ":client_testlib", ":config", ":constant_op", - ":client_testlib", ":platform", ":test_ops", ":util", - ], - python_version = "PY3", - tags = ["no_pip"], # test_ops are not available in pip. + ] + tf_additional_xla_deps_py(), ) py_library( @@ -1570,7 +1570,8 @@ tf_py_test( name = "smart_cond_test", size = "small", srcs = ["framework/smart_cond_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":constant_op", ":framework_ops", @@ -1578,7 +1579,6 @@ tf_py_test( ":session", ":smart_cond", ], - python_version = "PY3", ) py_library( @@ -1640,19 +1640,19 @@ py_test( tf_py_test( name = "framework_composite_tensor_utils_test", srcs = ["framework/composite_tensor_utils_test.py"], - additional_deps = [ + main = "framework/composite_tensor_utils_test.py", + python_version = "PY3", + deps = [ ":array_ops", ":composite_tensor", ":composite_tensor_utils", ":framework_test_lib", ":sparse_ops", ":sparse_tensor", - "//third_party/py/numpy", "//tensorflow/python/ops/ragged:ragged_tensor", "//tensorflow/python/ops/ragged:ragged_tensor_value", + "//third_party/py/numpy", ], - main = "framework/composite_tensor_utils_test.py", - python_version = "PY3", ) # This target is maintained separately from :util to provide separate visibility @@ -1851,47 +1851,49 @@ tf_py_test( name = "framework_registry_test", size = "small", srcs = ["framework/registry_test.py"], - additional_deps = [ - ":framework_for_generated_wrappers", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - ], main = "framework/registry_test.py", python_version = "PY3", + deps = [ + ":framework_for_generated_wrappers", + "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "framework_errors_test", size = "small", srcs = ["framework/errors_test.py"], - additional_deps = [ + main = "framework/errors_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":errors", "//tensorflow/core:protos_all_py", ], - main = "framework/errors_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_error_interpolation_test", size = "small", srcs = ["framework/error_interpolation_test.py"], - additional_deps = [ + main = "framework/error_interpolation_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":constant_op", ":error_interpolation", ":traceable_stack", ], - main = "framework/error_interpolation_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_subscribe_test", size = "small", srcs = ["framework/subscribe_test.py"], - additional_deps = [ + main = "framework/subscribe_test.py", + python_version = "PY3", + deps = [ ":framework", ":framework_for_generated_wrappers", ":framework_test_lib", @@ -1900,8 +1902,6 @@ tf_py_test( ":script_ops", ":subscribe", ], - main = "framework/subscribe_test.py", - python_version = "PY3", ) tf_py_test( @@ -1911,16 +1911,16 @@ tf_py_test( "platform/build_info.py", "platform/build_info_test.py", ], - additional_deps = [ - ":client_testlib", - ":platform", - ], main = "platform/build_info_test.py", python_version = "PY3", tags = [ "no_pip", "notap", ], + deps = [ + ":client_testlib", + ":platform", + ], ) tf_py_test( @@ -1930,28 +1930,28 @@ tf_py_test( "platform/benchmark.py", "platform/benchmark_test.py", ], - additional_deps = [ - ":client_testlib", - ":platform", - ], main = "platform/benchmark_test.py", python_version = "PY3", tags = [ "no_pip", ], + deps = [ + ":client_testlib", + ":platform", + ], ) tf_py_test( name = "proto_test", size = "small", srcs = ["framework/proto_test.py"], - additional_deps = [ + main = "framework/proto_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":framework_for_generated_wrappers", "//third_party/py/numpy", ], - main = "framework/proto_test.py", - python_version = "PY3", ) tf_gen_op_wrapper_private_py( @@ -1985,7 +1985,13 @@ cuda_py_test( name = "function_test", size = "medium", srcs = ["framework/function_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 10, + tags = [ + "noasan", + "optonly", + ], + deps = [ ":array_ops", ":client", ":client_testlib", @@ -2004,14 +2010,8 @@ cuda_py_test( ":random_ops", ":variable_scope", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - ], - python_version = "PY3", - shard_count = 10, - tags = [ - "noasan", - "optonly", + "//third_party/py/numpy", ], ) @@ -2019,19 +2019,21 @@ tf_py_test( name = "framework_versions_test", size = "small", srcs = ["framework/versions_test.py"], - additional_deps = [ + main = "framework/versions_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ], - main = "framework/versions_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_importer_test", size = "large", srcs = ["framework/importer_test.py"], - additional_deps = [ + main = "framework/importer_test.py", + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":framework", @@ -2043,11 +2045,9 @@ tf_py_test( ":random_ops", ":test_ops", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - main = "framework/importer_test.py", - python_version = "PY3", ) filegroup( @@ -2062,7 +2062,14 @@ tf_py_test( name = "framework_meta_graph_test", size = "small", srcs = ["framework/meta_graph_test.py"], - additional_deps = [ + data = [":meta_graph_testdata"], + main = "framework/meta_graph_test.py", + python_version = "PY3", + tags = [ + "no_pip", + "no_windows", + ], + deps = [ ":array_ops", ":client_testlib", ":control_flow_ops", @@ -2077,28 +2084,21 @@ tf_py_test( ":training", ":variables", ], - data = [":meta_graph_testdata"], - main = "framework/meta_graph_test.py", - python_version = "PY3", - tags = [ - "no_pip", - "no_windows", - ], ) tf_py_test( name = "framework_traceable_stack_test", size = "small", srcs = ["framework/traceable_stack_test.py"], - additional_deps = [ + main = "framework/traceable_stack_test.py", + python_version = "PY3", + deps = [ ":framework_test_lib", ":platform_test", ":test_ops", ":traceable_stack", ":util", ], - main = "framework/traceable_stack_test.py", - python_version = "PY3", ) tf_gen_op_wrapper_py( @@ -2144,22 +2144,25 @@ tf_py_test( name = "framework_common_shapes_test", size = "small", srcs = ["framework/common_shapes_test.py"], - additional_deps = [ + main = "framework/common_shapes_test.py", + python_version = "PY3", + deps = [ ":framework", ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", "//tensorflow/core:protos_all_py", ], - main = "framework/common_shapes_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_ops_test", size = "small", srcs = ["framework/ops_test.py"], - additional_deps = [ + main = "framework/ops_test.py", + python_version = "PY3", + tags = ["no_pip"], # test_ops_2 is not available in pip. + deps = [ ":cond_v2", ":control_flow_ops", ":errors", @@ -2180,144 +2183,144 @@ tf_py_test( "//tensorflow/python/eager:context", "//tensorflow/python/eager:function", ], - main = "framework/ops_test.py", - python_version = "PY3", - tags = ["no_pip"], # test_ops_2 is not available in pip. ) tf_py_test( name = "framework_ops_enable_eager_test", size = "small", srcs = ["framework/ops_enable_eager_test.py"], - additional_deps = [ + main = "framework/ops_enable_eager_test.py", + python_version = "PY3", + deps = [ ":framework", ":platform_test", "//tensorflow/python/eager:context", ], - main = "framework/ops_enable_eager_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_tensor_shape_test", size = "small", srcs = ["framework/tensor_shape_test.py"], - additional_deps = [ + main = "framework/tensor_shape_test.py", + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", "//tensorflow/core:protos_all_py", "@absl_py//absl/testing:parameterized", ], - main = "framework/tensor_shape_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_type_spec_test", size = "small", srcs = ["framework/type_spec_test.py"], - additional_deps = [ + main = "framework/type_spec_test.py", + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", ":type_spec", "//third_party/py/numpy", ], - main = "framework/type_spec_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_tensor_spec_test", size = "small", srcs = ["framework/tensor_spec_test.py"], - additional_deps = [ + main = "framework/tensor_spec_test.py", + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", ":tensor_spec", "//third_party/py/numpy", ], - main = "framework/tensor_spec_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_sparse_tensor_test", size = "small", srcs = ["framework/sparse_tensor_test.py"], - additional_deps = [ + main = "framework/sparse_tensor_test.py", + python_version = "PY3", + deps = [ ":framework", ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", "//tensorflow/core:protos_all_py", ], - main = "framework/sparse_tensor_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_device_spec_test", size = "small", srcs = ["framework/device_spec_test.py"], - additional_deps = [ + main = "framework/device_spec_test.py", + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", "//tensorflow/core:protos_all_py", ], - main = "framework/device_spec_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_device_test", size = "small", srcs = ["framework/device_test.py"], - additional_deps = [ + main = "framework/device_test.py", + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", "//tensorflow/core:protos_all_py", ], - main = "framework/device_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_random_seed_test", size = "small", srcs = ["framework/random_seed_test.py"], - additional_deps = [ + main = "framework/random_seed_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":framework", ], - main = "framework/random_seed_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_tensor_shape_div_test", size = "small", srcs = ["framework/tensor_shape_div_test.py"], - additional_deps = [ + main = "framework/tensor_shape_div_test.py", + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", - "@six_archive//:six", "//tensorflow/core:protos_all_py", + "@six_archive//:six", ], - main = "framework/tensor_shape_div_test.py", - python_version = "PY3", ) tf_py_test( name = "framework_tensor_util_test", size = "small", srcs = ["framework/tensor_util_test.py"], - additional_deps = [ + main = "framework/tensor_util_test.py", + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":array_ops", ":client_testlib", ":framework", @@ -2327,16 +2330,16 @@ tf_py_test( ":state_ops_gen", "//third_party/py/numpy", ], - main = "framework/tensor_util_test.py", - python_version = "PY3", - tags = ["no_windows"], ) tf_py_test( name = "framework_test_util_test", size = "small", srcs = ["framework/test_util_test.py"], - additional_deps = [ + main = "framework/test_util_test.py", + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":control_flow_ops", ":errors", ":framework_for_generated_wrappers", @@ -2347,56 +2350,53 @@ tf_py_test( ":session", ":test_ops", ":variables", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", "//tensorflow/python/eager:context", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - main = "framework/test_util_test.py", - python_version = "PY3", - tags = ["no_windows"], ) tf_py_test( name = "framework_dtypes_test", size = "small", srcs = ["framework/dtypes_test.py"], - additional_deps = [ + main = "framework/dtypes_test.py", + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", - "//third_party/py/numpy", "//tensorflow:tensorflow_py", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - main = "framework/dtypes_test.py", - python_version = "PY3", ) tf_py_test( name = "op_def_library_test", size = "small", srcs = ["framework/op_def_library_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", ], - python_version = "PY3", ) tf_py_test( name = "framework_kernels_test", size = "small", srcs = ["framework/kernels_test.py"], - additional_deps = [ + main = "framework/kernels_test.py", + python_version = "PY3", + deps = [ ":framework_test_lib", ":kernels", ":platform_test", ":test_ops", ], - main = "framework/kernels_test.py", - python_version = "PY3", ) tf_gen_op_wrapper_private_py( @@ -2910,13 +2910,13 @@ tf_py_test( name = "clip_ops_test", size = "small", srcs = ["ops/clip_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":clip_ops", ":framework_for_generated_wrappers", "//third_party/py/numpy", ], - python_version = "PY3", ) py_library( @@ -2935,13 +2935,13 @@ tf_py_test( name = "clustering_ops_test", size = "medium", srcs = ["ops/clustering_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":clustering_ops", ":framework_for_generated_wrappers", "//third_party/py/numpy", ], - python_version = "PY3", ) py_library( @@ -2958,32 +2958,32 @@ tf_py_test( name = "collective_ops_test", size = "small", srcs = ["ops/collective_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":collective_ops", - ":kernels", ":framework_for_generated_wrappers", + ":kernels", "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_test( name = "collective_ops_gpu_test", size = "small", srcs = ["ops/collective_ops_gpu_test.py"], - additional_deps = [ - ":client_testlib", - ":collective_ops", - ":framework_for_generated_wrappers", - "//third_party/py/numpy", - ], python_version = "PY3", tags = [ "no_cuda_on_cpu_tap", "no_rocm", "no_windows", ], + deps = [ + ":client_testlib", + ":collective_ops", + ":framework_for_generated_wrappers", + "//third_party/py/numpy", + ], ) py_library( @@ -3080,38 +3080,32 @@ tf_py_test( name = "control_flow_v2_toggles_test", size = "small", srcs = ["ops/control_flow_v2_toggles_test.py"], - additional_deps = [ - ":control_flow_v2_toggles", - ":control_flow_util_v2", + python_version = "PY3", + deps = [ ":client_testlib", + ":control_flow_util_v2", + ":control_flow_v2_toggles", ":platform_test", ], - python_version = "PY3", ) tf_py_test( name = "control_flow_v2_enable_test", size = "small", srcs = ["ops/control_flow_v2_enable_test.py"], - additional_deps = [ - ":tf2", - ":control_flow_util", - ":client_testlib", - ":platform_test", - ], python_version = "PY3", + deps = [ + ":client_testlib", + ":control_flow_util", + ":platform_test", + ":tf2", + ], ) tf_py_test( name = "control_flow_v2_disable_test", size = "small", srcs = ["ops/control_flow_v2_disable_test.py"], - additional_deps = [ - ":tf2", - ":control_flow_util", - ":client_testlib", - ":platform_test", - ], python_version = "PY3", # This tests that it is possible to disable cfv2 using env vars. # This does not apply to TF 2.0 nightly builds which enable @@ -3121,6 +3115,12 @@ tf_py_test( "no_oss", "no_pip", ], + deps = [ + ":client_testlib", + ":control_flow_util", + ":platform_test", + ":tf2", + ], ) py_library( @@ -3648,15 +3648,15 @@ cuda_py_test( name = "mixed_precision_test", size = "small", srcs = ["training/experimental/mixed_precision_test.py"], - additional_deps = [ - ":mixed_precision", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - ], python_version = "PY3", tags = [ "no_rocm", ], + deps = [ + ":mixed_precision", + "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", + ], ) py_library( @@ -3677,17 +3677,17 @@ cuda_py_test( name = "loss_scaling_gradient_tape_test", size = "medium", srcs = ["training/experimental/loss_scaling_gradient_tape_test.py"], - additional_deps = [ + deps = [ ":client_testlib", ":constant_op", ":framework_test_combinations_lib", ":loss_scale", ":loss_scaling_gradient_tape", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python/compat:v2_compat", "//tensorflow/python/distribute:mirrored_strategy", "//tensorflow/python/eager:def_function", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -3954,18 +3954,18 @@ cuda_py_test( name = "stateful_random_ops_test", size = "medium", srcs = ["ops/stateful_random_ops_test.py"], - additional_deps = [ - ":stateful_random_ops", + python_version = "PY3", + tags = ["no_rocm"], + xla_enable_strict_auto_jit = False, + deps = [ ":client_testlib", ":config", ":logging_ops", ":random_ops_gen", - "//tensorflow/python/kernel_tests/random:util", + ":stateful_random_ops", "//tensorflow/python/distribute:mirrored_strategy", + "//tensorflow/python/kernel_tests/random:util", ], - python_version = "PY3", - tags = ["no_rocm"], - xla_enable_strict_auto_jit = False, ) py_library( @@ -4092,18 +4092,18 @@ py_library( tf_py_test( name = "sparse_ops_test", srcs = ["ops/sparse_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_grad", ":constant_op", ":dtypes", ":framework_test_lib", + ":gradient_checker_v2", + ":sparse_grad", ":sparse_ops", ":sparse_tensor", - ":sparse_grad", - ":gradient_checker_v2", "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", ) py_library( @@ -4122,7 +4122,8 @@ py_library( tf_py_test( name = "sort_ops_test", srcs = ["ops/sort_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":framework", @@ -4130,7 +4131,6 @@ tf_py_test( ":sort_ops", "//third_party/py/numpy", ], - python_version = "PY3", ) py_library( @@ -4487,21 +4487,23 @@ cuda_py_test( name = "bitwise_ops_test", size = "small", srcs = ["ops/bitwise_ops_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":bitwise_ops", ":constant_op", ":dtypes", ":framework_test_lib", ], - python_version = "PY3", - tags = ["no_windows"], ) cuda_py_test( name = "control_flow_ops_test", size = "small", srcs = ["ops/control_flow_ops_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 2, + deps = [ ":array_ops", ":cond_v2", ":control_flow_ops", @@ -4523,15 +4525,14 @@ cuda_py_test( ":while_v2", "//tensorflow/python/eager:def_function", ], - python_version = "PY3", - shard_count = 2, ) cuda_py_test( name = "gradient_checker_test", size = "medium", srcs = ["ops/gradient_checker_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -4541,7 +4542,6 @@ cuda_py_test( ":platform", "//third_party/py/numpy", ], - python_version = "PY3", ) py_test( @@ -4562,7 +4562,8 @@ cuda_py_test( name = "gradient_checker_v2_test", size = "medium", srcs = ["ops/gradient_checker_v2_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -4572,14 +4573,15 @@ cuda_py_test( ":platform", "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_test( name = "gradients_test", size = "medium", srcs = ["ops/gradients_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_oss"], # b/118709825 + deps = [ ":array_grad", ":array_ops", ":control_flow_grad", @@ -4604,19 +4606,18 @@ cuda_py_test( ":test_ops", ":unconnected_gradients", ":variable_scope", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python/keras:engine", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", - tags = ["no_oss"], # b/118709825 ) cuda_py_test( name = "histogram_ops_test", size = "small", srcs = ["ops/histogram_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -4625,28 +4626,30 @@ cuda_py_test( ":variables", "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_test( name = "image_grad_test", size = "medium", srcs = ["ops/image_grad_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":gradients", ":image_ops", "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_test( name = "image_ops_test", size = "medium", srcs = ["ops/image_ops_test.py"], - additional_deps = [ + data = ["//tensorflow/core:image_testdata"], + python_version = "PY3", + shard_count = 5, + deps = [ ":array_ops", ":client", ":client_testlib", @@ -4660,50 +4663,49 @@ cuda_py_test( ":platform_test", ":random_ops", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - data = ["//tensorflow/core:image_testdata"], - python_version = "PY3", - shard_count = 5, ) cuda_py_test( name = "init_ops_test", size = "small", srcs = ["ops/init_ops_test.py"], - additional_deps = [ - ":client_testlib", - ":init_ops", - ":framework_ops", - ":resource_variable_ops", - "//third_party/py/numpy", - "//tensorflow/python/eager:context", - ], python_version = "PY3", + deps = [ + ":client_testlib", + ":framework_ops", + ":init_ops", + ":resource_variable_ops", + "//tensorflow/python/eager:context", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "init_ops_v2_test", size = "medium", srcs = ["ops/init_ops_v2_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", + ":framework_ops", ":init_ops_v2", ":random_ops", - ":framework_ops", - "//third_party/py/numpy", "//tensorflow/python/eager:context", + "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_test( name = "math_grad_test", size = "small", srcs = ["ops/math_grad_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows_gpu"], + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -4713,15 +4715,15 @@ cuda_py_test( "//tensorflow/python/eager:context", "//third_party/py/numpy", ], - python_version = "PY3", - tags = ["no_windows_gpu"], ) cuda_py_test( name = "math_ops_test", size = "small", srcs = ["ops/math_ops_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows_gpu"], + deps = [ ":array_ops", ":errors", ":framework_for_generated_wrappers", @@ -4732,15 +4734,16 @@ cuda_py_test( ":variables", "//third_party/py/numpy", ], - python_version = "PY3", - tags = ["no_windows_gpu"], ) cuda_py_test( name = "nn_batchnorm_test", size = "medium", srcs = ["ops/nn_batchnorm_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 4, + tags = ["no_windows"], + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -4751,16 +4754,16 @@ cuda_py_test( ":nn_ops_gen", "//third_party/py/numpy", ], - python_version = "PY3", - shard_count = 4, - tags = ["no_windows"], ) cuda_py_test( name = "nn_fused_batchnorm_test", size = "large", srcs = ["ops/nn_fused_batchnorm_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 16, + tags = ["no_rocm"], + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -4769,16 +4772,17 @@ cuda_py_test( ":nn_grad", "//third_party/py/numpy", ], - python_version = "PY3", - shard_count = 16, - tags = ["no_rocm"], ) cuda_py_test( name = "nn_test", size = "medium", srcs = ["ops/nn_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows"], + # TODO(b/130689556): Numerical differences due to fast math on CPU. + xla_enable_strict_auto_jit = False, + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -4788,13 +4792,9 @@ cuda_py_test( ":partitioned_variables", ":variable_scope", ":variables", - "@absl_py//absl/testing:parameterized", "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", - tags = ["no_windows"], - # TODO(b/130689556): Numerical differences due to fast math on CPU. - xla_enable_strict_auto_jit = False, ) py_test( @@ -4814,7 +4814,8 @@ cuda_py_test( name = "nn_xent_test", size = "medium", srcs = ["ops/nn_xent_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":gradients", @@ -4822,7 +4823,6 @@ cuda_py_test( ":nn_grad", "//third_party/py/numpy", ], - python_version = "PY3", ) py_test( @@ -4841,7 +4841,10 @@ cuda_py_test( name = "special_math_ops_test", size = "medium", srcs = ["ops/special_math_ops_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 10, + tags = ["no_windows_gpu"], + deps = [ ":array_ops", ":client", ":client_testlib", @@ -4850,22 +4853,19 @@ cuda_py_test( ":special_math_ops", "//third_party/py/numpy", ], - python_version = "PY3", - shard_count = 10, - tags = ["no_windows_gpu"], ) tf_py_test( name = "variable_spec_test", size = "small", srcs = ["ops/variable_spec_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":framework_for_generated_wrappers", ":framework_test_lib", ":platform_test", "//third_party/py/numpy", ], - python_version = "PY3", ) py_library( @@ -5066,7 +5066,13 @@ tf_py_test( name = "evaluation_test", size = "small", srcs = ["training/evaluation_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 3, + tags = [ + "manual", + "notap", # Disabling until b/33000128 and b/33040312 are fixed. + ], + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5080,15 +5086,9 @@ tf_py_test( ":summary", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", "//tensorflow/python/ops/losses", - ], - python_version = "PY3", - shard_count = 3, - tags = [ - "manual", - "notap", # Disabling until b/33000128 and b/33040312 are fixed. + "//third_party/py/numpy", ], ) @@ -5155,11 +5155,11 @@ py_library( tf_py_test( name = "tf_stack_test", srcs = ["util/tf_stack_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":util", ], - python_version = "PY3", ) tf_py_test( @@ -5174,9 +5174,9 @@ tf_py_test( name = "util_nest_test", size = "small", srcs = ["util/nest_test.py"], - additional_deps = [":util_nest_test_main_lib"], main = "util/nest_test.py", python_version = "PY3", + deps = [":util_nest_test_main_lib"], ) py_library( @@ -5199,44 +5199,44 @@ tf_py_test( name = "util_serialization_test", size = "small", srcs = ["util/serialization_test.py"], - additional_deps = [ + main = "util/serialization_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":util", ], - main = "util/serialization_test.py", - python_version = "PY3", ) tf_py_test( name = "function_utils_test", srcs = ["util/function_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":util", ], - python_version = "PY3", ) tf_py_test( name = "tf_contextlib_test", size = "small", srcs = ["util/tf_contextlib_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":util", ], - python_version = "PY3", ) tf_py_test( name = "tf_decorator_test", size = "small", srcs = ["util/tf_decorator_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":util", ], - python_version = "PY3", ) py_library( @@ -5255,22 +5255,22 @@ tf_py_test( name = "tf_should_use_test", size = "small", srcs = ["util/tf_should_use_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":tf_should_use", ], - python_version = "PY3", ) tf_py_test( name = "tf_inspect_test", size = "small", srcs = ["util/tf_inspect_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":util", ], - python_version = "PY3", ) py_library( @@ -5289,26 +5289,26 @@ tf_py_test( name = "lock_util_test", size = "small", srcs = ["util/lock_util_test.py"], - additional_deps = [ + main = "util/lock_util_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":util", "@absl_py//absl/testing:parameterized", ], - main = "util/lock_util_test.py", - python_version = "PY3", ) tf_py_test( name = "module_wrapper_test", size = "small", srcs = ["util/module_wrapper_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":util", - "@six_archive//:six", "//tensorflow/tools/compatibility:all_renames_v2", + "@six_archive//:six", ], - python_version = "PY3", ) tf_proto_library( @@ -5341,22 +5341,24 @@ tf_py_test( name = "protobuf_compare_test", size = "small", srcs = ["util/protobuf/compare_test.py"], - additional_deps = [ + main = "util/protobuf/compare_test.py", + python_version = "PY3", + tags = ["no_pip"], # compare_test_pb2 proto is not available in pip. + deps = [ ":compare_test_proto_py", ":platform_test", ":util", "@six_archive//:six", ], - main = "util/protobuf/compare_test.py", - python_version = "PY3", - tags = ["no_pip"], # compare_test_pb2 proto is not available in pip. ) tf_py_test( name = "util_example_parser_configuration_test", size = "small", srcs = ["util/example_parser_configuration_test.py"], - additional_deps = [ + main = "util/example_parser_configuration_test.py", + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5364,22 +5366,20 @@ tf_py_test( ":parsing_ops", ":util_example_parser_configuration", ], - main = "util/example_parser_configuration_test.py", - python_version = "PY3", ) tf_py_test( name = "events_writer_test", size = "small", srcs = ["client/events_writer_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":errors", ":framework_test_lib", ":lib", ":platform_test", ":util", ], - python_version = "PY3", ) py_library( @@ -5413,14 +5413,14 @@ cuda_py_tests( srcs = [ "client/device_lib_test.py", ], - additional_deps = [ + python_version = "PY3", + deps = [ ":client", ":client_testlib", ":framework_test_lib", ":platform_test", "//tensorflow/core:protos_all_py", ], - python_version = "PY3", ) cc_library( @@ -5556,7 +5556,7 @@ WIN_LIB_FILES_FOR_EXPORTED_SYMBOLS = [ ":cpp_python_util", # util ":py_func_lib", # py_func ":model_analyzer_lib", # model_analyzer - "//tensorflow/core:util_port", # util_port + "//tensorflow/core/util:port", # util_port "//tensorflow/stream_executor:stream_executor_pimpl", # stat_summarizer "//tensorflow/core/grappler/graph_analyzer:graph_analyzer_tool", # graph_analyzer "//tensorflow/core/profiler/internal:print_model_analysis", # tfprof @@ -5713,7 +5713,9 @@ tf_py_test( name = "server_lib_test", size = "small", srcs = ["training/server_lib_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5723,18 +5725,18 @@ tf_py_test( ":math_ops", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", ) tf_py_test( name = "server_lib_multiple_containers_test", size = "small", srcs = ["training/server_lib_multiple_containers_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5744,18 +5746,18 @@ tf_py_test( ":math_ops", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", ) tf_py_test( name = "server_lib_same_variables_clear_container_test", size = "small", srcs = ["training/server_lib_same_variables_clear_container_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5765,18 +5767,18 @@ tf_py_test( ":math_ops", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", ) tf_py_test( name = "server_lib_same_variables_clear_test", size = "small", srcs = ["training/server_lib_same_variables_clear_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5786,18 +5788,18 @@ tf_py_test( ":math_ops", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", ) tf_py_test( name = "server_lib_same_variables_no_clear_test", size = "small", srcs = ["training/server_lib_same_variables_no_clear_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5807,18 +5809,18 @@ tf_py_test( ":math_ops", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", ) tf_py_test( name = "server_lib_sparse_job_test", size = "small", srcs = ["training/server_lib_sparse_job_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5828,11 +5830,9 @@ tf_py_test( ":math_ops", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", ) cuda_py_test( @@ -5841,7 +5841,13 @@ cuda_py_test( srcs = [ "training/localhost_cluster_performance_test.py", ], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + tags = [ + "no_oss", # Test flaky due to port collisions. + "oss_serial", + ], + deps = [ ":client", ":client_testlib", ":distributed_framework_test_lib", @@ -5852,12 +5858,6 @@ cuda_py_test( ":variables", "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", - tags = [ - "no_oss", # Test flaky due to port collisions. - "oss_serial", - ], ) tf_py_test( @@ -5866,12 +5866,6 @@ tf_py_test( srcs = [ "training/sync_replicas_optimizer_test.py", ], - additional_deps = [ - ":client_testlib", - ":framework_for_generated_wrappers", - ":training", - ":variables", - ], grpc_enabled = True, python_version = "PY3", tags = [ @@ -5879,6 +5873,12 @@ tf_py_test( "notsan", # data race due to b/62910646 "oss_serial", ], + deps = [ + ":client_testlib", + ":framework_for_generated_wrappers", + ":training", + ":variables", + ], ) py_library( @@ -5908,7 +5908,14 @@ tf_py_test( name = "session_test", size = "medium", srcs = ["client/session_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + tags = [ + "no_gpu", # b/127001953 + "no_pip_gpu", # testInteractivePlacePrunedGraph fails on invalid assumption about GPU ops. + "no_windows", + ], + deps = [ ":array_ops", ":client", ":config", @@ -5927,20 +5934,22 @@ tf_py_test( "//third_party/py/numpy", "@six_archive//:six", ], - grpc_enabled = True, - python_version = "PY3", - tags = [ - "no_gpu", # b/127001953 - "no_pip_gpu", # testInteractivePlacePrunedGraph fails on invalid assumption about GPU ops. - "no_windows", - ], ) tf_py_test( name = "session_clusterspec_prop_test", size = "small", srcs = ["client/session_clusterspec_prop_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + tags = [ + "no_gpu", + "no_oss", + "no_pip", + "no_pip_gpu", + "notap", + ], + deps = [ ":array_ops", ":client", ":client_testlib", @@ -5955,28 +5964,12 @@ tf_py_test( ":variables", "//third_party/py/numpy", ], - grpc_enabled = True, - python_version = "PY3", - tags = [ - "no_gpu", - "no_oss", - "no_pip", - "no_pip_gpu", - "notap", - ], ) tf_py_test( name = "session_list_devices_test", size = "small", srcs = ["client/session_list_devices_test.py"], - additional_deps = [ - ":client", - ":framework", - ":framework_test_lib", - ":platform_test", - ":training", - ], grpc_enabled = True, python_version = "PY3", tags = [ @@ -5984,13 +5977,26 @@ tf_py_test( "no_pip_gpu", "notsan", # data race due to b/62910646 ], + deps = [ + ":client", + ":framework", + ":framework_test_lib", + ":platform_test", + ":training", + ], ) tf_py_test( name = "session_partial_run_test", size = "small", srcs = ["client/session_partial_run_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + tags = [ + "no_gpu", + "no_windows", + ], + deps = [ ":array_ops", ":client", ":errors", @@ -6003,65 +6009,59 @@ tf_py_test( ":util", "@six_archive//:six", ], - grpc_enabled = True, - python_version = "PY3", - tags = [ - "no_gpu", - "no_windows", - ], ) cuda_py_test( name = "timeline_test", size = "small", srcs = ["client/timeline_test.py"], - additional_deps = [ + python_version = "PY3", + xla_enable_strict_auto_jit = False, # Graph structure is different with autojit + deps = [ ":client", ":client_testlib", ":framework_for_generated_wrappers", ":math_ops", "//tensorflow/core:protos_all_py", ], - args = tf_additional_cupti_test_flags(), - python_version = "PY3", - xla_enable_strict_auto_jit = False, # Graph structure is different with autojit ) cuda_py_test( name = "virtual_gpu_test", size = "small", srcs = ["client/virtual_gpu_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "no_gpu", # b/127386241 + "no_windows_gpu", + ], + deps = [ ":client", ":client_testlib", ":framework_for_generated_wrappers", ":math_ops", "//tensorflow/core:protos_all_py", ], - python_version = "PY3", - tags = [ - "no_gpu", # b/127386241 - "no_windows_gpu", - ], ) tf_py_test( name = "c_api_util_test", size = "small", srcs = ["framework/c_api_util_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":c_api_util", ":framework_test_lib", ":platform_test", ], - python_version = "PY3", ) tf_py_test( name = "graph_util_test", size = "small", srcs = ["framework/graph_util_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client", ":client_testlib", ":framework", @@ -6072,69 +6072,62 @@ tf_py_test( ":variables", "//tensorflow/core:protos_all_py", ], - python_version = "PY3", ) tf_py_test( name = "convert_to_constants_test", size = "small", srcs = ["framework/convert_to_constants_test.py"], - additional_deps = [ - ":convert_to_constants", + python_version = "PY3", + deps = [ "client_testlib", "framework_test_lib", + ":convert_to_constants", ], - python_version = "PY3", ) tf_py_test( name = "bfloat16_test", size = "small", srcs = ["lib/core/bfloat16_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":lib", ":pywrap_tensorflow", ], - python_version = "PY3", ) tf_py_test( name = "file_io_test", size = "small", srcs = ["lib/io/file_io_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":client_testlib", ":errors", ":lib", ], - python_version = "PY3", - tags = ["no_windows"], ) tf_py_test( name = "tf_record_test", size = "small", srcs = ["lib/io/tf_record_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":errors", ":lib", ":util", ], - python_version = "PY3", ) tf_py_test( name = "tf_record_multiprocessing_test", size = "small", srcs = ["lib/io/tf_record_multiprocessing_test.py"], - additional_deps = [ - ":client_testlib", - ":errors", - ":lib", - ":util", - ], python_version = "PY3", tags = [ # multiprocessing can be flaky in the internal google @@ -6144,23 +6137,29 @@ tf_py_test( # windows, so we disable this test on windows. "no_windows", ], + deps = [ + ":client_testlib", + ":errors", + ":lib", + ":util", + ], ) cuda_py_test( name = "adam_test", size = "small", srcs = ["training/adam_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", + ":client_testlib", ":framework", ":math_ops", ":platform", - ":training", ":platform_test", - ":client_testlib", + ":training", "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_test( @@ -6169,7 +6168,12 @@ cuda_py_test( srcs = [ "training/moving_averages_test.py", ], - additional_deps = [ + python_version = "PY3", + tags = [ + "no_windows", # b/139083295: bfloat16 tests fail on Windows + "notsan", + ], + deps = [ ":array_ops", ":client_testlib", ":constant_op", @@ -6180,11 +6184,6 @@ cuda_py_test( ":variable_scope", ":variables", ], - python_version = "PY3", - tags = [ - "no_windows", # b/139083295: bfloat16 tests fail on Windows - "notsan", - ], ) cuda_py_tests( @@ -6211,7 +6210,8 @@ cuda_py_tests( "training/tensorboard_logging_test.py", "training/training_ops_test.py", ], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -6223,8 +6223,8 @@ cuda_py_tests( ":framework", ":framework_for_generated_wrappers", ":framework_test_lib", - ":lookup_ops", ":gradients", + ":lookup_ops", ":math_ops", ":nn_grad", ":nn_ops", @@ -6243,11 +6243,10 @@ cuda_py_tests( ":util", ":variable_scope", ":variables", + "//tensorflow/core:protos_all_py", "//third_party/py/numpy", "@six_archive//:six", - "//tensorflow/core:protos_all_py", ], - python_version = "PY3", ) py_library( @@ -6266,7 +6265,9 @@ cuda_py_test( srcs = [ "training/saver_test.py", ], - additional_deps = [ + python_version = "PY3", + tags = ["multi_gpu"], + deps = [ ":array_ops", ":client_testlib", ":control_flow_ops", @@ -6276,26 +6277,24 @@ cuda_py_test( ":math_ops", ":nn_grad", ":nn_ops", - ":saver_test_utils", ":partitioned_variables", ":platform", ":platform_test", ":py_checkpoint_reader", ":random_ops", ":resource_variable_ops", + ":saver_test_utils", ":sparse_ops", ":summary", ":training", ":util", ":variable_scope", ":variables", - "//third_party/py/numpy", - "@six_archive//:six", "//tensorflow/core:protos_all_py", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@six_archive//:six", ], - python_version = "PY3", - tags = ["multi_gpu"], ) cuda_py_test( @@ -6304,7 +6303,8 @@ cuda_py_test( srcs = [ "training/checkpoint_management_test.py", ], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":control_flow_ops", @@ -6314,32 +6314,37 @@ cuda_py_test( ":math_ops", ":nn_grad", ":nn_ops", - ":saver_test_utils", ":partitioned_variables", ":platform", ":platform_test", ":pywrap_tensorflow", ":random_ops", ":resource_variable_ops", + ":saver_test_utils", ":sparse_ops", ":summary", ":training", ":util", ":variable_scope", ":variables", - "//third_party/py/numpy", - "@six_archive//:six", "//tensorflow/core:protos_all_py", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@six_archive//:six", ], - python_version = "PY3", ) tf_py_test( name = "saver_large_variable_test", size = "medium", srcs = ["training/saver_large_variable_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "manual", + "noasan", # http://b/30379628 + "notsan", # http://b/30379628 + ], + deps = [ ":client", ":client_testlib", ":errors", @@ -6348,19 +6353,18 @@ tf_py_test( ":variables", "//tensorflow/core:protos_all_py", ], - python_version = "PY3", - tags = [ - "manual", - "noasan", # http://b/30379628 - "notsan", # http://b/30379628 - ], ) tf_py_test( name = "saver_large_partitioned_variable_test", size = "medium", srcs = ["training/saver_large_partitioned_variable_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "noasan", # http://b/30782289 + "notsan", # http://b/30782289 + ], + deps = [ ":client", ":client_testlib", ":framework_for_generated_wrappers", @@ -6368,38 +6372,36 @@ tf_py_test( ":training", ":variables", ], - python_version = "PY3", - tags = [ - "noasan", # http://b/30782289 - "notsan", # http://b/30782289 - ], ) cuda_py_test( name = "session_manager_test", size = "medium", # TODO(irving): Can this be made small? srcs = ["training/session_manager_test.py"], - additional_deps = [ + grpc_enabled = True, + main = "training/session_manager_test.py", + python_version = "PY3", + deps = [ ":array_ops", - ":control_flow_ops", ":client", ":client_testlib", + ":control_flow_ops", ":errors", ":framework_for_generated_wrappers", ":platform", ":training", ":variables", ], - grpc_enabled = True, - main = "training/session_manager_test.py", - python_version = "PY3", ) tf_py_test( name = "supervisor_test", size = "small", srcs = ["training/supervisor_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":array_ops", ":checkpoint_management", ":client_testlib", @@ -6415,16 +6417,19 @@ tf_py_test( ":variables", "//tensorflow/core:protos_all_py", ], - grpc_enabled = True, - python_version = "PY3", - tags = ["no_windows"], ) tf_py_test( name = "basic_session_run_hooks_test", size = "medium", srcs = ["training/basic_session_run_hooks_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "no_pip", # Relies on contrib + "no_windows", + "notsan", # intermittent races on a few percent of runs + ], + deps = [ ":client", ":client_testlib", ":control_flow_ops", @@ -6440,19 +6445,21 @@ tf_py_test( ":variables", "//tensorflow/core:protos_all_py", ], - python_version = "PY3", - tags = [ - "no_pip", # Relies on contrib - "no_windows", - "notsan", # intermittent races on a few percent of runs - ], ) tf_py_test( name = "checkpoint_utils_test", size = "small", srcs = ["training/checkpoint_utils_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "manual", + "no_cuda_on_cpu_tap", + "no_oss", + "no_windows", + "notap", + ], + deps = [ ":client", ":client_testlib", ":framework_for_generated_wrappers", @@ -6465,21 +6472,14 @@ tf_py_test( ":variable_scope", ":variables", ], - python_version = "PY3", - tags = [ - "manual", - "no_cuda_on_cpu_tap", - "no_oss", - "no_windows", - "notap", - ], ) tf_py_test( name = "checkpoint_ops_test", size = "small", srcs = ["training/checkpoint_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":checkpoint_ops_gen", ":client", ":client_testlib", @@ -6493,14 +6493,14 @@ tf_py_test( ":variable_scope", ":variables", ], - python_version = "PY3", ) tf_py_test( name = "warm_starting_util_test", size = "medium", srcs = ["training/warm_starting_util_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":dtypes", @@ -6511,14 +6511,17 @@ tf_py_test( ":variables", "//third_party/py/numpy", ], - python_version = "PY3", ) tf_py_test( name = "monitored_session_test", size = "medium", srcs = ["training/monitored_session_test.py"], - additional_deps = [ + tags = [ + "no_pip", + "notsan", # b/67945581 + ], + deps = [ ":array_ops", ":checkpoint_management", ":client_testlib", @@ -6536,10 +6539,6 @@ tf_py_test( "//tensorflow/python/distribute:collective_all_reduce_strategy", "//tensorflow/python/distribute:distribute_coordinator", ], - tags = [ - "no_pip", - "notsan", # b/67945581 - ], ) py_library( @@ -6565,7 +6564,8 @@ tf_py_test( name = "training_util_test", size = "small", srcs = ["training/training_util_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":framework", ":platform", @@ -6573,14 +6573,14 @@ tf_py_test( ":training_util", ":variables", ], - python_version = "PY3", ) tf_py_test( name = "input_test", size = "medium", srcs = ["training/input_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":errors", @@ -6588,12 +6588,11 @@ tf_py_test( ":framework_for_generated_wrappers", ":math_ops", ":platform", + ":training", ":util", ":variables", - ":training", "//third_party/py/numpy", ], - python_version = "PY3", ) py_library( @@ -6660,20 +6659,20 @@ py_tests( "summary/summary_test.py", "summary/writer/writer_test.py", ], - additional_deps = [ + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", - ":framework_for_generated_wrappers", - ":variables", ":framework", + ":framework_for_generated_wrappers", ":framework_test_lib", ":platform", ":platform_test", ":summary", ":summary_ops_v2", + ":variables", "//tensorflow/core:protos_all_py", ], - python_version = "PY3", ) py_library( @@ -6755,7 +6754,9 @@ tf_py_test( name = "layers_base_test", size = "small", srcs = ["layers/base_test.py"], - additional_deps = [ + main = "layers/base_test.py", + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -6768,15 +6769,15 @@ tf_py_test( ":variable_scope", "//tensorflow/python/eager:context", ], - main = "layers/base_test.py", - python_version = "PY3", ) tf_py_test( name = "layers_core_test", size = "small", srcs = ["layers/core_test.py"], - additional_deps = [ + main = "layers/core_test.py", + python_version = "PY3", + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -6789,15 +6790,15 @@ tf_py_test( ":variables", "//third_party/py/numpy", ], - main = "layers/core_test.py", - python_version = "PY3", ) tf_py_test( name = "layers_convolutional_test", size = "small", srcs = ["layers/convolutional_test.py"], - additional_deps = [ + main = "layers/convolutional_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":framework_test_lib", @@ -6806,41 +6807,42 @@ tf_py_test( ":nn_ops", ":random_ops", ], - main = "layers/convolutional_test.py", - python_version = "PY3", ) tf_py_test( name = "layers_utils_test", size = "small", srcs = ["layers/utils_test.py"], - additional_deps = [ + main = "layers/utils_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":layers", ], - main = "layers/utils_test.py", - python_version = "PY3", ) tf_py_test( name = "layers_pooling_test", size = "small", srcs = ["layers/pooling_test.py"], - additional_deps = [ + main = "layers/pooling_test.py", + python_version = "PY3", + deps = [ ":client_testlib", ":framework_test_lib", ":layers", ":random_ops", ], - main = "layers/pooling_test.py", - python_version = "PY3", ) cuda_py_test( name = "layers_normalization_test", size = "medium", srcs = ["layers/normalization_test.py"], - additional_deps = [ + main = "layers/normalization_test.py", + python_version = "PY3", + shard_count = 10, + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", @@ -6851,9 +6853,6 @@ cuda_py_test( ":variables", "//third_party/py/numpy", ], - main = "layers/normalization_test.py", - python_version = "PY3", - shard_count = 10, ) # ----------------------------------------------------------------------------- @@ -6863,42 +6862,42 @@ tf_py_test( name = "dequantize_op_test", size = "small", srcs = ["ops/dequantize_op_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", "//third_party/py/numpy", ], - python_version = "PY3", - tags = ["no_windows"], ) tf_py_test( name = "quantized_ops_test", size = "small", srcs = ["ops/quantized_ops_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", "//third_party/py/numpy", ], - python_version = "PY3", - tags = ["no_windows"], ) tf_py_test( name = "quantized_conv_ops_test", size = "small", srcs = ["ops/quantized_conv_ops_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows"], + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":nn_ops", "//third_party/py/numpy", ], - python_version = "PY3", - tags = ["no_windows"], ) py_test( @@ -6923,7 +6922,10 @@ cuda_py_test( name = "accumulate_n_benchmark", size = "medium", srcs = ["ops/accumulate_n_benchmark.py"], - additional_deps = [ + main = "ops/accumulate_n_benchmark.py", + python_version = "PY3", + shard_count = 6, + deps = [ ":array_ops", ":client", ":client_testlib", @@ -6935,15 +6937,14 @@ cuda_py_test( ":state_ops", ":state_ops_gen", ], - main = "ops/accumulate_n_benchmark.py", - python_version = "PY3", - shard_count = 6, ) cuda_py_test( name = "batch_norm_benchmark", srcs = ["ops/batch_norm_benchmark.py"], - additional_deps = [ + main = "ops/batch_norm_benchmark.py", + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -6957,14 +6958,14 @@ cuda_py_test( ":random_ops", ":variables", ], - main = "ops/batch_norm_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "collective_ops_benchmark", srcs = ["ops/collective_ops_benchmark.py"], - additional_deps = [ + main = "ops/collective_ops_benchmark.py", + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -6974,14 +6975,14 @@ cuda_py_test( ":variables", "//tensorflow/core:protos_all_py", ], - main = "ops/collective_ops_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "concat_benchmark", srcs = ["ops/concat_benchmark.py"], - additional_deps = [ + main = "ops/concat_benchmark.py", + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -6992,29 +6993,29 @@ cuda_py_test( ":variables", "//tensorflow/core:protos_all_py", ], - main = "ops/concat_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "control_flow_ops_benchmark", srcs = ["ops/control_flow_ops_benchmark.py"], - additional_deps = [ + main = "ops/control_flow_ops_benchmark.py", + python_version = "PY3", + deps = [ ":client_testlib", ":constant_op", ":control_flow_ops", ":framework_ops", "//tensorflow/python/eager:function", ], - main = "ops/control_flow_ops_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "conv2d_benchmark", size = "large", srcs = ["ops/conv2d_benchmark.py"], - additional_deps = [ + main = "ops/conv2d_benchmark.py", + python_version = "PY3", + deps = [ ":client", ":client_testlib", ":control_flow_ops", @@ -7024,17 +7025,17 @@ cuda_py_test( ":platform_benchmark", ":random_ops", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - main = "ops/conv2d_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "split_benchmark", srcs = ["ops/split_benchmark.py"], - additional_deps = [ + main = "ops/split_benchmark.py", + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -7043,18 +7044,18 @@ cuda_py_test( ":platform", ":platform_benchmark", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - main = "ops/split_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "transpose_benchmark", size = "medium", srcs = ["ops/transpose_benchmark.py"], - additional_deps = [ + main = "ops/transpose_benchmark.py", + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -7063,20 +7064,18 @@ cuda_py_test( ":platform", ":platform_benchmark", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", ], - main = "ops/transpose_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "matmul_benchmark", size = "medium", srcs = ["ops/matmul_benchmark.py"], - additional_deps = [":matmul_benchmark_main_lib"], main = "ops/matmul_benchmark.py", python_version = "PY3", + deps = [":matmul_benchmark_main_lib"], ) py_library( @@ -7102,7 +7101,10 @@ py_library( cuda_py_test( name = "session_benchmark", srcs = ["client/session_benchmark.py"], - additional_deps = [ + grpc_enabled = True, + main = "client/session_benchmark.py", + python_version = "PY3", + deps = [ ":array_ops", ":client", ":client_testlib", @@ -7112,23 +7114,20 @@ cuda_py_test( ":variables", "//third_party/py/numpy", ], - grpc_enabled = True, - main = "client/session_benchmark.py", - python_version = "PY3", ) cuda_py_test( name = "nn_grad_test", size = "small", srcs = ["ops/nn_grad_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":nn_grad", ":nn_ops", "//third_party/py/numpy", ], - python_version = "PY3", ) py_library( @@ -7150,18 +7149,18 @@ tf_py_test( srcs = [ "grappler/item_test.py", ], - additional_deps = [ + python_version = "PY3", + tags = [ + "grappler", + "no_pip", # tf_optimizer is not available in pip. + ], + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":math_ops", ":tf_item", "//tensorflow/core:protos_all_py", ], - python_version = "PY3", - tags = [ - "grappler", - "no_pip", # tf_optimizer is not available in pip. - ], ) tf_py_test( @@ -7170,7 +7169,12 @@ tf_py_test( srcs = [ "grappler/datasets_test.py", ], - additional_deps = [ + python_version = "PY3", + tags = [ + "grappler", + "no_pip", # tf_optimizer is not available in pip. + ], + deps = [ ":array_ops", ":client_testlib", ":framework_combinations", @@ -7179,11 +7183,6 @@ tf_py_test( "//tensorflow/core:protos_all_py", "//tensorflow/python/data", ], - python_version = "PY3", - tags = [ - "grappler", - "no_pip", # tf_optimizer is not available in pip. - ], ) py_library( @@ -7205,13 +7204,6 @@ cuda_py_test( srcs = [ "grappler/cluster_test.py", ], - additional_deps = [ - ":client_testlib", - ":framework_for_generated_wrappers", - ":tf_cluster", - ":tf_item", - "//tensorflow/core:protos_all_py", - ], python_version = "PY3", shard_count = 10, tags = [ @@ -7221,6 +7213,13 @@ cuda_py_test( ], # This test will not run on XLA because it primarily tests the TF Classic flow. xla_enable_strict_auto_jit = False, + deps = [ + ":client_testlib", + ":framework_for_generated_wrappers", + ":tf_cluster", + ":tf_item", + "//tensorflow/core:protos_all_py", + ], ) py_library( @@ -7242,19 +7241,19 @@ tf_py_test( srcs = [ "grappler/tf_optimizer_test.py", ], - additional_deps = [ + python_version = "PY3", + tags = [ + "grappler", + "no_pip", # tf_optimizer is not available in pip. + ], + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":math_ops", ":tf_item", ":tf_optimizer", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - ], - python_version = "PY3", - tags = [ - "grappler", - "no_pip", # tf_optimizer is not available in pip. + "//third_party/py/numpy", ], ) @@ -7275,16 +7274,16 @@ tf_py_test( name = "graph_placer_test", size = "large", srcs = ["grappler/graph_placer_test.py"], - additional_deps = [ - ":client_testlib", - ":graph_placer", - "//tensorflow/python:math_ops", - ], python_version = "PY3", tags = [ "grappler", "no_pip", # graph_placer is not available in pip. ], + deps = [ + ":client_testlib", + ":graph_placer", + "//tensorflow/python:math_ops", + ], ) tf_py_test( @@ -7293,7 +7292,11 @@ tf_py_test( srcs = [ "grappler/memory_optimizer_test.py", ], - additional_deps = [ + python_version = "PY3", + tags = [ + "grappler", + ], + deps = [ ":client_testlib", ":framework_for_generated_wrappers", ":math_ops", @@ -7304,12 +7307,8 @@ tf_py_test( ":training", ":variable_scope", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - ], - python_version = "PY3", - tags = [ - "grappler", + "//third_party/py/numpy", ], ) @@ -7319,22 +7318,22 @@ cuda_py_test( srcs = [ "grappler/constant_folding_test.py", ], - additional_deps = [ - ":client_testlib", - ":framework_for_generated_wrappers", - ":array_ops", - ":control_flow_ops", - ":dtypes", - ":functional_ops", - ":math_ops", - ":ops", - "//third_party/py/numpy", - "//tensorflow/core:protos_all_py", - ], python_version = "PY3", tags = [ "grappler", ], + deps = [ + ":array_ops", + ":client_testlib", + ":control_flow_ops", + ":dtypes", + ":framework_for_generated_wrappers", + ":functional_ops", + ":math_ops", + ":ops", + "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", + ], ) # TODO(b/131764887) Remove once LayoutOptimizer is swapped out with GenericLayoutOptimizer. @@ -7345,7 +7344,7 @@ cuda_py_test( # srcs = [ # "grappler/layout_optimizer_test.py", # ], -# additional_deps = [ +# deps = [ # ":client_testlib", # ":framework_for_generated_wrappers", # ":array_ops", @@ -7403,7 +7402,14 @@ tf_py_test( name = "cost_analyzer_test", size = "small", srcs = ["grappler/cost_analyzer_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "grappler", + "no_cuda_on_cpu_tap", + "no_pip", + "nomac", + ], + deps = [ ":array_ops", ":client_testlib", ":cost_analyzer", @@ -7415,15 +7421,8 @@ tf_py_test( ":state_ops", ":training", ":variables", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - ], - python_version = "PY3", - tags = [ - "grappler", - "no_cuda_on_cpu_tap", - "no_pip", - "nomac", + "//third_party/py/numpy", ], ) @@ -7440,19 +7439,19 @@ tf_py_test( name = "model_analyzer_test", size = "small", srcs = ["grappler/model_analyzer_test.py"], - additional_deps = [ + tags = [ + "grappler", + "no_pip", + ], + deps = [ ":array_ops", ":client_testlib", ":framework_for_generated_wrappers", ":math_ops", ":model_analyzer", ":state_ops", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - ], - tags = [ - "grappler", - "no_pip", + "//third_party/py/numpy", ], ) @@ -7462,21 +7461,6 @@ cuda_py_test( srcs = [ "grappler/auto_mixed_precision_test.py", ], - additional_deps = [ - ":client_testlib", - ":framework_for_generated_wrappers", - ":array_ops", - ":constant_op", - ":dtypes", - ":math_ops", - ":nn", - ":ops", - ":random_ops", - ":control_flow_ops", - ":training", - "//third_party/py/numpy", - "//tensorflow/core:protos_all_py", - ], python_version = "PY3", tags = [ "grappler", @@ -7484,6 +7468,21 @@ cuda_py_test( ], # This test analyzes the graph, but XLA changes the names of nodes. xla_enable_strict_auto_jit = False, + deps = [ + ":array_ops", + ":client_testlib", + ":constant_op", + ":control_flow_ops", + ":dtypes", + ":framework_for_generated_wrappers", + ":math_ops", + ":nn", + ":ops", + ":random_ops", + ":training", + "//tensorflow/core:protos_all_py", + "//third_party/py/numpy", + ], ) tf_gen_op_wrapper_private_py( @@ -7514,13 +7513,6 @@ cuda_py_test( name = "nccl_ops_test", size = "small", srcs = ["ops/nccl_ops_test.py"], - additional_deps = [ - ":nccl_ops", - ":array_ops", - ":client_testlib", - ":framework_test_lib", - ":platform_test", - ], python_version = "PY3", # Disabled on jenkins until errors finding nvmlShutdown are found. tags = [ @@ -7530,6 +7522,13 @@ cuda_py_test( "noguitar", "notap", ], + deps = [ + ":array_ops", + ":client_testlib", + ":framework_test_lib", + ":nccl_ops", + ":platform_test", + ], ) tf_gen_op_wrapper_private_py( @@ -7607,8 +7606,8 @@ py_test( cuda_py_test( name = "raw_ops_test", srcs = ["ops/raw_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ "//tensorflow/python:client_testlib", ], - python_version = "PY3", ) diff --git a/tensorflow/python/autograph/converters/call_trees.py b/tensorflow/python/autograph/converters/call_trees.py index abf3baa5d74..763a6563574 100644 --- a/tensorflow/python/autograph/converters/call_trees.py +++ b/tensorflow/python/autograph/converters/call_trees.py @@ -191,7 +191,7 @@ class CallTreeTransformer(converter.Base): if not set_trace_warned: # TODO(mdan): Update and shorten once available on tensorflow.org. ag_logging.warn( - 'Detected `pdb.set_trace()` in converted code. The code' + 'Detected `pdb.set_trace()` in user code. The code' ' generated by AutoGraph is not optimized for step-by-step' ' debugging. See https://github.com/tensorflow/tensorflow/' 'blob/master/tensorflow/python/autograph/g3doc/reference/' diff --git a/tensorflow/python/autograph/converters/directives.py b/tensorflow/python/autograph/converters/directives.py index fe1c75a5864..7a98a43b5d9 100644 --- a/tensorflow/python/autograph/converters/directives.py +++ b/tensorflow/python/autograph/converters/directives.py @@ -40,11 +40,18 @@ from tensorflow.python.autograph.lang import directives from tensorflow.python.autograph.pyct import anno from tensorflow.python.util import tf_inspect -ENCLOSING_LOOP = 'enclosing_loop' + STATIC_VALUE = 'static_value' """Used for AST annotations, see visit_Name.""" +class _LoopScope(object): + + def __init__(self): + self.ast_node = None + self.statements_visited = 0 + + def _map_args(call_node, function): """Maps AST call nodes to the actual function's arguments. @@ -94,10 +101,14 @@ class DirectivesTransformer(converter.Base): return call_node def _process_statement_directive(self, call_node, directive): - if self.local_scope_level < 2: + if self.state[_LoopScope].statements_visited > 1: + raise ValueError( + '"%s" must be the first statement in the loop block' % ( + directive.__name__)) + if self.state[_LoopScope].level < 2: raise ValueError( '"%s" must be used inside a statement' % directive.__name__) - target = self.get_local(ENCLOSING_LOOP) + target = self.state[_LoopScope].ast_node node_anno = anno.getanno(target, anno.Basic.DIRECTIVES, {}) node_anno[directive] = _map_args(call_node, directive) anno.setanno(target, anno.Basic.DIRECTIVES, node_anno) @@ -120,7 +131,16 @@ class DirectivesTransformer(converter.Base): anno.setanno(node, STATIC_VALUE, getattr(parent_val, node.attr)) return node + def visit_Assign(self, node): + self.state[_LoopScope].statements_visited += 1 + return self.generic_visit(node) + + def visit_AugAssign(self, node): + self.state[_LoopScope].statements_visited += 1 + return self.generic_visit(node) + def visit_Expr(self, node): + self.state[_LoopScope].statements_visited += 1 node = self.generic_visit(node) if isinstance(node.value, gast.Call): call_node = node.value @@ -141,10 +161,10 @@ class DirectivesTransformer(converter.Base): # That means that if we ever have a directive that affects things other than # loops, we'll need support for parallel scopes, or have multiple converters. def _track_and_visit_loop(self, node): - self.enter_local_scope() - self.set_local(ENCLOSING_LOOP, node) + self.state[_LoopScope].enter() + self.state[_LoopScope].ast_node = node node = self.generic_visit(node) - self.exit_local_scope() + self.state[_LoopScope].exit() return node def visit_While(self, node): diff --git a/tensorflow/python/autograph/converters/directives_test.py b/tensorflow/python/autograph/converters/directives_test.py index 545094521ec..27a52971afc 100644 --- a/tensorflow/python/autograph/converters/directives_test.py +++ b/tensorflow/python/autograph/converters/directives_test.py @@ -73,7 +73,7 @@ class DirectivesTest(converter_testing.TestCase): self.assertEqual(d['back_prop'].id, 'a') self.assertNotIn('swap_memory', d) - def test_loop_target_with_no_loop(self): + def test_loop_target_no_loop(self): def test_fn(): directives.set_loop_options() @@ -82,6 +82,18 @@ class DirectivesTest(converter_testing.TestCase): with self.assertRaisesRegexp(ValueError, 'must be used inside a statement'): node = directives_converter.transform(node, ctx) + def test_loop_target_not_first(self): + + def test_fn(): + a = 1 + while True: + a = 2 + directives.set_loop_options(parallel_iterations=10, back_prop=a) + + node, ctx = self.prepare(test_fn, {'directives': directives}) + with self.assertRaisesRegexp(ValueError, 'must be the first statement'): + node = directives_converter.transform(node, ctx) + def test_invalid_default(self): def invalid_directive(valid_arg, invalid_default=object()): diff --git a/tensorflow/python/autograph/g3doc/reference/error_handling.md b/tensorflow/python/autograph/g3doc/reference/error_handling.md index ce3a64f8f28..6b1808404aa 100644 --- a/tensorflow/python/autograph/g3doc/reference/error_handling.md +++ b/tensorflow/python/autograph/g3doc/reference/error_handling.md @@ -38,6 +38,10 @@ Among the distinctive features of the re-raised exception: the `@tf.function` * the references corresponding to converted code are marked with an asterisk (`*`) + * the references corresponding to code which AutoGraph reached, but decided not + to convert, are marked with a double asterisk (`**`) + * the references corresponding to code that AutoGraph didn't reach at all have + no marking For example, the code below triggers an exception in the Python runtime, at graph construction time: @@ -62,7 +66,7 @@ TypeError: in converted code: :8 f * tf.constant(1) + tf.constant(1.0) - tensorflow/python/ops/math_ops.py:900 binary_op_wrapper + tensorflow/python/ops/math_ops.py:900 binary_op_wrapper ** return func(x, y, name=name) ... more TensorFlow internal frames ... diff --git a/tensorflow/python/autograph/impl/BUILD b/tensorflow/python/autograph/impl/BUILD index 324e2118cb4..05b9b7cd33d 100644 --- a/tensorflow/python/autograph/impl/BUILD +++ b/tensorflow/python/autograph/impl/BUILD @@ -40,12 +40,12 @@ py_library( tf_py_test( name = "api_test", srcs = ["api_test.py"], - additional_deps = [ + deps = [ ":impl", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python/autograph/core:test_lib", "//tensorflow/python/autograph/utils", + "//third_party/py/numpy", ], ) @@ -70,9 +70,9 @@ py_test( tf_py_test( name = "conversion_test", srcs = ["conversion_test.py"], - additional_deps = [ + deps = [ ":impl", - "@gast_archive//:gast", "//tensorflow/python:client_testlib", + "@gast_archive//:gast", ], ) diff --git a/tensorflow/python/autograph/impl/api.py b/tensorflow/python/autograph/impl/api.py index 17104c10c1b..dbcdf4333c6 100644 --- a/tensorflow/python/autograph/impl/api.py +++ b/tensorflow/python/autograph/impl/api.py @@ -312,23 +312,24 @@ def do_not_convert(func=None): return autograph_artifact(wrapper) -def _attach_metadata(e, f, converted): +def _attach_metadata(e, f): """Augments an error with the metadata necessary for rewrite.""" if hasattr(e, 'ag_pass_through'): return metadata = getattr(e, 'ag_error_metadata', None) - source_map = f.ag_source_map if converted else {} + source_map = f.ag_source_map if metadata is None: - logging.log( - 1, 'Caught error in %s (converted=%s)', f, converted, exc_info=True) + logging.log(1, 'Caught error in user callable %s', f, exc_info=True) message = '{}: {}'.format(e.__class__.__name__, e) else: message = None cause_tb = traceback.extract_tb(sys.exc_info()[2])[1:] - e.ag_error_metadata = _ErrorMetadata(cause_tb, metadata, message, source_map) + + e.ag_error_metadata = _ErrorMetadata( + cause_tb, metadata, message, source_map, __file__) def _call_unconverted(f, args, kwargs, options, update_cache=True): @@ -339,14 +340,10 @@ def _call_unconverted(f, args, kwargs, options, update_cache=True): if inspect_utils.istfmethodtarget(f): return f.__self__.call(args, kwargs) - try: - if kwargs is not None: - return f(*args, **kwargs) - else: - return f(*args) - except Exception as e: # pylint:disable=broad-except - _attach_metadata(e, f, False) - raise + if kwargs is not None: + return f(*args, **kwargs) + else: + return f(*args) def _is_known_loaded_type(f, module_name, entity_name): @@ -415,7 +412,7 @@ def converted_call(f, options = caller_fn_scope.callopts if conversion.is_in_whitelist_cache(f, options): - logging.log(2, 'Whitelisted %s: from cache') + logging.log(2, 'Whitelisted %s: from cache', f) return _call_unconverted(f, args, kwargs, options, False) if ag_ctx.control_status_ctx().status == ag_ctx.Status.DISABLED: @@ -584,7 +581,7 @@ def converted_call(f, else: result = converted_f(*effective_args) except Exception as e: - _attach_metadata(e, converted_f, True) + _attach_metadata(e, converted_f) raise return result diff --git a/tensorflow/python/autograph/impl/conversion.py b/tensorflow/python/autograph/impl/conversion.py index c256b4e8e65..78a8e1b392b 100644 --- a/tensorflow/python/autograph/impl/conversion.py +++ b/tensorflow/python/autograph/impl/conversion.py @@ -496,7 +496,7 @@ def convert_entity_to_ast(o, program_ctx): keyed by their symbol name. Raises: - ValueError: if the entity type is not supported. + NotImplementedError: if entity is of a type that is not yet supported. """ logging.log(1, 'Converting %s', o) @@ -513,7 +513,7 @@ def convert_entity_to_ast(o, program_ctx): 'cannot convert entity "{}": object conversion is not yet' ' supported.'.format(o)) else: - raise ValueError( + raise NotImplementedError( 'Entity "%s" has unsupported type "%s". Only functions and classes are ' 'supported for now.' % (o, type(o))) diff --git a/tensorflow/python/autograph/pyct/error_utils.py b/tensorflow/python/autograph/pyct/error_utils.py index 34c3a3954e5..3f7ace067fe 100644 --- a/tensorflow/python/autograph/pyct/error_utils.py +++ b/tensorflow/python/autograph/pyct/error_utils.py @@ -26,11 +26,13 @@ from tensorflow.python.autograph.pyct import origin_info class FrameInfo( collections.namedtuple( 'FrameInfo', - ('filename', 'lineno', 'function_name', 'code', 'converted'))): - pass + ('filename', 'lineno', 'function_name', 'code', 'is_converted', + 'is_whitelisted'))): + + __slots__ = () -def _stack_trace_inside_mapped_code(tb, source_map): +def _stack_trace_inside_mapped_code(tb, source_map, converter_filename): """Summarizes inner traceback frames up to the call to a given function. This functions locates the innermost (i.e. most recent) frame that corresponds @@ -67,10 +69,14 @@ def _stack_trace_inside_mapped_code(tb, source_map): raise ... Args: - tb: List[Tuple], the traceback corresponding to an error; typically, - the output of traceback.extract_tb. + tb: traceback.FrameSummary, The traceback corresponding to an error. + Typically, the output of traceback.Summary.extract(capture_locals=True). source_map: Dict[LineLocation, OriginInfo], a source map as created by origin_info.create_source_map. + converter_filename: str, the file path of the converted module. Call frames + corresponding to this module are elided and their preceding frames are + marked as whitelisted. Note that frames enclosing converted code are + dropped using a different mechanism. Returns: List[FrameInfo] @@ -81,21 +87,37 @@ def _stack_trace_inside_mapped_code(tb, source_map): loc = origin_info.LineLocation(filename=filename, lineno=line_number) if loc in source_map: origin = source_map[loc] - origin_frame_info = FrameInfo( + fi = FrameInfo( filename=origin.loc.filename, lineno=origin.loc.lineno, function_name=origin.function_name, code=origin.source_code_line, - converted=True) - result_frames.append(origin_frame_info) + is_converted=True, + is_whitelisted=False) + result_frames.append(fi) break + if filename == converter_filename: + if result_frames: + prev = result_frames[-1] + assert not prev.is_converted # See the if above. + fi = FrameInfo( + filename=prev.filename, + lineno=prev.lineno, + function_name=prev.function_name, + code=prev.code, + is_converted=False, + is_whitelisted=True) + result_frames[-1] = fi + continue + fi = FrameInfo( filename=filename, lineno=line_number, function_name=function_name, code=text, - converted=False) + is_converted=False, + is_whitelisted=False) result_frames.append(fi) return tuple(result_frames) @@ -129,15 +151,19 @@ MultilineMessageKeyError.__name__ = KeyError.__name__ class ErrorMetadataBase(object): - """Container objects attached to exceptions in converted code. + """Container objects attached to exceptions raised in user code. This metadata allows re-raising exceptions that occur in generated code, with a custom error message that includes a stack trace relative to user-readable code from which the generated code originated. """ - def __init__(self, callsite_tb, cause_metadata, cause_message, source_map): - translated_stack = _stack_trace_inside_mapped_code(callsite_tb, source_map) + __slots__ = ('translated_stack', 'cause_message') + + def __init__(self, callsite_tb, cause_metadata, cause_message, source_map, + converter_filename): + translated_stack = _stack_trace_inside_mapped_code( + callsite_tb, source_map, converter_filename) if cause_metadata is None: self.translated_stack = translated_stack @@ -152,16 +178,19 @@ class ErrorMetadataBase(object): """Returns the message for the underlying exception.""" lines = [] - lines.append('in converted code:') + lines.append('in user code:') lines.append('') for frame_info in reversed(self.translated_stack): - lines.append(' {}:{} {}{}'.format( - frame_info.filename, - frame_info.lineno, - frame_info.function_name, - ' *' if frame_info.converted else '', - )) + formatted_line = ' {}:{} {}'.format(frame_info.filename, + frame_info.lineno, + frame_info.function_name) + if frame_info.is_converted: + formatted_line += ' *' + elif frame_info.is_whitelisted: + formatted_line += ' **' + lines.append(formatted_line) + if frame_info.code is None: code_snippet = '' else: diff --git a/tensorflow/python/autograph/pyct/error_utils_test.py b/tensorflow/python/autograph/pyct/error_utils_test.py index 9fdbc55579e..601a2c59796 100644 --- a/tensorflow/python/autograph/pyct/error_utils_test.py +++ b/tensorflow/python/autograph/pyct/error_utils_test.py @@ -21,6 +21,7 @@ from __future__ import print_function import re from tensorflow.python.autograph.pyct import error_utils +from tensorflow.python.autograph.pyct import origin_info from tensorflow.python.platform import test @@ -35,7 +36,8 @@ class ErrorMetadataBaseTest(test.TestCase): callsite_tb=(), cause_metadata=None, cause_message='test message', - source_map={}) + source_map={}, + converter_filename=None) exc = em.create_exception(CustomError()) self.assertIsInstance(exc, CustomError) self.assertIn('test message', str(exc)) @@ -51,11 +53,12 @@ class ErrorMetadataBaseTest(test.TestCase): callsite_tb=(), cause_metadata=None, cause_message='test message', - source_map={}) + source_map={}, + converter_filename=None) exc = em.create_exception(CustomError()) self.assertIsNone(exc) - def test_get_message_when_frame_info_code_is_none(self): + def test_get_message_no_code(self): callsite_tb = [ ('/path/one.py', 11, 'test_fn_1', None), ('/path/two.py', 171, 'test_fn_2', 'test code'), @@ -65,11 +68,57 @@ class ErrorMetadataBaseTest(test.TestCase): callsite_tb=callsite_tb, cause_metadata=None, cause_message=cause_message, - source_map={}) + source_map={}, + converter_filename=None) self.assertRegex( em.get_message(), re.compile('test_fn_1.*test_fn_2.*Test message', re.DOTALL)) + def test_get_message_converted_code(self): + callsite_tb = [ + ('/path/one.py', 11, 'test_fn_1', 'test code 1'), + ('/path/two.py', 171, 'test_fn_2', 'test code 2'), + ('/path/three.py', 171, 'test_fn_3', 'test code 3'), + ] + cause_message = 'Test message' + em = error_utils.ErrorMetadataBase( + callsite_tb=callsite_tb, + cause_metadata=None, + cause_message=cause_message, + source_map={ + origin_info.LineLocation(filename='/path/two.py', lineno=171): + origin_info.OriginInfo( + loc=origin_info.LineLocation( + filename='/path/other_two.py', lineno=13), + function_name='converted_fn', + source_code_line='converted test code', + comment=None) + }, + converter_filename=None) + result = em.get_message() + self.assertRegex( + result, + re.compile(r'converted_fn \*.*test_fn_3.*Test message', re.DOTALL)) + self.assertNotRegex(result, re.compile('test_fn_1')) + + def test_get_message_call_overload(self): + + callsite_tb = [ + ('/path/one.py', 11, 'test_fn_1', 'test code 1'), + ('/path/two.py', 0, 'test_fn_2', 'test code 2'), + ('/path/three.py', 0, 'test_fn_3', 'test code 3'), + ] + cause_message = 'Test message' + em = error_utils.ErrorMetadataBase( + callsite_tb=callsite_tb, + cause_metadata=None, + cause_message=cause_message, + source_map={}, + converter_filename='/path/two.py') + self.assertRegex( + em.get_message(), + re.compile(r'test_fn_1.*test_fn_3 \*\*.*Test message', re.DOTALL)) + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/compat/BUILD b/tensorflow/python/compat/BUILD index 78f14631055..3e211777c05 100644 --- a/tensorflow/python/compat/BUILD +++ b/tensorflow/python/compat/BUILD @@ -32,20 +32,20 @@ tf_py_test( name = "compat_test", size = "small", srcs = ["compat_test.py"], - additional_deps = [ + tags = ["nofwdcompat"], + deps = [ ":compat", "//tensorflow/python:client_testlib", ], - tags = ["nofwdcompat"], ) tf_py_test( name = "disable_v2_behavior_test", size = "small", srcs = ["disable_v2_behavior_test.py"], - additional_deps = [ + deps = [ ":v2_compat", - "//tensorflow/python:framework", "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", ], ) diff --git a/tensorflow/python/compat/compat.py b/tensorflow/python/compat/compat.py index bd7cb81ff8f..a800885f5c6 100644 --- a/tensorflow/python/compat/compat.py +++ b/tensorflow/python/compat/compat.py @@ -31,7 +31,7 @@ from tensorflow.python.util.tf_export import tf_export # This value changes every day with an automatic CL. It can be modified in code # via `forward_compatibility_horizon()` or with the environment variable # TF_FORWARD_COMPATIBILITY_DELTA_DAYS, which is added to the compatibility date. -_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 12, 9) +_FORWARD_COMPATIBILITY_HORIZON = datetime.date(2019, 12, 17) _FORWARD_COMPATIBILITY_DELTA_DAYS_VAR_NAME = "TF_FORWARD_COMPATIBILITY_DELTA_DAYS" _FORWARD_COMPATIBILITY_DATE_NUMBER = None diff --git a/tensorflow/python/compiler/tensorrt/BUILD b/tensorflow/python/compiler/tensorrt/BUILD index 0e4c43f82e2..96867070cbc 100644 --- a/tensorflow/python/compiler/tensorrt/BUILD +++ b/tensorflow/python/compiler/tensorrt/BUILD @@ -79,9 +79,22 @@ py_library( cuda_py_test( name = "trt_convert_test", srcs = ["trt_convert_test.py"], - additional_deps = [ + data = [ + "test/testdata/tftrt_2.0_saved_model/saved_model.pb", + "test/testdata/tftrt_2.0_saved_model/variables/variables.data-00000-of-00002", + "test/testdata/tftrt_2.0_saved_model/variables/variables.data-00001-of-00002", + "test/testdata/tftrt_2.0_saved_model/variables/variables.index", + ], + python_version = "PY3", + tags = [ + "no_cuda_on_cpu_tap", + "no_pip", + "no_rocm", + "no_windows", + "nomac", + ], + deps = [ ":trt_convert_py", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_test_lib", "//tensorflow/python:graph_util", @@ -93,12 +106,7 @@ cuda_py_test( "//tensorflow/python/saved_model:utils", "//tensorflow/python/tools:freeze_graph_lib", "//tensorflow/python/tools:saved_model_utils", - ], - tags = [ - "no_cuda_on_cpu_tap", - "no_rocm", - "no_windows", - "nomac", + "@absl_py//absl/testing:parameterized", ], ) @@ -126,17 +134,18 @@ cuda_py_tests( "test/vgg_block_nchw_test.py", "test/vgg_block_test.py", ], - additional_deps = [ - ":tf_trt_integration_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_test_lib", - ], + python_version = "PY3", tags = [ "no_cuda_on_cpu_tap", "no_rocm", "no_windows", "nomac", ], + deps = [ + ":tf_trt_integration_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_test_lib", + ], ) cuda_py_tests( @@ -145,34 +154,29 @@ cuda_py_tests( "test/biasadd_matmul_test.py", "test/concatenation_test.py", ], - additional_deps = [ - ":tf_trt_integration_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_test_lib", - ], + python_version = "PY3", tags = [ "no_rocm", "no_windows", "nomac", "notap", # b/140261407 ], + deps = [ + ":tf_trt_integration_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_test_lib", + ], ) cuda_py_test( name = "quantization_mnist_test", srcs = ["test/quantization_mnist_test.py"], - additional_deps = [ - ":tf_trt_integration_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python/keras:keras", - "//tensorflow/python/estimator:estimator", - ], data = [ - "test/testdata/checkpoint", - "test/testdata/model.ckpt-46900.data-00000-of-00001", - "test/testdata/model.ckpt-46900.index", + "test/testdata/mnist/checkpoint", + "test/testdata/mnist/model.ckpt-46900.data-00000-of-00001", + "test/testdata/mnist/model.ckpt-46900.index", ], + python_version = "PY3", tags = [ "no_cuda_on_cpu_tap", "no_oss", # TODO(b/125290478): allow running in at least some OSS configurations. @@ -182,4 +186,11 @@ cuda_py_test( "no_windows", "nomac", ], + deps = [ + ":tf_trt_integration_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python/estimator", + "//tensorflow/python/keras", + ], ) diff --git a/tensorflow/python/compiler/tensorrt/test/base_test.py b/tensorflow/python/compiler/tensorrt/test/base_test.py index 3bdb409393d..8622fd13147 100644 --- a/tensorflow/python/compiler/tensorrt/test/base_test.py +++ b/tensorflow/python/compiler/tensorrt/test/base_test.py @@ -119,12 +119,16 @@ class SimpleMultiEnginesTest(trt_test.TfTrtIntegrationTestBase): def GetConversionParams(self, run_params): """Return a ConversionParams for test.""" - return super( - SimpleMultiEnginesTest, self - ).GetConversionParams(run_params)._replace( - # Disable layout optimizer, since it'll add Transpose(Const, Const) to - # the graph and breaks the conversion check. - rewriter_config_template=trt_test.OptimizerDisabledRewriterConfig()) + conversion_params = super(SimpleMultiEnginesTest, + self).GetConversionParams(run_params) + rewrite_config_with_trt = self.GetTrtRewriterConfig( + run_params=run_params, + conversion_params=conversion_params, + # Disable layout optimizer, since it will convert BiasAdd with NHWC + # format to NCHW format under four dimentional input. + disable_non_trt_optimizers=True) + return conversion_params._replace( + rewriter_config_template=rewrite_config_with_trt) class SimpleMultiEnginesTest2(trt_test.TfTrtIntegrationTestBase): diff --git a/tensorflow/python/compiler/tensorrt/test/biasadd_matmul_test.py b/tensorflow/python/compiler/tensorrt/test/biasadd_matmul_test.py index 680a06988ed..5e35711fb2a 100644 --- a/tensorflow/python/compiler/tensorrt/test/biasadd_matmul_test.py +++ b/tensorflow/python/compiler/tensorrt/test/biasadd_matmul_test.py @@ -109,12 +109,15 @@ class BiasaddMatMulTest(trt_test.TfTrtIntegrationTestBase): """Return a ConversionParams for test.""" conversion_params = super(BiasaddMatMulTest, self).GetConversionParams(run_params) - return conversion_params._replace( - max_batch_size=4, - maximum_cached_engines=1, + conversion_params._replace(max_batch_size=4, maximum_cached_engines=1) + rewrite_config_with_trt = self.GetTrtRewriterConfig( + run_params=run_params, + conversion_params=conversion_params, # Disable layout optimizer, since it will convert BiasAdd with NHWC # format to NCHW format under four dimentional input. - rewriter_config_template=trt_test.OptimizerDisabledRewriterConfig()) + disable_non_trt_optimizers=True) + return conversion_params._replace( + rewriter_config_template=rewrite_config_with_trt) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/python/compiler/tensorrt/test/dynamic_input_shapes_test.py b/tensorflow/python/compiler/tensorrt/test/dynamic_input_shapes_test.py index 6df0ab48844..2a291a62c37 100644 --- a/tensorflow/python/compiler/tensorrt/test/dynamic_input_shapes_test.py +++ b/tensorflow/python/compiler/tensorrt/test/dynamic_input_shapes_test.py @@ -84,11 +84,15 @@ class DynamicInputShapesTest(trt_test.TfTrtIntegrationTestBase): """Return a ConversionParams for test.""" conversion_params = super(DynamicInputShapesTest, self).GetConversionParams(run_params) - return conversion_params._replace( - maximum_cached_engines=10, + conversion_params._replace(maximum_cached_engines=10) + rewrite_config_with_trt = self.GetTrtRewriterConfig( + run_params=run_params, + conversion_params=conversion_params, # Disable layout optimizer, since it will convert BiasAdd with NHWC # format to NCHW format under four dimentional input. - rewriter_config_template=trt_test.OptimizerDisabledRewriterConfig()) + disable_non_trt_optimizers=True) + return conversion_params._replace( + rewriter_config_template=rewrite_config_with_trt) def ExpectedEnginesToBuild(self, run_params): return ["TRTEngineOp_0"] diff --git a/tensorflow/python/compiler/tensorrt/test/int32_test.py b/tensorflow/python/compiler/tensorrt/test/int32_test.py index 63a72288d36..ba50f0c81fa 100644 --- a/tensorflow/python/compiler/tensorrt/test/int32_test.py +++ b/tensorflow/python/compiler/tensorrt/test/int32_test.py @@ -50,12 +50,15 @@ class ExcludeUnsupportedInt32Test(trt_test.TfTrtIntegrationTestBase): """Return a ConversionParams for test.""" conversion_params = super(ExcludeUnsupportedInt32Test, self).GetConversionParams(run_params) - return conversion_params._replace( - max_batch_size=100, - maximum_cached_engines=1, + conversion_params._replace(max_batch_size=100, maximum_cached_engines=1) + rewrite_config_with_trt = self.GetTrtRewriterConfig( + run_params=run_params, + conversion_params=conversion_params, # Disable layout optimizer, since it will convert BiasAdd with NHWC # format to NCHW format under four dimentional input. - rewriter_config_template=trt_test.OptimizerDisabledRewriterConfig()) + disable_non_trt_optimizers=True) + return conversion_params._replace( + rewriter_config_template=rewrite_config_with_trt) def ExpectedEnginesToBuild(self, run_params): """Return the expected engines to build.""" diff --git a/tensorflow/python/compiler/tensorrt/test/quantization_mnist_test.py b/tensorflow/python/compiler/tensorrt/test/quantization_mnist_test.py index f5be3850002..83a3ddc8ef0 100644 --- a/tensorflow/python/compiler/tensorrt/test/quantization_mnist_test.py +++ b/tensorflow/python/compiler/tensorrt/test/quantization_mnist_test.py @@ -265,7 +265,8 @@ class QuantizationAwareTrainingMNISTTest(test_util.TensorFlowTestCase): def testEval(self): if not is_tensorrt_enabled(): return - model_dir = test.test_src_dir_path('python/compiler/tensorrt/test/testdata') + model_dir = test.test_src_dir_path( + 'python/compiler/tensorrt/test/testdata/mnist') accuracy_tf_native = self._Run( is_training=False, diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/gen_tftrt_model.py b/tensorflow/python/compiler/tensorrt/test/testdata/gen_tftrt_model.py new file mode 100644 index 00000000000..46c64a53d6a --- /dev/null +++ b/tensorflow/python/compiler/tensorrt/test/testdata/gen_tftrt_model.py @@ -0,0 +1,137 @@ +# Copyright 2019 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. +# ============================================================================= +"""Saves a SavedModel after TensorRT conversion. + + The saved model is loaded and executed by tests to ensure backward + compatibility across TF versions. + The script may not work in TF1.x. + + Instructions on how to use this script: + - Execute the script as follows: + python gen_tftrt_model + - Rename tftrt_saved_model to what makes sense for your test. + - Delete directory tf_saved_model unless you want to use it. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python import Session +from tensorflow.python.compiler.tensorrt import trt_convert +from tensorflow.python.eager import def_function +from tensorflow.python.framework import dtypes +from tensorflow.python.framework import ops +from tensorflow.python.framework import tensor_spec +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import variables +from tensorflow.python.saved_model import builder +from tensorflow.python.saved_model import save +from tensorflow.python.saved_model import signature_constants +from tensorflow.python.saved_model import signature_def_utils +from tensorflow.python.saved_model import tag_constants +from tensorflow.python.saved_model import utils +from tensorflow.python.training.tracking import tracking + + +def GetGraph(input1, input2, var): + """Define graph.""" + add = input1 + var + mul = input1 * add + add = mul + add + add = add + input2 + out = array_ops.identity(add, name="output") + return out + + +def GenerateModelV2(tf_saved_model_dir, tftrt_saved_model_dir): + """Generate and convert a model using TFv2 API.""" + + class SimpleModel(tracking.AutoTrackable): + """Define model with a TF function.""" + + def __init__(self): + self.v = None + + @def_function.function(input_signature=[ + tensor_spec.TensorSpec(shape=[None, 1, 1], dtype=dtypes.float32), + tensor_spec.TensorSpec(shape=[None, 1, 1], dtype=dtypes.float32) + ]) + def run(self, input1, input2): + if self.v is None: + self.v = variables.Variable([[[1.0]]], dtype=dtypes.float32) + return GetGraph(input1, input2, self.v) + + root = SimpleModel() + + # Saved TF model + save(root, tf_saved_model_dir, + {signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: root.run}) + + # Convert TF model to TensorRT + converter = trt_convert.TrtGraphConverterV2( + input_saved_model_dir=tf_saved_model_dir) + converter.convert() + converter.save(tftrt_saved_model_dir) + + +def GenerateModelV1(tf_saved_model_dir, tftrt_saved_model_dir): + """Generate and convert a model using TFv1 API.""" + + def SimpleModel(): + """Define model with a TF graph.""" + + def GraphFn(): + input1 = array_ops.placeholder( + dtype=dtypes.float32, shape=[None, 1, 1], name="input1") + input2 = array_ops.placeholder( + dtype=dtypes.float32, shape=[None, 1, 1], name="input2") + var = variables.Variable([[[1.0]]], dtype=dtypes.float32, name="v1") + out = GetGraph(input1, input2, var) + return g, var, input1, input2, out + + g = ops.Graph() + with g.as_default(): + return GraphFn() + + g, var, input1, input2, out = SimpleModel() + signature_def = signature_def_utils.build_signature_def( + inputs={ + "input1": utils.build_tensor_info(input1), + "input2": utils.build_tensor_info(input2) + }, + outputs={"output": utils.build_tensor_info(out)}, + method_name=signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY) + saved_model_builder = builder.SavedModelBuilder(tf_saved_model_dir) + with Session(graph=g) as sess: + sess.run(var.initializer) + saved_model_builder.add_meta_graph_and_variables( + sess, [tag_constants.SERVING], + signature_def_map={ + signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: signature_def + }) + saved_model_builder.save() + + # Convert TF model to TensorRT + converter = trt_convert.TrtGraphConverter( + input_saved_model_dir=tf_saved_model_dir, is_dynamic_op=True) + converter.convert() + converter.save(tftrt_saved_model_dir) + + +if __name__ == "__main__": + GenerateModelV2( + tf_saved_model_dir="tf_saved_model", + tftrt_saved_model_dir="tftrt_saved_model") diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/checkpoint b/tensorflow/python/compiler/tensorrt/test/testdata/mnist/checkpoint similarity index 100% rename from tensorflow/python/compiler/tensorrt/test/testdata/checkpoint rename to tensorflow/python/compiler/tensorrt/test/testdata/mnist/checkpoint diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/model.ckpt-46900.data-00000-of-00001 b/tensorflow/python/compiler/tensorrt/test/testdata/mnist/model.ckpt-46900.data-00000-of-00001 similarity index 100% rename from tensorflow/python/compiler/tensorrt/test/testdata/model.ckpt-46900.data-00000-of-00001 rename to tensorflow/python/compiler/tensorrt/test/testdata/mnist/model.ckpt-46900.data-00000-of-00001 diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/model.ckpt-46900.index b/tensorflow/python/compiler/tensorrt/test/testdata/mnist/model.ckpt-46900.index similarity index 100% rename from tensorflow/python/compiler/tensorrt/test/testdata/model.ckpt-46900.index rename to tensorflow/python/compiler/tensorrt/test/testdata/mnist/model.ckpt-46900.index diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/saved_model.pb b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/saved_model.pb new file mode 100644 index 00000000000..cedf8d96a53 Binary files /dev/null and b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/saved_model.pb differ diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.data-00000-of-00002 b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.data-00000-of-00002 new file mode 100644 index 00000000000..02064eb10ed Binary files /dev/null and b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.data-00000-of-00002 differ diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.data-00001-of-00002 b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.data-00001-of-00002 new file mode 100644 index 00000000000..9c12e8adf98 Binary files /dev/null and b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.data-00001-of-00002 differ diff --git a/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.index b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.index new file mode 100644 index 00000000000..16f42aada8d Binary files /dev/null and b/tensorflow/python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model/variables/variables.index differ diff --git a/tensorflow/python/compiler/tensorrt/test/tf_trt_integration_test_base.py b/tensorflow/python/compiler/tensorrt/test/tf_trt_integration_test_base.py index 2149ede25a3..8bd5e019d34 100644 --- a/tensorflow/python/compiler/tensorrt/test/tf_trt_integration_test_base.py +++ b/tensorflow/python/compiler/tensorrt/test/tf_trt_integration_test_base.py @@ -33,7 +33,6 @@ import six from tensorflow.compiler.tf2tensorrt.wrap_py_utils import is_tensorrt_enabled from tensorflow.core.framework import graph_pb2 from tensorflow.core.protobuf import config_pb2 -from tensorflow.core.protobuf import rewriter_config_pb2 from tensorflow.python.compiler.tensorrt import trt_convert from tensorflow.python.eager import def_function from tensorflow.python.framework import graph_io @@ -106,34 +105,6 @@ class GraphState(object): INFERENCE = 2 -def OptimizerDisabledRewriterConfig(): - """Returns a RewriterConfig with all default Grappler optimizers disabled.""" - rewriter_config = rewriter_config_pb2.RewriterConfig() - - # Turn off all default Grappler optimizers. - off = rewriter_config_pb2.RewriterConfig.OFF - rewriter_config.layout_optimizer = off - rewriter_config.constant_folding = off - rewriter_config.shape_optimization = off - rewriter_config.remapping = off - rewriter_config.arithmetic_optimization = off - rewriter_config.dependency_optimization = off - rewriter_config.loop_optimization = off - rewriter_config.function_optimization = off - rewriter_config.debug_stripper = off - rewriter_config.disable_model_pruning = True - rewriter_config.scoped_allocator_optimization = off - rewriter_config.memory_optimization = ( - rewriter_config_pb2.RewriterConfig.NO_MEM_OPT) - rewriter_config.pin_to_host_optimization = off - rewriter_config.auto_parallel.enable = False - - # Run only once for each enabled optimizer. - rewriter_config.meta_optimizer_iterations = ( - rewriter_config_pb2.RewriterConfig.ONE) - return rewriter_config - - class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): """Class to test Tensorflow-TensorRT integration.""" @@ -238,6 +209,15 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): max_batch_size=min(batch_list)) return conversion_params + def GetTrtRewriterConfig(self, + run_params, + conversion_params, + disable_non_trt_optimizers=False): + return trt_convert.get_tensorrt_rewriter_config( + conversion_params=conversion_params, + is_v2=run_params.is_v2, + disable_non_trt_optimizers=disable_non_trt_optimizers) + def ShouldRunTest(self, run_params): """Whether to run the test.""" # Ensure use_calibration=True in case of INT8 precision @@ -606,7 +586,7 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase): trt_op_names.append(node.name) # Remove the function name prefix. def _Canonicalize(names): - return set([self._ToString(name.split("/")[-1]) for name in names]) + return set(self._ToString(name.split("/")[-1]) for name in names) all_op_names = _Canonicalize(all_op_names) trt_op_names = _Canonicalize(trt_op_names) diff --git a/tensorflow/python/compiler/tensorrt/trt_convert.py b/tensorflow/python/compiler/tensorrt/trt_convert.py index 5d4ab191bcb..dc1a7a3097c 100644 --- a/tensorflow/python/compiler/tensorrt/trt_convert.py +++ b/tensorflow/python/compiler/tensorrt/trt_convert.py @@ -161,7 +161,8 @@ TrtConversionParams = collections.namedtuple( "use_calibration", # Max size for the input batch. - # This option is deprecated in TF 2.0. + # This parameter is only effective when is_dynamic_op=False which + # is not supported in TF 2.0. "max_batch_size", ]) @@ -178,11 +179,12 @@ DEFAULT_TRT_CONVERSION_PARAMS = TrtConversionParams( _TRT_ENGINE_OP_NAME = "TRTEngineOp" -def _check_conversion_params(conversion_params): +def _check_conversion_params(conversion_params, is_v2=False): """Validate the provided TrtConversionParams. Args: conversion_params: a TrtConversionParams instance. + is_v2: whether we're getting a RewriterConfig for TF 2.0. Raises: TypeError: if any of the parameters are of unexpected type. @@ -194,6 +196,44 @@ def _check_conversion_params(conversion_params): ("precision mode '{}' is not supported." "It should be one of {}").format(conversion_params.precision_mode, supported_precision_modes)) + if is_v2: + # Static mode (building TRT engine without executing the op) is deprecated + # in TF 2.0. See TrtGraphConverterV2 for more details. + if not conversion_params.is_dynamic_op: + raise ValueError("Option is_dynamic_op=False is not supported in TF 2.0, " + "please set it to True instead.") + + if conversion_params.rewriter_config_template: + rewriter_cfg = conversion_params.rewriter_config_template + trt_optimizer = None + for optimizer in rewriter_cfg.custom_optimizers: + if optimizer.name == "TensorRTOptimizer": + if trt_optimizer: + raise ValueError( + "Found more than one TensorRTOptimizer in " + "rewriter_config_template while only one is allowed.") + trt_optimizer = optimizer + # If rewriter_config_template is set, it should inculde TensorRTOptimizer. + # It is possible to remove this requirement if needed. + if not trt_optimizer: + raise ValueError( + "Found no TensorRTOptimizer in rewriter_config_template.") + if not trt_optimizer.parameter_map: + raise ValueError("Found no parameter_map in TensorRTOptimizer.") + if ("precision_mode" in trt_optimizer.parameter_map.keys() and + trt_optimizer.parameter_map["precision_mode"].s not in map( + _to_bytes, supported_precision_modes)): + raise ValueError(("precision_mode '{}' is not supported. " + "It should be one of {}").format( + trt_optimizer.parameter_map["precision_mode"], + supported_precision_modes)) + if is_v2: + # Static mode (building TRT engine without executing the op) is not + # supported in TF 2.0. See TrtGraphConverterV2 for more details. + if ("is_dynamic_op" in trt_optimizer.parameter_map.keys() and + not trt_optimizer.parameter_map["is_dynamic_op"]): + raise ValueError("Option is_dynamic_op=False is not supported " + "in TF 2.0, please set it to True instead.") def _check_trt_version_compatibility(): @@ -213,8 +253,8 @@ def _check_trt_version_compatibility(): if loaded_version < linked_version: tf_logging.error( "Loaded TensorRT %s but linked TensorFlow against TensorRT %s. " % - (".".join([str(x) for x in loaded_version]), - ".".join([str(x) for x in linked_version])) + + (".".join(str(x) for x in loaded_version), ".".join( + str(x) for x in linked_version)) + "TensorRT does not support forward compatibility. " + "It is also required to use the same major version of TensorRT " + "during compilation and runtime.") @@ -222,26 +262,29 @@ def _check_trt_version_compatibility(): if loaded_version[0] > linked_version[0]: tf_logging.error( "Loaded TensorRT %s but linked TensorFlow against TensorRT %s. " % - (".".join([str(x) for x in loaded_version]), - ".".join([str(x) for x in linked_version])) + + (".".join(str(x) for x in loaded_version), ".".join( + str(x) for x in linked_version)) + "It is required to use the same major version " + "of TensorRT during compilation and runtime.") raise RuntimeError("Incompatible TensorRT major version") if loaded_version != linked_version: tf_logging.info( "Loaded TensorRT %s and linked TensorFlow against TensorRT %s. " % - (".".join([str(x) for x in loaded_version]), - ".".join([str(x) for x in linked_version])) + + (".".join(str(x) for x in loaded_version), ".".join( + str(x) for x in linked_version)) + "This is supported because TensorRT " + " minor/patch upgrades are backward compatible") -def get_tensorrt_rewriter_config(conversion_params, is_v2=False): +def get_tensorrt_rewriter_config(conversion_params, + is_v2=False, + disable_non_trt_optimizers=False): """Returns a RewriterConfig proto for TRT transformation. Args: conversion_params: a TrtConversionParams instance. is_v2: whether we're getting a RewriterConfig for TF 2.0. + disable_non_trt_optimizers: Turn off all default Grappler optimizers. Returns: A RewriterConfig proto which sets a TensorRTOptimizer to run Grappler. @@ -255,46 +298,61 @@ def get_tensorrt_rewriter_config(conversion_params, is_v2=False): rewriter_config_pb2.RewriterConfig): raise TypeError( "rewriter_config_template should be a RewriterConfig proto.") - _check_conversion_params(conversion_params) + _check_conversion_params(conversion_params, is_v2=is_v2) rewriter_config_with_trt = rewriter_config_pb2.RewriterConfig() + if conversion_params.rewriter_config_template is None: - # Layout optimizer may add Const nodes followed by Reshape nodes, thus we - # need to run constant folding again. - rewriter_config_with_trt.optimizers.extend( - ["constfold", "layout", "constfold"]) + if not disable_non_trt_optimizers: + # Layout optimizer may add Const nodes followed by Reshape nodes, thus we + # need to run constant folding again. + rewriter_config_with_trt.optimizers.extend( + ["constfold", "layout", "constfold"]) rewriter_config_with_trt.meta_optimizer_iterations = ( rewriter_config_pb2.RewriterConfig.ONE) + optimizer = rewriter_config_with_trt.custom_optimizers.add() + # Add a constfold optimizer to cleanup the unused Const nodes. + rewriter_config_with_trt.custom_optimizers.add().name = "constfold" + + optimizer.name = "TensorRTOptimizer" + optimizer.parameter_map[ + "minimum_segment_size"].i = conversion_params.minimum_segment_size + optimizer.parameter_map["max_workspace_size_bytes"].i = ( + conversion_params.max_workspace_size_bytes) + optimizer.parameter_map["precision_mode"].s = _to_bytes( + conversion_params.precision_mode) + optimizer.parameter_map[ + "maximum_cached_engines"].i = conversion_params.maximum_cached_engines + optimizer.parameter_map[ + "use_calibration"].b = conversion_params.use_calibration + optimizer.parameter_map["is_dynamic_op"].b = conversion_params.is_dynamic_op + if not is_v2: + optimizer.parameter_map[ + "max_batch_size"].i = conversion_params.max_batch_size else: rewriter_config_with_trt.CopyFrom( conversion_params.rewriter_config_template) - optimizer = rewriter_config_with_trt.custom_optimizers.add() - # Add a constfold optimizer to cleanup the unused Const nodes. - rewriter_config_with_trt.custom_optimizers.add().name = "constfold" + # Disabling optimizers should happen after CopyFrom the temaplte + # otherwise the template can overwrite the disablement. + if disable_non_trt_optimizers: + off = rewriter_config_pb2.RewriterConfig.OFF + rewriter_config_with_trt.layout_optimizer = off + rewriter_config_with_trt.constant_folding = off + rewriter_config_with_trt.shape_optimization = off + rewriter_config_with_trt.remapping = off + rewriter_config_with_trt.arithmetic_optimization = off + rewriter_config_with_trt.dependency_optimization = off + rewriter_config_with_trt.loop_optimization = off + rewriter_config_with_trt.function_optimization = off + rewriter_config_with_trt.debug_stripper = off + rewriter_config_with_trt.disable_model_pruning = True + rewriter_config_with_trt.scoped_allocator_optimization = off + rewriter_config_with_trt.memory_optimization = ( + rewriter_config_pb2.RewriterConfig.NO_MEM_OPT) + rewriter_config_with_trt.pin_to_host_optimization = off + rewriter_config_with_trt.auto_parallel.enable = False - optimizer.name = "TensorRTOptimizer" - optimizer.parameter_map[ - "minimum_segment_size"].i = conversion_params.minimum_segment_size - optimizer.parameter_map[ - "max_workspace_size_bytes"].i = conversion_params.max_workspace_size_bytes - optimizer.parameter_map["precision_mode"].s = _to_bytes( - conversion_params.precision_mode) - optimizer.parameter_map[ - "maximum_cached_engines"].i = conversion_params.maximum_cached_engines - optimizer.parameter_map[ - "use_calibration"].b = conversion_params.use_calibration - if is_v2: - # Static mode (building TRT engine without executing the op) is deprecated - # in TF 2.0. See TrtGraphConverterV2 for more details. - if not conversion_params.is_dynamic_op: - raise ValueError("Option is_dynamic_op=False is not supported in TF 2.0, " - "please set it to True instead.") - optimizer.parameter_map["is_dynamic_op"].b = True - else: - optimizer.parameter_map[ - "max_batch_size"].i = conversion_params.max_batch_size - optimizer.parameter_map["is_dynamic_op"].b = conversion_params.is_dynamic_op return rewriter_config_with_trt @@ -517,7 +575,7 @@ class TrtGraphConverter(object): def _gather_names(tensor_info): """Get the node names from a TensorInfo.""" - return set([tensor_info[key].name.split(":")[0] for key in tensor_info]) + return {tensor_info[key].name.split(":")[0] for key in tensor_info} # Get input and outputs from all SignatureDef. output_node_names = _gather_names(input_signature_def.inputs).union( @@ -713,7 +771,7 @@ class TrtGraphConverter(object): # exists try: col_op = dest_graph.as_graph_element(name) - except (TypeError, ValueError, KeyError) as e: + except (TypeError, ValueError, KeyError): continue dest_graph.add_to_collection(key, col_op) elif kind == "int64_list": @@ -909,7 +967,7 @@ class TrtGraphConverterV2(object): """ assert context.executing_eagerly() _check_trt_version_compatibility() - _check_conversion_params(conversion_params) + _check_conversion_params(conversion_params, is_v2=True) self._conversion_params = conversion_params self._input_saved_model_dir = input_saved_model_dir @@ -964,11 +1022,7 @@ class TrtGraphConverterV2(object): calibration_input_fn: a generator function that yields input data as a list or tuple, which will be used to execute the converted signature for calibration. All the returned input data should have the same shape. - Example: - ``` - def input_fn(): - yield input1, input2, input3 - ``` + Example: `def input_fn(): yield input1, input2, input3` Raises: ValueError: if the input combination is invalid. @@ -1041,11 +1095,7 @@ class TrtGraphConverterV2(object): input_fn: a generator function that yields input data as a list or tuple, which will be used to execute the converted signature to generate TRT engines. - Example: - ``` - def input_fn(): - yield input1, input2, input3 - ``` + Example: `def input_fn(): yield input1, input2, input3` """ for inp in input_fn(): self._converted_func(*map(ops.convert_to_tensor, inp)) diff --git a/tensorflow/python/compiler/tensorrt/trt_convert_test.py b/tensorflow/python/compiler/tensorrt/trt_convert_test.py index 5d7e079f3d7..d6e43121f10 100644 --- a/tensorflow/python/compiler/tensorrt/trt_convert_test.py +++ b/tensorflow/python/compiler/tensorrt/trt_convert_test.py @@ -21,7 +21,6 @@ from __future__ import print_function import gc import os import tempfile - from absl.testing import parameterized import numpy as np @@ -111,6 +110,60 @@ class TrtConvertTest(test_util.TensorFlowTestCase, parameterized.TestCase): trt_optimizer.parameter_map["precision_mode"].s) self.assertEqual(2, trt_optimizer.parameter_map["maximum_cached_engines"].i) + def testGetTensorrtRewriterConfigTemplate(self): + """Test case for TrtGraphConverter.get_tensorrt_rewriter_config().""" + if not is_tensorrt_enabled(): + return + + rewriter_config_with_trt = rewriter_config_pb2.RewriterConfig() + rewriter_config_with_trt.optimizers.extend( + ["constfold", "layout", "constfold"]) + rewriter_config_with_trt.meta_optimizer_iterations = ( + rewriter_config_pb2.RewriterConfig.ONE) + optimizer = rewriter_config_with_trt.custom_optimizers.add() + rewriter_config_with_trt.custom_optimizers.add().name = "constfold" + optimizer.name = "TensorRTOptimizer" + optimizer.parameter_map["minimum_segment_size"].i = 10 + optimizer.parameter_map["max_batch_size"].i = 128 + optimizer.parameter_map["is_dynamic_op"].b = True + optimizer.parameter_map["max_workspace_size_bytes"].i = 1234 + optimizer.parameter_map["precision_mode"].s = trt_convert._to_bytes( + trt_convert.TrtPrecisionMode.INT8) + optimizer.parameter_map["maximum_cached_engines"].i = 2 + optimizer.parameter_map["use_calibration"].b = False + optimizer.parameter_map["use_implicit_batch"].b = True + + conversion_params = trt_convert.DEFAULT_TRT_CONVERSION_PARAMS._replace( + rewriter_config_template=rewriter_config_with_trt) + rewriter_cfg = trt_convert.get_tensorrt_rewriter_config( + conversion_params=conversion_params) + self.assertEqual(["constfold", "layout", "constfold"], + rewriter_cfg.optimizers) + self.assertEqual(rewriter_config_pb2.RewriterConfig.ONE, + rewriter_cfg.meta_optimizer_iterations) + trt_optimizer = None + for optimizer in rewriter_cfg.custom_optimizers: + if optimizer.name == "TensorRTOptimizer": + self.assertIsNone(trt_optimizer) + trt_optimizer = optimizer + self.assertIsNotNone(trt_optimizer) + for key in [ + "minimum_segment_size", "max_batch_size", "is_dynamic_op", + "max_workspace_size_bytes", "precision_mode", "maximum_cached_engines" + ]: + self.assertIn(key, trt_optimizer.parameter_map) + self.assertEqual(10, trt_optimizer.parameter_map["minimum_segment_size"].i) + self.assertEqual(128, trt_optimizer.parameter_map["max_batch_size"].i) + self.assertEqual(True, trt_optimizer.parameter_map["is_dynamic_op"].b) + self.assertEqual(1234, + trt_optimizer.parameter_map["max_workspace_size_bytes"].i) + self.assertEqual( + trt_convert._to_bytes("INT8"), + trt_optimizer.parameter_map["precision_mode"].s) + self.assertEqual(2, trt_optimizer.parameter_map["maximum_cached_engines"].i) + self.assertEqual(False, trt_optimizer.parameter_map["use_calibration"].b) + self.assertEqual(True, trt_optimizer.parameter_map["use_implicit_batch"].b) + def _GetConfigProto(self, rewriter_config=None): """Get ConfigProto for session creation.""" config = config_pb2.ConfigProto( @@ -481,11 +534,10 @@ class TrtConvertTest(test_util.TensorFlowTestCase, parameterized.TestCase): {_SAVED_MODEL_SIGNATURE_KEY: root.run}) # Run TRT conversion. - converter = self._CreateConverterV2( - input_saved_model_dir, is_dynamic_op=False) with self.assertRaisesRegexp( - ValueError, r"Option is_dynamic_op=False is not supported in TF 2.0"): - converter.convert() + ValueError, r"Option is_dynamic_op=False is not supported in TF 2.0, " + "please set it to True instead."): + self._CreateConverterV2(input_saved_model_dir, is_dynamic_op=False) @test_util.run_v2_only def testTrtGraphConverter_Int8Conversion_v2(self): @@ -837,6 +889,26 @@ class TrtConvertTest(test_util.TensorFlowTestCase, parameterized.TestCase): # to fall back to TF function. self._TestRun(sess, 2) + @test_util.run_v2_only + def testBackwardCompatibility(self): + """Load and execute a model that was saved in TF2.0.""" + if not is_tensorrt_enabled(): + return + + model_dir = test.test_src_dir_path( + "python/compiler/tensorrt/test/testdata/tftrt_2.0_saved_model") + saved_model_loaded = load.load(model_dir, tags=[tag_constants.SERVING]) + graph_func = saved_model_loaded.signatures[ + signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY] + + np_input1 = ops.convert_to_tensor(np.ones([4, 1, 1]).astype(np.float32)) + np_input2 = ops.convert_to_tensor(np.ones([4, 1, 1]).astype(np.float32)) + output = graph_func(input1=np_input1, input2=np_input2)["output_0"] + + self.assertEqual(output.shape, (4, 1, 1)) + self.assertAllClose( + np.asarray([5.0, 5.0, 5.0, 5.0]).reshape([4, 1, 1]), output) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/compiler/xla/BUILD b/tensorflow/python/compiler/xla/BUILD index 381635c416d..2061f0cca2f 100644 --- a/tensorflow/python/compiler/xla/BUILD +++ b/tensorflow/python/compiler/xla/BUILD @@ -24,9 +24,9 @@ cuda_py_test( name = "jit_test", size = "small", srcs = ["jit_test.py"], - additional_deps = [ + xla_enabled = True, + deps = [ ":compiler_py", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -40,8 +40,8 @@ cuda_py_test( "//tensorflow/python:random_ops", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - xla_enabled = True, ) py_library( @@ -68,26 +68,26 @@ py_library( cuda_py_test( name = "xla_test", srcs = ["xla_test.py"], - additional_deps = [ - ":xla", - "@absl_py//absl/testing:parameterized", - "//tensorflow/compiler/tests:xla_test", - "//tensorflow/python/tpu:tpu_lib", - "//tensorflow/python:client_testlib", - "//tensorflow/python:constant_op", - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:control_flow_util", - "//tensorflow/python:math_ops", - "//tensorflow/python/estimator:estimator_py", - "//tensorflow/python:platform", - "//tensorflow/python:state_ops", - "//tensorflow/python:summary", - "//tensorflow/python:variable_scope", - ], tags = [ "no_mac", "no_rocm", # XLA support is not enabled on the ROCm platform "no_windows", ], xla_enabled = True, + deps = [ + ":xla", + "//tensorflow/compiler/tests:xla_test", + "//tensorflow/python:client_testlib", + "//tensorflow/python:constant_op", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:control_flow_util", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + "//tensorflow/python:state_ops", + "//tensorflow/python:summary", + "//tensorflow/python:variable_scope", + "//tensorflow/python/estimator:estimator_py", + "//tensorflow/python/tpu:tpu_lib", + "@absl_py//absl/testing:parameterized", + ], ) diff --git a/tensorflow/python/data/benchmarks/BUILD b/tensorflow/python/data/benchmarks/BUILD index bc8240227e8..a9422e83edc 100644 --- a/tensorflow/python/data/benchmarks/BUILD +++ b/tensorflow/python/data/benchmarks/BUILD @@ -10,12 +10,12 @@ exports_files(["LICENSE"]) tf_py_test( name = "meta_benchmark", srcs = ["meta_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:session", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -34,18 +34,18 @@ py_library( tf_py_test( name = "batch_benchmark", srcs = ["batch_benchmark.py"], - additional_deps = [ + deps = [ ":benchmark_base", - "//third_party/py/numpy", "//tensorflow/python:sparse_tensor", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "filter_benchmark", srcs = ["filter_benchmark.py"], - additional_deps = [ + deps = [ ":benchmark_base", "//tensorflow/python/data/ops:dataset_ops", ], @@ -54,31 +54,31 @@ tf_py_test( tf_py_test( name = "from_tensor_slices_benchmark", srcs = ["from_tensor_slices_benchmark.py"], - additional_deps = [ + deps = [ ":benchmark_base", - "//third_party/py/numpy", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "list_files_benchmark", srcs = ["list_files_benchmark.py"], - additional_deps = [ + deps = [ ":benchmark_base", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python:session", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "map_benchmark", srcs = ["map_benchmark.py"], - additional_deps = [ + deps = [ ":benchmark_base", "//tensorflow/python/data/ops:dataset_ops", ], @@ -87,7 +87,7 @@ tf_py_test( tf_py_test( name = "range_benchmark", srcs = ["range_benchmark.py"], - additional_deps = [ + deps = [ ":benchmark_base", "//tensorflow/python/data/ops:dataset_ops", ], diff --git a/tensorflow/python/data/experimental/benchmarks/BUILD b/tensorflow/python/data/experimental/benchmarks/BUILD index 0759a345b29..9a8c7e2164a 100644 --- a/tensorflow/python/data/experimental/benchmarks/BUILD +++ b/tensorflow/python/data/experimental/benchmarks/BUILD @@ -15,33 +15,32 @@ exports_files( tf_py_test( name = "autotune_benchmark", srcs = ["autotune_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:math_ops", "//tensorflow/python:session", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "choose_fastest_benchmark", srcs = ["choose_fastest_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", "//tensorflow/python:math_ops", "//tensorflow/python:session", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "choose_fastest_branch_benchmark", srcs = ["choose_fastest_branch_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", "//tensorflow/python:math_ops", @@ -49,14 +48,15 @@ tf_py_test( "//tensorflow/python/data/benchmarks:benchmark_base", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "csv_dataset_benchmark", srcs = ["csv_dataset_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_pip"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:parsing_ops", "//tensorflow/python:platform", @@ -64,15 +64,14 @@ tf_py_test( "//tensorflow/python:session", "//tensorflow/python/data/experimental/ops:readers", "//tensorflow/python/data/ops:readers", + "//third_party/py/numpy", ], - tags = ["no_pip"], ) tf_py_test( name = "map_and_batch_benchmark", srcs = ["map_and_batch_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -83,13 +82,14 @@ tf_py_test( "//tensorflow/python:session", "//tensorflow/python/data/experimental/ops:batching", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "map_defun_benchmark", srcs = ["map_defun_benchmark.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -104,8 +104,7 @@ tf_py_test( tf_py_test( name = "map_vectorization_benchmark", srcs = ["map_vectorization_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -116,6 +115,7 @@ tf_py_test( "//tensorflow/python:session", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], ) @@ -123,8 +123,7 @@ tf_py_test( name = "matching_files_benchmark", size = "small", srcs = ["matching_files_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -132,54 +131,54 @@ tf_py_test( "//tensorflow/python:util", "//tensorflow/python/data/experimental/ops:matching_files", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "optimize_benchmark", srcs = ["optimize_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", "//tensorflow/python:math_ops", "//tensorflow/python:session", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "parallel_interleave_benchmark", srcs = ["parallel_interleave_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:math_ops", "//tensorflow/python:session", "//tensorflow/python/data/experimental/ops:interleave_ops", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "rejection_resample_benchmark", srcs = ["rejection_resample_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", - "@six_archive//:six", + tags = ["no_pip"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python/data/experimental/ops:resampling", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@six_archive//:six", ], - tags = ["no_pip"], ) tf_py_test( name = "snapshot_dataset_benchmark", srcs = ["snapshot_dataset_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -190,14 +189,14 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:snapshot", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "unbatch_benchmark", srcs = ["unbatch_benchmark.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -205,5 +204,6 @@ tf_py_test( "//tensorflow/python:session", "//tensorflow/python/data/experimental/ops:batching", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) diff --git a/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py b/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py index 10bddb0ab83..a065e7deebb 100644 --- a/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py +++ b/tensorflow/python/data/experimental/benchmarks/csv_dataset_benchmark.py @@ -54,8 +54,8 @@ class CsvDatasetBenchmark(test.Benchmark): with open(fn, 'wb') as f: # Just write 100 rows and use `repeat`... Assumes the cost # of creating an iterator is not significant - row = ','.join([str_val for _ in range(n)]) - f.write('\n'.join([row for _ in range(100)])) + row = ','.join(str_val for _ in range(n)) + f.write('\n'.join(row for _ in range(100))) self._filenames.append(fn) def _tear_down(self): diff --git a/tensorflow/python/data/experimental/kernel_tests/BUILD b/tensorflow/python/data/experimental/kernel_tests/BUILD index 7e9b52e43de..ad85fbf493a 100644 --- a/tensorflow/python/data/experimental/kernel_tests/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/BUILD @@ -12,27 +12,29 @@ tf_py_test( name = "assert_next_test", size = "small", srcs = ["assert_next_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "auto_shard_dataset_test", size = "medium", srcs = ["auto_shard_dataset_test.py"], - additional_deps = [ + tags = [ + "no_pip", + ], + deps = [ ":reader_dataset_ops_test_base", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python/data/experimental/ops:distribute", "//tensorflow/python/data/experimental/ops:readers", @@ -40,9 +42,7 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/util:nest", - ], - tags = [ - "no_pip", + "@absl_py//absl/testing:parameterized", ], ) @@ -50,9 +50,7 @@ tf_py_test( name = "bucket_by_sequence_length_test", size = "medium", srcs = ["bucket_by_sequence_length_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -62,17 +60,19 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:grouping", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) tf_py_test( name = "cardinality_test", srcs = ["cardinality_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/python/data/experimental/ops:cardinality", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -80,27 +80,27 @@ cuda_py_test( name = "copy_to_device_test", size = "small", srcs = ["copy_to_device_test.py"], - additional_deps = [ - "//tensorflow/python/data/experimental/ops:prefetching_ops", + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", - "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python:dtypes", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", - "//tensorflow/python/compat:compat", + "//tensorflow/python/compat", + "//tensorflow/python/data/experimental/ops:prefetching_ops", + "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/ops:iterator_ops", ], - tags = ["no_windows_gpu"], ) tf_py_test( name = "counter_test", size = "small", srcs = ["counter_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python/data/experimental/ops:counter", @@ -112,7 +112,8 @@ tf_py_test( name = "csv_dataset_test", size = "medium", srcs = ["csv_dataset_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -125,14 +126,12 @@ tf_py_test( "//tensorflow/python/data/ops:readers", "//tensorflow/python/eager:context", ], - tags = ["no_pip"], ) tf_py_test( name = "dense_to_sparse_batch_test", srcs = ["dense_to_sparse_batch_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -140,20 +139,21 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:batching", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "dense_to_ragged_batch_test", srcs = ["dense_to_ragged_batch_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -161,14 +161,14 @@ tf_py_test( name = "directed_interleave_dataset_test", size = "medium", srcs = ["directed_interleave_dataset_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:random_seed", "//tensorflow/python/data/experimental/ops:interleave_ops", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -176,8 +176,7 @@ tf_py_test( name = "get_single_element_test", size = "small", srcs = ["get_single_element_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -188,6 +187,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:grouping", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -195,8 +195,7 @@ tf_py_test( name = "group_by_reducer_test", size = "medium", srcs = ["group_by_reducer_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -208,6 +207,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:grouping", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -215,8 +215,7 @@ tf_py_test( name = "group_by_window_test", size = "medium", srcs = ["group_by_window_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -229,14 +228,14 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:grouping", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) tf_py_test( name = "ignore_errors_test", srcs = ["ignore_errors_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -245,6 +244,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:error_ops", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -252,9 +252,9 @@ tf_py_test( name = "make_batched_features_dataset_test", size = "medium", srcs = ["make_batched_features_dataset_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ ":reader_dataset_ops_test_base", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", @@ -264,16 +264,16 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:readers", "//tensorflow/python/data/ops:readers", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], - tags = ["no_pip"], ) tf_py_test( name = "make_csv_dataset_test", size = "medium", srcs = ["make_csv_dataset_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_pip"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -282,15 +282,16 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:readers", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], - tags = ["no_pip"], ) tf_py_test( name = "make_tf_record_dataset_test", size = "medium", srcs = ["make_tf_record_dataset_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ ":reader_dataset_ops_test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -299,16 +300,13 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:readers", "//tensorflow/python/data/util:nest", ], - tags = ["no_pip"], ) tf_py_test( name = "map_and_batch_test", size = "medium", srcs = ["map_and_batch_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:cond_v2", @@ -321,6 +319,8 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:batching", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -328,7 +328,8 @@ tf_py_test( name = "map_defun_op_test", size = "small", srcs = ["map_defun_op_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:check_ops", "//tensorflow/python:client_testlib", @@ -345,15 +346,14 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:map_defun", "//tensorflow/python/data/kernel_tests:test_base", ], - tags = ["no_pip"], ) tf_py_test( name = "matching_files_test", size = "small", srcs = ["matching_files_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_pip"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -362,26 +362,26 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:matching_files", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], - tags = ["no_pip"], ) tf_py_test( name = "model_dataset_test", size = "small", srcs = ["model_dataset_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "@absl_py//absl/testing:parameterized", ], ) @@ -389,27 +389,30 @@ tf_py_test( name = "non_serializable_test", size = "small", srcs = ["non_serializable_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "optimize_dataset_test", size = "medium", srcs = ["optimize_dataset_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -424,11 +427,8 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/eager:context", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -436,9 +436,8 @@ tf_py_test( name = "override_threadpool_test", size = "small", srcs = ["override_threadpool_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + tags = ["no_pip"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", @@ -447,16 +446,17 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:unique", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - tags = ["no_pip"], ) tf_py_test( name = "parallel_interleave_test", size = "medium", srcs = ["parallel_interleave_test.py"], - additional_deps = [ - "@six_archive//:six", + tags = ["no_pip"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -468,16 +468,15 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:interleave_ops", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", + "@six_archive//:six", ], - tags = ["no_pip"], ) tf_py_test( name = "parse_example_dataset_test", size = "small", srcs = ["parse_example_dataset_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -490,6 +489,7 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], ) @@ -497,25 +497,24 @@ cuda_py_test( name = "prefetch_to_device_test", size = "small", srcs = ["prefetch_to_device_test.py"], - additional_deps = [ - "//tensorflow/python/data/experimental/ops:prefetching_ops", + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", - "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python:dtypes", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", + "//tensorflow/python/data/experimental/ops:prefetching_ops", + "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = ["no_windows_gpu"], ) tf_py_test( name = "prefetch_with_slack_test", size = "small", srcs = ["prefetch_with_slack_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -524,6 +523,7 @@ tf_py_test( "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/ops:iterator_ops", "//tensorflow/python/data/ops:multi_device_iterator_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -553,8 +553,7 @@ tf_py_test( name = "rebatch_dataset_test", size = "small", srcs = ["rebatch_dataset_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:parsing_ops", @@ -563,6 +562,7 @@ tf_py_test( "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/util:nest", "//tensorflow/python/ops/ragged:ragged_tensor", + "@absl_py//absl/testing:parameterized", ], ) @@ -570,10 +570,12 @@ tf_py_test( name = "rejection_resample_test", size = "medium", srcs = ["rejection_resample_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "@six_archive//:six", + shard_count = 5, + tags = [ + "noasan", + "optonly", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", @@ -584,48 +586,46 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:resampling", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - shard_count = 5, - tags = [ - "noasan", - "optonly", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + "@six_archive//:six", ], ) tf_py_test( name = "replicate_test", srcs = ["replicate_test.py"], - additional_deps = [ + grpc_enabled = True, + tags = ["no_oss"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_combinations", "//tensorflow/python/data/experimental/ops:distribute", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - grpc_enabled = True, - tags = ["no_oss"], ) tf_py_test( name = "replicate_cluster_test", srcs = ["replicate_cluster_test.py"], - additional_deps = [ + grpc_enabled = True, + tags = ["no_oss"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_combinations", "//tensorflow/python/data/experimental/ops:distribute", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - grpc_enabled = True, - tags = ["no_oss"], ) cuda_py_test( name = "scan_test", size = "small", srcs = ["scan_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_pip"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -641,33 +641,33 @@ cuda_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/eager:context", + "//third_party/py/numpy", ], - tags = ["no_pip"], ) tf_py_test( name = "shuffle_and_repeat_test", size = "medium", srcs = ["shuffle_and_repeat_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = [ + "no_pip", + "optonly", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python/data/experimental/ops:shuffle_ops", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_pip", - "optonly", + "//third_party/py/numpy", ], ) tf_py_test( name = "sleep_test", srcs = ["sleep_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:util", "//tensorflow/python/data/experimental/ops:testing", @@ -698,41 +698,42 @@ tf_py_test( name = "sql_dataset_test", size = "medium", srcs = ["sql_dataset_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ ":sql_dataset_test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", ], - tags = ["no_pip"], ) tf_py_test( name = "snapshot_test", size = "medium", srcs = ["snapshot_test.py"], - additional_deps = [ + shard_count = 10, + deps = [ ":reader_dataset_ops_test_base", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_test_lib", "//tensorflow/python:string_ops", "//tensorflow/python/data/experimental/ops:snapshot", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/ops:readers", + "@absl_py//absl/testing:parameterized", ], - shard_count = 10, ) tf_py_test( name = "stats_dataset_ops_test", size = "large", srcs = ["stats_dataset_ops_test.py"], - additional_deps = [ + tags = [ + "no_pip", + ], + deps = [ ":reader_dataset_ops_test_base", ":stats_dataset_test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -743,9 +744,8 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:stats_ops", "//tensorflow/python/data/experimental/ops:stats_options", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_pip", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -764,9 +764,7 @@ tf_py_test( name = "take_while_test", size = "small", srcs = ["take_while_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -778,6 +776,8 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/eager:context", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -785,7 +785,7 @@ tf_py_test( name = "tf_record_writer_test", size = "small", srcs = ["tf_record_writer_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -802,7 +802,8 @@ tf_py_test( name = "unique_test", size = "small", srcs = ["unique_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", @@ -811,13 +812,12 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = ["no_pip"], ) tf_py_test( name = "variant_test", srcs = ["variant_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:util", "//tensorflow/python/data/kernel_tests:test_base", @@ -829,7 +829,7 @@ cuda_py_test( name = "wrap_unwrap_test", size = "small", srcs = ["wrap_unwrap_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", diff --git a/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD b/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD index 4cd2a3d1fcd..06d5305c885 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/optimization/BUILD @@ -11,18 +11,18 @@ tf_py_test( name = "choose_fastest_dataset_test", size = "small", srcs = ["choose_fastest_dataset_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:optimization", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "@absl_py//absl/testing:parameterized", ], ) @@ -30,8 +30,12 @@ tf_py_test( name = "filter_fusion_test", size = "medium", srcs = ["filter_fusion_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -41,11 +45,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "@absl_py//absl/testing:parameterized", ], ) @@ -53,8 +53,14 @@ tf_py_test( name = "filter_with_random_uniform_fusion_test", size = "medium", srcs = ["filter_with_random_uniform_fusion_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "manual", + "no_oss", + "no_pip", + "no_windows", + "notap", # TODO(b/131229793) + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -64,13 +70,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "manual", - "no_oss", - "no_pip", - "no_windows", - "notap", # TODO(b/131229793) + "@absl_py//absl/testing:parameterized", ], ) @@ -78,8 +78,12 @@ tf_py_test( name = "hoist_random_uniform_test", size = "small", srcs = ["hoist_random_uniform_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:control_flow_ops", @@ -92,11 +96,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "@absl_py//absl/testing:parameterized", ], ) @@ -104,25 +104,30 @@ tf_py_test( name = "inject_prefetch_test", size = "small", srcs = ["inject_prefetch_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "latency_all_edges_test", size = "small", srcs = ["latency_all_edges_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/kernel_tests:stats_dataset_test_base", @@ -131,17 +136,17 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "map_and_batch_fusion_test", srcs = ["map_and_batch_fusion_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:optimization_options", @@ -149,18 +154,17 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "map_and_filter_fusion_test", srcs = ["map_and_filter_fusion_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -170,30 +174,26 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "@absl_py//absl/testing:parameterized", ], ) tf_py_test( name = "map_fusion_test", srcs = ["map_fusion_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:optimization_options", "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "@absl_py//absl/testing:parameterized", ], ) @@ -201,8 +201,12 @@ tf_py_test( name = "map_parallelization_test", size = "small", srcs = ["map_parallelization_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:control_flow_ops", @@ -215,11 +219,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "@absl_py//absl/testing:parameterized", ], ) @@ -227,9 +227,13 @@ tf_py_test( name = "map_vectorization_test", size = "small", srcs = ["map_vectorization_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + shard_count = 8, + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:bitwise_ops", @@ -251,12 +255,8 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", - ], - shard_count = 8, - tags = [ - "no_oss", - "no_pip", - "no_windows", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -264,7 +264,12 @@ tf_py_test( name = "noop_elimination_test", size = "small", srcs = ["noop_elimination_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -274,17 +279,17 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "shuffle_and_repeat_fusion_test", srcs = ["shuffle_and_repeat_fusion_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python/data/experimental/ops:optimization_options", @@ -292,9 +297,4 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) diff --git a/tensorflow/python/data/experimental/kernel_tests/optimize_dataset_test.py b/tensorflow/python/data/experimental/kernel_tests/optimize_dataset_test.py index 397703e1c40..90c269a6825 100644 --- a/tensorflow/python/data/experimental/kernel_tests/optimize_dataset_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/optimize_dataset_test.py @@ -221,7 +221,7 @@ class OptimizeDatasetTest(test_base.DatasetTestBase, parameterized.TestCase): " To enable rewrites, use resource variables instead by " "calling `tf.enable_resource_variables()` at the start of the " "program." % (", ".join(options._graph_rewrites()))) - self.assertTrue(any([expected in str(warning) for warning in w])) + self.assertTrue(any(expected in str(warning) for warning in w)) # Check that outputs are the same in the optimized and unoptimized cases, # when the variable value is changing. diff --git a/tensorflow/python/data/experimental/kernel_tests/override_threadpool_test.py b/tensorflow/python/data/experimental/kernel_tests/override_threadpool_test.py index d7944042c6e..cf768d3145d 100644 --- a/tensorflow/python/data/experimental/kernel_tests/override_threadpool_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/override_threadpool_test.py @@ -120,7 +120,7 @@ class OverrideThreadpoolTest(test_base.DatasetTestBase, graph = graph_pb2.GraphDef().FromString( self.evaluate(dataset._as_serialized_graph())) self.assertTrue( - any([node.op != "MaxIntraOpParallelismDataset" for node in graph.node])) + any(node.op != "MaxIntraOpParallelismDataset" for node in graph.node)) if __name__ == "__main__": diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD index 70671a89aad..a8eaf375121 100644 --- a/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/BUILD @@ -35,9 +35,13 @@ tf_py_test( name = "auto_shard_dataset_serialization_test", size = "medium", srcs = ["auto_shard_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:sparse_tensor", @@ -45,11 +49,7 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:interleave_ops", "//tensorflow/python/data/experimental/ops:readers", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "//third_party/py/numpy", ], ) @@ -57,19 +57,19 @@ tf_py_test( name = "batch_dataset_serialization_test", size = "medium", srcs = ["batch_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:sparse_tensor", "//tensorflow/python/data/experimental/ops:batching", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "//third_party/py/numpy", ], ) @@ -77,25 +77,30 @@ tf_py_test( name = "cache_dataset_serialization_test", size = "small", srcs = ["cache_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - "//tensorflow/python:errors", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:errors", + "//tensorflow/python/data/ops:dataset_ops", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "checkpoint_input_pipeline_hook_test", size = "small", srcs = ["checkpoint_input_pipeline_hook_test.py"], - additional_deps = [ + tags = [ + "no_pip", + "no_windows", + "notsan", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -107,18 +112,18 @@ tf_py_test( "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/estimator:estimator_py", ], - tags = [ - "no_pip", - "no_windows", - "notsan", - ], ) tf_py_test( name = "choose_fastest_branch_dataset_serialization_test", size = "medium", srcs = ["choose_fastest_branch_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -127,124 +132,141 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:optimization", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "choose_fastest_dataset_serialization_test", size = "small", srcs = ["choose_fastest_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:optimization", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:optimization", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "concatenate_dataset_serialization_test", size = "small", srcs = ["concatenate_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], +) + +tf_py_test( + name = "snapshot_dataset_serialization_test", + size = "medium", + srcs = ["snapshot_dataset_serialization_test.py"], + shard_count = 3, + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "csv_dataset_serialization_test", size = "small", srcs = ["csv_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_ops", - "//tensorflow/python/data/experimental/ops:readers", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_ops", + "//tensorflow/python/data/experimental/ops:readers", + ], ) tf_py_test( name = "dataset_constructor_serialization_test", size = "medium", srcs = ["dataset_constructor_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:sparse_tensor", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "filter_dataset_serialization_test", size = "medium", srcs = ["filter_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:math_ops", - "//tensorflow/python:sparse_tensor", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:math_ops", + "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "fixed_length_record_dataset_serialization_test", size = "medium", srcs = ["fixed_length_record_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", - "//tensorflow/python/data/ops:readers", - ], shard_count = 4, tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", + "//tensorflow/python/data/ops:readers", + ], ) tf_py_test( name = "flat_map_dataset_serialization_test", size = "medium", srcs = ["flat_map_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -258,113 +280,112 @@ tf_py_test( "//tensorflow/python:variable_scope", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "group_by_reducer_serialization_test", size = "medium", srcs = ["group_by_reducer_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:grouping", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:grouping", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "group_by_window_serialization_test", size = "medium", srcs = ["group_by_window_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:grouping", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:grouping", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "ignore_errors_serialization_test", size = "small", srcs = ["ignore_errors_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:error_ops", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:error_ops", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "interleave_dataset_serialization_test", size = "medium", srcs = ["interleave_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:sparse_ops", - "//tensorflow/python:sparse_tensor", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:sparse_ops", + "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "map_and_batch_dataset_serialization_test", size = "medium", srcs = ["map_and_batch_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:math_ops", "//tensorflow/python/data/experimental/ops:batching", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "map_dataset_serialization_test", size = "medium", srcs = ["map_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -375,11 +396,7 @@ tf_py_test( "//tensorflow/python:sparse_tensor", "//tensorflow/python:variable_scope", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "//third_party/py/numpy", ], ) @@ -387,15 +404,15 @@ tf_py_test( name = "matching_files_dataset_serialization_test", size = "small", srcs = ["matching_files_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python/data/experimental/ops:matching_files", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_windows", + "//third_party/py/numpy", ], ) @@ -403,72 +420,72 @@ tf_py_test( name = "optimize_dataset_serialization_test", size = "small", srcs = ["optimize_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:optimization", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:optimization", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "rebatch_dataset_serialization_test", size = "small", srcs = ["rebatch_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:distribute", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:distribute", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "padded_batch_dataset_serialization_test", size = "medium", srcs = ["padded_batch_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:string_ops", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:string_ops", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "parallel_interleave_dataset_serialization_test", size = "medium", srcs = ["parallel_interleave_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:sparse_ops", "//tensorflow/python:sparse_tensor", "//tensorflow/python/data/experimental/ops:interleave_ops", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "//third_party/py/numpy", ], ) @@ -476,9 +493,13 @@ tf_py_test( name = "parallel_map_dataset_serialization_test", size = "medium", srcs = ["parallel_map_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -488,11 +509,7 @@ tf_py_test( "//tensorflow/python:random_ops", "//tensorflow/python:variable_scope", "//tensorflow/python/data/ops:dataset_ops", - ], - tags = [ - "no_oss", - "no_pip", - "no_windows", + "//third_party/py/numpy", ], ) @@ -500,39 +517,44 @@ tf_py_test( name = "parse_example_dataset_serialization_test", size = "medium", srcs = ["parse_example_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", + ], ) tf_py_test( name = "prefetch_dataset_serialization_test", size = "small", srcs = ["prefetch_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "range_dataset_serialization_test", size = "small", srcs = ["range_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:dataset_ops_gen", @@ -544,69 +566,69 @@ tf_py_test( "//tensorflow/python:variables", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "sample_from_datasets_serialization_test", size = "medium", srcs = ["sample_from_datasets_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:interleave_ops", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:interleave_ops", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "scan_dataset_serialization_test", size = "small", srcs = ["scan_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:scan_ops", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:scan_ops", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "sequence_dataset_serialization_test", size = "medium", srcs = ["sequence_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "serialization_integration_test", size = "small", srcs = ["serialization_integration_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_combinations", "//tensorflow/python:framework_ops", @@ -615,52 +637,52 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "shard_dataset_serialization_test", size = "medium", srcs = ["shard_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "shuffle_and_repeat_dataset_serialization_test", size = "medium", srcs = ["shuffle_and_repeat_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:shuffle_ops", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:shuffle_ops", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "shuffle_dataset_serialization_test", size = "medium", srcs = ["shuffle_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", @@ -668,18 +690,18 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:iterator_ops", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "sql_dataset_serialization_test", size = "small", srcs = ["sql_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -687,18 +709,18 @@ tf_py_test( "//tensorflow/python/data/experimental/kernel_tests:sql_dataset_test_base", "//tensorflow/python/data/experimental/ops:readers", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "stats_dataset_serialization_test", size = "medium", srcs = ["stats_dataset_serialization_test.py"], - additional_deps = [ + tags = [ + "no_oss", + "no_pip", + "no_windows", + ], + deps = [ ":dataset_serialization_test_base", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -707,115 +729,110 @@ tf_py_test( "//tensorflow/python/data/experimental/ops:stats_ops", "//tensorflow/python/data/ops:dataset_ops", ], - tags = [ - "no_oss", - "no_pip", - "no_windows", - ], ) tf_py_test( name = "take_while_dataset_serialization_test", size = "medium", srcs = ["take_while_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:take_while_ops", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:take_while_ops", + "//tensorflow/python/data/ops:dataset_ops", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "textline_dataset_serialization_test", size = "medium", srcs = ["textline_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", - "//tensorflow/python/data/ops:readers", - ], shard_count = 4, tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", + "//tensorflow/python/data/ops:readers", + ], ) tf_py_test( name = "tf_record_dataset_serialization_test", size = "medium", srcs = ["tf_record_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", - "//tensorflow/python/data/ops:readers", - ], shard_count = 4, tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/kernel_tests:reader_dataset_ops_test_base", + "//tensorflow/python/data/ops:readers", + ], ) tf_py_test( name = "unbatch_dataset_serialization_test", size = "medium", srcs = ["unbatch_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:batching", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:batching", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "unique_dataset_serialization_test", size = "small", srcs = ["unique_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:unique", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/experimental/ops:unique", + "//tensorflow/python/data/ops:dataset_ops", + ], ) tf_py_test( name = "zip_dataset_serialization_test", size = "small", srcs = ["zip_dataset_serialization_test.py"], - additional_deps = [ - ":dataset_serialization_test_base", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/ops:dataset_ops", - ], tags = [ "no_oss", "no_pip", "no_windows", ], + deps = [ + ":dataset_serialization_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + ], ) diff --git a/tensorflow/python/data/experimental/kernel_tests/serialization/snapshot_dataset_serialization_test.py b/tensorflow/python/data/experimental/kernel_tests/serialization/snapshot_dataset_serialization_test.py new file mode 100644 index 00000000000..c1d55dab54f --- /dev/null +++ b/tensorflow/python/data/experimental/kernel_tests/serialization/snapshot_dataset_serialization_test.py @@ -0,0 +1,168 @@ +# Copyright 2019 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 the MapDataset serialization.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +from absl.testing import parameterized + +from tensorflow.python.data.experimental.kernel_tests.serialization import dataset_serialization_test_base +from tensorflow.python.data.experimental.ops import snapshot +from tensorflow.python.data.kernel_tests import test_base +from tensorflow.python.data.ops import dataset_ops +from tensorflow.python.framework import combinations +from tensorflow.python.platform import test + + +class SnapshotDatasetSerializationTest( + dataset_serialization_test_base.DatasetSerializationTestBase, + parameterized.TestCase): + + def _build_snapshot_dataset(self, + num_threads=1, + repeat=False, + pending_snapshot_expiry_seconds=-1): + + def ds_fn(): + snapshot_dir = os.path.join(self.get_temp_dir(), "snapshot") + if not os.path.exists(snapshot_dir): + os.mkdir(snapshot_dir) + dataset = dataset_ops.Dataset.range(1000) + dataset = dataset.apply( + snapshot.snapshot( + snapshot_dir, + num_writer_threads=num_threads, + writer_buffer_size=2 * num_threads, + num_reader_threads=num_threads, + reader_buffer_size=2 * num_threads, + pending_snapshot_expiry_seconds=pending_snapshot_expiry_seconds)) + if repeat: + dataset = dataset.repeat(2) + return dataset + + return ds_fn + + @combinations.generate( + combinations.times( + test_base.default_test_combinations(), + combinations.combine(pending_snapshot_expiry_seconds=[None, 1]))) + def testSnapshotBeforeEpochEnd(self, pending_snapshot_expiry_seconds): + ds_fn = self._build_snapshot_dataset( + pending_snapshot_expiry_seconds=pending_snapshot_expiry_seconds) + outputs = self.gen_outputs(ds_fn, [], 100, verify_exhausted=False) + self.assertSequenceEqual(outputs, range(100)) + outputs.extend( + self.gen_outputs( + ds_fn, [], 900, ckpt_saved=True, verify_exhausted=False)) + self.assertSequenceEqual(outputs, range(1000)) + + @combinations.generate( + combinations.times( + test_base.default_test_combinations(), + combinations.combine(pending_snapshot_expiry_seconds=[None, 1]))) + def testCheckpointBeforeOneEpochThenRunFewSteps( + self, pending_snapshot_expiry_seconds): + ds_fn = self._build_snapshot_dataset( + pending_snapshot_expiry_seconds=pending_snapshot_expiry_seconds) + + # Generate 200 entries from iterator but save checkpoint after producing + # 100. + outputs = self.gen_outputs( + ds_fn, [100], 200, verify_exhausted=False, save_checkpoint_at_end=False) + self.assertSequenceEqual(outputs, range(200)) + + outputs = outputs[:100] + outputs.extend( + self.gen_outputs( + ds_fn, [], 900, ckpt_saved=True, verify_exhausted=False)) + self.assertSequenceEqual(outputs, range(1000)) + + @combinations.generate( + combinations.times( + test_base.default_test_combinations(), + combinations.combine(pending_snapshot_expiry_seconds=[None, 1]))) + def testCheckpointBeforeOneEpochThenRunFewStepsMultipleThreads( + self, pending_snapshot_expiry_seconds): + ds_fn = self._build_snapshot_dataset( + num_threads=2, + pending_snapshot_expiry_seconds=pending_snapshot_expiry_seconds) + + # Generate 200 entries from iterator but save checkpoint after producing + # 100. + outputs = self.gen_outputs( + ds_fn, [100], 200, verify_exhausted=False, save_checkpoint_at_end=False) + self.assertSequenceEqual(outputs, range(200)) + + outputs = outputs[:100] + outputs.extend( + self.gen_outputs( + ds_fn, [], 900, ckpt_saved=True, verify_exhausted=False)) + self.assertSequenceEqual(outputs, range(1000)) + + @combinations.generate( + combinations.times( + test_base.default_test_combinations(), + combinations.combine(pending_snapshot_expiry_seconds=[None, 1]))) + def testCheckpointAfterOneEpoch(self, pending_snapshot_expiry_seconds): + ds_fn = self._build_snapshot_dataset( + repeat=True, + pending_snapshot_expiry_seconds=pending_snapshot_expiry_seconds) + + # Generate 1100 entries from iterator and save checkpoint. + outputs = self.gen_outputs(ds_fn, [], 1100, verify_exhausted=False) + self.assertSequenceEqual(outputs, list(range(1000)) + list(range(100))) + + # Restore from checkpoint and produce the rest of the elements from the + # iterator. + t = self.gen_outputs( + ds_fn, [], 900, ckpt_saved=True, verify_exhausted=False) + outputs.extend(t) + self.assertSequenceEqual( + outputs, + list(range(1000)) + list(range(100)) + list(range(900))) + + @combinations.generate( + combinations.times( + test_base.default_test_combinations(), + combinations.combine(pending_snapshot_expiry_seconds=[None, 1]))) + def testCheckpointAfterOneEpochThenRunFewSteps( + self, pending_snapshot_expiry_seconds): + ds_fn = self._build_snapshot_dataset( + repeat=True, + pending_snapshot_expiry_seconds=pending_snapshot_expiry_seconds) + + # Generate 200 entries from iterator but save checkpoint after producing + # 100. + outputs = self.gen_outputs( + ds_fn, [1100], + 1200, + verify_exhausted=False, + save_checkpoint_at_end=False) + self.assertSequenceEqual( + outputs, + list(range(1000)) + list(range(100)) + list(range(100))) + + outputs = outputs[:1100] + t = self.gen_outputs( + ds_fn, [], 900, ckpt_saved=True, verify_exhausted=False) + outputs.extend(t) + self.assertSequenceEqual( + outputs, (list(range(1000)) + list(range(100)) + list(range(900)))) + + +if __name__ == "__main__": + test.main() diff --git a/tensorflow/python/data/experimental/kernel_tests/shuffle_and_repeat_test.py b/tensorflow/python/data/experimental/kernel_tests/shuffle_and_repeat_test.py index 8bb109a6519..cffc507d4b0 100644 --- a/tensorflow/python/data/experimental/kernel_tests/shuffle_and_repeat_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/shuffle_and_repeat_test.py @@ -25,6 +25,7 @@ from tensorflow.python.data.kernel_tests import test_base from tensorflow.python.data.ops import dataset_ops from tensorflow.python.framework import combinations from tensorflow.python.framework import errors +from tensorflow.python.framework import random_seed from tensorflow.python.platform import test @@ -132,6 +133,24 @@ class ShuffleAndRepeatTest(test_base.DatasetTestBase, parameterized.TestCase): output[i * 5:(i + 1) * 5], key=lambda batch: batch[0]) self.assertAllEqual(sorted_epoch, np.arange(500).reshape([5, 100])) + @combinations.generate(test_base.default_test_combinations()) + def testRerandomizeOnReplicate(self): + random_seed.set_random_seed(None) + # When no seeds are fixed, each instantiation of the dataset should + # produce elements in a different order. + num_epochs = 2 + num_elements = 100 + ds = dataset_ops.Dataset.range(num_elements).apply( + shuffle_ops.shuffle_and_repeat( + buffer_size=num_elements, count=num_epochs)) + + shuffle_1 = self.getDatasetOutput(ds) + ds = self.graphRoundTrip(ds) + shuffle_2 = self.getDatasetOutput(ds) + + self.assertCountEqual(shuffle_1, shuffle_2) + self.assertNotEqual(shuffle_1, shuffle_2) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/data/experimental/kernel_tests/snapshot_test.py b/tensorflow/python/data/experimental/kernel_tests/snapshot_test.py index 3d83a5e5a1a..5f3f1a66120 100644 --- a/tensorflow/python/data/experimental/kernel_tests/snapshot_test.py +++ b/tensorflow/python/data/experimental/kernel_tests/snapshot_test.py @@ -110,9 +110,9 @@ class SnapshotDatasetTest(reader_dataset_ops_test_base.TFRecordDatasetTestBase, dataset2 = dataset2.apply(snapshot.snapshot(tmpdir)) next2 = self.getNext(dataset2) - for _ in range(1000): - self.evaluate(next1()) - self.evaluate(next2()) + for i in range(0, 1000): + self.assertEqual(i, self.evaluate(next1())) + self.assertEqual(i, self.evaluate(next2())) # we check that only one copy of the metadata has been written, and the # one that lost the race would be in passthrough mode. diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD index db749da77f8..3f6906e8fd4 100644 --- a/tensorflow/python/data/kernel_tests/BUILD +++ b/tensorflow/python/data/kernel_tests/BUILD @@ -12,18 +12,18 @@ tf_py_test( name = "batch_test", size = "small", srcs = ["batch_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:math_ops", "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/ops/ragged", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -31,11 +31,8 @@ tf_py_test( name = "cache_test", size = "small", srcs = ["cache_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:iterator_ops", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -43,6 +40,9 @@ tf_py_test( "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python:variables", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", + "//third_party/py/numpy", ], ) @@ -50,32 +50,32 @@ tf_py_test( name = "checkpoint_test", size = "medium", srcs = ["checkpoint_test.py"], - additional_deps = [ + grpc_enabled = True, + deps = [ ":test_base", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/eager:context", - "//tensorflow/python/training/tracking:util", "//tensorflow/python:checkpoint_management", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/eager:context", + "//tensorflow/python/training/tracking:util", ], - grpc_enabled = True, ) tf_py_test( name = "concatenate_test", size = "small", srcs = ["concatenate_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:tensor_shape", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], ) @@ -83,22 +83,22 @@ tf_py_test( name = "dataset_test", size = "small", srcs = ["dataset_test.py"], - additional_deps = [ + tags = [ + "no_rocm", + ], + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:readers", - "//tensorflow/python/data/util:nest", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:script_ops", "//tensorflow/python:sparse_tensor", - ], - tags = [ - "no_rocm", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:readers", + "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -106,7 +106,7 @@ tf_py_test( name = "enumerate_test", size = "small", srcs = ["enumerate_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -121,9 +121,8 @@ tf_py_test( name = "filter_test", size = "small", srcs = ["filter_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -132,6 +131,7 @@ tf_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:sparse_tensor", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -139,7 +139,7 @@ tf_py_test( name = "fixed_length_record_dataset_test", size = "small", srcs = ["fixed_length_record_dataset_test.py"], - additional_deps = [ + deps = [ ":test_base", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -160,12 +160,10 @@ tf_py_test( name = "flat_map_test", size = "medium", srcs = ["flat_map_test.py"], - additional_deps = [ + grpc_enabled = True, + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:readers", - "//tensorflow/python/data/util:nest", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:session", @@ -174,26 +172,28 @@ tf_py_test( "//tensorflow/python:tensor_array_ops", "//tensorflow/python:training", "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:readers", + "//tensorflow/python/data/util:nest", "//tensorflow/python/ops/ragged", + "//third_party/py/numpy", ], - grpc_enabled = True, ) tf_py_test( name = "from_generator_test", size = "medium", srcs = ["from_generator_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", - "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:script_ops", - "//tensorflow/python:tensor_array_ops", "//tensorflow/python:session", + "//tensorflow/python:tensor_array_ops", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -201,9 +201,8 @@ tf_py_test( name = "from_sparse_tensor_slices_test", size = "small", srcs = ["from_sparse_tensor_slices_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -217,6 +216,7 @@ tf_py_test( "//tensorflow/python:tensor_shape", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], ) @@ -224,9 +224,8 @@ tf_py_test( name = "from_tensors_test", size = "small", srcs = ["from_tensors_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -240,6 +239,7 @@ tf_py_test( "//tensorflow/python:tensor_shape", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/util:nest", + "//third_party/py/numpy", ], ) @@ -247,11 +247,8 @@ tf_py_test( name = "from_tensor_slices_test", size = "small", srcs = ["from_tensor_slices_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", - "//tensorflow/python/ops/ragged:ragged_factory_ops", - "//tensorflow/python/ops/ragged:ragged_tensor", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", @@ -260,6 +257,9 @@ tf_py_test( "//tensorflow/python:tensor_shape", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/ops/ragged", + "//tensorflow/python/ops/ragged:ragged_factory_ops", + "//tensorflow/python/ops/ragged:ragged_tensor", + "//third_party/py/numpy", ], ) @@ -267,18 +267,18 @@ tf_py_test( name = "interleave_test", size = "medium", srcs = ["interleave_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:script_ops", "//tensorflow/python:sparse_ops", "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -286,11 +286,14 @@ tf_py_test( name = "iterator_cluster_test", size = "small", srcs = ["iterator_cluster_test.py"], - additional_deps = [ + grpc_enabled = True, + tags = [ + "no_oss", # Test flaky due to port collisions. + "no_windows", + ], + deps = [ ":test_base", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:iterator_ops", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -304,11 +307,8 @@ tf_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:session", "//tensorflow/python:string_ops", - ], - grpc_enabled = True, - tags = [ - "no_oss", # Test flaky due to port collisions. - "no_windows", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", ], ) @@ -316,17 +316,10 @@ cuda_py_test( name = "iterator_test", size = "medium", srcs = ["iterator_test.py"], - additional_deps = [ + grpc_enabled = True, + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/data/ops:readers", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:iterator_ops", - "//tensorflow/python/data/util:structure", - "//tensorflow/python/eager:context", - "//tensorflow/python/training/tracking:util", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -347,18 +340,25 @@ cuda_py_test( "//tensorflow/python:sparse_tensor", "//tensorflow/python:tensor_shape", "//tensorflow/python:training", - "//tensorflow/python/compat:compat", "//tensorflow/python:util", "//tensorflow/python:variables", + "//tensorflow/python/compat", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", + "//tensorflow/python/data/ops:readers", + "//tensorflow/python/data/util:structure", + "//tensorflow/python/eager:context", + "//tensorflow/python/training/tracking:util", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - grpc_enabled = True, ) tf_py_test( name = "list_files_test", size = "small", srcs = ["list_files_test.py"], - additional_deps = [ + deps = [ ":test_base", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -373,12 +373,10 @@ tf_py_test( name = "map_test", size = "medium", srcs = ["map_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -399,34 +397,36 @@ tf_py_test( "//tensorflow/python:tensor_array_ops", "//tensorflow/python:tensor_util", "//tensorflow/python:variable_scope", + "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/ops/ragged", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - shard_count = 4, ) cuda_py_test( name = "multi_device_iterator_test", size = "medium", srcs = ["multi_device_iterator_test.py"], - additional_deps = [ + tags = [ + "no_gpu", #TODO(b/141255188): Enable test after bug is resolved. + "no_oss", + "no_windows_gpu", + ], + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:multi_device_iterator_ops", - "//tensorflow/python/data/ops:iterator_ops", - "//tensorflow/python/data/experimental/ops:testing", - "//tensorflow/python/data/experimental/ops:optimization_options", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:framework_test_lib", - ], - tags = [ - "no_gpu", #TODO(b/141255188): Enable test after bug is resolved. - "no_oss", - "no_windows_gpu", + "//tensorflow/python/data/experimental/ops:optimization_options", + "//tensorflow/python/data/experimental/ops:testing", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", + "//tensorflow/python/data/ops:multi_device_iterator_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -434,32 +434,32 @@ cuda_py_test( name = "memory_cleanup_test", size = "medium", srcs = ["memory_cleanup_test.py"], - additional_deps = [ + tags = ["notsan"], # b/144706539 + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", "//tensorflow/core:protos_all_py", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:multi_device_iterator_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_test_lib", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:multi_device_iterator_ops", + "@absl_py//absl/testing:parameterized", ], - tags = ["notsan"], # b/144706539 ) tf_py_test( name = "as_numpy_iterator_test", size = "small", srcs = ["as_numpy_iterator_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/ops/ragged:ragged_tensor_value", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:framework_combinations", "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/ops/ragged:ragged_tensor_value", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -467,20 +467,20 @@ cuda_py_test( name = "optional_test", size = "small", srcs = ["optional_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:iterator_ops", - "//tensorflow/python/data/ops:optional_ops", - "//tensorflow/python:client_testlib", "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", "//tensorflow/python:tensor_shape", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", + "//tensorflow/python/data/ops:optional_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -488,12 +488,12 @@ tf_py_test( name = "options_test", size = "small", srcs = ["options_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python:client_testlib", "//tensorflow/python/data/experimental/ops:optimization_options", "//tensorflow/python/data/experimental/ops:threading_options", - "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", ], ) @@ -501,11 +501,8 @@ tf_py_test( name = "padded_batch_test", size = "small", srcs = ["padded_batch_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -515,6 +512,9 @@ tf_py_test( "//tensorflow/python:string_ops", "//tensorflow/python:tensor_shape", "//tensorflow/python:util", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -522,14 +522,14 @@ tf_py_test( name = "prefetch_test", size = "small", srcs = ["prefetch_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dataset_ops_gen", "//tensorflow/python:dtypes", "//tensorflow/python:errors", + "@absl_py//absl/testing:parameterized", ], ) @@ -537,12 +537,12 @@ tf_py_test( name = "range_test", size = "small", srcs = ["range_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_test_lib", + "//tensorflow/python/data/ops:dataset_ops", ], ) @@ -550,17 +550,17 @@ cuda_py_test( name = "reduce_test", size = "small", srcs = ["reduce_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:math_ops", - "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python:sparse_tensor", + "//tensorflow/python/data/experimental/ops:testing", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -568,14 +568,14 @@ tf_py_test( name = "repeat_test", size = "small", srcs = ["repeat_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -583,7 +583,7 @@ tf_py_test( name = "shard_test", size = "small", srcs = ["shard_test.py"], - additional_deps = [ + deps = [ ":test_base", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -595,10 +595,8 @@ tf_py_test( name = "shuffle_test", size = "small", srcs = ["shuffle_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -608,6 +606,8 @@ tf_py_test( "//tensorflow/python:random_seed", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/data/ops:iterator_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -615,14 +615,14 @@ tf_py_test( name = "skip_test", size = "small", srcs = ["skip_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -630,14 +630,14 @@ tf_py_test( name = "take_test", size = "small", srcs = ["take_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) @@ -645,16 +645,16 @@ tf_py_test( name = "text_line_dataset_test", size = "small", srcs = ["text_line_dataset_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//tensorflow/python/data/ops:iterator_ops", - "//tensorflow/python/data/ops:readers", - "//tensorflow/python/eager:context", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:util", + "//tensorflow/python/data/ops:iterator_ops", + "//tensorflow/python/data/ops:readers", + "//tensorflow/python/eager:context", ], ) @@ -662,11 +662,8 @@ tf_py_test( name = "tf_record_dataset_test", size = "small", srcs = ["tf_record_dataset_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:iterator_ops", - "//tensorflow/python/data/ops:readers", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -674,6 +671,9 @@ tf_py_test( "//tensorflow/python:errors", "//tensorflow/python:lib", "//tensorflow/python:util", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:iterator_ops", + "//tensorflow/python/data/ops:readers", ], ) @@ -699,9 +699,7 @@ tf_py_test( name = "unbatch_test", size = "medium", srcs = ["unbatch_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -717,6 +715,8 @@ tf_py_test( "//tensorflow/python/data/kernel_tests:test_base", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/ops/ragged", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -724,10 +724,8 @@ tf_py_test( name = "window_test", size = "medium", srcs = ["window_test.py"], - additional_deps = [ + deps = [ ":test_base", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -735,6 +733,8 @@ tf_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:sparse_tensor", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -742,13 +742,13 @@ tf_py_test( name = "zip_test", size = "small", srcs = ["zip_test.py"], - additional_deps = [ + deps = [ ":test_base", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", ], ) diff --git a/tensorflow/python/data/kernel_tests/dataset_test.py b/tensorflow/python/data/kernel_tests/dataset_test.py index df151a85db0..58a9a2ee337 100644 --- a/tensorflow/python/data/kernel_tests/dataset_test.py +++ b/tensorflow/python/data/kernel_tests/dataset_test.py @@ -52,7 +52,7 @@ class DatasetTest(test_base.DatasetTestBase, parameterized.TestCase): dataset = dataset_ops.Dataset.range(10) graph = graph_pb2.GraphDef().FromString( self.evaluate(dataset._as_serialized_graph())) - self.assertTrue(any([node.op == "RangeDataset" for node in graph.node])) + self.assertTrue(any(node.op == "RangeDataset" for node in graph.node)) def testAsSerializedGraphStateful(self): dataset = dataset_ops.Dataset.range(10).map( diff --git a/tensorflow/python/data/kernel_tests/shuffle_test.py b/tensorflow/python/data/kernel_tests/shuffle_test.py index c9d17b79016..a42abb5e432 100644 --- a/tensorflow/python/data/kernel_tests/shuffle_test.py +++ b/tensorflow/python/data/kernel_tests/shuffle_test.py @@ -331,6 +331,24 @@ class ShuffleTest(test_base.DatasetTestBase, parameterized.TestCase): with self.assertRaises(errors.OutOfRangeError): self.evaluate(get_next()) + @combinations.generate( + combinations.times(test_base.default_test_combinations(), + combinations.combine(reshuffle=[True, False]))) + def testRerandomizeOnReplicate(self, reshuffle): + random_seed.set_random_seed(None) + # When no seeds are fixed, each instantiation of the shuffle dataset should + # produce elements in a different order. + num_elements = 100 + dataset = dataset_ops.Dataset.range(num_elements) + dataset = dataset.shuffle(num_elements, reshuffle_each_iteration=reshuffle) + + shuffle_1 = self.getDatasetOutput(dataset) + dataset = self.graphRoundTrip(dataset, allow_stateful=True) + shuffle_2 = self.getDatasetOutput(dataset) + + self.assertCountEqual(shuffle_1, shuffle_2) + self.assertNotEqual(shuffle_1, shuffle_2) + if __name__ == "__main__": test.main() diff --git a/tensorflow/python/data/kernel_tests/test_base.py b/tensorflow/python/data/kernel_tests/test_base.py index 60796b178bf..4b92fea9feb 100644 --- a/tensorflow/python/data/kernel_tests/test_base.py +++ b/tensorflow/python/data/kernel_tests/test_base.py @@ -29,6 +29,8 @@ from tensorflow.python.framework import errors from tensorflow.python.framework import ops from tensorflow.python.framework import sparse_tensor from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_dataset_ops +from tensorflow.python.ops import gen_experimental_dataset_ops from tensorflow.python.ops import tensor_array_ops from tensorflow.python.ops.ragged import ragged_tensor from tensorflow.python.platform import test @@ -129,6 +131,17 @@ class DatasetTestBase(test.TestCase): nest.flatten(result_values[i]), nest.flatten(expected_values[i])): self.assertValuesEqual(expected_value, result_value) + def getDatasetOutput(self, dataset, requires_initialization=False): + get_next = self.getNext( + dataset, requires_initialization=requires_initialization) + results = [] + while True: + try: + results.append(self.evaluate(get_next())) + except errors.OutOfRangeError: + break + return results + def assertDatasetProduces(self, dataset, expected_output=None, @@ -265,6 +278,14 @@ class DatasetTestBase(test.TestCase): for substructure in dataset_structure ])) + def graphRoundTrip(self, dataset, allow_stateful=False): + """Converts a dataset to a graph and back.""" + graph = gen_dataset_ops.dataset_to_graph( + dataset._variant_tensor, allow_stateful=allow_stateful) # pylint: disable=protected-access + return dataset_ops.from_variant( + gen_experimental_dataset_ops.dataset_from_graph(graph), + dataset.element_spec) + def structuredElement(self, element_structure, shape=None, dtype=dtypes.int64): """Returns an element with the given structure.""" diff --git a/tensorflow/python/data/kernel_tests/text_line_dataset_test.py b/tensorflow/python/data/kernel_tests/text_line_dataset_test.py index 35b479faa21..2a81dff0058 100644 --- a/tensorflow/python/data/kernel_tests/text_line_dataset_test.py +++ b/tensorflow/python/data/kernel_tests/text_line_dataset_test.py @@ -103,13 +103,13 @@ class TextLineDatasetTest(test_base.DatasetTestBase, parameterized.TestCase): # Basic test: read from both files. expected_output = [self._lineText(0, i) for i in range(5)] - expected_output.extend([self._lineText(1, i) for i in range(5)]) + expected_output.extend(self._lineText(1, i) for i in range(5)) self.assertDatasetProduces( dataset_fn(test_filenames, 1), expected_output=expected_output) # Test repeated iteration through both files. expected_output = [self._lineText(0, i) for i in range(5)] - expected_output.extend([self._lineText(1, i) for i in range(5)]) + expected_output.extend(self._lineText(1, i) for i in range(5)) self.assertDatasetProduces( dataset_fn(test_filenames, 10), expected_output=expected_output * 10) @@ -125,7 +125,7 @@ class TextLineDatasetTest(test_base.DatasetTestBase, parameterized.TestCase): files = dataset_ops.Dataset.from_tensor_slices(test_filenames).repeat(10) expected_output = [] for j in range(10): - expected_output.extend([self._lineText(j, i) for i in range(10)]) + expected_output.extend(self._lineText(j, i) for i in range(10)) dataset = readers.TextLineDataset(files, num_parallel_reads=4) self.assertDatasetProduces( dataset, expected_output=expected_output * 10, assert_items_equal=True) diff --git a/tensorflow/python/data/ops/dataset_ops.py b/tensorflow/python/data/ops/dataset_ops.py index 06bdfd03eb8..a9837990011 100644 --- a/tensorflow/python/data/ops/dataset_ops.py +++ b/tensorflow/python/data/ops/dataset_ops.py @@ -115,7 +115,7 @@ class DatasetV2(tracking_base.Trackable, composite_tensor.CompositeTensor): Iteration happens in a streaming fashion, so the full dataset does not need to fit into memory. - # Source Datasets + Source Datasets: The simplest way to create a dataset is to create it from a python `list`: @@ -142,7 +142,7 @@ class DatasetV2(tracking_base.Trackable, composite_tensor.CompositeTensor): See `tf.data.FixedLengthRecordDataset` and `tf.data.Dataset.from_generator` for more ways to create datasets. - # Transformations + Transformations: Once you have a dataset, you can apply transformations to prepare the data for your model: @@ -152,7 +152,7 @@ class DatasetV2(tracking_base.Trackable, composite_tensor.CompositeTensor): >>> list(dataset.as_numpy_iterator()) [2, 4, 6] - # Common Terms + Common Terms: **Element**: A single output from calling `next()` on a dataset iterator. Elements may be nested structures containing multiple components. For @@ -160,7 +160,7 @@ class DatasetV2(tracking_base.Trackable, composite_tensor.CompositeTensor): tuple. The components are `1`, `3`, and `"apple"`. **Component**: The leaf in the nested structure of an element. - # Supported types + Supported types: Elements can be nested structures of tuples, named tuples, and dictionaries. Element components can be of any type representable by `tf.TypeSpec`, @@ -174,6 +174,7 @@ class DatasetV2(tracking_base.Trackable, composite_tensor.CompositeTensor): >>> Point = collections.namedtuple("Point", ["x", "y"]) # doctest: +SKIP >>> e = Point(1, 2) # Named tuple # doctest: +SKIP >>> f = tf.data.Dataset.range(10) # Dataset element + """ def __init__(self, variant_tensor): @@ -311,10 +312,10 @@ class DatasetV2(tracking_base.Trackable, composite_tensor.CompositeTensor): # If the captured tensor is an eager tensor, we cannot trace its inputs. if isinstance(tensor, ops._EagerTensorBase): # pylint: disable=protected-access return False - return any([is_tensor_or_parent_ref(x) for x in tensor.op.inputs]) + return any(is_tensor_or_parent_ref(x) for x in tensor.op.inputs) for fn in self._functions(): - if any([is_tensor_or_parent_ref(t) for t in fn.function.captured_inputs]): + if any(is_tensor_or_parent_ref(t) for t in fn.function.captured_inputs): return True return any( diff --git a/tensorflow/python/data/util/traverse_test.py b/tensorflow/python/data/util/traverse_test.py index 2e847aa6a1a..64df77d6b6f 100644 --- a/tensorflow/python/data/util/traverse_test.py +++ b/tensorflow/python/data/util/traverse_test.py @@ -53,7 +53,7 @@ class TraverseTest(test.TestCase): variant_tensor_ops = traverse.obtain_all_variant_tensor_ops(ds) self.assertSetEqual( set(["MapDataset", "RangeDataset"]), - set([x.name for x in variant_tensor_ops])) + set(x.name for x in variant_tensor_ops)) @test_util.run_deprecated_v1 def testConcat(self): @@ -63,7 +63,7 @@ class TraverseTest(test.TestCase): variant_tensor_ops = traverse.obtain_all_variant_tensor_ops(ds) self.assertSetEqual( set(["ConcatenateDataset", "RangeDataset", "RangeDataset_1"]), - set([x.name for x in variant_tensor_ops])) + set(x.name for x in variant_tensor_ops)) @test_util.run_deprecated_v1 def testZip(self): @@ -73,7 +73,7 @@ class TraverseTest(test.TestCase): variant_tensor_ops = traverse.obtain_all_variant_tensor_ops(ds) self.assertSetEqual( set(["ZipDataset", "RangeDataset", "RangeDataset_1"]), - set([x.name for x in variant_tensor_ops])) + set(x.name for x in variant_tensor_ops)) @test_util.run_deprecated_v1 def testMultipleVariantTensors(self): @@ -82,7 +82,7 @@ class TraverseTest(test.TestCase): variant_tensor_ops = traverse.obtain_all_variant_tensor_ops(ds) self.assertSetEqual( set(["RangeDataset", "ModelDataset", "PrefetchDataset"]), - set([x.name for x in variant_tensor_ops])) + set(x.name for x in variant_tensor_ops)) @test_util.run_deprecated_v1 def testFlatMap(self): @@ -102,7 +102,7 @@ class TraverseTest(test.TestCase): set([ "FlatMapDataset", "PrefetchDataset", "RepeatDataset", "RangeDataset", "RangeDataset_1" - ]), set([x.name for x in variant_tensor_ops])) + ]), set(x.name for x in variant_tensor_ops)) if __name__ == "__main__": diff --git a/tensorflow/python/debug/BUILD b/tensorflow/python/debug/BUILD index 43592e63fa8..c3b49e7564a 100644 --- a/tensorflow/python/debug/BUILD +++ b/tensorflow/python/debug/BUILD @@ -695,36 +695,21 @@ cuda_py_test( name = "check_numerics_callback_test", size = "medium", srcs = ["lib/check_numerics_callback_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":check_numerics_callback", - "//third_party/py/numpy", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_test( name = "distributed_callbacks_test", size = "medium", srcs = ["lib/distributed_callbacks_test.py"], - additional_deps = [ - ":check_numerics_callback", - ":debug_events_writer", - ":dumping_callback", - ":dumping_callback_test_lib", - "//third_party/py/numpy", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform_test", - "//tensorflow/python:variables", - "//tensorflow/python/distribute:combinations", - "//tensorflow/python/distribute:mirrored_strategy", - "//tensorflow/python/distribute:strategy_combinations", - "//tensorflow/python/keras", - ], python_version = "PY3", shard_count = 4, tags = [ @@ -735,41 +720,59 @@ cuda_py_test( "no_windows_gpu", # TODO(b/130551176) ], xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ + ":check_numerics_callback", + ":debug_events_writer", + ":dumping_callback", + ":dumping_callback_test_lib", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:variables", + "//tensorflow/python/distribute:combinations", + "//tensorflow/python/distribute:mirrored_strategy", + "//tensorflow/python/distribute:strategy_combinations", + "//tensorflow/python/keras", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "dumping_callback_test", size = "medium", srcs = ["lib/dumping_callback_test.py"], - additional_deps = [ - ":debug_events_reader", - ":debug_events_writer", - ":dumping_callback", - ":dumping_callback_test_lib", - "//third_party/py/numpy", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform_test", - "//tensorflow/python:variables", - "//tensorflow/python/keras", - ], python_version = "PY3", shard_count = 8, tags = [ "no_windows", # TODO(b/142475891): Enable this test on Windows. ], xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ + ":debug_events_reader", + ":debug_events_writer", + ":dumping_callback", + ":dumping_callback_test_lib", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:variables", + "//tensorflow/python/keras", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "debug_v2_ops_test", size = "medium", srcs = ["lib/debug_v2_ops_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "no_windows", # b/142475891 + ], + deps = [ ":debug_events_reader", ":debug_events_writer", ":dumping_callback_test_lib", - "//third_party/py/numpy", "//tensorflow/python:debug_ops_gen", "//tensorflow/python:framework_test_lib", "//tensorflow/python:gradients", @@ -779,10 +782,7 @@ cuda_py_test( "//tensorflow/python:variables", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/keras", - ], - python_version = "PY3", - tags = [ - "no_windows", # b/142475891 + "//third_party/py/numpy", ], ) @@ -790,7 +790,9 @@ cuda_py_test( name = "debug_gradients_test", size = "small", srcs = ["lib/debug_gradients_test.py"], - additional_deps = [ + python_version = "PY3", + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":debug_data", ":debug_gradients", ":debug_utils", @@ -802,8 +804,6 @@ cuda_py_test( "//tensorflow/python:training", "//tensorflow/python:variables", ], - python_version = "PY3", - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) py_test( @@ -1030,7 +1030,9 @@ cuda_py_test( name = "debug_grappler_test", size = "small", srcs = ["lib/debug_grappler_test.py"], - additional_deps = [ + python_version = "PY3", + xla_enable_strict_auto_jit = False, # Tests TF:Classic implementation. + deps = [ ":debug_data", ":debug_utils", "//tensorflow/python:client", @@ -1039,15 +1041,16 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:variables", ], - python_version = "PY3", - xla_enable_strict_auto_jit = False, # Tests TF:Classic implementation. ) cuda_py_test( name = "session_debug_file_test", size = "small", srcs = ["lib/session_debug_file_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["notsan"], + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":debug_data", ":debug_utils", ":session_debug_testlib", @@ -1057,16 +1060,15 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:variables", ], - python_version = "PY3", - tags = ["notsan"], - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) cuda_py_test( name = "debug_graph_reconstruction_test", size = "small", srcs = ["lib/debug_graph_reconstruction_test.py"], - additional_deps = [ + python_version = "PY3", + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":debug_data", ":debug_utils", "//tensorflow/python:client", @@ -1077,15 +1079,16 @@ cuda_py_test( "//tensorflow/python:training", "//tensorflow/python:variables", ], - python_version = "PY3", - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) cuda_py_test( name = "session_debug_multi_gpu_test", size = "small", srcs = ["lib/session_debug_multi_gpu_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows_gpu"], + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":debug_data", ":debug_utils", "//tensorflow/python:client", @@ -1095,9 +1098,6 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:variables", ], - python_version = "PY3", - tags = ["no_windows_gpu"], - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) py_test( @@ -1205,7 +1205,10 @@ cuda_py_test( name = "analyzer_cli_test", size = "small", srcs = ["cli/analyzer_cli_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_windows"], # TODO: needs investigation on Windows + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":analyzer_cli", ":cli_config", ":cli_test_utils", @@ -1214,8 +1217,6 @@ cuda_py_test( ":debug_utils", ":debugger_cli_common", ":source_utils", - "//third_party/py/numpy", - "@six_archive//:six", "//tensorflow:tensorflow_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -1227,10 +1228,9 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:util", "//tensorflow/python:variables", + "//third_party/py/numpy", + "@six_archive//:six", ], - python_version = "PY3", - tags = ["no_windows"], # TODO: needs investigation on Windows - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) py_test( @@ -1258,7 +1258,15 @@ cuda_py_test( name = "session_debug_grpc_test", size = "medium", srcs = ["lib/session_debug_grpc_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "no_oss", # Test flaky due to port collisions. + "no_windows", + "notsan", + "oss_serial", + ], + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":debug_data", ":debug_utils", ":dumping_wrapper", @@ -1273,32 +1281,12 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:variables", ], - python_version = "PY3", - tags = [ - "no_oss", # Test flaky due to port collisions. - "no_windows", - "notsan", - "oss_serial", - ], - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) cuda_py_test( name = "grpc_large_data_test", size = "medium", srcs = ["lib/grpc_large_data_test.py"], - additional_deps = [ - ":dumping_wrapper", - ":grpc_debug_test_server", - ":grpc_wrapper", - ":session_debug_testlib", - "//third_party/py/numpy", - "//tensorflow/python:client", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:platform_test", - "//tensorflow/python:variables", - ], python_version = "PY3", tags = [ "no_oss", # Test flaky due to port collisions. @@ -1308,6 +1296,18 @@ cuda_py_test( "oss_serial", ], xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ + ":dumping_wrapper", + ":grpc_debug_test_server", + ":grpc_wrapper", + ":session_debug_testlib", + "//tensorflow/python:client", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:platform_test", + "//tensorflow/python:variables", + "//third_party/py/numpy", + ], ) # TODO(cais): Run the test in OSS, perhaps through a sh_test. @@ -1315,7 +1315,15 @@ cuda_py_test( name = "dist_session_debug_grpc_test", size = "medium", srcs = ["lib/dist_session_debug_grpc_test.py"], - additional_deps = [ + grpc_enabled = True, + python_version = "PY3", + tags = [ + "no_oss", # Incompatible with bazel_pip. + "no_windows", + "notsan", + ], + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":debug_data", ":debug_utils", ":dumping_wrapper", @@ -1329,14 +1337,6 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:variables", ], - grpc_enabled = True, - python_version = "PY3", - tags = [ - "no_oss", # Incompatible with bazel_pip. - "no_windows", - "notsan", - ], - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) py_test( diff --git a/tensorflow/python/debug/cli/analyzer_cli.py b/tensorflow/python/debug/cli/analyzer_cli.py index 5c21b1518ed..0be1f5894c2 100644 --- a/tensorflow/python/debug/cli/analyzer_cli.py +++ b/tensorflow/python/debug/cli/analyzer_cli.py @@ -1200,12 +1200,12 @@ class DebugAnalyzer(object): return debugger_cli_common.rich_text_lines_from_rich_line_list(lines) path_column_width = max( - max([len(item[0]) for item in source_list]), len(path_head)) + 1 + max(len(item[0]) for item in source_list), len(path_head)) + 1 num_nodes_column_width = max( - max([len(str(item[2])) for item in source_list]), + max(len(str(item[2])) for item in source_list), len(num_nodes_head)) + 1 num_tensors_column_width = max( - max([len(str(item[3])) for item in source_list]), + max(len(str(item[3])) for item in source_list), len(num_tensors_head)) + 1 head = RL(path_head + " " * (path_column_width - len(path_head)), color) diff --git a/tensorflow/python/debug/cli/curses_ui_test.py b/tensorflow/python/debug/cli/curses_ui_test.py index ba7886b87e4..3c09ad64876 100644 --- a/tensorflow/python/debug/cli/curses_ui_test.py +++ b/tensorflow/python/debug/cli/curses_ui_test.py @@ -42,7 +42,7 @@ def string_to_codes(cmd): def codes_to_string(cmd_code): # Omit non-ASCII key codes. - return "".join([chr(code) for code in cmd_code if code < 256]) + return "".join(chr(code) for code in cmd_code if code < 256) class MockCursesUI(curses_ui.CursesUI): diff --git a/tensorflow/python/debug/cli/profile_analyzer_cli.py b/tensorflow/python/debug/cli/profile_analyzer_cli.py index 0e846d2d2d9..4dcb16677cc 100644 --- a/tensorflow/python/debug/cli/profile_analyzer_cli.py +++ b/tensorflow/python/debug/cli/profile_analyzer_cli.py @@ -624,7 +624,7 @@ class ProfileAnalyzer(object): device_stats = self._run_metadata.step_stats.dev_stats[index] if device_name_regex and not device_name_regex.match(device_stats.device): continue - profile_data.extend([datum for datum in data_generator(device_stats)]) + profile_data.extend(data_generator(device_stats)) source_annotation = source_utils.annotate_source_against_profile( profile_data, diff --git a/tensorflow/python/debug/lib/debug_grappler_test.py b/tensorflow/python/debug/lib/debug_grappler_test.py index 5fa2cfe6083..0e9f5517860 100644 --- a/tensorflow/python/debug/lib/debug_grappler_test.py +++ b/tensorflow/python/debug/lib/debug_grappler_test.py @@ -88,7 +88,7 @@ class SessionDebugGrapplerInteractionTest(test_util.TensorFlowTestCase): self._dump_root, partition_graphs=run_metadata.partition_graphs, validate=True) - original_node_names = set([op.name for op in sess.graph.get_operations()]) + original_node_names = set(op.name for op in sess.graph.get_operations()) dumped_node_names = set(dump_data.nodes()) grappler_created_node_names = dumped_node_names - original_node_names grappler_removed_node_names = original_node_names - dumped_node_names diff --git a/tensorflow/python/debug/lib/dumping_callback.py b/tensorflow/python/debug/lib/dumping_callback.py index b0cac891dde..2532bd2e7e3 100644 --- a/tensorflow/python/debug/lib/dumping_callback.py +++ b/tensorflow/python/debug/lib/dumping_callback.py @@ -23,7 +23,6 @@ import re import socket import threading import uuid -import weakref from six.moves import xrange # pylint: disable=redefined-builtin @@ -88,7 +87,7 @@ class _DumpingCallback(object): self._stack_frame_to_id = dict() # Mapping op context to unique ID. self._context_to_id = dict() - self._function_weakref_to_graph_id = dict() + self._function_to_graph_id = dict() # pylint:disable=protected-access self._function_prefixes = ( compat.as_bytes(function_lib._FORWARD_PREFIX), @@ -112,10 +111,15 @@ class _DumpingCallback(object): Args: function: The just-created Function. """ - function_weakref = weakref.ref(function) graph_id = self._get_context_id(function.graph) with self._context_lock: - self._function_weakref_to_graph_id[function_weakref] = graph_id + # NOTE(cais): We currently store the function (_EagerDefinedFunction) + # as keys of this dict, because weakrefs to them sometimes become + # unreferenceable by the time the op callback is called. This approach + # may cause memory leaks due to the holding of the functions. If that's + # the case, calling `tf.debugging.disable_dump_debug_info()` should + # cause GC of this object and this dict. + self._function_to_graph_id[function] = graph_id @property def dump_root(self): @@ -467,10 +471,13 @@ class _DumpingCallback(object): return self._instrument_symbolic_tensors( outputs, op_type, op_name, context_id, output_tensor_ids) else: - if compat.as_bytes(op_type) == b"DebugNumericSummaryV2": + op_type_bytes = compat.as_bytes(op_type) + if op_type_bytes == b"DebugNumericSummaryV2": # TODO(b/140334369): Remove this special casing logic once op_callback. # automatically prevents infinite recursion in eager mode. return None + if op_type_bytes in op_callbacks_common.OP_CALLBACK_SKIP_OPS: + return None context_id = self._func_graph_id_from_func_name(op_type) input_ids = [t._id for t in inputs] # pylint:disable=protected-access writer.WriteExecution(self._dump_eager_tensors( @@ -502,9 +509,9 @@ class _DumpingCallback(object): if op_type in self._op_type_to_context_id: return self._op_type_to_context_id[op_type] with self._context_lock: - for function_weakref in self._function_weakref_to_graph_id: - if function_weakref().name == op_type: - graph_id = self._function_weakref_to_graph_id[function_weakref] + for function in self._function_to_graph_id: + if function.name == op_type: + graph_id = self._function_to_graph_id[function] self._op_type_to_context_id[op_type] = graph_id return graph_id return None diff --git a/tensorflow/python/debug/lib/op_callbacks_common.py b/tensorflow/python/debug/lib/op_callbacks_common.py index 76d44dd36d5..1848bd43a3a 100644 --- a/tensorflow/python/debug/lib/op_callbacks_common.py +++ b/tensorflow/python/debug/lib/op_callbacks_common.py @@ -41,4 +41,6 @@ OP_CALLBACK_SKIP_OPS = ( b"TPUCompilationResult", b"TPUReplicatedOutput", b"ConfigureDistributedTPU", + # Other special ops used by TensorFlow internally. + b"DestroyResourceOp", ) diff --git a/tensorflow/python/distribute/BUILD b/tensorflow/python/distribute/BUILD index a04e0b2b3ea..16ed490dd8b 100644 --- a/tensorflow/python/distribute/BUILD +++ b/tensorflow/python/distribute/BUILD @@ -1,6 +1,6 @@ load("//tensorflow:tensorflow.bzl", "py_test", "tf_py_test") load("//tensorflow:tensorflow.bzl", "cuda_py_test") -load("//tensorflow/core/platform:default/distribute.bzl", "distribute_py_test") +load("//tensorflow/core/platform/default:distribute.bzl", "distribute_py_test") package( default_visibility = ["//tensorflow:internal"], @@ -41,20 +41,20 @@ py_library( tf_py_test( name = "all_reduce_test", srcs = ["all_reduce_test.py"], - additional_deps = [ + deps = [ ":all_reduce", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", + "//tensorflow/python:client_testlib", + "//tensorflow/python:constant_op", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", - "//tensorflow/python:constant_op", - "//tensorflow/python:client_testlib", "//tensorflow/python:platform", "//tensorflow/python:platform_test", "//tensorflow/python:state_ops", + "//third_party/py/numpy", ], ) @@ -113,7 +113,7 @@ py_library( cuda_py_test( name = "device_util_test", srcs = ["device_util_test.py"], - additional_deps = [ + deps = [ ":device_util", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", @@ -170,7 +170,7 @@ py_test( name = "distribute_lib_test", size = "small", srcs = ["distribute_lib_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = [ "no_rocm", @@ -221,7 +221,7 @@ py_test( name = "distribute_coordinator_test", size = "medium", srcs = ["distribute_coordinator_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = ["no_oss_py2"], # b/138443278 deps = [ @@ -379,10 +379,11 @@ py_library( tf_py_test( name = "mirrored_function_strategy_test", srcs = ["mirrored_function_strategy_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ ":distribute_lib", - ":strategy_combinations", ":mirrored_function_strategy", + ":strategy_combinations", ":values", "//tensorflow/python:constant_op", "//tensorflow/python:framework_ops", @@ -390,7 +391,6 @@ tf_py_test( "//tensorflow/python/eager:def_function", "//tensorflow/python/eager:test", ], - tags = ["no_pip"], ) py_library( @@ -410,14 +410,13 @@ cuda_py_test( srcs = [ "multi_worker_continuous_run_test.py", ], - additional_deps = [ + deps = [ ":collective_all_reduce_strategy", ":multi_process_runner", ":reduce_util", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:errors", + "//tensorflow/python:framework_ops", "//tensorflow/python:math_ops", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/distribute:combinations", @@ -425,7 +424,8 @@ cuda_py_test( "//tensorflow/python/distribute:strategy_combinations", "//tensorflow/python/eager:context", "//tensorflow/python/eager:test", - "//tensorflow/python:framework_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -448,7 +448,7 @@ py_test( name = "numpy_dataset_test", size = "small", srcs = ["numpy_dataset_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":numpy_dataset", @@ -487,24 +487,24 @@ py_library( cuda_py_test( name = "input_ops_test", srcs = ["input_ops_test.py"], - additional_deps = [ + deps = [ ":input_ops", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python/data/ops:readers", - "//tensorflow/python/data/util:structure", - "//tensorflow/python:errors", "//tensorflow/python:client_testlib", + "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", "//tensorflow/python:io_ops", "//tensorflow/python:util", + "//tensorflow/python/data/ops:dataset_ops", + "//tensorflow/python/data/ops:readers", + "//tensorflow/python/data/util:structure", ], ) py_test( name = "multi_worker_util_test", srcs = ["multi_worker_util_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":multi_worker_util", @@ -585,7 +585,7 @@ py_library( py_test( name = "shared_variable_creator_test", srcs = ["shared_variable_creator_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":shared_variable_creator", @@ -659,7 +659,7 @@ py_library( py_test( name = "strategy_combinations_test", srcs = ["strategy_combinations_test.py"], - python_version = "PY2", + python_version = "PY3", deps = [ ":combinations", ":reduce_util", @@ -704,17 +704,17 @@ cuda_py_test( name = "checkpoint_utils_test", size = "medium", srcs = ["checkpoint_utils_test.py"], - additional_deps = [ - "//tensorflow/python/distribute:combinations", - "//tensorflow/python/distribute:strategy_combinations", + tags = [ + "multi_and_single_gpu", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", "//tensorflow/python:training", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", - ], - tags = [ - "multi_and_single_gpu", + "//tensorflow/python/distribute:combinations", + "//tensorflow/python/distribute:strategy_combinations", ], ) @@ -764,54 +764,54 @@ distribute_py_test( cuda_py_test( name = "cross_device_utils_test", srcs = ["cross_device_utils_test.py"], - additional_deps = [ - "//tensorflow/python/distribute:combinations", - "//tensorflow/python/distribute:strategy_combinations", - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/python:constant_op", "//tensorflow/python:framework_ops", "//tensorflow/python:math_ops", + "//tensorflow/python/distribute:combinations", "//tensorflow/python/distribute:cross_device_utils", + "//tensorflow/python/distribute:strategy_combinations", "//tensorflow/python/eager:context", "//tensorflow/python/eager:test", + "@absl_py//absl/testing:parameterized", ], ) cuda_py_test( name = "cross_device_ops_test", srcs = ["cross_device_ops_test.py"], - additional_deps = [ + tags = [ + # TODO(b/138143527): Re-enable after fixing Guitar failure. + # "multi_and_single_gpu", + ], + deps = [ ":collective_all_reduce_strategy", - "//tensorflow/python/distribute:combinations", - "//tensorflow/python/distribute:strategy_combinations", - "//tensorflow/python/distribute:multi_worker_test_base", ":mirrored_strategy", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:array_ops", "//tensorflow/python:constant_op", "//tensorflow/python:framework_ops", "//tensorflow/python:math_ops", + "//tensorflow/python/distribute:combinations", "//tensorflow/python/distribute:cross_device_ops", + "//tensorflow/python/distribute:multi_worker_test_base", + "//tensorflow/python/distribute:strategy_combinations", "//tensorflow/python/distribute:values", "//tensorflow/python/eager:context", "//tensorflow/python/eager:test", - ], - tags = [ - # TODO(b/138143527): Re-enable after fixing Guitar failure. - # "multi_and_single_gpu", + "@absl_py//absl/testing:parameterized", ], ) cuda_py_test( name = "one_device_strategy_test", srcs = ["one_device_strategy_test.py"], - additional_deps = [ + grpc_enabled = True, + deps = [ ":strategy_test_lib", "//tensorflow/python/distribute:combinations", "//tensorflow/python/distribute:strategy_combinations", "//tensorflow/python/eager:test", ], - grpc_enabled = True, ) py_library( @@ -830,7 +830,7 @@ tf_py_test( name = "sharded_variable_test", size = "small", srcs = ["sharded_variable_test.py"], - additional_deps = [ + deps = [ ":sharded_variable", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -1014,24 +1014,24 @@ cuda_py_test( name = "warm_starting_util_test", size = "medium", srcs = ["warm_starting_util_test.py"], - additional_deps = [ - "//tensorflow/python/distribute:combinations", - "//tensorflow/python/distribute:strategy_combinations", + tags = [ + "multi_and_single_gpu", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", "//tensorflow/python:training", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", - ], - tags = [ - "multi_and_single_gpu", + "//tensorflow/python/distribute:combinations", + "//tensorflow/python/distribute:strategy_combinations", ], ) cuda_py_test( name = "remote_mirrored_strategy_eager_test", srcs = ["remote_mirrored_strategy_eager_test.py"], - additional_deps = [ + deps = [ ":combinations", ":mirrored_strategy", ":multi_worker_test_base", @@ -1054,11 +1054,18 @@ cuda_py_test( cuda_py_test( name = "mirrored_strategy_test", srcs = ["mirrored_strategy_test.py"], - additional_deps = [ + shard_count = 5, + tags = [ + "guitar", + "multi_and_single_gpu", + "no_rocm", + "no_windows_gpu", # TODO(b/130551176) + ], + deps = [ ":combinations", - ":strategy_combinations", ":mirrored_strategy", ":multi_worker_test_base", + ":strategy_combinations", ":strategy_test_lib", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", @@ -1069,24 +1076,22 @@ cuda_py_test( "//tensorflow/python:tensor_shape", "//tensorflow/python:tensor_util", "//tensorflow/python:variable_scope", + "//tensorflow/python/autograph/core:test_lib", "//tensorflow/python/distribute:distribute_lib", "//tensorflow/python/distribute:values", "//tensorflow/python/eager:context", "//tensorflow/python/eager:test", ], - shard_count = 5, - tags = [ - "guitar", - "multi_and_single_gpu", - "no_rocm", - "no_windows_gpu", # TODO(b/130551176) - ], ) cuda_py_test( name = "mirrored_variable_test", srcs = ["mirrored_variable_test.py"], - additional_deps = [ + tags = [ + "guitar", + "multi_and_single_gpu", + ], + deps = [ ":collective_all_reduce_strategy", ":combinations", ":strategy_combinations", @@ -1102,10 +1107,6 @@ cuda_py_test( "//tensorflow/python/eager:context", "//tensorflow/python/eager:test", ], - tags = [ - "guitar", - "multi_and_single_gpu", - ], ) distribute_py_test( @@ -1254,14 +1255,16 @@ distribute_py_test( cuda_py_test( name = "collective_all_reduce_strategy_test", srcs = ["collective_all_reduce_strategy_test.py"], - additional_deps = [ + tags = [ + "multi_and_single_gpu", + ], + xla_enable_strict_auto_jit = False, + deps = [ ":collective_all_reduce_strategy", ":combinations", - ":strategy_combinations", ":multi_worker_test_base", + ":strategy_combinations", ":strategy_test_lib", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -1277,24 +1280,27 @@ cuda_py_test( "//tensorflow/python/eager:context", "//tensorflow/python/estimator:estimator_py", "//tensorflow/python/keras/mixed_precision/experimental:test_util", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - tags = [ - "multi_and_single_gpu", - ], - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "parameter_server_strategy_test", srcs = ["parameter_server_strategy_test.py"], - additional_deps = [ - ":parameter_server_strategy", + tags = [ + "multi_and_single_gpu", + "no_oss", # TODO(b/133330625) + ], + # b/141096229: Non-atomic AssignAdd + xla_enable_strict_auto_jit = False, + deps = [ ":central_storage_strategy", ":combinations", - ":strategy_combinations", ":multi_worker_test_base", + ":parameter_server_strategy", + ":strategy_combinations", ":strategy_test_lib", - "@absl_py//absl/testing:parameterized", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -1312,13 +1318,8 @@ cuda_py_test( "//tensorflow/python/distribute:values", "//tensorflow/python/eager:context", "//tensorflow/python/estimator:estimator_py", + "@absl_py//absl/testing:parameterized", ], - tags = [ - "multi_and_single_gpu", - "no_oss", # TODO(b/133330625) - ], - # b/141096229: Non-atomic AssignAdd - xla_enable_strict_auto_jit = False, ) py_library( diff --git a/tensorflow/python/distribute/cluster_resolver/BUILD b/tensorflow/python/distribute/cluster_resolver/BUILD index 8793543b283..4ea961c273d 100644 --- a/tensorflow/python/distribute/cluster_resolver/BUILD +++ b/tensorflow/python/distribute/cluster_resolver/BUILD @@ -60,58 +60,14 @@ py_library( ], ) -py_library( - name = "cloud_tpu_client", - srcs = ["cloud_tpu_client.py"], - srcs_version = "PY2AND3", - deps = [ - "//tensorflow/python:util", - "@six_archive//:six", - ], -) - -tf_py_test( - name = "cloud_tpu_client_py_test", - size = "small", - srcs = ["cloud_tpu_client_test.py"], - additional_deps = [ - ":cloud_tpu_client", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:platform_test", - "//tensorflow/python:training_server_lib", - ], - grpc_enabled = True, - main = "cloud_tpu_client_test.py", - python_version = "PY3", -) - -tf_py_test( - name = "cloud_tpu_client_py2_test", - size = "small", - srcs = ["cloud_tpu_client_test.py"], - additional_deps = [ - ":cloud_tpu_client", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:platform_test", - "//tensorflow/python:training_server_lib", - ], - grpc_enabled = True, - main = "cloud_tpu_client_test.py", - python_version = "PY2", -) - py_library( name = "tpu_cluster_resolver_py", srcs = ["tpu_cluster_resolver.py"], srcs_version = "PY2AND3", deps = [ - ":cloud_tpu_client", ":base_cluster_resolver_py", "//tensorflow/python:training_server_lib", + "//tensorflow/python/tpu/client", ] + tf_additional_rpc_deps(), ) @@ -138,7 +94,8 @@ py_library( tf_py_test( name = "base_cluster_resolver_py_test", srcs = ["cluster_resolver_test.py"], - additional_deps = [ + main = "cluster_resolver_test.py", + deps = [ ":base_cluster_resolver_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -146,14 +103,14 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:training_server_lib", ], - main = "cluster_resolver_test.py", ) tf_py_test( name = "gce_cluster_resolver_py_test", size = "small", srcs = ["gce_cluster_resolver_test.py"], - additional_deps = [ + main = "gce_cluster_resolver_test.py", + deps = [ ":gce_cluster_resolver_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -161,14 +118,15 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:training_server_lib", ], - main = "gce_cluster_resolver_test.py", ) tf_py_test( name = "tfconfig_cluster_resolver_py_test", size = "small", srcs = ["tfconfig_cluster_resolver_test.py"], - additional_deps = [ + grpc_enabled = True, + main = "tfconfig_cluster_resolver_test.py", + deps = [ ":tfconfig_cluster_resolver_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -176,32 +134,34 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:training_server_lib", ], - grpc_enabled = True, - main = "tfconfig_cluster_resolver_test.py", ) tf_py_test( name = "tpu_cluster_resolver_py_test", size = "small", srcs = ["tpu_cluster_resolver_test.py"], - additional_deps = [ + grpc_enabled = True, + main = "tpu_cluster_resolver_test.py", + python_version = "PY3", + deps = [ ":tpu_cluster_resolver_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:platform_test", "//tensorflow/python:training_server_lib", + "//tensorflow/python/tpu/client", ], - grpc_enabled = True, - main = "tpu_cluster_resolver_test.py", - python_version = "PY3", ) tf_py_test( name = "tpu_cluster_resolver_py2_test", size = "small", srcs = ["tpu_cluster_resolver_test.py"], - additional_deps = [ + grpc_enabled = True, + main = "tpu_cluster_resolver_test.py", + python_version = "PY3", + deps = [ ":tpu_cluster_resolver_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -209,16 +169,15 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:training_server_lib", ], - grpc_enabled = True, - main = "tpu_cluster_resolver_test.py", - python_version = "PY2", ) tf_py_test( name = "slurm_cluster_resolver_py_test", size = "small", srcs = ["slurm_cluster_resolver_test.py"], - additional_deps = [ + main = "slurm_cluster_resolver_test.py", + tags = [], + deps = [ ":slurm_cluster_resolver_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -226,15 +185,14 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:training_server_lib", ], - main = "slurm_cluster_resolver_test.py", - tags = [], ) tf_py_test( name = "kubernetes_cluster_resolver_py_test", size = "small", srcs = ["kubernetes_cluster_resolver_test.py"], - additional_deps = [ + main = "kubernetes_cluster_resolver_test.py", + deps = [ ":kubernetes_cluster_resolver_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -242,5 +200,4 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:training_server_lib", ], - main = "kubernetes_cluster_resolver_test.py", ) diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py index 70bf80de4f2..30dfea24fa7 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver.py @@ -21,16 +21,20 @@ from __future__ import print_function import collections import re -from tensorflow.python.distribute.cluster_resolver.cloud_tpu_client import CloudTPUClient -from tensorflow.python.distribute.cluster_resolver.cluster_resolver import ClusterResolver -from tensorflow.python.distribute.cluster_resolver.cluster_resolver import format_master_url -from tensorflow.python.distribute.cluster_resolver.cluster_resolver import get_accelerator_devices +from tensorflow.python.distribute.cluster_resolver import cluster_resolver from tensorflow.python.framework import errors from tensorflow.python.platform import tf_logging as logging from tensorflow.python.training import server_lib from tensorflow.python.util import compat from tensorflow.python.util.tf_export import tf_export +try: + from cloud_tpu_client import client # pylint: disable=g-import-not-at-top +except ImportError: + logging.warning( + 'Falling back to tensorflow client, its recommended to install the cloud ' + 'tpu client directly with pip install cloud-tpu-client .') + from tensorflow.python.tpu.client import client def is_running_in_gce(): return True @@ -44,7 +48,7 @@ DeviceDetails = collections.namedtuple( @tf_export('distribute.cluster_resolver.TPUClusterResolver') -class TPUClusterResolver(ClusterResolver): +class TPUClusterResolver(cluster_resolver.ClusterResolver): """Cluster Resolver for Google Cloud TPUs. This is an implementation of cluster resolvers for the Google Cloud TPU @@ -135,7 +139,6 @@ class TPUClusterResolver(ClusterResolver): filled in produce an absolute URL to the discovery document for that service. The environment variable 'TPU_API_DISCOVERY_URL' will override this. - **kwargs: Extra keyword arguments passed to CloudTPUClient. Raises: ImportError: If the googleapiclient is not installed. @@ -144,7 +147,7 @@ class TPUClusterResolver(ClusterResolver): Google Cloud environment. """ - self._cloud_tpu_client = CloudTPUClient( + self._cloud_tpu_client = client.Client( tpu=tpu, zone=zone, project=project, @@ -208,7 +211,7 @@ class TPUClusterResolver(ClusterResolver): if not job_tasks: raise ValueError('No TPUs with the specified names exist.') master = job_tasks[0] - return format_master_url(master, 'grpc') + return cluster_resolver.format_master_url(master, 'grpc') def get_master(self): return self.master() @@ -277,7 +280,8 @@ class TPUClusterResolver(ClusterResolver): while True: try: device_details = TPUClusterResolver._get_device_dict_and_cores( - get_accelerator_devices(self.master(), config_proto=config_proto)) + cluster_resolver.get_accelerator_devices( + self.master(), config_proto=config_proto)) break except errors.DeadlineExceededError: error_message = ('Failed to connect to master. The TPU might not be ' diff --git a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py index f29d221ab09..2be44872885 100644 --- a/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py +++ b/tensorflow/python/distribute/cluster_resolver/tpu_cluster_resolver_test.py @@ -25,16 +25,24 @@ from six.moves.urllib.error import URLError from tensorflow.python import framework from tensorflow.python.client import session -from tensorflow.python.distribute.cluster_resolver import cloud_tpu_client from tensorflow.python.distribute.cluster_resolver import tpu_cluster_resolver as resolver from tensorflow.python.eager.context import LogicalDevice from tensorflow.python.framework import errors from tensorflow.python.framework import test_util from tensorflow.python.platform import test +from tensorflow.python.platform import tf_logging as logging from tensorflow.python.training import server_lib from tensorflow.python.util import compat mock = test.mock +try: + from cloud_tpu_client import client # pylint: disable=g-import-not-at-top +except ImportError: + logging.warning( + 'Falling back to tensorflow client, its recommended to install the cloud ' + 'tpu client directly with pip install cloud-tpu-client .') + from tensorflow.python.tpu.client import client + class MockRequestClass(object): @@ -141,7 +149,7 @@ class TPUClusterResolverTest(test.TestCase): def testIsRunningInGce(self): self.assertTrue(resolver.is_running_in_gce()) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testRetrieveProjectAndZoneFromMetadata(self): tpu_map = { @@ -174,7 +182,7 @@ class TPUClusterResolverTest(test.TestCase): self._verifyClusterSpecEquality(actual_cluster_spec, str(expected_proto)) self.assertEqual(cluster_resolver.master(), 'grpc://10.1.2.3:8470') - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testRetrieveProjectAndZoneFromMetadataNoCoordinator(self): tpu_map = { @@ -200,7 +208,7 @@ class TPUClusterResolverTest(test.TestCase): self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) self.assertEqual(cluster_resolver.master(), 'grpc://10.1.2.3:8470') - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testNotReadyCloudTpu(self): tpu_map = { @@ -299,7 +307,7 @@ class TPUClusterResolverTest(test.TestCase): self._verifyClusterSpecEquality(actual_cluster_spec, expected_proto) self.assertEqual('grpc://10.2.3.4:8470', cluster_resolver.master()) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testPodResolution(self): tpu_map = { diff --git a/tensorflow/python/distribute/collective_all_reduce_strategy_test.py b/tensorflow/python/distribute/collective_all_reduce_strategy_test.py index 14ed6b071d1..0c0bff429e6 100644 --- a/tensorflow/python/distribute/collective_all_reduce_strategy_test.py +++ b/tensorflow/python/distribute/collective_all_reduce_strategy_test.py @@ -364,9 +364,9 @@ class CollectiveAllReduceStrategyTestBase( computed_value = sess.run([values.select_replica(r, next_element) for r in range(len(devices))]) if ignore_order: - self.assertCountEqual(expected_value, computed_value) + self.assertCountEqual(list(expected_value), list(computed_value)) else: - self.assertEqual(expected_value, computed_value) + self.assertEqual(list(expected_value), list(computed_value)) with self.assertRaises(errors.OutOfRangeError): next_element = iterator.get_next() @@ -382,9 +382,9 @@ class CollectiveAllReduceStrategyTestBase( computed_value = sess.run([values.select_replica(r, next_element) for r in range(len(devices))]) if ignore_order: - self.assertCountEqual(expected_value, computed_value) + self.assertCountEqual(list(expected_value), list(computed_value)) else: - self.assertEqual(expected_value, computed_value) + self.assertEqual(list(expected_value), list(computed_value)) class DistributedCollectiveAllReduceStrategyTest( diff --git a/tensorflow/python/distribute/cross_device_ops.py b/tensorflow/python/distribute/cross_device_ops.py index 9fc49df0ead..ec762b9c239 100644 --- a/tensorflow/python/distribute/cross_device_ops.py +++ b/tensorflow/python/distribute/cross_device_ops.py @@ -1231,7 +1231,7 @@ def choose_the_best(devices, session_config=None): Returns: A subclass of `CrossDeviceOps`. """ - requested_devices = set([device_util.canonicalize(d) for d in devices]) + requested_devices = set(device_util.canonicalize(d) for d in devices) if ops.executing_eagerly_outside_functions(): logical_gpus = context.context().list_logical_devices(device_type="GPU") physical_gpus = context.context().list_physical_devices(device_type="GPU") diff --git a/tensorflow/python/distribute/cross_device_utils.py b/tensorflow/python/distribute/cross_device_utils.py index 90d29eabe7a..febdc2ae556 100644 --- a/tensorflow/python/distribute/cross_device_utils.py +++ b/tensorflow/python/distribute/cross_device_utils.py @@ -722,7 +722,7 @@ def is_indexed_slices(value): if isinstance(value, ops.IndexedSlices): return True assert isinstance(value, value_lib.DistributedValues) - return all([isinstance(v, ops.IndexedSlices) for v in value.values]) + return all(isinstance(v, ops.IndexedSlices) for v in value.values) def split_by_sparsity(values): diff --git a/tensorflow/python/distribute/input_lib.py b/tensorflow/python/distribute/input_lib.py index 80d03ed438a..78c2d68c788 100644 --- a/tensorflow/python/distribute/input_lib.py +++ b/tensorflow/python/distribute/input_lib.py @@ -151,8 +151,8 @@ class InputWorkers(object): self._fed_devices = tuple(tuple(device_util.canonicalize(d) for d in f) for _, f in worker_device_pairs) flattened = tuple(d for l in self._fed_devices for d in l) - assert (flattened == - device_map.logical_to_actual_devices(logical_device)), ( + assert (len(flattened) == + len(device_map.logical_to_actual_devices(logical_device))), ( "flattened: %s logical device %d: %s" % (flattened, logical_device, device_map.logical_to_actual_devices(logical_device))) diff --git a/tensorflow/python/distribute/minimize_loss_test.py b/tensorflow/python/distribute/minimize_loss_test.py index cb92840481e..92e5f6d65a6 100644 --- a/tensorflow/python/distribute/minimize_loss_test.py +++ b/tensorflow/python/distribute/minimize_loss_test.py @@ -215,7 +215,7 @@ class MinimizeLossStepTest(test.TestCase, parameterized.TestCase): for replica in range(1, num_parameter_devices) ] variables = list(variables) + extended_variables - return set([v + ":0" for v in variables]) + return set(v + ":0" for v in variables) self.assertEqual( get_expected_variables(len(distribution.extended.parameter_devices)), diff --git a/tensorflow/python/distribute/mirrored_strategy.py b/tensorflow/python/distribute/mirrored_strategy.py index 85958724002..4e45e3bd664 100644 --- a/tensorflow/python/distribute/mirrored_strategy.py +++ b/tensorflow/python/distribute/mirrored_strategy.py @@ -25,6 +25,8 @@ import threading import weakref from tensorflow.python import pywrap_tensorflow +from tensorflow.python.autograph.core import ag_ctx +from tensorflow.python.autograph.impl import api as autograph from tensorflow.python.distribute import cross_device_ops as cross_device_ops_lib from tensorflow.python.distribute import device_util from tensorflow.python.distribute import distribute_lib @@ -757,6 +759,14 @@ class MirroredExtended(distribute_lib.StrategyExtendedV1): "`experimental_run_v2` inside a tf.function to get " "the best performance." % self._container_strategy().__class__.__name__, 5) + else: + # When a tf.function is wrapped to trigger _call_for_each_replica (see + # the other branch above), AutoGraph stops conversion at + # _call_for_each_replica itself (TF library functions are whitelisted). + # This makes suresure that the Python function that originally passed to + # the tf.function is still converted. + fn = autograph.tf_convert(fn, ag_ctx.control_status_ctx()) + return _call_for_each_replica(self._container_strategy(), self._device_map, fn, args, kwargs) diff --git a/tensorflow/python/distribute/mirrored_strategy_test.py b/tensorflow/python/distribute/mirrored_strategy_test.py index 32966c904d8..fb5525af50a 100644 --- a/tensorflow/python/distribute/mirrored_strategy_test.py +++ b/tensorflow/python/distribute/mirrored_strategy_test.py @@ -23,7 +23,9 @@ import sys from absl.testing import parameterized import numpy as np + from tensorflow.core.protobuf import config_pb2 +from tensorflow.python.autograph.core import converter_testing from tensorflow.python.data.ops import dataset_ops from tensorflow.python.distribute import combinations from tensorflow.python.distribute import cross_device_ops as cross_device_ops_lib @@ -40,6 +42,7 @@ from tensorflow.python.eager import context from tensorflow.python.eager import def_function from tensorflow.python.eager import function from tensorflow.python.eager import test +from tensorflow.python.framework import config from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import func_graph @@ -1349,6 +1352,23 @@ class FunctionTest(test.TestCase): devices_for_this_node.add(node.device) self.assertSetEqual(devices_for_this_node, set(devices)) + def testFuctionPreservesAutoGraph(self): + config.set_logical_device_configuration( + config.list_physical_devices("CPU")[0], + [context.LogicalDeviceConfiguration()] * 2) + ms = mirrored_strategy.MirroredStrategy() + + def f(): + self.assertTrue(converter_testing.is_inside_generated_code()) + return 1 + + with ms.scope(): + @def_function.function + def replica_fn(): + return f() + + ms.experimental_run_v2(replica_fn) + def _replica_id(): replica_id = ds_context.get_replica_context().replica_id_in_sync_group diff --git a/tensorflow/python/distribute/multi_process_runner.py b/tensorflow/python/distribute/multi_process_runner.py index 00265e85f55..7f150d9c6a3 100644 --- a/tensorflow/python/distribute/multi_process_runner.py +++ b/tensorflow/python/distribute/multi_process_runner.py @@ -24,6 +24,7 @@ import json import os import signal import sys +import threading import time import six @@ -60,6 +61,10 @@ STD_STREAM_QUEUE = 'std_stream_queue' # Inter-process queue is used for communications between subprocesses. INTER_PROCESS_QUEUE = 'inter_process_queue' +# Parent-to-sub queue is used for communications from parent to subprocess. +# Currently this is only used to terminate subprocesses. +PARENT_TO_SUB_QUEUE = 'parent_to_sub_queue' + class _LogCollector(object): """Tool to collect logs before sending them to std stream.""" @@ -150,7 +155,7 @@ class MultiProcessRunner(object): self._grpc_fail_fast = grpc_fail_fast self._args = args or () self._kwargs = kwargs or {} - self._processes = [] + self._outstanding_subprocess_count = 0 # Child processes should have the same v2 and eager behavior. self._v2_enabled = tf2.enabled() @@ -200,16 +205,33 @@ class MultiProcessRunner(object): self._add_std_stream_data_flattened(stderr_collector.log) self._get_process_status_queue().put(process_status_info) - def _proc_func_wrapper(self, task_type, task_id, *arg, **kwargs): + def _message_checking_func(self, task_type, task_id, stdout_collector, + stderr_collector): + """A function that regularly checks messages from parent process.""" + while True: + try: + message = self._get_parent_to_sub_queue().get(block=False) + # Currently the only possible message is termination. + assert message.startswith('terminate') + if message == 'terminate {} {}'.format(task_type, task_id): + break + else: + # If the message is not targeting this process, put it back to the + # queue. + self._get_parent_to_sub_queue().put(message) + time.sleep(1) + except Queue.Empty: + time.sleep(0.1) + self._finish_process( + _ProcessStatusInfo( + task_type=task_type, is_successful=True, exc_info=None), None, + stdout_collector, stderr_collector) + # `os._exit(0)` is used to more reliably terminate a subprocess. + os._exit(0) # pylint: disable=protected-access + + def _proc_func_wrapper(self, proc_func, task_type, task_id, + per_process_cluster_spec, *arg, **kwargs): """The wrapper function that actually gets run in child process(es).""" - os.environ['GRPC_FAIL_FAST'] = str(self._grpc_fail_fast) - os.environ['TF_CONFIG'] = json.dumps({ - 'cluster': self._cluster_spec, - 'task': { - 'type': task_type, - 'index': task_id, - } - }) if self._capture_std_stream: # TODO(yuefengz): consider a lighter way of capturing std streams. @@ -221,6 +243,20 @@ class MultiProcessRunner(object): stdout_collector = None stderr_collector = None + # The thread will be dedicated to checking messages from parent process. + threading.Thread( + target=self._message_checking_func, + args=(task_type, task_id, stdout_collector, stderr_collector)).start() + + os.environ['GRPC_FAIL_FAST'] = str(self._grpc_fail_fast) + os.environ['TF_CONFIG'] = json.dumps({ + 'cluster': per_process_cluster_spec, + 'task': { + 'type': task_type, + 'index': task_id, + } + }) + if self._v2_enabled: v2_compat.enable_v2_behavior() @@ -236,6 +272,7 @@ class MultiProcessRunner(object): _ProcessStatusInfo( task_type=task_type, is_successful=True, exc_info=None), None, stdout_collector, stderr_collector) + # `os._exit(0)` is used to more reliably terminate a subprocess. os._exit(0) # pylint: disable=protected-access signal.signal(signal.SIGALRM, handler) @@ -243,7 +280,7 @@ class MultiProcessRunner(object): try: with self._runtime_mode(): - return_value = self._proc_func(*arg, **kwargs) + return_value = proc_func(*arg, **kwargs) except Exception: # pylint: disable=broad-except # Capture all exceptions to be reported to parent process. self._finish_process( @@ -277,10 +314,48 @@ class MultiProcessRunner(object): for task_id, _ in enumerate(addresses): p = multi_process_lib.Process( target=self._proc_func_wrapper, - args=(task_type, task_id) + self._args, + args=(self._proc_func, task_type, task_id, self._cluster_spec) + + self._args, kwargs=self._kwargs) p.start() - self._processes.append(p) + self._outstanding_subprocess_count += 1 + + def start_single_process(self, + task_type, + task_id, + proc_func=None, + updated_cluster_spec=None, + args=None, + kwargs=None): + """Starts a single process. + + This starts a process in the cluster with the task type, task id, and the + process function (`proc_func`). If process function is `None`, the function + provided at `__init__` will be used. If `updated_cluster_spec` is not + `None`, the cluster spec used by this subprocess will be updated. + + TODO(rchao): It is meant that all subprocesses will be updated with the new + cluster spec, but this has yet to be implemented. At this time only the + newly started subprocess picks up this updated cluster spec. + + Args: + task_type: The task type. + task_id: The task id. + proc_func: The process function to be run on the newly started + process. If `None`, the function provided at `__init__` will be used. + updated_cluster_spec: If not `None`, the cluster spec used by this + subprocess will be updated. + args: Optional positional arguments to be supplied in `proc_func`. + kwargs: Optional keyword arguments to be supplied in `proc_func`. + """ + self._cluster_spec = updated_cluster_spec or self._cluster_spec + proc_func = proc_func or self._proc_func + p = multi_process_lib.Process( + target=self._proc_func_wrapper, + args=(proc_func, task_type, task_id, self._cluster_spec) + (args or ()), + kwargs=(kwargs or {})) + p.start() + self._outstanding_subprocess_count += 1 def _queue_to_list(self, queue_to_convert): """Convert `queue.Queue` to `list`.""" @@ -316,9 +391,8 @@ class MultiProcessRunner(object): timeout = self._max_run_time + 10 # add 10 seconds grace period else: timeout = float('inf') - num_returned = 0 start_time = time.time() - while num_returned < len(self._processes): + while self._outstanding_subprocess_count > 0: while True: try: process_status = self._get_process_status_queue().get(timeout=10) @@ -329,16 +403,14 @@ class MultiProcessRunner(object): raise RuntimeError( 'One or more subprocesses timed out. Please use ' '`--test_arg=--logtostderr` bazel flag to inspect logs for ' - 'subprocess debugging info. Number of returned processes is ' - '%d.' % num_returned) + 'subprocess debugging info. Number of outstanding subprocesses ' + 'is %d.' % self._outstanding_subprocess_count) - num_returned += 1 + self._outstanding_subprocess_count -= 1 assert isinstance(process_status, _ProcessStatusInfo) if not process_status.is_successful: six.reraise(*process_status.exc_info) - self._processes = [] - if self._capture_std_stream: # TODO(yuefengz): we need to make sure elements match the same process in # the two returned lists so as to not surprise users. Consider creating a @@ -350,6 +422,11 @@ class MultiProcessRunner(object): return (self._queue_to_list( multi_process_lib.get_user_data()[RETURN_VALUE_QUEUE]), None) + def terminate(self, task_type, task_id): + """Terminates the process with `task_type` and `task_id`.""" + self._get_parent_to_sub_queue().put('terminate {} {}'.format( + task_type, task_id)) + def _add_return_data(self, data): """Adds return data that will be returned by `join`. @@ -379,6 +456,9 @@ class MultiProcessRunner(object): def _get_inter_process_queue(self): return multi_process_lib.get_user_data()[INTER_PROCESS_QUEUE] + def _get_parent_to_sub_queue(self): + return multi_process_lib.get_user_data()[PARENT_TO_SUB_QUEUE] + def run(proc_func, cluster_spec, diff --git a/tensorflow/python/distribute/multi_process_runner_test.py b/tensorflow/python/distribute/multi_process_runner_test.py index 4144eb6f040..839646a5d1f 100644 --- a/tensorflow/python/distribute/multi_process_runner_test.py +++ b/tensorflow/python/distribute/multi_process_runner_test.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import json +import os import time from six.moves import queue as Queue @@ -50,6 +52,10 @@ def proc_func_that_return_args_and_kwargs(*args, **kwargs): class MultiProcessRunnerTest(test.TestCase): + def _worker_idx(self): + config_task = json.loads(os.environ['TF_CONFIG'])['task'] + return config_task['index'] + def test_multi_process_runner(self): returned_data, _ = multi_process_runner.run( proc_func_that_adds_task_type_in_return_data, @@ -140,6 +146,55 @@ class MultiProcessRunnerTest(test.TestCase): # queue, so verifying it's empty. mpr._get_process_status_queue().get(block=False) + def test_termination(self): + + def proc_func(): + for i in range(0, 10): + print('index {}, iteration {}'.format(self._worker_idx(), i)) + time.sleep(1) + + mpr = multi_process_runner.MultiProcessRunner( + proc_func, + multi_worker_test_base.create_cluster_spec(num_workers=2), + capture_std_stream=True) + mpr.start() + time.sleep(5) + mpr.terminate('worker', 0) + std_stream_result = mpr.join()[1] + + # Worker 0 is terminated in the middle, so it should not have iteration 9 + # printed. + self.assertIn('index 0, iteration 0', std_stream_result) + self.assertNotIn('index 0, iteration 9', std_stream_result) + self.assertIn('index 1, iteration 0', std_stream_result) + self.assertIn('index 1, iteration 9', std_stream_result) + + def test_termination_and_start_single_process(self): + + def proc_func(): + for i in range(0, 10): + print('index {}, iteration {}'.format(self._worker_idx(), i)) + time.sleep(1) + + mpr = multi_process_runner.MultiProcessRunner( + proc_func, + multi_worker_test_base.create_cluster_spec(num_workers=2), + capture_std_stream=True) + mpr.start() + time.sleep(5) + mpr.terminate('worker', 0) + mpr.start_single_process('worker', 0) + std_stream_result = mpr.join()[1] + + # Worker 0 is terminated in the middle, but a new worker 0 is added, so it + # should still have iteration 9 printed. Moreover, iteration 0 of worker 0 + # should happen twice. + self.assertLen( + [s for s in std_stream_result if s == 'index 0, iteration 0'], 2) + self.assertIn('index 0, iteration 9', std_stream_result) + self.assertIn('index 1, iteration 0', std_stream_result) + self.assertIn('index 1, iteration 9', std_stream_result) + if __name__ == '__main__': multi_process_runner.test_main() diff --git a/tensorflow/python/distribute/tpu_strategy.py b/tensorflow/python/distribute/tpu_strategy.py index 8f32e8e2226..1cc1c329787 100644 --- a/tensorflow/python/distribute/tpu_strategy.py +++ b/tensorflow/python/distribute/tpu_strategy.py @@ -259,8 +259,8 @@ class TPUExtended(distribute_lib.StrategyExtendedV1): host_device = device_util.get_host_for_device(tpu_device) input_worker_devices.setdefault(host_device, []) input_worker_devices[host_device].append(tpu_device) - self._input_workers = input_lib.InputWorkers( - self._device_map, tuple(input_worker_devices.items())) + self._input_worker_devices = tuple(input_worker_devices.items()) + self._input_workers_obj = None # TODO(sourabhbajaj): Remove this once performance of running one step # at a time is comparable to multiple steps. @@ -273,6 +273,39 @@ class TPUExtended(distribute_lib.StrategyExtendedV1): self.experimental_enable_get_next_as_optional = True self.experimental_enable_dynamic_batch_size = True + self._prefetch_on_host = False + + # TODO(bfontain): Remove once a proper dataset API exists for prefetching + # a dataset to multiple devices exists. + # If value is true, this forces prefetch of data to the host's memeory rather + # than the individual TPU device's memory. This is needed when using for TPU + # Embeddings as a) sparse tensors cannot be prefetched to the TPU device + # memory and b) TPU Embedding enqueue operation are CPU ops and this avoids + # a copy back to the host for dense tensors + def _set_prefetch_on_host(self, value): + if self._prefetch_on_host == value: + return + if self._input_workers_obj is not None: + raise RuntimeError("Unable to change prefetch on host behavior as " + "InputWorkers are already created.") + self._prefetch_on_host = value + if value: + # To prefetch on the host, we must set all the input worker devices to the + # corresponding host devices. + self._input_worker_devices = tuple([ + tuple([host, + [device_util.get_host_for_device(d) for d in devices]]) + for host, devices in self._input_worker_devices]) + # Force creation of the workers. + workers = self._input_workers + del workers + + @property + def _input_workers(self): + if self._input_workers_obj is None: + self._input_workers_obj = input_lib.InputWorkers( + self._device_map, self._input_worker_devices) + return self._input_workers_obj def _validate_colocate_with_variable(self, colocate_with_variable): values.validate_colocate(colocate_with_variable, self) diff --git a/tensorflow/python/eager/BUILD b/tensorflow/python/eager/BUILD index f5898c55e61..d869a3b627e 100644 --- a/tensorflow/python/eager/BUILD +++ b/tensorflow/python/eager/BUILD @@ -108,7 +108,8 @@ cuda_py_test( name = "cancellation_test", size = "small", srcs = ["cancellation_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":cancellation", ":test", ], @@ -147,7 +148,8 @@ cuda_py_test( name = "context_test", size = "small", srcs = ["context_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":context", ":test", ], @@ -171,7 +173,8 @@ py_library( cuda_py_test( name = "monitoring_test", srcs = ["monitoring_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":monitoring", ":test", ], @@ -192,7 +195,8 @@ py_library( cuda_py_test( name = "profiler_test", srcs = ["profiler_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":profiler", ":test", "//tensorflow/python:constant_op", @@ -212,7 +216,7 @@ py_library( py_test( name = "profiler_client_test", srcs = ["profiler_client_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = ["no_pip"], visibility = ["//tensorflow:internal"], @@ -232,7 +236,8 @@ py_library( cuda_py_test( name = "tensor_test", srcs = ["tensor_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":context", ":test", "//tensorflow/python:errors", @@ -243,55 +248,58 @@ cuda_py_test( cuda_py_test( name = "backprop_test", srcs = ["backprop_test.py"], - additional_deps = [ - ":backprop", - ":context", - ":test", - "//third_party/py/numpy", - "//tensorflow/python:embedding_ops", - "//tensorflow/python:array_ops", - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:layers", - "//tensorflow/python:math_ops", - "//tensorflow/python:nn_ops", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:random_ops", - "//tensorflow/python:nn_grad", - "//tensorflow/python:training", - ], + python_version = "PY3", tags = [ "no_rocm", "no_windows", #TODO(b/139745667) ], + deps = [ + ":backprop", + ":context", + ":test", + "//tensorflow/python:array_ops", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:embedding_ops", + "//tensorflow/python:layers", + "//tensorflow/python:math_ops", + "//tensorflow/python:nn_grad", + "//tensorflow/python:nn_ops", + "//tensorflow/python:random_ops", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:training", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "forwardprop_test", srcs = ["forwardprop_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 5, + deps = [ ":forwardprop", ":forwardprop_util", ":test", "//tensorflow/python/distribute:mirrored_strategy", ], - shard_count = 5, ) cuda_py_test( name = "core_test", size = "small", srcs = ["core_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":context", ":core", ":execute", ":test", - "//third_party/py/numpy", "//tensorflow/python:dtypes", "//tensorflow/python:errors", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", "//tensorflow/python:pywrap_tensorflow", + "//third_party/py/numpy", ], ) @@ -299,13 +307,14 @@ cuda_py_test( name = "function_argument_naming_test", size = "medium", srcs = ["function_argument_naming_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":backprop", ":def_function", ":function", ":test", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:math_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -313,14 +322,15 @@ cuda_py_test( name = "function_defun_collection_test", size = "medium", srcs = ["function_defun_collection_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":backprop", ":def_function", ":function", ":test", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:math_ops", "//tensorflow/python:resource_variable_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -328,38 +338,40 @@ cuda_py_test( name = "function_gradients_test", size = "medium", srcs = ["function_gradients_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 5, + deps = [ ":backprop", ":context", ":def_function", ":function", ":test", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:math_ops", "//tensorflow/python:resource_variable_ops", + "@absl_py//absl/testing:parameterized", ], - shard_count = 5, ) cuda_py_test( name = "function_test", size = "medium", srcs = ["function_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 15, + deps = [ ":backprop", ":cancellation", ":context", ":def_function", ":function", ":test", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:clip_ops", "//tensorflow/python:constant_op", "//tensorflow/python:data_flow_ops", "//tensorflow/python:dtypes", - "//tensorflow/python:gradients", "//tensorflow/python:errors", "//tensorflow/python:framework_ops", + "//tensorflow/python:gradients", "//tensorflow/python:indexed_slices", "//tensorflow/python:init_ops", "//tensorflow/python:layers", @@ -371,8 +383,8 @@ cuda_py_test( "//tensorflow/python:tensor_shape", "//tensorflow/python:tensor_spec", "//tensorflow/python:test_ops", + "@absl_py//absl/testing:parameterized", ], - shard_count = 15, ) py_library( @@ -420,13 +432,14 @@ py_library( cuda_py_test( name = "graph_only_ops_test", srcs = ["graph_only_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ "graph_only_ops", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -529,33 +542,35 @@ py_library( cuda_py_test( name = "benchmarks_test", srcs = ["benchmarks_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":backprop", ":context", ":forwardprop", ":function", - ":test", ":profiler", ":remote", - "//third_party/py/numpy", + ":test", "//tensorflow/python:math_ops", "//tensorflow/python:pywrap_tensorflow", "//tensorflow/python:random_ops", "//tensorflow/python/keras", + "//third_party/py/numpy", ], ) cuda_py_test( name = "remote_benchmarks_test", srcs = ["remote_benchmarks_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":backprop", ":context", ":forwardprop", ":function", - ":test", ":profiler", ":remote", + ":test", "//tensorflow/python:math_ops", "//tensorflow/python:pywrap_tensorflow", "//tensorflow/python:random_ops", @@ -564,13 +579,15 @@ cuda_py_test( tf_py_logged_benchmark( name = "benchmarks", + python_version = "PY3", target = "//tensorflow/python/eager:benchmarks_test", ) tf_py_test( name = "tape_test", srcs = ["tape_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":backprop", ":context", ":test", @@ -587,11 +604,11 @@ tf_py_test( cuda_py_test( name = "ops_test", srcs = ["ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":context", ":execute", ":test", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:config", "//tensorflow/python:control_flow_ops", @@ -605,23 +622,25 @@ cuda_py_test( "//tensorflow/python:resource_variable_ops", "//tensorflow/python:sparse_ops", "//tensorflow/python:tensor_shape", + "//third_party/py/numpy", ], ) tf_py_test( name = "pywrap_tfe_test", srcs = ["pywrap_tfe_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":backprop", ":context", ":core", ":test", - "//third_party/py/numpy", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:pywrap_tensorflow", "//tensorflow/python:random_ops", "//tensorflow/python:test_ops", + "//third_party/py/numpy", ], ) @@ -672,32 +691,40 @@ tf_py_test( name = "lift_to_graph_test", size = "medium", srcs = ["lift_to_graph_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ "lift_to_graph", - "//tensorflow/python/eager:test", "//tensorflow/python:framework_ops", "//tensorflow/python:math_ops", "//tensorflow/python:util", + "//tensorflow/python/eager:test", ], ) cuda_py_test( name = "def_function_test", srcs = ["def_function_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":def_function", - "//tensorflow/python/autograph/core", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:framework_ops", + "//tensorflow/python/autograph/core", + "@absl_py//absl/testing:parameterized", ], ) cuda_py_test( name = "def_function_xla_jit_test", srcs = ["def_function_xla_jit_test.py"], - additional_deps = [ + python_version = "PY3", + tags = [ + "no_mac", + "no_windows", + ], + xla_enabled = True, + deps = [ ":backprop", ":def_function", "//tensorflow/python:client_testlib", @@ -705,16 +732,12 @@ cuda_py_test( "//tensorflow/python:framework_ops", "//tensorflow/python:resource_variable_ops", ], - tags = [ - "no_mac", - "no_windows", - ], - xla_enabled = True, ) tf_xla_py_test( name = "def_function_xla_test", srcs = ["def_function_xla_test.py"], + python_version = "PY3", tags = [ "no_pip", "no_windows", @@ -751,7 +774,8 @@ py_library( tf_py_test( name = "wrap_function_test", srcs = ["wrap_function_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":wrap_function", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_ops", @@ -774,26 +798,28 @@ cuda_py_test( name = "remote_test", size = "medium", srcs = ["remote_test.py"], - additional_deps = [ - ":context", - ":def_function", - ":test", - ":remote", - "@absl_py//absl/testing:parameterized", - "@six_archive//:six", - "//tensorflow/python:resource_variable_ops", - ], grpc_enabled = True, + python_version = "PY3", shard_count = 2, tags = [ "no_oss", # This test launches local server. "optonly", # times out ], + deps = [ + ":context", + ":def_function", + ":remote", + ":test", + "//tensorflow/python:resource_variable_ops", + "@absl_py//absl/testing:parameterized", + "@six_archive//:six", + ], ) tpu_py_test( name = "remote_cloud_tpu_test", srcs = ["remote_cloud_tpu_test.py"], + python_version = "PY3", tags = [ "notap", ], @@ -809,11 +835,12 @@ cuda_py_test( name = "device_placement_test", size = "small", srcs = ["device_placement_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":context", ":def_function", - ":test", ":remote", + ":test", "//tensorflow/python:resource_variable_ops", ], ) diff --git a/tensorflow/python/eager/benchmarks/resnet50/BUILD b/tensorflow/python/eager/benchmarks/resnet50/BUILD index 4f2407b50b6..14f55d09c4c 100644 --- a/tensorflow/python/eager/benchmarks/resnet50/BUILD +++ b/tensorflow/python/eager/benchmarks/resnet50/BUILD @@ -39,11 +39,6 @@ cuda_py_test( name = "resnet50_test", size = "medium", srcs = ["resnet50_test.py"], - additional_deps = [ - ":resnet50", - ":resnet50_test_util", - "//tensorflow:tensorflow_py_no_contrib", - ], shard_count = 4, tags = [ "no_windows", # TODO(b/141617449): needs investigation @@ -51,18 +46,17 @@ cuda_py_test( "oss_serial", "v1only", ], + deps = [ + ":resnet50", + ":resnet50_test_util", + "//tensorflow:tensorflow_py_no_contrib", + ], ) cuda_py_test( name = "hvp_test", size = "medium", srcs = ["hvp_test.py"], - additional_deps = [ - ":resnet50", - ":resnet50_test_util", - "//tensorflow/python/eager:forwardprop", - "//tensorflow:tensorflow_py_no_contrib", - ], shard_count = 7, tags = [ "no_windows", # TODO(b/141617449): needs investigation @@ -72,18 +66,18 @@ cuda_py_test( ], # Times out xla_enable_strict_auto_jit = False, + deps = [ + ":resnet50", + ":resnet50_test_util", + "//tensorflow:tensorflow_py_no_contrib", + "//tensorflow/python/eager:forwardprop", + ], ) cuda_py_test( name = "resnet50_graph_test", size = "medium", srcs = ["resnet50_graph_test.py"], - additional_deps = [ - ":resnet50", - ":resnet50_test_lib", - "//third_party/py/numpy", - "//tensorflow:tensorflow_py_no_contrib", - ], shard_count = 4, tags = [ "no_windows", # TODO(b/141617449): needs investigation @@ -93,4 +87,10 @@ cuda_py_test( "optonly", "oss_serial", ], + deps = [ + ":resnet50", + ":resnet50_test_lib", + "//tensorflow:tensorflow_py_no_contrib", + "//third_party/py/numpy", + ], ) diff --git a/tensorflow/python/eager/benchmarks_test.py b/tensorflow/python/eager/benchmarks_test.py index 2bdc03b55cc..79abb3bda5d 100644 --- a/tensorflow/python/eager/benchmarks_test.py +++ b/tensorflow/python/eager/benchmarks_test.py @@ -778,10 +778,17 @@ class MicroBenchmarks(test.Benchmark): def benchmark_forwardprop_of_defun_matmul_100_by_784_CPU(self): self._benchmark_forwardprop_of_defun_matmul_CPU(shape=(100, 784)) - def _benchmark_tf_reduce_logsumexp(self, device=CPU, execution_mode=None): + def _benchmark_tf_reduce_logsumexp(self, + device=CPU, + execution_mode=None, + defunc=False): with context.device(device): x = constant_op.constant([[1, 0.], [0., 0.]]) - func = lambda: math_ops.reduce_logsumexp(x) + if defunc: + reduce_func = def_function.function(math_ops.reduce_logsumexp) + func = lambda: reduce_func(x) + else: + func = lambda: math_ops.reduce_logsumexp(x) self._run(func, 3000, execution_mode=execution_mode) def benchmark_tf_reduce_logsumexp_CPU(self): @@ -797,6 +804,20 @@ class MicroBenchmarks(test.Benchmark): self._benchmark_tf_reduce_logsumexp(device=GPU, execution_mode=context.ASYNC) + def benchmark_tf_reduce_logsumexp_CPU_defunc(self): + self._benchmark_tf_reduce_logsumexp(defunc=True) + + def benchmark_tf_reduce_logsumexp_CPU_async_defun(self): + self._benchmark_tf_reduce_logsumexp( + execution_mode=context.ASYNC, defunc=True) + + def benchmark_tf_reduce_logsumexp_GPU_defun(self): + self._benchmark_tf_reduce_logsumexp(device=GPU, defunc=True) + + def benchmark_tf_reduce_logsumexp_GPU_async_defun(self): + self._benchmark_tf_reduce_logsumexp( + device=GPU, execution_mode=context.ASYNC, defunc=True) + def _benchmark_tf_tensordot(self, device=CPU, execution_mode=None): with context.device(device): a = array_ops.ones((2, 2)) diff --git a/tensorflow/python/eager/context.py b/tensorflow/python/eager/context.py index 19626ec7059..b18f3ebad37 100644 --- a/tensorflow/python/eager/context.py +++ b/tensorflow/python/eager/context.py @@ -405,7 +405,7 @@ class Context(object): if execution_mode is None: execution_mode = SYNC self._default_is_async = execution_mode == ASYNC - self._lazy_remote_inputs_copy = False + self._lazy_remote_inputs_copy = None self._server_def = server_def self._collective_ops_server_def = None self._collective_leader = None @@ -506,9 +506,9 @@ class Context(object): opts, self._mirroring_policy) if self._default_is_async == ASYNC: pywrap_tensorflow.TFE_ContextOptionsSetAsync(opts, True) - if self._lazy_remote_inputs_copy: + if self._lazy_remote_inputs_copy is not None: pywrap_tensorflow.TFE_ContextOptionsSetLazyRemoteInputsCopy( - opts, True) + opts, self._lazy_remote_inputs_copy) context_handle = pywrap_tensorflow.TFE_NewContext(opts) finally: pywrap_tensorflow.TFE_DeleteContextOptions(opts) diff --git a/tensorflow/python/eager/def_function.py b/tensorflow/python/eager/def_function.py index 2c1ced2b87d..1c671050bc0 100644 --- a/tensorflow/python/eager/def_function.py +++ b/tensorflow/python/eager/def_function.py @@ -405,7 +405,7 @@ class Function(object): self._implements = experimental_implements self._autograph = autograph self._experimental_autograph_options = experimental_autograph_options - self.experimental_relax_shapes = experimental_relax_shapes + self._experimental_relax_shapes = experimental_relax_shapes self._experimental_compile = experimental_compile self._created_variables = None # GUARDED_BY(self._lock) self._stateful_fn = None # GUARDED_BY(self._lock) @@ -458,7 +458,7 @@ class Function(object): attributes=attributes, autograph=self._autograph, experimental_autograph_options=self._experimental_autograph_options, - experimental_relax_shapes=self.experimental_relax_shapes) + experimental_relax_shapes=self._experimental_relax_shapes) def _initialize(self, args, kwds, add_initializers_to=None): """Initializes, on the first call. @@ -514,7 +514,7 @@ class Function(object): autograph=self._autograph, experimental_implements=self._implements, experimental_autograph_options=self._experimental_autograph_options, - experimental_relax_shapes=self.experimental_relax_shapes, + experimental_relax_shapes=self._experimental_relax_shapes, experimental_compile=self._experimental_compile) def _decorate(self, decorator): diff --git a/tensorflow/python/eager/def_function_test.py b/tensorflow/python/eager/def_function_test.py index b558412fd9a..cb0c239ceee 100644 --- a/tensorflow/python/eager/def_function_test.py +++ b/tensorflow/python/eager/def_function_test.py @@ -681,7 +681,7 @@ class DefFunctionTest(test.TestCase, parameterized.TestCase): self.assertEqual(autograph, cloned._autograph) self.assertEqual(implements, cloned._implements) self.assertEqual(autograph_options, cloned._experimental_autograph_options) - self.assertEqual(relax_shapes, cloned.experimental_relax_shapes) + self.assertEqual(relax_shapes, cloned._experimental_relax_shapes) self.assertEqual(compile_, cloned._experimental_compile) # This test does not run with XLA JIT support linked in so we can only check diff --git a/tensorflow/python/eager/forwardprop.py b/tensorflow/python/eager/forwardprop.py index 92a082538f2..6ddaedc2fdb 100644 --- a/tensorflow/python/eager/forwardprop.py +++ b/tensorflow/python/eager/forwardprop.py @@ -250,9 +250,9 @@ class ForwardAccumulator(object): ... primal_out = primal ** tf.constant(3.5) >>> inner_jvp = inner.jvp(primal_out) >>> inner_jvp # 3.5 * 1.1 ** 2.5 - + >>> outer.jvp(inner_jvp) # 3.5 * 2.5 * 1.1 ** 1.5 - + Reversing the collection in the last line to instead retrieve `inner.jvp(outer.jvp(primal_out))` will not work. diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py index 89d731332f8..c0c6ed6bb54 100644 --- a/tensorflow/python/eager/function.py +++ b/tensorflow/python/eager/function.py @@ -380,6 +380,11 @@ def remove_function_callback(function_callback): _function_callbacks.remove(function_callback) +def clear_function_callbacks(): + """Clear all function callbacks, if any have been regisered.""" + _function_callbacks.clear() + + _FORWARD_PREFIX = "__forward_" _BACKWARD_PREFIX = "__backward_" _INFERENCE_PREFIX = "__inference_" @@ -2246,10 +2251,9 @@ def _convert_inputs_to_signature(inputs, input_signature, flat_input_signature): """Convert inputs to pass into a function with an explicit signature.""" def format_error_message(inputs, input_signature): - return (" inputs: (\n" + " " + - ",\n ".join([str(i) for i in inputs]) + ")\n" + - " input_signature: (\n" + " " + - ",\n ".join([str(i) for i in input_signature]) + ")") + return (" inputs: (\n" + " " + ",\n ".join(str(i) for i in inputs) + + ")\n" + " input_signature: (\n" + " " + + ",\n ".join(str(i) for i in input_signature) + ")") try: # TODO(b/124370185): Use all elements as inputs to throw an error if there @@ -3270,7 +3274,8 @@ def class_method_to_instance_method(original_function, instance): tf_decorator.make_decorator(bound_method, bound_method_wrapper), name=original_function._name, autograph=original_function._autograph, - input_signature=original_function.input_signature) + input_signature=original_function.input_signature, + experimental_relax_shapes=original_function._experimental_relax_shapes) # pylint: enable=protected-access # And we wrap the function with tf_decorator so inspection works correctly diff --git a/tensorflow/python/eager/function_test.py b/tensorflow/python/eager/function_test.py index 8a92589cbc5..de35852dc28 100644 --- a/tensorflow/python/eager/function_test.py +++ b/tensorflow/python/eager/function_test.py @@ -323,6 +323,29 @@ class FunctionTest(test.TestCase, parameterized.TestCase): self.assertTrue(unknown_dim[0]) self.assertLen(total_function_cache(func), 2) + def testInputShapeRelaxationOnInstanceMethod(self): + # Test that experimental_relax_shapes is passed during + # instance method bounding. + unknown_dim = [False] + + class Foo(object): + + @def_function.function(experimental_relax_shapes=True) + def func(self, a): + if a._shape_tuple()[0] is None: + unknown_dim[0] = True + return a + 1 + + foo = Foo() + foo.func(constant_op.constant([])) + self.assertFalse(unknown_dim[0]) + + foo.func(constant_op.constant([1.0])) + self.assertFalse(unknown_dim[0]) + + foo.func(constant_op.constant([1.0, 2.0])) + self.assertTrue(unknown_dim[0]) + def testCapturesVariables(self): a = variables.Variable(1.0, trainable=False) b = variables.Variable(1.0) @@ -338,10 +361,14 @@ class FunctionTest(test.TestCase, parameterized.TestCase): cf = f.get_concrete_function() c = cc[0] - self.assertEqual(cf.variables, (a, b, c)) - self.assertEqual(cf.trainable_variables, (b, c)) - self.assertEqual(cf.graph.variables, (a, b, c)) - self.assertEqual(cf.graph.trainable_variables, (b, c)) + captured_variables = {v.experimental_ref() for v in (a, b, c)} + trainable_variables = {v.experimental_ref() for v in (b, c)} + self.assertEqual({v.experimental_ref() for v in cf.variables}, + captured_variables) + self.assertEqual({v.experimental_ref() for v in cf.trainable_variables}, + trainable_variables) + self.assertEqual(cf.variables, cf.graph.variables) + self.assertEqual(cf.trainable_variables, cf.graph.trainable_variables) def testNestedInputShapeFunctionRelaxation(self): unknown_dim = [False] @@ -3138,6 +3165,66 @@ class FunctionTest(test.TestCase, parameterized.TestCase): # Cancellation after the function executes is a no-op. c_mgr.start_cancel() + def testAddFunctionCallback(self): + functions = [] + def function_callback(f): + functions.append(f) + + @def_function.function + def plus_one(x): + return x + 1 + + try: + function.add_function_callback(function_callback) + x_float32 = numpy.array(3.0, dtype=numpy.float32) + self.assertAllClose(plus_one(x_float32), 4.0) + self.assertLen(functions, 1) + # Function is already created. Executing it again should not invoke the + # function callback. + self.assertAllClose(plus_one(x_float32), 4.0) + self.assertLen(functions, 1) + # Signature change leads to a new Function being built. + x_float64 = numpy.array(3.0, dtype=numpy.float64) + self.assertAllClose(plus_one(x_float64), 4.0) + self.assertLen(functions, 2) + finally: + function.clear_function_callbacks() + + def testRemoveFunctionCallback(self): + functions_1 = [] + def function_callback_1(f): + functions_1.append(f) + + functions_2 = [] + def function_callback_2(f): + functions_2.append(f) + + @def_function.function + def plus_one(x): + return x + 1 + + try: + function.add_function_callback(function_callback_1) + function.add_function_callback(function_callback_2) + self.assertAllClose(plus_one(numpy.array(3.0, dtype=numpy.float32)), 4.0) + self.assertLen(functions_1, 1) + self.assertLen(functions_2, 1) + function.remove_function_callback(function_callback_1) + # The 1st callback should not be invokved after remove_function_callback() + # is called. + self.assertAllClose(plus_one(numpy.array(3.0, dtype=numpy.float64)), 4.0) + self.assertLen(functions_1, 1) + self.assertLen(functions_2, 2) + finally: + function.clear_function_callbacks() + + def testClearFunctionCallbacks(self): + function.add_function_callback(lambda f: None) + function.add_function_callback(lambda f: None) + self.assertLen(function._function_callbacks, 2) + function.clear_function_callbacks() + self.assertEmpty(function._function_callbacks) # pylint:disable=protected-access + class MultiDeviceTest(test.TestCase, parameterized.TestCase): diff --git a/tensorflow/python/eager/lift_to_graph.py b/tensorflow/python/eager/lift_to_graph.py index f884f6ab2ce..9d4c0068685 100644 --- a/tensorflow/python/eager/lift_to_graph.py +++ b/tensorflow/python/eager/lift_to_graph.py @@ -246,7 +246,7 @@ def lift_to_graph(tensors, # Check that the initializer does not depend on any placeholders. sources = object_identity.ObjectIdentitySet(sources or []) - visited_ops = set([x.op for x in sources]) + visited_ops = set(x.op for x in sources) op_outputs = collections.defaultdict(set) # First we extract the subgraph between init_tensors and sources. diff --git a/tensorflow/python/eager/memory_tests/BUILD b/tensorflow/python/eager/memory_tests/BUILD index 5d03863cf40..c9694c64694 100644 --- a/tensorflow/python/eager/memory_tests/BUILD +++ b/tensorflow/python/eager/memory_tests/BUILD @@ -19,16 +19,6 @@ cuda_py_test( name = "memory_test", size = "medium", srcs = ["memory_test.py"], - additional_deps = [ - ":memory_test_util", - "//tensorflow/python/keras", - "//tensorflow/python/eager:backprop", - "//tensorflow/python/eager:test", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_test_lib", - "@six_archive//:six", - ], tags = [ "manual", "no_oss", @@ -37,22 +27,32 @@ cuda_py_test( ], # TODO(b/140065350): Re-enable xla_enable_strict_auto_jit = False, + deps = [ + ":memory_test_util", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python/eager:backprop", + "//tensorflow/python/eager:test", + "//tensorflow/python/keras", + "@six_archive//:six", + ], ) cuda_py_test( name = "remote_memory_test", size = "medium", srcs = ["remote_memory_test.py"], - additional_deps = [ - ":memory_test_util", - "//tensorflow/python/eager:backprop", - "//tensorflow/python/eager:remote", - "//tensorflow/python/eager:test", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - ], tags = [ "optonly", # The test is too slow in non-opt mode ], xla_enable_strict_auto_jit = False, # b/140261762 + deps = [ + ":memory_test_util", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python/eager:backprop", + "//tensorflow/python/eager:remote", + "//tensorflow/python/eager:test", + ], ) diff --git a/tensorflow/python/eager/pywrap_tfe_src.cc b/tensorflow/python/eager/pywrap_tfe_src.cc index f5f336c2323..f5508d76583 100644 --- a/tensorflow/python/eager/pywrap_tfe_src.cc +++ b/tensorflow/python/eager/pywrap_tfe_src.cc @@ -68,13 +68,14 @@ TFE_Op* ReleaseThreadLocalOp() { } TFE_Op* GetOp(TFE_Context* ctx, const char* op_or_function_name, - TF_Status* status) { + const char* raw_device_name, TF_Status* status) { TFE_Op* maybe_op = ReleaseThreadLocalOp(); if (maybe_op) { - TFE_OpReset(ctx, op_or_function_name, status, maybe_op); + TFE_OpReset(ctx, op_or_function_name, raw_device_name, status, maybe_op); return maybe_op; } else { - return TFE_NewOp(ctx, op_or_function_name, status); + return NewOrResetOp(ctx, op_or_function_name, raw_device_name, status, + nullptr); } } @@ -834,14 +835,12 @@ void TFE_Py_ExecuteCancelable(TFE_Context* ctx, const char* device_name, TFE_CancellationManager* cancellation_manager, TFE_OutputTensorHandles* outputs, TF_Status* out_status) { - TFE_Op* op = GetOp(ctx, op_name, out_status); + TFE_Op* op = GetOp(ctx, op_name, device_name, out_status); auto cleaner = tensorflow::gtl::MakeCleanup([op] { ReturnOp(op); }); if (!out_status->status.ok()) return; - TFE_OpSetDevice(op, device_name, out_status); - if (out_status->status.ok()) { - for (int i = 0; i < inputs->size() && out_status->status.ok(); ++i) { - TFE_OpAddInput(op, inputs->at(i), out_status); - } + + for (int i = 0; i < inputs->size() && out_status->status.ok(); ++i) { + TFE_OpAddInput(op, inputs->at(i), out_status); } if (cancellation_manager && out_status->status.ok()) { TFE_OpSetCancellationManager(op, cancellation_manager, out_status); @@ -3506,7 +3505,8 @@ PyObject* TFE_Py_FastPathExecute_C(PyObject*, PyObject* args) { return nullptr; } - TFE_Op* op = GetOp(op_exec_info.ctx, op_name, status); + TFE_Op* op = + GetOp(op_exec_info.ctx, op_name, op_exec_info.device_name, status); auto cleaner = tensorflow::gtl::MakeCleanup([status, op] { ReturnStatus(status); ReturnOp(op); @@ -3573,11 +3573,6 @@ PyObject* TFE_Py_FastPathExecute_C(PyObject*, PyObject* args) { } } - TFE_OpSetDevice(op, op_exec_info.device_name, status); - if (MaybeRaiseExceptionFromTFStatus(status, nullptr)) { - return nullptr; - } - // Flat attrs and inputs as required by the record_gradient call. The attrs // here only contain inferred attrs (non-inferred attrs are added directly // from the input args). diff --git a/tensorflow/python/eager/remote_test.py b/tensorflow/python/eager/remote_test.py index f4322b8b0e4..acafbb2626d 100644 --- a/tensorflow/python/eager/remote_test.py +++ b/tensorflow/python/eager/remote_test.py @@ -94,12 +94,8 @@ class SingleWorkerTest(test.TestCase, parameterized.TestCase): c = variable_b + 1 return c, i + variable_b - with self.assertRaises(errors.UnimplementedError) as cm: - remote_output(constant_op.constant([1])) - - self.assertIn( - 'Currently, outputting tensors on remote devices is not supported.', - cm.exception.message) + self.assertAllEqual( + remote_output(constant_op.constant([1]))[0].numpy(), 2) def testMultiDeviceFunctionAmbiguousDevice(self): @@ -176,6 +172,19 @@ class MultiWorkersTest(test.TestCase, parameterized.TestCase): # Reset the context to avoid polluting other test cases. context._reset_context() + @test_util.eager_lazy_remote_copy_on_and_off + def testReturnRemoteArgument(self): + + @def_function.function + def local_func(i): + return i + + with ops.device('/job:worker/replica:0/task:0'): + x = constant_op.constant([2, 1]) + + with ops.device('/job:worker/replica:0/task:1'): + self.assertAllEqual(local_func(x), [2, 1]) + @test_util.eager_lazy_remote_copy_on_and_off def testMultiDeviceFunctionOnLocalDevice(self): with ops.device('/job:worker/replica:0/task:1'): diff --git a/tensorflow/python/feature_column/BUILD b/tensorflow/python/feature_column/BUILD index 38c3657ef58..0cec3e6f8a9 100644 --- a/tensorflow/python/feature_column/BUILD +++ b/tensorflow/python/feature_column/BUILD @@ -99,6 +99,7 @@ filegroup( "testdata/embedding.ckpt.data-00000-of-00001", "testdata/embedding.ckpt.index", "testdata/embedding.ckpt.meta", + "testdata/unicode_vocabulary", "testdata/warriors_vocabulary.txt", "testdata/wire_vocabulary.txt", ], @@ -107,23 +108,23 @@ filegroup( tf_py_test( name = "feature_column_test", srcs = ["feature_column_test.py"], - additional_deps = [ - ":feature_column_test_main_lib", - ], tags = [ "no_cuda_on_cpu_tap", "no_pip", "no_windows", ], + deps = [ + ":feature_column_test_main_lib", + ], ) tf_py_test( name = "dense_features_test", srcs = ["dense_features_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ ":feature_column_test_main_lib", ], - tags = ["no_pip"], ) py_library( @@ -158,22 +159,22 @@ py_library( tf_py_test( name = "feature_column_v2_test", srcs = ["feature_column_v2_test.py"], - additional_deps = [":feature_column_v2_test_main_lib"], shard_count = 5, tags = [ "no_cuda_on_cpu_tap", "no_pip", "no_windows", ], + deps = [":feature_column_v2_test_main_lib"], ) tf_py_test( name = "dense_features_v2_test", srcs = ["dense_features_v2_test.py"], - additional_deps = [ + tags = ["no_pip"], + deps = [ ":feature_column_v2_test_main_lib", ], - tags = ["no_pip"], ) py_library( @@ -221,10 +222,8 @@ py_library( tf_py_test( name = "sequence_feature_column_test", srcs = ["sequence_feature_column_test.py"], - additional_deps = [ + deps = [ ":feature_column_v2", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:errors", @@ -234,13 +233,15 @@ tf_py_test( "//tensorflow/python:session", "//tensorflow/python:sparse_tensor", "//tensorflow/python:training", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) py_test( name = "sequence_feature_column_integration_test", srcs = ["sequence_feature_column_integration_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = ["no_pip"], deps = [ @@ -257,10 +258,10 @@ py_test( tf_py_test( name = "serialization_test", srcs = ["serialization_test.py"], - additional_deps = [ + deps = [ ":feature_column_v2", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python:util", + "@absl_py//absl/testing:parameterized", ], ) diff --git a/tensorflow/python/feature_column/feature_column_v2.py b/tensorflow/python/feature_column/feature_column_v2.py index 7af63b57b7d..b3a75231325 100644 --- a/tensorflow/python/feature_column/feature_column_v2.py +++ b/tensorflow/python/feature_column/feature_column_v2.py @@ -1665,7 +1665,7 @@ def categorical_column_with_vocabulary_file_v2(key, if not gfile.Exists(vocabulary_file): raise ValueError('vocabulary_file in {} does not exist.'.format(key)) - with gfile.GFile(vocabulary_file) as f: + with gfile.GFile(vocabulary_file, mode='rb') as f: vocabulary_size = sum(1 for _ in f) logging.info( 'vocabulary_size = %d in %s is inferred from the number of elements ' @@ -4246,16 +4246,14 @@ class IndicatorColumn( """See `FeatureColumn` base class.""" return '{}_indicator'.format(self.categorical_column.name) - def _transform_id_weight_pair(self, id_weight_pair): + def _transform_id_weight_pair(self, id_weight_pair, size): id_tensor = id_weight_pair.id_tensor weight_tensor = id_weight_pair.weight_tensor # If the underlying column is weighted, return the input as a dense tensor. if weight_tensor is not None: weighted_column = sparse_ops.sparse_merge( - sp_ids=id_tensor, - sp_values=weight_tensor, - vocab_size=int(self._variable_shape[-1])) + sp_ids=id_tensor, sp_values=weight_tensor, vocab_size=int(size)) # Remove (?, -1) index. weighted_column = sparse_ops.sparse_slice(weighted_column, [0, 0], weighted_column.dense_shape) @@ -4271,10 +4269,7 @@ class IndicatorColumn( # One hot must be float for tf.concat reasons since all other inputs to # input_layer are float32. one_hot_id_tensor = array_ops.one_hot( - dense_id_tensor, - depth=self._variable_shape[-1], - on_value=1.0, - off_value=0.0) + dense_id_tensor, depth=size, on_value=1.0, off_value=0.0) # Reduce to get a multi-hot per example. return math_ops.reduce_sum(one_hot_id_tensor, axis=[-2]) @@ -4296,13 +4291,15 @@ class IndicatorColumn( """ id_weight_pair = self.categorical_column.get_sparse_tensors( transformation_cache, state_manager) - return self._transform_id_weight_pair(id_weight_pair) + return self._transform_id_weight_pair(id_weight_pair, + self.variable_shape[-1]) @deprecation.deprecated(_FEATURE_COLUMN_DEPRECATION_DATE, _FEATURE_COLUMN_DEPRECATION) def _transform_feature(self, inputs): id_weight_pair = self.categorical_column._get_sparse_tensors(inputs) # pylint: disable=protected-access - return self._transform_id_weight_pair(id_weight_pair) + return self._transform_id_weight_pair(id_weight_pair, + self._variable_shape[-1]) @property def parse_example_spec(self): diff --git a/tensorflow/python/feature_column/feature_column_v2_test.py b/tensorflow/python/feature_column/feature_column_v2_test.py index 0dcbb53fb13..ce0e19725fa 100644 --- a/tensorflow/python/feature_column/feature_column_v2_test.py +++ b/tensorflow/python/feature_column/feature_column_v2_test.py @@ -3886,6 +3886,10 @@ class VocabularyFileCategoricalColumnTest(test.TestCase): 'python/feature_column/testdata/wire_vocabulary.txt') self._wire_vocabulary_size = 3 + # Contains unicode characters. + self._unicode_vocabulary_file_name = test.test_src_dir_path( + 'python/feature_column/testdata/unicode_vocabulary') + @test_util.run_deprecated_v1 def test_defaults(self): column = fc.categorical_column_with_vocabulary_file( @@ -3898,6 +3902,17 @@ class VocabularyFileCategoricalColumnTest(test.TestCase): }, column.parse_example_spec) self.assertTrue(column._is_v2_column) + @test_util.run_deprecated_v1 + def test_defaults_unicode(self): + column = fc.categorical_column_with_vocabulary_file( + key='aaa', vocabulary_file=self._unicode_vocabulary_file_name) + self.assertEqual('aaa', column.name) + self.assertEqual('aaa', column.key) + self.assertEqual(165, column.num_buckets) + self.assertEqual({'aaa': parsing_ops.VarLenFeature(dtypes.string)}, + column.parse_example_spec) + self.assertTrue(column._is_v2_column) + def test_key_should_be_string(self): with self.assertRaisesRegexp(ValueError, 'key must be a string.'): fc.categorical_column_with_vocabulary_file( diff --git a/tensorflow/python/feature_column/testdata/unicode_vocabulary b/tensorflow/python/feature_column/testdata/unicode_vocabulary new file mode 100644 index 00000000000..0399ca09211 --- /dev/null +++ b/tensorflow/python/feature_column/testdata/unicode_vocabulary @@ -0,0 +1,165 @@ +t +/ +e +o +a +s +p +i +c +n +. +r +h +m +x +l +d +w +- +u +g +b +: +2 +0 +1 +f +% +8 +3 +5 +k +9 +4 +y +7 +6 +v += +_ +? +A +D +j +& +F +z +E +B +S +C +q +M +L +I +R +T +N +W +P +U +G +Z +O +V +Y +H +J +X +Q +K ++ +# +, +; +~ +) +@ +! +| +' +( +$ +* +] +[ +{ +} +\ + +^ +` +" + + + + + + +> +< + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tensorflow/python/framework/c_api_util.py b/tensorflow/python/framework/c_api_util.py index e7700fa0d4f..f105d6eb029 100644 --- a/tensorflow/python/framework/c_api_util.py +++ b/tensorflow/python/framework/c_api_util.py @@ -44,12 +44,14 @@ class ScopedTFGraph(object): def __init__(self): self.graph = c_api.TF_NewGraph() + # Note: when we're destructing the global context (i.e when the process is + # terminating) we may have already deleted other modules. By capturing the + # DeleteGraph function here, we retain the ability to cleanly destroy the + # graph at shutdown, which satisfies leak checkers. + self.deleter = c_api.TF_DeleteGraph def __del__(self): - # Note: when we're destructing the global context (i.e when the process is - # terminating) we can have already deleted other modules. - if c_api is not None and c_api.TF_DeleteGraph is not None: - c_api.TF_DeleteGraph(self.graph) + self.deleter(self.graph) class ScopedTFImportGraphDefOptions(object): @@ -83,14 +85,16 @@ class ScopedTFFunction(object): def __init__(self, func): self.func = func + # Note: when we're destructing the global context (i.e when the process is + # terminating) we may have already deleted other modules. By capturing the + # DeleteFunction function here, we retain the ability to cleanly destroy the + # Function at shutdown, which satisfies leak checkers. + self.deleter = c_api.TF_DeleteFunction def __del__(self): - # Note: when we're destructing the global context (i.e when the process is - # terminating) we can have already deleted other modules. - if c_api is not None and c_api.TF_DeleteFunction is not None: - if self.func is not None: - c_api.TF_DeleteFunction(self.func) - self.func = None + if self.func is not None: + self.deleter(self.func) + self.func = None class ApiDefMap(object): diff --git a/tensorflow/python/framework/config_test.py b/tensorflow/python/framework/config_test.py index 5f0c9b7e0bc..f956d8cf170 100644 --- a/tensorflow/python/framework/config_test.py +++ b/tensorflow/python/framework/config_test.py @@ -416,15 +416,27 @@ class DeviceTest(test.TestCase): self.assertEqual(len(config.get_visible_devices('CPU')), 1) self.assertGreater(len(config.get_visible_devices('GPU')), 0) + + # get_visible_devices filters out XLA_* devices. list_logical_devices does + # not, but we can't call it here because it initializes the devices and + # calling set_visible_devices after that is disallowed. + self.assertEqual(len(config.get_visible_devices('XLA_GPU')), 0) + config.set_visible_devices(cpus[0]) self.assertEqual(len(config.get_visible_devices('CPU')), 1) self.assertEqual(len(config.get_visible_devices('GPU')), 0) + self.assertEqual(len(config.list_logical_devices('XLA_GPU')), 0) with self.assertRaisesRegexp(RuntimeError, 'unknown device'): with ops.device('/device:GPU:0'): a = constant_op.constant(1.0) self.evaluate(a) + with self.assertRaisesRegexp(RuntimeError, 'unknown device'): + with ops.device('/device:XLA_GPU:0'): + a = constant_op.constant(1.0) + self.evaluate(a) + # Modifying the visible devices is not supported with self.assertRaisesRegexp(RuntimeError, 'cannot be modified'): config.set_visible_devices(gpus) diff --git a/tensorflow/python/framework/func_graph.py b/tensorflow/python/framework/func_graph.py index 57ee6d19cce..18dddea8a15 100644 --- a/tensorflow/python/framework/func_graph.py +++ b/tensorflow/python/framework/func_graph.py @@ -94,13 +94,13 @@ def convert_structure_to_signature(structure, arg_names=None): # of the function argument. name = user_specified_name else: - name = "/".join([str(p) for p in path]) + name = "/".join(str(p) for p in path) return tensor_spec.TensorSpec(arg.shape, arg.dtype, name) if isinstance(arg, composite_tensor.CompositeTensor): # TODO(b/133606651) Do we need to inject arg_name? return arg._type_spec # pylint: disable=protected-access if isinstance(arg, resource_variable_ops.BaseResourceVariable): - name = "/".join([str(p) for p in path]) + name = "/".join(str(p) for p in path) return resource_variable_ops.VariableSpec(arg.shape, arg.dtype, name) if isinstance(arg, ( int, diff --git a/tensorflow/python/framework/function.py b/tensorflow/python/framework/function.py index 6bd4ac32788..e39d3fc1d6f 100644 --- a/tensorflow/python/framework/function.py +++ b/tensorflow/python/framework/function.py @@ -1296,7 +1296,7 @@ def get_extra_args(): def _type_list_to_str(types): if any(_ not in _DTYPE_TO_STR for _ in types): raise ValueError("Unsupported dtypes: %s" % types) - return "".join([_DTYPE_TO_STR[_] for _ in types]) + return "".join(_DTYPE_TO_STR[_] for _ in types) # NOTE: The list needs to be extended when more data types are added. diff --git a/tensorflow/python/framework/graph_to_function_def.py b/tensorflow/python/framework/graph_to_function_def.py index 6e2ba345d75..a0ff548ac38 100644 --- a/tensorflow/python/framework/graph_to_function_def.py +++ b/tensorflow/python/framework/graph_to_function_def.py @@ -170,7 +170,7 @@ def graph_to_function_def(graph, operations, inputs, outputs, out_names=None): else: func.signature.output_arg.extend( [_tensor_to_argdef(o, name=n) for o, n in zip(outputs, out_names)]) - func_arg_placeholders = set([i.name for i in inputs]) + func_arg_placeholders = set(i.name for i in inputs) input_dict = _create_input_dict(graph, func_arg_placeholders, initial_value=initial_dict) diff --git a/tensorflow/python/framework/meta_graph.py b/tensorflow/python/framework/meta_graph.py index 0d4e49ad564..c1451ea5c48 100644 --- a/tensorflow/python/framework/meta_graph.py +++ b/tensorflow/python/framework/meta_graph.py @@ -297,9 +297,10 @@ def _find_extraneous_saver_nodes(graph_def, saver_def): # but it seems unnecessarily complex given the name scope solution. # load the graph DAG in minimal form, without initializing a full Graph object - nodes = {node_def.name: - (set([_op_name(x) for x in node_def.input]), node_def.op) - for node_def in graph_def.node} + nodes = { + node_def.name: (set(_op_name(x) for x in node_def.input), node_def.op) + for node_def in graph_def.node + } retain_scope_save = None retain_scope_restore = None @@ -313,12 +314,12 @@ def _find_extraneous_saver_nodes(graph_def, saver_def): retain_scope_restore = _get_scope(restore_op_name) + "/" retain_scope_save = _get_scope(save_op_name) + "/" - all_saver_node_names = set([name for name, (_, op) in nodes.items() - if op in SAVE_AND_RESTORE_OPS]) + all_saver_node_names = set( + name for name, (_, op) in nodes.items() if op in SAVE_AND_RESTORE_OPS) - all_saver_scopes = (set([_get_scope(x) for x in all_saver_node_names]) - - all_saver_node_names) - all_saver_scopes = set([x + "/" for x in all_saver_scopes]) + all_saver_scopes = ( + set(_get_scope(x) for x in all_saver_node_names) - all_saver_node_names) + all_saver_scopes = set(x + "/" for x in all_saver_scopes) extraneous_scopes = all_saver_scopes - set([retain_scope_save, retain_scope_restore]) @@ -766,9 +767,10 @@ def import_scoped_meta_graph_with_return_elements( sorted([compat.as_str(v) for v in field.value]) != sorted(input_map)): raise ValueError("Graph contains unbound inputs: %s. Must " - "provide these inputs through input_map." % - ",".join([compat.as_str(v) for v in field.value - if not input_map or v not in input_map])) + "provide these inputs through input_map." % ",".join( + compat.as_str(v) + for v in field.value + if not input_map or v not in input_map)) break # Sets graph to default graph if it's not passed in. diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py index faeb868da72..55af78c822c 100644 --- a/tensorflow/python/framework/ops.py +++ b/tensorflow/python/framework/ops.py @@ -1587,21 +1587,26 @@ _VALID_OP_NAME_REGEX = re.compile("^[A-Za-z0-9.][A-Za-z0-9_.\\-/>]*$") _VALID_SCOPE_NAME_REGEX = re.compile("^[A-Za-z0-9_.\\-/>]*$") -def _create_c_op(graph, node_def, inputs, control_inputs): +def _create_c_op(graph, node_def, inputs, control_inputs, op_def=None): """Creates a TF_Operation. Args: graph: a `Graph`. node_def: `node_def_pb2.NodeDef` for the operation to create. - inputs: A list of `Tensor`s (corresponding to scalar inputs) and lists of - `Tensor`s (corresponding to sequence inputs, e.g. "int64 * N", - "list(int64)"). The length of the list should be equal to the number of - inputs specified by this operation's op def. + inputs: A flattened list of `Tensor`s. This function handles grouping + tensors into lists as per attributes in the `node_def`. control_inputs: A list of `Operation`s to set as control dependencies. + op_def: Optional. `op_def_pb2.OpDef` for the operation to create. If not + specified, is looked up from the `graph` using `node_def.op`. Returns: A wrapped TF_Operation*. """ + if op_def is None: + op_def = graph._get_op_def(node_def.op) # pylint: disable=protected-access + # TODO(skyewm): op_def_library.apply_op() flattens the incoming inputs. + # Refactor so we don't have to do this here. + inputs = _reconstruct_sequence_inputs(op_def, inputs, node_def.attr) # pylint: disable=protected-access op_desc = c_api.TF_NewOperation(graph._c_graph, compat.as_str(node_def.op), compat.as_str(node_def.name)) @@ -1789,12 +1794,8 @@ class Operation(object): else: if op_def is None: op_def = self._graph._get_op_def(node_def.op) - # TODO(skyewm): op_def_library.apply_op() flattens the incoming inputs. - # Refactor so we don't have to do this here. - grouped_inputs = self._reconstruct_sequence_inputs( - op_def, inputs, node_def.attr) - self._c_op = _create_c_op(self._graph, node_def, grouped_inputs, - control_input_ops) + self._c_op = _create_c_op(self._graph, node_def, inputs, + control_input_ops, op_def) name = compat.as_str(node_def.name) # pylint: enable=protected-access @@ -1832,41 +1833,6 @@ class Operation(object): if self._control_flow_context is not None: self._control_flow_context.AddOp(self) - def _reconstruct_sequence_inputs(self, op_def, inputs, attrs): - """Regroups a flat list of input tensors into scalar and sequence inputs. - - Args: - op_def: The `op_def_pb2.OpDef` (for knowing the input types) - inputs: a list of input `Tensor`s to the op. - attrs: mapping from attr name to `attr_value_pb2.AttrValue` (these define - how long each sequence is) - - Returns: - A list of `Tensor`s (corresponding to scalar inputs) and lists of - `Tensor`s (corresponding to sequence inputs). - """ - grouped_inputs = [] - i = 0 - for input_arg in op_def.input_arg: - if input_arg.number_attr: - input_len = attrs[input_arg.number_attr].i - is_sequence = True - elif input_arg.type_list_attr: - input_len = len(attrs[input_arg.type_list_attr].list.type) - is_sequence = True - else: - input_len = 1 - is_sequence = False - - if is_sequence: - grouped_inputs.append(inputs[i:i + input_len]) - else: - grouped_inputs.append(inputs[i]) - i += input_len - - assert i == len(inputs) - return grouped_inputs - def colocation_groups(self): """Returns the list of colocation groups of the op.""" default_colocation_group = [compat.as_bytes("loc:@%s" % self.name)] @@ -3317,7 +3283,7 @@ class Graph(object): node_def = _NodeDef(op_type, name, attrs) - input_ops = set([t.op for t in inputs]) + input_ops = set(t.op for t in inputs) control_inputs = self._control_dependencies_for_inputs(input_ops) # _create_op_helper mutates the new Operation. `_mutation_lock` ensures a # Session.run call cannot occur between creating and mutating the op. @@ -4476,7 +4442,7 @@ class Graph(object): # Don't add a control input if we already have a data dependency on i. # NOTE(mrry): We do not currently track transitive data dependencies, # so we may add redundant control inputs. - ret.extend([c for c in controller.control_inputs if c not in input_ops]) + ret.extend(c for c in controller.control_inputs if c not in input_ops) return ret def _record_op_seen_by_control_dependencies(self, op): @@ -6196,6 +6162,9 @@ def name_scope(name, default_name=None, values=None, skip_on_eager=True): if not in_eager_mode: return internal_name_scope_v1(name, default_name, values) + if skip_on_eager: + return NullContextmanager() + name = default_name if name is None else name if values: # The presence of a graph tensor in `values` overrides the context. @@ -6207,9 +6176,6 @@ def name_scope(name, default_name=None, values=None, skip_on_eager=True): if graph_value is not None: return graph_value.graph.name_scope(name) - if skip_on_eager: - return NullContextmanager() - return name_scope_v2(name or "") @@ -6666,3 +6632,39 @@ def add_exit_callback_to_default_func_graph(fn): "Cannot add scope exit callbacks when not building a function. " "Default graph: {}".format(default_graph)) default_graph._add_scope_exit_callback(fn) # pylint: disable=protected-access + + +def _reconstruct_sequence_inputs(op_def, inputs, attrs): + """Regroups a flat list of input tensors into scalar and sequence inputs. + + Args: + op_def: The `op_def_pb2.OpDef` (for knowing the input types) + inputs: a list of input `Tensor`s to the op. + attrs: mapping from attr name to `attr_value_pb2.AttrValue` (these define + how long each sequence is) + + Returns: + A list of `Tensor`s (corresponding to scalar inputs) and lists of + `Tensor`s (corresponding to sequence inputs). + """ + grouped_inputs = [] + i = 0 + for input_arg in op_def.input_arg: + if input_arg.number_attr: + input_len = attrs[input_arg.number_attr].i + is_sequence = True + elif input_arg.type_list_attr: + input_len = len(attrs[input_arg.type_list_attr].list.type) + is_sequence = True + else: + input_len = 1 + is_sequence = False + + if is_sequence: + grouped_inputs.append(inputs[i:i + input_len]) + else: + grouped_inputs.append(inputs[i]) + i += input_len + + assert i == len(inputs) + return grouped_inputs diff --git a/tensorflow/python/framework/tensor_spec.py b/tensorflow/python/framework/tensor_spec.py index c96bfb13b3b..b5275372447 100644 --- a/tensorflow/python/framework/tensor_spec.py +++ b/tensorflow/python/framework/tensor_spec.py @@ -255,10 +255,10 @@ class BoundedTensorSpec(TensorSpec): raise ValueError("maximum is not compatible with shape. " "Message: {!r}.".format(exception)) - self._minimum = np.array(minimum, dtype=self.dtype.as_numpy_dtype()) + self._minimum = np.array(minimum, dtype=self.dtype.as_numpy_dtype) self._minimum.setflags(write=False) - self._maximum = np.array(maximum, dtype=self.dtype.as_numpy_dtype()) + self._maximum = np.array(maximum, dtype=self.dtype.as_numpy_dtype) self._maximum.setflags(write=False) @classmethod diff --git a/tensorflow/python/framework/test_util.py b/tensorflow/python/framework/test_util.py index dfeca7e8157..4a7dd5a573a 100644 --- a/tensorflow/python/framework/test_util.py +++ b/tensorflow/python/framework/test_util.py @@ -2409,7 +2409,7 @@ class TensorFlowTestCase(googletest.TestCase): path=None, msg=None): path = path or [] - path_str = (("[" + "][".join([str(p) for p in path]) + "]") if path else "") + path_str = (("[" + "][".join(str(p) for p in path) + "]") if path else "") msg = msg if msg else "" # Check if a and/or b are namedtuples. diff --git a/tensorflow/python/framework/testdata/metrics_export_meta_graph.pb b/tensorflow/python/framework/testdata/metrics_export_meta_graph.pb index fffbe8df2f0..35fa0eb7fff 100644 --- a/tensorflow/python/framework/testdata/metrics_export_meta_graph.pb +++ b/tensorflow/python/framework/testdata/metrics_export_meta_graph.pb @@ -503,7 +503,8 @@ graph_def { size: 2 } } - tensor_content: "\000\000\000\000\000\000\200?" + float_val: 0. + float_val: 1. } } } @@ -567,7 +568,8 @@ graph_def { size: 2 } } - tensor_content: "ff\206\300\232\231\021A" + float_val: -4.2 + float_val: 9.1 } } } @@ -631,7 +633,8 @@ graph_def { size: 2 } } - tensor_content: "\000\000\320@\000\000\000\000" + float_val: 6.5 + float_val: 0. } } } @@ -695,7 +698,8 @@ graph_def { size: 2 } } - tensor_content: "\315\314L\300\000\000\200@" + float_val: -3.2 + float_val: 0. } } } @@ -1158,7 +1162,8 @@ graph_def { size: 2 } } - tensor_content: "\000\000\000\000\001\000\000\000" + int_val: 0 + int_val: 1 } } } diff --git a/tensorflow/python/grappler/memory_optimizer_test.py b/tensorflow/python/grappler/memory_optimizer_test.py index d2b634a4c62..2beed594479 100644 --- a/tensorflow/python/grappler/memory_optimizer_test.py +++ b/tensorflow/python/grappler/memory_optimizer_test.py @@ -90,7 +90,7 @@ class MemoryOptimizerSwapTest(test.TestCase): self.assertEqual(len(graph.node), graph_size + 2) self.assertTrue( - set([node.name for node in graph.node]) > set( + set(node.name for node in graph.node) > set( ['a', 'b', 'c', 'd', 'swap_in_d_0', 'swap_out_d_0'])) for node in graph.node: if node.name == 'swap_in_d_0': diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD index b90a208292b..b5f565f2a6f 100755 --- a/tensorflow/python/keras/BUILD +++ b/tensorflow/python/keras/BUILD @@ -129,6 +129,7 @@ py_library( name = "engine", srcs = [ "engine/__init__.py", + "engine/compile_utils.py", "engine/input_layer.py", "engine/network.py", "engine/node.py", @@ -546,27 +547,29 @@ tf_py_test( name = "integration_test", size = "medium", srcs = ["integration_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:nn_ops", - ], + python_version = "PY3", shard_count = 16, tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//tensorflow/python:nn_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "activations_test", size = "small", srcs = ["activations_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -574,11 +577,12 @@ tf_py_test( name = "constraints_test", size = "small", srcs = ["constraints_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -586,26 +590,28 @@ tf_py_test( name = "data_adapter_test", size = "medium", srcs = ["engine/data_adapter_test.py"], - additional_deps = [ - ":data_adapter", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":data_adapter", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], ) tf_py_test( name = "initializers_test", size = "small", srcs = ["initializers_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:init_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -613,10 +619,11 @@ tf_py_test( name = "regularizers_test", size = "medium", srcs = ["regularizers_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], ) @@ -624,26 +631,28 @@ tf_py_test( name = "optimizers_test", size = "medium", srcs = ["optimizers_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:training", - ], + python_version = "PY3", shard_count = 8, tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//tensorflow/python:training", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "losses_test", size = "small", srcs = ["losses_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -651,10 +660,11 @@ tf_py_test( name = "metrics_functional_test", size = "small", srcs = ["metrics_functional_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", ], ) @@ -662,61 +672,66 @@ tf_py_test( name = "metrics_test", size = "medium", srcs = ["metrics_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "metrics_confusion_matrix_test", size = "medium", srcs = ["metrics_confusion_matrix_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "metrics_correctness_test", size = "medium", srcs = ["metrics_correctness_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "temporal_sample_weights_correctness_test", size = "medium", srcs = ["temporal_sample_weights_correctness_test.py"], - additional_deps = [ - ":keras", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 20, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], ) tf_py_test( name = "advanced_activations_test", size = "medium", srcs = ["layers/advanced_activations_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], ) @@ -724,67 +739,72 @@ tf_py_test( name = "tensorflow_op_layer_test", size = "medium", srcs = ["layers/tensorflow_op_layer_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 3, + deps = [ ":keras", ":saving", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python/eager:backprop", "//tensorflow/python/eager:context", "//tensorflow/python/eager:def_function", + "@absl_py//absl/testing:parameterized", ], - shard_count = 3, ) tf_py_test( name = "convolutional_recurrent_test", size = "medium", srcs = ["layers/convolutional_recurrent_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = ["no_rocm"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "convolutional_test", size = "medium", srcs = ["layers/convolutional_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 8, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "image_preprocessing_test", size = "medium", srcs = ["layers/preprocessing/image_preprocessing_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "convolutional_transpose_test", size = "medium", srcs = ["layers/convolutional_transpose_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -792,84 +812,90 @@ cuda_py_test( name = "cudnn_recurrent_test", size = "medium", srcs = ["layers/cudnn_recurrent_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "no_rocm", "no_windows_gpu", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "base_preprocessing_layer_test", size = "medium", srcs = ["engine/base_preprocessing_layer_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "pooling_test", size = "medium", srcs = ["layers/pooling_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 8, # TODO(b/127881287): Re-enable. tags = [ "no_windows_gpu", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "core_test", size = "medium", srcs = ["layers/core_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 3, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "subclassed_layers_test", size = "medium", srcs = ["layers/subclassed_layers_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 3, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "dense_attention_test", size = "medium", srcs = ["layers/dense_attention_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", + "//tensorflow/python:client_testlib", "//third_party/py/numpy", "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", ], ) @@ -877,10 +903,11 @@ cuda_py_test( name = "embeddings_test", size = "medium", srcs = ["layers/embeddings_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], ) @@ -888,25 +915,27 @@ tf_py_test( name = "local_test", size = "medium", srcs = ["layers/local_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = ["no_windows"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "merge_test", size = "medium", srcs = ["layers/merge_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -914,10 +943,11 @@ tf_py_test( name = "noise_test", size = "small", srcs = ["layers/noise_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], ) @@ -925,127 +955,136 @@ cuda_py_test( name = "normalization_test", size = "medium", srcs = ["layers/normalization_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "no_rocm", "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "preprocessing_normalization_test", size = "small", srcs = ["layers/preprocessing/normalization_test.py"], - additional_deps = [ + main = "normalization_test.py", + python_version = "PY3", + deps = [ ":keras", ":preprocessing_test_utils", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], - main = "normalization_test.py", ) tf_py_test( name = "preprocessing_text_vectorization_test", size = "medium", srcs = ["layers/preprocessing/text_vectorization_test.py"], - additional_deps = [ + main = "text_vectorization_test.py", + python_version = "PY3", + deps = [ ":generic_utils", ":keras", ":preprocessing_test_utils", + "//tensorflow/python:client_testlib", "//tensorflow/python/ops/ragged:ragged_string_ops", "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", ], - main = "text_vectorization_test.py", ) tf_py_test( name = "simplernn_test", size = "medium", srcs = ["layers/simplernn_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "gru_test", size = "medium", srcs = ["layers/gru_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = ["notsan"], # http://b/62136390 + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "lstm_test", size = "medium", srcs = ["layers/lstm_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "noasan", # times out b/63678675 "notsan", # http://b/62189182 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "recurrent_test", size = "medium", srcs = ["layers/recurrent_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 10, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "recurrent_v2_test", size = "medium", srcs = ["layers/recurrent_v2_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 2, + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "separable_convolutional_test", size = "medium", srcs = ["layers/separable_convolutional_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1053,38 +1092,41 @@ cuda_py_test( name = "lstm_v2_test", size = "medium", srcs = ["layers/lstm_v2_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 12, tags = ["no_rocm"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "gru_v2_test", size = "medium", srcs = ["layers/gru_v2_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 12, tags = ["no_rocm"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "serialization_test", size = "small", srcs = ["layers/serialization_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], ) @@ -1092,13 +1134,12 @@ tf_py_test( name = "kernelized_test", size = "small", srcs = ["layers/kernelized_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":backend", ":initializers", ":keras", ":layers", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -1111,6 +1152,8 @@ tf_py_test( "//tensorflow/python:random_seed", "//tensorflow/python:tensor_shape", "//tensorflow/python/eager:context", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1118,75 +1161,73 @@ tf_py_test( name = "wrappers_test", size = "large", srcs = ["layers/wrappers_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/ops/ragged:ragged_factory_ops", - "//tensorflow/python/ops/ragged:ragged_concat_ops", - ], + python_version = "PY3", shard_count = 6, tags = [ "noasan", # http://b/78599823 "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//tensorflow/python/ops/ragged:ragged_concat_ops", + "//tensorflow/python/ops/ragged:ragged_factory_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "rnn_cell_wrapper_v2_test", size = "medium", srcs = ["layers/rnn_cell_wrapper_v2_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "time_distributed_learning_phase_test", size = "small", srcs = ["layers/time_distributed_learning_phase_test.py"], - additional_deps = [ - ":keras", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "noasan", # http://b/78599823 "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + ], ) tf_py_test( name = "scikit_learn_test", size = "small", srcs = ["wrappers/scikit_learn_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "data_utils_test", size = "medium", srcs = ["utils/data_utils_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], python_version = "PY3", shard_count = 6, tags = [ @@ -1194,16 +1235,23 @@ tf_py_test( "notsan", "optonly", # times out ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "generic_utils_test", size = "small", srcs = ["utils/generic_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], ) @@ -1211,10 +1259,11 @@ tf_py_test( name = "version_utils_test", size = "small", srcs = ["utils/version_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], ) @@ -1222,7 +1271,8 @@ tf_py_test( name = "tf_utils_test", size = "small", srcs = ["utils/tf_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", "//tensorflow/python:client_testlib", ], @@ -1232,12 +1282,12 @@ tf_py_test( name = "composite_tensor_support_test", size = "medium", srcs = ["utils/composite_tensor_support_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 8, + tags = ["no_windows"], # b/135752236 + deps = [ ":engine", ":layers", - "//third_party/py/numpy", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python/ops/ragged:ragged_tensor", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -1246,36 +1296,39 @@ tf_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:sparse_ops", "//tensorflow/python:sparse_tensor", + "//tensorflow/python/ops/ragged:ragged_tensor", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - shard_count = 8, - tags = ["no_windows"], # b/135752236 ) tf_py_test( name = "io_utils_test", size = "small", srcs = ["utils/io_utils_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "no_windows", # TODO: needs investigation on Windows "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "np_utils_test", size = "small", srcs = ["utils/np_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1283,54 +1336,58 @@ tf_py_test( name = "kernelized_utils_test", size = "small", srcs = ["utils/kernelized_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":layers", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", + "@absl_py//absl/testing:parameterized", ], ) cuda_py_test( name = "multi_gpu_utils_test", srcs = ["utils/multi_gpu_utils_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "guitar", "multi_gpu", ], xla_enable_strict_auto_jit = False, # b/142744009 + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "training_gpu_test", size = "small", srcs = ["engine/training_gpu_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "vis_utils_test", size = "small", srcs = ["utils/vis_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1338,11 +1395,12 @@ tf_py_test( name = "conv_utils_test", size = "small", srcs = ["utils/conv_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1350,11 +1408,12 @@ tf_py_test( name = "image_test", size = "medium", srcs = ["preprocessing/image_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1362,11 +1421,12 @@ tf_py_test( name = "sequence_test", size = "small", srcs = ["preprocessing/sequence_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1374,11 +1434,12 @@ tf_py_test( name = "text_test", size = "small", srcs = ["preprocessing/text_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1386,218 +1447,245 @@ tf_py_test( name = "callbacks_test", size = "medium", srcs = ["callbacks_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "@six_archive//:six", - "//tensorflow/python:client_testlib", - ], python_version = "PY3", shard_count = 4, tags = [ "no_oss", "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + "@six_archive//:six", + ], ) tf_py_test( name = "callbacks_v1_test", size = "medium", srcs = ["callbacks_v1_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "correctness_test", size = "medium", srcs = ["engine/correctness_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 2, tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "input_spec_test", size = "small", srcs = ["engine/input_spec_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "training_test", size = "medium", srcs = ["engine/training_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 20, tags = [ "no_rocm", "nomac", # TODO(mihaimaruseac): b/127695564 "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], +) + +tf_py_test( + name = "compile_utils_test", + size = "medium", + srcs = ["engine/compile_utils_test.py"], + tags = [ + "nomac", # TODO(b/146226927) + ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "training_dataset_test", size = "medium", srcs = ["engine/training_dataset_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "training_arrays_test", size = "medium", srcs = ["engine/training_arrays_test.py"], - additional_deps = [ - ":keras", - ":layers", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "@six_archive//:six", - "//tensorflow/python/data/ops:dataset_ops", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "no_rocm", "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + ":layers", + "//tensorflow/python:client_testlib", + "//tensorflow/python/data/ops:dataset_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + "@six_archive//:six", + ], ) tf_py_test( name = "training_generator_test", size = "medium", srcs = ["engine/training_generator_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 6, tags = [ "noasan", # TODO(b/132183295): Re-enable this. "nomac", # TODO(b/140193633): Re-enable this. "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "training_integration_test", size = "medium", srcs = ["engine/training_integration_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 30, tags = [ "no_rocm", "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "feature_columns_integration_test", size = "medium", srcs = ["engine/feature_columns_integration_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/feature_column:feature_column_py", - ], + python_version = "PY3", tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//tensorflow/python/feature_column:feature_column_py", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "training_eager_test", size = "medium", srcs = ["engine/training_eager_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "no_rocm", "nomac", # TODO(mihaimaruseac): b/127695564 "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "training_utils_test", size = "medium", srcs = ["engine/training_utils_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "no_oss", # TODO(b/135021748) reenable "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "training_v2_utils_test", size = "medium", srcs = ["engine/training_v2_utils_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python/distribute:strategy_combinations", - ], + python_version = "PY3", tags = [ "no_oss", # TODO(b/135021748) reenable "notsan", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//tensorflow/python/distribute:strategy_combinations", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) py_library( @@ -1613,187 +1701,197 @@ tf_py_test( name = "model_subclassing_test", size = "medium", srcs = ["model_subclassing_test.py"], - additional_deps = [ - ":model_subclassing_test_util", - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], python_version = "PY3", shard_count = 4, tags = [ "no_windows", "notsan", ], + deps = [ + ":keras", + ":model_subclassing_test_util", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "model_subclassing_compiled_test", size = "medium", srcs = ["model_subclassing_compiled_test.py"], - additional_deps = [ - ":model_subclassing_test_util", - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "no_windows", "notsan", ], + deps = [ + ":keras", + ":model_subclassing_test_util", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "custom_training_loop_test", size = "medium", srcs = ["custom_training_loop_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "network_test", size = "medium", srcs = ["engine/network_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 8, tags = [ "no-internal-py3", "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "base_layer_test", size = "medium", srcs = ["engine/base_layer_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], python_version = "PY3", shard_count = 8, tags = [ "no_rocm", "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "base_layer_utils_test", srcs = ["engine/base_layer_utils_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "no_rocm", "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "control_flow_test", size = "medium", srcs = ["engine/control_flow_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 8, tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "hdf5_format_test", size = "medium", srcs = ["saving/hdf5_format_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "no_windows", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "sequential_test", size = "medium", srcs = ["engine/sequential_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = [ "nomac", # TODO(mihaimaruseac): b/127695564 ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "models_test", size = "medium", srcs = ["models_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:training", - ], + python_version = "PY3", shard_count = 8, tags = ["notsan"], # b/67509773 + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//tensorflow/python:training", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "backend_test", size = "medium", srcs = ["backend_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 4, + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:util", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - shard_count = 4, ) tf_py_test( name = "backend_config_test", size = "medium", srcs = ["backend_config_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:util", + "//third_party/py/numpy", ], ) @@ -1801,25 +1899,27 @@ tf_py_test( name = "keras_parameterized_test", size = "small", srcs = ["keras_parameterized_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "save_test", size = "medium", srcs = ["saving/save_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python/feature_column:feature_column_v2", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1827,57 +1927,59 @@ tf_py_test( name = "saved_model_experimental_test", size = "medium", srcs = ["saving/saved_model_experimental_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "no_oss", # TODO(b/119349471): Re-enable "no_windows", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "saved_model_test", size = "medium", srcs = ["saving/saved_model/saved_model_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python/distribute:mirrored_strategy", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", shard_count = 4, tags = [ "no_windows", ], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//tensorflow/python/distribute:mirrored_strategy", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "saving_utils_test", size = "medium", srcs = ["saving/saving_utils_test.py"], - additional_deps = [ - ":keras", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - ], + python_version = "PY3", tags = ["notsan"], + deps = [ + ":keras", + "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "metrics_utils_test", size = "small", srcs = ["utils/metrics_utils_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":keras", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python/ops/ragged:ragged_tensor", "//tensorflow/python:constant_op", "//tensorflow/python:framework_ops", "//tensorflow/python:framework_test_lib", @@ -1885,5 +1987,7 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python/eager:context", "//tensorflow/python/ops/ragged:ragged_factory_ops", + "//tensorflow/python/ops/ragged:ragged_tensor", + "@absl_py//absl/testing:parameterized", ], ) diff --git a/tensorflow/python/keras/applications/BUILD b/tensorflow/python/keras/applications/BUILD index 69f26928edf..f5faae02a7e 100644 --- a/tensorflow/python/keras/applications/BUILD +++ b/tensorflow/python/keras/applications/BUILD @@ -34,7 +34,6 @@ py_library( "//tensorflow/python/keras:backend", "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers_base", - "@keras_applications_archive//:keras_applications", ], ) @@ -42,10 +41,22 @@ tf_py_test( name = "applications_test", size = "medium", srcs = ["applications_test.py"], - additional_deps = [ + shard_count = 32, + deps = [ ":applications", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", + ], +) + +tf_py_test( + name = "imagenet_utils_test", + size = "medium", + srcs = ["imagenet_utils_test.py"], + shard_count = 2, + deps = [ + ":applications", + "//tensorflow/python:client_testlib", + "@absl_py//absl/testing:parameterized", ], - shard_count = 11, ) diff --git a/tensorflow/python/keras/applications/__init__.py b/tensorflow/python/keras/applications/__init__.py index 601c990e2f6..37fe5b24132 100644 --- a/tensorflow/python/keras/applications/__init__.py +++ b/tensorflow/python/keras/applications/__init__.py @@ -13,35 +13,6 @@ # limitations under the License. # ============================================================================== """Keras Applications are canned architectures with pre-trained weights.""" -# pylint: disable=g-import-not-at-top -# pylint: disable=g-bad-import-order from __future__ import absolute_import from __future__ import division from __future__ import print_function - -import keras_applications - - -def keras_modules_injection(base_fun): - """Decorator injecting tf.keras replacements for Keras modules. - - Arguments: - base_fun: Application function to decorate (e.g. `MobileNet`). - - Returns: - Decorated function that injects keyword argument for the tf.keras - modules required by the Applications. - """ - from tensorflow.python.keras import backend - from tensorflow.python.keras import layers - from tensorflow.python.keras import models - from tensorflow.python.keras.utils import all_utils - - def wrapper(*args, **kwargs): - kwargs['backend'] = backend - if 'layers' not in kwargs: - kwargs['layers'] = layers - kwargs['models'] = models - kwargs['utils'] = all_utils - return base_fun(*args, **kwargs) - return wrapper diff --git a/tensorflow/python/keras/applications/applications_test.py b/tensorflow/python/keras/applications/applications_test.py index 957fd5c7a82..b790eb83f95 100644 --- a/tensorflow/python/keras/applications/applications_test.py +++ b/tensorflow/python/keras/applications/applications_test.py @@ -20,6 +20,7 @@ from __future__ import print_function from absl.testing import parameterized +from tensorflow.python.keras import backend from tensorflow.python.keras.applications import densenet from tensorflow.python.keras.applications import inception_resnet_v2 from tensorflow.python.keras.applications import inception_v3 @@ -34,7 +35,7 @@ from tensorflow.python.keras.applications import xception from tensorflow.python.platform import test -MODEL_LIST = [ +MODEL_LIST_NO_NASNET = [ (resnet.ResNet50, 2048), (resnet.ResNet101, 2048), (resnet.ResNet152, 2048), @@ -51,17 +52,70 @@ MODEL_LIST = [ (densenet.DenseNet121, 1024), (densenet.DenseNet169, 1664), (densenet.DenseNet201, 1920), - (nasnet.NASNetMobile, 1056), ] +NASNET_LIST = [ + (nasnet.NASNetMobile, 1056), + (nasnet.NASNetLarge, 4032), +] + +MODEL_LIST = MODEL_LIST_NO_NASNET + NASNET_LIST + class ApplicationsTest(test.TestCase, parameterized.TestCase): + def assertShapeEqual(self, shape1, shape2): + if len(shape1) != len(shape2): + raise AssertionError( + 'Shapes are different rank: %s vs %s' % (shape1, shape2)) + for v1, v2 in zip(shape1, shape2): + if v1 != v2: + raise AssertionError('Shapes differ: %s vs %s' % (shape1, shape2)) + @parameterized.parameters(*MODEL_LIST) - def test_feature_extration_model(self, model_fn, output_dim): - model = model_fn(include_top=False, weights=None) - self.assertLen(model.output_shape, 4) - self.assertEqual(model.output_shape[-1], output_dim) + def test_application_notop(self, app, last_dim): + if 'NASNet' in app.__name__: + only_check_last_dim = True + else: + only_check_last_dim = False + output_shape = _get_output_shape( + lambda: app(weights=None, include_top=False)) + if only_check_last_dim: + self.assertEqual(output_shape[-1], last_dim) + else: + self.assertShapeEqual(output_shape, (None, None, None, last_dim)) + backend.clear_session() + + @parameterized.parameters(MODEL_LIST) + def test_application_pooling(self, app, last_dim): + output_shape = _get_output_shape( + lambda: app(weights=None, include_top=False, pooling='avg')) + self.assertShapeEqual(output_shape, (None, last_dim)) + + @parameterized.parameters(*MODEL_LIST_NO_NASNET) + def test_application_variable_input_channels(self, app, last_dim): + if backend.image_data_format() == 'channels_first': + input_shape = (1, None, None) + else: + input_shape = (None, None, 1) + output_shape = _get_output_shape( + lambda: app(weights=None, include_top=False, input_shape=input_shape)) + self.assertShapeEqual(output_shape, (None, None, None, last_dim)) + backend.clear_session() + + if backend.image_data_format() == 'channels_first': + input_shape = (4, None, None) + else: + input_shape = (None, None, 4) + output_shape = _get_output_shape( + lambda: app(weights=None, include_top=False, input_shape=input_shape)) + self.assertShapeEqual(output_shape, (None, None, None, last_dim)) + backend.clear_session() + + +def _get_output_shape(model_fn): + model = model_fn() + return model.output_shape if __name__ == '__main__': diff --git a/tensorflow/python/keras/applications/densenet.py b/tensorflow/python/keras/applications/densenet.py index 9404968c810..237202ff429 100644 --- a/tensorflow/python/keras/applications/densenet.py +++ b/tensorflow/python/keras/applications/densenet.py @@ -14,45 +14,381 @@ # ============================================================================== # pylint: disable=invalid-name """DenseNet models for Keras. + +Reference paper: + - [Densely Connected Convolutional Networks] + (https://arxiv.org/abs/1608.06993) (CVPR 2017 Best Paper Award) """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import densenet +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils from tensorflow.python.util.tf_export import keras_export +BASE_WEIGTHS_PATH = ('https://storage.googleapis.com/tensorflow/' + 'keras-applications/densenet/') +DENSENET121_WEIGHT_PATH = ( + BASE_WEIGTHS_PATH + 'densenet121_weights_tf_dim_ordering_tf_kernels.h5') +DENSENET121_WEIGHT_PATH_NO_TOP = ( + BASE_WEIGTHS_PATH + + 'densenet121_weights_tf_dim_ordering_tf_kernels_notop.h5') +DENSENET169_WEIGHT_PATH = ( + BASE_WEIGTHS_PATH + 'densenet169_weights_tf_dim_ordering_tf_kernels.h5') +DENSENET169_WEIGHT_PATH_NO_TOP = ( + BASE_WEIGTHS_PATH + + 'densenet169_weights_tf_dim_ordering_tf_kernels_notop.h5') +DENSENET201_WEIGHT_PATH = ( + BASE_WEIGTHS_PATH + 'densenet201_weights_tf_dim_ordering_tf_kernels.h5') +DENSENET201_WEIGHT_PATH_NO_TOP = ( + BASE_WEIGTHS_PATH + + 'densenet201_weights_tf_dim_ordering_tf_kernels_notop.h5') + + +def dense_block(x, blocks, name): + """A dense block. + + Arguments: + x: input tensor. + blocks: integer, the number of building blocks. + name: string, block label. + + Returns: + Output tensor for the block. + """ + for i in range(blocks): + x = conv_block(x, 32, name=name + '_block' + str(i + 1)) + return x + + +def transition_block(x, reduction, name): + """A transition block. + + Arguments: + x: input tensor. + reduction: float, compression rate at transition layers. + name: string, block label. + + Returns: + output tensor for the block. + """ + bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1 + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_bn')( + x) + x = layers.Activation('relu', name=name + '_relu')(x) + x = layers.Conv2D( + int(backend.int_shape(x)[bn_axis] * reduction), + 1, + use_bias=False, + name=name + '_conv')( + x) + x = layers.AveragePooling2D(2, strides=2, name=name + '_pool')(x) + return x + + +def conv_block(x, growth_rate, name): + """A building block for a dense block. + + Arguments: + x: input tensor. + growth_rate: float, growth rate at dense layers. + name: string, block label. + + Returns: + Output tensor for the block. + """ + bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1 + x1 = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_0_bn')( + x) + x1 = layers.Activation('relu', name=name + '_0_relu')(x1) + x1 = layers.Conv2D( + 4 * growth_rate, 1, use_bias=False, name=name + '_1_conv')( + x1) + x1 = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_1_bn')( + x1) + x1 = layers.Activation('relu', name=name + '_1_relu')(x1) + x1 = layers.Conv2D( + growth_rate, 3, padding='same', use_bias=False, name=name + '_2_conv')( + x1) + x = layers.Concatenate(axis=bn_axis, name=name + '_concat')([x, x1]) + return x + + +def DenseNet(blocks, + include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the DenseNet architecture. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + blocks: numbers of building blocks for the four dense layers. + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor + (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` (with `'channels_last'` data format) + or `(3, 224, 224)` (with `'channels_first'` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 32. + E.g. `(200, 200, 3)` would be one valid value. + pooling: optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top`' + ' as true, `classes` should be 1000') + + # Determine proper input shape + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=224, + min_size=32, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1 + + x = layers.ZeroPadding2D(padding=((3, 3), (3, 3)))(img_input) + x = layers.Conv2D(64, 7, strides=2, use_bias=False, name='conv1/conv')(x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name='conv1/bn')( + x) + x = layers.Activation('relu', name='conv1/relu')(x) + x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)))(x) + x = layers.MaxPooling2D(3, strides=2, name='pool1')(x) + + x = dense_block(x, blocks[0], name='conv2') + x = transition_block(x, 0.5, name='pool2') + x = dense_block(x, blocks[1], name='conv3') + x = transition_block(x, 0.5, name='pool3') + x = dense_block(x, blocks[2], name='conv4') + x = transition_block(x, 0.5, name='pool4') + x = dense_block(x, blocks[3], name='conv5') + + x = layers.BatchNormalization(axis=bn_axis, epsilon=1.001e-5, name='bn')(x) + x = layers.Activation('relu', name='relu')(x) + + if include_top: + x = layers.GlobalAveragePooling2D(name='avg_pool')(x) + x = layers.Dense(classes, activation='softmax', name='fc1000')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D(name='avg_pool')(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D(name='max_pool')(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + + # Create model. + if blocks == [6, 12, 24, 16]: + model = training.Model(inputs, x, name='densenet121') + elif blocks == [6, 12, 32, 32]: + model = training.Model(inputs, x, name='densenet169') + elif blocks == [6, 12, 48, 32]: + model = training.Model(inputs, x, name='densenet201') + else: + model = training.Model(inputs, x, name='densenet') + + # Load weights. + if weights == 'imagenet': + if include_top: + if blocks == [6, 12, 24, 16]: + weights_path = data_utils.get_file( + 'densenet121_weights_tf_dim_ordering_tf_kernels.h5', + DENSENET121_WEIGHT_PATH, + cache_subdir='models', + file_hash='9d60b8095a5708f2dcce2bca79d332c7') + elif blocks == [6, 12, 32, 32]: + weights_path = data_utils.get_file( + 'densenet169_weights_tf_dim_ordering_tf_kernels.h5', + DENSENET169_WEIGHT_PATH, + cache_subdir='models', + file_hash='d699b8f76981ab1b30698df4c175e90b') + elif blocks == [6, 12, 48, 32]: + weights_path = data_utils.get_file( + 'densenet201_weights_tf_dim_ordering_tf_kernels.h5', + DENSENET201_WEIGHT_PATH, + cache_subdir='models', + file_hash='1ceb130c1ea1b78c3bf6114dbdfd8807') + else: + if blocks == [6, 12, 24, 16]: + weights_path = data_utils.get_file( + 'densenet121_weights_tf_dim_ordering_tf_kernels_notop.h5', + DENSENET121_WEIGHT_PATH_NO_TOP, + cache_subdir='models', + file_hash='30ee3e1110167f948a6b9946edeeb738') + elif blocks == [6, 12, 32, 32]: + weights_path = data_utils.get_file( + 'densenet169_weights_tf_dim_ordering_tf_kernels_notop.h5', + DENSENET169_WEIGHT_PATH_NO_TOP, + cache_subdir='models', + file_hash='b8c4d4c20dd625c148057b9ff1c1176b') + elif blocks == [6, 12, 48, 32]: + weights_path = data_utils.get_file( + 'densenet201_weights_tf_dim_ordering_tf_kernels_notop.h5', + DENSENET201_WEIGHT_PATH_NO_TOP, + cache_subdir='models', + file_hash='c13680b51ded0fb44dff2d8f86ac8bb1') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model + + @keras_export('keras.applications.densenet.DenseNet121', 'keras.applications.DenseNet121') -@keras_modules_injection -def DenseNet121(*args, **kwargs): - return densenet.DenseNet121(*args, **kwargs) +def DenseNet121(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the Densenet121 architecture.""" + return DenseNet([6, 12, 24, 16], include_top, weights, input_tensor, + input_shape, pooling, classes) @keras_export('keras.applications.densenet.DenseNet169', 'keras.applications.DenseNet169') -@keras_modules_injection -def DenseNet169(*args, **kwargs): - return densenet.DenseNet169(*args, **kwargs) +def DenseNet169(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the Densenet169 architecture.""" + return DenseNet([6, 12, 32, 32], include_top, weights, input_tensor, + input_shape, pooling, classes) @keras_export('keras.applications.densenet.DenseNet201', 'keras.applications.DenseNet201') -@keras_modules_injection -def DenseNet201(*args, **kwargs): - return densenet.DenseNet201(*args, **kwargs) - - -@keras_export('keras.applications.densenet.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return densenet.decode_predictions(*args, **kwargs) +def DenseNet201(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the Densenet201 architecture.""" + return DenseNet([6, 12, 48, 32], include_top, weights, input_tensor, + input_shape, pooling, classes) @keras_export('keras.applications.densenet.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return densenet.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input( + x, data_format=data_format, mode='torch') + + +@keras_export('keras.applications.densenet.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) + + +DOC = """ + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` (with `'channels_last'` data format) + or `(3, 224, 224)` (with `'channels_first'` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 32. + E.g. `(200, 200, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. +""" + +setattr(DenseNet121, '__doc__', DenseNet121.__doc__ + DOC) +setattr(DenseNet169, '__doc__', DenseNet169.__doc__ + DOC) +setattr(DenseNet201, '__doc__', DenseNet201.__doc__ + DOC) diff --git a/tensorflow/python/keras/applications/imagenet_utils.py b/tensorflow/python/keras/applications/imagenet_utils.py index 92c9e312cee..206be8406ee 100644 --- a/tensorflow/python/keras/applications/imagenet_utils.py +++ b/tensorflow/python/keras/applications/imagenet_utils.py @@ -1,4 +1,4 @@ -# Copyright 2015 The TensorFlow Authors. All Rights Reserved. +# Copyright 2019 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. @@ -12,25 +12,346 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Utilities for ImageNet data preprocessing & prediction decoding. -""" +"""Utilities for ImageNet data preprocessing & prediction decoding.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import imagenet_utils +import json +import warnings -from tensorflow.python.keras.applications import keras_modules_injection +import numpy as np + +from tensorflow.python.keras import backend +from tensorflow.python.keras.utils import data_utils from tensorflow.python.util.tf_export import keras_export -@keras_export('keras.applications.imagenet_utils.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return imagenet_utils.decode_predictions(*args, **kwargs) +CLASS_INDEX = None +CLASS_INDEX_PATH = ('https://storage.googleapis.com/download.tensorflow.org/' + 'data/imagenet_class_index.json') @keras_export('keras.applications.imagenet_utils.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return imagenet_utils.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None, mode='caffe'): + """Preprocesses a tensor or Numpy array encoding a batch of images. + + Arguments: + x: Input Numpy or symbolic tensor, 3D or 4D. + The preprocessed data is written over the input data + if the data types are compatible. To avoid this + behaviour, `numpy.copy(x)` can be used. + data_format: Data format of the image tensor/array. + mode: One of "caffe", "tf" or "torch". + - caffe: will convert the images from RGB to BGR, + then will zero-center each color channel with + respect to the ImageNet dataset, + without scaling. + - tf: will scale pixels between -1 and 1, + sample-wise. + - torch: will scale pixels between 0 and 1 and then + will normalize each channel with respect to the + ImageNet dataset. + + Returns: + Preprocessed tensor or Numpy array. + + Raises: + ValueError: In case of unknown `data_format` argument. + """ + if data_format is None: + data_format = backend.image_data_format() + if data_format not in {'channels_first', 'channels_last'}: + raise ValueError('Unknown data_format ' + str(data_format)) + + if isinstance(x, np.ndarray): + return _preprocess_numpy_input( + x, data_format=data_format, mode=mode) + else: + return _preprocess_symbolic_input( + x, data_format=data_format, mode=mode) + + +@keras_export('keras.applications.imagenet_utils.decode_predictions') +def decode_predictions(preds, top=5): + """Decodes the prediction of an ImageNet model. + + Arguments: + preds: Numpy tensor encoding a batch of predictions. + top: Integer, how many top-guesses to return. + + Returns: + A list of lists of top class prediction tuples + `(class_name, class_description, score)`. + One list of tuples per sample in batch input. + + Raises: + ValueError: In case of invalid shape of the `pred` array + (must be 2D). + """ + global CLASS_INDEX + + if len(preds.shape) != 2 or preds.shape[1] != 1000: + raise ValueError('`decode_predictions` expects ' + 'a batch of predictions ' + '(i.e. a 2D array of shape (samples, 1000)). ' + 'Found array with shape: ' + str(preds.shape)) + if CLASS_INDEX is None: + fpath = data_utils.get_file( + 'imagenet_class_index.json', + CLASS_INDEX_PATH, + cache_subdir='models', + file_hash='c2c37ea517e94d9795004a39431a14cb') + with open(fpath) as f: + CLASS_INDEX = json.load(f) + results = [] + for pred in preds: + top_indices = pred.argsort()[-top:][::-1] + result = [tuple(CLASS_INDEX[str(i)]) + (pred[i],) for i in top_indices] + result.sort(key=lambda x: x[2], reverse=True) + results.append(result) + return results + + +def _preprocess_numpy_input(x, data_format, mode): + """Preprocesses a Numpy array encoding a batch of images. + + Arguments: + x: Input array, 3D or 4D. + data_format: Data format of the image array. + mode: One of "caffe", "tf" or "torch". + - caffe: will convert the images from RGB to BGR, + then will zero-center each color channel with + respect to the ImageNet dataset, + without scaling. + - tf: will scale pixels between -1 and 1, + sample-wise. + - torch: will scale pixels between 0 and 1 and then + will normalize each channel with respect to the + ImageNet dataset. + + Returns: + Preprocessed Numpy array. + """ + if not issubclass(x.dtype.type, np.floating): + x = x.astype(backend.floatx(), copy=False) + + if mode == 'tf': + x /= 127.5 + x -= 1. + return x + + if mode == 'torch': + x /= 255. + mean = [0.485, 0.456, 0.406] + std = [0.229, 0.224, 0.225] + else: + if data_format == 'channels_first': + # 'RGB'->'BGR' + if x.ndim == 3: + x = x[::-1, ...] + else: + x = x[:, ::-1, ...] + else: + # 'RGB'->'BGR' + x = x[..., ::-1] + mean = [103.939, 116.779, 123.68] + std = None + + # Zero-center by mean pixel + if data_format == 'channels_first': + if x.ndim == 3: + x[0, :, :] -= mean[0] + x[1, :, :] -= mean[1] + x[2, :, :] -= mean[2] + if std is not None: + x[0, :, :] /= std[0] + x[1, :, :] /= std[1] + x[2, :, :] /= std[2] + else: + x[:, 0, :, :] -= mean[0] + x[:, 1, :, :] -= mean[1] + x[:, 2, :, :] -= mean[2] + if std is not None: + x[:, 0, :, :] /= std[0] + x[:, 1, :, :] /= std[1] + x[:, 2, :, :] /= std[2] + else: + x[..., 0] -= mean[0] + x[..., 1] -= mean[1] + x[..., 2] -= mean[2] + if std is not None: + x[..., 0] /= std[0] + x[..., 1] /= std[1] + x[..., 2] /= std[2] + return x + + +def _preprocess_symbolic_input(x, data_format, mode): + """Preprocesses a tensor encoding a batch of images. + + Arguments: + x: Input tensor, 3D or 4D. + data_format: Data format of the image tensor. + mode: One of "caffe", "tf" or "torch". + - caffe: will convert the images from RGB to BGR, + then will zero-center each color channel with + respect to the ImageNet dataset, + without scaling. + - tf: will scale pixels between -1 and 1, + sample-wise. + - torch: will scale pixels between 0 and 1 and then + will normalize each channel with respect to the + ImageNet dataset. + + Returns: + Preprocessed tensor. + """ + if mode == 'tf': + x /= 127.5 + x -= 1. + return x + + if mode == 'torch': + x /= 255. + mean = [0.485, 0.456, 0.406] + std = [0.229, 0.224, 0.225] + else: + if data_format == 'channels_first': + # 'RGB'->'BGR' + if backend.ndim(x) == 3: + x = x[::-1, ...] + else: + x = x[:, ::-1, ...] + else: + # 'RGB'->'BGR' + x = x[..., ::-1] + mean = [103.939, 116.779, 123.68] + std = None + + mean_tensor = backend.constant(-np.array(mean)) + + # Zero-center by mean pixel + if backend.dtype(x) != backend.dtype(mean_tensor): + x = backend.bias_add( + x, backend.cast(mean_tensor, backend.dtype(x)), data_format=data_format) + else: + x = backend.bias_add(x, mean_tensor, data_format) + if std is not None: + x /= std + return x + + +def obtain_input_shape(input_shape, + default_size, + min_size, + data_format, + require_flatten, + weights=None): + """Internal utility to compute/validate a model's input shape. + + Arguments: + input_shape: Either None (will return the default network input shape), + or a user-provided shape to be validated. + default_size: Default input width/height for the model. + min_size: Minimum input width/height accepted by the model. + data_format: Image data format to use. + require_flatten: Whether the model is expected to + be linked to a classifier via a Flatten layer. + weights: One of `None` (random initialization) + or 'imagenet' (pre-training on ImageNet). + If weights='imagenet' input channels must be equal to 3. + + Returns: + An integer shape tuple (may include None entries). + + Raises: + ValueError: In case of invalid argument values. + """ + if weights != 'imagenet' and input_shape and len(input_shape) == 3: + if data_format == 'channels_first': + if input_shape[0] not in {1, 3}: + warnings.warn('This model usually expects 1 or 3 input channels. ' + 'However, it was passed an input_shape with ' + + str(input_shape[0]) + ' input channels.') + default_shape = (input_shape[0], default_size, default_size) + else: + if input_shape[-1] not in {1, 3}: + warnings.warn('This model usually expects 1 or 3 input channels. ' + 'However, it was passed an input_shape with ' + + str(input_shape[-1]) + ' input channels.') + default_shape = (default_size, default_size, input_shape[-1]) + else: + if data_format == 'channels_first': + default_shape = (3, default_size, default_size) + else: + default_shape = (default_size, default_size, 3) + if weights == 'imagenet' and require_flatten: + if input_shape is not None: + if input_shape != default_shape: + raise ValueError('When setting `include_top=True` ' + 'and loading `imagenet` weights, ' + '`input_shape` should be ' + str(default_shape) + '.') + return default_shape + if input_shape: + if data_format == 'channels_first': + if input_shape is not None: + if len(input_shape) != 3: + raise ValueError('`input_shape` must be a tuple of three integers.') + if input_shape[0] != 3 and weights == 'imagenet': + raise ValueError('The input must have 3 channels; got ' + '`input_shape=' + str(input_shape) + '`') + if ((input_shape[1] is not None and input_shape[1] < min_size) or + (input_shape[2] is not None and input_shape[2] < min_size)): + raise ValueError('Input size must be at least ' + str(min_size) + + 'x' + str(min_size) + '; got `input_shape=' + + str(input_shape) + '`') + else: + if input_shape is not None: + if len(input_shape) != 3: + raise ValueError('`input_shape` must be a tuple of three integers.') + if input_shape[-1] != 3 and weights == 'imagenet': + raise ValueError('The input must have 3 channels; got ' + '`input_shape=' + str(input_shape) + '`') + if ((input_shape[0] is not None and input_shape[0] < min_size) or + (input_shape[1] is not None and input_shape[1] < min_size)): + raise ValueError('Input size must be at least ' + str(min_size) + + 'x' + str(min_size) + '; got `input_shape=' + + str(input_shape) + '`') + else: + if require_flatten: + input_shape = default_shape + else: + if data_format == 'channels_first': + input_shape = (3, None, None) + else: + input_shape = (None, None, 3) + if require_flatten: + if None in input_shape: + raise ValueError('If `include_top` is True, ' + 'you should specify a static `input_shape`. ' + 'Got `input_shape=' + str(input_shape) + '`') + return input_shape + + +def correct_pad(inputs, kernel_size): + """Returns a tuple for zero-padding for 2D convolution with downsampling. + + Arguments: + inputs: Input tensor. + kernel_size: An integer or tuple/list of 2 integers. + + Returns: + A tuple. + """ + img_dim = 2 if backend.image_data_format() == 'channels_first' else 1 + input_size = backend.int_shape(inputs)[img_dim:(img_dim + 2)] + if isinstance(kernel_size, int): + kernel_size = (kernel_size, kernel_size) + if input_size[0] is None: + adjust = (1, 1) + else: + adjust = (1 - input_size[0] % 2, 1 - input_size[1] % 2) + correct = (kernel_size[0] // 2, kernel_size[1] // 2) + return ((correct[0] - adjust[0], correct[0]), + (correct[1] - adjust[1], correct[1])) diff --git a/tensorflow/python/keras/applications/imagenet_utils_test.py b/tensorflow/python/keras/applications/imagenet_utils_test.py new file mode 100644 index 00000000000..f37ae8c188c --- /dev/null +++ b/tensorflow/python/keras/applications/imagenet_utils_test.py @@ -0,0 +1,250 @@ +# Copyright 2019 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 imagenet_utils.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl.testing import parameterized +import numpy as np + +from tensorflow.python import keras +from tensorflow.python.keras import keras_parameterized +from tensorflow.python.keras.applications import imagenet_utils as utils +from tensorflow.python.platform import test + + +class TestImageNetUtils(keras_parameterized.TestCase): + + def test_preprocess_input(self): + # Test image batch with float and int image input + x = np.random.uniform(0, 255, (2, 10, 10, 3)) + xint = x.astype('int32') + self.assertEqual(utils.preprocess_input(x).shape, x.shape) + self.assertEqual(utils.preprocess_input(xint).shape, xint.shape) + + out1 = utils.preprocess_input(x, 'channels_last') + out1int = utils.preprocess_input(xint, 'channels_last') + out2 = utils.preprocess_input( + np.transpose(x, (0, 3, 1, 2)), 'channels_first') + out2int = utils.preprocess_input( + np.transpose(xint, (0, 3, 1, 2)), 'channels_first') + self.assertAllClose(out1, out2.transpose(0, 2, 3, 1)) + self.assertAllClose(out1int, out2int.transpose(0, 2, 3, 1)) + + # Test single image + x = np.random.uniform(0, 255, (10, 10, 3)) + xint = x.astype('int32') + self.assertEqual(utils.preprocess_input(x).shape, x.shape) + self.assertEqual(utils.preprocess_input(xint).shape, xint.shape) + + out1 = utils.preprocess_input(x, 'channels_last') + out1int = utils.preprocess_input(xint, 'channels_last') + out2 = utils.preprocess_input(np.transpose(x, (2, 0, 1)), 'channels_first') + out2int = utils.preprocess_input( + np.transpose(xint, (2, 0, 1)), 'channels_first') + self.assertAllClose(out1, out2.transpose(1, 2, 0)) + self.assertAllClose(out1int, out2int.transpose(1, 2, 0)) + + # Test that writing over the input data works predictably + for mode in ['torch', 'tf']: + x = np.random.uniform(0, 255, (2, 10, 10, 3)) + xint = x.astype('int') + x2 = utils.preprocess_input(x, mode=mode) + xint2 = utils.preprocess_input(xint) + self.assertAllClose(x, x2) + self.assertNotEqual(xint.astype('float').max(), xint2.max()) + + # Caffe mode works differently from the others + x = np.random.uniform(0, 255, (2, 10, 10, 3)) + xint = x.astype('int') + x2 = utils.preprocess_input(x, data_format='channels_last', mode='caffe') + xint2 = utils.preprocess_input(xint) + self.assertAllClose(x, x2[..., ::-1]) + self.assertNotEqual(xint.astype('float').max(), xint2.max()) + + def test_preprocess_input_symbolic(self): + # Test image batch + x = np.random.uniform(0, 255, (2, 10, 10, 3)) + inputs = keras.layers.Input(shape=x.shape[1:]) + outputs = keras.layers.Lambda( + utils.preprocess_input, output_shape=x.shape[1:])( + inputs) + model = keras.Model(inputs, outputs) + self.assertEqual(model.predict(x).shape, x.shape) + + outputs1 = keras.layers.Lambda( + lambda x: utils.preprocess_input(x, 'channels_last'), + output_shape=x.shape[1:])( + inputs) + model1 = keras.Model(inputs, outputs1) + out1 = model1.predict(x) + x2 = np.transpose(x, (0, 3, 1, 2)) + inputs2 = keras.layers.Input(shape=x2.shape[1:]) + outputs2 = keras.layers.Lambda( + lambda x: utils.preprocess_input(x, 'channels_first'), + output_shape=x2.shape[1:])( + inputs2) + model2 = keras.Model(inputs2, outputs2) + out2 = model2.predict(x2) + self.assertAllClose(out1, out2.transpose(0, 2, 3, 1)) + + # Test single image + x = np.random.uniform(0, 255, (10, 10, 3)) + inputs = keras.layers.Input(shape=x.shape) + outputs = keras.layers.Lambda( + utils.preprocess_input, output_shape=x.shape)( + inputs) + model = keras.Model(inputs, outputs) + self.assertEqual(model.predict(x[np.newaxis])[0].shape, x.shape) + + outputs1 = keras.layers.Lambda( + lambda x: utils.preprocess_input(x, 'channels_last'), + output_shape=x.shape)( + inputs) + model1 = keras.Model(inputs, outputs1) + out1 = model1.predict(x[np.newaxis])[0] + x2 = np.transpose(x, (2, 0, 1)) + inputs2 = keras.layers.Input(shape=x2.shape) + outputs2 = keras.layers.Lambda( + lambda x: utils.preprocess_input(x, 'channels_first'), + output_shape=x2.shape)( + inputs2) + model2 = keras.Model(inputs2, outputs2) + out2 = model2.predict(x2[np.newaxis])[0] + self.assertAllClose(out1, out2.transpose(1, 2, 0)) + + @parameterized.named_parameters([ + {'testcase_name': 'channels_last_format', + 'data_format': 'channels_last'}, + {'testcase_name': 'channels_first_format', + 'data_format': 'channels_first'}, + ]) + def test_obtain_input_shape(self, data_format): + # input_shape and default_size are not identical. + with self.assertRaises(ValueError): + utils.obtain_input_shape( + input_shape=(224, 224, 3), + default_size=299, + min_size=139, + data_format='channels_last', + require_flatten=True, + weights='imagenet') + + # Test invalid use cases + + shape = (139, 139) + if data_format == 'channels_last': + input_shape = shape + (99,) + else: + input_shape = (99,) + shape + + # input_shape is smaller than min_size. + shape = (100, 100) + if data_format == 'channels_last': + input_shape = shape + (3,) + else: + input_shape = (3,) + shape + with self.assertRaises(ValueError): + utils.obtain_input_shape( + input_shape=input_shape, + default_size=None, + min_size=139, + data_format=data_format, + require_flatten=False) + + # shape is 1D. + shape = (100,) + if data_format == 'channels_last': + input_shape = shape + (3,) + else: + input_shape = (3,) + shape + with self.assertRaises(ValueError): + utils.obtain_input_shape( + input_shape=input_shape, + default_size=None, + min_size=139, + data_format=data_format, + require_flatten=False) + + # the number of channels is 5 not 3. + shape = (100, 100) + if data_format == 'channels_last': + input_shape = shape + (5,) + else: + input_shape = (5,) + shape + with self.assertRaises(ValueError): + utils.obtain_input_shape( + input_shape=input_shape, + default_size=None, + min_size=139, + data_format=data_format, + require_flatten=False) + + # require_flatten=True with dynamic input shape. + with self.assertRaises(ValueError): + utils.obtain_input_shape( + input_shape=None, + default_size=None, + min_size=139, + data_format='channels_first', + require_flatten=True) + + # test include top + self.assertEqual(utils.obtain_input_shape( + input_shape=(3, 200, 200), + default_size=None, + min_size=139, + data_format='channels_first', + require_flatten=True), (3, 200, 200)) + + self.assertEqual(utils.obtain_input_shape( + input_shape=None, + default_size=None, + min_size=139, + data_format='channels_last', + require_flatten=False), (None, None, 3)) + + self.assertEqual(utils.obtain_input_shape( + input_shape=None, + default_size=None, + min_size=139, + data_format='channels_first', + require_flatten=False), (3, None, None)) + + self.assertEqual(utils.obtain_input_shape( + input_shape=None, + default_size=None, + min_size=139, + data_format='channels_last', + require_flatten=False), (None, None, 3)) + + self.assertEqual(utils.obtain_input_shape( + input_shape=(150, 150, 3), + default_size=None, + min_size=139, + data_format='channels_last', + require_flatten=False), (150, 150, 3)) + + self.assertEqual(utils.obtain_input_shape( + input_shape=(3, None, None), + default_size=None, + min_size=139, + data_format='channels_first', + require_flatten=False), (3, None, None)) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/python/keras/applications/inception_resnet_v2.py b/tensorflow/python/keras/applications/inception_resnet_v2.py index 0203cf6ad90..092343144c7 100644 --- a/tensorflow/python/keras/applications/inception_resnet_v2.py +++ b/tensorflow/python/keras/applications/inception_resnet_v2.py @@ -14,31 +14,357 @@ # ============================================================================== # pylint: disable=invalid-name """Inception-ResNet V2 model for Keras. + + +Reference paper: + - [Inception-v4, Inception-ResNet and the Impact of + Residual Connections on Learning](https://arxiv.org/abs/1602.07261) + (AAAI 2017) """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import inception_resnet_v2 +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils from tensorflow.python.util.tf_export import keras_export +BASE_WEIGHT_URL = ('https://storage.googleapis.com/tensorflow/' + 'keras-applications/inception_resnet_v2/') + + @keras_export('keras.applications.inception_resnet_v2.InceptionResNetV2', 'keras.applications.InceptionResNetV2') -@keras_modules_injection -def InceptionResNetV2(*args, **kwargs): - return inception_resnet_v2.InceptionResNetV2(*args, **kwargs) +def InceptionResNetV2(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + **kwargs): + """Instantiates the Inception-ResNet v2 architecture. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is `False` (otherwise the input shape + has to be `(299, 299, 3)` (with `'channels_last'` data format) + or `(3, 299, 299)` (with `'channels_first'` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 75. + E.g. `(150, 150, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the last convolutional block. + - `'avg'` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `'max'` means that global max pooling will be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is `True`, and + if no `weights` argument is specified. + **kwargs: For backwards compatibility only. + + Returns: + A Keras `Model` instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if 'layers' in kwargs: + global layers + layers = kwargs.pop('layers') + if kwargs: + raise ValueError('Unknown argument(s): %s' % (kwargs,)) + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top`' + ' as true, `classes` should be 1000') + + # Determine proper input shape + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=299, + min_size=75, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + # Stem block: 35 x 35 x 192 + x = conv2d_bn(img_input, 32, 3, strides=2, padding='valid') + x = conv2d_bn(x, 32, 3, padding='valid') + x = conv2d_bn(x, 64, 3) + x = layers.MaxPooling2D(3, strides=2)(x) + x = conv2d_bn(x, 80, 1, padding='valid') + x = conv2d_bn(x, 192, 3, padding='valid') + x = layers.MaxPooling2D(3, strides=2)(x) + + # Mixed 5b (Inception-A block): 35 x 35 x 320 + branch_0 = conv2d_bn(x, 96, 1) + branch_1 = conv2d_bn(x, 48, 1) + branch_1 = conv2d_bn(branch_1, 64, 5) + branch_2 = conv2d_bn(x, 64, 1) + branch_2 = conv2d_bn(branch_2, 96, 3) + branch_2 = conv2d_bn(branch_2, 96, 3) + branch_pool = layers.AveragePooling2D(3, strides=1, padding='same')(x) + branch_pool = conv2d_bn(branch_pool, 64, 1) + branches = [branch_0, branch_1, branch_2, branch_pool] + channel_axis = 1 if backend.image_data_format() == 'channels_first' else 3 + x = layers.Concatenate(axis=channel_axis, name='mixed_5b')(branches) + + # 10x block35 (Inception-ResNet-A block): 35 x 35 x 320 + for block_idx in range(1, 11): + x = inception_resnet_block( + x, scale=0.17, block_type='block35', block_idx=block_idx) + + # Mixed 6a (Reduction-A block): 17 x 17 x 1088 + branch_0 = conv2d_bn(x, 384, 3, strides=2, padding='valid') + branch_1 = conv2d_bn(x, 256, 1) + branch_1 = conv2d_bn(branch_1, 256, 3) + branch_1 = conv2d_bn(branch_1, 384, 3, strides=2, padding='valid') + branch_pool = layers.MaxPooling2D(3, strides=2, padding='valid')(x) + branches = [branch_0, branch_1, branch_pool] + x = layers.Concatenate(axis=channel_axis, name='mixed_6a')(branches) + + # 20x block17 (Inception-ResNet-B block): 17 x 17 x 1088 + for block_idx in range(1, 21): + x = inception_resnet_block( + x, scale=0.1, block_type='block17', block_idx=block_idx) + + # Mixed 7a (Reduction-B block): 8 x 8 x 2080 + branch_0 = conv2d_bn(x, 256, 1) + branch_0 = conv2d_bn(branch_0, 384, 3, strides=2, padding='valid') + branch_1 = conv2d_bn(x, 256, 1) + branch_1 = conv2d_bn(branch_1, 288, 3, strides=2, padding='valid') + branch_2 = conv2d_bn(x, 256, 1) + branch_2 = conv2d_bn(branch_2, 288, 3) + branch_2 = conv2d_bn(branch_2, 320, 3, strides=2, padding='valid') + branch_pool = layers.MaxPooling2D(3, strides=2, padding='valid')(x) + branches = [branch_0, branch_1, branch_2, branch_pool] + x = layers.Concatenate(axis=channel_axis, name='mixed_7a')(branches) + + # 10x block8 (Inception-ResNet-C block): 8 x 8 x 2080 + for block_idx in range(1, 10): + x = inception_resnet_block( + x, scale=0.2, block_type='block8', block_idx=block_idx) + x = inception_resnet_block( + x, scale=1., activation=None, block_type='block8', block_idx=10) + + # Final convolution block: 8 x 8 x 1536 + x = conv2d_bn(x, 1536, 1, name='conv_7b') + + if include_top: + # Classification block + x = layers.GlobalAveragePooling2D(name='avg_pool')(x) + x = layers.Dense(classes, activation='softmax', name='predictions')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + + # Create model. + model = training.Model(inputs, x, name='inception_resnet_v2') + + # Load weights. + if weights == 'imagenet': + if include_top: + fname = 'inception_resnet_v2_weights_tf_dim_ordering_tf_kernels.h5' + weights_path = data_utils.get_file( + fname, + BASE_WEIGHT_URL + fname, + cache_subdir='models', + file_hash='e693bd0210a403b3192acc6073ad2e96') + else: + fname = ('inception_resnet_v2_weights_' + 'tf_dim_ordering_tf_kernels_notop.h5') + weights_path = data_utils.get_file( + fname, + BASE_WEIGHT_URL + fname, + cache_subdir='models', + file_hash='d19885ff4a710c122648d3b5c3b684e4') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model -@keras_export('keras.applications.inception_resnet_v2.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return inception_resnet_v2.decode_predictions(*args, **kwargs) +def conv2d_bn(x, + filters, + kernel_size, + strides=1, + padding='same', + activation='relu', + use_bias=False, + name=None): + """Utility function to apply conv + BN. + + Arguments: + x: input tensor. + filters: filters in `Conv2D`. + kernel_size: kernel size as in `Conv2D`. + strides: strides in `Conv2D`. + padding: padding mode in `Conv2D`. + activation: activation in `Conv2D`. + use_bias: whether to use a bias in `Conv2D`. + name: name of the ops; will become `name + '_ac'` for the activation + and `name + '_bn'` for the batch norm layer. + + Returns: + Output tensor after applying `Conv2D` and `BatchNormalization`. + """ + x = layers.Conv2D( + filters, + kernel_size, + strides=strides, + padding=padding, + use_bias=use_bias, + name=name)( + x) + if not use_bias: + bn_axis = 1 if backend.image_data_format() == 'channels_first' else 3 + bn_name = None if name is None else name + '_bn' + x = layers.BatchNormalization(axis=bn_axis, scale=False, name=bn_name)(x) + if activation is not None: + ac_name = None if name is None else name + '_ac' + x = layers.Activation(activation, name=ac_name)(x) + return x + + +def inception_resnet_block(x, scale, block_type, block_idx, activation='relu'): + """Adds a Inception-ResNet block. + + This function builds 3 types of Inception-ResNet blocks mentioned + in the paper, controlled by the `block_type` argument (which is the + block name used in the official TF-slim implementation): + - Inception-ResNet-A: `block_type='block35'` + - Inception-ResNet-B: `block_type='block17'` + - Inception-ResNet-C: `block_type='block8'` + + Arguments: + x: input tensor. + scale: scaling factor to scale the residuals (i.e., the output of + passing `x` through an inception module) before adding them + to the shortcut branch. + Let `r` be the output from the residual branch, + the output of this block will be `x + scale * r`. + block_type: `'block35'`, `'block17'` or `'block8'`, determines + the network structure in the residual branch. + block_idx: an `int` used for generating layer names. + The Inception-ResNet blocks + are repeated many times in this network. + We use `block_idx` to identify + each of the repetitions. For example, + the first Inception-ResNet-A block + will have `block_type='block35', block_idx=0`, + and the layer names will have + a common prefix `'block35_0'`. + activation: activation function to use at the end of the block + (see [activations](../activations.md)). + When `activation=None`, no activation is applied + (i.e., "linear" activation: `a(x) = x`). + + Returns: + Output tensor for the block. + + Raises: + ValueError: if `block_type` is not one of `'block35'`, + `'block17'` or `'block8'`. + """ + if block_type == 'block35': + branch_0 = conv2d_bn(x, 32, 1) + branch_1 = conv2d_bn(x, 32, 1) + branch_1 = conv2d_bn(branch_1, 32, 3) + branch_2 = conv2d_bn(x, 32, 1) + branch_2 = conv2d_bn(branch_2, 48, 3) + branch_2 = conv2d_bn(branch_2, 64, 3) + branches = [branch_0, branch_1, branch_2] + elif block_type == 'block17': + branch_0 = conv2d_bn(x, 192, 1) + branch_1 = conv2d_bn(x, 128, 1) + branch_1 = conv2d_bn(branch_1, 160, [1, 7]) + branch_1 = conv2d_bn(branch_1, 192, [7, 1]) + branches = [branch_0, branch_1] + elif block_type == 'block8': + branch_0 = conv2d_bn(x, 192, 1) + branch_1 = conv2d_bn(x, 192, 1) + branch_1 = conv2d_bn(branch_1, 224, [1, 3]) + branch_1 = conv2d_bn(branch_1, 256, [3, 1]) + branches = [branch_0, branch_1] + else: + raise ValueError('Unknown Inception-ResNet block type. ' + 'Expects "block35", "block17" or "block8", ' + 'but got: ' + str(block_type)) + + block_name = block_type + '_' + str(block_idx) + channel_axis = 1 if backend.image_data_format() == 'channels_first' else 3 + mixed = layers.Concatenate( + axis=channel_axis, name=block_name + '_mixed')( + branches) + up = conv2d_bn( + mixed, + backend.int_shape(x)[channel_axis], + 1, + activation=None, + use_bias=True, + name=block_name + '_conv') + + x = layers.Lambda( + lambda inputs, scale: inputs[0] + inputs[1] * scale, + output_shape=backend.int_shape(x)[1:], + arguments={'scale': scale}, + name=block_name)([x, up]) + if activation is not None: + x = layers.Activation(activation, name=block_name + '_ac')(x) + return x @keras_export('keras.applications.inception_resnet_v2.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return inception_resnet_v2.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input(x, data_format=data_format, mode='tf') + + +@keras_export('keras.applications.inception_resnet_v2.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/applications/inception_v3.py b/tensorflow/python/keras/applications/inception_v3.py index 08bf3f3f2b2..4cad9624081 100644 --- a/tensorflow/python/keras/applications/inception_v3.py +++ b/tensorflow/python/keras/applications/inception_v3.py @@ -14,31 +14,384 @@ # ============================================================================== # pylint: disable=invalid-name """Inception V3 model for Keras. + +Reference paper: + - [Rethinking the Inception Architecture for Computer Vision]( + http://arxiv.org/abs/1512.00567) (CVPR 2016) """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import inception_v3 +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils from tensorflow.python.util.tf_export import keras_export +WEIGHTS_PATH = ( + 'https://storage.googleapis.com/tensorflow/keras-applications/' + 'inception_v3/inception_v3_weights_tf_dim_ordering_tf_kernels.h5') +WEIGHTS_PATH_NO_TOP = ( + 'https://storage.googleapis.com/tensorflow/keras-applications/' + 'inception_v3/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5') + + @keras_export('keras.applications.inception_v3.InceptionV3', 'keras.applications.InceptionV3') -@keras_modules_injection -def InceptionV3(*args, **kwargs): - return inception_v3.InceptionV3(*args, **kwargs) +def InceptionV3(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the Inception v3 architecture. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(299, 299, 3)` (with `channels_last` data format) + or `(3, 299, 299)` (with `channels_first` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 75. + E.g. `(150, 150, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top`' + ' as true, `classes` should be 1000') + + # Determine proper input shape + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=299, + min_size=75, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + if backend.image_data_format() == 'channels_first': + channel_axis = 1 + else: + channel_axis = 3 + + x = conv2d_bn(img_input, 32, 3, 3, strides=(2, 2), padding='valid') + x = conv2d_bn(x, 32, 3, 3, padding='valid') + x = conv2d_bn(x, 64, 3, 3) + x = layers.MaxPooling2D((3, 3), strides=(2, 2))(x) + + x = conv2d_bn(x, 80, 1, 1, padding='valid') + x = conv2d_bn(x, 192, 3, 3, padding='valid') + x = layers.MaxPooling2D((3, 3), strides=(2, 2))(x) + + # mixed 0: 35 x 35 x 256 + branch1x1 = conv2d_bn(x, 64, 1, 1) + + branch5x5 = conv2d_bn(x, 48, 1, 1) + branch5x5 = conv2d_bn(branch5x5, 64, 5, 5) + + branch3x3dbl = conv2d_bn(x, 64, 1, 1) + branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) + branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) + + branch_pool = layers.AveragePooling2D( + (3, 3), strides=(1, 1), padding='same')(x) + branch_pool = conv2d_bn(branch_pool, 32, 1, 1) + x = layers.concatenate([branch1x1, branch5x5, branch3x3dbl, branch_pool], + axis=channel_axis, + name='mixed0') + + # mixed 1: 35 x 35 x 288 + branch1x1 = conv2d_bn(x, 64, 1, 1) + + branch5x5 = conv2d_bn(x, 48, 1, 1) + branch5x5 = conv2d_bn(branch5x5, 64, 5, 5) + + branch3x3dbl = conv2d_bn(x, 64, 1, 1) + branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) + branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) + + branch_pool = layers.AveragePooling2D( + (3, 3), strides=(1, 1), padding='same')(x) + branch_pool = conv2d_bn(branch_pool, 64, 1, 1) + x = layers.concatenate([branch1x1, branch5x5, branch3x3dbl, branch_pool], + axis=channel_axis, + name='mixed1') + + # mixed 2: 35 x 35 x 288 + branch1x1 = conv2d_bn(x, 64, 1, 1) + + branch5x5 = conv2d_bn(x, 48, 1, 1) + branch5x5 = conv2d_bn(branch5x5, 64, 5, 5) + + branch3x3dbl = conv2d_bn(x, 64, 1, 1) + branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) + branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) + + branch_pool = layers.AveragePooling2D( + (3, 3), strides=(1, 1), padding='same')(x) + branch_pool = conv2d_bn(branch_pool, 64, 1, 1) + x = layers.concatenate([branch1x1, branch5x5, branch3x3dbl, branch_pool], + axis=channel_axis, + name='mixed2') + + # mixed 3: 17 x 17 x 768 + branch3x3 = conv2d_bn(x, 384, 3, 3, strides=(2, 2), padding='valid') + + branch3x3dbl = conv2d_bn(x, 64, 1, 1) + branch3x3dbl = conv2d_bn(branch3x3dbl, 96, 3, 3) + branch3x3dbl = conv2d_bn( + branch3x3dbl, 96, 3, 3, strides=(2, 2), padding='valid') + + branch_pool = layers.MaxPooling2D((3, 3), strides=(2, 2))(x) + x = layers.concatenate([branch3x3, branch3x3dbl, branch_pool], + axis=channel_axis, + name='mixed3') + + # mixed 4: 17 x 17 x 768 + branch1x1 = conv2d_bn(x, 192, 1, 1) + + branch7x7 = conv2d_bn(x, 128, 1, 1) + branch7x7 = conv2d_bn(branch7x7, 128, 1, 7) + branch7x7 = conv2d_bn(branch7x7, 192, 7, 1) + + branch7x7dbl = conv2d_bn(x, 128, 1, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 1, 7) + branch7x7dbl = conv2d_bn(branch7x7dbl, 128, 7, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) + + branch_pool = layers.AveragePooling2D( + (3, 3), strides=(1, 1), padding='same')(x) + branch_pool = conv2d_bn(branch_pool, 192, 1, 1) + x = layers.concatenate([branch1x1, branch7x7, branch7x7dbl, branch_pool], + axis=channel_axis, + name='mixed4') + + # mixed 5, 6: 17 x 17 x 768 + for i in range(2): + branch1x1 = conv2d_bn(x, 192, 1, 1) + + branch7x7 = conv2d_bn(x, 160, 1, 1) + branch7x7 = conv2d_bn(branch7x7, 160, 1, 7) + branch7x7 = conv2d_bn(branch7x7, 192, 7, 1) + + branch7x7dbl = conv2d_bn(x, 160, 1, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 1, 7) + branch7x7dbl = conv2d_bn(branch7x7dbl, 160, 7, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) + + branch_pool = layers.AveragePooling2D((3, 3), + strides=(1, 1), + padding='same')( + x) + branch_pool = conv2d_bn(branch_pool, 192, 1, 1) + x = layers.concatenate([branch1x1, branch7x7, branch7x7dbl, branch_pool], + axis=channel_axis, + name='mixed' + str(5 + i)) + + # mixed 7: 17 x 17 x 768 + branch1x1 = conv2d_bn(x, 192, 1, 1) + + branch7x7 = conv2d_bn(x, 192, 1, 1) + branch7x7 = conv2d_bn(branch7x7, 192, 1, 7) + branch7x7 = conv2d_bn(branch7x7, 192, 7, 1) + + branch7x7dbl = conv2d_bn(x, 192, 1, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) + branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 7, 1) + branch7x7dbl = conv2d_bn(branch7x7dbl, 192, 1, 7) + + branch_pool = layers.AveragePooling2D( + (3, 3), strides=(1, 1), padding='same')(x) + branch_pool = conv2d_bn(branch_pool, 192, 1, 1) + x = layers.concatenate([branch1x1, branch7x7, branch7x7dbl, branch_pool], + axis=channel_axis, + name='mixed7') + + # mixed 8: 8 x 8 x 1280 + branch3x3 = conv2d_bn(x, 192, 1, 1) + branch3x3 = conv2d_bn(branch3x3, 320, 3, 3, strides=(2, 2), padding='valid') + + branch7x7x3 = conv2d_bn(x, 192, 1, 1) + branch7x7x3 = conv2d_bn(branch7x7x3, 192, 1, 7) + branch7x7x3 = conv2d_bn(branch7x7x3, 192, 7, 1) + branch7x7x3 = conv2d_bn( + branch7x7x3, 192, 3, 3, strides=(2, 2), padding='valid') + + branch_pool = layers.MaxPooling2D((3, 3), strides=(2, 2))(x) + x = layers.concatenate([branch3x3, branch7x7x3, branch_pool], + axis=channel_axis, + name='mixed8') + + # mixed 9: 8 x 8 x 2048 + for i in range(2): + branch1x1 = conv2d_bn(x, 320, 1, 1) + + branch3x3 = conv2d_bn(x, 384, 1, 1) + branch3x3_1 = conv2d_bn(branch3x3, 384, 1, 3) + branch3x3_2 = conv2d_bn(branch3x3, 384, 3, 1) + branch3x3 = layers.concatenate([branch3x3_1, branch3x3_2], + axis=channel_axis, + name='mixed9_' + str(i)) + + branch3x3dbl = conv2d_bn(x, 448, 1, 1) + branch3x3dbl = conv2d_bn(branch3x3dbl, 384, 3, 3) + branch3x3dbl_1 = conv2d_bn(branch3x3dbl, 384, 1, 3) + branch3x3dbl_2 = conv2d_bn(branch3x3dbl, 384, 3, 1) + branch3x3dbl = layers.concatenate([branch3x3dbl_1, branch3x3dbl_2], + axis=channel_axis) + + branch_pool = layers.AveragePooling2D((3, 3), + strides=(1, 1), + padding='same')( + x) + branch_pool = conv2d_bn(branch_pool, 192, 1, 1) + x = layers.concatenate([branch1x1, branch3x3, branch3x3dbl, branch_pool], + axis=channel_axis, + name='mixed' + str(9 + i)) + if include_top: + # Classification block + x = layers.GlobalAveragePooling2D(name='avg_pool')(x) + x = layers.Dense(classes, activation='softmax', name='predictions')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = training.Model(inputs, x, name='inception_v3') + + # Load weights. + if weights == 'imagenet': + if include_top: + weights_path = data_utils.get_file( + 'inception_v3_weights_tf_dim_ordering_tf_kernels.h5', + WEIGHTS_PATH, + cache_subdir='models', + file_hash='9a0d58056eeedaa3f26cb7ebd46da564') + else: + weights_path = data_utils.get_file( + 'inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5', + WEIGHTS_PATH_NO_TOP, + cache_subdir='models', + file_hash='bcbd6486424b2319ff4ef7d526e38f63') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model -@keras_export('keras.applications.inception_v3.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return inception_v3.decode_predictions(*args, **kwargs) +def conv2d_bn(x, + filters, + num_row, + num_col, + padding='same', + strides=(1, 1), + name=None): + """Utility function to apply conv + BN. + + Arguments: + x: input tensor. + filters: filters in `Conv2D`. + num_row: height of the convolution kernel. + num_col: width of the convolution kernel. + padding: padding mode in `Conv2D`. + strides: strides in `Conv2D`. + name: name of the ops; will become `name + '_conv'` + for the convolution and `name + '_bn'` for the + batch norm layer. + + Returns: + Output tensor after applying `Conv2D` and `BatchNormalization`. + """ + if name is not None: + bn_name = name + '_bn' + conv_name = name + '_conv' + else: + bn_name = None + conv_name = None + if backend.image_data_format() == 'channels_first': + bn_axis = 1 + else: + bn_axis = 3 + x = layers.Conv2D( + filters, (num_row, num_col), + strides=strides, + padding=padding, + use_bias=False, + name=conv_name)( + x) + x = layers.BatchNormalization(axis=bn_axis, scale=False, name=bn_name)(x) + x = layers.Activation('relu', name=name)(x) + return x @keras_export('keras.applications.inception_v3.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return inception_v3.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input(x, data_format=data_format, mode='tf') + + +@keras_export('keras.applications.inception_v3.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/applications/mobilenet.py b/tensorflow/python/keras/applications/mobilenet.py index d40e4a76147..4cde855dd4a 100644 --- a/tensorflow/python/keras/applications/mobilenet.py +++ b/tensorflow/python/keras/applications/mobilenet.py @@ -14,31 +14,410 @@ # ============================================================================== # pylint: disable=invalid-name """MobileNet v1 models for Keras. + +MobileNet is a general architecture and can be used for multiple use cases. +Depending on the use case, it can use different input layer size and +different width factors. This allows different width models to reduce +the number of multiply-adds and thereby +reduce inference cost on mobile devices. + +MobileNets support any input size greater than 32 x 32, with larger image sizes +offering better performance. +The number of parameters and number of multiply-adds +can be modified by using the `alpha` parameter, +which increases/decreases the number of filters in each layer. +By altering the image size and `alpha` parameter, +all 16 models from the paper can be built, with ImageNet weights provided. + +The paper demonstrates the performance of MobileNets using `alpha` values of +1.0 (also called 100 % MobileNet), 0.75, 0.5 and 0.25. +For each of these `alpha` values, weights for 4 different input image sizes +are provided (224, 192, 160, 128). + +The following table describes the size and accuracy of the 100% MobileNet +on size 224 x 224: +---------------------------------------------------------------------------- +Width Multiplier (alpha) | ImageNet Acc | Multiply-Adds (M) | Params (M) +---------------------------------------------------------------------------- +| 1.0 MobileNet-224 | 70.6 % | 529 | 4.2 | +| 0.75 MobileNet-224 | 68.4 % | 325 | 2.6 | +| 0.50 MobileNet-224 | 63.7 % | 149 | 1.3 | +| 0.25 MobileNet-224 | 50.6 % | 41 | 0.5 | +---------------------------------------------------------------------------- + +The following table describes the performance of +the 100 % MobileNet on various input sizes: +------------------------------------------------------------------------ + Resolution | ImageNet Acc | Multiply-Adds (M) | Params (M) +------------------------------------------------------------------------ +| 1.0 MobileNet-224 | 70.6 % | 529 | 4.2 | +| 1.0 MobileNet-192 | 69.1 % | 529 | 4.2 | +| 1.0 MobileNet-160 | 67.2 % | 529 | 4.2 | +| 1.0 MobileNet-128 | 64.4 % | 529 | 4.2 | +------------------------------------------------------------------------ + +Reference paper: + - [MobileNets: Efficient Convolutional Neural Networks for + Mobile Vision Applications](https://arxiv.org/abs/1704.04861) """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import mobilenet +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils +from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util.tf_export import keras_export +BASE_WEIGHT_PATH = ('https://storage.googleapis.com/tensorflow/' + 'keras-applications/mobilenet/') + @keras_export('keras.applications.mobilenet.MobileNet', 'keras.applications.MobileNet') -@keras_modules_injection -def MobileNet(*args, **kwargs): - return mobilenet.MobileNet(*args, **kwargs) +def MobileNet(input_shape=None, + alpha=1.0, + depth_multiplier=1, + dropout=1e-3, + include_top=True, + weights='imagenet', + input_tensor=None, + pooling=None, + classes=1000, + **kwargs): + """Instantiates the MobileNet architecture. + + Arguments: + input_shape: optional shape tuple, only to be specified if `include_top` + is False (otherwise the input shape has to be `(224, 224, 3)` (with + `channels_last` data format) or (3, 224, 224) (with `channels_first` + data format). It should have exactly 3 inputs channels, and width and + height should be no smaller than 32. E.g. `(200, 200, 3)` would be one + valid value. + alpha: controls the width of the network. This is known as the width + multiplier in the MobileNet paper. - If `alpha` < 1.0, proportionally + decreases the number of filters in each layer. - If `alpha` > 1.0, + proportionally increases the number of filters in each layer. - If + `alpha` = 1, default number of filters from the paper are used at each + layer. + depth_multiplier: depth multiplier for depthwise convolution. This is + called the resolution multiplier in the MobileNet paper. + dropout: dropout rate + include_top: whether to include the fully-connected layer at the top of + the network. + weights: one of `None` (random initialization), 'imagenet' (pre-training + on ImageNet), or the path to the weights file to be loaded. + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) to + use as image input for the model. + pooling: Optional pooling mode for feature extraction when `include_top` + is `False`. - `None` means that the output of the model will be the 4D + tensor output of the last convolutional block. - `avg` means that + global average pooling will be applied to the output of the last + convolutional block, and thus the output of the model will be a 2D + tensor. - `max` means that global max pooling will be applied. + classes: optional number of classes to classify images into, only to be + specified if `include_top` is True, and if no `weights` argument is + specified. + **kwargs: For backwards compatibility only. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if 'layers' in kwargs: + global layers + layers = kwargs.pop('layers') + if kwargs: + raise ValueError('Unknown argument(s): %s' % (kwargs,)) + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top` ' + 'as true, `classes` should be 1000') + + # Determine proper input shape and default size. + if input_shape is None: + default_size = 224 + else: + if backend.image_data_format() == 'channels_first': + rows = input_shape[1] + cols = input_shape[2] + else: + rows = input_shape[0] + cols = input_shape[1] + + if rows == cols and rows in [128, 160, 192, 224]: + default_size = rows + else: + default_size = 224 + + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=default_size, + min_size=32, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if backend.image_data_format() == 'channels_last': + row_axis, col_axis = (0, 1) + else: + row_axis, col_axis = (1, 2) + rows = input_shape[row_axis] + cols = input_shape[col_axis] + + if weights == 'imagenet': + if depth_multiplier != 1: + raise ValueError('If imagenet weights are being loaded, ' + 'depth multiplier must be 1') + + if alpha not in [0.25, 0.50, 0.75, 1.0]: + raise ValueError('If imagenet weights are being loaded, ' + 'alpha can be one of' + '`0.25`, `0.50`, `0.75` or `1.0` only.') + + if rows != cols or rows not in [128, 160, 192, 224]: + rows = 224 + logging.warning('`input_shape` is undefined or non-square, ' + 'or `rows` is not in [128, 160, 192, 224]. ' + 'Weights for input shape (224, 224) will be' + ' loaded as the default.') + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + x = _conv_block(img_input, 32, alpha, strides=(2, 2)) + x = _depthwise_conv_block(x, 64, alpha, depth_multiplier, block_id=1) + + x = _depthwise_conv_block( + x, 128, alpha, depth_multiplier, strides=(2, 2), block_id=2) + x = _depthwise_conv_block(x, 128, alpha, depth_multiplier, block_id=3) + + x = _depthwise_conv_block( + x, 256, alpha, depth_multiplier, strides=(2, 2), block_id=4) + x = _depthwise_conv_block(x, 256, alpha, depth_multiplier, block_id=5) + + x = _depthwise_conv_block( + x, 512, alpha, depth_multiplier, strides=(2, 2), block_id=6) + x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=7) + x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=8) + x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=9) + x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=10) + x = _depthwise_conv_block(x, 512, alpha, depth_multiplier, block_id=11) + + x = _depthwise_conv_block( + x, 1024, alpha, depth_multiplier, strides=(2, 2), block_id=12) + x = _depthwise_conv_block(x, 1024, alpha, depth_multiplier, block_id=13) + + if include_top: + if backend.image_data_format() == 'channels_first': + shape = (int(1024 * alpha), 1, 1) + else: + shape = (1, 1, int(1024 * alpha)) + + x = layers.GlobalAveragePooling2D()(x) + x = layers.Reshape(shape, name='reshape_1')(x) + x = layers.Dropout(dropout, name='dropout')(x) + x = layers.Conv2D(classes, (1, 1), padding='same', name='conv_preds')(x) + x = layers.Reshape((classes,), name='reshape_2')(x) + x = layers.Activation('softmax', name='act_softmax')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + + # Create model. + model = training.Model(inputs, x, name='mobilenet_%0.2f_%s' % (alpha, rows)) + + # Load weights. + if weights == 'imagenet': + if alpha == 1.0: + alpha_text = '1_0' + elif alpha == 0.75: + alpha_text = '7_5' + elif alpha == 0.50: + alpha_text = '5_0' + else: + alpha_text = '2_5' + + if include_top: + model_name = 'mobilenet_%s_%d_tf.h5' % (alpha_text, rows) + weight_path = BASE_WEIGHT_PATH + model_name + weights_path = data_utils.get_file( + model_name, weight_path, cache_subdir='models') + else: + model_name = 'mobilenet_%s_%d_tf_no_top.h5' % (alpha_text, rows) + weight_path = BASE_WEIGHT_PATH + model_name + weights_path = data_utils.get_file( + model_name, weight_path, cache_subdir='models') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model -@keras_export('keras.applications.mobilenet.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return mobilenet.decode_predictions(*args, **kwargs) +def _conv_block(inputs, filters, alpha, kernel=(3, 3), strides=(1, 1)): + """Adds an initial convolution layer (with batch normalization and relu6). + + Arguments: + inputs: Input tensor of shape `(rows, cols, 3)` (with `channels_last` + data format) or (3, rows, cols) (with `channels_first` data format). + It should have exactly 3 inputs channels, and width and height should + be no smaller than 32. E.g. `(224, 224, 3)` would be one valid value. + filters: Integer, the dimensionality of the output space (i.e. the + number of output filters in the convolution). + alpha: controls the width of the network. - If `alpha` < 1.0, + proportionally decreases the number of filters in each layer. - If + `alpha` > 1.0, proportionally increases the number of filters in each + layer. - If `alpha` = 1, default number of filters from the paper are + used at each layer. + kernel: An integer or tuple/list of 2 integers, specifying the width and + height of the 2D convolution window. Can be a single integer to + specify the same value for all spatial dimensions. + strides: An integer or tuple/list of 2 integers, specifying the strides + of the convolution along the width and height. Can be a single integer + to specify the same value for all spatial dimensions. Specifying any + stride value != 1 is incompatible with specifying any `dilation_rate` + value != 1. # Input shape + 4D tensor with shape: `(samples, channels, rows, cols)` if + data_format='channels_first' + or 4D tensor with shape: `(samples, rows, cols, channels)` if + data_format='channels_last'. # Output shape + 4D tensor with shape: `(samples, filters, new_rows, new_cols)` if + data_format='channels_first' + or 4D tensor with shape: `(samples, new_rows, new_cols, filters)` if + data_format='channels_last'. `rows` and `cols` values might have + changed due to stride. + + Returns: + Output tensor of block. + """ + channel_axis = 1 if backend.image_data_format() == 'channels_first' else -1 + filters = int(filters * alpha) + x = layers.ZeroPadding2D(padding=((0, 1), (0, 1)), name='conv1_pad')(inputs) + x = layers.Conv2D( + filters, + kernel, + padding='valid', + use_bias=False, + strides=strides, + name='conv1')( + x) + x = layers.BatchNormalization(axis=channel_axis, name='conv1_bn')(x) + return layers.ReLU(6., name='conv1_relu')(x) + + +def _depthwise_conv_block(inputs, + pointwise_conv_filters, + alpha, + depth_multiplier=1, + strides=(1, 1), + block_id=1): + """Adds a depthwise convolution block. + + A depthwise convolution block consists of a depthwise conv, + batch normalization, relu6, pointwise convolution, + batch normalization and relu6 activation. + + Arguments: + inputs: Input tensor of shape `(rows, cols, channels)` (with + `channels_last` data format) or (channels, rows, cols) (with + `channels_first` data format). + pointwise_conv_filters: Integer, the dimensionality of the output space + (i.e. the number of output filters in the pointwise convolution). + alpha: controls the width of the network. - If `alpha` < 1.0, + proportionally decreases the number of filters in each layer. - If + `alpha` > 1.0, proportionally increases the number of filters in each + layer. - If `alpha` = 1, default number of filters from the paper are + used at each layer. + depth_multiplier: The number of depthwise convolution output channels + for each input channel. The total number of depthwise convolution + output channels will be equal to `filters_in * depth_multiplier`. + strides: An integer or tuple/list of 2 integers, specifying the strides + of the convolution along the width and height. Can be a single integer + to specify the same value for all spatial dimensions. Specifying any + stride value != 1 is incompatible with specifying any `dilation_rate` + value != 1. + block_id: Integer, a unique identification designating the block number. + # Input shape + 4D tensor with shape: `(batch, channels, rows, cols)` if + data_format='channels_first' + or 4D tensor with shape: `(batch, rows, cols, channels)` if + data_format='channels_last'. # Output shape + 4D tensor with shape: `(batch, filters, new_rows, new_cols)` if + data_format='channels_first' + or 4D tensor with shape: `(batch, new_rows, new_cols, filters)` if + data_format='channels_last'. `rows` and `cols` values might have + changed due to stride. + + Returns: + Output tensor of block. + """ + channel_axis = 1 if backend.image_data_format() == 'channels_first' else -1 + pointwise_conv_filters = int(pointwise_conv_filters * alpha) + + if strides == (1, 1): + x = inputs + else: + x = layers.ZeroPadding2D(((0, 1), (0, 1)), name='conv_pad_%d' % block_id)( + inputs) + x = layers.DepthwiseConv2D((3, 3), + padding='same' if strides == (1, 1) else 'valid', + depth_multiplier=depth_multiplier, + strides=strides, + use_bias=False, + name='conv_dw_%d' % block_id)( + x) + x = layers.BatchNormalization( + axis=channel_axis, name='conv_dw_%d_bn' % block_id)( + x) + x = layers.ReLU(6., name='conv_dw_%d_relu' % block_id)(x) + + x = layers.Conv2D( + pointwise_conv_filters, (1, 1), + padding='same', + use_bias=False, + strides=(1, 1), + name='conv_pw_%d' % block_id)( + x) + x = layers.BatchNormalization( + axis=channel_axis, name='conv_pw_%d_bn' % block_id)( + x) + return layers.ReLU(6., name='conv_pw_%d_relu' % block_id)(x) @keras_export('keras.applications.mobilenet.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return mobilenet.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input(x, data_format=data_format, mode='tf') + + +@keras_export('keras.applications.mobilenet.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/applications/mobilenet_v2.py b/tensorflow/python/keras/applications/mobilenet_v2.py index 696844067ef..bcb0b2ee360 100644 --- a/tensorflow/python/keras/applications/mobilenet_v2.py +++ b/tensorflow/python/keras/applications/mobilenet_v2.py @@ -14,31 +14,476 @@ # ============================================================================== # pylint: disable=invalid-name """MobileNet v2 models for Keras. + +MobileNetV2 is a general architecture and can be used for multiple use cases. +Depending on the use case, it can use different input layer size and +different width factors. This allows different width models to reduce +the number of multiply-adds and thereby +reduce inference cost on mobile devices. + +MobileNetV2 is very similar to the original MobileNet, +except that it uses inverted residual blocks with +bottlenecking features. It has a drastically lower +parameter count than the original MobileNet. +MobileNets support any input size greater +than 32 x 32, with larger image sizes +offering better performance. + +The number of parameters and number of multiply-adds +can be modified by using the `alpha` parameter, +which increases/decreases the number of filters in each layer. +By altering the image size and `alpha` parameter, +all 22 models from the paper can be built, with ImageNet weights provided. + +The paper demonstrates the performance of MobileNets using `alpha` values of +1.0 (also called 100 % MobileNet), 0.35, 0.5, 0.75, 1.0, 1.3, and 1.4 +For each of these `alpha` values, weights for 5 different input image sizes +are provided (224, 192, 160, 128, and 96). + +The following table describes the performance of +MobileNet on various input sizes: +------------------------------------------------------------------------ +MACs stands for Multiply Adds + Classification Checkpoint|MACs (M)|Parameters (M)|Top 1 Accuracy|Top 5 Accuracy +--------------------------|------------|---------------|---------|----|--------- +| [mobilenet_v2_1.4_224] | 582 | 6.06 | 75.0 | 92.5 | +| [mobilenet_v2_1.3_224] | 509 | 5.34 | 74.4 | 92.1 | +| [mobilenet_v2_1.0_224] | 300 | 3.47 | 71.8 | 91.0 | +| [mobilenet_v2_1.0_192] | 221 | 3.47 | 70.7 | 90.1 | +| [mobilenet_v2_1.0_160] | 154 | 3.47 | 68.8 | 89.0 | +| [mobilenet_v2_1.0_128] | 99 | 3.47 | 65.3 | 86.9 | +| [mobilenet_v2_1.0_96] | 56 | 3.47 | 60.3 | 83.2 | +| [mobilenet_v2_0.75_224] | 209 | 2.61 | 69.8 | 89.6 | +| [mobilenet_v2_0.75_192] | 153 | 2.61 | 68.7 | 88.9 | +| [mobilenet_v2_0.75_160] | 107 | 2.61 | 66.4 | 87.3 | +| [mobilenet_v2_0.75_128] | 69 | 2.61 | 63.2 | 85.3 | +| [mobilenet_v2_0.75_96] | 39 | 2.61 | 58.8 | 81.6 | +| [mobilenet_v2_0.5_224] | 97 | 1.95 | 65.4 | 86.4 | +| [mobilenet_v2_0.5_192] | 71 | 1.95 | 63.9 | 85.4 | +| [mobilenet_v2_0.5_160] | 50 | 1.95 | 61.0 | 83.2 | +| [mobilenet_v2_0.5_128] | 32 | 1.95 | 57.7 | 80.8 | +| [mobilenet_v2_0.5_96] | 18 | 1.95 | 51.2 | 75.8 | +| [mobilenet_v2_0.35_224] | 59 | 1.66 | 60.3 | 82.9 | +| [mobilenet_v2_0.35_192] | 43 | 1.66 | 58.2 | 81.2 | +| [mobilenet_v2_0.35_160] | 30 | 1.66 | 55.7 | 79.1 | +| [mobilenet_v2_0.35_128] | 20 | 1.66 | 50.8 | 75.0 | +| [mobilenet_v2_0.35_96] | 11 | 1.66 | 45.5 | 70.4 | + +Reference paper: + - [MobileNetV2: Inverted Residuals and Linear Bottlenecks] + (https://arxiv.org/abs/1801.04381) (CVPR 2018) """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import mobilenet_v2 +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils +from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util.tf_export import keras_export +BASE_WEIGHT_PATH = ('https://storage.googleapis.com/tensorflow/' + 'keras-applications/mobilenet_v2/') + + @keras_export('keras.applications.mobilenet_v2.MobileNetV2', 'keras.applications.MobileNetV2') -@keras_modules_injection -def MobileNetV2(*args, **kwargs): - return mobilenet_v2.MobileNetV2(*args, **kwargs) +def MobileNetV2(input_shape=None, + alpha=1.0, + include_top=True, + weights='imagenet', + input_tensor=None, + pooling=None, + classes=1000, + **kwargs): + """Instantiates the MobileNetV2 architecture. + + Arguments: + input_shape: optional shape tuple, to be specified if you would + like to use a model with an input img resolution that is not + (224, 224, 3). + It should have exactly 3 inputs channels (224, 224, 3). + You can also omit this option if you would like + to infer input_shape from an input_tensor. + If you choose to include both input_tensor and input_shape then + input_shape will be used if they match, if the shapes + do not match then we will throw an error. + E.g. `(160, 160, 3)` would be one valid value. + alpha: controls the width of the network. This is known as the + width multiplier in the MobileNetV2 paper, but the name is kept for + consistency with MobileNetV1 in Keras. + - If `alpha` < 1.0, proportionally decreases the number + of filters in each layer. + - If `alpha` > 1.0, proportionally increases the number + of filters in each layer. + - If `alpha` = 1, default number of filters from the paper + are used at each layer. + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor (i.e. output of + `layers.Input()`) + to use as image input for the model. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model + will be the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a + 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + **kwargs: For backwards compatibility only. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape or invalid alpha, rows when + weights='imagenet' + """ + if 'layers' in kwargs: + global layers + layers = kwargs.pop('layers') + if kwargs: + raise ValueError('Unknown argument(s): %s' % (kwargs,)) + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top` ' + 'as true, `classes` should be 1000') + + # Determine proper input shape and default size. + # If both input_shape and input_tensor are used, they should match + if input_shape is not None and input_tensor is not None: + try: + is_input_t_tensor = backend.is_keras_tensor(input_tensor) + except ValueError: + try: + is_input_t_tensor = backend.is_keras_tensor( + layer_utils.get_source_inputs(input_tensor)) + except ValueError: + raise ValueError('input_tensor: ', input_tensor, + 'is not type input_tensor') + if is_input_t_tensor: + if backend.image_data_format == 'channels_first': + if backend.int_shape(input_tensor)[1] != input_shape[1]: + raise ValueError('input_shape: ', input_shape, 'and input_tensor: ', + input_tensor, + 'do not meet the same shape requirements') + else: + if backend.int_shape(input_tensor)[2] != input_shape[1]: + raise ValueError('input_shape: ', input_shape, 'and input_tensor: ', + input_tensor, + 'do not meet the same shape requirements') + else: + raise ValueError('input_tensor specified: ', input_tensor, + 'is not a keras tensor') + + # If input_shape is None, infer shape from input_tensor + if input_shape is None and input_tensor is not None: + + try: + backend.is_keras_tensor(input_tensor) + except ValueError: + raise ValueError('input_tensor: ', input_tensor, 'is type: ', + type(input_tensor), 'which is not a valid type') + + if input_shape is None and not backend.is_keras_tensor(input_tensor): + default_size = 224 + elif input_shape is None and backend.is_keras_tensor(input_tensor): + if backend.image_data_format() == 'channels_first': + rows = backend.int_shape(input_tensor)[2] + cols = backend.int_shape(input_tensor)[3] + else: + rows = backend.int_shape(input_tensor)[1] + cols = backend.int_shape(input_tensor)[2] + + if rows == cols and rows in [96, 128, 160, 192, 224]: + default_size = rows + else: + default_size = 224 + + # If input_shape is None and no input_tensor + elif input_shape is None: + default_size = 224 + + # If input_shape is not None, assume default size + else: + if backend.image_data_format() == 'channels_first': + rows = input_shape[1] + cols = input_shape[2] + else: + rows = input_shape[0] + cols = input_shape[1] + + if rows == cols and rows in [96, 128, 160, 192, 224]: + default_size = rows + else: + default_size = 224 + + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=default_size, + min_size=32, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if backend.image_data_format() == 'channels_last': + row_axis, col_axis = (0, 1) + else: + row_axis, col_axis = (1, 2) + rows = input_shape[row_axis] + cols = input_shape[col_axis] + + if weights == 'imagenet': + if alpha not in [0.35, 0.50, 0.75, 1.0, 1.3, 1.4]: + raise ValueError('If imagenet weights are being loaded, ' + 'alpha can be one of `0.35`, `0.50`, `0.75`, ' + '`1.0`, `1.3` or `1.4` only.') + + if rows != cols or rows not in [96, 128, 160, 192, 224]: + rows = 224 + logging.warning('`input_shape` is undefined or non-square, ' + 'or `rows` is not in [96, 128, 160, 192, 224].' + ' Weights for input shape (224, 224) will be' + ' loaded as the default.') + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + channel_axis = 1 if backend.image_data_format() == 'channels_first' else -1 + + first_block_filters = _make_divisible(32 * alpha, 8) + x = layers.ZeroPadding2D( + padding=imagenet_utils.correct_pad(img_input, 3), + name='Conv1_pad')(img_input) + x = layers.Conv2D( + first_block_filters, + kernel_size=3, + strides=(2, 2), + padding='valid', + use_bias=False, + name='Conv1')( + x) + x = layers.BatchNormalization( + axis=channel_axis, epsilon=1e-3, momentum=0.999, name='bn_Conv1')( + x) + x = layers.ReLU(6., name='Conv1_relu')(x) + + x = _inverted_res_block( + x, filters=16, alpha=alpha, stride=1, expansion=1, block_id=0) + + x = _inverted_res_block( + x, filters=24, alpha=alpha, stride=2, expansion=6, block_id=1) + x = _inverted_res_block( + x, filters=24, alpha=alpha, stride=1, expansion=6, block_id=2) + + x = _inverted_res_block( + x, filters=32, alpha=alpha, stride=2, expansion=6, block_id=3) + x = _inverted_res_block( + x, filters=32, alpha=alpha, stride=1, expansion=6, block_id=4) + x = _inverted_res_block( + x, filters=32, alpha=alpha, stride=1, expansion=6, block_id=5) + + x = _inverted_res_block( + x, filters=64, alpha=alpha, stride=2, expansion=6, block_id=6) + x = _inverted_res_block( + x, filters=64, alpha=alpha, stride=1, expansion=6, block_id=7) + x = _inverted_res_block( + x, filters=64, alpha=alpha, stride=1, expansion=6, block_id=8) + x = _inverted_res_block( + x, filters=64, alpha=alpha, stride=1, expansion=6, block_id=9) + + x = _inverted_res_block( + x, filters=96, alpha=alpha, stride=1, expansion=6, block_id=10) + x = _inverted_res_block( + x, filters=96, alpha=alpha, stride=1, expansion=6, block_id=11) + x = _inverted_res_block( + x, filters=96, alpha=alpha, stride=1, expansion=6, block_id=12) + + x = _inverted_res_block( + x, filters=160, alpha=alpha, stride=2, expansion=6, block_id=13) + x = _inverted_res_block( + x, filters=160, alpha=alpha, stride=1, expansion=6, block_id=14) + x = _inverted_res_block( + x, filters=160, alpha=alpha, stride=1, expansion=6, block_id=15) + + x = _inverted_res_block( + x, filters=320, alpha=alpha, stride=1, expansion=6, block_id=16) + + # no alpha applied to last conv as stated in the paper: + # if the width multiplier is greater than 1 we + # increase the number of output channels + if alpha > 1.0: + last_block_filters = _make_divisible(1280 * alpha, 8) + else: + last_block_filters = 1280 + + x = layers.Conv2D( + last_block_filters, kernel_size=1, use_bias=False, name='Conv_1')( + x) + x = layers.BatchNormalization( + axis=channel_axis, epsilon=1e-3, momentum=0.999, name='Conv_1_bn')( + x) + x = layers.ReLU(6., name='out_relu')(x) + + if include_top: + x = layers.GlobalAveragePooling2D()(x) + x = layers.Dense( + classes, activation='softmax', use_bias=True, name='Logits')( + x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + + # Create model. + model = training.Model(inputs, x, name='mobilenetv2_%0.2f_%s' % (alpha, rows)) + + # Load weights. + if weights == 'imagenet': + if include_top: + model_name = ('mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + + str(alpha) + '_' + str(rows) + '.h5') + weight_path = BASE_WEIGHT_PATH + model_name + weights_path = data_utils.get_file( + model_name, weight_path, cache_subdir='models') + else: + model_name = ('mobilenet_v2_weights_tf_dim_ordering_tf_kernels_' + + str(alpha) + '_' + str(rows) + '_no_top' + '.h5') + weight_path = BASE_WEIGHT_PATH + model_name + weights_path = data_utils.get_file( + model_name, weight_path, cache_subdir='models') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model -@keras_export('keras.applications.mobilenet_v2.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return mobilenet_v2.decode_predictions(*args, **kwargs) +def _inverted_res_block(inputs, expansion, stride, alpha, filters, block_id): + """Inverted ResNet block.""" + channel_axis = 1 if backend.image_data_format() == 'channels_first' else -1 + + in_channels = backend.int_shape(inputs)[channel_axis] + pointwise_conv_filters = int(filters * alpha) + pointwise_filters = _make_divisible(pointwise_conv_filters, 8) + x = inputs + prefix = 'block_{}_'.format(block_id) + + if block_id: + # Expand + x = layers.Conv2D( + expansion * in_channels, + kernel_size=1, + padding='same', + use_bias=False, + activation=None, + name=prefix + 'expand')( + x) + x = layers.BatchNormalization( + axis=channel_axis, + epsilon=1e-3, + momentum=0.999, + name=prefix + 'expand_BN')( + x) + x = layers.ReLU(6., name=prefix + 'expand_relu')(x) + else: + prefix = 'expanded_conv_' + + # Depthwise + if stride == 2: + x = layers.ZeroPadding2D( + padding=imagenet_utils.correct_pad(x, 3), + name=prefix + 'pad')(x) + x = layers.DepthwiseConv2D( + kernel_size=3, + strides=stride, + activation=None, + use_bias=False, + padding='same' if stride == 1 else 'valid', + name=prefix + 'depthwise')( + x) + x = layers.BatchNormalization( + axis=channel_axis, + epsilon=1e-3, + momentum=0.999, + name=prefix + 'depthwise_BN')( + x) + + x = layers.ReLU(6., name=prefix + 'depthwise_relu')(x) + + # Project + x = layers.Conv2D( + pointwise_filters, + kernel_size=1, + padding='same', + use_bias=False, + activation=None, + name=prefix + 'project')( + x) + x = layers.BatchNormalization( + axis=channel_axis, + epsilon=1e-3, + momentum=0.999, + name=prefix + 'project_BN')( + x) + + if in_channels == pointwise_filters and stride == 1: + return layers.Add(name=prefix + 'add')([inputs, x]) + return x + + +def _make_divisible(v, divisor, min_value=None): + if min_value is None: + min_value = divisor + new_v = max(min_value, int(v + divisor / 2) // divisor * divisor) + # Make sure that round down does not go down by more than 10%. + if new_v < 0.9 * v: + new_v += divisor + return new_v @keras_export('keras.applications.mobilenet_v2.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return mobilenet_v2.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input(x, data_format=data_format, mode='tf') + + +@keras_export('keras.applications.mobilenet_v2.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/applications/nasnet.py b/tensorflow/python/keras/applications/nasnet.py index 90c4fb23bb8..0a693b83652 100644 --- a/tensorflow/python/keras/applications/nasnet.py +++ b/tensorflow/python/keras/applications/nasnet.py @@ -14,38 +14,763 @@ # ============================================================================== # pylint: disable=invalid-name """NASNet-A models for Keras. + +NASNet refers to Neural Architecture Search Network, a family of models +that were designed automatically by learning the model architectures +directly on the dataset of interest. + +Here we consider NASNet-A, the highest performance model that was found +for the CIFAR-10 dataset, and then extended to ImageNet 2012 dataset, +obtaining state of the art performance on CIFAR-10 and ImageNet 2012. +Only the NASNet-A models, and their respective weights, which are suited +for ImageNet 2012 are provided. + +The below table describes the performance on ImageNet 2012: +-------------------------------------------------------------------------------- + Architecture | Top-1 Acc | Top-5 Acc | Multiply-Adds | Params (M) +-------------------------------------------------------------------------------- +| NASNet-A (4 @ 1056) | 74.0 % | 91.6 % | 564 M | 5.3 | +| NASNet-A (6 @ 4032) | 82.7 % | 96.2 % | 23.8 B | 88.9 | +-------------------------------------------------------------------------------- + +References: + - [Learning Transferable Architectures for Scalable Image Recognition] + (https://arxiv.org/abs/1707.07012) (CVPR 2018) """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import nasnet +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils +from tensorflow.python.platform import tf_logging as logging from tensorflow.python.util.tf_export import keras_export +BASE_WEIGHTS_PATH = ('https://storage.googleapis.com/tensorflow/' + 'keras-applications/nasnet/') +NASNET_MOBILE_WEIGHT_PATH = BASE_WEIGHTS_PATH + 'NASNet-mobile.h5' +NASNET_MOBILE_WEIGHT_PATH_NO_TOP = BASE_WEIGHTS_PATH + 'NASNet-mobile-no-top.h5' +NASNET_LARGE_WEIGHT_PATH = BASE_WEIGHTS_PATH + 'NASNet-large.h5' +NASNET_LARGE_WEIGHT_PATH_NO_TOP = BASE_WEIGHTS_PATH + 'NASNet-large-no-top.h5' + + +def NASNet(input_shape=None, + penultimate_filters=4032, + num_blocks=6, + stem_block_filters=96, + skip_reduction=True, + filter_multiplier=2, + include_top=True, + weights=None, + input_tensor=None, + pooling=None, + classes=1000, + default_size=None): + """Instantiates a NASNet model. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + input_shape: Optional shape tuple, the input shape + is by default `(331, 331, 3)` for NASNetLarge and + `(224, 224, 3)` for NASNetMobile. + It should have exactly 3 input channels, + and width and height should be no smaller than 32. + E.g. `(224, 224, 3)` would be one valid value. + penultimate_filters: Number of filters in the penultimate layer. + NASNet models use the notation `NASNet (N @ P)`, where: + - N is the number of blocks + - P is the number of penultimate filters + num_blocks: Number of repeated blocks of the NASNet model. + NASNet models use the notation `NASNet (N @ P)`, where: + - N is the number of blocks + - P is the number of penultimate filters + stem_block_filters: Number of filters in the initial stem block + skip_reduction: Whether to skip the reduction step at the tail + end of the network. + filter_multiplier: Controls the width of the network. + - If `filter_multiplier` < 1.0, proportionally decreases the number + of filters in each layer. + - If `filter_multiplier` > 1.0, proportionally increases the number + of filters in each layer. + - If `filter_multiplier` = 1, default number of filters from the + paper are used at each layer. + include_top: Whether to include the fully-connected + layer at the top of the network. + weights: `None` (random initialization) or + `imagenet` (ImageNet weights) + input_tensor: Optional Keras tensor (i.e. output of + `layers.Input()`) + to use as image input for the model. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model + will be the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a + 2D tensor. + - `max` means that global max pooling will + be applied. + classes: Optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + default_size: Specifies the default image size of the model + + Returns: + A Keras model instance. + + Raises: + ValueError: In case of invalid argument for `weights`, + invalid input shape or invalid `penultimate_filters` value. + """ + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top` ' + 'as true, `classes` should be 1000') + + if (isinstance(input_shape, tuple) and None in input_shape and + weights == 'imagenet'): + raise ValueError('When specifying the input shape of a NASNet' + ' and loading `ImageNet` weights, ' + 'the input_shape argument must be static ' + '(no None entries). Got: `input_shape=' + + str(input_shape) + '`.') + + if default_size is None: + default_size = 331 + + # Determine proper input shape and default size. + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=default_size, + min_size=32, + data_format=backend.image_data_format(), + require_flatten=True, + weights=weights) + + if backend.image_data_format() != 'channels_last': + logging.warning('The NASNet family of models is only available ' + 'for the input data format "channels_last" ' + '(width, height, channels). ' + 'However your settings specify the default ' + 'data format "channels_first" (channels, width, height).' + ' You should set `image_data_format="channels_last"` ' + 'in your Keras config located at ~/.keras/keras.json. ' + 'The model being returned right now will expect inputs ' + 'to follow the "channels_last" data format.') + backend.set_image_data_format('channels_last') + old_data_format = 'channels_first' + else: + old_data_format = None + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + if penultimate_filters % (24 * (filter_multiplier**2)) != 0: + raise ValueError( + 'For NASNet-A models, the `penultimate_filters` must be a multiple ' + 'of 24 * (`filter_multiplier` ** 2). Current value: %d' % + penultimate_filters) + + channel_dim = 1 if backend.image_data_format() == 'channels_first' else -1 + filters = penultimate_filters // 24 + + x = layers.Conv2D( + stem_block_filters, (3, 3), + strides=(2, 2), + padding='valid', + use_bias=False, + name='stem_conv1', + kernel_initializer='he_normal')( + img_input) + + x = layers.BatchNormalization( + axis=channel_dim, momentum=0.9997, epsilon=1e-3, name='stem_bn1')( + x) + + p = None + x, p = _reduction_a_cell( + x, p, filters // (filter_multiplier**2), block_id='stem_1') + x, p = _reduction_a_cell( + x, p, filters // filter_multiplier, block_id='stem_2') + + for i in range(num_blocks): + x, p = _normal_a_cell(x, p, filters, block_id='%d' % (i)) + + x, p0 = _reduction_a_cell( + x, p, filters * filter_multiplier, block_id='reduce_%d' % (num_blocks)) + + p = p0 if not skip_reduction else p + + for i in range(num_blocks): + x, p = _normal_a_cell( + x, p, filters * filter_multiplier, block_id='%d' % (num_blocks + i + 1)) + + x, p0 = _reduction_a_cell( + x, + p, + filters * filter_multiplier**2, + block_id='reduce_%d' % (2 * num_blocks)) + + p = p0 if not skip_reduction else p + + for i in range(num_blocks): + x, p = _normal_a_cell( + x, + p, + filters * filter_multiplier**2, + block_id='%d' % (2 * num_blocks + i + 1)) + + x = layers.Activation('relu')(x) + + if include_top: + x = layers.GlobalAveragePooling2D()(x) + x = layers.Dense(classes, activation='softmax', name='predictions')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + + model = training.Model(inputs, x, name='NASNet') + + # Load weights. + if weights == 'imagenet': + if default_size == 224: # mobile version + if include_top: + weights_path = data_utils.get_file( + 'nasnet_mobile.h5', + NASNET_MOBILE_WEIGHT_PATH, + cache_subdir='models', + file_hash='020fb642bf7360b370c678b08e0adf61') + else: + weights_path = data_utils.get_file( + 'nasnet_mobile_no_top.h5', + NASNET_MOBILE_WEIGHT_PATH_NO_TOP, + cache_subdir='models', + file_hash='1ed92395b5b598bdda52abe5c0dbfd63') + model.load_weights(weights_path) + elif default_size == 331: # large version + if include_top: + weights_path = data_utils.get_file( + 'nasnet_large.h5', + NASNET_LARGE_WEIGHT_PATH, + cache_subdir='models', + file_hash='11577c9a518f0070763c2b964a382f17') + else: + weights_path = data_utils.get_file( + 'nasnet_large_no_top.h5', + NASNET_LARGE_WEIGHT_PATH_NO_TOP, + cache_subdir='models', + file_hash='d81d89dc07e6e56530c4e77faddd61b5') + model.load_weights(weights_path) + else: + raise ValueError('ImageNet weights can only be loaded with NASNetLarge' + ' or NASNetMobile') + elif weights is not None: + model.load_weights(weights) + + if old_data_format: + backend.set_image_data_format(old_data_format) + + return model + + @keras_export('keras.applications.nasnet.NASNetMobile', 'keras.applications.NASNetMobile') -@keras_modules_injection -def NASNetMobile(*args, **kwargs): - return nasnet.NASNetMobile(*args, **kwargs) +def NASNetMobile(input_shape=None, + include_top=True, + weights='imagenet', + input_tensor=None, + pooling=None, + classes=1000): + """Instantiates a Mobile NASNet model in ImageNet mode. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + input_shape: Optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` for NASNetMobile + It should have exactly 3 inputs channels, + and width and height should be no smaller than 32. + E.g. `(224, 224, 3)` would be one valid value. + include_top: Whether to include the fully-connected + layer at the top of the network. + weights: `None` (random initialization) or + `imagenet` (ImageNet weights) + input_tensor: Optional Keras tensor (i.e. output of + `layers.Input()`) + to use as image input for the model. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model + will be the 4D tensor output of the + last convolutional layer. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional layer, and thus + the output of the model will be a + 2D tensor. + - `max` means that global max pooling will + be applied. + classes: Optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. + + Raises: + ValueError: In case of invalid argument for `weights`, + or invalid input shape. + RuntimeError: If attempting to run this model with a + backend that does not support separable convolutions. + """ + return NASNet( + input_shape, + penultimate_filters=1056, + num_blocks=4, + stem_block_filters=32, + skip_reduction=False, + filter_multiplier=2, + include_top=include_top, + weights=weights, + input_tensor=input_tensor, + pooling=pooling, + classes=classes, + default_size=224) @keras_export('keras.applications.nasnet.NASNetLarge', 'keras.applications.NASNetLarge') -@keras_modules_injection -def NASNetLarge(*args, **kwargs): - return nasnet.NASNetLarge(*args, **kwargs) +def NASNetLarge(input_shape=None, + include_top=True, + weights='imagenet', + input_tensor=None, + pooling=None, + classes=1000): + """Instantiates a NASNet model in ImageNet mode. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + input_shape: Optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(331, 331, 3)` for NASNetLarge. + It should have exactly 3 inputs channels, + and width and height should be no smaller than 32. + E.g. `(224, 224, 3)` would be one valid value. + include_top: Whether to include the fully-connected + layer at the top of the network. + weights: `None` (random initialization) or + `imagenet` (ImageNet weights) + input_tensor: Optional Keras tensor (i.e. output of + `layers.Input()`) + to use as image input for the model. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model + will be the 4D tensor output of the + last convolutional layer. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional layer, and thus + the output of the model will be a + 2D tensor. + - `max` means that global max pooling will + be applied. + classes: Optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + RuntimeError: If attempting to run this model with a + backend that does not support separable convolutions. + """ + return NASNet( + input_shape, + penultimate_filters=4032, + num_blocks=6, + stem_block_filters=96, + skip_reduction=True, + filter_multiplier=2, + include_top=include_top, + weights=weights, + input_tensor=input_tensor, + pooling=pooling, + classes=classes, + default_size=331) -@keras_export('keras.applications.nasnet.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return nasnet.decode_predictions(*args, **kwargs) +def _separable_conv_block(ip, + filters, + kernel_size=(3, 3), + strides=(1, 1), + block_id=None): + """Adds 2 blocks of [relu-separable conv-batchnorm]. + + Arguments: + ip: Input tensor + filters: Number of output filters per layer + kernel_size: Kernel size of separable convolutions + strides: Strided convolution for downsampling + block_id: String block_id + + Returns: + A Keras tensor + """ + channel_dim = 1 if backend.image_data_format() == 'channels_first' else -1 + + with backend.name_scope('separable_conv_block_%s' % block_id): + x = layers.Activation('relu')(ip) + if strides == (2, 2): + x = layers.ZeroPadding2D( + padding=imagenet_utils.correct_pad(x, kernel_size), + name='separable_conv_1_pad_%s' % block_id)(x) + conv_pad = 'valid' + else: + conv_pad = 'same' + x = layers.SeparableConv2D( + filters, + kernel_size, + strides=strides, + name='separable_conv_1_%s' % block_id, + padding=conv_pad, + use_bias=False, + kernel_initializer='he_normal')( + x) + x = layers.BatchNormalization( + axis=channel_dim, + momentum=0.9997, + epsilon=1e-3, + name='separable_conv_1_bn_%s' % (block_id))( + x) + x = layers.Activation('relu')(x) + x = layers.SeparableConv2D( + filters, + kernel_size, + name='separable_conv_2_%s' % block_id, + padding='same', + use_bias=False, + kernel_initializer='he_normal')( + x) + x = layers.BatchNormalization( + axis=channel_dim, + momentum=0.9997, + epsilon=1e-3, + name='separable_conv_2_bn_%s' % (block_id))( + x) + return x + + +def _adjust_block(p, ip, filters, block_id=None): + """Adjusts the input `previous path` to match the shape of the `input`. + + Used in situations where the output number of filters needs to be changed. + + Arguments: + p: Input tensor which needs to be modified + ip: Input tensor whose shape needs to be matched + filters: Number of output filters to be matched + block_id: String block_id + + Returns: + Adjusted Keras tensor + """ + channel_dim = 1 if backend.image_data_format() == 'channels_first' else -1 + img_dim = 2 if backend.image_data_format() == 'channels_first' else -2 + + ip_shape = backend.int_shape(ip) + + if p is not None: + p_shape = backend.int_shape(p) + + with backend.name_scope('adjust_block'): + if p is None: + p = ip + + elif p_shape[img_dim] != ip_shape[img_dim]: + with backend.name_scope('adjust_reduction_block_%s' % block_id): + p = layers.Activation('relu', name='adjust_relu_1_%s' % block_id)(p) + p1 = layers.AveragePooling2D((1, 1), + strides=(2, 2), + padding='valid', + name='adjust_avg_pool_1_%s' % block_id)( + p) + p1 = layers.Conv2D( + filters // 2, (1, 1), + padding='same', + use_bias=False, + name='adjust_conv_1_%s' % block_id, + kernel_initializer='he_normal')( + p1) + + p2 = layers.ZeroPadding2D(padding=((0, 1), (0, 1)))(p) + p2 = layers.Cropping2D(cropping=((1, 0), (1, 0)))(p2) + p2 = layers.AveragePooling2D((1, 1), + strides=(2, 2), + padding='valid', + name='adjust_avg_pool_2_%s' % block_id)( + p2) + p2 = layers.Conv2D( + filters // 2, (1, 1), + padding='same', + use_bias=False, + name='adjust_conv_2_%s' % block_id, + kernel_initializer='he_normal')( + p2) + + p = layers.concatenate([p1, p2], axis=channel_dim) + p = layers.BatchNormalization( + axis=channel_dim, + momentum=0.9997, + epsilon=1e-3, + name='adjust_bn_%s' % block_id)( + p) + + elif p_shape[channel_dim] != filters: + with backend.name_scope('adjust_projection_block_%s' % block_id): + p = layers.Activation('relu')(p) + p = layers.Conv2D( + filters, (1, 1), + strides=(1, 1), + padding='same', + name='adjust_conv_projection_%s' % block_id, + use_bias=False, + kernel_initializer='he_normal')( + p) + p = layers.BatchNormalization( + axis=channel_dim, + momentum=0.9997, + epsilon=1e-3, + name='adjust_bn_%s' % block_id)( + p) + return p + + +def _normal_a_cell(ip, p, filters, block_id=None): + """Adds a Normal cell for NASNet-A (Fig. 4 in the paper). + + Arguments: + ip: Input tensor `x` + p: Input tensor `p` + filters: Number of output filters + block_id: String block_id + + Returns: + A Keras tensor + """ + channel_dim = 1 if backend.image_data_format() == 'channels_first' else -1 + + with backend.name_scope('normal_A_block_%s' % block_id): + p = _adjust_block(p, ip, filters, block_id) + + h = layers.Activation('relu')(ip) + h = layers.Conv2D( + filters, (1, 1), + strides=(1, 1), + padding='same', + name='normal_conv_1_%s' % block_id, + use_bias=False, + kernel_initializer='he_normal')( + h) + h = layers.BatchNormalization( + axis=channel_dim, + momentum=0.9997, + epsilon=1e-3, + name='normal_bn_1_%s' % block_id)( + h) + + with backend.name_scope('block_1'): + x1_1 = _separable_conv_block( + h, filters, kernel_size=(5, 5), block_id='normal_left1_%s' % block_id) + x1_2 = _separable_conv_block( + p, filters, block_id='normal_right1_%s' % block_id) + x1 = layers.add([x1_1, x1_2], name='normal_add_1_%s' % block_id) + + with backend.name_scope('block_2'): + x2_1 = _separable_conv_block( + p, filters, (5, 5), block_id='normal_left2_%s' % block_id) + x2_2 = _separable_conv_block( + p, filters, (3, 3), block_id='normal_right2_%s' % block_id) + x2 = layers.add([x2_1, x2_2], name='normal_add_2_%s' % block_id) + + with backend.name_scope('block_3'): + x3 = layers.AveragePooling2D((3, 3), + strides=(1, 1), + padding='same', + name='normal_left3_%s' % (block_id))( + h) + x3 = layers.add([x3, p], name='normal_add_3_%s' % block_id) + + with backend.name_scope('block_4'): + x4_1 = layers.AveragePooling2D((3, 3), + strides=(1, 1), + padding='same', + name='normal_left4_%s' % (block_id))( + p) + x4_2 = layers.AveragePooling2D((3, 3), + strides=(1, 1), + padding='same', + name='normal_right4_%s' % (block_id))( + p) + x4 = layers.add([x4_1, x4_2], name='normal_add_4_%s' % block_id) + + with backend.name_scope('block_5'): + x5 = _separable_conv_block( + h, filters, block_id='normal_left5_%s' % block_id) + x5 = layers.add([x5, h], name='normal_add_5_%s' % block_id) + + x = layers.concatenate([p, x1, x2, x3, x4, x5], + axis=channel_dim, + name='normal_concat_%s' % block_id) + return x, ip + + +def _reduction_a_cell(ip, p, filters, block_id=None): + """Adds a Reduction cell for NASNet-A (Fig. 4 in the paper). + + Arguments: + ip: Input tensor `x` + p: Input tensor `p` + filters: Number of output filters + block_id: String block_id + + Returns: + A Keras tensor + """ + channel_dim = 1 if backend.image_data_format() == 'channels_first' else -1 + + with backend.name_scope('reduction_A_block_%s' % block_id): + p = _adjust_block(p, ip, filters, block_id) + + h = layers.Activation('relu')(ip) + h = layers.Conv2D( + filters, (1, 1), + strides=(1, 1), + padding='same', + name='reduction_conv_1_%s' % block_id, + use_bias=False, + kernel_initializer='he_normal')( + h) + h = layers.BatchNormalization( + axis=channel_dim, + momentum=0.9997, + epsilon=1e-3, + name='reduction_bn_1_%s' % block_id)( + h) + h3 = layers.ZeroPadding2D( + padding=imagenet_utils.correct_pad(h, 3), + name='reduction_pad_1_%s' % block_id)( + h) + + with backend.name_scope('block_1'): + x1_1 = _separable_conv_block( + h, + filters, (5, 5), + strides=(2, 2), + block_id='reduction_left1_%s' % block_id) + x1_2 = _separable_conv_block( + p, + filters, (7, 7), + strides=(2, 2), + block_id='reduction_right1_%s' % block_id) + x1 = layers.add([x1_1, x1_2], name='reduction_add_1_%s' % block_id) + + with backend.name_scope('block_2'): + x2_1 = layers.MaxPooling2D((3, 3), + strides=(2, 2), + padding='valid', + name='reduction_left2_%s' % block_id)( + h3) + x2_2 = _separable_conv_block( + p, + filters, (7, 7), + strides=(2, 2), + block_id='reduction_right2_%s' % block_id) + x2 = layers.add([x2_1, x2_2], name='reduction_add_2_%s' % block_id) + + with backend.name_scope('block_3'): + x3_1 = layers.AveragePooling2D((3, 3), + strides=(2, 2), + padding='valid', + name='reduction_left3_%s' % block_id)( + h3) + x3_2 = _separable_conv_block( + p, + filters, (5, 5), + strides=(2, 2), + block_id='reduction_right3_%s' % block_id) + x3 = layers.add([x3_1, x3_2], name='reduction_add3_%s' % block_id) + + with backend.name_scope('block_4'): + x4 = layers.AveragePooling2D((3, 3), + strides=(1, 1), + padding='same', + name='reduction_left4_%s' % block_id)( + x1) + x4 = layers.add([x2, x4]) + + with backend.name_scope('block_5'): + x5_1 = _separable_conv_block( + x1, filters, (3, 3), block_id='reduction_left4_%s' % block_id) + x5_2 = layers.MaxPooling2D((3, 3), + strides=(2, 2), + padding='valid', + name='reduction_right5_%s' % block_id)( + h3) + x5 = layers.add([x5_1, x5_2], name='reduction_add4_%s' % block_id) + + x = layers.concatenate([x2, x3, x4, x5], + axis=channel_dim, + name='reduction_concat_%s' % block_id) + return x, ip @keras_export('keras.applications.nasnet.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return nasnet.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input(x, data_format=data_format, mode='tf') + + +@keras_export('keras.applications.nasnet.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/applications/resnet.py b/tensorflow/python/keras/applications/resnet.py index b91389c39fb..dca9ed66f84 100644 --- a/tensorflow/python/keras/applications/resnet.py +++ b/tensorflow/python/keras/applications/resnet.py @@ -13,49 +13,567 @@ # limitations under the License. # ============================================================================== # pylint: disable=invalid-name -"""ResNet models for Keras. -""" +"""ResNet models for Keras.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import resnet +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils from tensorflow.python.util.tf_export import keras_export +BASE_WEIGHTS_PATH = ( + 'https://storage.googleapis.com/tensorflow/keras-applications/resnet/') +WEIGHTS_HASHES = { + 'resnet50': ('2cb95161c43110f7111970584f804107', + '4d473c1dd8becc155b73f8504c6f6626'), + 'resnet101': ('f1aeb4b969a6efcfb50fad2f0c20cfc5', + '88cf7a10940856eca736dc7b7e228a21'), + 'resnet152': ('100835be76be38e30d865e96f2aaae62', + 'ee4c566cf9a93f14d82f913c2dc6dd0c'), + 'resnet50v2': ('3ef43a0b657b3be2300d5770ece849e0', + 'fac2f116257151a9d068a22e544a4917'), + 'resnet101v2': ('6343647c601c52e1368623803854d971', + 'c0ed64b8031c3730f411d2eb4eea35b5'), + 'resnet152v2': ('a49b44d1979771252814e80f8ec446f9', + 'ed17cf2e0169df9d443503ef94b23b33'), + 'resnext50': ('67a5b30d522ed92f75a1f16eef299d1a', + '62527c363bdd9ec598bed41947b379fc'), + 'resnext101': + ('34fb605428fcc7aa4d62f44404c11509', '0f678c91647380debd923963594981b3') +} + + +def ResNet(stack_fn, + preact, + use_bias, + model_name='resnet', + include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + **kwargs): + """Instantiates the ResNet, ResNetV2, and ResNeXt architecture. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + stack_fn: a function that returns output tensor for the + stacked residual blocks. + preact: whether to use pre-activation or not + (True for ResNetV2, False for ResNet and ResNeXt). + use_bias: whether to use biases for convolutional layers or not + (True for ResNet and ResNetV2, False for ResNeXt). + model_name: string, model name. + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor + (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` (with `channels_last` data format) + or `(3, 224, 224)` (with `channels_first` data format). + It should have exactly 3 inputs channels. + pooling: optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional layer. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional layer, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + **kwargs: For backwards compatibility only. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if 'layers' in kwargs: + global layers + layers = kwargs.pop('layers') + if kwargs: + raise ValueError('Unknown argument(s): %s' % (kwargs,)) + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top`' + ' as true, `classes` should be 1000') + + # Determine proper input shape + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=224, + min_size=32, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1 + + x = layers.ZeroPadding2D( + padding=((3, 3), (3, 3)), name='conv1_pad')( + img_input) + x = layers.Conv2D(64, 7, strides=2, use_bias=use_bias, name='conv1_conv')(x) + + if not preact: + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name='conv1_bn')( + x) + x = layers.Activation('relu', name='conv1_relu')(x) + + x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name='pool1_pad')(x) + x = layers.MaxPooling2D(3, strides=2, name='pool1_pool')(x) + + x = stack_fn(x) + + if preact: + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name='post_bn')( + x) + x = layers.Activation('relu', name='post_relu')(x) + + if include_top: + x = layers.GlobalAveragePooling2D(name='avg_pool')(x) + x = layers.Dense(classes, activation='softmax', name='probs')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D(name='avg_pool')(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D(name='max_pool')(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + + # Create model. + model = training.Model(inputs, x, name=model_name) + + # Load weights. + if (weights == 'imagenet') and (model_name in WEIGHTS_HASHES): + if include_top: + file_name = model_name + '_weights_tf_dim_ordering_tf_kernels.h5' + file_hash = WEIGHTS_HASHES[model_name][0] + else: + file_name = model_name + '_weights_tf_dim_ordering_tf_kernels_notop.h5' + file_hash = WEIGHTS_HASHES[model_name][1] + weights_path = data_utils.get_file( + file_name, + BASE_WEIGHTS_PATH + file_name, + cache_subdir='models', + file_hash=file_hash) + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model + + +def block1(x, filters, kernel_size=3, stride=1, conv_shortcut=True, name=None): + """A residual block. + + Arguments: + x: input tensor. + filters: integer, filters of the bottleneck layer. + kernel_size: default 3, kernel size of the bottleneck layer. + stride: default 1, stride of the first layer. + conv_shortcut: default True, use convolution shortcut if True, + otherwise identity shortcut. + name: string, block label. + + Returns: + Output tensor for the residual block. + """ + bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1 + + if conv_shortcut: + shortcut = layers.Conv2D( + 4 * filters, 1, strides=stride, name=name + '_0_conv')( + x) + shortcut = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_0_bn')( + shortcut) + else: + shortcut = x + + x = layers.Conv2D(filters, 1, strides=stride, name=name + '_1_conv')(x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_1_bn')( + x) + x = layers.Activation('relu', name=name + '_1_relu')(x) + + x = layers.Conv2D( + filters, kernel_size, padding='SAME', name=name + '_2_conv')( + x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_2_bn')( + x) + x = layers.Activation('relu', name=name + '_2_relu')(x) + + x = layers.Conv2D(4 * filters, 1, name=name + '_3_conv')(x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_3_bn')( + x) + + x = layers.Add(name=name + '_add')([shortcut, x]) + x = layers.Activation('relu', name=name + '_out')(x) + return x + + +def stack1(x, filters, blocks, stride1=2, name=None): + """A set of stacked residual blocks. + + Arguments: + x: input tensor. + filters: integer, filters of the bottleneck layer in a block. + blocks: integer, blocks in the stacked blocks. + stride1: default 2, stride of the first layer in the first block. + name: string, stack label. + + Returns: + Output tensor for the stacked blocks. + """ + x = block1(x, filters, stride=stride1, name=name + '_block1') + for i in range(2, blocks + 1): + x = block1(x, filters, conv_shortcut=False, name=name + '_block' + str(i)) + return x + + +def block2(x, filters, kernel_size=3, stride=1, conv_shortcut=False, name=None): + """A residual block. + + Arguments: + x: input tensor. + filters: integer, filters of the bottleneck layer. + kernel_size: default 3, kernel size of the bottleneck layer. + stride: default 1, stride of the first layer. + conv_shortcut: default False, use convolution shortcut if True, + otherwise identity shortcut. + name: string, block label. + + Returns: + Output tensor for the residual block. + """ + bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1 + + preact = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_preact_bn')( + x) + preact = layers.Activation('relu', name=name + '_preact_relu')(preact) + + if conv_shortcut: + shortcut = layers.Conv2D( + 4 * filters, 1, strides=stride, name=name + '_0_conv')( + preact) + else: + shortcut = layers.MaxPooling2D(1, strides=stride)(x) if stride > 1 else x + + x = layers.Conv2D( + filters, 1, strides=1, use_bias=False, name=name + '_1_conv')( + preact) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_1_bn')( + x) + x = layers.Activation('relu', name=name + '_1_relu')(x) + + x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name=name + '_2_pad')(x) + x = layers.Conv2D( + filters, + kernel_size, + strides=stride, + use_bias=False, + name=name + '_2_conv')( + x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_2_bn')( + x) + x = layers.Activation('relu', name=name + '_2_relu')(x) + + x = layers.Conv2D(4 * filters, 1, name=name + '_3_conv')(x) + x = layers.Add(name=name + '_out')([shortcut, x]) + return x + + +def stack2(x, filters, blocks, stride1=2, name=None): + """A set of stacked residual blocks. + + Arguments: + x: input tensor. + filters: integer, filters of the bottleneck layer in a block. + blocks: integer, blocks in the stacked blocks. + stride1: default 2, stride of the first layer in the first block. + name: string, stack label. + + Returns: + Output tensor for the stacked blocks. + """ + x = block2(x, filters, conv_shortcut=True, name=name + '_block1') + for i in range(2, blocks): + x = block2(x, filters, name=name + '_block' + str(i)) + x = block2(x, filters, stride=stride1, name=name + '_block' + str(blocks)) + return x + + +def block3(x, + filters, + kernel_size=3, + stride=1, + groups=32, + conv_shortcut=True, + name=None): + """A residual block. + + Arguments: + x: input tensor. + filters: integer, filters of the bottleneck layer. + kernel_size: default 3, kernel size of the bottleneck layer. + stride: default 1, stride of the first layer. + groups: default 32, group size for grouped convolution. + conv_shortcut: default True, use convolution shortcut if True, + otherwise identity shortcut. + name: string, block label. + + Returns: + Output tensor for the residual block. + """ + bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1 + + if conv_shortcut: + shortcut = layers.Conv2D( + (64 // groups) * filters, + 1, + strides=stride, + use_bias=False, + name=name + '_0_conv')( + x) + shortcut = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_0_bn')( + shortcut) + else: + shortcut = x + + x = layers.Conv2D(filters, 1, use_bias=False, name=name + '_1_conv')(x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_1_bn')( + x) + x = layers.Activation('relu', name=name + '_1_relu')(x) + + c = filters // groups + x = layers.ZeroPadding2D(padding=((1, 1), (1, 1)), name=name + '_2_pad')(x) + x = layers.DepthwiseConv2D( + kernel_size, + strides=stride, + depth_multiplier=c, + use_bias=False, + name=name + '_2_conv')( + x) + x_shape = backend.int_shape(x)[1:-1] + x = layers.Reshape(x_shape + (groups, c, c))(x) + output_shape = x_shape + (groups, + c) if backend.backend() == 'theano' else None + x = layers.Lambda( + lambda x: sum(x[:, :, :, :, i] for i in range(c)), + output_shape=output_shape, + name=name + '_2_reduce')( + x) + x = layers.Reshape(x_shape + (filters,))(x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_2_bn')( + x) + x = layers.Activation('relu', name=name + '_2_relu')(x) + + x = layers.Conv2D( + (64 // groups) * filters, 1, use_bias=False, name=name + '_3_conv')( + x) + x = layers.BatchNormalization( + axis=bn_axis, epsilon=1.001e-5, name=name + '_3_bn')( + x) + + x = layers.Add(name=name + '_add')([shortcut, x]) + x = layers.Activation('relu', name=name + '_out')(x) + return x + + +def stack3(x, filters, blocks, stride1=2, groups=32, name=None): + """A set of stacked residual blocks. + + Arguments: + x: input tensor. + filters: integer, filters of the bottleneck layer in a block. + blocks: integer, blocks in the stacked blocks. + stride1: default 2, stride of the first layer in the first block. + groups: default 32, group size for grouped convolution. + name: string, stack label. + + Returns: + Output tensor for the stacked blocks. + """ + x = block3(x, filters, stride=stride1, groups=groups, name=name + '_block1') + for i in range(2, blocks + 1): + x = block3( + x, + filters, + groups=groups, + conv_shortcut=False, + name=name + '_block' + str(i)) + return x + + @keras_export('keras.applications.resnet50.ResNet50', 'keras.applications.resnet.ResNet50', 'keras.applications.ResNet50') -@keras_modules_injection -def ResNet50(*args, **kwargs): - return resnet.ResNet50(*args, **kwargs) +def ResNet50(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + **kwargs): + """Instantiates the ResNet50 architecture.""" + + def stack_fn(x): + x = stack1(x, 64, 3, stride1=1, name='conv2') + x = stack1(x, 128, 4, name='conv3') + x = stack1(x, 256, 6, name='conv4') + return stack1(x, 512, 3, name='conv5') + + return ResNet(stack_fn, False, True, 'resnet50', include_top, weights, + input_tensor, input_shape, pooling, classes, **kwargs) @keras_export('keras.applications.resnet.ResNet101', 'keras.applications.ResNet101') -@keras_modules_injection -def ResNet101(*args, **kwargs): - return resnet.ResNet101(*args, **kwargs) +def ResNet101(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + **kwargs): + """Instantiates the ResNet101 architecture.""" + + def stack_fn(x): + x = stack1(x, 64, 3, stride1=1, name='conv2') + x = stack1(x, 128, 4, name='conv3') + x = stack1(x, 256, 23, name='conv4') + return stack1(x, 512, 3, name='conv5') + + return ResNet(stack_fn, False, True, 'resnet101', include_top, weights, + input_tensor, input_shape, pooling, classes, **kwargs) @keras_export('keras.applications.resnet.ResNet152', 'keras.applications.ResNet152') -@keras_modules_injection -def ResNet152(*args, **kwargs): - return resnet.ResNet152(*args, **kwargs) +def ResNet152(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000, + **kwargs): + """Instantiates the ResNet152 architecture.""" + def stack_fn(x): + x = stack1(x, 64, 3, stride1=1, name='conv2') + x = stack1(x, 128, 8, name='conv3') + x = stack1(x, 256, 36, name='conv4') + return stack1(x, 512, 3, name='conv5') -@keras_export('keras.applications.resnet50.decode_predictions', - 'keras.applications.resnet.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return resnet.decode_predictions(*args, **kwargs) + return ResNet(stack_fn, False, True, 'resnet152', include_top, weights, + input_tensor, input_shape, pooling, classes, **kwargs) @keras_export('keras.applications.resnet50.preprocess_input', 'keras.applications.resnet.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return resnet.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input( + x, data_format=data_format, mode='caffe') + + +@keras_export('keras.applications.resnet50.decode_predictions', + 'keras.applications.resnet.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) + + +DOC = """ + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` (with `'channels_last'` data format) + or `(3, 224, 224)` (with `'channels_first'` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 32. + E.g. `(200, 200, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. +""" + +setattr(ResNet50, '__doc__', ResNet50.__doc__ + DOC) +setattr(ResNet101, '__doc__', ResNet101.__doc__ + DOC) +setattr(ResNet152, '__doc__', ResNet152.__doc__ + DOC) diff --git a/tensorflow/python/keras/applications/resnet_v2.py b/tensorflow/python/keras/applications/resnet_v2.py index e00f6f6aac5..4c782042af2 100644 --- a/tensorflow/python/keras/applications/resnet_v2.py +++ b/tensorflow/python/keras/applications/resnet_v2.py @@ -13,46 +13,121 @@ # limitations under the License. # ============================================================================== # pylint: disable=invalid-name -"""ResNet v2 models for Keras. -""" +"""ResNet v2 models for Keras.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import resnet_v2 - -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.applications import resnet from tensorflow.python.util.tf_export import keras_export @keras_export('keras.applications.resnet_v2.ResNet50V2', 'keras.applications.ResNet50V2') -@keras_modules_injection -def ResNet50V2(*args, **kwargs): - return resnet_v2.ResNet50V2(*args, **kwargs) +def ResNet50V2(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the ResNet50V2 architecture.""" + def stack_fn(x): + x = resnet.stack2(x, 64, 3, name='conv2') + x = resnet.stack2(x, 128, 4, name='conv3') + x = resnet.stack2(x, 256, 6, name='conv4') + return resnet.stack2(x, 512, 3, stride1=1, name='conv5') + return resnet.ResNet(stack_fn, True, True, 'resnet50v2', include_top, weights, + input_tensor, input_shape, pooling, classes) @keras_export('keras.applications.resnet_v2.ResNet101V2', 'keras.applications.ResNet101V2') -@keras_modules_injection -def ResNet101V2(*args, **kwargs): - return resnet_v2.ResNet101V2(*args, **kwargs) +def ResNet101V2(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the ResNet101V2 architecture.""" + def stack_fn(x): + x = resnet.stack2(x, 64, 3, name='conv2') + x = resnet.stack2(x, 128, 4, name='conv3') + x = resnet.stack2(x, 256, 23, name='conv4') + return resnet.stack2(x, 512, 3, stride1=1, name='conv5') + return resnet.ResNet(stack_fn, True, True, 'resnet101v2', include_top, + weights, input_tensor, input_shape, pooling, classes) @keras_export('keras.applications.resnet_v2.ResNet152V2', 'keras.applications.ResNet152V2') -@keras_modules_injection -def ResNet152V2(*args, **kwargs): - return resnet_v2.ResNet152V2(*args, **kwargs) - - -@keras_export('keras.applications.resnet_v2.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return resnet_v2.decode_predictions(*args, **kwargs) +def ResNet152V2(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the ResNet152V2 architecture.""" + def stack_fn(x): + x = resnet.stack2(x, 64, 3, name='conv2') + x = resnet.stack2(x, 128, 8, name='conv3') + x = resnet.stack2(x, 256, 36, name='conv4') + return resnet.stack2(x, 512, 3, stride1=1, name='conv5') + return resnet.ResNet(stack_fn, True, True, 'resnet152v2', include_top, + weights, input_tensor, input_shape, pooling, classes) @keras_export('keras.applications.resnet_v2.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return resnet_v2.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input( + x, data_format=data_format, mode='caffe') + + +@keras_export('keras.applications.resnet_v2.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) + + +DOC = """ + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` (with `'channels_last'` data format) + or `(3, 224, 224)` (with `'channels_first'` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 32. + E.g. `(200, 200, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. +""" + +setattr(ResNet50V2, '__doc__', ResNet50V2.__doc__ + DOC) +setattr(ResNet101V2, '__doc__', ResNet101V2.__doc__ + DOC) +setattr(ResNet152V2, '__doc__', ResNet152V2.__doc__ + DOC) diff --git a/tensorflow/python/keras/applications/vgg16.py b/tensorflow/python/keras/applications/vgg16.py index e2a34258caa..73770a03abd 100644 --- a/tensorflow/python/keras/applications/vgg16.py +++ b/tensorflow/python/keras/applications/vgg16.py @@ -13,32 +13,196 @@ # limitations under the License. # ============================================================================== # pylint: disable=invalid-name -"""VGG16 model for Keras. -""" +"""VGG16 model for Keras.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import vgg16 +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils from tensorflow.python.util.tf_export import keras_export -@keras_export('keras.applications.vgg16.VGG16', - 'keras.applications.VGG16') -@keras_modules_injection -def VGG16(*args, **kwargs): - return vgg16.VGG16(*args, **kwargs) +WEIGHTS_PATH = ('https://storage.googleapis.com/tensorflow/keras-applications/' + 'vgg16/vgg16_weights_tf_dim_ordering_tf_kernels.h5') +WEIGHTS_PATH_NO_TOP = ('https://storage.googleapis.com/tensorflow/' + 'keras-applications/vgg16/' + 'vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5') -@keras_export('keras.applications.vgg16.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return vgg16.decode_predictions(*args, **kwargs) +@keras_export('keras.applications.vgg16.VGG16', 'keras.applications.VGG16') +def VGG16(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the VGG16 architecture. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + include_top: whether to include the 3 fully-connected + layers at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor + (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` + (with `channels_last` data format) + or `(3, 224, 224)` (with `channels_first` data format). + It should have exactly 3 input channels, + and width and height should be no smaller than 32. + E.g. `(200, 200, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top`' + ' as true, `classes` should be 1000') + # Determine proper input shape + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=224, + min_size=32, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + # Block 1 + x = layers.Conv2D( + 64, (3, 3), activation='relu', padding='same', name='block1_conv1')( + img_input) + x = layers.Conv2D( + 64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x) + + # Block 2 + x = layers.Conv2D( + 128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x) + x = layers.Conv2D( + 128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x) + + # Block 3 + x = layers.Conv2D( + 256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x) + x = layers.Conv2D( + 256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x) + x = layers.Conv2D( + 256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x) + + # Block 4 + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x) + + # Block 5 + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block5_pool')(x) + + if include_top: + # Classification block + x = layers.Flatten(name='flatten')(x) + x = layers.Dense(4096, activation='relu', name='fc1')(x) + x = layers.Dense(4096, activation='relu', name='fc2')(x) + x = layers.Dense(classes, activation='softmax', name='predictions')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = training.Model(inputs, x, name='vgg16') + + # Load weights. + if weights == 'imagenet': + if include_top: + weights_path = data_utils.get_file( + 'vgg16_weights_tf_dim_ordering_tf_kernels.h5', + WEIGHTS_PATH, + cache_subdir='models', + file_hash='64373286793e3c8b2b4e3219cbf3544b') + else: + weights_path = data_utils.get_file( + 'vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5', + WEIGHTS_PATH_NO_TOP, + cache_subdir='models', + file_hash='6d6bbae143d832006294945121d1f1fc') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model @keras_export('keras.applications.vgg16.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return vgg16.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input( + x, data_format=data_format, mode='caffe') + + +@keras_export('keras.applications.vgg16.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/applications/vgg19.py b/tensorflow/python/keras/applications/vgg19.py index ed362edfa82..d75e2d5875f 100644 --- a/tensorflow/python/keras/applications/vgg19.py +++ b/tensorflow/python/keras/applications/vgg19.py @@ -14,31 +14,206 @@ # ============================================================================== # pylint: disable=invalid-name """VGG19 model for Keras. + +Reference: + - [Very Deep Convolutional Networks for Large-Scale Image Recognition]( + https://arxiv.org/abs/1409.1556) (ICLR 2015) """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import vgg19 +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils from tensorflow.python.util.tf_export import keras_export -@keras_export('keras.applications.vgg19.VGG19', - 'keras.applications.VGG19') -@keras_modules_injection -def VGG19(*args, **kwargs): - return vgg19.VGG19(*args, **kwargs) +WEIGHTS_PATH = ('https://storage.googleapis.com/tensorflow/keras-applications/' + 'vgg19/vgg19_weights_tf_dim_ordering_tf_kernels.h5') +WEIGHTS_PATH_NO_TOP = ('https://storage.googleapis.com/tensorflow/' + 'keras-applications/vgg19/' + 'vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5') -@keras_export('keras.applications.vgg19.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return vgg19.decode_predictions(*args, **kwargs) +@keras_export('keras.applications.vgg19.VGG19', 'keras.applications.VGG19') +def VGG19(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the VGG19 architecture. + + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + + Arguments: + include_top: whether to include the 3 fully-connected + layers at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor + (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(224, 224, 3)` + (with `channels_last` data format) + or `(3, 224, 224)` (with `channels_first` data format). + It should have exactly 3 inputs channels, + and width and height should be no smaller than 32. + E.g. `(200, 200, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, and + if no `weights` argument is specified. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top`' + ' as true, `classes` should be 1000') + # Determine proper input shape + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=224, + min_size=32, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + # Block 1 + x = layers.Conv2D( + 64, (3, 3), activation='relu', padding='same', name='block1_conv1')( + img_input) + x = layers.Conv2D( + 64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x) + + # Block 2 + x = layers.Conv2D( + 128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x) + x = layers.Conv2D( + 128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x) + + # Block 3 + x = layers.Conv2D( + 256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x) + x = layers.Conv2D( + 256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x) + x = layers.Conv2D( + 256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x) + x = layers.Conv2D( + 256, (3, 3), activation='relu', padding='same', name='block3_conv4')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x) + + # Block 4 + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block4_conv4')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x) + + # Block 5 + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x) + x = layers.Conv2D( + 512, (3, 3), activation='relu', padding='same', name='block5_conv4')(x) + x = layers.MaxPooling2D((2, 2), strides=(2, 2), name='block5_pool')(x) + + if include_top: + # Classification block + x = layers.Flatten(name='flatten')(x) + x = layers.Dense(4096, activation='relu', name='fc1')(x) + x = layers.Dense(4096, activation='relu', name='fc2')(x) + x = layers.Dense(classes, activation='softmax', name='predictions')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = training.Model(inputs, x, name='vgg19') + + # Load weights. + if weights == 'imagenet': + if include_top: + weights_path = data_utils.get_file( + 'vgg19_weights_tf_dim_ordering_tf_kernels.h5', + WEIGHTS_PATH, + cache_subdir='models', + file_hash='cbe5617147190e668d6c5d5026f83318') + else: + weights_path = data_utils.get_file( + 'vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5', + WEIGHTS_PATH_NO_TOP, + cache_subdir='models', + file_hash='253f8cb515780f3b799900260a226db6') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model @keras_export('keras.applications.vgg19.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return vgg19.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input( + x, data_format=data_format, mode='caffe') + + +@keras_export('keras.applications.vgg19.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/applications/xception.py b/tensorflow/python/keras/applications/xception.py index 4476213f6d4..47f386cc721 100644 --- a/tensorflow/python/keras/applications/xception.py +++ b/tensorflow/python/keras/applications/xception.py @@ -14,31 +14,294 @@ # ============================================================================== # pylint: disable=invalid-name """Xception V1 model for Keras. + +On ImageNet, this model gets to a top-1 validation accuracy of 0.790 +and a top-5 validation accuracy of 0.945. + +Reference paper: + - [Xception: Deep Learning with Depthwise Separable Convolutions]( + https://arxiv.org/abs/1610.02357) (CVPR 2017) + """ from __future__ import absolute_import from __future__ import division from __future__ import print_function -from keras_applications import xception +import os -from tensorflow.python.keras.applications import keras_modules_injection +from tensorflow.python.keras import backend +from tensorflow.python.keras import layers +from tensorflow.python.keras.applications import imagenet_utils +from tensorflow.python.keras.engine import training +from tensorflow.python.keras.utils import data_utils +from tensorflow.python.keras.utils import layer_utils from tensorflow.python.util.tf_export import keras_export +TF_WEIGHTS_PATH = ( + 'https://storage.googleapis.com/tensorflow/keras-applications/' + 'xception/xception_weights_tf_dim_ordering_tf_kernels.h5') +TF_WEIGHTS_PATH_NO_TOP = ( + 'https://storage.googleapis.com/tensorflow/keras-applications/' + 'xception/xception_weights_tf_dim_ordering_tf_kernels_notop.h5') + + @keras_export('keras.applications.xception.Xception', 'keras.applications.Xception') -@keras_modules_injection -def Xception(*args, **kwargs): - return xception.Xception(*args, **kwargs) +def Xception(include_top=True, + weights='imagenet', + input_tensor=None, + input_shape=None, + pooling=None, + classes=1000): + """Instantiates the Xception architecture. + Optionally loads weights pre-trained on ImageNet. + Note that the data format convention used by the model is + the one specified in your Keras config at `~/.keras/keras.json`. + Note that the default input image size for this model is 299x299. -@keras_export('keras.applications.xception.decode_predictions') -@keras_modules_injection -def decode_predictions(*args, **kwargs): - return xception.decode_predictions(*args, **kwargs) + Arguments: + include_top: whether to include the fully-connected + layer at the top of the network. + weights: one of `None` (random initialization), + 'imagenet' (pre-training on ImageNet), + or the path to the weights file to be loaded. + input_tensor: optional Keras tensor + (i.e. output of `layers.Input()`) + to use as image input for the model. + input_shape: optional shape tuple, only to be specified + if `include_top` is False (otherwise the input shape + has to be `(299, 299, 3)`. + It should have exactly 3 inputs channels, + and width and height should be no smaller than 71. + E.g. `(150, 150, 3)` would be one valid value. + pooling: Optional pooling mode for feature extraction + when `include_top` is `False`. + - `None` means that the output of the model will be + the 4D tensor output of the + last convolutional block. + - `avg` means that global average pooling + will be applied to the output of the + last convolutional block, and thus + the output of the model will be a 2D tensor. + - `max` means that global max pooling will + be applied. + classes: optional number of classes to classify images + into, only to be specified if `include_top` is True, + and if no `weights` argument is specified. + + Returns: + A Keras model instance. + + Raises: + ValueError: in case of invalid argument for `weights`, + or invalid input shape. + """ + if not (weights in {'imagenet', None} or os.path.exists(weights)): + raise ValueError('The `weights` argument should be either ' + '`None` (random initialization), `imagenet` ' + '(pre-training on ImageNet), ' + 'or the path to the weights file to be loaded.') + + if weights == 'imagenet' and include_top and classes != 1000: + raise ValueError('If using `weights` as `"imagenet"` with `include_top`' + ' as true, `classes` should be 1000') + + # Determine proper input shape + input_shape = imagenet_utils.obtain_input_shape( + input_shape, + default_size=299, + min_size=71, + data_format=backend.image_data_format(), + require_flatten=include_top, + weights=weights) + + if input_tensor is None: + img_input = layers.Input(shape=input_shape) + else: + if not backend.is_keras_tensor(input_tensor): + img_input = layers.Input(tensor=input_tensor, shape=input_shape) + else: + img_input = input_tensor + + channel_axis = 1 if backend.image_data_format() == 'channels_first' else -1 + + x = layers.Conv2D( + 32, (3, 3), + strides=(2, 2), + use_bias=False, + name='block1_conv1')(img_input) + x = layers.BatchNormalization(axis=channel_axis, name='block1_conv1_bn')(x) + x = layers.Activation('relu', name='block1_conv1_act')(x) + x = layers.Conv2D(64, (3, 3), use_bias=False, name='block1_conv2')(x) + x = layers.BatchNormalization(axis=channel_axis, name='block1_conv2_bn')(x) + x = layers.Activation('relu', name='block1_conv2_act')(x) + + residual = layers.Conv2D( + 128, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x) + residual = layers.BatchNormalization(axis=channel_axis)(residual) + + x = layers.SeparableConv2D( + 128, (3, 3), padding='same', use_bias=False, name='block2_sepconv1')(x) + x = layers.BatchNormalization(axis=channel_axis, name='block2_sepconv1_bn')(x) + x = layers.Activation('relu', name='block2_sepconv2_act')(x) + x = layers.SeparableConv2D( + 128, (3, 3), padding='same', use_bias=False, name='block2_sepconv2')(x) + x = layers.BatchNormalization(axis=channel_axis, name='block2_sepconv2_bn')(x) + + x = layers.MaxPooling2D((3, 3), + strides=(2, 2), + padding='same', + name='block2_pool')(x) + x = layers.add([x, residual]) + + residual = layers.Conv2D( + 256, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x) + residual = layers.BatchNormalization(axis=channel_axis)(residual) + + x = layers.Activation('relu', name='block3_sepconv1_act')(x) + x = layers.SeparableConv2D( + 256, (3, 3), padding='same', use_bias=False, name='block3_sepconv1')(x) + x = layers.BatchNormalization(axis=channel_axis, name='block3_sepconv1_bn')(x) + x = layers.Activation('relu', name='block3_sepconv2_act')(x) + x = layers.SeparableConv2D( + 256, (3, 3), padding='same', use_bias=False, name='block3_sepconv2')(x) + x = layers.BatchNormalization(axis=channel_axis, name='block3_sepconv2_bn')(x) + + x = layers.MaxPooling2D((3, 3), + strides=(2, 2), + padding='same', + name='block3_pool')(x) + x = layers.add([x, residual]) + + residual = layers.Conv2D( + 728, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x) + residual = layers.BatchNormalization(axis=channel_axis)(residual) + + x = layers.Activation('relu', name='block4_sepconv1_act')(x) + x = layers.SeparableConv2D( + 728, (3, 3), padding='same', use_bias=False, name='block4_sepconv1')(x) + x = layers.BatchNormalization(axis=channel_axis, name='block4_sepconv1_bn')(x) + x = layers.Activation('relu', name='block4_sepconv2_act')(x) + x = layers.SeparableConv2D( + 728, (3, 3), padding='same', use_bias=False, name='block4_sepconv2')(x) + x = layers.BatchNormalization(axis=channel_axis, name='block4_sepconv2_bn')(x) + + x = layers.MaxPooling2D((3, 3), + strides=(2, 2), + padding='same', + name='block4_pool')(x) + x = layers.add([x, residual]) + + for i in range(8): + residual = x + prefix = 'block' + str(i + 5) + + x = layers.Activation('relu', name=prefix + '_sepconv1_act')(x) + x = layers.SeparableConv2D( + 728, (3, 3), + padding='same', + use_bias=False, + name=prefix + '_sepconv1')(x) + x = layers.BatchNormalization( + axis=channel_axis, name=prefix + '_sepconv1_bn')(x) + x = layers.Activation('relu', name=prefix + '_sepconv2_act')(x) + x = layers.SeparableConv2D( + 728, (3, 3), + padding='same', + use_bias=False, + name=prefix + '_sepconv2')(x) + x = layers.BatchNormalization( + axis=channel_axis, name=prefix + '_sepconv2_bn')(x) + x = layers.Activation('relu', name=prefix + '_sepconv3_act')(x) + x = layers.SeparableConv2D( + 728, (3, 3), + padding='same', + use_bias=False, + name=prefix + '_sepconv3')(x) + x = layers.BatchNormalization( + axis=channel_axis, name=prefix + '_sepconv3_bn')(x) + + x = layers.add([x, residual]) + + residual = layers.Conv2D( + 1024, (1, 1), strides=(2, 2), padding='same', use_bias=False)(x) + residual = layers.BatchNormalization(axis=channel_axis)(residual) + + x = layers.Activation('relu', name='block13_sepconv1_act')(x) + x = layers.SeparableConv2D( + 728, (3, 3), padding='same', use_bias=False, name='block13_sepconv1')(x) + x = layers.BatchNormalization( + axis=channel_axis, name='block13_sepconv1_bn')(x) + x = layers.Activation('relu', name='block13_sepconv2_act')(x) + x = layers.SeparableConv2D( + 1024, (3, 3), padding='same', use_bias=False, name='block13_sepconv2')(x) + x = layers.BatchNormalization( + axis=channel_axis, name='block13_sepconv2_bn')(x) + + x = layers.MaxPooling2D((3, 3), + strides=(2, 2), + padding='same', + name='block13_pool')(x) + x = layers.add([x, residual]) + + x = layers.SeparableConv2D( + 1536, (3, 3), padding='same', use_bias=False, name='block14_sepconv1')(x) + x = layers.BatchNormalization( + axis=channel_axis, name='block14_sepconv1_bn')(x) + x = layers.Activation('relu', name='block14_sepconv1_act')(x) + + x = layers.SeparableConv2D( + 2048, (3, 3), padding='same', use_bias=False, name='block14_sepconv2')(x) + x = layers.BatchNormalization( + axis=channel_axis, name='block14_sepconv2_bn')(x) + x = layers.Activation('relu', name='block14_sepconv2_act')(x) + + if include_top: + x = layers.GlobalAveragePooling2D(name='avg_pool')(x) + x = layers.Dense(classes, activation='softmax', name='predictions')(x) + else: + if pooling == 'avg': + x = layers.GlobalAveragePooling2D()(x) + elif pooling == 'max': + x = layers.GlobalMaxPooling2D()(x) + + # Ensure that the model takes into account + # any potential predecessors of `input_tensor`. + if input_tensor is not None: + inputs = layer_utils.get_source_inputs(input_tensor) + else: + inputs = img_input + # Create model. + model = training.Model(inputs, x, name='xception') + + # Load weights. + if weights == 'imagenet': + if include_top: + weights_path = data_utils.get_file( + 'xception_weights_tf_dim_ordering_tf_kernels.h5', + TF_WEIGHTS_PATH, + cache_subdir='models', + file_hash='0a58e3b7378bc2990ea3b43d5981f1f6') + else: + weights_path = data_utils.get_file( + 'xception_weights_tf_dim_ordering_tf_kernels_notop.h5', + TF_WEIGHTS_PATH_NO_TOP, + cache_subdir='models', + file_hash='b0042744bf5b25fce3cb969f33bebb97') + model.load_weights(weights_path) + elif weights is not None: + model.load_weights(weights) + + return model @keras_export('keras.applications.xception.preprocess_input') -@keras_modules_injection -def preprocess_input(*args, **kwargs): - return xception.preprocess_input(*args, **kwargs) +def preprocess_input(x, data_format=None): + return imagenet_utils.preprocess_input(x, data_format=data_format, mode='tf') + + +@keras_export('keras.applications.xception.decode_predictions') +def decode_predictions(preds, top=5): + return imagenet_utils.decode_predictions(preds, top=top) diff --git a/tensorflow/python/keras/backend.py b/tensorflow/python/keras/backend.py index 7122d6e3cc1..f1e199ee4ce 100644 --- a/tensorflow/python/keras/backend.py +++ b/tensorflow/python/keras/backend.py @@ -1035,7 +1035,7 @@ def placeholder(shape=None, dtype = floatx() if not shape: if ndim: - shape = tuple([None for _ in range(ndim)]) + shape = (None,) * ndim with get_graph().as_default(): if sparse: x = array_ops.sparse_placeholder(dtype, shape=shape, name=name) @@ -1768,7 +1768,7 @@ def batch_dot(x, y, axes=None): else: axes = [x_ndim - 1, y_ndim - 2] - if py_any([isinstance(a, (list, tuple)) for a in axes]): + if py_any(isinstance(a, (list, tuple)) for a in axes): raise ValueError('Multiple target dimensions are not supported. ' + 'Expected: None, int, (int, int), ' + 'Provided: ' + str(axes)) @@ -1905,7 +1905,7 @@ def transpose(x): >>> input_transposed = tf.keras.backend.transpose(input) >>> input_transposed - + """ @@ -4486,7 +4486,7 @@ def categorical_crossentropy(target, output, from_logits=False, axis=-1): tf.Tensor([0.10536055 0.8046684 0.06187541], shape=(3,), dtype=float32) >>> loss = tf.keras.backend.categorical_crossentropy(a, a) >>> print(loss) - tf.Tensor([1.1920929e-07 1.1920929e-07 1.19...e-07], shape=(3,), + tf.Tensor([1.1920929e-07 1.1920929e-07 1.1920930e-07], shape=(3,), dtype=float32) """ @@ -4580,7 +4580,7 @@ def sparse_categorical_crossentropy(target, output, from_logits=False, axis=-1): target = flatten(target) output = array_ops.reshape(output, [-1, output_shape[-1]]) - if py_any([_is_symbolic_tensor(v) for v in [target, output]]): + if py_any(_is_symbolic_tensor(v) for v in [target, output]): with get_graph().as_default(): res = nn.sparse_softmax_cross_entropy_with_logits_v2( labels=target, logits=output) @@ -5424,9 +5424,9 @@ def local_conv(inputs, if data_format == 'channels_first': slices.append(slice(None)) - slices.extend([slice(position[d] * strides[d], - position[d] * strides[d] + kernel_size[d]) - for d in spatial_dimensions]) + slices.extend( + slice(position[d] * strides[d], position[d] * strides[d] + + kernel_size[d]) for d in spatial_dimensions) if data_format == 'channels_last': slices.append(slice(None)) diff --git a/tensorflow/python/keras/datasets/imdb.py b/tensorflow/python/keras/datasets/imdb.py index d9f209add01..d65aa00f3f6 100644 --- a/tensorflow/python/keras/datasets/imdb.py +++ b/tensorflow/python/keras/datasets/imdb.py @@ -113,7 +113,7 @@ def load_data(path='imdb.npz', str(maxlen) + ', no sequence was kept. ' 'Increase maxlen.') if not num_words: - num_words = max([max(x) for x in xs]) + num_words = max(max(x) for x in xs) # by convention, use 2 as OOV word # reserve 'index_from' (=3 by default) characters: diff --git a/tensorflow/python/keras/datasets/reuters.py b/tensorflow/python/keras/datasets/reuters.py index e1aa1f5d185..7767a1730e1 100644 --- a/tensorflow/python/keras/datasets/reuters.py +++ b/tensorflow/python/keras/datasets/reuters.py @@ -99,7 +99,7 @@ def load_data(path='reuters.npz', xs, labels = _remove_long_seq(maxlen, xs, labels) if not num_words: - num_words = max([max(x) for x in xs]) + num_words = max(max(x) for x in xs) # by convention, use 2 as OOV word # reserve 'index_from' (=3 by default) characters: diff --git a/tensorflow/python/keras/distribute/BUILD b/tensorflow/python/keras/distribute/BUILD index ff58dd11fc2..de47f903b33 100644 --- a/tensorflow/python/keras/distribute/BUILD +++ b/tensorflow/python/keras/distribute/BUILD @@ -2,7 +2,7 @@ # keras/distribute package is intended to serve as the centralized place for things # related to dist-strat used by Keras.. -load("//tensorflow/core/platform:default/distribute.bzl", "distribute_py_test") +load("//tensorflow/core/platform/default:distribute.bzl", "distribute_py_test") load("//tensorflow:tensorflow.bzl", "cuda_py_test") package( @@ -63,7 +63,8 @@ py_library( cuda_py_test( name = "multi_worker_training_state_test", srcs = ["multi_worker_training_state_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":multi_worker_testing_utils", ":multi_worker_training_state", "//tensorflow/python:platform", @@ -71,7 +72,6 @@ cuda_py_test( "//tensorflow/python/distribute:multi_worker_test_base", "//tensorflow/python/keras", ], - shard_count = 4, ) py_library( @@ -305,23 +305,27 @@ py_library( cuda_py_test( name = "keras_optimizer_v2_test", srcs = ["keras_optimizer_v2_test.py"], - additional_deps = [ - ":keras_test_lib", - ], shard_count = 4, tags = [ "multi_and_single_gpu", "no_oss", # http://b/119349471 "tf_integration_test", ], + deps = [ + ":keras_test_lib", + ], ) cuda_py_test( name = "multi_worker_test", srcs = ["multi_worker_test.py"], - additional_deps = [ + shard_count = 32, + tags = [ + "no_oss", # TODO(b/130369494): Investigate why it times out on OSS. + # TODO(b/123307453): Add "multi_and_single_gpu", + ], + deps = [ ":multi_worker_testing_utils", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", @@ -343,28 +347,13 @@ cuda_py_test( "//tensorflow/python/keras:engine", "//tensorflow/python/keras:optimizers", "//tensorflow/python/keras/optimizer_v2", - ], - shard_count = 32, - tags = [ - "no_oss", # TODO(b/130369494): Investigate why it times out on OSS. - # TODO(b/123307453): Add "multi_and_single_gpu", + "@absl_py//absl/testing:parameterized", ], ) cuda_py_test( name = "multi_worker_callback_tf1_test", srcs = ["multi_worker_callback_tf1_test.py"], - additional_deps = [ - ":distribute", - "//tensorflow/python:platform", - "//tensorflow/python/distribute:collective_all_reduce_strategy", - "//tensorflow/python/distribute:combinations", - "//tensorflow/python/distribute:multi_worker_test_base", - "//tensorflow/python/distribute:distribute_config", - "//tensorflow/python/distribute:distribute_coordinator", - ":multi_worker_testing_utils", - "//tensorflow/python/keras", - ], # TODO(b/132384649): Enable for guitar and oss tests. shard_count = 24, tags = [ @@ -373,12 +362,23 @@ cuda_py_test( "noguitar", "notap", ], + deps = [ + ":distribute", + ":multi_worker_testing_utils", + "//tensorflow/python:platform", + "//tensorflow/python/distribute:collective_all_reduce_strategy", + "//tensorflow/python/distribute:combinations", + "//tensorflow/python/distribute:distribute_config", + "//tensorflow/python/distribute:distribute_coordinator", + "//tensorflow/python/distribute:multi_worker_test_base", + "//tensorflow/python/keras", + ], ) cuda_py_test( name = "multi_worker_callback_tf2_test", srcs = ["multi_worker_callback_tf2_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python/distribute:collective_all_reduce_strategy", "//tensorflow/python/distribute:combinations", "//tensorflow/python/distribute:multi_process_runner", @@ -390,42 +390,22 @@ cuda_py_test( cuda_py_test( name = "multi_worker_fault_tolerance_test", srcs = ["multi_worker_fault_tolerance_test.py"], - additional_deps = [ - ":distribute", - "//tensorflow/python:platform", - "//tensorflow/python/distribute:collective_all_reduce_strategy", - "//tensorflow/python/distribute:combinations", - "//tensorflow/python/distribute:multi_worker_test_base", - "//tensorflow/python/distribute:distribute_config", - "//tensorflow/python/distribute:distribute_coordinator", - ":multi_worker_testing_utils", - "//tensorflow/python/keras", - ], shard_count = 14, # TODO(b/132384649): Enable once fixed. tags = [ "no_oss", ], -) - -cuda_py_test( - name = "multi_worker_optimizer_comparison_test", - srcs = ["multi_worker_optimizer_comparison_test.py"], - additional_deps = [ + deps = [ + ":distribute", + ":multi_worker_testing_utils", + "//tensorflow/python:platform", "//tensorflow/python/distribute:collective_all_reduce_strategy", "//tensorflow/python/distribute:combinations", - "//tensorflow/python/keras/distribute:multi_worker_test", - "//tensorflow/python/distribute:multi_worker_test_base", - "//tensorflow/python:platform", + "//tensorflow/python/distribute:distribute_config", "//tensorflow/python/distribute:distribute_coordinator", + "//tensorflow/python/distribute:multi_worker_test_base", "//tensorflow/python/keras", ], - tags = [ - "manual", # b/139844866 - "multi_and_single_gpu", - "no_oss", - "notap", - ], ) py_library( diff --git a/tensorflow/python/keras/distribute/multi_worker_optimizer_comparison_test.py b/tensorflow/python/keras/distribute/multi_worker_optimizer_comparison_test.py deleted file mode 100644 index 21128881b5d..00000000000 --- a/tensorflow/python/keras/distribute/multi_worker_optimizer_comparison_test.py +++ /dev/null @@ -1,140 +0,0 @@ -# Copyright 2019 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 Keras multi worker callbacks.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import sys -import threading - -from absl.testing import parameterized - -# pylint: disable=g-direct-tensorflow-import -from tensorflow.python import keras -from tensorflow.python.data.ops import dataset_ops -from tensorflow.python.distribute import collective_all_reduce_strategy as collective_strategy -from tensorflow.python.distribute import combinations -from tensorflow.python.distribute import distribute_coordinator as dc -from tensorflow.python.distribute import multi_worker_test_base as test_base -from tensorflow.python.keras.engine import base_layer -from tensorflow.python.keras.engine import sequential -from tensorflow.python.keras.optimizer_v2 import gradient_descent -from tensorflow.python.platform import test -from tensorflow.python.training import gradient_descent as gradient_descent_v1 - - -class KerasMultiWorkerOptimizerTest(test_base.IndependentWorkerTestBase, - parameterized.TestCase): - - def run_optimizer_comparison_with_simple_bias_model( - self, strategy_cls, optimizer_class_1, optimizer_class_2): - - def get_input_datasets(): - # Simple training input. - train_input = [[1]] * 16 - train_label = [[0]] * 16 - ds = dataset_ops.Dataset.from_tensor_slices((train_input, train_label)) - # TODO(rchao): Investigate to figure out the reason for having 8 workers - # instead of 2 as expected. - return ds.batch(8, drop_remainder=True) - - def get_simple_bias_model(): - - class Bias(base_layer.Layer): - - def build(self, input_shape): - self.bias = self.add_variable('bias', (1,), initializer='zeros') - - def call(self, inputs): - return inputs + self.bias - - model = sequential.Sequential() - model.add(Bias(input_shape=(1,))) - - return model - - self._lock = threading.Lock() - cluster_spec = test_base.create_cluster_spec(num_workers=2) - self._barrier = dc._Barrier(2) - - def _independent_worker_fn(*args, **kwargs): # pylint: disable=unused-argument - """Simulates an Independent Worker inside a thread.""" - # TODO(rchao): Refactor to abstract the common boilerplate out. - with test.mock.patch.object(dc, '_run_std_server', - self._make_mock_run_std_server()): - - model = get_simple_bias_model() - - initial_weights = model.get_weights() - - def _get_model_results(optimizer, initial_weights): - - # Clear Keras session to reset device assignment - keras.backend._SESSION.session = None - strategy = strategy_cls() - - with strategy.scope(): - train_ds = get_input_datasets() - model = get_simple_bias_model() - model.set_weights(initial_weights) - model.compile(loss='mae', optimizer=optimizer, metrics=['mae']) - - return { - 'trained_loss_and_accuracy': - model.fit(x=train_ds, epochs=20).history, - 'trained_weights': - model.get_weights(), - } - - results1 = _get_model_results(optimizer_class_1(0.01), initial_weights) - results2 = _get_model_results(optimizer_class_2(0.01), initial_weights) - - for key in results1: - self.assertAllClose( - results1[key], - results2[key], - atol=1e-5, - rtol=1e-5, - msg='Fail to assert {}'.format(key)) - - threads = self.run_multiple_tasks_in_threads(_independent_worker_fn, - cluster_spec) - - threads_to_join = [] - strategy = strategy_cls() - if strategy.extended.experimental_between_graph: - for ts in threads.values(): - threads_to_join.extend(ts) - else: - threads_to_join = [threads['worker'][0]] - self.join_independent_workers(threads_to_join) - - @combinations.generate( - combinations.combine( - mode=['graph'], - strategy_cls=[collective_strategy.CollectiveAllReduceStrategy], - required_gpus=[0, 1])) - def test_sgd_optimizer_v1_v2_comparison(self, strategy_cls): - self.run_optimizer_comparison_with_simple_bias_model( - strategy_cls, gradient_descent.SGD, - gradient_descent_v1.GradientDescentOptimizer) - - -if __name__ == '__main__': - with test.mock.patch.object(sys, 'exit', os._exit): - test.main() diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py index b5d1d31219b..0d1bdc42281 100644 --- a/tensorflow/python/keras/engine/base_layer.py +++ b/tensorflow/python/keras/engine/base_layer.py @@ -2578,10 +2578,6 @@ class TensorFlowOpLayer(Layer): if value is not None: constant = constant_op.constant(value, name=node_def.input[index]) inputs.insert(index, constant) - # Check for case where first input should be a list of Tensors. - if 'N' in node_def.attr: - num_tensors = node_def.attr['N'].i - inputs = [inputs[:num_tensors]] + inputs[num_tensors:] c_op = ops._create_c_op(graph, node_def, inputs, control_inputs=[]) op = graph._create_op_from_tf_operation(c_op) op._control_flow_post_processing() diff --git a/tensorflow/python/keras/engine/base_layer_test.py b/tensorflow/python/keras/engine/base_layer_test.py index 913464ae479..02433be946d 100644 --- a/tensorflow/python/keras/engine/base_layer_test.py +++ b/tensorflow/python/keras/engine/base_layer_test.py @@ -485,9 +485,15 @@ class BaseLayerTest(keras_parameterized.TestCase): add4 = keras.layers.Add()([inputs, inputs]) model = keras.models.Model( inputs=[inputs], outputs=[add1, add2, add3, add4]) - self.assertEqual( - [l.name for l in model.layers], - ['input_1', 'tf_op_layer_add', 'add', 'tf_op_layer_add_2', 'add_1']) + actual_names = [l.name for l in model.layers] + graph_names = [ + 'input_1', 'tf_op_layer_AddV2', 'add', 'tf_op_layer_AddV2_1', 'add_1' + ] + eager_names = [ + 'input_1', 'tf_op_layer_add', 'add', 'tf_op_layer_add_2', 'add_1' + ] + for actual, eager, graph in zip(actual_names, graph_names, eager_names): + self.assertIn(actual, {eager, graph}) def test_add_trainable_weight_on_frozen_layer(self): @@ -688,8 +694,7 @@ class SymbolicSupportTest(test.TestCase): if hasattr(e, 'ag_error_metadata'): self.assertIn('easily_identifiable_name', str(e)) # See ErrorMetadataBase in autograph/pyct/errors.py - # Topmost frame corresponds to `call` itself. - function_name = e.ag_error_metadata.translated_stack[-2].function_name + function_name = e.ag_error_metadata.translated_stack[-1].function_name else: tb = traceback.extract_tb(sys.exc_info()[2]) last_entry = tb[-1] diff --git a/tensorflow/python/keras/engine/base_layer_utils.py b/tensorflow/python/keras/engine/base_layer_utils.py index a93c1c26600..f16f7d16284 100644 --- a/tensorflow/python/keras/engine/base_layer_utils.py +++ b/tensorflow/python/keras/engine/base_layer_utils.py @@ -245,6 +245,7 @@ def _create_keras_history_helper(tensors, processed_ops, created_layers): else: with ops.init_scope(): constants[i] = backend.function([], op_input)([]) + layer_inputs = unnest_if_single_tensor(layer_inputs) processed_ops, created_layers = _create_keras_history_helper( layer_inputs, processed_ops, created_layers) name = op.name @@ -258,6 +259,17 @@ def _create_keras_history_helper(tensors, processed_ops, created_layers): return processed_ops, created_layers +def unnest_if_single_tensor(input_tensors): + # Preserve compatibility with older configs + flat_input_tensors = nest.flatten(input_tensors) + # If this is a single element but not a dict, unwrap. If this is a dict, + # assume the first layer expects a dict (as is the case with a + # DenseFeatures layer); pass through. + if not isinstance(input_tensors, dict) and len(flat_input_tensors) == 1: + input_tensors = flat_input_tensors[0] + return input_tensors + + def needs_keras_history(tensors, ignore_call_context=False): """Check if any Tensors need to be wrapped in TensorFlowOpLayers. diff --git a/tensorflow/python/keras/engine/compile_utils.py b/tensorflow/python/keras/engine/compile_utils.py new file mode 100644 index 00000000000..a2eb00c8679 --- /dev/null +++ b/tensorflow/python/keras/engine/compile_utils.py @@ -0,0 +1,173 @@ +# Copyright 2019 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. +# ============================================================================== +"""Utilites for `Model.compile`.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import copy + +from tensorflow.python.keras import losses as losses_mod +from tensorflow.python.keras.utils import losses_utils +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops.losses import util as tf_losses_utils +from tensorflow.python.util import nest + + +class LossesContainer(object): + """A container class for losses passed to `Model.compile`.""" + + def __init__(self, losses, loss_weights=None, output_names=None): + + # Used only with Functional API models to flatten losses passed as a dict + # into a list using the Model's named outputs. + if output_names: + losses = map_to_outputs(output_names, losses) + loss_weights = map_to_outputs(output_names, loss_weights) + + self._losses = losses + self._loss_weights = loss_weights + self._built = False + + def _build(self, y_pred): + """One-time setup of loss objects.""" + # Broadcast single config values to apply to each output. + if not nest.is_sequence(self._losses): + self._losses = nest.map_structure(lambda output: self._losses, y_pred) + if not nest.is_sequence(self._loss_weights): + self._loss_weights = nest.map_structure(lambda output: self._loss_weights, + y_pred) + + self._losses = nest.map_structure(self._get_loss_object, self._losses) + + # Now that structures have been checked, it is safe to flatten. + self._losses = nest.flatten(self._losses) + self._loss_weights = nest.flatten(self._loss_weights) + self._built = True + + def __call__(self, y_true, y_pred, sample_weight=None): + """Computes the overall loss. + + Arguments: + y_true: An arbitrary structure of Tensors representing the ground truth. + y_pred: An arbitrary structure of Tensors representing a Model's outputs. + sample_weight: An arbitrary structure of Tensors representing the + per-sample loss weights. If one Tensor is passed, it is used for all + losses. If multiple Tensors are passed, the structure should match + `y_pred`. + + Returns: + Tuple of `(total_loss, per_output_loss_list)` + """ + if not self._built: + self._build(y_pred) + + y_true = nest.flatten(y_true) + y_pred = nest.flatten(y_pred) + + # TODO(omalleyt): Remove ambiguity here. + # This is currently needed to support passing only 1 loss and 1 target + # to a Functional Model with multiple outputs. However, this is + # ambiguous, especially with subclass, and we should reconsider how we + # support this. + if len(y_true) == 1 and len(y_pred) > 1: + y_true = y_true * len(y_pred) + + sample_weight = nest.flatten(sample_weight) + # Allows passing one sample-weight array for all outputs. + if len(sample_weight) == 1 and len(y_pred) > 1: + sample_weight = sample_weight * len(y_pred) + + loss_values = [] + metric_loss_values = [] # The loss value passed on to `Mean` metrics. + zip_args = (y_true, y_pred, sample_weight, self._losses, self._loss_weights) + for y_t, y_p, sw, loss_obj, loss_weight in zip(*zip_args): + if loss_obj is None: # Ok to have no loss for an output. + continue + + y_t = math_ops.cast(y_t, y_p.dtype) + if sw is not None: + sw = math_ops.cast(sw, y_p.dtype) + + # Handle Keras mask on outputs. + mask = getattr(y_p, '_keras_mask', None) + if mask is not None: + mask = math_ops.cast(mask, y_p.dtype) + if sw is not None: + mask, _, sw = ( + tf_losses_utils.squeeze_or_expand_dimensions( + mask, sample_weight=sw)) + sw *= mask + else: + sw = mask + + loss_value = loss_obj(y_t, y_p, sample_weight=sw) + if loss_weight is not None: + loss_value *= loss_weight + metric_loss_values.append(loss_value) + + # TODO(omalleyt): Should this be in the `Loss` class? + if (loss_obj.reduction == losses_utils.ReductionV2.SUM_OVER_BATCH_SIZE or + loss_obj.reduction == losses_utils.ReductionV2.AUTO): + loss_value = losses_utils.scale_loss_for_distribution(loss_value) + loss_values.append(loss_value) + + # Ok for a model to have no compiled loss. + total_loss = math_ops.add_n( + loss_values) if loss_values else array_ops.zeros((1,)) + + # TODO(omalleyt): Don't return per-output losses once MetricsContainer + # handles this. + return total_loss, metric_loss_values + + def _get_loss_object(self, loss): + """Returns a `Loss` object. + + Converts the user-supplied loss to a `Loss` object. Also allows + `SUM_OVER_BATCH_SIZE` reduction to be used for this loss. + + Arguments: + loss: A string, function, or `Loss` object. + + Returns: + A `Loss` object. + """ + if loss is None: + return None # Ok to have no loss for an output. + + # TODO(omalleyt): Handle special casing for crossentropy. + loss = losses_mod.get(loss) + if not isinstance(loss, losses_mod.Loss): + loss = losses_mod.LossFunctionWrapper(loss) + # Allow AUTO and SUM_OVER_BATCH_SIZE reductions. + # TODO(omalleyt): Can we reconcile CTL and built-in loss reductions? + loss._allow_sum_over_batch_size = True # pylint: disable=protected-access + return loss + + +def map_to_outputs(output_names, struct): + """Map losses/metrics to outputs in the Functional API.""" + # Used only for Functional API Models: allows users to specify + # metrics/losses using a dict with output names as keys. + if isinstance(struct, dict): + struct = copy.copy(struct) + new_struct = [struct.pop(name, None) for name in output_names] + if struct: + raise ValueError('Found unexpected keys that do not correspond ' + 'to any Model output: {}. Expected: {}'.format( + struct.keys(), output_names)) + return new_struct + return struct diff --git a/tensorflow/python/keras/engine/compile_utils_test.py b/tensorflow/python/keras/engine/compile_utils_test.py new file mode 100644 index 00000000000..5535c34296e --- /dev/null +++ b/tensorflow/python/keras/engine/compile_utils_test.py @@ -0,0 +1,158 @@ +# Copyright 2019 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 compile utitilies.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.framework import ops +from tensorflow.python.keras import keras_parameterized +from tensorflow.python.keras.engine import compile_utils +from tensorflow.python.ops import array_ops +from tensorflow.python.platform import test + + +class LossesContainerTest(keras_parameterized.TestCase): + + def test_single_loss(self): + loss_container = compile_utils.LossesContainer('mse') + y_t, y_p = array_ops.ones((10, 5)), array_ops.zeros((10, 5)) + total_loss, output_losses = self.evaluate(loss_container(y_t, y_p)) + + self.assertTrue(loss_container._built) + self.assertLen(loss_container._losses, 1) + self.assertEqual(total_loss, 1.) + self.assertLen(output_losses, 1) + + def test_loss_list(self): + loss_container = compile_utils.LossesContainer(['mse', 'mae'], [1, 0.5]) + + y_t = [array_ops.ones((10, 1)), array_ops.zeros((10, 1))] + y_p = [array_ops.ones((10, 1)), array_ops.ones((10, 1))] + sw = ops.convert_to_tensor([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + total_loss, output_losses = self.evaluate( + loss_container(y_t, y_p, sample_weight=sw)) + + self.assertLen(loss_container._losses, 2) + self.assertEqual(total_loss, 0.25) + self.assertLen(output_losses, 2) + self.assertEqual(output_losses[0], 0) + self.assertEqual(output_losses[1], 0.25) + + def test_loss_dict(self): + loss_container = compile_utils.LossesContainer( + { + 'out1': 'mse', + 'out2': 'mae' + }, { + 'out1': 1, + 'out2': 0.5 + }) + + y_t = {'out1': array_ops.ones((10, 1)), 'out2': array_ops.zeros((10, 1))} + y_p = {'out1': array_ops.ones((10, 1)), 'out2': array_ops.ones((10, 1))} + sw = ops.convert_to_tensor([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + total_loss, output_losses = self.evaluate( + loss_container(y_t, y_p, sample_weight=sw)) + + self.assertLen(loss_container._losses, 2) + self.assertEqual(total_loss, 0.25) + self.assertLen(output_losses, 2) + self.assertEqual(output_losses[0], 0) + self.assertEqual(output_losses[1], 0.25) + + def test_loss_partial_dict_with_output_names(self): + loss_container = compile_utils.LossesContainer( + {'out2': 'mae'}, {'out2': 1.}, output_names=['out1', 'out2']) + + y_t = [array_ops.ones((10, 1)), array_ops.zeros((10, 1))] + y_p = [array_ops.ones((10, 1)), array_ops.ones((10, 1))] + sw = ops.convert_to_tensor([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + total_loss, output_losses = self.evaluate( + loss_container(y_t, y_p, sample_weight=sw)) + + self.assertEqual(total_loss, 0.5) + self.assertLen(output_losses, 1) + self.assertEqual(output_losses[0], 0.5) + + def test_loss_dict_with_nones(self): + loss_container = compile_utils.LossesContainer({ + 'out1': None, + 'out2': 'mae' + }) + + y_t = [array_ops.ones((10, 1)), array_ops.zeros((10, 1))] + y_p = [array_ops.ones((10, 1)), array_ops.ones((10, 1))] + sw = ops.convert_to_tensor([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + total_loss, output_losses = self.evaluate( + loss_container(y_t, y_p, sample_weight=sw)) + + self.assertEqual(total_loss, 0.5) + self.assertLen(output_losses, 1) + self.assertEqual(output_losses[0], 0.5) + + def test_nested_structure(self): + loss_container = compile_utils.LossesContainer( + { + 'b': ['mse', None], + 'a': 'mae' + }, loss_weights={ + 'b': [0.5, 0], + 'a': 1 + }) + + y_t = { + 'b': [array_ops.ones((10, 1)), + array_ops.zeros((10, 1))], + 'a': array_ops.zeros((10, 1)) + } + y_p = { + 'b': [array_ops.zeros((10, 1)), + array_ops.zeros((10, 1))], + 'a': array_ops.ones((10, 1)) + } + sw = ops.convert_to_tensor([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + total_loss, output_losses = self.evaluate( + loss_container(y_t, y_p, sample_weight=sw)) + + self.assertEqual(total_loss, 0.75) + self.assertLen(output_losses, 2) + self.assertEqual(output_losses[0], 0.5) + self.assertEqual(output_losses[1], 0.25) + + def test_broadcast_single_loss(self): + loss_container = compile_utils.LossesContainer('mse') + + y_t = [array_ops.ones((10, 1)), array_ops.zeros((10, 1))] + y_p = [array_ops.ones((10, 1)), array_ops.ones((10, 1))] + sw = ops.convert_to_tensor([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) + + total_loss, output_losses = self.evaluate( + loss_container(y_t, y_p, sample_weight=sw)) + + self.assertEqual(total_loss, 0.5) + self.assertLen(output_losses, 2) + self.assertEqual(output_losses[0], 0.) + self.assertEqual(output_losses[1], 0.5) + + +if __name__ == '__main__': + test.main() diff --git a/tensorflow/python/keras/engine/data_adapter.py b/tensorflow/python/keras/engine/data_adapter.py index 50db978e77a..e5271bbfad8 100644 --- a/tensorflow/python/keras/engine/data_adapter.py +++ b/tensorflow/python/keras/engine/data_adapter.py @@ -266,7 +266,7 @@ class TensorLikeDataAdapter(DataAdapter): msg = "Data cardinality is ambiguous:\n" for label, data in zip(["x", "y", "sample_weight"], inputs): msg += " {} sizes: {}\n".format( - label, ", ".join([str(i.shape[0]) for i in nest.flatten(data)])) + label, ", ".join(str(i.shape[0]) for i in nest.flatten(data))) msg += "Please provide data which shares the same first dimension." raise ValueError(msg) num_samples = num_samples.pop() @@ -734,6 +734,10 @@ class GeneratorDataAdapter(DataAdapter): def __init__(self, x, y=None, sample_weights=None, standardize_function=None, workers=1, use_multiprocessing=False, max_queue_size=10, **kwargs): + # Generators should never shuffle as exhausting the generator in order to + # shuffle the batches is inefficient. + kwargs.pop("shuffle", None) + if not is_none_or_empty(y): raise ValueError("`y` argument is not supported when using " "python generator as input.") @@ -766,9 +770,6 @@ class GeneratorDataAdapter(DataAdapter): if standardize_function is not None: dataset = standardize_function(dataset) - if kwargs.get("shuffle", False) and self.get_size() is not None: - dataset = dataset.shuffle(self.get_size()) - if workers == 1 and not use_multiprocessing: dataset = dataset.prefetch(1) diff --git a/tensorflow/python/keras/engine/data_adapter_test.py b/tensorflow/python/keras/engine/data_adapter_test.py index 63d04d97ad6..1b6c92f46fa 100644 --- a/tensorflow/python/keras/engine/data_adapter_test.py +++ b/tensorflow/python/keras/engine/data_adapter_test.py @@ -719,6 +719,16 @@ class GeneratorDataAdapterTest(DataAdapterTestBase): self.adapter_cls( self.generator_input, sample_weights=self.generator_input) + def test_not_shuffled(self): + def generator(): + for i in range(10): + yield np.ones((1, 1)) * i + + adapter = self.adapter_cls(generator(), shuffle=True) + with context.eager_mode(): + for i, data in enumerate(adapter.get_dataset()): + self.assertEqual(i, data[0].numpy().flatten()) + class KerasSequenceAdapterTest(DataAdapterTestBase): diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py index 8b8bbd902fd..7aebdb24e51 100644 --- a/tensorflow/python/keras/engine/network.py +++ b/tensorflow/python/keras/engine/network.py @@ -1841,13 +1841,7 @@ def reconstruct_from_config(config, custom_objects=None, created_layers=None): # Call layer on its inputs, thus creating the node # and building the layer if needed. if input_tensors is not None: - # Preserve compatibility with older configs - flat_input_tensors = nest.flatten(input_tensors) - # If this is a single element but not a dict, unwrap. If this is a dict, - # assume the first layer expects a dict (as is the case with a - # DenseFeatures layer); pass through. - if not isinstance(input_tensors, dict) and len(flat_input_tensors) == 1: - input_tensors = flat_input_tensors[0] + input_tensors = base_layer_utils.unnest_if_single_tensor(input_tensors) output_tensors = layer(input_tensors, **kwargs) # Update node index map. diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py index 78c4feb7be9..b04ffacc7c1 100644 --- a/tensorflow/python/keras/engine/training.py +++ b/tensorflow/python/keras/engine/training.py @@ -242,6 +242,12 @@ class Model(network.Network, version_utils.VersionSelector): You can also pass a list (len = len(outputs)) of lists of metrics such as `metrics=[['accuracy'], ['accuracy', 'mse']]` or `metrics=['accuracy', ['accuracy', 'mse']]`. + When you pass the strings 'accuracy' or 'acc', we convert this to + one of `tf.keras.metrics.BinaryAccuracy`, + `tf.keras.metrics.CategoricalAccuracy`, + `tf.keras.metrics.SparseCategoricalAccuracy` based on the loss + function used and the model output shape. We do a similar conversion + for the strings 'crossentropy' and 'ce' as well. loss_weights: Optional list or dictionary specifying scalar coefficients (Python floats) to weight the loss contributions of different model outputs. @@ -542,14 +548,14 @@ class Model(network.Network, version_utils.VersionSelector): - tuple `(x_val, y_val)` of Numpy arrays or tensors - tuple `(x_val, y_val, val_sample_weights)` of Numpy arrays - dataset - + For the first two cases, `batch_size` must be provided. For the last case, `validation_steps` could be provided. shuffle: Boolean (whether to shuffle the training data - before each epoch) or str (for 'batch'). - 'batch' is a special option for dealing with the - limitations of HDF5 data; it shuffles in batch-sized chunks. - Has no effect when `steps_per_epoch` is not `None`. + before each epoch) or str (for 'batch'). This argument is ignored + when `x` is a generator. 'batch' is a special option for dealing + with the limitations of HDF5 data; it shuffles in batch-sized + chunks. Has no effect when `steps_per_epoch` is not `None`. class_weight: Optional dictionary mapping class indices (integers) to a weight (float) value, used for weighting the loss function (during training only). @@ -1313,7 +1319,7 @@ class Model(network.Network, version_utils.VersionSelector): """ if not self._is_compiled: return - if sample_weights and any([s is not None for s in sample_weights]): + if sample_weights and any(s is not None for s in sample_weights): for endpoint in self._training_endpoints: endpoint.sample_weight_mode = ( endpoint.sample_weight_mode or 'samplewise') @@ -1324,8 +1330,8 @@ class Model(network.Network, version_utils.VersionSelector): def _recompile_weights_loss_and_weighted_metrics(self): if not self._is_compiled: return False - recompile = any([e.sample_weights_mismatch() - for e in self._training_endpoints]) + recompile = any( + e.sample_weights_mismatch() for e in self._training_endpoints) if recompile: self._compile_weights_loss_and_weighted_metrics() diff --git a/tensorflow/python/keras/engine/training_test.py b/tensorflow/python/keras/engine/training_test.py index 1209215c1b6..2f268b957c8 100644 --- a/tensorflow/python/keras/engine/training_test.py +++ b/tensorflow/python/keras/engine/training_test.py @@ -1505,6 +1505,38 @@ class TrainingTest(keras_parameterized.TestCase): history = model.fit([x, y], y, batch_size=3, epochs=5) self.assertAllClose(history.history['loss'], [4., 3.6, 3.2, 2.8, 2.4], 1e-3) + @keras_parameterized.run_all_keras_modes + def test_add_loss_with_sample_weight_correctness(self): + + class Bias(keras.layers.Layer): + + def build(self, input_shape): + self.bias = self.add_variable('bias', (1,), initializer='zeros') + + def call(self, inputs): + return inputs + self.bias + + inputs = keras.Input(shape=(1,)) + targets = keras.Input(shape=(1,)) + sw = keras.Input(shape=(1,)) + outputs = Bias()(inputs) + model = keras.Model([inputs, targets, sw], outputs) + + model.add_loss(2 * math_ops.reduce_mean( + sw * keras.losses.mean_absolute_error(targets, outputs))) + model.add_loss(keras.losses.MeanAbsoluteError()(targets, outputs, sw)) + model.compile( + keras.optimizer_v2.gradient_descent.SGD(0.025), + loss=keras.losses.MeanAbsoluteError(), + run_eagerly=testing_utils.should_run_eagerly(), + experimental_run_tf_function=testing_utils.should_run_tf_function()) + + x = np.array([[0.], [1.], [2.]]) + y = np.array([[0.5], [2.], [3.5]]) + w = np.array([1.25, 0.5, 1.25]) + history = model.fit([x, y, w], y, batch_size=3, epochs=5, sample_weight=w) + self.assertAllClose(history.history['loss'], [4., 3.6, 3.2, 2.8, 2.4], 1e-3) + @keras_parameterized.run_all_keras_modes def test_unconditional_add_loss_correctness(self): diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py index ca576935a64..3b42bff8d30 100644 --- a/tensorflow/python/keras/engine/training_utils.py +++ b/tensorflow/python/keras/engine/training_utils.py @@ -253,7 +253,7 @@ class SliceAggregator(Aggregator): shape = (self.num_samples,) + batch_element.shape[1:] dtype = batch_element.dtype if isinstance(batch_element, ops.EagerTensor): - dtype = dtype.as_numpy_dtype() + dtype = dtype.as_numpy_dtype self.results = np.empty(shape=shape, dtype=dtype) diff --git a/tensorflow/python/keras/engine/training_v1.py b/tensorflow/python/keras/engine/training_v1.py index 69acc360054..eeb60784ba8 100644 --- a/tensorflow/python/keras/engine/training_v1.py +++ b/tensorflow/python/keras/engine/training_v1.py @@ -1492,7 +1492,7 @@ class Model(training_lib.Model): """ if not self._is_compiled: return - if sample_weights and any([s is not None for s in sample_weights]): + if sample_weights and any(s is not None for s in sample_weights): for endpoint in self._training_endpoints: endpoint.sample_weight_mode = ( endpoint.sample_weight_mode or 'samplewise') diff --git a/tensorflow/python/keras/engine/training_v2_utils.py b/tensorflow/python/keras/engine/training_v2_utils.py index a732dba60f2..b7eb1b123b6 100644 --- a/tensorflow/python/keras/engine/training_v2_utils.py +++ b/tensorflow/python/keras/engine/training_v2_utils.py @@ -119,7 +119,9 @@ def _make_on_batch_function(model, mode): func = model if not model.run_eagerly: - func = def_function.function(func) + # Pass `experimental_relax_shapes` to avoid retracing for dynamic batch + # size, variable length sequences, etc. + func = def_function.function(func, experimental_relax_shapes=True) return func diff --git a/tensorflow/python/keras/estimator/__init__.py b/tensorflow/python/keras/estimator/__init__.py index 19e7410cb8c..725ffe41f9d 100644 --- a/tensorflow/python/keras/estimator/__init__.py +++ b/tensorflow/python/keras/estimator/__init__.py @@ -25,8 +25,8 @@ from tensorflow.python.util.tf_export import keras_export # As long as you depend //third_party/py/tensorflow:tensorflow target # everything will work as normal. -_model_to_estiamtor_usage_gauge = monitoring.BoolGauge( - '/tensorflow/api/keras/model_to_estiamtor', +_model_to_estimator_usage_gauge = monitoring.BoolGauge( + '/tensorflow/api/keras/model_to_estimator', 'Whether tf.keras.estimator.model_to_estimator() is called.', 'version') @@ -106,7 +106,7 @@ def model_to_estimator( raise NotImplementedError( 'tf.keras.estimator.model_to_estimator function not available in your ' 'installation.') - _model_to_estiamtor_usage_gauge.get_cell('v1').set(True) + _model_to_estimator_usage_gauge.get_cell('v1').set(True) return keras_lib.model_to_estimator( # pylint:disable=unexpected-keyword-arg keras_model=keras_model, keras_model_path=keras_model_path, @@ -191,7 +191,7 @@ def model_to_estimator_v2( raise NotImplementedError( 'tf.keras.estimator.model_to_estimator function not available in your ' 'installation.') - _model_to_estiamtor_usage_gauge.get_cell('v2').set(True) + _model_to_estimator_usage_gauge.get_cell('v2').set(True) return keras_lib.model_to_estimator( # pylint:disable=unexpected-keyword-arg keras_model=keras_model, keras_model_path=keras_model_path, diff --git a/tensorflow/python/keras/layers/__init__.py b/tensorflow/python/keras/layers/__init__.py index 87dfa34f932..07cb1bdf1b3 100644 --- a/tensorflow/python/keras/layers/__init__.py +++ b/tensorflow/python/keras/layers/__init__.py @@ -18,8 +18,11 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +from tensorflow.python import tf2 + # Generic layers. # pylint: disable=g-bad-import-order +# pylint: disable=g-import-not-at-top from tensorflow.python.keras.engine.input_layer import Input from tensorflow.python.keras.engine.input_layer import InputLayer from tensorflow.python.keras.engine.input_spec import InputSpec @@ -27,10 +30,20 @@ from tensorflow.python.keras.engine.base_layer import Layer from tensorflow.python.keras.engine.base_preprocessing_layer import PreprocessingLayer # Preprocessing layers. -from tensorflow.python.keras.layers.preprocessing.normalization import Normalization -from tensorflow.python.keras.layers.preprocessing.normalization_v1 import Normalization as NormalizationV1 -from tensorflow.python.keras.layers.preprocessing.text_vectorization import TextVectorization -from tensorflow.python.keras.layers.preprocessing.text_vectorization_v1 import TextVectorization as TextVectorizationV1 +if tf2.enabled(): + from tensorflow.python.keras.layers.preprocessing.normalization import Normalization + from tensorflow.python.keras.layers.preprocessing.normalization_v1 import Normalization as NormalizationV1 + NormalizationV2 = Normalization + from tensorflow.python.keras.layers.preprocessing.text_vectorization import TextVectorization + from tensorflow.python.keras.layers.preprocessing.text_vectorization_v1 import TextVectorization as TextVectorizationV1 + TextVectorizationV2 = TextVectorization +else: + from tensorflow.python.keras.layers.preprocessing.normalization_v1 import Normalization + from tensorflow.python.keras.layers.preprocessing.normalization import Normalization as NormalizationV2 + NormalizationV1 = Normalization + from tensorflow.python.keras.layers.preprocessing.text_vectorization_v1 import TextVectorization + from tensorflow.python.keras.layers.preprocessing.text_vectorization import TextVectorization as TextVectorizationV2 + TextVectorizationV1 = TextVectorization # Advanced activations. from tensorflow.python.keras.layers.advanced_activations import LeakyReLU @@ -121,8 +134,14 @@ from tensorflow.python.keras.layers.noise import GaussianDropout # Normalization layers. from tensorflow.python.keras.layers.normalization import LayerNormalization -from tensorflow.python.keras.layers.normalization import BatchNormalization -from tensorflow.python.keras.layers.normalization_v2 import BatchNormalization as BatchNormalizationV2 +if tf2.enabled(): + from tensorflow.python.keras.layers.normalization_v2 import BatchNormalization + from tensorflow.python.keras.layers.normalization import BatchNormalization as BatchNormalizationV1 + BatchNormalizationV2 = BatchNormalization +else: + from tensorflow.python.keras.layers.normalization import BatchNormalization + from tensorflow.python.keras.layers.normalization_v2 import BatchNormalization as BatchNormalizationV2 + BatchNormalizationV1 = BatchNormalization # Kernelized layers. from tensorflow.python.keras.layers.kernelized import RandomFourierFeatures @@ -163,14 +182,32 @@ from tensorflow.python.keras.layers.recurrent import SimpleRNNCell from tensorflow.python.keras.layers.recurrent import PeepholeLSTMCell from tensorflow.python.keras.layers.recurrent import SimpleRNN -from tensorflow.python.keras.layers.recurrent import GRU -from tensorflow.python.keras.layers.recurrent import GRUCell -from tensorflow.python.keras.layers.recurrent import LSTM -from tensorflow.python.keras.layers.recurrent import LSTMCell -from tensorflow.python.keras.layers.recurrent_v2 import GRU as GRU_v2 -from tensorflow.python.keras.layers.recurrent_v2 import GRUCell as GRUCell_v2 -from tensorflow.python.keras.layers.recurrent_v2 import LSTM as LSTM_v2 -from tensorflow.python.keras.layers.recurrent_v2 import LSTMCell as LSTMCell_v2 +if tf2.enabled(): + from tensorflow.python.keras.layers.recurrent_v2 import GRU + from tensorflow.python.keras.layers.recurrent_v2 import GRUCell + from tensorflow.python.keras.layers.recurrent_v2 import LSTM + from tensorflow.python.keras.layers.recurrent_v2 import LSTMCell + from tensorflow.python.keras.layers.recurrent import GRU as GRUV1 + from tensorflow.python.keras.layers.recurrent import GRUCell as GRUCellV1 + from tensorflow.python.keras.layers.recurrent import LSTM as LSTMV1 + from tensorflow.python.keras.layers.recurrent import LSTMCell as LSTMCellV1 + GRUV2 = GRU + GRUCellV2 = GRUCell + LSTMV2 = LSTM + LSTMCellV2 = LSTMCell +else: + from tensorflow.python.keras.layers.recurrent import GRU + from tensorflow.python.keras.layers.recurrent import GRUCell + from tensorflow.python.keras.layers.recurrent import LSTM + from tensorflow.python.keras.layers.recurrent import LSTMCell + from tensorflow.python.keras.layers.recurrent_v2 import GRU as GRUV2 + from tensorflow.python.keras.layers.recurrent_v2 import GRUCell as GRUCellV2 + from tensorflow.python.keras.layers.recurrent_v2 import LSTM as LSTMV2 + from tensorflow.python.keras.layers.recurrent_v2 import LSTMCell as LSTMCellV2 + GRUV1 = GRU + GRUCellV1 = GRUCell + LSTMV1 = LSTM + LSTMCellV1 = LSTMCell # Convolutional-recurrent layers. from tensorflow.python.keras.layers.convolutional_recurrent import ConvLSTM2D diff --git a/tensorflow/python/keras/layers/convolutional.py b/tensorflow/python/keras/layers/convolutional.py index fd7402a28c3..b4cb8fe5f42 100644 --- a/tensorflow/python/keras/layers/convolutional.py +++ b/tensorflow/python/keras/layers/convolutional.py @@ -71,8 +71,8 @@ class Conv(Layer): data_format: A string, one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, ..., channels)` while `channels_first` corresponds to - inputs with shape `(batch, channels, ...)`. + `(batch_size, ..., channels)` while `channels_first` corresponds to + inputs with shape `(batch_size, channels, ...)`. dilation_rate: An integer or tuple/list of n integers, specifying the dilation rate to use for dilated convolution. Currently, specifying any `dilation_rate` value != 1 is @@ -506,9 +506,9 @@ class Conv2D(Conv): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, height, width, channels)` while `channels_first` + `(batch_size, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, height, width)`. + `(batch_size, channels, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -644,9 +644,9 @@ class Conv3D(Conv): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, spatial_dim1, spatial_dim2, spatial_dim3, channels)` + `(batch_size, spatial_dim1, spatial_dim2, spatial_dim3, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. + `(batch_size, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -785,9 +785,9 @@ class Conv2DTranspose(Conv2D): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, height, width, channels)` while `channels_first` + `(batch_size, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, height, width)`. + `(batch_size, channels, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -1075,9 +1075,9 @@ class Conv3DTranspose(Conv3D): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, depth, height, width, channels)` while `channels_first` + `(batch_size, depth, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, depth, height, width)`. + `(batch_size, channels, depth, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -1361,8 +1361,8 @@ class SeparableConv(Conv): data_format: A string, one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, ..., channels)` while `channels_first` corresponds to - inputs with shape `(batch, channels, ...)`. + `(batch_size, ..., channels)` while `channels_first` corresponds to + inputs with shape `(batch_size, channels, ...)`. dilation_rate: An integer or tuple/list of 2 integers, specifying the dilation rate to use for dilated convolution. Can be a single integer to specify the same value for @@ -1565,8 +1565,8 @@ class SeparableConv1D(SeparableConv): data_format: A string, one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, length, channels)` while `channels_first` corresponds to - inputs with shape `(batch, channels, length)`. + `(batch_size, length, channels)` while `channels_first` corresponds to + inputs with shape `(batch_size, channels, length)`. dilation_rate: A single integer, specifying the dilation rate to use for dilated convolution. Currently, specifying any `dilation_rate` value != 1 is @@ -1751,9 +1751,9 @@ class SeparableConv2D(SeparableConv): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, height, width, channels)` while `channels_first` + `(batch_size, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, height, width)`. + `(batch_size, channels, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -1915,9 +1915,9 @@ class DepthwiseConv2D(Conv2D): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, height, width, channels)` while `channels_first` + `(batch_size, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, height, width)`. + `(batch_size, channels, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be 'channels_last'. @@ -1944,15 +1944,15 @@ class DepthwiseConv2D(Conv2D): Input shape: 4D tensor with shape: - `[batch, channels, rows, cols]` if data_format='channels_first' + `[batch_size, channels, rows, cols]` if data_format='channels_first' or 4D tensor with shape: - `[batch, rows, cols, channels]` if data_format='channels_last'. + `[batch_size, rows, cols, channels]` if data_format='channels_last'. Output shape: 4D tensor with shape: - `[batch, filters, new_rows, new_cols]` if data_format='channels_first' + `[batch_size, filters, new_rows, new_cols]` if data_format='channels_first' or 4D tensor with shape: - `[batch, new_rows, new_cols, filters]` if data_format='channels_last'. + `[batch_size, new_rows, new_cols, filters]` if data_format='channels_last'. `rows` and `cols` values might have changed due to padding. Returns: @@ -2097,14 +2097,35 @@ class UpSampling1D(Layer): Repeats each temporal step `size` times along the time axis. + Examples: + + >>> input_shape = (2, 2, 3) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> print(x) + [[[ 0 1 2] + [ 3 4 5]] + [[ 6 7 8] + [ 9 10 11]]] + >>> y = tf.keras.layers.UpSampling1D(size=2)(x) + >>> print(y) + tf.Tensor( + [[[ 0 1 2] + [ 0 1 2] + [ 3 4 5] + [ 3 4 5]] + [[ 6 7 8] + [ 6 7 8] + [ 9 10 11] + [ 9 10 11]]], shape=(2, 4, 3), dtype=int64) + Arguments: size: Integer. Upsampling factor. Input shape: - 3D tensor with shape: `(batch, steps, features)`. + 3D tensor with shape: `(batch_size, steps, features)`. Output shape: - 3D tensor with shape: `(batch, upsampled_steps, features)`. + 3D tensor with shape: `(batch_size, upsampled_steps, features)`. """ def __init__(self, size=2, **kwargs): @@ -2134,6 +2155,27 @@ class UpSampling2D(Layer): Repeats the rows and columns of the data by `size[0]` and `size[1]` respectively. + Examples: + + >>> input_shape = (2, 2, 1, 3) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> print(x) + [[[[ 0 1 2]] + [[ 3 4 5]]] + [[[ 6 7 8]] + [[ 9 10 11]]]] + >>> y = tf.keras.layers.UpSampling2D(size=(1, 2))(x) + >>> print(y) + tf.Tensor( + [[[[ 0 1 2] + [ 0 1 2]] + [[ 3 4 5] + [ 3 4 5]]] + [[[ 6 7 8] + [ 6 7 8]] + [[ 9 10 11] + [ 9 10 11]]]], shape=(2, 2, 2, 3), dtype=int64) + Arguments: size: Int, or tuple of 2 integers. The upsampling factors for rows and columns. @@ -2141,9 +2183,9 @@ class UpSampling2D(Layer): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, height, width, channels)` while `channels_first` + `(batch_size, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, height, width)`. + `(batch_size, channels, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -2152,16 +2194,16 @@ class UpSampling2D(Layer): Input shape: 4D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, rows, cols, channels)` + `(batch_size, rows, cols, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, rows, cols)` + `(batch_size, channels, rows, cols)` Output shape: 4D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, upsampled_rows, upsampled_cols, channels)` + `(batch_size, upsampled_rows, upsampled_cols, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, upsampled_rows, upsampled_cols)` + `(batch_size, channels, upsampled_rows, upsampled_cols)` """ def __init__(self, @@ -2217,6 +2259,14 @@ class UpSampling3D(Layer): Repeats the 1st, 2nd and 3rd dimensions of the data by `size[0]`, `size[1]` and `size[2]` respectively. + Examples: + + >>> input_shape = (2, 1, 2, 1, 3) + >>> x = tf.constant(1, shape=input_shape) + >>> y = tf.keras.layers.UpSampling3D(size=2)(x) + >>> print(y.shape) + (2, 2, 4, 2, 3) + Arguments: size: Int, or tuple of 3 integers. The upsampling factors for dim1, dim2 and dim3. @@ -2224,9 +2274,9 @@ class UpSampling3D(Layer): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, spatial_dim1, spatial_dim2, spatial_dim3, channels)` + `(batch_size, spatial_dim1, spatial_dim2, spatial_dim3, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. + `(batch_size, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -2234,16 +2284,16 @@ class UpSampling3D(Layer): Input shape: 5D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, dim1, dim2, dim3, channels)` + `(batch_size, dim1, dim2, dim3, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, dim1, dim2, dim3)` + `(batch_size, channels, dim1, dim2, dim3)` Output shape: 5D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, upsampled_dim1, upsampled_dim2, upsampled_dim3, channels)` + `(batch_size, upsampled_dim1, upsampled_dim2, upsampled_dim3, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, upsampled_dim1, upsampled_dim2, upsampled_dim3)` + `(batch_size, channels, upsampled_dim1, upsampled_dim2, upsampled_dim3)` """ def __init__(self, size=(2, 2, 2), data_format=None, **kwargs): @@ -2287,6 +2337,31 @@ class UpSampling3D(Layer): class ZeroPadding1D(Layer): """Zero-padding layer for 1D input (e.g. temporal sequence). + Examples: + + >>> input_shape = (2, 2, 3) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> print(x) + [[[ 0 1 2] + [ 3 4 5]] + [[ 6 7 8] + [ 9 10 11]]] + >>> y = tf.keras.layers.ZeroPadding1D(padding=2)(x) + >>> print(y) + tf.Tensor( + [[[ 0 0 0] + [ 0 0 0] + [ 0 1 2] + [ 3 4 5] + [ 0 0 0] + [ 0 0 0]] + [[ 0 0 0] + [ 0 0 0] + [ 6 7 8] + [ 9 10 11] + [ 0 0 0] + [ 0 0 0]]], shape=(2, 6, 3), dtype=int64) + Arguments: padding: Int, or tuple of int (length 2), or dictionary. - If int: @@ -2297,10 +2372,10 @@ class ZeroPadding1D(Layer): the padding dimension (`(left_pad, right_pad)`). Input shape: - 3D tensor with shape `(batch, axis_to_pad, features)` + 3D tensor with shape `(batch_size, axis_to_pad, features)` Output shape: - 3D tensor with shape `(batch, padded_axis, features)` + 3D tensor with shape `(batch_size, padded_axis, features)` """ def __init__(self, padding=1, **kwargs): @@ -2331,6 +2406,29 @@ class ZeroPadding2D(Layer): This layer can add rows and columns of zeros at the top, bottom, left and right side of an image tensor. + Examples: + + >>> input_shape = (1, 1, 2, 2) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> print(x) + [[[[0 1] + [2 3]]]] + >>> y = tf.keras.layers.ZeroPadding2D(padding=1)(x) + >>> print(y) + tf.Tensor( + [[[[0 0] + [0 0] + [0 0] + [0 0]] + [[0 0] + [0 1] + [2 3] + [0 0]] + [[0 0] + [0 0] + [0 0] + [0 0]]]], shape=(1, 3, 4, 2), dtype=int64) + Arguments: padding: Int, or tuple of 2 ints, or tuple of 2 tuples of 2 ints. - If int: the same symmetric padding @@ -2346,9 +2444,9 @@ class ZeroPadding2D(Layer): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, height, width, channels)` while `channels_first` + `(batch_size, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, height, width)`. + `(batch_size, channels, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -2356,16 +2454,16 @@ class ZeroPadding2D(Layer): Input shape: 4D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, rows, cols, channels)` + `(batch_size, rows, cols, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, rows, cols)` + `(batch_size, channels, rows, cols)` Output shape: 4D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, padded_rows, padded_cols, channels)` + `(batch_size, padded_rows, padded_cols, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, padded_rows, padded_cols)` + `(batch_size, channels, padded_rows, padded_cols)` """ def __init__(self, padding=(1, 1), data_format=None, **kwargs): @@ -2430,6 +2528,14 @@ class ZeroPadding2D(Layer): class ZeroPadding3D(Layer): """Zero-padding layer for 3D data (spatial or spatio-temporal). + Examples: + + >>> input_shape = (1, 1, 2, 2, 3) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> y = tf.keras.layers.ZeroPadding3D(padding=2)(x) + >>> print(y.shape) + (1, 5, 6, 6, 3) + Arguments: padding: Int, or tuple of 3 ints, or tuple of 3 tuples of 2 ints. - If int: the same symmetric padding @@ -2446,9 +2552,9 @@ class ZeroPadding3D(Layer): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, spatial_dim1, spatial_dim2, spatial_dim3, channels)` + `(batch_size, spatial_dim1, spatial_dim2, spatial_dim3, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. + `(batch_size, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -2456,19 +2562,19 @@ class ZeroPadding3D(Layer): Input shape: 5D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, first_axis_to_pad, second_axis_to_pad, third_axis_to_pad, + `(batch_size, first_axis_to_pad, second_axis_to_pad, third_axis_to_pad, depth)` - If `data_format` is `"channels_first"`: - `(batch, depth, first_axis_to_pad, second_axis_to_pad, + `(batch_size, depth, first_axis_to_pad, second_axis_to_pad, third_axis_to_pad)` Output shape: 5D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, first_padded_axis, second_padded_axis, third_axis_to_pad, + `(batch_size, first_padded_axis, second_padded_axis, third_axis_to_pad, depth)` - If `data_format` is `"channels_first"`: - `(batch, depth, first_padded_axis, second_padded_axis, + `(batch_size, depth, first_padded_axis, second_padded_axis, third_axis_to_pad)` """ @@ -2550,6 +2656,23 @@ class Cropping1D(Layer): It crops along the time dimension (axis 1). + Examples: + + >>> input_shape = (2, 3, 2) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> print(x) + [[[ 0 1] + [ 2 3] + [ 4 5]] + [[ 6 7] + [ 8 9] + [10 11]]] + >>> y = tf.keras.layers.Cropping1D(cropping=1)(x) + >>> print(y) + tf.Tensor( + [[[2 3]] + [[8 9]]], shape=(2, 1, 2), dtype=int64) + Arguments: cropping: Int or tuple of int (length 2) How many units should be trimmed off at the beginning and end of @@ -2557,10 +2680,10 @@ class Cropping1D(Layer): If a single int is provided, the same value will be used for both. Input shape: - 3D tensor with shape `(batch, axis_to_crop, features)` + 3D tensor with shape `(batch_size, axis_to_crop, features)` Output shape: - 3D tensor with shape `(batch, cropped_axis, features)` + 3D tensor with shape `(batch_size, cropped_axis, features)` """ def __init__(self, cropping=(1, 1), **kwargs): @@ -2594,6 +2717,14 @@ class Cropping2D(Layer): It crops along spatial dimensions, i.e. height and width. + Examples: + + >>> input_shape = (2, 28, 28, 3) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> y = tf.keras.layers.Cropping2D(cropping=((2, 2), (4, 4)))(x) + >>> print(y.shape) + (2, 24, 20, 3) + Arguments: cropping: Int, or tuple of 2 ints, or tuple of 2 tuples of 2 ints. - If int: the same symmetric cropping @@ -2609,9 +2740,9 @@ class Cropping2D(Layer): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, height, width, channels)` while `channels_first` + `(batch_size, height, width, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, height, width)`. + `(batch_size, channels, height, width)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -2619,29 +2750,16 @@ class Cropping2D(Layer): Input shape: 4D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, rows, cols, channels)` + `(batch_size, rows, cols, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, rows, cols)` + `(batch_size, channels, rows, cols)` Output shape: 4D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, cropped_rows, cropped_cols, channels)` + `(batch_size, cropped_rows, cropped_cols, channels)` - If `data_format` is `"channels_first"`: - `(batch, channels, cropped_rows, cropped_cols)` - - Examples: - - ```python - # Crop the input 2D images or feature maps - model = Sequential() - model.add(Cropping2D(cropping=((2, 2), (4, 4)), - input_shape=(28, 28, 3))) - # now model.output_shape == (None, 24, 20, 3) - model.add(Conv2D(64, (3, 3), padding='same')) - model.add(Cropping2D(cropping=((2, 2), (2, 2)))) - # now model.output_shape == (None, 20, 16. 64) - ``` + `(batch_size, channels, cropped_rows, cropped_cols)` """ def __init__(self, cropping=((0, 0), (0, 0)), data_format=None, **kwargs): @@ -2724,6 +2842,14 @@ class Cropping2D(Layer): class Cropping3D(Layer): """Cropping layer for 3D data (e.g. spatial or spatio-temporal). + Examples: + + >>> input_shape = (2, 28, 28, 10, 3) + >>> x = np.arange(np.prod(input_shape)).reshape(input_shape) + >>> y = tf.keras.layers.Cropping3D(cropping=(2, 4, 2))(x) + >>> print(y.shape) + (2, 24, 20, 6, 3) + Arguments: cropping: Int, or tuple of 3 ints, or tuple of 3 tuples of 2 ints. - If int: the same symmetric cropping @@ -2738,9 +2864,9 @@ class Cropping3D(Layer): one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. `channels_last` corresponds to inputs with shape - `(batch, spatial_dim1, spatial_dim2, spatial_dim3, channels)` + `(batch_size, spatial_dim1, spatial_dim2, spatial_dim3, channels)` while `channels_first` corresponds to inputs with shape - `(batch, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. + `(batch_size, channels, spatial_dim1, spatial_dim2, spatial_dim3)`. It defaults to the `image_data_format` value found in your Keras config file at `~/.keras/keras.json`. If you never set it, then it will be "channels_last". @@ -2748,19 +2874,19 @@ class Cropping3D(Layer): Input shape: 5D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, first_axis_to_crop, second_axis_to_crop, third_axis_to_crop, + `(batch_size, first_axis_to_crop, second_axis_to_crop, third_axis_to_crop, depth)` - If `data_format` is `"channels_first"`: - `(batch, depth, first_axis_to_crop, second_axis_to_crop, + `(batch_size, depth, first_axis_to_crop, second_axis_to_crop, third_axis_to_crop)` Output shape: 5D tensor with shape: - If `data_format` is `"channels_last"`: - `(batch, first_cropped_axis, second_cropped_axis, third_cropped_axis, + `(batch_size, first_cropped_axis, second_cropped_axis, third_cropped_axis, depth)` - If `data_format` is `"channels_first"`: - `(batch, depth, first_cropped_axis, second_cropped_axis, + `(batch_size, depth, first_cropped_axis, second_cropped_axis, third_cropped_axis)` """ diff --git a/tensorflow/python/keras/layers/core.py b/tensorflow/python/keras/layers/core.py index ee44fcbd946..8f1e5a715a5 100644 --- a/tensorflow/python/keras/layers/core.py +++ b/tensorflow/python/keras/layers/core.py @@ -20,11 +20,13 @@ from __future__ import print_function import copy import sys +import textwrap import types as python_types import warnings import numpy as np +from tensorflow.python.eager import backprop from tensorflow.python.eager import context from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes @@ -47,6 +49,8 @@ from tensorflow.python.ops import nn from tensorflow.python.ops import sparse_ops from tensorflow.python.ops import standard_ops from tensorflow.python.ops import variable_scope +from tensorflow.python.platform import tf_logging +from tensorflow.python.training.tracking import base as trackable from tensorflow.python.util import nest from tensorflow.python.util import tf_inspect from tensorflow.python.util.tf_export import keras_export @@ -588,8 +592,7 @@ class Flatten(Layer): if (self.data_format == 'channels_first' and K.ndim(inputs) is not None and K.ndim(inputs) > 1): permutation = [0] - permutation.extend([i for i in - range(2, K.ndim(inputs))]) + permutation.extend(range(2, K.ndim(inputs))) permutation.append(1) inputs = array_ops.transpose(inputs, perm=permutation) @@ -626,7 +629,7 @@ class Flatten(Layer): output_shape = tensor_shape.TensorShape([1]) else: output_shape = [input_shape[0]] - if all(input_shape[1:]): + if np.all(input_shape[1:]): output_shape += [np.prod(input_shape[1:], dtype=int)] else: output_shape += [None] @@ -690,7 +693,7 @@ class Lambda(Layer): can be used when constructing `Sequential` and Functional API models. `Lambda` layers are best suited for simple operations or quick experimentation. For more advanced usecases, follow - [this guide](https://www.tensorflow.org/alpha/guide/keras/custom_layers_and_models) + [this guide](https://www.tensorflow.org/guide/keras/custom_layers_and_models) for subclassing `tf.keras.layers.Layer`. The main reason to subclass `tf.keras.layers.Layer` instead of using a @@ -721,30 +724,34 @@ class Lambda(Layer): model.add(Lambda(antirectifier)) ``` - Variables can be created within a `Lambda` layer. Like with - other layers, these variables will be created only once and reused - if the `Lambda` layer is called on new inputs. If creating more - than one variable in a given `Lambda` instance, be sure to use - a different name for each variable. Note that calling sublayers - from within a `Lambda` is not supported. + Variables: + While it is possible to use Variables with Lambda layers, this practice is + discouraged as it can easily lead to bugs. For instance, consider the + following layer: - Example of variable creation: + ```python + scale = tf.Variable(1.) + scale_layer = tf.keras.layers.Lambda(lambda x: x * scale) + ``` - ```python - def linear_transform(x): - v1 = tf.Variable(1., name='multiplier') - v2 = tf.Variable(0., name='bias') - return x*v1 + v2 + Because scale_layer does not directly track the `scale` variable, it will + not appear in `scale_layer.trainable_weights` and will therefore not be + trained if `scale_layer` is used in a Model. - linear_layer = Lambda(linear_transform) - model.add(linear_layer) - model.add(keras.layers.Dense(10, activation='relu')) - model.add(linear_layer) # Reuses existing Variables - ``` + A better pattern is to write a subclassed Layer: - Note that creating two instances of `Lambda` using the same function - will *not* share Variables between the two instances. Each instance of - `Lambda` will create and manage its own weights. + ```python + class ScaleLayer(tf.keras.layers.Layer): + def __init__(self): + super(ScaleLayer, self).__init__() + self.scale = tf.Variable(1.) + + def call(self, inputs): + return inputs * self.scale + ``` + + In general, Lambda layers can be convenient for simple stateless + computation, but anything more complex should use a subclass Layer instead. Arguments: function: The function to be evaluated. Takes input tensor as first @@ -769,22 +776,24 @@ class Lambda(Layer): Output shape: Specified by `output_shape` argument """ + @trackable.no_automatic_dependency_tracking def __init__(self, function, output_shape=None, mask=None, arguments=None, **kwargs): super(Lambda, self).__init__(**kwargs) + + self.arguments = arguments or {} self.function = function - self.arguments = arguments if arguments else {} + if mask is not None: self.supports_masking = True self.mask = mask self._supports_ragged_inputs = True self._output_shape = output_shape - self._variable_dict = {} - # These attributes are inherited from `Layer`. - self._trainable_weights = [] - self._non_trainable_weights = [] - function_args = tf_inspect.getfullargspec(self.function).args + # Warning on every invocation will be quite irksome in Eager mode. + self._already_warned = False + + function_args = tf_inspect.getfullargspec(function).args self._fn_expects_training_arg = 'training' in function_args self._fn_expects_mask_arg = 'mask' in function_args @@ -818,38 +827,69 @@ class Lambda(Layer): return nest.map_structure(_add_batch, output_shapes) def call(self, inputs, mask=None, training=None): - # Disallow two variables with the same name. - self._variables_added_in_call = set() - arguments = self.arguments + # We must copy for thread safety, but it only needs to be a shallow copy. + kwargs = {k: v for k, v in self.arguments.items()} if self._fn_expects_mask_arg: - arguments['mask'] = mask + kwargs['mask'] = mask if self._fn_expects_training_arg: - arguments['training'] = training - with variable_scope.variable_creator_scope(self._variable_creator): - return self.function(inputs, **arguments) + kwargs['training'] = training - def _variable_creator(self, next_creator, **kwargs): - name = kwargs['name'] + created_variables = [] + def _variable_creator(next_creator, **kwargs): + var = next_creator(**kwargs) + created_variables.append(var) + return var - # Variable named "name" already created in this invocation of `call`. - if name in self._variables_added_in_call: - raise RuntimeError('`Variable`s in a `Lambda` layer must have unique ' - 'names, found duplicate name: {}'.format(name)) - self._variables_added_in_call.add(name) + with backprop.GradientTape(watch_accessed_variables=True) as tape,\ + variable_scope.variable_creator_scope(_variable_creator): + result = self.function(inputs, **kwargs) + self._check_variables(created_variables, tape.watched_variables()) + return result - # Reuse Variables across invocations of `call`. - if name in self._variable_dict: - return self._variable_dict[name] + def _check_variables(self, created_variables, accessed_variables): + if not created_variables and not accessed_variables: + # In the common case that a Lambda layer does not touch a Variable, we + # don't want to incur the runtime cost of assembling any state used for + # checking only to immediately discard it. + return - # Variable was never created before. - var = next_creator(**kwargs) - self._variable_dict[name] = var - if var.trainable: - self._trainable_weights.append(var) - else: - self._non_trainable_weights.append(var) - K.track_variable(var) - return var + tracked_weights = set(v.experimental_ref() for v in self.weights) + untracked_new_vars = [v for v in created_variables + if v.experimental_ref() not in tracked_weights] + if untracked_new_vars: + variable_str = '\n'.join(' {}'.format(i) for i in untracked_new_vars) + error_str = textwrap.dedent( + ''' + The following Variables were created within a Lambda layer ({name}) + but are not tracked by said layer: + {variable_str} + The layer cannot safely ensure proper Variable reuse across multiple + calls, and consquently this behavior is disallowed for safety. Lambda + layers are not well suited to stateful computation; instead, writing a + subclassed Layer is the recommend way to define layers with + Variables.''' + ).format(name=self.name, variable_str=variable_str) + raise ValueError(error_str) + + untracked_used_vars = [v for v in accessed_variables + if v.experimental_ref() not in tracked_weights] + if untracked_used_vars and not self._already_warned: + variable_str = '\n'.join(' {}'.format(i) for i in untracked_used_vars) + self._warn(textwrap.dedent( + ''' + The following Variables were used a Lambda layer's call ({name}), but + are not present in its tracked objects: + {variable_str} + It is possible that this is intended behavior, but it is more likely + an omission. This is a strong indication that this layer should be + formulated as a subclassed Layer rather than a Lambda layer.''' + ).format(name=self.name, variable_str=variable_str)) + self._already_warned = True + + def _warn(self, msg): + # This method will be overridden in a unit test to raise an error, because + # self.assertWarns is not universally implemented. + return tf_logging.warn(msg) def compute_mask(self, inputs, mask=None): if callable(self.mask): diff --git a/tensorflow/python/keras/layers/core_test.py b/tensorflow/python/keras/layers/core_test.py index 05f89053fcb..8e6ad99873c 100644 --- a/tensorflow/python/keras/layers/core_test.py +++ b/tensorflow/python/keras/layers/core_test.py @@ -18,6 +18,8 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import textwrap + import numpy as np from tensorflow.python import keras @@ -225,28 +227,6 @@ class LambdaLayerTest(keras_parameterized.TestCase): self.assertAllEqual(layer._output_shape, (1, 1)) self.assertAllEqual(layer.mask(1, True), True) - def test_lambda_with_variable(self): - - def fn(x): - return x * variables.Variable(2., name='multiplier') - - layer = keras.layers.Lambda(fn) - for _ in range(10): - layer(np.ones((10, 10), 'float32')) - self.assertLen(layer.trainable_weights, 1) - self.assertEqual(layer.trainable_weights[0].name, 'lambda/multiplier:0') - - def test_lambda_with_duplicate_variable_names(self): - - def fn(x): - v1 = variables.Variable(2.) - v2 = variables.Variable(1.) - return x * v1 * v2 - - layer = keras.layers.Lambda(fn) - with self.assertRaisesRegexp(RuntimeError, 'must have unique names'): - layer(np.ones((10, 10), 'float32')) - def test_lambda_with_training_arg(self): def fn(x, training=True): @@ -294,19 +274,25 @@ class LambdaLayerTest(keras_parameterized.TestCase): expected_out = ragged_factory_ops.constant([[2.0], [3.0, 4.0]]) self.assertAllClose(out, expected_out) + class TestStatefulLambda(keras_parameterized.TestCase): @keras_parameterized.run_all_keras_modes @keras_parameterized.run_with_all_model_types def test_lambda_with_variable_in_model(self): - - def lambda_fn(x): - # Variable will only get created once. - v = variables.Variable(1., trainable=True) + v = variables.Variable(1., trainable=True) + def lambda_fn(x, v): return x * v - model = testing_utils.get_model_from_layers( - [keras.layers.Lambda(lambda_fn)], input_shape=(10,)) + # While it is generally not advised to mix Variables with Lambda layers, if + # the variables are explicitly set as attributes then they are still + # tracked. This is consistent with the base Layer behavior. + layer = keras.layers.Lambda(lambda_fn, arguments={'v': v}) + self.assertLen(layer.trainable_weights, 0) + layer.v = v + self.assertLen(layer.trainable_weights, 1) + + model = testing_utils.get_model_from_layers([layer], input_shape=(10,)) model.compile( keras.optimizer_v2.gradient_descent.SGD(0.1), 'mae', @@ -317,6 +303,66 @@ class TestStatefulLambda(keras_parameterized.TestCase): self.assertLen(model.trainable_weights, 1) self.assertAllClose(keras.backend.get_value(model.trainable_weights[0]), 2.) + @keras_parameterized.run_all_keras_modes + @keras_parameterized.run_with_all_model_types + def test_creation_inside_lambda(self): + def lambda_fn(x): + scale = variables.Variable(1., trainable=True, name='scale') + shift = variables.Variable(1., trainable=True, name='shift') + return x * scale + shift + + expected_error = textwrap.dedent(r''' + ( )?The following Variables were created within a Lambda layer \(shift_and_scale\) + ( )?but are not tracked by said layer: + ( )? 1: raise ValueError(err_msg) diff --git a/tensorflow/python/keras/layers/pooling.py b/tensorflow/python/keras/layers/pooling.py index 4b3083c9143..b4293289393 100644 --- a/tensorflow/python/keras/layers/pooling.py +++ b/tensorflow/python/keras/layers/pooling.py @@ -110,14 +110,62 @@ class Pooling1D(Layer): @keras_export('keras.layers.MaxPool1D', 'keras.layers.MaxPooling1D') class MaxPooling1D(Pooling1D): - """Max pooling operation for temporal data. + """Max pooling operation for 1D temporal data. + + Downsamples the input representation by taking the maximum value over the + window defined by `pool_size`. The window is shifted by `strides`. The + resulting output when using "valid" padding option has a shape of: + `output_shape = (input_shape - pool_size + 1) / strides)` + + The resulting output shape when using the "same" padding option is: + `output_shape = input_shape / strides` + + For example, for strides=1 and padding="valid": + + >>> x = tf.constant([1., 2., 3., 4., 5.]) + >>> x = tf.reshape(x, [1, 5, 1]) + >>> max_pool_1d = tf.keras.layers.MaxPooling1D(pool_size=2, + ... strides=1, padding='valid') + >>> max_pool_1d(x) + + + For example, for strides=2 and padding="valid": + + >>> x = tf.constant([1., 2., 3., 4., 5.]) + >>> x = tf.reshape(x, [1, 5, 1]) + >>> max_pool_1d = tf.keras.layers.MaxPooling1D(pool_size=2, + ... strides=2, padding='valid') + >>> max_pool_1d(x) + + + For example, for strides=1 and padding="same": + + >>> x = tf.constant([1., 2., 3., 4., 5.]) + >>> x = tf.reshape(x, [1, 5, 1]) + >>> max_pool_1d = tf.keras.layers.MaxPooling1D(pool_size=2, + ... strides=1, padding='same') + >>> max_pool_1d(x) + Arguments: - pool_size: Integer, size of the max pooling windows. - strides: Integer, or None. Factor by which to downscale. - E.g. 2 will halve the input. + pool_size: Integer, size of the max pooling window. + strides: Integer, or None. Specifies how much the pooling window moves + for each pooling step. If None, it will default to `pool_size`. padding: One of `"valid"` or `"same"` (case-insensitive). + "valid" adds no padding. "same" adds padding such that if the stride + is 1, the output shape is the same as the input shape. data_format: A string, one of `channels_last` (default) or `channels_first`. The ordering of the dimensions in the inputs. diff --git a/tensorflow/python/keras/layers/preprocessing/image_preprocessing.py b/tensorflow/python/keras/layers/preprocessing/image_preprocessing.py index b27012fed6f..72e4a932793 100644 --- a/tensorflow/python/keras/layers/preprocessing/image_preprocessing.py +++ b/tensorflow/python/keras/layers/preprocessing/image_preprocessing.py @@ -13,7 +13,7 @@ # limitations under the License. # ============================================================================== """Keras image preprocessing layers.""" - +# pylint: disable=g-classes-have-attributes from __future__ import absolute_import from __future__ import division from __future__ import print_function @@ -53,7 +53,7 @@ class Resizing(Layer): Resize the batched image input to target height and width. The input should be a 4-D tensor in the format of NHWC. - Attributes: + Arguments: height: Integer, the height of the output shape. width: Integer, the width of the output shape. interpolation: String, the interpolation method. Defaults to `bilinear`. @@ -111,7 +111,7 @@ class CenterCrop(Layer): If the input height/width is even and the target height/width is odd (or inversely), the input image is left-padded by 1 pixel. - Attributes: + Arguments: height: Integer, the height of the output shape. width: Integer, the width of the output shape. """ @@ -187,7 +187,7 @@ class RandomCrop(Layer): 4D tensor with shape: `(samples, target_height, target_width, channels)`. - Attributes: + Arguments: height: Integer, the height of the output shape. width: Integer, the width of the output shape. seed: Integer. Used to create a random seed. @@ -276,6 +276,112 @@ class RandomCrop(Layer): return dict(list(base_config.items()) + list(config.items())) +class Rescaling(Layer): + """Multiply inputs by `scale`. + + For instance, to rescale an input in the `[0, 255]` range + to be in the `[0, 1]` range, you would pass `scale=1./255`. + + The rescaling is applied both during training and inference. + + Input shape: + Arbitrary. + + Output shape: + Same as input. + + Arguments: + scale: Float, the scale to apply to the inputs. + """ + + def __init__(self, scale, **kwargs): + self.scale = scale + super(Rescaling, self).__init__(**kwargs) + + def call(self, inputs): + dtype = self._compute_dtype + return math_ops.cast(inputs, dtype) * math_ops.cast(self.scale, dtype) + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + 'scale': self.scale, + } + base_config = super(Rescaling, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + +class RandomFlip(Layer): + """Randomly flip each image horizontally and vertically. + + This layer will by default flip the images horizontally and then vertically + during training time. + `RandomFlip(horizontal=True)` will only flip the input horizontally. + `RandomFlip(vertical=True)` will only flip the input vertically. + During inference time, the output will be identical to input. Call the layer + with `training=True` to flip the input. + + Input shape: + 4D tensor with shape: + `(samples, height, width, channels)`, data_format='channels_last'. + + Output shape: + 4D tensor with shape: + `(samples, height, width, channels)`, data_format='channels_last'. + + Attributes: + horizontal: Bool, whether to randomly flip horizontally. + width: Bool, whether to randomly flip vertically. + seed: Integer. Used to create a random seed. + """ + + def __init__(self, horizontal=None, vertical=None, seed=None, **kwargs): + # If both arguments are None, set both to True. + if horizontal is None and vertical is None: + self.horizontal = True + self.vertical = True + else: + self.horizontal = horizontal or False + self.vertical = vertical or False + self.seed = seed + self._rng = make_generator(self.seed) + self.input_spec = InputSpec(ndim=4) + super(RandomFlip, self).__init__(**kwargs) + + def call(self, inputs, training=None): + if training is None: + training = K.learning_phase() + + def random_flipped_inputs(): + flipped_outputs = inputs + if self.horizontal: + flipped_outputs = image_ops.random_flip_up_down(flipped_outputs, + self.seed) + if self.vertical: + flipped_outputs = image_ops.random_flip_left_right( + flipped_outputs, self.seed) + return flipped_outputs + + output = tf_utils.smart_cond(training, random_flipped_inputs, + lambda: inputs) + output.set_shape(inputs.shape) + return output + + def compute_output_shape(self, input_shape): + return input_shape + + def get_config(self): + config = { + 'horizontal': self.horizontal, + 'vertical': self.vertical, + 'seed': self.seed, + } + base_config = super(RandomFlip, self).get_config() + return dict(list(base_config.items()) + list(config.items())) + + def make_generator(seed=None): if seed: return stateful_random_ops.Generator.from_seed(seed) diff --git a/tensorflow/python/keras/layers/preprocessing/image_preprocessing_test.py b/tensorflow/python/keras/layers/preprocessing/image_preprocessing_test.py index 19433b8290f..32fa2f15e6c 100644 --- a/tensorflow/python/keras/layers/preprocessing/image_preprocessing_test.py +++ b/tensorflow/python/keras/layers/preprocessing/image_preprocessing_test.py @@ -22,11 +22,13 @@ from absl.testing import parameterized import numpy as np from tensorflow.python.framework import errors +from tensorflow.python.framework import test_util as tf_test_util from tensorflow.python.keras import keras_parameterized from tensorflow.python.keras import testing_utils from tensorflow.python.keras.layers.preprocessing import image_preprocessing from tensorflow.python.keras.utils.generic_utils import CustomObjectScope from tensorflow.python.ops import image_ops_impl as image_ops +from tensorflow.python.ops import random_ops from tensorflow.python.ops import stateless_random_ops from tensorflow.python.platform import test @@ -251,5 +253,126 @@ class RandomCropTest(keras_parameterized.TestCase): self.assertEqual(layer_1.name, layer.name) +class RescalingTest(keras_parameterized.TestCase): + + @keras_parameterized.run_all_keras_modes(always_skip_v1=True) + def test_rescaling_base(self): + kwargs = {'scale': 0.004} + testing_utils.layer_test( + image_preprocessing.Rescaling, + kwargs=kwargs, + input_shape=(2, 5, 6, 3), + expected_output_shape=(None, 5, 6, 3)) + + @tf_test_util.run_v2_only + def test_rescaling_correctness_float(self): + layer = image_preprocessing.Rescaling(0.004) + inputs = random_ops.random_uniform((2, 4, 5, 3)) + outputs = layer(inputs) + self.assertAllClose(outputs.numpy(), inputs.numpy() * 0.004) + + @tf_test_util.run_v2_only + def test_rescaling_correctness_int(self): + layer = image_preprocessing.Rescaling(0.004) + inputs = random_ops.random_uniform((2, 4, 5, 3), 0, 100, dtype='int32') + outputs = layer(inputs) + self.assertEqual(outputs.dtype.name, 'float32') + self.assertAllClose(outputs.numpy(), inputs.numpy() * 0.004) + + def test_config_with_custom_name(self): + layer = image_preprocessing.Rescaling(0.5, name='rescaling') + config = layer.get_config() + layer_1 = image_preprocessing.Rescaling.from_config(config) + self.assertEqual(layer_1.name, layer.name) + + +@keras_parameterized.run_all_keras_modes(always_skip_v1=True) +class RandomFlipTest(keras_parameterized.TestCase): + + def _run_test(self, + flip_horizontal, + flip_vertical, + expected_output=None, + mock_random=None): + np.random.seed(1337) + num_samples = 2 + orig_height = 5 + orig_width = 8 + channels = 3 + if mock_random is None: + mock_random = [1 for _ in range(num_samples)] + mock_random = np.reshape(mock_random, [2, 1, 1, 1]) + inp = np.random.random((num_samples, orig_height, orig_width, channels)) + if expected_output is None: + expected_output = inp + if flip_horizontal: + expected_output = np.flip(expected_output, axis=1) + if flip_vertical: + expected_output = np.flip(expected_output, axis=2) + with test.mock.patch.object( + random_ops, 'random_uniform', return_value=mock_random): + with tf_test_util.use_gpu(): + layer = image_preprocessing.RandomFlip(flip_horizontal, flip_vertical) + actual_output = layer(inp, training=1) + self.assertAllClose(expected_output, actual_output) + + @parameterized.named_parameters(('random_flip_horizontal', True, False), + ('random_flip_vertical', False, True), + ('random_flip_both', True, True), + ('random_flip_neither', False, False)) + def test_random_flip(self, flip_horizontal, flip_vertical): + with CustomObjectScope({'RandomFlip': image_preprocessing.RandomFlip}): + self._run_test(flip_horizontal, flip_vertical) + + def test_random_flip_horizontal_half(self): + with CustomObjectScope({'RandomFlip': image_preprocessing.RandomFlip}): + np.random.seed(1337) + mock_random = [1, 0] + mock_random = np.reshape(mock_random, [2, 1, 1, 1]) + input_images = np.random.random((2, 5, 8, 3)).astype(np.float32) + expected_output = input_images.copy() + expected_output[0, :, :, :] = np.flip(input_images[0, :, :, :], axis=0) + self._run_test(True, False, expected_output, mock_random) + + def test_random_flip_vertical_half(self): + with CustomObjectScope({'RandomFlip': image_preprocessing.RandomFlip}): + np.random.seed(1337) + mock_random = [1, 0] + mock_random = np.reshape(mock_random, [2, 1, 1, 1]) + input_images = np.random.random((2, 5, 8, 3)).astype(np.float32) + expected_output = input_images.copy() + expected_output[0, :, :, :] = np.flip(input_images[0, :, :, :], axis=1) + self._run_test(False, True, expected_output, mock_random) + + def test_random_flip_inference(self): + with CustomObjectScope({'RandomFlip': image_preprocessing.RandomFlip}): + input_images = np.random.random((2, 5, 8, 3)).astype(np.float32) + expected_output = input_images + with tf_test_util.use_gpu(): + layer = image_preprocessing.RandomFlip(True, True) + actual_output = layer(input_images, training=0) + self.assertAllClose(expected_output, actual_output) + + def test_random_flip_default(self): + with CustomObjectScope({'RandomFlip': image_preprocessing.RandomFlip}): + input_images = np.random.random((2, 5, 8, 3)).astype(np.float32) + expected_output = np.flip(np.flip(input_images, axis=1), axis=2) + mock_random = [1, 1] + mock_random = np.reshape(mock_random, [2, 1, 1, 1]) + with test.mock.patch.object( + random_ops, 'random_uniform', return_value=mock_random): + with self.cached_session(use_gpu=True): + layer = image_preprocessing.RandomFlip() + actual_output = layer(input_images, training=1) + self.assertAllClose(expected_output, actual_output) + + @tf_test_util.run_v2_only + def test_config_with_custom_name(self): + layer = image_preprocessing.RandomFlip(5, 5, name='image_preproc') + config = layer.get_config() + layer_1 = image_preprocessing.RandomFlip.from_config(config) + self.assertEqual(layer_1.name, layer.name) + + if __name__ == '__main__': test.main() diff --git a/tensorflow/python/keras/layers/preprocessing/text_vectorization.py b/tensorflow/python/keras/layers/preprocessing/text_vectorization.py index 3f9c36eb4ce..6080d0b7ede 100644 --- a/tensorflow/python/keras/layers/preprocessing/text_vectorization.py +++ b/tensorflow/python/keras/layers/preprocessing/text_vectorization.py @@ -568,7 +568,7 @@ class TextVectorization(CombinerPreprocessingLayer): self.set_vocabulary(updates[_VOCAB_NAME]) def _preprocess(self, inputs): - if self._standardize is LOWER_AND_STRIP_PUNCTUATION: + if self._standardize == LOWER_AND_STRIP_PUNCTUATION: lowercase_inputs = gen_string_ops.string_lower(inputs) inputs = string_ops.regex_replace(lowercase_inputs, DEFAULT_STRIP_REGEX, "") @@ -586,7 +586,7 @@ class TextVectorization(CombinerPreprocessingLayer): # so can be squeezed out. We do this here instead of after splitting for # performance reasons - it's more expensive to squeeze a ragged tensor. inputs = array_ops.squeeze(inputs, axis=1) - if self._split is SPLIT_ON_WHITESPACE: + if self._split == SPLIT_ON_WHITESPACE: # This treats multiple whitespaces as one whitespace, and strips leading # and trailing whitespace. inputs = ragged_string_ops.string_split_v2(inputs) diff --git a/tensorflow/python/keras/layers/preprocessing/text_vectorization_test.py b/tensorflow/python/keras/layers/preprocessing/text_vectorization_test.py index 8f65c481400..b20b0164247 100644 --- a/tensorflow/python/keras/layers/preprocessing/text_vectorization_test.py +++ b/tensorflow/python/keras/layers/preprocessing/text_vectorization_test.py @@ -470,6 +470,34 @@ class TextVectorizationPreprocessingTest( with self.assertRaisesRegex(ValueError, ".*is not a supported splitting.*"): _ = layer(input_data) + def test_standardize_with_no_identical_argument(self): + input_array = np.array([["hello world"]]) + expected_output = np.array([[1, 1]]) + + standardize = "".join(["lower", "_and_strip_punctuation"]) + layer = get_layer_class()(standardize=standardize) + + input_data = keras.Input(shape=(1,), dtype=dtypes.string) + output_data = layer(input_data) + model = keras.Model(inputs=input_data, outputs=output_data) + output = model.predict(input_array) + + self.assertAllEqual(expected_output, output) + + def test_splitting_with_no_identical_argument(self): + input_array = np.array([["hello world"]]) + expected_output = np.array([[1, 1]]) + + split = "".join(["white", "space"]) + layer = get_layer_class()(split=split) + + input_data = keras.Input(shape=(1,), dtype=dtypes.string) + output_data = layer(input_data) + model = keras.Model(inputs=input_data, outputs=output_data) + output = model.predict(input_array) + + self.assertAllEqual(expected_output, output) + @keras_parameterized.run_all_keras_modes class TextVectorizationOutputTest( diff --git a/tensorflow/python/keras/layers/rnn_cell_wrapper_v2_test.py b/tensorflow/python/keras/layers/rnn_cell_wrapper_v2_test.py index 15cbf68c87a..02ad2587dea 100644 --- a/tensorflow/python/keras/layers/rnn_cell_wrapper_v2_test.py +++ b/tensorflow/python/keras/layers/rnn_cell_wrapper_v2_test.py @@ -175,7 +175,7 @@ class RNNCellWrapperTest(test.TestCase, parameterized.TestCase): _ = rnn_cell(inputs, [state, state]) weights = base_cell._cells[0].weights self.assertLen(weights, expected_len=2) - self.assertTrue(all(["_wrapper" in v.name for v in weights])) + self.assertTrue(all("_wrapper" in v.name for v in weights)) @parameterized.parameters( [rnn_cell_wrapper_v2.DropoutWrapper, rnn_cell_wrapper_v2.ResidualWrapper]) @@ -256,7 +256,7 @@ class RNNCellWrapperTest(test.TestCase, parameterized.TestCase): with self.assertRaisesRegexp(ValueError, "does not work with "): wrapper_cls(cell) - cell = layers.LSTMCell_v2(10) + cell = layers.LSTMCellV2(10) with self.assertRaisesRegexp(ValueError, "does not work with "): wrapper_cls(cell) diff --git a/tensorflow/python/keras/layers/serialization.py b/tensorflow/python/keras/layers/serialization.py index 85fca80f985..a7c43c60350 100644 --- a/tensorflow/python/keras/layers/serialization.py +++ b/tensorflow/python/keras/layers/serialization.py @@ -39,6 +39,7 @@ from tensorflow.python.keras.layers.merge import * from tensorflow.python.keras.layers.noise import * from tensorflow.python.keras.layers.normalization import * from tensorflow.python.keras.layers.pooling import * +from tensorflow.python.keras.layers.preprocessing.image_preprocessing import * from tensorflow.python.keras.layers.recurrent import * from tensorflow.python.keras.layers.rnn_cell_wrapper_v2 import * from tensorflow.python.keras.layers.wrappers import * diff --git a/tensorflow/python/keras/losses.py b/tensorflow/python/keras/losses.py index 4ddf176094a..5e89cf4448b 100644 --- a/tensorflow/python/keras/losses.py +++ b/tensorflow/python/keras/losses.py @@ -92,6 +92,9 @@ class Loss(object): losses_utils.ReductionV2.validate(reduction) self.reduction = reduction self.name = name + # SUM_OVER_BATCH is only allowed in losses managed by `fit` or + # CannedEstimators. + self._allow_sum_over_batch_size = False def __call__(self, y_true, y_pred, sample_weight=None): """Invokes the `Loss` instance. @@ -155,9 +158,10 @@ class Loss(object): def _get_reduction(self): """Handles `AUTO` reduction cases and returns the reduction value.""" - if distribution_strategy_context.has_strategy() and ( - self.reduction == losses_utils.ReductionV2.AUTO or - self.reduction == losses_utils.ReductionV2.SUM_OVER_BATCH_SIZE): + if (not self._allow_sum_over_batch_size and + distribution_strategy_context.has_strategy() and + (self.reduction == losses_utils.ReductionV2.AUTO or + self.reduction == losses_utils.ReductionV2.SUM_OVER_BATCH_SIZE)): raise ValueError( 'Please use `tf.keras.losses.Reduction.SUM` or ' '`tf.keras.losses.Reduction.NONE` for loss reduction when losses are ' diff --git a/tensorflow/python/keras/losses_test.py b/tensorflow/python/keras/losses_test.py index d760f3581d8..5776ebd0b4e 100644 --- a/tensorflow/python/keras/losses_test.py +++ b/tensorflow/python/keras/losses_test.py @@ -26,6 +26,7 @@ import numpy as np from tensorflow.python import keras from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes +from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops from tensorflow.python.framework import test_util from tensorflow.python.keras.utils import generic_utils @@ -328,8 +329,9 @@ class MeanSquaredErrorTest(test.TestCase): y_true = constant_op.constant([1, 9, 2, -5, -2, 6], shape=(2, 3, 1)) y_pred = constant_op.constant([4, 8, 12, 8, 1, 3], shape=(2, 3, 1)) sample_weight = constant_op.constant([3, 6, 5, 0], shape=(2, 2)) - with self.assertRaisesRegexp(ValueError, - 'weights can not be broadcast to values'): + with self.assertRaisesRegexp((ValueError, errors_impl.InvalidArgumentError), + (r'Incompatible shapes: \[2,3\] vs. \[2,2\]|' + 'Dimensions must be equal')): mse_obj(y_true, y_pred, sample_weight=sample_weight) def test_no_reduction(self): @@ -421,8 +423,9 @@ class MeanAbsoluteErrorTest(test.TestCase): y_true = constant_op.constant([1, 9, 2, -5, -2, 6], shape=(2, 3, 1)) y_pred = constant_op.constant([4, 8, 12, 8, 1, 3], shape=(2, 3, 1)) sample_weight = constant_op.constant([3, 6, 5, 0], shape=(2, 2)) - with self.assertRaisesRegexp(ValueError, - 'weights can not be broadcast to values'): + with self.assertRaisesRegexp((ValueError, errors_impl.InvalidArgumentError), + (r'Incompatible shapes: \[2,3\] vs. \[2,2\]|' + 'Dimensions must be equal')): mae_obj(y_true, y_pred, sample_weight=sample_weight) def test_no_reduction(self): diff --git a/tensorflow/python/keras/mixed_precision/experimental/BUILD b/tensorflow/python/keras/mixed_precision/experimental/BUILD index 8130689f8e9..ff595cd5089 100644 --- a/tensorflow/python/keras/mixed_precision/experimental/BUILD +++ b/tensorflow/python/keras/mixed_precision/experimental/BUILD @@ -124,7 +124,8 @@ cuda_py_test( name = "loss_scale_optimizer_test", size = "small", srcs = ["loss_scale_optimizer_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":loss_scale_optimizer", ":test_util", "//tensorflow/python:client_testlib", @@ -133,7 +134,6 @@ cuda_py_test( "//tensorflow/python/distribute:one_device_strategy", "//tensorflow/python/keras", ], - python_version = "PY3", ) py_library( @@ -150,30 +150,30 @@ cuda_py_test( name = "keras_test", size = "medium", srcs = ["keras_test.py"], - additional_deps = [ + python_version = "PY3", + shard_count = 4, + tags = ["no_windows"], # b/139083295: bfloat16 tests fail on Windows + deps = [ ":test_util", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", "//tensorflow/python/distribute:mirrored_strategy", "//tensorflow/python/distribute:one_device_strategy", "//tensorflow/python/keras", + "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", - shard_count = 4, - tags = ["no_windows"], # b/139083295: bfloat16 tests fail on Windows ) cuda_py_test( name = "layer_correctness_test", size = "medium", srcs = ["layer_correctness_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + python_version = "PY3", + tags = ["no_rocm"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python/distribute:mirrored_strategy", "//tensorflow/python/distribute:one_device_strategy", "//tensorflow/python/keras", + "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", - tags = ["no_rocm"], ) diff --git a/tensorflow/python/keras/optimizer_v2/BUILD b/tensorflow/python/keras/optimizer_v2/BUILD index 009f1c17320..bb20d4bb788 100644 --- a/tensorflow/python/keras/optimizer_v2/BUILD +++ b/tensorflow/python/keras/optimizer_v2/BUILD @@ -63,155 +63,140 @@ cuda_py_test( name = "adagrad_test", size = "medium", srcs = ["adagrad_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":optimizer_v2", "//tensorflow/python:client_testlib", "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", "//tensorflow/python:framework", "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:resources", "//tensorflow/python:variables", "//tensorflow/python/eager:context", ], - shard_count = 4, ) cuda_py_test( name = "adam_test", size = "medium", srcs = ["adam_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":optimizer_v2", "//tensorflow/python:client_testlib", "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", "//tensorflow/python:framework", "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:resources", "//tensorflow/python:variables", "//tensorflow/python/eager:context", ], - shard_count = 4, ) cuda_py_test( name = "adamax_test", size = "medium", srcs = ["adamax_test.py"], - additional_deps = [ + shard_count = 4, + tags = ["no_rocm"], + deps = [ ":optimizer_v2", "//tensorflow/python:client_testlib", "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", "//tensorflow/python:framework", "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:resources", "//tensorflow/python:variables", "//tensorflow/python/eager:context", ], - shard_count = 4, - tags = ["no_rocm"], ) cuda_py_test( name = "adadelta_test", size = "medium", srcs = ["adadelta_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":optimizer_v2", "//tensorflow/python:client_testlib", "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", "//tensorflow/python:framework", "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:resources", "//tensorflow/python:variables", "//tensorflow/python/eager:context", ], - shard_count = 4, ) cuda_py_test( name = "ftrl_test", size = "medium", srcs = ["ftrl_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":optimizer_v2", "//tensorflow/python:client_testlib", "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", "//tensorflow/python:framework", "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:resources", "//tensorflow/python:variables", "//tensorflow/python/eager:context", ], - shard_count = 4, ) cuda_py_test( name = "gradient_descent_test", size = "medium", srcs = ["gradient_descent_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":optimizer_v2", "//tensorflow/python:client_testlib", "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", "//tensorflow/python:framework", "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:resources", "//tensorflow/python:variables", "//tensorflow/python/eager:context", ], - shard_count = 4, ) cuda_py_test( name = "nadam_test", size = "medium", srcs = ["nadam_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":optimizer_v2", "//tensorflow/python:client_testlib", "//tensorflow/python:embedding_ops", - "//tensorflow/python:platform_test", "//tensorflow/python:framework", "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:resources", "//tensorflow/python:variables", "//tensorflow/python/eager:context", ], - shard_count = 4, ) cuda_py_test( name = "optimizer_v2_test", size = "medium", srcs = ["optimizer_v2_test.py"], - additional_deps = [ - ":optimizer_v2", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:clip_ops", - "//tensorflow/python:framework", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:gradients", - "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:state_ops", - "//tensorflow/python:variables", - "//tensorflow/python/eager:def_function", - "//tensorflow/python/keras", - ], shard_count = 8, tags = [ "manual", @@ -220,39 +205,54 @@ cuda_py_test( "no_windows", "notap", ], + deps = [ + ":optimizer_v2", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:clip_ops", + "//tensorflow/python:framework", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:gradients", + "//tensorflow/python:resource_variable_ops", + "//tensorflow/python:state_ops", + "//tensorflow/python:variables", + "//tensorflow/python/eager:def_function", + "//tensorflow/python/keras", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_test( name = "learning_rate_schedule_test", size = "medium", srcs = ["learning_rate_schedule_test.py"], - additional_deps = [ + shard_count = 4, + deps = [ ":optimizer_v2", - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python/keras", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - shard_count = 4, ) cuda_py_test( name = "rmsprop_test", size = "medium", srcs = ["rmsprop_test.py"], - additional_deps = [ + shard_count = 2, + deps = [ ":optimizer_v2", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python/eager:def_function", + "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", + "//tensorflow/python:clip_ops", "//tensorflow/python:framework", "//tensorflow/python:framework_test_lib", - "//tensorflow/python:array_ops", - "//tensorflow/python:clip_ops", "//tensorflow/python:gradients", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:state_ops", "//tensorflow/python:variables", + "//tensorflow/python/eager:def_function", + "@absl_py//absl/testing:parameterized", ], - shard_count = 2, ) diff --git a/tensorflow/python/keras/optimizer_v2/adam.py b/tensorflow/python/keras/optimizer_v2/adam.py index b0b0b956c17..bf7d79ea501 100644 --- a/tensorflow/python/keras/optimizer_v2/adam.py +++ b/tensorflow/python/keras/optimizer_v2/adam.py @@ -66,30 +66,34 @@ class Adam(optimizer_v2.OptimizerV2): described at the end of section 2 of the paper: $$t := t + 1$$ - $$lr_t := \text{learning\_rate} * \sqrt{1 - beta_2^t} / (1 - beta_1^t)$$ + $$\text{lr}_t := \mathrm{learning_rate} * + \sqrt{1 - \beta_2^t} / (1 - \beta_1^t)$$ - $$m_t := beta_1 * m_{t-1} + (1 - beta_1) * g$$ - $$v_t := beta_2 * v_{t-1} + (1 - beta_2) * g * g$$ - $$variable := variable - lr_t * m_t / (\sqrt{v_t} + \epsilon)$$ + $$m_t := \beta_1 * m_{t-1} + (1 - \beta_1) * g$$ + $$v_t := \beta_2 * v_{t-1} + (1 - \beta_2) * g * g$$ + $$\text{variable} := \text{variable} - + lr_t * m_t / (\sqrt{v_t} + \epsilon)$$ If amsgrad = True: Initialization: $$m_0 := 0 \text{(Initialize initial 1st moment vector)}$$ $$v_0 := 0 \text{(Initialize initial 2nd moment vector)}$$ - $$v_hat_0 := 0 \text{(Initialize initial 2nd moment vector)}$$ + $$\hat{v}_0 := 0 \text{(Initialize initial 2nd moment vector)}$$ $$t := 0 \text{(Initialize timestep)}$$ The update rule for `variable` with gradient `g` uses an optimization described at the end of section 2 of the paper: $$t := t + 1$$ - $$lr_t := \text{learning\_rate} * \sqrt{1 - beta_2^t} / (1 - beta_1^t)$$ + $$\text{lr}_t := \mathrm{learning_rate} * + \sqrt{1 - \beta_2^t} / (1 - \beta_1^t)$$ - $$m_t := beta_1 * m_{t-1} + (1 - beta_1) * g$$ - $$v_t := beta_2 * v_{t-1} + (1 - beta_2) * g * g$$ - $$v_hat_t := max(v_hat_{t-1}, v_t)$$ - $$variable := variable - lr_t * m_t / (\sqrt{v_hat_t} + \epsilon)$$ + $$m_t := \beta_1 * m_{t-1} + (1 - \beta_1) * g$$ + $$v_t := \beta_2 * v_{t-1} + (1 - \beta_2) * g * g$$ + $$\hat{v}_t := \max(\hat{v}_{t-1}, v_t)$$ + $$\text{variable} := \text{variable} - + \text{lr}_t * m_t / (\sqrt{\hat{v}_t} + \epsilon)$$ The default value of 1e-7 for epsilon might not be a good default in general. For example, when training an Inception network on ImageNet a diff --git a/tensorflow/python/keras/optimizer_v2/adam_test.py b/tensorflow/python/keras/optimizer_v2/adam_test.py index 47a0cf67247..8259fc155a3 100644 --- a/tensorflow/python/keras/optimizer_v2/adam_test.py +++ b/tensorflow/python/keras/optimizer_v2/adam_test.py @@ -539,8 +539,8 @@ class AdamOptimizerTest(test.TestCase): opt = adam.Adam(1.) opt.minimize(lambda: v1 + v2, var_list=[v1, v2]) # There should be iteration, and two unique slot variables for v1 and v2. - self.assertEqual( - 5, len(set([v.experimental_ref() for v in opt.variables()]))) + self.assertEqual(5, + len(set(v.experimental_ref() for v in opt.variables()))) self.assertEqual( self.evaluate(opt.variables()[0]), self.evaluate(opt.iterations)) diff --git a/tensorflow/python/keras/optimizers.py b/tensorflow/python/keras/optimizers.py index 44d266b9604..b847dbe3fdd 100644 --- a/tensorflow/python/keras/optimizers.py +++ b/tensorflow/python/keras/optimizers.py @@ -89,7 +89,7 @@ class Optimizer(object): function not implemented). """ grads = K.gradients(loss, params) - if any([g is None for g in grads]): + if any(g is None for g in grads): raise ValueError('An operation has `None` for gradient. ' 'Please make sure that all of your ops have a ' 'gradient defined (i.e. are differentiable). ' diff --git a/tensorflow/python/keras/saving/hdf5_format.py b/tensorflow/python/keras/saving/hdf5_format.py index c896460bfee..e006080e83b 100644 --- a/tensorflow/python/keras/saving/hdf5_format.py +++ b/tensorflow/python/keras/saving/hdf5_format.py @@ -804,8 +804,7 @@ def save_attributes_to_hdf5_group(group, name, data): if bad_attributes: raise RuntimeError('The following attributes cannot be saved to HDF5 ' 'file because they are larger than %d bytes: %s' % - (HDF5_OBJECT_HEADER_LIMIT, - ', '.join([x for x in bad_attributes]))) + (HDF5_OBJECT_HEADER_LIMIT, ', '.join(bad_attributes))) data_npy = np.asarray(data) diff --git a/tensorflow/python/keras/saving/hdf5_format_test.py b/tensorflow/python/keras/saving/hdf5_format_test.py index c515c510a5d..28101cfb4e7 100644 --- a/tensorflow/python/keras/saving/hdf5_format_test.py +++ b/tensorflow/python/keras/saving/hdf5_format_test.py @@ -130,7 +130,7 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase): (None, input_dim, 4, 4, 4), ], [ - (keras.layers.GRU(output_dim)), + (keras.layers.GRUV1(output_dim)), [np.random.random((input_dim, output_dim)), np.random.random((output_dim, output_dim)), np.random.random((output_dim,)), @@ -143,7 +143,7 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase): (None, 4, input_dim), ], [ - (keras.layers.LSTM(output_dim)), + (keras.layers.LSTMV1(output_dim)), [np.random.random((input_dim, output_dim)), np.random.random((output_dim, output_dim)), np.random.random((output_dim,)), @@ -241,14 +241,13 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase): self.assertAllClose(y, ref_y) + @keras_parameterized.run_with_all_saved_model_formats @test_util.run_in_graph_and_eager_modes def test_nested_model_weight_loading(self): - if h5py is None: - return - + save_format = testing_utils.get_save_format() temp_dir = self.get_temp_dir() self.addCleanup(shutil.rmtree, temp_dir) - h5_path = os.path.join(temp_dir, 'test.h5') + saved_model_dir = os.path.join(temp_dir, 'saved_model') batch_size = 5 shape = (None, None, 3) @@ -274,10 +273,10 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase): x = np.random.random((batch_size, 1, 1, 3)) ref_y = model.predict(x) - model.save_weights(h5_path) + model.save_weights(saved_model_dir, save_format=save_format) model = gen_model() - model.load_weights(h5_path) + model.load_weights(saved_model_dir) y = model.predict(x) self.assertAllClose(y, ref_y) @@ -372,12 +371,20 @@ class TestWeightSavingAndLoading(test.TestCase, parameterized.TestCase): keras.backend.get_value(model.layers[1].bias)) -class TestWholeModelSaving(test.TestCase): +@keras_parameterized.run_with_all_saved_model_formats +class TestWholeModelSaving(test.TestCase, parameterized.TestCase): + + def _save_model_dir(self, dirname='saved_model'): + temp_dir = self.get_temp_dir() + self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True) + return os.path.join(temp_dir, dirname) - @test_util.run_v1_only('b/120994067') def test_sequential_model_saving(self): - if h5py is None: - self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() + # TODO(b/145951332): skip TF format for now. + if save_format in ['tf', 'tensorflow']: + return with self.cached_session(): model = keras.models.Sequential() @@ -386,7 +393,7 @@ class TestWholeModelSaving(test.TestCase): model.add(keras.layers.TimeDistributed(keras.layers.Dense(3))) model.compile( loss=keras.losses.MSE, - optimizer=keras.optimizers.RMSprop(lr=0.0001), + optimizer=keras.optimizer_v2.rmsprop.RMSprop(lr=0.0001), metrics=[ keras.metrics.categorical_accuracy, keras.metrics.CategoricalCrossentropy( @@ -404,12 +411,9 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) out = model.predict(x) - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) - new_model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + new_model = keras.models.load_model(saved_model_dir) out2 = new_model.predict(x) self.assertAllClose(out, out2, atol=1e-05) @@ -435,9 +439,8 @@ class TestWholeModelSaving(test.TestCase): @test_util.run_deprecated_v1 def test_sequential_model_saving_without_input_shape(self): - if h5py is None: - self.skipTest('h5py required to run this test') - + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() with self.cached_session(): model = keras.models.Sequential() model.add(keras.layers.Dense(2)) @@ -460,20 +463,16 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) out = model.predict(x) - fd, fname = tempfile.mkstemp('.h5', dir=self.get_temp_dir()) - model.save(fname) + model.save(saved_model_dir, save_format=save_format) - new_model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + new_model = keras.models.load_model(saved_model_dir) out2 = new_model.predict(x) self.assertAllClose(out, out2, atol=1e-05) def test_sequential_model_saving_without_compile(self): - if h5py is None: - self.skipTest('h5py required to run this test') - + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() with self.cached_session(): model = keras.models.Sequential() model.add(keras.layers.Dense(2, input_shape=(3,))) @@ -482,22 +481,22 @@ class TestWholeModelSaving(test.TestCase): x = np.random.random((1, 3)) out = model.predict(x) - fd, fname = tempfile.mkstemp('.h5') # Save the model without any compilation or training. - keras.models.save_model(model, fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) - new_model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + new_model = keras.models.load_model(saved_model_dir) out2 = new_model.predict(x) self.assertAllClose(out, out2, atol=1e-05) @test_util.run_deprecated_v1 def test_sequential_model_saving_2(self): - if h5py is None: - self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() + # TODO(b/145133418): skip tf format for now. + if save_format in ['tf', 'tensorflow']: + return with self.cached_session(): # test with custom optimizer, loss @@ -518,24 +517,20 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) out = model.predict(x) - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) model = keras.models.load_model( - fname, + saved_model_dir, custom_objects={'CustomOp': CustomOp, 'custom_loss': custom_loss}) - os.close(fd) - os.remove(fname) out2 = model.predict(x) self.assertAllClose(out, out2, atol=1e-05) @test_util.run_deprecated_v1 def test_functional_model_saving(self): - if h5py is None: - self.skipTest('h5py required to run this test') - + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() with self.cached_session(): inputs = keras.layers.Input(shape=(3,)) x = keras.layers.Dense(2)(inputs) @@ -558,54 +553,40 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) out = model.predict(x) - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - - model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) + model = keras.models.load_model(saved_model_dir) out2 = model.predict(x) self.assertAllClose(out, out2, atol=1e-05) def test_saving_without_compilation(self): - if h5py is None: - self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() + model = keras.models.Sequential() + model.add(keras.layers.Dense(2, input_shape=(3,))) + model.add(keras.layers.Dense(3)) + model.compile(loss='mse', optimizer='sgd', metrics=['acc']) - with self.cached_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='sgd', metrics=['acc']) - - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) + model = keras.models.load_model(saved_model_dir) def test_saving_with_tf_optimizer(self): - if h5py is None: - self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() - with self.cached_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.AdadeltaOptimizer(0.1), - metrics=['acc']) + 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.AdadeltaOptimizer(0.1), + metrics=['acc']) - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) + model = keras.models.load_model(saved_model_dir) def test_saving_right_after_compilation(self): - if h5py is None: - self.skipTest('h5py required to run this test') - + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() with self.cached_session(): model = keras.models.Sequential() model.add(keras.layers.Dense(2, input_shape=(3,))) @@ -613,39 +594,38 @@ class TestWholeModelSaving(test.TestCase): model.compile(loss='mse', optimizer='sgd', metrics=['acc']) model._make_train_function() - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) + model = keras.models.load_model(saved_model_dir) def test_saving_lambda_numpy_array_arguments(self): - with self.cached_session(): - if h5py is None: - self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() - mean = np.random.random((4, 2, 3)) - std = np.abs(np.random.random((4, 2, 3))) + 1e-5 - inputs = keras.layers.Input(shape=(4, 2, 3)) - output = keras.layers.Lambda(lambda image, mu, std: (image - mu) / std, - arguments={'mu': mean, 'std': std})(inputs) - model = keras.models.Model(inputs, output) - model.compile(loss='mse', optimizer='sgd', metrics=['acc']) - - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - - model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) - - self.assertAllClose(mean, model.layers[1].arguments['mu']) - self.assertAllClose(std, model.layers[1].arguments['std']) - - def test_saving_model_with_long_layer_names(self): if h5py is None: self.skipTest('h5py required to run this test') + mean = np.random.random((4, 2, 3)) + std = np.abs(np.random.random((4, 2, 3))) + 1e-5 + inputs = keras.layers.Input(shape=(4, 2, 3)) + output = keras.layers.Lambda(lambda image, mu, std: (image - mu) / std, + arguments={'mu': mean, 'std': std})(inputs) + model = keras.models.Model(inputs, output) + model.compile(loss='mse', optimizer='sgd', metrics=['acc']) + + keras.models.save_model(model, saved_model_dir, save_format=save_format) + + model = keras.models.load_model(saved_model_dir) + + # TODO(b/145150660): skip the checking for tf format. + if save_format in ['tf', 'tensorflow']: + return + + self.assertAllClose(mean, model.layers[1].arguments['mu']) + self.assertAllClose(std, model.layers[1].arguments['std']) + + def test_saving_model_with_long_layer_names(self): + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() with self.cached_session(): # This layer name will make the `layers_name` HDF5 attribute blow # out of proportion. Note that it fits into the internal HDF5 @@ -666,13 +646,14 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) out = model.predict(x) - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - model = keras.models.load_model(fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) + model = keras.models.load_model(saved_model_dir) + if save_format in ['tf', 'tensorflow']: + return # Check that the HDF5 files contains chunked array # of layer names. - with h5py.File(fname, 'r') as h5file: + with h5py.File(saved_model_dir, 'r') as h5file: num_names_arrays = len([attr for attr in h5file['model_weights'].attrs if attr.startswith('layer_names')]) # The chunking of layer names array should have happened. @@ -680,13 +661,12 @@ class TestWholeModelSaving(test.TestCase): out2 = model.predict(x) self.assertAllClose(out, out2, atol=1e-05) - # Cleanup - os.close(fd) - os.remove(fname) - def test_saving_model_with_long_weights_names(self): - if h5py is None: - self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() + # TODO(b/145139873): skip tf format for now. + if save_format in ['tf', 'tensorflow']: + return with self.cached_session(): x = keras.Input(shape=(2,), name='nested_model_input') @@ -710,13 +690,12 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) out = model.predict(x) - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - model = keras.models.load_model(fname) + keras.models.save_model(model, saved_model_dir, save_format=save_format) + model = keras.models.load_model(saved_model_dir) # Check that the HDF5 files contains chunked array # of weight names. - with h5py.File(fname, 'r') as h5file: + with h5py.File(saved_model_dir, 'r') as h5file: num_weight_arrays = len( [attr for attr in h5file['model_weights']['nested_model'].attrs if attr.startswith('weight_names')]) @@ -725,15 +704,10 @@ class TestWholeModelSaving(test.TestCase): out2 = model.predict(x) self.assertAllClose(out, out2, atol=1e-05) - # Cleanup - os.close(fd) - os.remove(fname) - @test_util.run_deprecated_v1 def test_model_saving_to_pre_created_h5py_file(self): - if h5py is None: - self.skipTest('h5py required to run this test') - + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() with self.cached_session(): inputs = keras.Input(shape=(3,)) x = keras.layers.Dense(2)(inputs) @@ -752,6 +726,15 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) out = model.predict(x) + + keras.models.save_model(model, saved_model_dir, save_format=save_format) + loaded_model = keras.models.load_model(saved_model_dir) + out1 = loaded_model.predict(x) + self.assertAllClose(out, out1, atol=1e-05) + if save_format in ['tf', 'tensorflow']: + return + + # Test h5 format specifically fd, fname = tempfile.mkstemp('.h5') with h5py.File(fname, mode='r+') as h5file: keras.models.save_model(model, h5file) @@ -772,42 +755,38 @@ class TestWholeModelSaving(test.TestCase): os.remove(fname) def test_saving_constant_initializer_with_numpy(self): - if h5py is None: - self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() - with self.cached_session(): - model = keras.models.Sequential() - model.add( - keras.layers.Dense( - 2, - input_shape=(3,), - kernel_initializer=keras.initializers.Constant(np.ones((3, 2))))) - model.add(keras.layers.Dense(3)) - model.compile(loss='mse', optimizer='sgd', metrics=['acc']) - fd, fname = tempfile.mkstemp('.h5') - keras.models.save_model(model, fname) - model = keras.models.load_model(fname) - os.close(fd) - os.remove(fname) + model = keras.models.Sequential() + model.add( + keras.layers.Dense( + 2, + input_shape=(3,), + kernel_initializer=keras.initializers.Constant(np.ones((3, 2))))) + model.add(keras.layers.Dense(3)) + model.compile(loss='mse', optimizer='sgd', metrics=['acc']) + keras.models.save_model(model, saved_model_dir, save_format=save_format) + model = keras.models.load_model(saved_model_dir) def test_primitive_attrs_contain_no_extraneous_strings(self): if h5py is None: self.skipTest('h5py required to run this test') + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() model = keras.models.Sequential() model.add(keras.layers.Dense(1, input_shape=[2])) - fname = os.path.join(self.get_temp_dir(), 'model.h5') - model.save(fname) + model.save(saved_model_dir, save_format=save_format) + if save_format in ['tf', 'tensorflow']: + return - h5file = h5py.File(fname, 'r') + h5file = h5py.File(saved_model_dir, 'r') self.assertRegexpMatches( h5file.attrs['keras_version'], r'^[\d]+\.[\d]+\.[\S]+$') @test_util.run_in_graph_and_eager_modes def test_functional_model_with_custom_loss_and_metric(self): - if h5py is None: - self.skipTest('h5py required to run this test') - def _make_model(): inputs = keras.Input(shape=(4,)) x = keras.layers.Dense(8, activation='relu')(inputs) @@ -818,6 +797,12 @@ class TestWholeModelSaving(test.TestCase): model.add_metric(custom_loss, aggregation='mean', name='custom_loss') return model + saved_model_dir = self._save_model_dir() + save_format = testing_utils.get_save_format() + # TODO(b/143487125): skip tf format for now. + if save_format in ['tf', 'tensorflow']: + return + model = _make_model() model.compile( loss=keras.losses.SparseCategoricalCrossentropy(), @@ -828,13 +813,12 @@ class TestWholeModelSaving(test.TestCase): model.train_on_batch(x, y) evaluation_results = model.evaluate(x, y) # Save and reload model. - model_path = os.path.join(self.get_temp_dir(), 'model.h5') - model.save(model_path) + model.save(saved_model_dir, save_format=save_format) del model # Prevent misuse. - loaded_model = keras.models.load_model(model_path) - os.remove(model_path) + loaded_model = keras.models.load_model(saved_model_dir) + loaded_model_eval_results = loaded_model.evaluate(x, y) # Assert all evaluation results are the same. - self.assertAllClose(evaluation_results, loaded_model.evaluate(x, y), 1e-9) + self.assertAllClose(evaluation_results, loaded_model_eval_results, 1e-9) # Check correctness of the loss calculation. self.assertAllGreater(evaluation_results, 0.) evaluation_results = dict( diff --git a/tensorflow/python/keras/saving/saving_utils.py b/tensorflow/python/keras/saving/saving_utils.py index bed126c6f09..1bbae9468c8 100644 --- a/tensorflow/python/keras/saving/saving_utils.py +++ b/tensorflow/python/keras/saving/saving_utils.py @@ -219,6 +219,18 @@ def should_overwrite(filepath, overwrite): return True +def convert_output_metrics(metrics_config, custom_objects): + from tensorflow.python.keras import metrics as metrics_module # pylint:disable=g-import-not-at-top + if isinstance(metrics_config, list): + return [convert_output_metrics(mc, custom_objects) for mc in metrics_config] + elif (isinstance(metrics_config, dict) or + (metrics_config not in ['accuracy', 'acc', 'crossentropy', 'ce'])): + # Do not deserialize accuracy and cross-entropy strings as we have special + # case handling for these in compile, based on model output shape. + return metrics_module.deserialize(metrics_config, custom_objects) + return metrics_config + + def compile_args_from_training_config(training_config, custom_objects=None): """Return model.compile arguments from training config.""" if custom_objects is None: @@ -228,17 +240,50 @@ def compile_args_from_training_config(training_config, custom_objects=None): optimizer = optimizers.deserialize( optimizer_config, custom_objects=custom_objects) - # Recover loss functions and metrics. - loss_config = training_config['loss'] # Deserialize loss class. - if isinstance(loss_config, dict) and 'class_name' in loss_config: - loss_config = losses.get(loss_config) - loss = nest.map_structure( - lambda obj: custom_objects.get(obj, obj), loss_config) - metrics = nest.map_structure( - lambda obj: custom_objects.get(obj, obj), training_config['metrics']) - weighted_metrics = nest.map_structure( - lambda obj: custom_objects.get(obj, obj), - training_config.get('weighted_metrics', None)) + # Recover losses. + loss_config = training_config['loss'] + if isinstance(loss_config, list): # Loss fed to compile as a list. + loss = [losses.deserialize(lc, custom_objects) for lc in loss_config] + elif isinstance(loss_config, dict) and 'class_name' not in loss_config: + # Loss fed to compile as a dict. + loss = { + k: losses.deserialize(v, custom_objects) + for (k, v) in loss_config.items() + } + else: # Loss fed to compile as a str/ function/ class instance. + loss = losses.deserialize(loss_config, custom_objects) + + # Recover metrics. + metrics_config = training_config.get('metrics', None) + if isinstance(metrics_config, dict): # Metrics fed to compile as a dict. + metrics = { + k: convert_output_metrics(v, custom_objects) + for (k, v) in metrics_config.items() + } + elif isinstance(metrics_config, list): # Metrics fed to compile as a list. + metrics = [ + convert_output_metrics(m, custom_objects) for m in metrics_config + ] + else: # No metrics. + metrics = None + + # Recover weighted metrics. + weighted_metrics_config = training_config.get('weighted_metrics', None) + if isinstance(weighted_metrics_config, dict): + # Metrics fed to compile as a dict. + weighted_metrics = { + k: convert_output_metrics(v, custom_objects) + for (k, v) in weighted_metrics_config.items() + } + elif isinstance(weighted_metrics_config, list): + # Metrics fed to compile as a list. + weighted_metrics = [ + convert_output_metrics(m, custom_objects) + for m in weighted_metrics_config + ] + else: # No metrics. + weighted_metrics = None + sample_weight_mode = training_config['sample_weight_mode'] loss_weights = training_config['loss_weights'] diff --git a/tensorflow/python/keras/utils/generic_utils.py b/tensorflow/python/keras/utils/generic_utils.py index 8ff27a38d77..4fbb6d68eeb 100644 --- a/tensorflow/python/keras/utils/generic_utils.py +++ b/tensorflow/python/keras/utils/generic_utils.py @@ -453,7 +453,8 @@ class Progbar(object): self._dynamic_display = ((hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()) or 'ipykernel' in sys.modules or - 'posix' in sys.modules) + 'posix' in sys.modules or + 'PYCHARM_HOSTED' in os.environ) self._total_width = 0 self._seen_so_far = 0 # We use a dict + list to avoid garbage collection @@ -680,7 +681,7 @@ def to_list(x): def object_list_uid(object_list): """Creates a single string from object ids.""" object_list = nest.flatten(object_list) - return ', '.join([str(abs(id(x))) for x in object_list]) + return ', '.join(str(abs(id(x))) for x in object_list) def to_snake_case(name): diff --git a/tensorflow/python/keras/utils/tf_utils.py b/tensorflow/python/keras/utils/tf_utils.py index cec7497851f..889da83f655 100644 --- a/tensorflow/python/keras/utils/tf_utils.py +++ b/tensorflow/python/keras/utils/tf_utils.py @@ -402,7 +402,7 @@ def assert_no_legacy_layers(layers): # isinstance check for tf.layers.Layer introduces a circular dependency. legacy_layers = [l for l in layers if getattr(l, '_is_legacy_layer', None)] if legacy_layers: - layer_str = '\n'.join([' ' + str(l) for l in legacy_layers]) + layer_str = '\n'.join(' ' + str(l) for l in legacy_layers) raise TypeError( 'The following are legacy tf.layers.Layers:\n{}\nTo use keras as a ' 'framework (for instance using the Network, Model, or Sequential ' diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD index bbc0f34d2c7..fb25e50b322 100644 --- a/tensorflow/python/kernel_tests/BUILD +++ b/tensorflow/python/kernel_tests/BUILD @@ -16,26 +16,26 @@ tf_py_test( name = "as_string_op_test", size = "small", srcs = ["as_string_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", + "//third_party/py/numpy", ], - tags = ["no_windows"], ) tf_py_test( name = "attention_ops_test", size = "small", srcs = ["attention_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:image_ops", + "//third_party/py/numpy", ], ) @@ -43,54 +43,53 @@ tf_py_test( name = "barrier_ops_test", size = "medium", # NOTE(ebrevdo): This test is NOT small. srcs = ["barrier_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + # TODO(b/129706424): Re-enable this test on Mac. + tags = ["no_mac"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], - shard_count = 20, - # TODO(b/129706424): Re-enable this test on Mac. - tags = ["no_mac"], ) tf_py_test( name = "base64_ops_test", size = "small", srcs = ["base64_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["nomac"], # b/35468214 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:string_ops", + "//third_party/py/numpy", ], - tags = ["nomac"], # b/35468214 ) tf_py_test( name = "batch_gather_op_test", srcs = ["batch_gather_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + tags = [ + "no_gpu", # b/127001953 + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", - ], - tags = [ - "no_gpu", # b/127001953 + "@absl_py//absl/testing:parameterized", ], ) tf_py_test( name = "batch_scatter_ops_test", srcs = ["batch_scatter_ops_test.py"], - additional_deps = [ - "//tensorflow/python/eager:context", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -102,6 +101,7 @@ tf_py_test( "//tensorflow/python:session", "//tensorflow/python:state_ops", "//tensorflow/python:variables", + "//tensorflow/python/eager:context", ], ) @@ -109,7 +109,7 @@ tf_py_test( name = "bcast_ops_test", size = "small", srcs = ["bcast_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops_gen", "//tensorflow/python:client_testlib", ], @@ -119,51 +119,51 @@ cuda_py_test( name = "list_ops_test", size = "small", srcs = ["list_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + grpc_enabled = True, + tags = ["no_rocm"], + deps = [ "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", "//tensorflow/python:gradients_impl", "//tensorflow/python:list_ops", "//tensorflow/python:math_ops", "//tensorflow/python:tensor_shape", "//tensorflow/python/eager:context", "//tensorflow/python/eager:def_function", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:client_testlib", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - grpc_enabled = True, - tags = ["no_rocm"], ) cuda_py_test( name = "benchmark_test", size = "small", srcs = ["benchmark_test.py"], - additional_deps = [ + tags = ["no_windows"], + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", "//tensorflow/python:platform_benchmark", ], - tags = ["no_windows"], ) cuda_py_test( name = "reduce_benchmark_test", srcs = ["reduce_benchmark_test.py"], - additional_deps = [ - "//tensorflow/python/eager:backprop", - "//tensorflow/python:client_testlib", - "//tensorflow/python/eager:context", - "//tensorflow/python:framework", + deps = [ "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", "//tensorflow/python:gradients", "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python:platform_benchmark", + "//tensorflow/python/eager:backprop", + "//tensorflow/python/eager:context", ], ) @@ -171,25 +171,25 @@ cuda_py_test( name = "bincount_op_test", size = "small", srcs = ["bincount_op_test.py"], - additional_deps = [ - "//tensorflow/python:client_testlib", - "//tensorflow/python:math_ops", - "//tensorflow/python:framework_for_generated_wrappers", - ], tags = ["no_windows_gpu"], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:math_ops", + ], ) tf_py_test( name = "candidate_sampler_ops_test", size = "small", srcs = ["candidate_sampler_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:candidate_sampling_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -197,8 +197,7 @@ tf_py_test( name = "checkpoint_ops_test", size = "medium", srcs = ["checkpoint_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:checkpoint_ops_gen", "//tensorflow/python:client_testlib", @@ -212,6 +211,7 @@ tf_py_test( "//tensorflow/python:training", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -219,8 +219,12 @@ cuda_py_test( name = "cholesky_op_test", size = "medium", srcs = ["cholesky_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 5, + tags = [ + "no_rocm", # TODO(rocm): feature not supported on ROCm platform + "nomsan", # TODO(b/131773093): Re-enable. + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -228,11 +232,7 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python/ops/linalg", - ], - shard_count = 5, - tags = [ - "no_rocm", # TODO(rocm): feature not supported on ROCm platform - "nomsan", # TODO(b/131773093): Re-enable. + "//third_party/py/numpy", ], ) @@ -240,23 +240,22 @@ tf_py_test( name = "clip_ops_test", size = "small", srcs = ["clip_ops_test.py"], - additional_deps = [ - "//tensorflow/python:client_testlib", - "//tensorflow/python:clip_ops", - "//tensorflow/python:framework_for_generated_wrappers", - ], tags = [ "no_gpu", # b/127001953 "no_windows", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:clip_ops", + "//tensorflow/python:framework_for_generated_wrappers", + ], ) tf_py_test( name = "conditional_accumulator_test", size = "small", srcs = ["conditional_accumulator_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", @@ -265,6 +264,7 @@ tf_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:state_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -272,12 +272,12 @@ tf_py_test( name = "ctc_decoder_ops_test", size = "small", srcs = ["ctc_decoder_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:ctc_ops", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -285,13 +285,13 @@ cuda_py_test( name = "ctc_loss_op_test", size = "medium", srcs = ["ctc_loss_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:ctc_ops", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", + "//third_party/py/numpy", ], ) @@ -311,17 +311,17 @@ cuda_py_test( name = "cudnn_deterministic_ops_test", size = "small", srcs = ["cudnn_deterministic_ops_test.py"], - additional_deps = [ + xla_enable_strict_auto_jit = True, + deps = [ ":cudnn_deterministic_base", ], - xla_enable_strict_auto_jit = True, ) cuda_py_test( name = "cudnn_deterministic_test", size = "small", srcs = ["cudnn_deterministic_test.py"], - additional_deps = [ + deps = [ ":cudnn_deterministic_base", ], ) @@ -330,14 +330,14 @@ cuda_py_test( name = "cumulative_logsumexp_test", size = "medium", srcs = ["cumulative_logsumexp_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ + "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:math_ops", "//tensorflow/python:map_fn", - "//tensorflow/python:array_ops", + "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -345,13 +345,13 @@ tf_py_test( name = "decode_csv_op_test", size = "small", srcs = ["decode_csv_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python/eager:context", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_test_lib", "//tensorflow/python:parsing_ops", + "//tensorflow/python/eager:context", + "//third_party/py/numpy", ], ) @@ -359,7 +359,7 @@ tf_py_test( name = "decode_png_op_test", size = "small", srcs = ["decode_png_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -372,7 +372,7 @@ tf_py_test( name = "decode_bmp_op_test", size = "small", srcs = ["decode_bmp_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -384,40 +384,40 @@ tf_py_test( tf_py_test( name = "decode_jpeg_op_test", srcs = ["decode_jpeg_op_test.py"], - additional_deps = [ + data = ["//tensorflow/core:image_testdata"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:image_ops", "//tensorflow/python:platform", ], - data = ["//tensorflow/core:image_testdata"], ) tf_py_test( name = "decode_image_op_test", size = "small", srcs = ["decode_image_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + data = ["//tensorflow/core:image_testdata"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:image_ops", "//tensorflow/python:io_ops", "//tensorflow/python:nn_grad", + "//third_party/py/numpy", ], - data = ["//tensorflow/core:image_testdata"], ) tf_py_test( name = "decode_raw_op_test", size = "small", srcs = ["decode_raw_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:parsing_ops", + "//third_party/py/numpy", ], ) @@ -425,12 +425,12 @@ tf_py_test( name = "decode_compressed_op_test", size = "small", srcs = ["decode_compressed_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:parsing_ops", + "//third_party/py/numpy", ], ) @@ -438,11 +438,11 @@ cuda_py_test( name = "determinant_op_test", size = "small", srcs = ["determinant_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", ], ) @@ -450,13 +450,13 @@ tf_py_test( name = "draw_bounding_box_op_test", size = "small", srcs = ["draw_bounding_box_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:image_ops", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -464,12 +464,12 @@ tf_py_test( name = "edit_distance_op_test", size = "small", srcs = ["edit_distance_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -477,8 +477,7 @@ tf_py_test( name = "fifo_queue_test", size = "small", srcs = ["fifo_queue_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -487,6 +486,7 @@ tf_py_test( "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:util", + "//third_party/py/numpy", ], ) @@ -494,7 +494,7 @@ tf_py_test( name = "fingerprint_op_test", size = "small", srcs = ["fingerprint_op_test.py"], - additional_deps = [ + deps = [ "//third_party/py/numpy", ], ) @@ -503,45 +503,45 @@ tf_py_test( name = "fractional_avg_pool_op_test", size = "small", srcs = ["fractional_avg_pool_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 5, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", "//tensorflow/python:nn_ops_gen", + "//third_party/py/numpy", ], - shard_count = 5, ) tf_py_test( name = "fractional_max_pool_op_test", size = "small", srcs = ["fractional_max_pool_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 5, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", "//tensorflow/python:nn_ops_gen", + "//third_party/py/numpy", ], - shard_count = 5, ) tf_py_test( name = "identity_op_py_test", size = "small", srcs = ["identity_op_py_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:array_ops_gen", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -549,13 +549,13 @@ tf_py_test( name = "identity_n_op_py_test", size = "small", srcs = ["identity_n_op_py_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:array_ops_gen", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -563,11 +563,11 @@ cuda_py_test( name = "in_topk_op_test", size = "small", srcs = ["in_topk_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -575,7 +575,7 @@ tf_py_test( name = "record_input_test", size = "medium", srcs = ["record_input_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", "//tensorflow/python:io_ops", @@ -587,7 +587,7 @@ tf_py_test( name = "io_ops_test", size = "small", srcs = ["io_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:io_ops", "//tensorflow/python:util", @@ -598,12 +598,12 @@ tf_py_test( name = "listdiff_op_test", size = "small", srcs = ["listdiff_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:util", + "//third_party/py/numpy", ], ) @@ -611,22 +611,22 @@ tf_py_test( name = "logging_ops_logging_level_test", size = "small", srcs = ["logging_ops_logging_level_test.py"], - additional_deps = [ + tags = [ + "no_windows", + ], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:logging_ops", ], - tags = [ - "no_windows", - ], ) cuda_py_test( name = "logging_ops_test", size = "small", srcs = ["logging_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", "//tensorflow/python:framework_for_generated_wrappers", @@ -641,7 +641,8 @@ tf_py_test( name = "lookup_ops_test", size = "small", srcs = ["lookup_ops_test.py"], - additional_deps = [ + grpc_enabled = True, + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -651,16 +652,13 @@ tf_py_test( "//tensorflow/python:sparse_tensor", "//tensorflow/python:training", ], - grpc_enabled = True, ) tf_py_test( name = "losses_test", size = "medium", srcs = ["losses_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python/ops/losses", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -671,6 +669,8 @@ tf_py_test( "//tensorflow/python:training", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//tensorflow/python/ops/losses", + "//third_party/py/numpy", ], ) @@ -678,25 +678,25 @@ tf_py_test( name = "matrix_exponential_op_test", size = "medium", srcs = ["matrix_exponential_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 16, + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", ], - shard_count = 16, - tags = ["no_windows_gpu"], ) tf_py_test( name = "matrix_logarithm_op_test", size = "medium", srcs = ["matrix_logarithm_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", ], ) @@ -704,27 +704,27 @@ cuda_py_test( name = "matrix_inverse_op_test", size = "small", srcs = ["matrix_inverse_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["optonly"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], - tags = ["optonly"], ) cuda_py_test( name = "matrix_solve_ls_op_test", size = "medium", srcs = ["matrix_solve_ls_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -732,11 +732,11 @@ cuda_py_test( name = "matrix_square_root_op_test", size = "medium", srcs = ["matrix_square_root_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", ], ) @@ -744,12 +744,12 @@ cuda_py_test( name = "matrix_solve_op_test", size = "medium", srcs = ["matrix_solve_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", ], ) @@ -757,10 +757,10 @@ cuda_py_test( name = "matrix_triangular_solve_op_test", size = "small", srcs = ["matrix_triangular_solve_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", ], ) @@ -768,9 +768,7 @@ cuda_py_test( name = "parameterized_truncated_normal_op_test", size = "medium", srcs = ["parameterized_truncated_normal_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -779,6 +777,8 @@ cuda_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", "//tensorflow/python:random_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -786,8 +786,7 @@ tf_py_test( name = "parsing_ops_test", size = "medium", srcs = ["parsing_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -796,6 +795,7 @@ tf_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:parsing_ops", "//tensorflow/python:platform", + "//third_party/py/numpy", ], ) @@ -803,8 +803,7 @@ tf_py_test( name = "parse_single_example_op_test", size = "small", srcs = ["parse_single_example_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -813,6 +812,7 @@ tf_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:parsing_ops", "//tensorflow/python:platform", + "//third_party/py/numpy", ], ) @@ -820,8 +820,7 @@ tf_py_test( name = "partitioned_variables_test", size = "small", srcs = ["partitioned_variables_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -830,6 +829,7 @@ tf_py_test( "//tensorflow/python:random_ops", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -837,14 +837,14 @@ tf_py_test( name = "priority_queue_test", size = "medium", srcs = ["priority_queue_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", + "//third_party/py/numpy", ], ) @@ -852,31 +852,31 @@ cuda_py_test( name = "resource_variable_ops_test", size = "medium", srcs = ["resource_variable_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + # TODO(b/128347673): Re-enable. + tags = ["no_windows"], + deps = [ "//tensorflow/python:array_ops", - "//tensorflow/python:constant_op", "//tensorflow/python:client_testlib", + "//tensorflow/python:constant_op", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:variables", + "@absl_py//absl/testing:parameterized", ], - # TODO(b/128347673): Re-enable. - tags = ["no_windows"], ) tf_py_test( name = "regex_replace_op_test", size = "small", srcs = ["regex_replace_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", "//tensorflow/python:string_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -884,12 +884,12 @@ tf_py_test( name = "regex_full_match_op_test", size = "small", srcs = ["regex_full_match_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", "//tensorflow/python:string_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -897,7 +897,7 @@ tf_py_test( name = "save_restore_ops_test", size = "small", srcs = ["save_restore_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -911,32 +911,33 @@ cuda_py_test( name = "scatter_nd_ops_test", size = "medium", srcs = ["scatter_nd_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["noasan"], # http://b/32635055 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", + "//tensorflow/python:resource_variable_ops", "//tensorflow/python:state_ops", "//tensorflow/python:variables", - "//tensorflow/python:resource_variable_ops", + "//third_party/py/numpy", ], - tags = ["noasan"], # http://b/32635055 ) tf_py_test( name = "segment_reduction_ops_test", size = "medium", srcs = ["segment_reduction_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 10, + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", - "//tensorflow/python:variables", "//tensorflow/python:nn_grad", + "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -944,8 +945,7 @@ tf_py_test( name = "sparse_add_op_test", size = "small", srcs = ["sparse_add_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -953,6 +953,7 @@ tf_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:sparse_grad", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -960,13 +961,13 @@ tf_py_test( name = "sparse_concat_op_test", size = "small", srcs = ["sparse_concat_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -974,13 +975,13 @@ tf_py_test( name = "sparse_conditional_accumulator_test", size = "small", srcs = ["sparse_conditional_accumulator_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -988,14 +989,14 @@ tf_py_test( name = "sparse_reorder_op_test", size = "small", srcs = ["sparse_reorder_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_grad", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -1003,13 +1004,13 @@ tf_py_test( name = "sparse_reshape_op_test", size = "small", srcs = ["sparse_reshape_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -1017,11 +1018,11 @@ tf_py_test( name = "sparse_split_op_test", size = "small", srcs = ["sparse_split_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -1029,12 +1030,12 @@ tf_py_test( name = "sparse_slice_op_test", size = "small", srcs = ["sparse_slice_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:sparse_grad", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -1042,12 +1043,12 @@ tf_py_test( name = "sparse_to_dense_op_py_test", size = "small", srcs = ["sparse_to_dense_op_py_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -1055,11 +1056,11 @@ tf_py_test( name = "sparsemask_op_test", size = "small", srcs = ["sparsemask_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -1067,12 +1068,12 @@ tf_py_test( name = "string_format_op_test", size = "small", srcs = ["string_format_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", - "//tensorflow/python:string_ops", "//tensorflow/python:math_ops", + "//tensorflow/python:string_ops", ], ) @@ -1080,7 +1081,7 @@ tf_py_test( name = "string_join_op_test", size = "small", srcs = ["string_join_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:string_ops", ], @@ -1090,18 +1091,18 @@ tf_py_test( name = "string_split_op_test", size = "small", srcs = ["string_split_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/ops/ragged:ragged_factory_ops", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:string_ops", - "//tensorflow/python/ops/ragged:ragged_string_ops", "//tensorflow/python:util", + "//tensorflow/python/ops/ragged:ragged_factory_ops", + "//tensorflow/python/ops/ragged:ragged_string_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1109,11 +1110,7 @@ tf_py_test( name = "string_bytes_split_op_test", size = "small", srcs = ["string_bytes_split_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/ops/ragged:ragged_factory_ops", - "//tensorflow/python/ops/ragged:ragged_string_ops", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -1121,6 +1118,10 @@ tf_py_test( "//tensorflow/python:framework_test_lib", "//tensorflow/python:string_ops", "//tensorflow/python/ops/ragged", + "//tensorflow/python/ops/ragged:ragged_factory_ops", + "//tensorflow/python/ops/ragged:ragged_string_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1128,7 +1129,7 @@ tf_py_test( name = "string_length_op_test", size = "small", srcs = ["string_length_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", @@ -1139,13 +1140,13 @@ tf_py_test( name = "string_strip_op_test", size = "small", srcs = ["string_strip_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", + "//third_party/py/numpy", ], ) @@ -1153,13 +1154,13 @@ tf_py_test( name = "string_lower_op_test", size = "small", srcs = ["string_lower_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", + "//third_party/py/numpy", ], ) @@ -1167,13 +1168,13 @@ tf_py_test( name = "string_upper_op_test", size = "small", srcs = ["string_upper_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", + "//third_party/py/numpy", ], ) @@ -1181,12 +1182,12 @@ tf_py_test( name = "substr_op_test", size = "small", srcs = ["substr_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:string_ops", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1194,7 +1195,7 @@ cuda_py_test( name = "summary_ops_test", size = "small", srcs = ["summary_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -1206,14 +1207,14 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python:summary_ops_v2", - "@six_archive//:six", "//tensorflow/python:tensor_spec", "//tensorflow/python:tensor_util", "//tensorflow/python:variables", - "//tensorflow/python/eager:function", "//tensorflow/python/eager:context", + "//tensorflow/python/eager:function", "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers", + "@six_archive//:six", ], ) @@ -1221,7 +1222,7 @@ tf_py_test( name = "summary_v1_ops_test", size = "small", srcs = ["summary_v1_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -1234,15 +1235,15 @@ tf_py_test( name = "summary_v1_tensor_op_test", size = "small", srcs = ["summary_v1_tensor_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "@six_archive//:six", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:summary", + "//third_party/py/numpy", + "@six_archive//:six", ], ) @@ -1250,7 +1251,7 @@ tf_py_test( name = "template_test", size = "small", srcs = ["template_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -1268,14 +1269,14 @@ cuda_py_test( name = "template_mirrored_strategy_test", size = "small", srcs = ["template_mirrored_strategy_test.py"], - additional_deps = [ - "//tensorflow/python/distribute:distribute_lib", - "//tensorflow/python/distribute:mirrored_strategy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:init_ops", "//tensorflow/python:template", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//tensorflow/python/distribute:distribute_lib", + "//tensorflow/python/distribute:mirrored_strategy", ], ) @@ -1283,24 +1284,24 @@ cuda_py_test( name = "tridiagonal_solve_op_test", size = "medium", srcs = ["tridiagonal_solve_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:linalg_ops", - ], shard_count = 10, tags = [ "no_oss", # TODO(b/142818120): Re-enable. "no_rocm", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", + ], ) tf_py_test( name = "unicode_script_op_test", size = "small", srcs = ["unicode_script_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -1312,14 +1313,14 @@ cuda_py_test( name = "topk_op_test", size = "medium", srcs = ["topk_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -1327,14 +1328,14 @@ cuda_py_test( name = "nth_element_op_test", size = "small", srcs = ["nth_element_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -1342,17 +1343,17 @@ tf_py_test( name = "unicode_encode_op_test", size = "small", srcs = ["unicode_encode_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/ops/ragged:ragged_tensor", - "//tensorflow/python/ops/ragged:ragged_tensor_value", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:errors", + "//tensorflow/python:framework_test_lib", "//tensorflow/python/ops/ragged:ragged_factory_ops", "//tensorflow/python/ops/ragged:ragged_string_ops", - "//tensorflow/python:framework_test_lib", + "//tensorflow/python/ops/ragged:ragged_tensor", + "//tensorflow/python/ops/ragged:ragged_tensor_value", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1360,11 +1361,11 @@ tf_py_test( name = "unicode_transcode_op_test", size = "small", srcs = ["unicode_transcode_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -1372,19 +1373,19 @@ tf_py_test( name = "unicode_decode_op_test", size = "small", srcs = ["unicode_decode_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//tensorflow/python/eager:context", - "//tensorflow/python/ops/ragged:ragged_factory_ops", - "//tensorflow/python/ops/ragged:ragged_string_ops", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python/ops/ragged:ragged", "//tensorflow/python:framework_test_lib", "//tensorflow/python:sparse_tensor", "//tensorflow/python:string_ops", + "//tensorflow/python/eager:context", + "//tensorflow/python/ops/ragged", + "//tensorflow/python/ops/ragged:ragged_factory_ops", + "//tensorflow/python/ops/ragged:ragged_string_ops", + "@absl_py//absl/testing:parameterized", ], ) @@ -1392,10 +1393,10 @@ tf_py_test( name = "unique_op_test", size = "small", srcs = ["unique_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", ], ) @@ -1403,8 +1404,8 @@ tf_py_test( name = "variable_scope_test", size = "small", srcs = ["variable_scope_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", "//tensorflow/python:errors", @@ -1413,25 +1414,24 @@ tf_py_test( "//tensorflow/python:init_ops", "//tensorflow/python:layers", "//tensorflow/python:math_ops", - "//tensorflow/python:variable_scope", "//tensorflow/python:resource_variable_ops", "//tensorflow/python:state_ops", "//tensorflow/python:util", + "//tensorflow/python:variable_scope", "//tensorflow/python:variables", "//tensorflow/python/eager:context", "//tensorflow/python/eager:function", "//tensorflow/python/eager:wrap_function", + "//third_party/py/numpy", ], - tags = ["no_windows"], ) tf_py_test( name = "variables_test", size = "small", srcs = ["variables_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + tags = ["no_windows"], # b/133869052 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", @@ -1444,19 +1444,20 @@ tf_py_test( "//tensorflow/python:util", "//tensorflow/python:variables", "//tensorflow/python/eager:function", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - tags = ["no_windows"], # b/133869052 ) cuda_py_test( name = "where_op_test", size = "medium", srcs = ["where_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -1464,15 +1465,6 @@ cuda_py_test( name = "cast_op_test", size = "small", srcs = ["cast_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:math_ops", - "//tensorflow/python:variables", - ], tags = [ "no_windows", "noasan", @@ -1480,47 +1472,56 @@ cuda_py_test( "notap", "optonly", ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:math_ops", + "//tensorflow/python:variables", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "dense_update_ops_no_tsan_test", size = "small", srcs = ["dense_update_ops_no_tsan_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["notsan"], + # TODO (b/140294007): the test fails with XLA. + xla_enable_strict_auto_jit = False, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:math_ops", "//tensorflow/python:state_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - tags = ["notsan"], - # TODO (b/140294007): the test fails with XLA. - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "diag_op_test", size = "medium", srcs = ["diag_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 2, + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", "//tensorflow/python:platform", + "//third_party/py/numpy", ], - shard_count = 2, - tags = ["no_windows_gpu"], ) tf_py_test( name = "reader_ops_test", size = "small", srcs = ["reader_ops_test.py"], - additional_deps = [ - "@six_archive//:six", + data = ["//tensorflow/core:lmdb_testdata"], + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", @@ -1530,20 +1531,20 @@ tf_py_test( "//tensorflow/python:lib", "//tensorflow/python:util", "//tensorflow/python:variables", + "@six_archive//:six", ], - data = ["//tensorflow/core:lmdb_testdata"], ) cuda_py_test( name = "aggregate_ops_test", size = "small", srcs = ["aggregate_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -1551,10 +1552,10 @@ cuda_py_test( name = "argmax_op_test", size = "small", srcs = ["argmax_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -1562,8 +1563,12 @@ cuda_py_test( name = "array_ops_test", size = "medium", srcs = ["array_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 10, + tags = [ + "noasan", # times out + "optonly", # times out + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -1579,11 +1584,7 @@ cuda_py_test( "//tensorflow/python:variables", "//tensorflow/python/eager:context", "//tensorflow/python/eager:def_function", - ], - shard_count = 10, - tags = [ - "noasan", # times out - "optonly", # times out + "//third_party/py/numpy", ], ) @@ -1591,11 +1592,11 @@ cuda_py_test( name = "broadcast_to_ops_test", size = "small", srcs = ["broadcast_to_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", + "//third_party/py/numpy", ], ) @@ -1603,42 +1604,42 @@ cuda_py_test( name = "inplace_ops_test", size = "small", srcs = ["inplace_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 10, + deps = [ "//tensorflow/python:array_ops", - "//tensorflow/python:errors", "//tensorflow/python:client_testlib", + "//tensorflow/python:errors", "//tensorflow/python:framework", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], - shard_count = 10, ) cuda_py_test( name = "batch_matmul_op_test", size = "small", srcs = ["batch_matmul_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], - shard_count = 20, ) cuda_py_test( name = "batchtospace_op_test", size = "small", srcs = ["batchtospace_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:array_ops_gen", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -1646,13 +1647,13 @@ cuda_py_test( name = "betainc_op_test", size = "small", srcs = ["betainc_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:platform", + "//third_party/py/numpy", ], ) @@ -1674,17 +1675,17 @@ cuda_py_test( name = "bias_op_deterministic_test", size = "medium", srcs = ["bias_op_deterministic_test.py"], - additional_deps = [ + xla_enable_strict_auto_jit = False, + deps = [ ":bias_op_base", ], - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "bias_op_test", size = "medium", srcs = ["bias_op_test.py"], - additional_deps = [ + deps = [ ":bias_op_base", ], ) @@ -1693,11 +1694,11 @@ cuda_py_test( name = "bitcast_op_test", size = "small", srcs = ["bitcast_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -1705,17 +1706,17 @@ cuda_py_test( name = "check_ops_test", size = "small", srcs = ["check_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python/eager:context", - "//tensorflow/python/eager:def_function", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:check_ops", - "//tensorflow/python:math_ops", - "//tensorflow/python:random_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:math_ops", + "//tensorflow/python:random_ops", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:def_function", + "//third_party/py/numpy", ], ) @@ -1723,14 +1724,14 @@ cuda_py_test( name = "constant_op_test", size = "small", srcs = ["constant_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:util", + "//third_party/py/numpy", ], ) @@ -1738,17 +1739,17 @@ cuda_py_test( name = "constant_op_eager_test", size = "small", srcs = ["constant_op_eager_test.py"], - additional_deps = [ - "//tensorflow/python/eager:core", - "//tensorflow/python/eager:context", - "//tensorflow/python/eager:test", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:util", + "//tensorflow/python/eager:context", + "//tensorflow/python/eager:core", + "//tensorflow/python/eager:test", + "//third_party/py/numpy", ], ) @@ -1756,9 +1757,11 @@ cuda_py_test( name = "control_flow_ops_py_test", size = "small", srcs = ["control_flow_ops_py_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + shard_count = 16, + tags = [ + "notsan", # TODO(b/132205147): Re-enable this. + ], + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:array_ops_gen", @@ -1788,10 +1791,8 @@ cuda_py_test( "//tensorflow/python:variable_scope", "//tensorflow/python:variables", "//tensorflow/python:while_v2", - ], - shard_count = 16, - tags = [ - "notsan", # TODO(b/132205147): Re-enable this. + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -1799,7 +1800,7 @@ tf_py_test( name = "control_flow_util_test", size = "small", srcs = ["control_flow_util_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", "//tensorflow/python:control_flow_ops_gen", @@ -1812,7 +1813,7 @@ tf_py_test( name = "control_flow_util_v2_test", size = "small", srcs = ["control_flow_util_v2_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:cond_v2", "//tensorflow/python:constant_op", @@ -1826,7 +1827,7 @@ cuda_py_test( name = "conv1d_test", size = "small", srcs = ["conv1d_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -1838,12 +1839,12 @@ cuda_py_test( name = "conv1d_transpose_test", size = "small", srcs = ["conv1d_transpose_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -1851,31 +1852,31 @@ cuda_py_test( name = "conv2d_transpose_test", size = "small", srcs = ["conv2d_transpose_test.py"], - additional_deps = [ - "//third_party/py/numpy", + + # TODO(b/144432983): S32 convolutions should not be auto-clustered, only + # crashes tests. + xla_enable_strict_auto_jit = False, + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], - - # TODO(b/144432983): S32 convolutions should not be auto-clustered, only - # crashes tests. - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "conv3d_backprop_filter_v2_grad_test", size = "small", srcs = ["conv3d_backprop_filter_v2_grad_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -1883,7 +1884,7 @@ cuda_py_test( name = "cross_grad_test", size = "small", srcs = ["cross_grad_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:math_ops", @@ -1894,12 +1895,12 @@ cuda_py_test( name = "denormal_test", size = "small", srcs = ["denormal_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", + "//third_party/py/numpy", ], ) @@ -1907,14 +1908,14 @@ cuda_py_test( name = "dense_update_ops_test", size = "small", srcs = ["dense_update_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:state_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -1922,40 +1923,40 @@ cuda_py_test( name = "depthtospace_op_test", size = "medium", srcs = ["depthtospace_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], - tags = ["no_windows_gpu"], ) cuda_py_test( name = "division_past_test", size = "medium", srcs = ["division_past_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["manual"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], - tags = ["manual"], ) cuda_py_test( name = "dynamic_partition_op_test", size = "medium", srcs = ["dynamic_partition_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_grad", "//tensorflow/python:data_flow_ops", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", + "//third_party/py/numpy", ], ) @@ -1963,13 +1964,13 @@ cuda_py_test( name = "dynamic_stitch_op_test", size = "small", srcs = ["dynamic_stitch_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_grad", "//tensorflow/python:data_flow_ops", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", + "//third_party/py/numpy", ], ) @@ -1977,25 +1978,25 @@ cuda_py_test( name = "extract_image_patches_op_test", size = "small", srcs = ["extract_image_patches_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + # TODO(b/144432983): S32 convolutions should not be auto-clustered. + xla_enable_strict_auto_jit = False, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], - # TODO(b/144432983): S32 convolutions should not be auto-clustered. - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "extract_volume_patches_op_test", size = "small", srcs = ["extract_volume_patches_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -2003,8 +2004,10 @@ cuda_py_test( name = "functional_ops_test", size = "medium", srcs = ["functional_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + grpc_enabled = True, + shard_count = 2, + tags = ["no_windows"], + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -2019,24 +2022,22 @@ cuda_py_test( "//tensorflow/python:variables", "//tensorflow/python:while_v2", "//tensorflow/python/data/ops:iterator_ops", + "//third_party/py/numpy", ], - grpc_enabled = True, - shard_count = 2, - tags = ["no_windows"], ) cuda_py_test( name = "gather_nd_op_test", size = "small", srcs = ["gather_nd_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -2044,13 +2045,13 @@ cuda_py_test( name = "gather_op_test", size = "medium", srcs = ["gather_op_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -2058,12 +2059,12 @@ cuda_py_test( name = "gradient_correctness_test", size = "small", srcs = ["gradient_correctness_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -2071,14 +2072,18 @@ cuda_py_test( name = "init_ops_test", size = "medium", srcs = ["init_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 4, + tags = [ + "noasan", + "notap", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", - "//tensorflow/python:layers", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:init_ops", + "//tensorflow/python:layers", "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", "//tensorflow/python:nn_ops", @@ -2086,11 +2091,7 @@ cuda_py_test( "//tensorflow/python:random_ops", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", - ], - shard_count = 4, - tags = [ - "noasan", - "notap", + "//third_party/py/numpy", ], ) @@ -2098,31 +2099,31 @@ cuda_py_test( name = "linalg_ops_test", size = "medium", srcs = ["linalg_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - tags = ["no_windows_gpu"], ) cuda_py_test( name = "lrn_op_test", size = "small", srcs = ["lrn_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", "//tensorflow/python:nn", "//tensorflow/python:nn_grad", + "//third_party/py/numpy", ], ) @@ -2130,8 +2131,7 @@ cuda_py_test( name = "lu_op_test", size = "small", srcs = ["lu_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -2139,6 +2139,7 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -2146,8 +2147,8 @@ cuda_py_test( name = "einsum_op_test", size = "medium", srcs = ["einsum_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 4, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -2155,29 +2156,29 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], - shard_count = 4, ) cuda_py_test( name = "manip_ops_test", size = "small", srcs = ["manip_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:manip_ops", + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:manip_ops", + "//third_party/py/numpy", ], - tags = ["no_windows_gpu"], ) cuda_py_test( name = "matmul_op_test", size = "medium", srcs = ["matmul_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -2185,20 +2186,20 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:random_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - shard_count = 20, ) cuda_py_test( name = "morphological_ops_test", size = "small", srcs = ["morphological_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -2206,14 +2207,14 @@ cuda_py_test( name = "numerics_test", size = "small", srcs = ["numerics_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:numerics", + "//third_party/py/numpy", ], ) @@ -2221,26 +2222,26 @@ cuda_py_test( name = "one_hot_op_test", size = "small", srcs = ["one_hot_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], - tags = ["no_windows_gpu"], ) cuda_py_test( name = "stack_op_test", size = "small", srcs = ["stack_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -2248,8 +2249,10 @@ cuda_py_test( name = "map_fn_test", size = "small", srcs = ["map_fn_test.py"], - additional_deps = [ - "//third_party/py/numpy", + grpc_enabled = True, + shard_count = 2, + tags = ["no_windows"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -2260,21 +2263,19 @@ cuda_py_test( "//tensorflow/python:tensor_array_grad", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - grpc_enabled = True, - shard_count = 2, - tags = ["no_windows"], ) cuda_py_test( name = "pad_op_test", size = "small", srcs = ["pad_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -2282,13 +2283,13 @@ cuda_py_test( name = "padding_fifo_queue_test", size = "small", srcs = ["padding_fifo_queue_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -2296,8 +2297,9 @@ cuda_py_test( name = "py_func_test", size = "small", srcs = ["py_func_test.py"], - additional_deps = [ - "//third_party/py/numpy", + grpc_enabled = True, + tags = ["no_windows"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -2306,21 +2308,20 @@ cuda_py_test( "//tensorflow/python:script_ops", "//tensorflow/python/eager:context", "//tensorflow/python/eager:function", + "//third_party/py/numpy", ], - grpc_enabled = True, - tags = ["no_windows"], ) cuda_py_test( name = "reduce_join_op_test", size = "small", srcs = ["reduce_join_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", + "//third_party/py/numpy", ], ) @@ -2328,12 +2329,12 @@ cuda_py_test( name = "unsorted_segment_join_op_test", size = "small", srcs = ["unsorted_segment_join_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:string_ops", + "//third_party/py/numpy", ], ) @@ -2341,16 +2342,16 @@ cuda_py_test( name = "reduction_ops_test", size = "medium", srcs = ["reduction_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 6, + tags = [ + "no_windows_gpu", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", - ], - shard_count = 6, - tags = [ - "no_windows_gpu", + "//third_party/py/numpy", ], ) @@ -2358,36 +2359,36 @@ cuda_py_test( name = "reduction_ops_test_big", size = "medium", srcs = ["reduction_ops_test_big.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:math_ops", - ], shard_count = 3, tags = [ "manual", "no_gpu", "noguitar", ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:math_ops", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "relu_op_test", size = "small", srcs = ["relu_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", "//tensorflow/python:random_ops", + "//tensorflow/python:tf2", "//tensorflow/python:training", "//tensorflow/python:variables", "//tensorflow/python/eager:backprop", - "//tensorflow/python:tf2", + "//third_party/py/numpy", ], ) @@ -2395,11 +2396,11 @@ cuda_py_test( name = "reshape_op_test", size = "small", srcs = ["reshape_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -2407,11 +2408,11 @@ cuda_py_test( name = "reverse_sequence_op_test", size = "small", srcs = ["reverse_sequence_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -2419,11 +2420,11 @@ cuda_py_test( name = "compare_and_bitpack_op_test", size = "small", srcs = ["compare_and_bitpack_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:math_ops", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -2431,8 +2432,9 @@ cuda_py_test( name = "scalar_test", size = "small", srcs = ["scalar_test.py"], - additional_deps = [ - "//third_party/py/numpy", + # b/140221961: Invalid dims for operations + xla_enable_strict_auto_jit = False, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -2442,21 +2444,20 @@ cuda_py_test( "//tensorflow/python:platform", "//tensorflow/python:random_ops", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], - # b/140221961: Invalid dims for operations - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "scan_ops_test", size = "medium", srcs = ["scan_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -2464,7 +2465,7 @@ cuda_py_test( name = "session_ops_test", size = "small", srcs = ["session_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", @@ -2476,9 +2477,7 @@ cuda_py_test( name = "shape_ops_test", size = "medium", srcs = ["shape_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", @@ -2486,6 +2485,8 @@ cuda_py_test( "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -2493,13 +2494,13 @@ cuda_py_test( name = "softmax_op_test", size = "medium", srcs = ["softmax_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -2507,12 +2508,12 @@ cuda_py_test( name = "softplus_op_test", size = "small", srcs = ["softplus_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -2520,12 +2521,12 @@ cuda_py_test( name = "softsign_op_test", size = "small", srcs = ["softsign_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -2533,14 +2534,14 @@ cuda_py_test( name = "spacetobatch_op_test", size = "small", srcs = ["spacetobatch_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:array_ops_gen", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -2548,16 +2549,16 @@ cuda_py_test( name = "spacetodepth_op_test", size = "medium", srcs = ["spacetodepth_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = [ + "no_windows", + "no_windows_gpu", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", - ], - tags = [ - "no_windows", - "no_windows_gpu", + "//third_party/py/numpy", ], ) @@ -2565,13 +2566,13 @@ tf_py_test( name = "sparse_serialization_ops_test", size = "small", srcs = ["sparse_serialization_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -2579,8 +2580,7 @@ tf_py_test( name = "sparse_tensors_map_ops_test", size = "small", srcs = ["sparse_tensors_map_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -2588,6 +2588,7 @@ tf_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -2595,13 +2596,13 @@ cuda_py_test( name = "sparse_tensor_dense_matmul_grad_test", size = "small", srcs = ["sparse_tensor_dense_matmul_grad_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_grad", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -2609,8 +2610,7 @@ cuda_py_test( name = "sparse_xent_op_test", size = "small", srcs = ["sparse_xent_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -2623,9 +2623,10 @@ cuda_py_test( "//tensorflow/python:nn_ops", "//tensorflow/python:nn_ops_gen", "//tensorflow/python:platform", - "//tensorflow/python:sparse_ops", "//tensorflow/python:random_ops", + "//tensorflow/python:sparse_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -2633,13 +2634,13 @@ cuda_py_test( name = "split_op_test", size = "medium", srcs = ["split_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -2647,14 +2648,14 @@ cuda_py_test( name = "stack_ops_test", size = "small", srcs = ["stack_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", "//tensorflow/python:data_flow_ops_gen", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -2662,7 +2663,7 @@ cuda_py_test( name = "string_to_hash_bucket_op_test", size = "small", srcs = ["string_to_hash_bucket_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -2674,7 +2675,7 @@ cuda_py_test( name = "string_to_number_op_test", size = "small", srcs = ["string_to_number_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -2686,12 +2687,12 @@ cuda_py_test( name = "summary_v1_audio_op_test", size = "small", srcs = ["summary_v1_audio_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:summary", + "//third_party/py/numpy", ], ) @@ -2699,14 +2700,14 @@ cuda_py_test( name = "summary_v1_image_op_test", size = "small", srcs = ["summary_v1_image_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:image_ops", "//tensorflow/python:nn_grad", "//tensorflow/python:summary", + "//third_party/py/numpy", ], ) @@ -2714,10 +2715,13 @@ cuda_py_test( name = "tensor_array_ops_test", size = "small", srcs = ["tensor_array_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + flaky = 1, # create_local_cluster sometimes times out. + shard_count = 10, + tags = ["no_rocm"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", + "//tensorflow/python:cond_v2", "//tensorflow/python:control_flow_ops", "//tensorflow/python:data_flow_ops_gen", "//tensorflow/python:distributed_framework_test_lib", @@ -2727,46 +2731,37 @@ cuda_py_test( "//tensorflow/python:init_ops", "//tensorflow/python:math_ops", "//tensorflow/python:nn_grad", - "//tensorflow/python:tensor_spec", - "//tensorflow/python:training", "//tensorflow/python:tensor_array_grad", "//tensorflow/python:tensor_array_ops", - "//tensorflow/python:variables", + "//tensorflow/python:tensor_spec", + "//tensorflow/python:training", "//tensorflow/python:variable_scope", - "//tensorflow/python:cond_v2", + "//tensorflow/python:variables", "//tensorflow/python:while_v2", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/eager:backprop", "//tensorflow/python/eager:context", "//tensorflow/python/eager:def_function", + "//third_party/py/numpy", ], - flaky = 1, # create_local_cluster sometimes times out. - shard_count = 10, - tags = ["no_rocm"], ) cuda_py_test( name = "trace_op_test", size = "small", srcs = ["trace_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], - tags = ["no_windows_gpu"], ) cuda_py_test( name = "transpose_op_test", size = "medium", srcs = ["transpose_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - ], shard_count = 10, tags = [ "no_gpu", @@ -2774,17 +2769,23 @@ cuda_py_test( "notap", # flaky timeout/segfault, b/136498892 "optonly", # times out ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "unstack_op_test", size = "small", srcs = ["unstack_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], ) @@ -2792,8 +2793,7 @@ cuda_py_test( name = "variable_ops_test", size = "small", srcs = ["variable_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -2802,6 +2802,7 @@ cuda_py_test( "//tensorflow/python:state_ops", "//tensorflow/python:state_ops_gen", "//tensorflow/python:variables", + "//third_party/py/numpy", ], ) @@ -2809,8 +2810,7 @@ cuda_py_test( name = "xent_op_test", size = "small", srcs = ["xent_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", @@ -2818,6 +2818,7 @@ cuda_py_test( "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", "//tensorflow/python:nn_ops_gen", + "//third_party/py/numpy", ], ) @@ -2825,7 +2826,7 @@ cuda_py_test( name = "zero_division_test", size = "small", srcs = ["zero_division_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", @@ -2836,18 +2837,18 @@ cuda_py_test( name = "atrous_conv2d_test", size = "medium", srcs = ["atrous_conv2d_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 2, + tags = [ + "no_gpu", # Flaky: b/80127739, b/127001953 + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", - ], - shard_count = 2, - tags = [ - "no_gpu", # Flaky: b/80127739, b/127001953 + "//third_party/py/numpy", ], ) @@ -2855,27 +2856,27 @@ cuda_py_test( name = "atrous_convolution_test", size = "medium", srcs = ["atrous_convolution_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["manual"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], - tags = ["manual"], ) cuda_py_test( name = "pool_test", size = "medium", srcs = ["pool_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -2883,17 +2884,17 @@ cuda_py_test( name = "conv2d_backprop_filter_grad_test", size = "medium", srcs = ["conv2d_backprop_filter_grad_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 2, + tags = [ + "optonly", # flaky timeouts unless optimized + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", - ], - shard_count = 2, - tags = [ - "optonly", # flaky timeouts unless optimized + "//third_party/py/numpy", ], ) @@ -2901,12 +2902,12 @@ cuda_py_test( name = "conv3d_transpose_test", size = "medium", srcs = ["conv3d_transpose_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], ) @@ -2914,8 +2915,11 @@ cuda_py_test( name = "conv_ops_test", size = "medium", srcs = ["conv_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 4, + tags = [ + "optonly", # times out + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client", "//tensorflow/python:client_testlib", @@ -2928,10 +2932,7 @@ cuda_py_test( "//tensorflow/python:platform", "//tensorflow/python:random_ops", "//tensorflow/python:variables", - ], - shard_count = 4, - tags = [ - "optonly", # times out + "//third_party/py/numpy", ], ) @@ -2939,67 +2940,67 @@ cuda_py_test( name = "depthwise_conv_op_test", size = "medium", # http://b/30603882 srcs = ["depthwise_conv_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + # TODO(b/118842098): Re-enable this test in Kokoro. + tags = ["no_oss"], + deps = [ + "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:array_ops", "//tensorflow/python:nn", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], - # TODO(b/118842098): Re-enable this test in Kokoro. - tags = ["no_oss"], ) tf_py_test( name = "neon_depthwise_conv_op_test", size = "medium", srcs = ["neon_depthwise_conv_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows"], + deps = [ + "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:array_ops", "//tensorflow/python:nn", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], - tags = ["no_windows"], ) cuda_py_test( name = "division_future_test", size = "medium", srcs = ["division_future_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["manual"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], - tags = ["manual"], ) cuda_py_test( name = "pooling_ops_3d_test", size = "medium", srcs = ["pooling_ops_3d_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_rocm"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", + "//third_party/py/numpy", ], - tags = ["no_rocm"], ) cuda_py_test( name = "pooling_ops_test", size = "medium", srcs = ["pooling_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 4, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -3008,8 +3009,8 @@ cuda_py_test( "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", "//tensorflow/python:nn_ops_gen", + "//third_party/py/numpy", ], - shard_count = 4, ) cuda_py_test( @@ -3017,8 +3018,8 @@ cuda_py_test( size = "medium", timeout = "long", srcs = ["rnn_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 10, + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -3037,18 +3038,17 @@ cuda_py_test( "//tensorflow/python:tensor_array_ops", "//tensorflow/python:variables", "//tensorflow/python/eager:context", + "//third_party/py/numpy", ], - shard_count = 10, ) cuda_py_test( name = "rnn_cell_test", size = "medium", srcs = ["rnn_cell_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python/eager:context", + shard_count = 15, + tags = ["no_windows"], # b/139739217 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", @@ -3065,54 +3065,55 @@ cuda_py_test( "//tensorflow/python:util", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//tensorflow/python/eager:context", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - shard_count = 15, - tags = ["no_windows"], # b/139739217 ) cuda_py_test( name = "scatter_ops_test", size = "medium", # NOTE: This is not run by default. srcs = ["scatter_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 2, + tags = ["optonly"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:state_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - shard_count = 2, - tags = ["optonly"], ) cuda_py_test( name = "slice_op_test", size = "medium", srcs = ["slice_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows"], # b/126916429 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients", + "//third_party/py/numpy", ], - tags = ["no_windows"], # b/126916429 ) cuda_py_test( name = "huge_slice_op_test", size = "medium", srcs = ["huge_slice_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = [ + "no_oss", # Requires 4GB+ RAM + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", - ], - tags = [ - "no_oss", # Requires 4GB+ RAM + "//third_party/py/numpy", ], ) @@ -3120,21 +3121,25 @@ cuda_py_test( name = "sparse_matmul_op_test", size = "medium", srcs = ["sparse_matmul_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], - tags = ["no_windows"], ) cuda_py_test( name = "sparse_ops_test", size = "medium", srcs = ["sparse_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 5, + tags = [ + "noasan", + "optonly", # b/77589990 + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -3144,11 +3149,7 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:sparse_grad", "//tensorflow/python:sparse_ops", - ], - shard_count = 5, - tags = [ - "noasan", - "optonly", # b/77589990 + "//third_party/py/numpy", ], ) @@ -3156,8 +3157,7 @@ cuda_py_test( name = "sparse_tensor_dense_matmul_op_test", size = "medium", srcs = ["sparse_tensor_dense_matmul_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -3168,6 +3168,7 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -3177,46 +3178,46 @@ cuda_py_test( name = "extract_image_patches_grad_test", size = "medium", srcs = ["extract_image_patches_grad_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 3, + tags = ["notap"], # http://b/31080670 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], - shard_count = 3, - tags = ["notap"], # http://b/31080670 ) cuda_py_test( name = "extract_volume_patches_grad_test", size = "medium", srcs = ["extract_volume_patches_grad_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - ], tags = [ "no_pip", "nomac", # http://b/139946976 "notap", # http://b/31080670 ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "stage_op_test", size = "medium", srcs = ["stage_op_test.py"], - additional_deps = [ + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", + "//tensorflow/python:data_flow_ops", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:util", - "//tensorflow/python:data_flow_ops", ], ) @@ -3224,23 +3225,23 @@ cuda_py_test( name = "map_stage_op_test", size = "medium", srcs = ["map_stage_op_test.py"], - additional_deps = [ + tags = ["no_oss"], # b/124474135 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", + "//tensorflow/python:data_flow_ops", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:util", - "//tensorflow/python:data_flow_ops", ], - tags = ["no_oss"], # b/124474135 ) cuda_py_test( name = "concat_op_test", size = "medium", srcs = ["concat_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows"], # b/126916429 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:array_ops_gen", "//tensorflow/python:client_testlib", @@ -3249,44 +3250,44 @@ cuda_py_test( "//tensorflow/python:gradients", "//tensorflow/python:math_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - tags = ["no_windows"], # b/126916429 ) cuda_py_test( name = "large_concat_op_test", size = "medium", srcs = ["large_concat_op_test.py"], - additional_deps = [ - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - ], tags = [ "nomsan", "notsan", ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + ], ) cuda_py_test( name = "conv_ops_3d_test", size = "medium", srcs = ["conv_ops_3d_test.py"], - additional_deps = [ + shard_count = 30, + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:nn_grad", "//tensorflow/python:nn_ops", ], - shard_count = 30, ) cuda_py_test( name = "cwise_ops_test", size = "medium", srcs = ["cwise_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 50, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -3297,16 +3298,18 @@ cuda_py_test( "//tensorflow/python:nn_grad", "//tensorflow/python:platform", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - shard_count = 50, ) cuda_py_test( name = "cwise_ops_binary_test", size = "medium", srcs = ["cwise_ops_binary_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 50, + # b/140155647: Error just outside of tolerance + xla_enable_strict_auto_jit = False, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -3317,18 +3320,18 @@ cuda_py_test( "//tensorflow/python:nn_grad", "//tensorflow/python:platform", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - shard_count = 50, - # b/140155647: Error just outside of tolerance - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "cwise_ops_unary_test", size = "medium", srcs = ["cwise_ops_unary_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 50, + # b/140155706: nans in result + xla_enable_strict_auto_jit = False, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -3339,18 +3342,16 @@ cuda_py_test( "//tensorflow/python:nn_grad", "//tensorflow/python:platform", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - shard_count = 50, - # b/140155706: nans in result - xla_enable_strict_auto_jit = False, ) cuda_py_test( name = "embedding_ops_test", size = "medium", srcs = ["embedding_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", @@ -3366,16 +3367,16 @@ cuda_py_test( "//tensorflow/python:util", "//tensorflow/python:variable_scope", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - shard_count = 20, ) cuda_py_test( name = "linalg_grad_test", size = "medium", srcs = ["linalg_grad_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -3383,41 +3384,41 @@ cuda_py_test( "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], - shard_count = 20, ) cuda_py_test( name = "matrix_band_part_op_test", size = "medium", srcs = ["matrix_band_part_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", + "//third_party/py/numpy", ], - shard_count = 20, ) tf_py_test( name = "eig_op_test", size = "medium", srcs = ["eig_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:linalg_ops", - "//tensorflow/python:math_ops", - ], data = ["//tensorflow/python/kernel_tests/testdata:self_adjoint_eig_op_test_files"], shard_count = 20, tags = [ "no_rocm", # flaky test "no_windows", ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:linalg_ops", + "//tensorflow/python:math_ops", + "//third_party/py/numpy", + ], # b/127344411: xla_enable_strict_auto_jit = True, ) @@ -3425,18 +3426,18 @@ cuda_py_test( name = "self_adjoint_eig_op_test", size = "medium", srcs = ["self_adjoint_eig_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + data = ["//tensorflow/python/kernel_tests/testdata:self_adjoint_eig_op_test_files"], + shard_count = 20, + tags = [ + "no_windows", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", - ], - data = ["//tensorflow/python/kernel_tests/testdata:self_adjoint_eig_op_test_files"], - shard_count = 20, - tags = [ - "no_windows", + "//third_party/py/numpy", ], # TODO(b/127344411): This test passes because XLA does not actually cluster # the self_adjoint_eig op. @@ -3446,17 +3447,17 @@ cuda_py_test( name = "qr_op_test", size = "medium", srcs = ["qr_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + tags = [ + "no_rocm", # TODO(rocm): feature not supported on ROCm platform + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", - ], - shard_count = 20, - tags = [ - "no_rocm", # TODO(rocm): feature not supported on ROCm platform + "//third_party/py/numpy", ], ) @@ -3464,19 +3465,19 @@ cuda_py_test( name = "svd_op_test", size = "medium", srcs = ["svd_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + tags = [ + "no_oss", # b/117185141. + "nomsan", # TODO(b/117236102): Re-enable in msan build. + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:gradients_impl", "//tensorflow/python:linalg_ops", "//tensorflow/python:math_ops", - ], - shard_count = 20, - tags = [ - "no_oss", # b/117185141. - "nomsan", # TODO(b/117236102): Re-enable in msan build. + "//third_party/py/numpy", ], # TODO(b/127344411): This test passes because XLA does not actually cluster # the svd op. @@ -3486,65 +3487,65 @@ cuda_py_test( name = "norm_op_test", size = "medium", srcs = ["norm_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:linalg_ops", - ], shard_count = 20, # TODO(b/117236102): Re-enable in msan build. tags = [ "no_windows_gpu", "nomsan", ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "normalize_op_test", size = "medium", srcs = ["normalize_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:nn", - ], shard_count = 20, # TODO(b/117236102): Re-enable in msan build. tags = [ "no_windows_gpu", "nomsan", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:nn", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "tensordot_op_test", size = "medium", srcs = ["tensordot_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], - shard_count = 20, ) sycl_py_test( name = "basic_gpu_test", size = "small", srcs = ["basic_gpu_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops_gen", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:math_ops_gen", + "//third_party/py/numpy", ], ) @@ -3552,8 +3553,7 @@ tf_py_test( name = "sets_test", size = "medium", srcs = ["sets_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:errors", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", @@ -3562,6 +3562,7 @@ tf_py_test( "//tensorflow/python:platform_test", "//tensorflow/python:sets", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], ) @@ -3569,8 +3570,8 @@ tf_py_test( name = "weights_broadcast_test", size = "small", srcs = ["weights_broadcast_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 3, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", @@ -3579,16 +3580,17 @@ tf_py_test( "//tensorflow/python:framework_test_lib", "//tensorflow/python:platform", "//tensorflow/python:weights_broadcast_ops", + "//third_party/py/numpy", ], - shard_count = 3, ) tf_py_test( name = "metrics_test", size = "medium", srcs = ["metrics_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 20, + tags = ["no_windows_gpu"], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_grad", @@ -3600,23 +3602,22 @@ tf_py_test( "//tensorflow/python:nn_grad", "//tensorflow/python:random_ops", "//tensorflow/python:variables", + "//third_party/py/numpy", ], - shard_count = 20, - tags = ["no_windows_gpu"], ) tf_py_test( name = "confusion_matrix_test", size = "small", srcs = ["confusion_matrix_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:confusion_matrix", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], ) @@ -3624,11 +3625,11 @@ cuda_py_test( name = "bucketize_op_test", size = "small", srcs = ["bucketize_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", + "//third_party/py/numpy", ], ) @@ -3636,26 +3637,26 @@ tf_py_test( name = "sparse_cross_op_test", size = "small", srcs = ["sparse_cross_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + tags = ["no_windows"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:sparse_ops", + "//third_party/py/numpy", ], - tags = ["no_windows"], ) tf_py_test( name = "garbage_collection_test", size = "small", srcs = ["garbage_collection_test.py"], - additional_deps = [ - "//tensorflow/python/eager:context", + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:dtypes", "//tensorflow/python:framework_test_lib", "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:dtypes", "//tensorflow/python:tensor_array_ops", - "//tensorflow/python:client_testlib", + "//tensorflow/python/eager:context", ], ) @@ -3669,16 +3670,16 @@ tf_py_test( name = "ackermann_test", size = "small", srcs = ["ackermann_test.py"], - additional_deps = [ - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:platform", - ], data = [":ackermann_op.so"], tags = [ "no_pip", "notap", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:platform", + ], ) tf_custom_op_library( @@ -3690,17 +3691,17 @@ tf_py_test( name = "duplicate_op_test", size = "small", srcs = ["duplicate_op_test.py"], - additional_deps = [ - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform", - ], data = [":duplicate_op.so"], tags = [ "no_pip", "notap", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform", + ], ) tf_custom_op_library( @@ -3712,24 +3713,25 @@ tf_py_test( name = "invalid_op_test", size = "small", srcs = ["invalid_op_test.py"], - additional_deps = [ - "//tensorflow/python:client_testlib", - "//tensorflow/python:errors", - "//tensorflow/python:framework", - "//tensorflow/python:platform", - ], data = [":invalid_op.so"], tags = [ "no_pip", "notap", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:errors", + "//tensorflow/python:framework", + "//tensorflow/python:platform", + ], ) cuda_py_test( name = "cond_v2_test", size = "medium", srcs = ["cond_v2_test.py"], - additional_deps = [ + grpc_enabled = True, + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:cond_v2", @@ -3745,18 +3747,16 @@ cuda_py_test( "//tensorflow/python:while_v2", "//tensorflow/python/compat", ], - grpc_enabled = True, ) cuda_py_test( name = "while_v2_test", size = "medium", srcs = ["while_v2_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", + grpc_enabled = True, + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", - "//tensorflow/python/eager:def_function", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", "//tensorflow/python:control_flow_ops", @@ -3772,30 +3772,31 @@ cuda_py_test( "//tensorflow/python:tensor_array_grad", "//tensorflow/python:tf_optimizer", "//tensorflow/python:while_v2", + "//tensorflow/python/eager:def_function", + "@absl_py//absl/testing:parameterized", ], - grpc_enabled = True, ) cuda_py_test( name = "critical_section_test", size = "medium", srcs = ["critical_section_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:client_testlib", - "//tensorflow/python/data/experimental/ops:prefetching_ops", + deps = [ "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", "//tensorflow/python:control_flow_v2_toggles", + "//tensorflow/python:critical_section_ops", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:gradients", "//tensorflow/python:platform_test", "//tensorflow/python:resource_variable_ops", - "//tensorflow/python:critical_section_ops", "//tensorflow/python:tensor_array_ops", + "//tensorflow/python/data/experimental/ops:prefetching_ops", "//tensorflow/python/data/ops:dataset_ops", "//tensorflow/python/eager:context", + "@absl_py//absl/testing:parameterized", ], ) @@ -3803,12 +3804,12 @@ cuda_py_test( name = "tridiagonal_matmul_op_test", size = "medium", srcs = ["tridiagonal_matmul_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 10, + tags = ["no_rocm"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:linalg_ops", + "//third_party/py/numpy", ], - shard_count = 10, - tags = ["no_rocm"], ) diff --git a/tensorflow/python/kernel_tests/basic_gpu_test.py b/tensorflow/python/kernel_tests/basic_gpu_test.py index 789f6e90c9f..df27e8afbba 100644 --- a/tensorflow/python/kernel_tests/basic_gpu_test.py +++ b/tensorflow/python/kernel_tests/basic_gpu_test.py @@ -273,7 +273,7 @@ class GpuMultiSessionMemoryTest(test_util.TensorFlowTestCase): for thread in threads: thread.join() - flat_results = set([x for x in itertools.chain(*results)]) + flat_results = set(itertools.chain(*results)) self.assertEqual(1, len(flat_results), 'Expected single value, got %r' % flat_results) diff --git a/tensorflow/python/kernel_tests/bias_op_deterministic_test.py b/tensorflow/python/kernel_tests/bias_op_deterministic_test.py index c46444d58a8..7630f8f145c 100644 --- a/tensorflow/python/kernel_tests/bias_op_deterministic_test.py +++ b/tensorflow/python/kernel_tests/bias_op_deterministic_test.py @@ -21,6 +21,8 @@ from __future__ import print_function import os import numpy as np +from absl.testing import parameterized + from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import test_util @@ -31,7 +33,8 @@ from tensorflow.python.ops import nn_ops from tensorflow.python.platform import test -class BiasAddDeterministicTest(bias_op_base.BiasAddTestBase): +class BiasAddDeterministicTest(bias_op_base.BiasAddTestBase, + parameterized.TestCase): def _make_shape_tuple(self, batch_size, channel_count, data_rank, data_dim, data_layout): @@ -65,7 +68,16 @@ class BiasAddDeterministicTest(bias_op_base.BiasAddTestBase): result_b = operation[0].eval(feed_dict=feed_dict) self.assertAllEqual(result_a, result_b) - def _testDeterministicGradientsCase(self, data_layout, data_rank, data_type): + # TODO(duncanriach): add test coverage for deterministic gradients + # in eager mode + @parameterized.named_parameters( + *test_util.generate_combinations_with_testcase_name( + data_layout=['channels_first', 'channels_last'], + data_rank=[1, 2, 3], + data_type=[dtypes.float16, dtypes.float32, dtypes.float64])) + @test_util.run_deprecated_v1 + @test_util.run_cuda_only + def testDeterministicGradients(self, data_layout, data_rank, data_type): seed = ( hash(data_layout) % 256 + hash(data_rank) % 256 + hash(data_type) % 256) np.random.seed(seed) @@ -97,17 +109,6 @@ class BiasAddDeterministicTest(bias_op_base.BiasAddTestBase): feed_dict = {upstream_gradients: self._random_ndarray(out_shape)} self._assert_reproducible(bias_gradients_op, feed_dict=feed_dict) - # TODO(duncanriach): add test coverage for deterministic gradients - # in eager mode - @test_util.run_deprecated_v1 - @test_util.run_cuda_only - def testDeterministicGradients(self): - for data_layout in ('channels_first', 'channels_last'): - for data_rank in (1, 2, 3): - for data_type in (dtypes.float16, dtypes.float32, dtypes.float64): - self._testDeterministicGradientsCase(data_layout, data_rank, - data_type) - # TODO(duncanriach): Re-enable the following three tests for the error checks # after deterministic functionality is implemented at the CUDA kernel level. def testInputDims(self): diff --git a/tensorflow/python/kernel_tests/boosted_trees/BUILD b/tensorflow/python/kernel_tests/boosted_trees/BUILD index d19284bbe55..5b318324d4c 100644 --- a/tensorflow/python/kernel_tests/boosted_trees/BUILD +++ b/tensorflow/python/kernel_tests/boosted_trees/BUILD @@ -24,7 +24,7 @@ tf_py_test( name = "resource_ops_test", size = "small", srcs = ["resource_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/core/kernels/boosted_trees:boosted_trees_proto_py", "//tensorflow/python:boosted_trees_ops", "//tensorflow/python:framework_ops", @@ -39,12 +39,12 @@ tf_py_test( name = "prediction_ops_test", size = "small", srcs = ["prediction_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/core/kernels/boosted_trees:boosted_trees_proto_py", "//tensorflow/python:array_ops", "//tensorflow/python:boosted_trees_ops", - "//tensorflow/python:framework_test_lib", "//tensorflow/python:constant_op", + "//tensorflow/python:framework_test_lib", "//tensorflow/python:resources", ], ) @@ -53,8 +53,7 @@ tf_py_test( name = "stats_ops_test", size = "medium", srcs = ["stats_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:boosted_trees_ops", "//tensorflow/python:constant_op", @@ -62,6 +61,7 @@ tf_py_test( "//tensorflow/python:framework", "//tensorflow/python:framework_test_lib", "//tensorflow/python:platform_test", + "//third_party/py/numpy", ], ) @@ -69,7 +69,7 @@ tf_py_test( name = "training_ops_test", size = "small", srcs = ["training_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/core/kernels/boosted_trees:boosted_trees_proto_py", "//tensorflow/python:array_ops", "//tensorflow/python:boosted_trees_ops", @@ -83,7 +83,7 @@ tf_py_test( name = "quantile_ops_test", size = "small", srcs = ["quantile_ops_test.py"], - additional_deps = [ + deps = [ "//tensorflow/core/kernels/boosted_trees:boosted_trees_proto_py", "//tensorflow/python:boosted_trees_ops", "//tensorflow/python:constant_op", 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 64da8352419..178743a5f35 100644 --- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py +++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py @@ -3456,7 +3456,6 @@ class ControlFlowTest(test.TestCase, parameterized.TestCase): r = gradients_impl.gradients([rx], x) self.assertAllClose(1024.0, r[0]) - @test_util.disable_control_flow_v2("b/116355153 (back_prop flag)") @test_util.run_v1_only("b/120545219") def testWhileGrad_NoGradient(self): with self.cached_session(): diff --git a/tensorflow/python/kernel_tests/cwise_ops_test.py b/tensorflow/python/kernel_tests/cwise_ops_test.py index ed5cbabdb1c..f762cec8668 100644 --- a/tensorflow/python/kernel_tests/cwise_ops_test.py +++ b/tensorflow/python/kernel_tests/cwise_ops_test.py @@ -1233,7 +1233,7 @@ class SingularGradientOpTest(test.TestCase): @test_util.run_deprecated_v1 def testGradientAtSingularity(self): - if not compat.forward_compatible(2019, 12, 14): + if not compat.forward_compatible(2020, 3, 14): self.skipTest("Skipping test for future functionality.") ops_and_singularity = [ diff --git a/tensorflow/python/kernel_tests/distributions/BUILD b/tensorflow/python/kernel_tests/distributions/BUILD index 0c5d472241a..23194dec2b8 100644 --- a/tensorflow/python/kernel_tests/distributions/BUILD +++ b/tensorflow/python/kernel_tests/distributions/BUILD @@ -11,16 +11,16 @@ cuda_py_test( name = "bijector_test", size = "small", srcs = ["bijector_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", - "@six_archive//:six", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", + "@six_archive//:six", ], ) @@ -28,9 +28,9 @@ cuda_py_test( name = "util_test", size = "medium", srcs = ["util_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + shard_count = 3, + xla_enable_strict_auto_jit = False, # TODO(b/144920376) + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -39,20 +39,20 @@ cuda_py_test( "//tensorflow/python:gradients", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], - shard_count = 3, - xla_enable_strict_auto_jit = False, # TODO(b/144920376) ) cuda_py_test( name = "kullback_leibler_test", size = "small", srcs = ["kullback_leibler_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", ], ) @@ -60,9 +60,7 @@ cuda_py_test( name = "beta_test", size = "small", srcs = ["beta_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -70,6 +68,8 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:nn_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) @@ -77,14 +77,14 @@ cuda_py_test( name = "bernoulli_test", size = "small", srcs = ["bernoulli_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) @@ -92,10 +92,7 @@ cuda_py_test( name = "categorical_test", size = "small", srcs = ["categorical_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -104,6 +101,9 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", "//tensorflow/python:random_ops", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], ) @@ -111,13 +111,13 @@ cuda_py_test( name = "dirichlet_test", size = "small", srcs = ["dirichlet_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) @@ -125,98 +125,87 @@ cuda_py_test( name = "dirichlet_multinomial_test", size = "medium", srcs = ["dirichlet_multinomial_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + tags = [ + "noguitar", # b/110489471 + "notap", # b/110489471 + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", - ], - tags = [ - "noguitar", # b/110489471 - "notap", # b/110489471 + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) cuda_py_test( name = "exponential_test", srcs = ["exponential_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:nn_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) cuda_py_test( name = "gamma_test", srcs = ["gamma_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:nn_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) cuda_py_test( name = "laplace_test", srcs = ["laplace_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:nn_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) cuda_py_test( name = "multinomial_test", srcs = ["multinomial_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + tags = ["manual"], # b/69001419 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], - tags = ["manual"], # b/69001419 ) cuda_py_test( name = "student_t_test", size = "small", srcs = ["student_t_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:math_ops", - "//tensorflow/python:nn_ops", - "//tensorflow/python:platform_test", - ], tags = [ # TODO(b/121223043): Re-enable this test after fixing "mean not defined" # errors. @@ -225,21 +214,32 @@ cuda_py_test( # disable to avoid false positives from scipy. "nomsan", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:math_ops", + "//tensorflow/python:nn_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", + ], ) cuda_py_test( name = "uniform_test", size = "small", srcs = ["uniform_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:errors", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) @@ -247,9 +247,7 @@ cuda_py_test( name = "normal_test", size = "medium", srcs = ["normal_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", @@ -258,6 +256,8 @@ cuda_py_test( "//tensorflow/python:nn_ops", "//tensorflow/python:platform_test", "//tensorflow/python:variables", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) @@ -265,15 +265,15 @@ cuda_py_test( name = "special_math_test", size = "medium", srcs = ["special_math_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:gradients", "//tensorflow/python:platform_test", "//tensorflow/python:variables", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", ], ) @@ -281,15 +281,15 @@ cuda_py_test( name = "identity_bijector_test", size = "small", srcs = ["identity_bijector_test.py"], - additional_deps = [ - "//tensorflow/python/ops/distributions", - "//third_party/py/numpy", - "@six_archive//:six", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/distributions", + "//third_party/py/numpy", + "@six_archive//:six", ], ) diff --git a/tensorflow/python/kernel_tests/fifo_queue_test.py b/tensorflow/python/kernel_tests/fifo_queue_test.py index 6ffff8bf350..6c8bdbd5fec 100644 --- a/tensorflow/python/kernel_tests/fifo_queue_test.py +++ b/tensorflow/python/kernel_tests/fifo_queue_test.py @@ -18,6 +18,7 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import gc import random import time @@ -26,20 +27,24 @@ from six.moves import xrange # pylint: disable=redefined-builtin from tensorflow.core.protobuf import config_pb2 from tensorflow.python.client import session as session_lib +from tensorflow.python.eager import context +from tensorflow.python.eager import def_function from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes as dtypes_lib from tensorflow.python.framework import errors_impl from tensorflow.python.framework import ops from tensorflow.python.framework import tensor_shape from tensorflow.python.framework import test_util +from tensorflow.python.module import module from tensorflow.python.ops import array_ops from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import data_flow_ops +from tensorflow.python.ops import gen_resource_variable_ops from tensorflow.python.platform import test from tensorflow.python.util import compat -@test_util.run_v1_only("FIFOQueue removed from v2") +@test_util.run_all_in_graph_and_eager_modes class FIFOQueueTest(test.TestCase): def testConstructor(self): @@ -100,41 +105,33 @@ class FIFOQueueTest(test.TestCase): """, q.queue_ref.op.node_def) def testEnqueue(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - enqueue_op = q.enqueue((10.0,)) - enqueue_op.run() + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + self.evaluate(q.enqueue((10.0,))) def testEnqueueHalf(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float16) - enqueue_op = q.enqueue((10.0,)) - enqueue_op.run() + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float16) + self.evaluate(q.enqueue((10.0,))) def testEnqueueWithShape(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shapes=(3, 2)) - enqueue_correct_op = q.enqueue(([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]],)) - enqueue_correct_op.run() - with self.assertRaises(ValueError): - q.enqueue(([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]],)) - self.assertEqual(1, q.size().eval()) + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shapes=(3, 2)) + self.evaluate(q.enqueue(([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]],))) + with self.assertRaises(ValueError): + q.enqueue(([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]],)) + self.assertEqual(1, self.evaluate(q.size())) def testEnqueueManyWithShape(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue( - 10, [dtypes_lib.int32, dtypes_lib.int32], shapes=[(), (2,)]) - q.enqueue_many([[1, 2, 3, 4], [[1, 1], [2, 2], [3, 3], [4, 4]]]).run() - self.assertEqual(4, q.size().eval()) + q = data_flow_ops.FIFOQueue( + 10, [dtypes_lib.int32, dtypes_lib.int32], shapes=[(), (2,)]) + self.evaluate( + q.enqueue_many([[1, 2, 3, 4], [[1, 1], [2, 2], [3, 3], [4, 4]]])) + self.assertEqual(4, self.evaluate(q.size())) - @test_util.run_in_graph_and_eager_modes def testMultipleDequeues(self): q = data_flow_ops.FIFOQueue(10, [dtypes_lib.int32], shapes=[()]) self.evaluate(q.enqueue_many([[1, 2, 3]])) a, b, c = self.evaluate([q.dequeue(), q.dequeue(), q.dequeue()]) self.assertAllEqual(set([1, 2, 3]), set([a, b, c])) - @test_util.run_in_graph_and_eager_modes def testQueuesDontShare(self): q = data_flow_ops.FIFOQueue(10, [dtypes_lib.int32], shapes=[()]) self.evaluate(q.enqueue(1)) @@ -143,13 +140,606 @@ class FIFOQueueTest(test.TestCase): self.assertAllEqual(self.evaluate(q2.dequeue()), 2) self.assertAllEqual(self.evaluate(q.dequeue()), 1) + def testQueueInFunction(self): + + class _M(module.Module): + + def __init__(self): + self.q1 = data_flow_ops.FIFOQueue(10, [dtypes_lib.int32], shapes=[()]) + self.q2 = None + + @def_function.function + def uses_queues(self, x): + if self.q2 is None: + self.q2 = data_flow_ops.FIFOQueue(10, [dtypes_lib.int32], shapes=[()]) + self.q2.enqueue(x) + self.q2.enqueue(x + 3) + self.q1.enqueue(self.q2.dequeue()) + + m = _M() + self.evaluate(m.uses_queues(constant_op.constant(2))) + self.assertAllEqual(2, self.evaluate(m.q1.dequeue())) + self.assertAllEqual(5, self.evaluate(m.q2.dequeue())) + if context.executing_eagerly(): + q1_handle = m.q1.queue_ref + q2_handle = m.q2.queue_ref + del m + gc.collect() + # If executing eagerly, deleting the Module should clean up the queue + # resources. + with self.assertRaisesRegexp(errors_impl.NotFoundError, + r"Resource .* does not exist."): + gen_resource_variable_ops.destroy_resource_op( + q1_handle, ignore_lookup_error=False) + with self.assertRaisesRegexp(errors_impl.NotFoundError, + r"Resource .* does not exist."): + gen_resource_variable_ops.destroy_resource_op( + q2_handle, ignore_lookup_error=False) + def testEnqueueDictWithoutNames(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + with self.assertRaisesRegexp(ValueError, "must have names"): + q.enqueue({"a": 12.0}) + with self.assertRaisesRegexp(ValueError, "must have names"): + q.enqueue_many({"a": [12.0, 13.0]}) + + def testDequeue(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + elems = [10.0, 20.0, 30.0] + + for x in elems: + self.evaluate(q.enqueue((x,))) + + for i in xrange(len(elems)): + vals = self.evaluate(q.dequeue()) + self.assertEqual([elems[i]], vals) + + def testDequeueHalf(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float16) + elems = [10.0, 20.0, 30.0] + + for x in elems: + self.evaluate(q.enqueue((x,))) + + for i in xrange(len(elems)): + vals = self.evaluate(q.dequeue()) + self.assertEqual([elems[i]], vals) + + def testMultiEnqueueAndDequeue(self): + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.float32)) + elems = [(5, 10.0), (10, 20.0), (15, 30.0)] + + for x in elems: + self.evaluate(q.enqueue(x)) + + for i in xrange(len(elems)): + x_val, y_val = self.evaluate(q.dequeue()) + x, y = elems[i] + self.assertEqual([x], x_val) + self.assertEqual([y], y_val) + + def testQueueSizeEmpty(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + self.assertEqual([0], self.evaluate(q.size())) + + def testQueueSizeAfterEnqueueAndDequeue(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + self.assertEqual([], q.size().get_shape()) + + self.evaluate(q.enqueue((10.0,))) + self.assertEqual(1, self.evaluate(q.size())) + self.evaluate(q.dequeue()) + self.assertEqual(0, self.evaluate(q.size())) + + def testEnqueueMany(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + elems = [10.0, 20.0, 30.0, 40.0] + self.evaluate(q.enqueue_many((elems,))) + self.evaluate(q.enqueue_many((elems,))) + + for i in range(8): + vals = self.evaluate(q.dequeue()) + self.assertEqual([elems[i % 4]], vals) + + def testEmptyEnqueueMany(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + empty_t = constant_op.constant( + [], dtype=dtypes_lib.float32, shape=[0, 2, 3]) + + self.assertEqual([0], self.evaluate(q.size())) + self.evaluate(q.enqueue_many((empty_t,))) + self.assertEqual([0], self.evaluate(q.size())) + + def testEmptyDequeueMany(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shapes=()) + + self.assertEqual([], self.evaluate(q.dequeue_many(0)).tolist()) + self.evaluate(q.enqueue((10.0,))) + self.assertEqual([], self.evaluate(q.dequeue_many(0)).tolist()) + + def testEmptyDequeueUpTo(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shapes=()) + + self.assertEqual([], self.evaluate(q.dequeue_up_to(0)).tolist()) + self.evaluate(q.enqueue((10.0,))) + self.assertEqual([], self.evaluate(q.dequeue_up_to(0)).tolist()) + + def testEmptyDequeueManyWithNoShape(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + # Expect the operation to fail due to the shape not being constrained. + with self.assertRaisesOpError("specified shapes"): + self.evaluate(q.dequeue_many(0)) + + def testMultiEnqueueMany(self): + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.float32, dtypes_lib.int32)) + float_elems = [10.0, 20.0, 30.0, 40.0] + int_elems = [[1, 2], [3, 4], [5, 6], [7, 8]] + + self.evaluate(q.enqueue_many((float_elems, int_elems))) + self.evaluate(q.enqueue_many((float_elems, int_elems))) + + for i in range(8): + float_val, int_val = self.evaluate(q.dequeue()) + self.assertEqual(float_elems[i % 4], float_val) + self.assertAllEqual(int_elems[i % 4], int_val) + + def testDequeueMany(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) + elems = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + + self.evaluate(q.enqueue_many((elems,))) + + self.assertAllEqual(elems[0:4], self.evaluate(q.dequeue_many(4))) + self.assertAllEqual(elems[4:8], self.evaluate(q.dequeue_many(4))) + + def testDequeueUpToNoBlocking(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) + elems = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] + + self.evaluate(q.enqueue_many((elems,))) + + self.assertAllEqual(elems[0:4], self.evaluate(q.dequeue_up_to(4))) + self.assertAllEqual(elems[4:8], self.evaluate(q.dequeue_up_to(4))) + + def testMultiDequeueMany(self): + q = data_flow_ops.FIFOQueue( + 10, (dtypes_lib.float32, dtypes_lib.int32), shapes=((), (2,))) + float_elems = [ + 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0 + ] + int_elems = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], + [15, 16], [17, 18], [19, 20]] + + self.evaluate(q.enqueue_many((float_elems, int_elems))) + + dequeued_t = q.dequeue_many(4) + float_val, int_val = self.evaluate(dequeued_t) + self.assertAllEqual(float_elems[0:4], float_val) + self.assertAllEqual(int_elems[0:4], int_val) + self.assertEqual(float_val.shape, dequeued_t[0].get_shape()) + self.assertEqual(int_val.shape, dequeued_t[1].get_shape()) + + float_val, int_val = self.evaluate(q.dequeue_many(4)) + self.assertAllEqual(float_elems[4:8], float_val) + self.assertAllEqual(int_elems[4:8], int_val) + + dequeued_single_t = q.dequeue() + float_val, int_val = self.evaluate(dequeued_single_t) + self.assertAllEqual(float_elems[8], float_val) + self.assertAllEqual(int_elems[8], int_val) + self.assertEqual(float_val.shape, dequeued_single_t[0].get_shape()) + self.assertEqual(int_val.shape, dequeued_single_t[1].get_shape()) + + def testMultiDequeueUpToNoBlocking(self): + q = data_flow_ops.FIFOQueue( + 10, (dtypes_lib.float32, dtypes_lib.int32), shapes=((), (2,))) + float_elems = [ + 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0 + ] + int_elems = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], + [15, 16], [17, 18], [19, 20]] + + self.evaluate(q.enqueue_many((float_elems, int_elems))) + + dequeued_t = q.dequeue_up_to(4) + float_val, int_val = self.evaluate(dequeued_t) + self.assertAllEqual(float_elems[0:4], float_val) + self.assertAllEqual(int_elems[0:4], int_val) + if not context.executing_eagerly(): + self.assertEqual([None], dequeued_t[0].get_shape().as_list()) + self.assertEqual([None, 2], dequeued_t[1].get_shape().as_list()) + + float_val, int_val = self.evaluate(q.dequeue_up_to(4)) + self.assertAllEqual(float_elems[4:8], float_val) + self.assertAllEqual(int_elems[4:8], int_val) + + def testHighDimension(self): + q = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, (4, 4, 4, 4)) + elems = np.array([[[[[x] * 4] * 4] * 4] * 4 for x in range(10)], np.int32) + + self.evaluate(q.enqueue_many((elems,))) + self.assertAllEqual(self.evaluate(q.dequeue_many(10)), elems) + + def testEnqueueWrongShape(self): + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32), ((), + (2))) + + with self.assertRaises(ValueError): + q.enqueue(([1, 2], [2, 2])) + + with self.assertRaises(ValueError): + q.enqueue_many((7, [[1, 2], [3, 4], [5, 6]])) + + def testEnqueueManyEmptyTypeConversion(self): + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.float32), ( + (), ())) + + @def_function.function + def _f(): + enq = q.enqueue_many(([], [])) + self.assertEqual(dtypes_lib.int32, enq.inputs[1].dtype) + self.assertEqual(dtypes_lib.float32, enq.inputs[2].dtype) + + _f() + + def testEnqueueWrongType(self): + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.float32), ( + (), ())) + + @def_function.function + def _f(): + with self.assertRaises(ValueError): + q.enqueue((array_ops.placeholder(dtypes_lib.int32), + array_ops.placeholder(dtypes_lib.int32))) + + with self.assertRaises(ValueError): + q.enqueue_many((array_ops.placeholder(dtypes_lib.int32), + array_ops.placeholder(dtypes_lib.int32))) + + _f() + + +@test_util.run_v1_only( + "These tests can likely run in 2.x with some fixes, but have not been " + "converted yet. Currently they hold on to operations and rely on " + "re-running them; for eager compatibility we need to 're-create' the op " + "each time.") +class UnconvertedFIFOQueueTests(test.TestCase): + + def testDequeueManyWithTensorParameter(self): + with self.cached_session(): + # Define a first queue that contains integer counts. + dequeue_counts = [random.randint(1, 10) for _ in range(100)] + count_q = data_flow_ops.FIFOQueue(100, dtypes_lib.int32, ()) + enqueue_counts_op = count_q.enqueue_many((dequeue_counts,)) + total_count = sum(dequeue_counts) + + # Define a second queue that contains total_count elements. + elems = [random.randint(0, 100) for _ in range(total_count)] + q = data_flow_ops.FIFOQueue(total_count, dtypes_lib.int32, ()) + enqueue_elems_op = q.enqueue_many((elems,)) + + # Define a subgraph that first dequeues a count, then DequeuesMany + # that number of elements. + dequeued_t = q.dequeue_many(count_q.dequeue()) + + enqueue_counts_op.run() + enqueue_elems_op.run() + + dequeued_elems = [] + for _ in dequeue_counts: + dequeued_elems.extend(dequeued_t.eval()) + self.assertEqual(elems, dequeued_elems) + + def testDequeueFromClosedQueue(self): with self.cached_session(): q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - with self.assertRaisesRegexp(ValueError, "must have names"): - q.enqueue({"a": 12.0}) - with self.assertRaisesRegexp(ValueError, "must have names"): - q.enqueue_many({"a": [12.0, 13.0]}) + elems = [10.0, 20.0, 30.0, 40.0] + enqueue_op = q.enqueue_many((elems,)) + close_op = q.close() + dequeued_t = q.dequeue() + + enqueue_op.run() + close_op.run() + for elem in elems: + self.assertEqual([elem], self.evaluate(dequeued_t)) + + # Expect the operation to fail due to the queue being closed. + with self.assertRaisesRegexp(errors_impl.OutOfRangeError, + "is closed and has insufficient"): + self.evaluate(dequeued_t) + + def testDoesNotLoseValue(self): + with self.cached_session(): + q = data_flow_ops.FIFOQueue(1, dtypes_lib.float32) + enqueue_op = q.enqueue((10.0,)) + size_t = q.size() + + enqueue_op.run() + for _ in range(500): + self.assertEqual(size_t.eval(), [1]) + + def testSharedQueueSameSession(self): + with self.cached_session(): + q1 = data_flow_ops.FIFOQueue( + 1, dtypes_lib.float32, shared_name="shared_queue") + q1.enqueue((10.0,)).run() + + q2 = data_flow_ops.FIFOQueue( + 1, dtypes_lib.float32, shared_name="shared_queue") + + q1_size_t = q1.size() + q2_size_t = q2.size() + + self.assertEqual(q1_size_t.eval(), [1]) + self.assertEqual(q2_size_t.eval(), [1]) + + self.assertEqual(q2.dequeue().eval(), [10.0]) + + self.assertEqual(q1_size_t.eval(), [0]) + self.assertEqual(q2_size_t.eval(), [0]) + + q2.enqueue((20.0,)).run() + + self.assertEqual(q1_size_t.eval(), [1]) + self.assertEqual(q2_size_t.eval(), [1]) + + self.assertEqual(q1.dequeue().eval(), [20.0]) + + self.assertEqual(q1_size_t.eval(), [0]) + self.assertEqual(q2_size_t.eval(), [0]) + + def testIncompatibleSharedQueueErrors(self): + with self.cached_session(): + q_a_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_a") + q_a_2 = data_flow_ops.FIFOQueue(15, dtypes_lib.float32, shared_name="q_a") + q_a_1.queue_ref.op.run() + with self.assertRaisesOpError("capacity"): + q_a_2.queue_ref.op.run() + + q_b_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_b") + q_b_2 = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, shared_name="q_b") + q_b_1.queue_ref.op.run() + with self.assertRaisesOpError("component types"): + q_b_2.queue_ref.op.run() + + q_c_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_c") + q_c_2 = data_flow_ops.FIFOQueue( + 10, dtypes_lib.float32, shapes=[(1, 1, 2, 3)], shared_name="q_c") + q_c_1.queue_ref.op.run() + with self.assertRaisesOpError("component shapes"): + q_c_2.queue_ref.op.run() + + q_d_1 = data_flow_ops.FIFOQueue( + 10, dtypes_lib.float32, shapes=[(1, 1, 2, 3)], shared_name="q_d") + q_d_2 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_d") + q_d_1.queue_ref.op.run() + with self.assertRaisesOpError("component shapes"): + q_d_2.queue_ref.op.run() + + q_e_1 = data_flow_ops.FIFOQueue( + 10, dtypes_lib.float32, shapes=[(1, 1, 2, 3)], shared_name="q_e") + q_e_2 = data_flow_ops.FIFOQueue( + 10, dtypes_lib.float32, shapes=[(1, 1, 2, 4)], shared_name="q_e") + q_e_1.queue_ref.op.run() + with self.assertRaisesOpError("component shapes"): + q_e_2.queue_ref.op.run() + + q_f_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_f") + q_f_2 = data_flow_ops.FIFOQueue( + 10, (dtypes_lib.float32, dtypes_lib.int32), shared_name="q_f") + q_f_1.queue_ref.op.run() + with self.assertRaisesOpError("component types"): + q_f_2.queue_ref.op.run() + + def testSelectQueue(self): + with self.cached_session(): + num_queues = 10 + qlist = [] + for _ in xrange(num_queues): + qlist.append(data_flow_ops.FIFOQueue(10, dtypes_lib.float32)) + # Enqueue/Dequeue into a dynamically selected queue + for _ in xrange(20): + index = np.random.randint(num_queues) + q = data_flow_ops.FIFOQueue.from_list(index, qlist) + q.enqueue((10.,)).run() + self.assertEqual(q.dequeue().eval(), 10.0) + + def testSelectQueueOutOfRange(self): + with self.cached_session(): + q1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) + q2 = data_flow_ops.FIFOQueue(15, dtypes_lib.float32) + enq_q = data_flow_ops.FIFOQueue.from_list(3, [q1, q2]) + with self.assertRaisesOpError("is not in"): + enq_q.dequeue().eval() + + def testDtypes(self): + with self.cached_session() as sess: + dtypes = [ + dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.int32, + dtypes_lib.uint8, dtypes_lib.int16, dtypes_lib.int8, dtypes_lib.int64, + dtypes_lib.uint16, dtypes_lib.bool, dtypes_lib.complex64, + dtypes_lib.complex128 + ] + shape = (32, 4, 128) + q = data_flow_ops.FIFOQueue(32, dtypes, [shape[1:]] * len(dtypes)) + + input_tuple = [] + for dtype in dtypes: + np_dtype = dtype.as_numpy_dtype + np_array = np.random.randint(-10, 10, shape) + if dtype == dtypes_lib.bool: + np_array = np_array > 0 + elif dtype in (dtypes_lib.complex64, dtypes_lib.complex128): + np_array = np.sqrt(np_array.astype(np_dtype)) + else: + np_array = np_array.astype(np_dtype) + input_tuple.append(np_array) + + q.enqueue_many(input_tuple).run() + + output_tuple_t = q.dequeue_many(32) + output_tuple = self.evaluate(output_tuple_t) + + for (input_elem, output_elem) in zip(input_tuple, output_tuple): + self.assertAllEqual(input_elem, output_elem) + + def testEnqueueDequeueOneComponent(self): + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue( + 10, dtypes_lib.float32, shapes=((),), names="f") + # Verify that enqueue() checks that when using names we must enqueue a + # dictionary. + with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): + enqueue_op = q.enqueue(10.0) + with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): + enqueue_op = q.enqueue((10.0,)) + # The dictionary keys must match the queue component names. + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op = q.enqueue({}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op = q.enqueue({"x": 12}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op = q.enqueue({"f": 10.0, "s": "aa"}) + enqueue_op = q.enqueue({"f": 10.0}) + enqueue_op2 = q.enqueue({"f": 20.0}) + enqueue_op3 = q.enqueue({"f": 30.0}) + # Verify that enqueue_many() checks that when using names we must enqueue + # a dictionary. + with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): + enqueue_op4 = q.enqueue_many([40.0, 50.0]) + # The dictionary keys must match the queue component names. + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op4 = q.enqueue_many({}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op4 = q.enqueue_many({"x": 12}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op4 = q.enqueue_many({"f": [40.0, 50.0], "s": ["aa", "bb"]}) + enqueue_op4 = q.enqueue_many({"f": [40.0, 50.0]}) + dequeue = q.dequeue() + dequeue_2 = q.dequeue_many(2) + self.evaluate(enqueue_op) + self.evaluate(enqueue_op2) + self.evaluate(enqueue_op3) + self.evaluate(enqueue_op4) + f = sess.run(dequeue["f"]) + self.assertEqual(10.0, f) + f = sess.run(dequeue_2["f"]) + self.assertEqual([20.0, 30.0], list(f)) + f = sess.run(dequeue_2["f"]) + self.assertEqual([40.0, 50.0], list(f)) + + def testEnqueueDequeueMultipleComponent(self): + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue( + 10, (dtypes_lib.float32, dtypes_lib.int32, dtypes_lib.string), + shapes=((), (), ()), + names=("f", "i", "s")) + # Verify that enqueue() checks that when using names we must enqueue a + # dictionary. + with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): + enqueue_op = q.enqueue((10.0, 123, "aa")) + # The dictionary keys must match the queue component names. + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op = q.enqueue({}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op = q.enqueue({"x": 10.0}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op = q.enqueue({"i": 12, "s": "aa"}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op = q.enqueue({"i": 123, "s": "aa", "f": 10.0, "x": 10.0}) + enqueue_op = q.enqueue({"i": 123, "s": "aa", "f": 10.0}) + enqueue_op2 = q.enqueue({"i": 124, "s": "bb", "f": 20.0}) + enqueue_op3 = q.enqueue({"i": 125, "s": "cc", "f": 30.0}) + # Verify that enqueue_many() checks that when using names we must enqueue + # a dictionary. + with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): + enqueue_op4 = q.enqueue_many(([40.0, 50.0], [126, 127], ["dd", "ee"])) + # The dictionary keys must match the queue component names. + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op4 = q.enqueue_many({}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op4 = q.enqueue_many({"x": [10.0, 20.0]}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op4 = q.enqueue_many({"i": [12, 12], "s": ["aa", "bb"]}) + with self.assertRaisesRegexp(ValueError, "match names of Queue"): + enqueue_op4 = q.enqueue_many({ + "f": [40.0, 50.0], + "i": [126, 127], + "s": ["dd", "ee"], + "x": [1, 2] + }) + enqueue_op4 = q.enqueue_many({ + "f": [40.0, 50.0], + "i": [126, 127], + "s": ["dd", "ee"] + }) + dequeue = q.dequeue() + dequeue_2 = q.dequeue_many(2) + self.evaluate(enqueue_op) + self.evaluate(enqueue_op2) + self.evaluate(enqueue_op3) + self.evaluate(enqueue_op4) + i, f, s = sess.run([dequeue["i"], dequeue["f"], dequeue["s"]]) + self.assertEqual(123, i) + self.assertEqual(10.0, f) + self.assertEqual(compat.as_bytes("aa"), s) + i, f, s = sess.run([dequeue_2["i"], dequeue_2["f"], dequeue_2["s"]]) + self.assertEqual([124, 125], list(i)) + self.assertTrue([20.0, 30.0], list(f)) + self.assertTrue([compat.as_bytes("bb"), compat.as_bytes("cc")], list(s)) + i, f, s = sess.run([dequeue_2["i"], dequeue_2["f"], dequeue_2["s"]]) + self.assertEqual([126, 127], list(i)) + self.assertTrue([40.0, 50.0], list(f)) + self.assertTrue([compat.as_bytes("dd"), compat.as_bytes("ee")], list(s)) + + def testBatchSizeMismatch(self): + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32, + dtypes_lib.int32), ((), (), ())) + + with self.assertRaises(ValueError): + q.enqueue_many(([1, 2, 3], [1, 2], [1, 2, 3])) + + with self.assertRaises(ValueError): + q.enqueue_many( + ([1, 2, 3], [1, 2], array_ops.placeholder(dtypes_lib.int32))) + + with self.assertRaises(ValueError): + q.enqueue_many( + (array_ops.placeholder(dtypes_lib.int32), [1, 2], [1, 2, 3])) + + def testEnqueueWrongShapeAtRuntime(self): + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32), ( + (2, 2), (3, 3))) + elems_ok = np.array([1] * 4).reshape((2, 2)).astype(np.int32) + elems_bad = array_ops.placeholder(dtypes_lib.int32) + enqueue_op = q.enqueue((elems_ok, elems_bad)) + with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, + r"Expected \[3,3\], got \[3,4\]"): + sess.run([enqueue_op], + feed_dict={elems_bad: np.array([1] * 12).reshape((3, 4))}) + + def testEnqueueDequeueManyWrongShape(self): + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32), ( + (2, 2), (3, 3))) + elems_ok = np.array([1] * 8).reshape((2, 2, 2)).astype(np.int32) + elems_bad = array_ops.placeholder(dtypes_lib.int32) + enqueue_op = q.enqueue_many((elems_ok, elems_bad)) + dequeued_t = q.dequeue_many(2) + with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, + "Shape mismatch in tuple component 1. " + r"Expected \[2,3,3\], got \[2,3,4\]"): + sess.run([enqueue_op], + feed_dict={elems_bad: np.array([1] * 24).reshape((2, 3, 4))}) + self.evaluate(dequeued_t) + + +@test_util.run_v1_only( + "These tests are heavily reliant on Session for parallelism. We can likely " + "convert them to run in 2.x/eager, but it may be difficult.") +class FIFOQueueParallelTests(test.TestCase): def testParallelEnqueue(self): # We need each thread to keep its own device stack or the device scopes @@ -207,34 +797,6 @@ class FIFOQueueTest(test.TestCase): thread.join() self.assertItemsEqual(elems, results) - def testDequeue(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - elems = [10.0, 20.0, 30.0] - enqueue_ops = [q.enqueue((x,)) for x in elems] - dequeued_t = q.dequeue() - - for enqueue_op in enqueue_ops: - enqueue_op.run() - - for i in xrange(len(elems)): - vals = self.evaluate(dequeued_t) - self.assertEqual([elems[i]], vals) - - def testDequeueHalf(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float16) - elems = [10.0, 20.0, 30.0] - enqueue_ops = [q.enqueue((x,)) for x in elems] - dequeued_t = q.dequeue() - - for enqueue_op in enqueue_ops: - enqueue_op.run() - - for i in xrange(len(elems)): - vals = self.evaluate(dequeued_t) - self.assertEqual([elems[i]], vals) - def testEnqueueAndBlockingDequeue(self): # We need each thread to keep its own device stack or the device scopes # won't be properly nested. @@ -268,550 +830,125 @@ class FIFOQueueTest(test.TestCase): for elem, result in zip(elems, results): self.assertEqual([elem], result) - def testMultiEnqueueAndDequeue(self): + def testBigDequeueMany(self): with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.float32)) - elems = [(5, 10.0), (10, 20.0), (15, 30.0)] - enqueue_ops = [q.enqueue((x, y)) for x, y in elems] - dequeued_t = q.dequeue() + q = data_flow_ops.FIFOQueue(2, dtypes_lib.int32, ((),)) + elem = np.arange(4, dtype=np.int32) + enq_list = [q.enqueue((e,)) for e in elem] + deq = q.dequeue_many(4) - for enqueue_op in enqueue_ops: - enqueue_op.run() + results = [] - for i in xrange(len(elems)): - x_val, y_val = self.evaluate(dequeued_t) - x, y = elems[i] - self.assertEqual([x], x_val) - self.assertEqual([y], y_val) + def blocking_dequeue(): + # Will only complete after 4 enqueues complete. + results.extend(self.evaluate(deq)) - def testQueueSizeEmpty(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - self.assertEqual([0], q.size().eval()) - - def testQueueSizeAfterEnqueueAndDequeue(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - enqueue_op = q.enqueue((10.0,)) - dequeued_t = q.dequeue() - size = q.size() - self.assertEqual([], size.get_shape()) - - enqueue_op.run() - self.assertEqual(1, self.evaluate(size)) - dequeued_t.op.run() - self.assertEqual(0, self.evaluate(size)) - - def testEnqueueMany(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - elems = [10.0, 20.0, 30.0, 40.0] - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue() - enqueue_op.run() - enqueue_op.run() - - for i in range(8): - vals = self.evaluate(dequeued_t) - self.assertEqual([elems[i % 4]], vals) - - def testEmptyEnqueueMany(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - empty_t = constant_op.constant( - [], dtype=dtypes_lib.float32, shape=[0, 2, 3]) - enqueue_op = q.enqueue_many((empty_t,)) - size_t = q.size() - - self.assertEqual([0], self.evaluate(size_t)) - enqueue_op.run() - self.assertEqual([0], self.evaluate(size_t)) - - def testEmptyDequeueMany(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shapes=()) - enqueue_op = q.enqueue((10.0,)) - dequeued_t = q.dequeue_many(0) - - self.assertEqual([], self.evaluate(dequeued_t).tolist()) - enqueue_op.run() - self.assertEqual([], self.evaluate(dequeued_t).tolist()) - - def testEmptyDequeueUpTo(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shapes=()) - enqueue_op = q.enqueue((10.0,)) - dequeued_t = q.dequeue_up_to(0) - - self.assertEqual([], self.evaluate(dequeued_t).tolist()) - enqueue_op.run() - self.assertEqual([], self.evaluate(dequeued_t).tolist()) - - def testEmptyDequeueManyWithNoShape(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - # Expect the operation to fail due to the shape not being constrained. - with self.assertRaisesOpError("specified shapes"): - q.dequeue_many(0).eval() - - def testMultiEnqueueMany(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.float32, dtypes_lib.int32)) - float_elems = [10.0, 20.0, 30.0, 40.0] - int_elems = [[1, 2], [3, 4], [5, 6], [7, 8]] - enqueue_op = q.enqueue_many((float_elems, int_elems)) - dequeued_t = q.dequeue() - - enqueue_op.run() - enqueue_op.run() - - for i in range(8): - float_val, int_val = self.evaluate(dequeued_t) - self.assertEqual(float_elems[i % 4], float_val) - self.assertAllEqual(int_elems[i % 4], int_val) - - def testDequeueMany(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) - elems = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue_many(4) - - enqueue_op.run() - - self.assertAllEqual(elems[0:4], self.evaluate(dequeued_t)) - self.assertAllEqual(elems[4:8], self.evaluate(dequeued_t)) - - def testDequeueUpToNoBlocking(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) - elems = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0] - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue_up_to(4) - - enqueue_op.run() - - self.assertAllEqual(elems[0:4], self.evaluate(dequeued_t)) - self.assertAllEqual(elems[4:8], self.evaluate(dequeued_t)) - - def testMultiDequeueMany(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue( - 10, (dtypes_lib.float32, dtypes_lib.int32), shapes=((), (2,))) - float_elems = [ - 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0 - ] - int_elems = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], - [15, 16], [17, 18], [19, 20]] - enqueue_op = q.enqueue_many((float_elems, int_elems)) - dequeued_t = q.dequeue_many(4) - dequeued_single_t = q.dequeue() - - enqueue_op.run() - - float_val, int_val = self.evaluate(dequeued_t) - self.assertAllEqual(float_elems[0:4], float_val) - self.assertAllEqual(int_elems[0:4], int_val) - self.assertEqual(float_val.shape, dequeued_t[0].get_shape()) - self.assertEqual(int_val.shape, dequeued_t[1].get_shape()) - - float_val, int_val = self.evaluate(dequeued_t) - self.assertAllEqual(float_elems[4:8], float_val) - self.assertAllEqual(int_elems[4:8], int_val) - - float_val, int_val = self.evaluate(dequeued_single_t) - self.assertAllEqual(float_elems[8], float_val) - self.assertAllEqual(int_elems[8], int_val) - self.assertEqual(float_val.shape, dequeued_single_t[0].get_shape()) - self.assertEqual(int_val.shape, dequeued_single_t[1].get_shape()) - - def testMultiDequeueUpToNoBlocking(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue( - 10, (dtypes_lib.float32, dtypes_lib.int32), shapes=((), (2,))) - float_elems = [ - 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0 - ] - int_elems = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], - [15, 16], [17, 18], [19, 20]] - enqueue_op = q.enqueue_many((float_elems, int_elems)) - dequeued_t = q.dequeue_up_to(4) - - enqueue_op.run() - - float_val, int_val = self.evaluate(dequeued_t) - self.assertAllEqual(float_elems[0:4], float_val) - self.assertAllEqual(int_elems[0:4], int_val) - self.assertEqual([None], dequeued_t[0].get_shape().as_list()) - self.assertEqual([None, 2], dequeued_t[1].get_shape().as_list()) - - float_val, int_val = self.evaluate(dequeued_t) - self.assertAllEqual(float_elems[4:8], float_val) - self.assertAllEqual(int_elems[4:8], int_val) - - def testHighDimension(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, (4, 4, 4, 4)) - elems = np.array([[[[[x] * 4] * 4] * 4] * 4 for x in range(10)], np.int32) - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue_many(10) - - enqueue_op.run() - self.assertAllEqual(dequeued_t.eval(), elems) - - def testEnqueueWrongShape(self): - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32), ((), - (2))) - - with self.assertRaises(ValueError): - q.enqueue(([1, 2], [2, 2])) - - with self.assertRaises(ValueError): - q.enqueue_many((7, [[1, 2], [3, 4], [5, 6]])) - - def testBatchSizeMismatch(self): - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32, - dtypes_lib.int32), ((), (), ())) - - with self.assertRaises(ValueError): - q.enqueue_many(([1, 2, 3], [1, 2], [1, 2, 3])) - - with self.assertRaises(ValueError): - q.enqueue_many( - ([1, 2, 3], [1, 2], array_ops.placeholder(dtypes_lib.int32))) - - with self.assertRaises(ValueError): - q.enqueue_many( - (array_ops.placeholder(dtypes_lib.int32), [1, 2], [1, 2, 3])) - - def testEnqueueManyEmptyTypeConversion(self): - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.float32), ( - (), ())) - enq = q.enqueue_many(([], [])) - self.assertEqual(dtypes_lib.int32, enq.inputs[1].dtype) - self.assertEqual(dtypes_lib.float32, enq.inputs[2].dtype) - - def testEnqueueWrongType(self): - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.float32), ( - (), ())) - - with self.assertRaises(ValueError): - q.enqueue((array_ops.placeholder(dtypes_lib.int32), - array_ops.placeholder(dtypes_lib.int32))) - - with self.assertRaises(ValueError): - q.enqueue_many((array_ops.placeholder(dtypes_lib.int32), - array_ops.placeholder(dtypes_lib.int32))) - - def testEnqueueWrongShapeAtRuntime(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32), ( - (2, 2), (3, 3))) - elems_ok = np.array([1] * 4).reshape((2, 2)).astype(np.int32) - elems_bad = array_ops.placeholder(dtypes_lib.int32) - enqueue_op = q.enqueue((elems_ok, elems_bad)) - with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, - r"Expected \[3,3\], got \[3,4\]"): - sess.run([enqueue_op], - feed_dict={elems_bad: np.array([1] * 12).reshape((3, 4))}) - - def testEnqueueDequeueManyWrongShape(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, (dtypes_lib.int32, dtypes_lib.int32), ( - (2, 2), (3, 3))) - elems_ok = np.array([1] * 8).reshape((2, 2, 2)).astype(np.int32) - elems_bad = array_ops.placeholder(dtypes_lib.int32) - enqueue_op = q.enqueue_many((elems_ok, elems_bad)) - dequeued_t = q.dequeue_many(2) - with self.assertRaisesRegexp(errors_impl.InvalidArgumentError, - "Shape mismatch in tuple component 1. " - r"Expected \[2,3,3\], got \[2,3,4\]"): - sess.run([enqueue_op], - feed_dict={elems_bad: np.array([1] * 24).reshape((2, 3, 4))}) - self.evaluate(dequeued_t) - - def testParallelEnqueueMany(self): - # We need each thread to keep its own device stack or the device scopes - # won't be properly nested. - ops.get_default_graph().switch_to_thread_local() - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(1000, dtypes_lib.float32, shapes=()) - elems = [10.0 * x for x in range(100)] - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue_many(1000) - - # Enqueue 100 items in parallel on 10 threads. - def enqueue(): - self.evaluate(enqueue_op) - - threads = [self.checkedThread(target=enqueue) for _ in range(10)] - for thread in threads: - thread.start() - for thread in threads: - thread.join() - - self.assertItemsEqual(dequeued_t.eval(), elems * 10) - - def testParallelDequeueMany(self): - # We need each thread to keep its own device stack or the device scopes - # won't be properly nested. - ops.get_default_graph().switch_to_thread_local() - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(1000, dtypes_lib.float32, shapes=()) - elems = [10.0 * x for x in range(1000)] - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue_many(100) - - enqueue_op.run() - - # Dequeue 100 items in parallel on 10 threads. - dequeued_elems = [] - - def dequeue(): - dequeued_elems.extend(self.evaluate(dequeued_t)) - - threads = [self.checkedThread(target=dequeue) for _ in range(10)] - for thread in threads: - thread.start() - for thread in threads: - thread.join() - self.assertItemsEqual(elems, dequeued_elems) - - def testParallelDequeueUpTo(self): - # We need each thread to keep its own device stack or the device scopes - # won't be properly nested. - ops.get_default_graph().switch_to_thread_local() - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(1000, dtypes_lib.float32, shapes=()) - elems = [10.0 * x for x in range(1000)] - enqueue_op = q.enqueue_many((elems,)) - close_op = q.close() - dequeued_t = q.dequeue_up_to(101) - - enqueue_op.run() - close_op.run() - - # Dequeue up to 101 items in parallel on 10 threads, from closed queue. - dequeued_elems = [] - - def dequeue(): - dequeued_elems.extend(self.evaluate(dequeued_t)) - - threads = [self.checkedThread(target=dequeue) for _ in range(10)] - for thread in threads: - thread.start() - for thread in threads: - thread.join() - self.assertItemsEqual(elems, dequeued_elems) - - def testParallelEnqueueAndDequeue(self): - # We need each thread to keep its own device stack or the device scopes - # won't be properly nested. - ops.get_default_graph().switch_to_thread_local() - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(50, dtypes_lib.float32, shapes=()) - initial_elements = [10.0] * 49 - q.enqueue_many((initial_elements,)).run() - - enqueue_op = q.enqueue((20.0,)) - dequeued_t = q.dequeue() - - def enqueue(): - for _ in xrange(100): - self.evaluate(enqueue_op) - - def dequeue(): - for _ in xrange(100): - self.assertTrue(self.evaluate(dequeued_t) in (10.0, 20.0)) - - enqueue_threads = [self.checkedThread(target=enqueue) for _ in range(10)] - dequeue_threads = [self.checkedThread(target=dequeue) for _ in range(10)] - for enqueue_thread in enqueue_threads: - enqueue_thread.start() - for dequeue_thread in dequeue_threads: - dequeue_thread.start() - for enqueue_thread in enqueue_threads: - enqueue_thread.join() - for dequeue_thread in dequeue_threads: - dequeue_thread.join() - - # Dequeue the initial count of elements to clean up. - cleanup_elems = q.dequeue_many(49).eval() - for elem in cleanup_elems: - self.assertTrue(elem in (10.0, 20.0)) - - def testMixtureOfEnqueueAndEnqueueMany(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, shapes=()) - enqueue_placeholder = array_ops.placeholder(dtypes_lib.int32, shape=()) - enqueue_op = q.enqueue((enqueue_placeholder,)) - enqueuemany_placeholder = array_ops.placeholder( - dtypes_lib.int32, shape=(None,)) - enqueuemany_op = q.enqueue_many((enqueuemany_placeholder,)) - - dequeued_t = q.dequeue() - close_op = q.close() - - def dequeue(): - for i in xrange(250): - self.assertEqual(i, self.evaluate(dequeued_t)) - - dequeue_thread = self.checkedThread(target=dequeue) - dequeue_thread.start() - - elements_enqueued = 0 - while elements_enqueued < 250: - # With equal probability, run Enqueue or enqueue_many. - if random.random() > 0.5: - enqueue_op.run({enqueue_placeholder: elements_enqueued}) - elements_enqueued += 1 - else: - count = random.randint(0, min(20, 250 - elements_enqueued)) - range_to_enqueue = np.arange( - elements_enqueued, elements_enqueued + count, dtype=np.int32) - enqueuemany_op.run({enqueuemany_placeholder: range_to_enqueue}) - elements_enqueued += count - - close_op.run() - dequeue_thread.join() - self.assertEqual(0, q.size().eval()) - - def testMixtureOfDequeueAndDequeueMany(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, shapes=()) - enqueue_op = q.enqueue_many((np.arange(250, dtype=np.int32),)) - dequeued_t = q.dequeue() - count_placeholder = array_ops.placeholder(dtypes_lib.int32, shape=()) - dequeuemany_t = q.dequeue_many(count_placeholder) - - def enqueue(): - self.evaluate(enqueue_op) - - enqueue_thread = self.checkedThread(target=enqueue) - enqueue_thread.start() - - elements_dequeued = 0 - while elements_dequeued < 250: - # With equal probability, run Dequeue or dequeue_many. - if random.random() > 0.5: - self.assertEqual(elements_dequeued, self.evaluate(dequeued_t)) - elements_dequeued += 1 - else: - count = random.randint(0, min(20, 250 - elements_dequeued)) - expected_range = np.arange( - elements_dequeued, elements_dequeued + count, dtype=np.int32) - self.assertAllEqual(expected_range, - dequeuemany_t.eval({ - count_placeholder: count - })) - elements_dequeued += count - - q.close().run() - enqueue_thread.join() - self.assertEqual(0, q.size().eval()) - - def testBlockingDequeueMany(self): - # We need each thread to keep its own device stack or the device scopes - # won't be properly nested. - ops.get_default_graph().switch_to_thread_local() - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) - elems = [10.0, 20.0, 30.0, 40.0] - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue_many(4) - - dequeued_elems = [] - - def enqueue(): - # The enqueue_op should run after the dequeue op has blocked. + thread = self.checkedThread(target=blocking_dequeue) + thread.start() + # The dequeue should start and then block. + for enq in enq_list: # TODO(mrry): Figure out how to do this without sleeping. time.sleep(0.1) - self.evaluate(enqueue_op) + self.assertEqual(len(results), 0) + self.evaluate(enq) - def dequeue(): - dequeued_elems.extend(self.evaluate(dequeued_t).tolist()) + # Enough enqueued to unblock the dequeue + thread.join() + self.assertAllEqual(elem, results) - enqueue_thread = self.checkedThread(target=enqueue) - dequeue_thread = self.checkedThread(target=dequeue) - enqueue_thread.start() - dequeue_thread.start() - enqueue_thread.join() - dequeue_thread.join() - - self.assertAllEqual(elems, dequeued_elems) - - def testBlockingDequeueUpTo(self): - # We need each thread to keep its own device stack or the device scopes - # won't be properly nested. - ops.get_default_graph().switch_to_thread_local() + def testBigEnqueueMany(self): with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) - elems = [10.0, 20.0, 30.0, 40.0] - enqueue_op = q.enqueue_many((elems,)) - dequeued_t = q.dequeue_up_to(4) + q = data_flow_ops.FIFOQueue(5, dtypes_lib.int32, ((),)) + elem = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + enq = q.enqueue_many((elem,)) + deq = q.dequeue() + size_op = q.size() - dequeued_elems = [] + enq_done = [] - def enqueue(): - # The enqueue_op should run after the dequeue op has blocked. - # TODO(mrry): Figure out how to do this without sleeping. - time.sleep(0.1) - self.evaluate(enqueue_op) + def blocking_enqueue(): + enq_done.append(False) + # This will fill the queue and then block until enough dequeues happen. + self.evaluate(enq) + enq_done.append(True) - def dequeue(): - dequeued_elems.extend(self.evaluate(dequeued_t).tolist()) + thread = self.checkedThread(target=blocking_enqueue) + thread.start() - enqueue_thread = self.checkedThread(target=enqueue) - dequeue_thread = self.checkedThread(target=dequeue) - enqueue_thread.start() - dequeue_thread.start() - enqueue_thread.join() - dequeue_thread.join() + # The enqueue should start and then block. + results = [] + results.append(deq.eval()) # Will only complete after the enqueue starts. + self.assertEqual(len(enq_done), 1) + self.assertEqual(self.evaluate(size_op), 5) - self.assertAllEqual(elems, dequeued_elems) + for _ in range(3): + results.append(deq.eval()) - def testDequeueManyWithTensorParameter(self): - with self.cached_session(): - # Define a first queue that contains integer counts. - dequeue_counts = [random.randint(1, 10) for _ in range(100)] - count_q = data_flow_ops.FIFOQueue(100, dtypes_lib.int32, ()) - enqueue_counts_op = count_q.enqueue_many((dequeue_counts,)) - total_count = sum(dequeue_counts) + time.sleep(0.1) + self.assertEqual(len(enq_done), 1) + self.assertEqual(self.evaluate(size_op), 5) - # Define a second queue that contains total_count elements. - elems = [random.randint(0, 100) for _ in range(total_count)] - q = data_flow_ops.FIFOQueue(total_count, dtypes_lib.int32, ()) - enqueue_elems_op = q.enqueue_many((elems,)) + # This dequeue will unblock the thread. + results.append(deq.eval()) + time.sleep(0.1) + self.assertEqual(len(enq_done), 2) + thread.join() - # Define a subgraph that first dequeues a count, then DequeuesMany - # that number of elements. - dequeued_t = q.dequeue_many(count_q.dequeue()) + for i in range(5): + self.assertEqual(size_op.eval(), 5 - i) + results.append(deq.eval()) + self.assertEqual(size_op.eval(), 5 - i - 1) - enqueue_counts_op.run() - enqueue_elems_op.run() + self.assertAllEqual(elem, results) - dequeued_elems = [] - for _ in dequeue_counts: - dequeued_elems.extend(dequeued_t.eval()) - self.assertEqual(elems, dequeued_elems) + def _blockingDequeue(self, sess, dequeue_op): + with self.assertRaisesOpError("was cancelled"): + sess.run(dequeue_op) - def testDequeueFromClosedQueue(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - elems = [10.0, 20.0, 30.0, 40.0] - enqueue_op = q.enqueue_many((elems,)) - close_op = q.close() - dequeued_t = q.dequeue() + def _blockingDequeueMany(self, sess, dequeue_many_op): + with self.assertRaisesOpError("was cancelled"): + sess.run(dequeue_many_op) - enqueue_op.run() - close_op.run() - for elem in elems: - self.assertEqual([elem], self.evaluate(dequeued_t)) + def _blockingEnqueue(self, sess, enqueue_op): + with self.assertRaisesOpError("was cancelled"): + sess.run(enqueue_op) - # Expect the operation to fail due to the queue being closed. - with self.assertRaisesRegexp(errors_impl.OutOfRangeError, - "is closed and has insufficient"): - self.evaluate(dequeued_t) + def _blockingEnqueueMany(self, sess, enqueue_many_op): + with self.assertRaisesOpError("was cancelled"): + sess.run(enqueue_many_op) + + def testResetOfBlockingOperation(self): + with self.session() as sess: + q_empty = data_flow_ops.FIFOQueue(5, dtypes_lib.float32, ()) + dequeue_op = q_empty.dequeue() + dequeue_many_op = q_empty.dequeue_many(1) + + q_full = data_flow_ops.FIFOQueue(5, dtypes_lib.float32) + sess.run(q_full.enqueue_many(([1.0, 2.0, 3.0, 4.0, 5.0],))) + enqueue_op = q_full.enqueue((6.0,)) + enqueue_many_op = q_full.enqueue_many(([6.0],)) + + threads = [ + self.checkedThread( + self._blockingDequeue, args=(sess, dequeue_op)), + self.checkedThread( + self._blockingDequeueMany, args=(sess, dequeue_many_op)), + self.checkedThread( + self._blockingEnqueue, args=(sess, enqueue_op)), + self.checkedThread( + self._blockingEnqueueMany, args=(sess, enqueue_many_op)) + ] + for t in threads: + t.start() + time.sleep(0.1) + sess.close() # Will cancel the blocked operations. + for t in threads: + t.join() + + # Create a new session that hasn't been closed, so cached_session + # isn't messed up. + with self.session() as sess: + pass def testBlockingDequeueFromClosedQueue(self): # We need each thread to keep its own device stack or the device scopes @@ -1218,260 +1355,245 @@ class FIFOQueueTest(test.TestCase): for elem in [20.0, 30.0, 50.0, 60.0]: self.assertEqual(elem, self.evaluate(dequeued_t)) - def testDoesNotLoseValue(self): - with self.cached_session(): - q = data_flow_ops.FIFOQueue(1, dtypes_lib.float32) - enqueue_op = q.enqueue((10.0,)) - size_t = q.size() + def testParallelEnqueueMany(self): + # We need each thread to keep its own device stack or the device scopes + # won't be properly nested. + ops.get_default_graph().switch_to_thread_local() + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue(1000, dtypes_lib.float32, shapes=()) + elems = [10.0 * x for x in range(100)] + enqueue_op = q.enqueue_many((elems,)) + dequeued_t = q.dequeue_many(1000) + + # Enqueue 100 items in parallel on 10 threads. + def enqueue(): + self.evaluate(enqueue_op) + + threads = [self.checkedThread(target=enqueue) for _ in range(10)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + + self.assertItemsEqual(dequeued_t.eval(), elems * 10) + + def testParallelDequeueMany(self): + # We need each thread to keep its own device stack or the device scopes + # won't be properly nested. + ops.get_default_graph().switch_to_thread_local() + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue(1000, dtypes_lib.float32, shapes=()) + elems = [10.0 * x for x in range(1000)] + enqueue_op = q.enqueue_many((elems,)) + dequeued_t = q.dequeue_many(100) enqueue_op.run() - for _ in range(500): - self.assertEqual(size_t.eval(), [1]) - def testSharedQueueSameSession(self): - with self.cached_session(): - q1 = data_flow_ops.FIFOQueue( - 1, dtypes_lib.float32, shared_name="shared_queue") - q1.enqueue((10.0,)).run() + # Dequeue 100 items in parallel on 10 threads. + dequeued_elems = [] - q2 = data_flow_ops.FIFOQueue( - 1, dtypes_lib.float32, shared_name="shared_queue") + def dequeue(): + dequeued_elems.extend(self.evaluate(dequeued_t)) - q1_size_t = q1.size() - q2_size_t = q2.size() + threads = [self.checkedThread(target=dequeue) for _ in range(10)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + self.assertItemsEqual(elems, dequeued_elems) - self.assertEqual(q1_size_t.eval(), [1]) - self.assertEqual(q2_size_t.eval(), [1]) - - self.assertEqual(q2.dequeue().eval(), [10.0]) - - self.assertEqual(q1_size_t.eval(), [0]) - self.assertEqual(q2_size_t.eval(), [0]) - - q2.enqueue((20.0,)).run() - - self.assertEqual(q1_size_t.eval(), [1]) - self.assertEqual(q2_size_t.eval(), [1]) - - self.assertEqual(q1.dequeue().eval(), [20.0]) - - self.assertEqual(q1_size_t.eval(), [0]) - self.assertEqual(q2_size_t.eval(), [0]) - - def testIncompatibleSharedQueueErrors(self): - with self.cached_session(): - q_a_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_a") - q_a_2 = data_flow_ops.FIFOQueue(15, dtypes_lib.float32, shared_name="q_a") - q_a_1.queue_ref.op.run() - with self.assertRaisesOpError("capacity"): - q_a_2.queue_ref.op.run() - - q_b_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_b") - q_b_2 = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, shared_name="q_b") - q_b_1.queue_ref.op.run() - with self.assertRaisesOpError("component types"): - q_b_2.queue_ref.op.run() - - q_c_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_c") - q_c_2 = data_flow_ops.FIFOQueue( - 10, dtypes_lib.float32, shapes=[(1, 1, 2, 3)], shared_name="q_c") - q_c_1.queue_ref.op.run() - with self.assertRaisesOpError("component shapes"): - q_c_2.queue_ref.op.run() - - q_d_1 = data_flow_ops.FIFOQueue( - 10, dtypes_lib.float32, shapes=[(1, 1, 2, 3)], shared_name="q_d") - q_d_2 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_d") - q_d_1.queue_ref.op.run() - with self.assertRaisesOpError("component shapes"): - q_d_2.queue_ref.op.run() - - q_e_1 = data_flow_ops.FIFOQueue( - 10, dtypes_lib.float32, shapes=[(1, 1, 2, 3)], shared_name="q_e") - q_e_2 = data_flow_ops.FIFOQueue( - 10, dtypes_lib.float32, shapes=[(1, 1, 2, 4)], shared_name="q_e") - q_e_1.queue_ref.op.run() - with self.assertRaisesOpError("component shapes"): - q_e_2.queue_ref.op.run() - - q_f_1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, shared_name="q_f") - q_f_2 = data_flow_ops.FIFOQueue( - 10, (dtypes_lib.float32, dtypes_lib.int32), shared_name="q_f") - q_f_1.queue_ref.op.run() - with self.assertRaisesOpError("component types"): - q_f_2.queue_ref.op.run() - - def testSelectQueue(self): - with self.cached_session(): - num_queues = 10 - qlist = [] - for _ in xrange(num_queues): - qlist.append(data_flow_ops.FIFOQueue(10, dtypes_lib.float32)) - # Enqueue/Dequeue into a dynamically selected queue - for _ in xrange(20): - index = np.random.randint(num_queues) - q = data_flow_ops.FIFOQueue.from_list(index, qlist) - q.enqueue((10.,)).run() - self.assertEqual(q.dequeue().eval(), 10.0) - - def testSelectQueueOutOfRange(self): - with self.cached_session(): - q1 = data_flow_ops.FIFOQueue(10, dtypes_lib.float32) - q2 = data_flow_ops.FIFOQueue(15, dtypes_lib.float32) - enq_q = data_flow_ops.FIFOQueue.from_list(3, [q1, q2]) - with self.assertRaisesOpError("is not in"): - enq_q.dequeue().eval() - - def _blockingDequeue(self, sess, dequeue_op): - with self.assertRaisesOpError("was cancelled"): - sess.run(dequeue_op) - - def _blockingDequeueMany(self, sess, dequeue_many_op): - with self.assertRaisesOpError("was cancelled"): - sess.run(dequeue_many_op) - - def _blockingEnqueue(self, sess, enqueue_op): - with self.assertRaisesOpError("was cancelled"): - sess.run(enqueue_op) - - def _blockingEnqueueMany(self, sess, enqueue_many_op): - with self.assertRaisesOpError("was cancelled"): - sess.run(enqueue_many_op) - - def testResetOfBlockingOperation(self): - with self.session() as sess: - q_empty = data_flow_ops.FIFOQueue(5, dtypes_lib.float32, ()) - dequeue_op = q_empty.dequeue() - dequeue_many_op = q_empty.dequeue_many(1) - - q_full = data_flow_ops.FIFOQueue(5, dtypes_lib.float32) - sess.run(q_full.enqueue_many(([1.0, 2.0, 3.0, 4.0, 5.0],))) - enqueue_op = q_full.enqueue((6.0,)) - enqueue_many_op = q_full.enqueue_many(([6.0],)) - - threads = [ - self.checkedThread( - self._blockingDequeue, args=(sess, dequeue_op)), - self.checkedThread( - self._blockingDequeueMany, args=(sess, dequeue_many_op)), - self.checkedThread( - self._blockingEnqueue, args=(sess, enqueue_op)), - self.checkedThread( - self._blockingEnqueueMany, args=(sess, enqueue_many_op)) - ] - for t in threads: - t.start() - time.sleep(0.1) - sess.close() # Will cancel the blocked operations. - for t in threads: - t.join() - - # Create a new session that hasn't been closed, so cached_session - # isn't messed up. - with self.session() as sess: - pass - - def testBigEnqueueMany(self): + def testParallelDequeueUpTo(self): + # We need each thread to keep its own device stack or the device scopes + # won't be properly nested. + ops.get_default_graph().switch_to_thread_local() with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(5, dtypes_lib.int32, ((),)) - elem = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - enq = q.enqueue_many((elem,)) - deq = q.dequeue() - size_op = q.size() + q = data_flow_ops.FIFOQueue(1000, dtypes_lib.float32, shapes=()) + elems = [10.0 * x for x in range(1000)] + enqueue_op = q.enqueue_many((elems,)) + close_op = q.close() + dequeued_t = q.dequeue_up_to(101) - enq_done = [] + enqueue_op.run() + close_op.run() - def blocking_enqueue(): - enq_done.append(False) - # This will fill the queue and then block until enough dequeues happen. - self.evaluate(enq) - enq_done.append(True) + # Dequeue up to 101 items in parallel on 10 threads, from closed queue. + dequeued_elems = [] - thread = self.checkedThread(target=blocking_enqueue) - thread.start() + def dequeue(): + dequeued_elems.extend(self.evaluate(dequeued_t)) - # The enqueue should start and then block. - results = [] - results.append(deq.eval()) # Will only complete after the enqueue starts. - self.assertEqual(len(enq_done), 1) - self.assertEqual(self.evaluate(size_op), 5) + threads = [self.checkedThread(target=dequeue) for _ in range(10)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + self.assertItemsEqual(elems, dequeued_elems) - for _ in range(3): - results.append(deq.eval()) - - time.sleep(0.1) - self.assertEqual(len(enq_done), 1) - self.assertEqual(self.evaluate(size_op), 5) - - # This dequeue will unblock the thread. - results.append(deq.eval()) - time.sleep(0.1) - self.assertEqual(len(enq_done), 2) - thread.join() - - for i in range(5): - self.assertEqual(size_op.eval(), 5 - i) - results.append(deq.eval()) - self.assertEqual(size_op.eval(), 5 - i - 1) - - self.assertAllEqual(elem, results) - - def testBigDequeueMany(self): + def testParallelEnqueueAndDequeue(self): + # We need each thread to keep its own device stack or the device scopes + # won't be properly nested. + ops.get_default_graph().switch_to_thread_local() with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue(2, dtypes_lib.int32, ((),)) - elem = np.arange(4, dtype=np.int32) - enq_list = [q.enqueue((e,)) for e in elem] - deq = q.dequeue_many(4) + q = data_flow_ops.FIFOQueue(50, dtypes_lib.float32, shapes=()) + initial_elements = [10.0] * 49 + q.enqueue_many((initial_elements,)).run() - results = [] + enqueue_op = q.enqueue((20.0,)) + dequeued_t = q.dequeue() - def blocking_dequeue(): - # Will only complete after 4 enqueues complete. - results.extend(self.evaluate(deq)) + def enqueue(): + for _ in xrange(100): + self.evaluate(enqueue_op) - thread = self.checkedThread(target=blocking_dequeue) - thread.start() - # The dequeue should start and then block. - for enq in enq_list: + def dequeue(): + for _ in xrange(100): + self.assertTrue(self.evaluate(dequeued_t) in (10.0, 20.0)) + + enqueue_threads = [self.checkedThread(target=enqueue) for _ in range(10)] + dequeue_threads = [self.checkedThread(target=dequeue) for _ in range(10)] + for enqueue_thread in enqueue_threads: + enqueue_thread.start() + for dequeue_thread in dequeue_threads: + dequeue_thread.start() + for enqueue_thread in enqueue_threads: + enqueue_thread.join() + for dequeue_thread in dequeue_threads: + dequeue_thread.join() + + # Dequeue the initial count of elements to clean up. + cleanup_elems = q.dequeue_many(49).eval() + for elem in cleanup_elems: + self.assertTrue(elem in (10.0, 20.0)) + + def testMixtureOfEnqueueAndEnqueueMany(self): + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, shapes=()) + enqueue_placeholder = array_ops.placeholder(dtypes_lib.int32, shape=()) + enqueue_op = q.enqueue((enqueue_placeholder,)) + enqueuemany_placeholder = array_ops.placeholder( + dtypes_lib.int32, shape=(None,)) + enqueuemany_op = q.enqueue_many((enqueuemany_placeholder,)) + + dequeued_t = q.dequeue() + close_op = q.close() + + def dequeue(): + for i in xrange(250): + self.assertEqual(i, self.evaluate(dequeued_t)) + + dequeue_thread = self.checkedThread(target=dequeue) + dequeue_thread.start() + + elements_enqueued = 0 + while elements_enqueued < 250: + # With equal probability, run Enqueue or enqueue_many. + if random.random() > 0.5: + enqueue_op.run({enqueue_placeholder: elements_enqueued}) + elements_enqueued += 1 + else: + count = random.randint(0, min(20, 250 - elements_enqueued)) + range_to_enqueue = np.arange( + elements_enqueued, elements_enqueued + count, dtype=np.int32) + enqueuemany_op.run({enqueuemany_placeholder: range_to_enqueue}) + elements_enqueued += count + + close_op.run() + dequeue_thread.join() + self.assertEqual(0, q.size().eval()) + + def testMixtureOfDequeueAndDequeueMany(self): + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue(10, dtypes_lib.int32, shapes=()) + enqueue_op = q.enqueue_many((np.arange(250, dtype=np.int32),)) + dequeued_t = q.dequeue() + count_placeholder = array_ops.placeholder(dtypes_lib.int32, shape=()) + dequeuemany_t = q.dequeue_many(count_placeholder) + + def enqueue(): + self.evaluate(enqueue_op) + + enqueue_thread = self.checkedThread(target=enqueue) + enqueue_thread.start() + + elements_dequeued = 0 + while elements_dequeued < 250: + # With equal probability, run Dequeue or dequeue_many. + if random.random() > 0.5: + self.assertEqual(elements_dequeued, self.evaluate(dequeued_t)) + elements_dequeued += 1 + else: + count = random.randint(0, min(20, 250 - elements_dequeued)) + expected_range = np.arange( + elements_dequeued, elements_dequeued + count, dtype=np.int32) + self.assertAllEqual(expected_range, + dequeuemany_t.eval({ + count_placeholder: count + })) + elements_dequeued += count + + q.close().run() + enqueue_thread.join() + self.assertEqual(0, q.size().eval()) + + def testBlockingDequeueMany(self): + # We need each thread to keep its own device stack or the device scopes + # won't be properly nested. + ops.get_default_graph().switch_to_thread_local() + with self.cached_session() as sess: + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) + elems = [10.0, 20.0, 30.0, 40.0] + enqueue_op = q.enqueue_many((elems,)) + dequeued_t = q.dequeue_many(4) + + dequeued_elems = [] + + def enqueue(): + # The enqueue_op should run after the dequeue op has blocked. # TODO(mrry): Figure out how to do this without sleeping. time.sleep(0.1) - self.assertEqual(len(results), 0) - self.evaluate(enq) + self.evaluate(enqueue_op) - # Enough enqueued to unblock the dequeue - thread.join() - self.assertAllEqual(elem, results) + def dequeue(): + dequeued_elems.extend(self.evaluate(dequeued_t).tolist()) - def testDtypes(self): + enqueue_thread = self.checkedThread(target=enqueue) + dequeue_thread = self.checkedThread(target=dequeue) + enqueue_thread.start() + dequeue_thread.start() + enqueue_thread.join() + dequeue_thread.join() + + self.assertAllEqual(elems, dequeued_elems) + + def testBlockingDequeueUpTo(self): + # We need each thread to keep its own device stack or the device scopes + # won't be properly nested. + ops.get_default_graph().switch_to_thread_local() with self.cached_session() as sess: - dtypes = [ - dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.int32, - dtypes_lib.uint8, dtypes_lib.int16, dtypes_lib.int8, dtypes_lib.int64, - dtypes_lib.uint16, dtypes_lib.bool, dtypes_lib.complex64, - dtypes_lib.complex128 - ] - shape = (32, 4, 128) - q = data_flow_ops.FIFOQueue(32, dtypes, [shape[1:]] * len(dtypes)) + q = data_flow_ops.FIFOQueue(10, dtypes_lib.float32, ()) + elems = [10.0, 20.0, 30.0, 40.0] + enqueue_op = q.enqueue_many((elems,)) + dequeued_t = q.dequeue_up_to(4) - input_tuple = [] - for dtype in dtypes: - np_dtype = dtype.as_numpy_dtype - np_array = np.random.randint(-10, 10, shape) - if dtype == dtypes_lib.bool: - np_array = np_array > 0 - elif dtype in (dtypes_lib.complex64, dtypes_lib.complex128): - np_array = np.sqrt(np_array.astype(np_dtype)) - else: - np_array = np_array.astype(np_dtype) - input_tuple.append(np_array) + dequeued_elems = [] - q.enqueue_many(input_tuple).run() + def enqueue(): + # The enqueue_op should run after the dequeue op has blocked. + # TODO(mrry): Figure out how to do this without sleeping. + time.sleep(0.1) + self.evaluate(enqueue_op) - output_tuple_t = q.dequeue_many(32) - output_tuple = self.evaluate(output_tuple_t) + def dequeue(): + dequeued_elems.extend(self.evaluate(dequeued_t).tolist()) - for (input_elem, output_elem) in zip(input_tuple, output_tuple): - self.assertAllEqual(input_elem, output_elem) + enqueue_thread = self.checkedThread(target=enqueue) + dequeue_thread = self.checkedThread(target=dequeue) + enqueue_thread.start() + dequeue_thread.start() + enqueue_thread.join() + dequeue_thread.join() + + self.assertAllEqual(elems, dequeued_elems) def testDequeueEnqueueFail(self): with self.cached_session() as session: @@ -1486,7 +1608,6 @@ class FIFOQueueTest(test.TestCase): session.run([a, c]) -@test_util.run_v1_only("FIFOQueue removed from v2") class FIFOQueueDictTest(test.TestCase): def testConstructor(self): @@ -1537,117 +1658,10 @@ class FIFOQueueDictTest(test.TestCase): """, q.queue_ref.op.node_def) self.assertEqual(["i", "f"], q.names) - def testEnqueueDequeueOneComponent(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue( - 10, dtypes_lib.float32, shapes=((),), names="f") - # Verify that enqueue() checks that when using names we must enqueue a - # dictionary. - with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): - enqueue_op = q.enqueue(10.0) - with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): - enqueue_op = q.enqueue((10.0,)) - # The dictionary keys must match the queue component names. - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op = q.enqueue({}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op = q.enqueue({"x": 12}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op = q.enqueue({"f": 10.0, "s": "aa"}) - enqueue_op = q.enqueue({"f": 10.0}) - enqueue_op2 = q.enqueue({"f": 20.0}) - enqueue_op3 = q.enqueue({"f": 30.0}) - # Verify that enqueue_many() checks that when using names we must enqueue - # a dictionary. - with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): - enqueue_op4 = q.enqueue_many([40.0, 50.0]) - # The dictionary keys must match the queue component names. - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op4 = q.enqueue_many({}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op4 = q.enqueue_many({"x": 12}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op4 = q.enqueue_many({"f": [40.0, 50.0], "s": ["aa", "bb"]}) - enqueue_op4 = q.enqueue_many({"f": [40.0, 50.0]}) - dequeue = q.dequeue() - dequeue_2 = q.dequeue_many(2) - self.evaluate(enqueue_op) - self.evaluate(enqueue_op2) - self.evaluate(enqueue_op3) - self.evaluate(enqueue_op4) - f = sess.run(dequeue["f"]) - self.assertEqual(10.0, f) - f = sess.run(dequeue_2["f"]) - self.assertEqual([20.0, 30.0], list(f)) - f = sess.run(dequeue_2["f"]) - self.assertEqual([40.0, 50.0], list(f)) - def testEnqueueDequeueMultipleComponent(self): - with self.cached_session() as sess: - q = data_flow_ops.FIFOQueue( - 10, (dtypes_lib.float32, dtypes_lib.int32, dtypes_lib.string), - shapes=((), (), ()), - names=("f", "i", "s")) - # Verify that enqueue() checks that when using names we must enqueue a - # dictionary. - with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): - enqueue_op = q.enqueue((10.0, 123, "aa")) - # The dictionary keys must match the queue component names. - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op = q.enqueue({}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op = q.enqueue({"x": 10.0}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op = q.enqueue({"i": 12, "s": "aa"}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op = q.enqueue({"i": 123, "s": "aa", "f": 10.0, "x": 10.0}) - enqueue_op = q.enqueue({"i": 123, "s": "aa", "f": 10.0}) - enqueue_op2 = q.enqueue({"i": 124, "s": "bb", "f": 20.0}) - enqueue_op3 = q.enqueue({"i": 125, "s": "cc", "f": 30.0}) - # Verify that enqueue_many() checks that when using names we must enqueue - # a dictionary. - with self.assertRaisesRegexp(ValueError, "enqueue a dictionary"): - enqueue_op4 = q.enqueue_many(([40.0, 50.0], [126, 127], ["dd", "ee"])) - # The dictionary keys must match the queue component names. - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op4 = q.enqueue_many({}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op4 = q.enqueue_many({"x": [10.0, 20.0]}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op4 = q.enqueue_many({"i": [12, 12], "s": ["aa", "bb"]}) - with self.assertRaisesRegexp(ValueError, "match names of Queue"): - enqueue_op4 = q.enqueue_many({ - "f": [40.0, 50.0], - "i": [126, 127], - "s": ["dd", "ee"], - "x": [1, 2] - }) - enqueue_op4 = q.enqueue_many({ - "f": [40.0, 50.0], - "i": [126, 127], - "s": ["dd", "ee"] - }) - dequeue = q.dequeue() - dequeue_2 = q.dequeue_many(2) - self.evaluate(enqueue_op) - self.evaluate(enqueue_op2) - self.evaluate(enqueue_op3) - self.evaluate(enqueue_op4) - i, f, s = sess.run([dequeue["i"], dequeue["f"], dequeue["s"]]) - self.assertEqual(123, i) - self.assertEqual(10.0, f) - self.assertEqual(compat.as_bytes("aa"), s) - i, f, s = sess.run([dequeue_2["i"], dequeue_2["f"], dequeue_2["s"]]) - self.assertEqual([124, 125], list(i)) - self.assertTrue([20.0, 30.0], list(f)) - self.assertTrue([compat.as_bytes("bb"), compat.as_bytes("cc")], list(s)) - i, f, s = sess.run([dequeue_2["i"], dequeue_2["f"], dequeue_2["s"]]) - self.assertEqual([126, 127], list(i)) - self.assertTrue([40.0, 50.0], list(f)) - self.assertTrue([compat.as_bytes("dd"), compat.as_bytes("ee")], list(s)) - - -@test_util.run_v1_only("FIFOQueue removed from v2") +@test_util.run_v1_only( + "These tests are heavily reliant on Session for parallelism. We can likely " + "convert them to run in 2.x/eager, but it may be difficult.") class FIFOQueueWithTimeoutTest(test.TestCase): def testDequeueWithTimeout(self): @@ -1682,7 +1696,6 @@ class FIFOQueueWithTimeoutTest(test.TestCase): self.assertEqual(37, self.evaluate(dequeued_t)) -@test_util.run_v1_only("FIFOQueue removed from v2") class QueueContainerTest(test.TestCase): def testContainer(self): @@ -1693,7 +1706,6 @@ class QueueContainerTest(test.TestCase): compat.as_bytes("test"), q.queue_ref.op.get_attr("container")) -@test_util.run_v1_only("FIFOQueue removed from v2") class FIFOQueueBenchmark(test.Benchmark): """Benchmark FIFOQueue operations.""" diff --git a/tensorflow/python/kernel_tests/linalg/BUILD b/tensorflow/python/kernel_tests/linalg/BUILD index b428356cc24..eb732fb5104 100644 --- a/tensorflow/python/kernel_tests/linalg/BUILD +++ b/tensorflow/python/kernel_tests/linalg/BUILD @@ -11,15 +11,15 @@ cuda_py_test( name = "linear_operator_test", size = "small", srcs = ["linear_operator_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -27,15 +27,15 @@ cuda_py_test( name = "linear_operator_addition_test", size = "small", srcs = ["linear_operator_addition_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -43,9 +43,12 @@ cuda_py_test( name = "linear_operator_adjoint_test", size = "medium", srcs = ["linear_operator_adjoint_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + shard_count = 5, + tags = [ + "noasan", # times out, b/63678675 + "optonly", # times out + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -53,11 +56,8 @@ cuda_py_test( "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", - ], - shard_count = 5, - tags = [ - "noasan", # times out, b/63678675 - "optonly", # times out + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -65,15 +65,15 @@ cuda_py_test( name = "linear_operator_algebra_test", size = "small", srcs = ["linear_operator_algebra_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -81,9 +81,12 @@ cuda_py_test( name = "linear_operator_block_diag_test", size = "medium", srcs = ["linear_operator_block_diag_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + shard_count = 6, + tags = [ + "noasan", + "optonly", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -91,11 +94,8 @@ cuda_py_test( "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", - ], - shard_count = 6, - tags = [ - "noasan", - "optonly", + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -103,9 +103,12 @@ cuda_py_test( name = "linear_operator_composition_test", size = "medium", srcs = ["linear_operator_composition_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + shard_count = 5, + tags = [ + "noasan", # times out, b/63678675 + "optonly", # times out + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -113,11 +116,8 @@ cuda_py_test( "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", - ], - shard_count = 5, - tags = [ - "noasan", # times out, b/63678675 - "optonly", # times out + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -125,8 +125,13 @@ cuda_py_test( name = "linear_operator_circulant_test", size = "medium", srcs = ["linear_operator_circulant_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 10, + tags = [ + "no_rocm", # calls BLAS ops for complex types + "noasan", # times out, b/63678675 + "optonly", # times out, b/79171797 + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -136,12 +141,7 @@ cuda_py_test( "//tensorflow/python:platform_test", "//tensorflow/python/ops/linalg", "//tensorflow/python/ops/signal", - ], - shard_count = 10, - tags = [ - "no_rocm", # calls BLAS ops for complex types - "noasan", # times out, b/63678675 - "optonly", # times out, b/79171797 + "//third_party/py/numpy", ], ) @@ -149,8 +149,12 @@ cuda_py_test( name = "linear_operator_diag_test", size = "medium", srcs = ["linear_operator_diag_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", + shard_count = 5, + tags = [ + "noasan", + "optonly", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -159,11 +163,7 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", "//tensorflow/python:random_ops", - ], - shard_count = 5, - tags = [ - "noasan", - "optonly", + "//tensorflow/python/ops/linalg", ], ) @@ -171,8 +171,12 @@ cuda_py_test( name = "linear_operator_householder_test", size = "medium", srcs = ["linear_operator_householder_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", + shard_count = 5, + tags = [ + "noasan", + "optonly", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -181,11 +185,7 @@ cuda_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", "//tensorflow/python:random_ops", - ], - shard_count = 5, - tags = [ - "noasan", - "optonly", + "//tensorflow/python/ops/linalg", ], ) @@ -193,8 +193,12 @@ cuda_py_test( name = "linear_operator_identity_test", size = "medium", srcs = ["linear_operator_identity_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", + shard_count = 5, + tags = [ + "noasan", + "optonly", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -202,11 +206,7 @@ cuda_py_test( "//tensorflow/python:linalg_ops", "//tensorflow/python:platform_test", "//tensorflow/python:random_ops", - ], - shard_count = 5, - tags = [ - "noasan", - "optonly", + "//tensorflow/python/ops/linalg", ], ) @@ -214,9 +214,12 @@ cuda_py_test( name = "linear_operator_inversion_test", size = "medium", srcs = ["linear_operator_inversion_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + shard_count = 5, + tags = [ + "noasan", # times out, b/63678675 + "optonly", # times out + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -224,11 +227,8 @@ cuda_py_test( "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", - ], - shard_count = 5, - tags = [ - "noasan", # times out, b/63678675 - "optonly", # times out + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -236,19 +236,19 @@ cuda_py_test( name = "linear_operator_full_matrix_test", size = "medium", srcs = ["linear_operator_full_matrix_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", + shard_count = 5, + tags = [ + "noasan", + "optonly", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:platform_test", - ], - shard_count = 5, - tags = [ - "noasan", - "optonly", + "//tensorflow/python/ops/linalg", ], ) @@ -256,9 +256,13 @@ cuda_py_test( name = "linear_operator_kronecker_test", size = "medium", srcs = ["linear_operator_kronecker_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + shard_count = 10, + tags = [ + "noasan", + "optonly", + ], + xla_enable_strict_auto_jit = False, # Fails in XLA b/143610154 + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -266,31 +270,27 @@ cuda_py_test( "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], - shard_count = 10, - tags = [ - "noasan", - "optonly", - ], - xla_enable_strict_auto_jit = False, # Fails in XLA b/143610154 ) cuda_py_test( name = "linear_operator_lower_triangular_test", size = "medium", srcs = ["linear_operator_lower_triangular_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", + tags = [ + "noasan", + "optonly", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:platform_test", - ], - tags = [ - "noasan", - "optonly", + "//tensorflow/python/ops/linalg", ], ) @@ -298,19 +298,19 @@ cuda_py_test( name = "linear_operator_low_rank_update_test", size = "medium", srcs = ["linear_operator_low_rank_update_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", + shard_count = 10, + tags = [ + "noasan", # times out + "optonly", + ], + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:platform_test", - ], - shard_count = 10, - tags = [ - "noasan", # times out - "optonly", + "//tensorflow/python/ops/linalg", ], ) @@ -318,6 +318,74 @@ cuda_py_test( name = "linear_operator_permutation_test", size = "medium", srcs = ["linear_operator_permutation_test.py"], + shard_count = 5, + tags = [ + "noasan", + "optonly", + ], + xla_enable_strict_auto_jit = True, + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:linalg_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:random_ops", + "//tensorflow/python/ops/linalg", + ], +) + +cuda_py_test( + name = "linear_operator_util_test", + size = "medium", + srcs = ["linear_operator_util_test.py"], + shard_count = 5, + tags = [ + "noasan", + "optonly", + ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python/ops/linalg", + ], +) + +cuda_py_test( + name = "linear_operator_toeplitz_test", + size = "medium", + srcs = ["linear_operator_toeplitz_test.py"], + shard_count = 5, + tags = [ + "no_cuda_on_cpu_tap", # flaky, b/135701551 + "no_gpu", # flaky, b/135701551 + "noasan", # times out, b/63678675 + "optonly", # times out, b/79171797 + ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python/ops/linalg", + "//tensorflow/python/ops/signal", + "//third_party/py/numpy", + ], +) + +cuda_py_test( + name = "linear_operator_tridiag_test", + size = "medium", + srcs = ["linear_operator_tridiag_test.py"], additional_deps = [ "//tensorflow/python/ops/linalg", "//tensorflow/python:array_ops", @@ -337,57 +405,13 @@ cuda_py_test( xla_enable_strict_auto_jit = True, ) -cuda_py_test( - name = "linear_operator_util_test", - size = "medium", - srcs = ["linear_operator_util_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform_test", - ], - shard_count = 5, - tags = [ - "noasan", - "optonly", - ], -) - -cuda_py_test( - name = "linear_operator_toeplitz_test", - size = "medium", - srcs = ["linear_operator_toeplitz_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:math_ops", - "//tensorflow/python:platform_test", - "//tensorflow/python/ops/linalg", - "//tensorflow/python/ops/signal", - ], - shard_count = 5, - tags = [ - "no_cuda_on_cpu_tap", # flaky, b/135701551 - "no_gpu", # flaky, b/135701551 - "noasan", # times out, b/63678675 - "optonly", # times out, b/79171797 - ], -) - cuda_py_test( name = "linear_operator_zeros_test", size = "medium", srcs = ["linear_operator_zeros_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", + shard_count = 5, + tags = ["optonly"], # Test is flaky without optimization. + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -395,7 +419,6 @@ cuda_py_test( "//tensorflow/python:linalg_ops", "//tensorflow/python:platform_test", "//tensorflow/python:random_ops", + "//tensorflow/python/ops/linalg", ], - shard_count = 5, - tags = ["optonly"], # Test is flaky without optimization. ) diff --git a/tensorflow/python/kernel_tests/linalg/linear_operator_tridiag_test.py b/tensorflow/python/kernel_tests/linalg/linear_operator_tridiag_test.py new file mode 100644 index 00000000000..d69f872f703 --- /dev/null +++ b/tensorflow/python/kernel_tests/linalg/linear_operator_tridiag_test.py @@ -0,0 +1,184 @@ +# Copyright 2019 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. +# ============================================================================== + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.framework import test_util +from tensorflow.python.ops import array_ops +from tensorflow.python.ops import gen_array_ops +from tensorflow.python.ops import manip_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops import variables as variables_module +from tensorflow.python.ops.linalg import linalg as linalg_lib +from tensorflow.python.ops.linalg import linear_operator_test_util +from tensorflow.python.platform import test + + +class _LinearOperatorTriDiagBase(object): + + def build_operator_and_matrix( + self, build_info, dtype, use_placeholder, + ensure_self_adjoint_and_pd=False, + diagonals_format='sequence'): + shape = list(build_info.shape) + + # Ensure that diagonal has large enough values. If we generate a + # self adjoint PD matrix, then the diagonal will be dominant guaranteeing + # positive definitess. + diag = linear_operator_test_util.random_sign_uniform( + shape[:-1], minval=4., maxval=6., dtype=dtype) + # We'll truncate these depending on the format + subdiag = linear_operator_test_util.random_sign_uniform( + shape[:-1], minval=1., maxval=2., dtype=dtype) + if ensure_self_adjoint_and_pd: + # Abs on complex64 will result in a float32, so we cast back up. + diag = math_ops.cast(math_ops.abs(diag), dtype=dtype) + # The first element of subdiag is ignored. We'll add a dummy element + # to superdiag to pad it. + superdiag = math_ops.conj(subdiag) + superdiag = manip_ops.roll(superdiag, shift=-1, axis=-1) + else: + superdiag = linear_operator_test_util.random_sign_uniform( + shape[:-1], minval=1., maxval=2., dtype=dtype) + + matrix_diagonals = array_ops.stack( + [superdiag, diag, subdiag], axis=-2) + matrix = gen_array_ops.matrix_diag_v3( + matrix_diagonals, + k=(-1, 1), + num_rows=-1, + num_cols=-1, + align='LEFT_RIGHT', + padding_value=0.) + + if diagonals_format == 'sequence': + diagonals = [superdiag, diag, subdiag] + elif diagonals_format == 'compact': + diagonals = array_ops.stack([superdiag, diag, subdiag], axis=-2) + elif diagonals_format == 'matrix': + diagonals = matrix + + lin_op_diagonals = diagonals + + if use_placeholder: + if diagonals_format == 'sequence': + lin_op_diagonals = [array_ops.placeholder_with_default( + d, shape=None) for d in lin_op_diagonals] + else: + lin_op_diagonals = array_ops.placeholder_with_default( + lin_op_diagonals, shape=None) + + operator = linalg_lib.LinearOperatorTridiag( + diagonals=lin_op_diagonals, + diagonals_format=diagonals_format, + is_self_adjoint=True if ensure_self_adjoint_and_pd else None, + is_positive_definite=True if ensure_self_adjoint_and_pd else None) + return operator, matrix + + @staticmethod + def operator_shapes_infos(): + shape_info = linear_operator_test_util.OperatorShapesInfo + # non-batch operators (n, n) and batch operators. + return [ + shape_info((3, 3)), + shape_info((1, 6, 6)), + shape_info((3, 4, 4)), + shape_info((2, 1, 3, 3)) + ] + + +@test_util.run_all_in_graph_and_eager_modes +class LinearOperatorTriDiagCompactTest( + _LinearOperatorTriDiagBase, + linear_operator_test_util.SquareLinearOperatorDerivedClassTest): + """Most tests done in the base class LinearOperatorDerivedClassTest.""" + + def operator_and_matrix( + self, build_info, dtype, use_placeholder, + ensure_self_adjoint_and_pd=False): + return self.build_operator_and_matrix( + build_info, dtype, use_placeholder, + ensure_self_adjoint_and_pd=ensure_self_adjoint_and_pd, + diagonals_format='compact') + + def test_tape_safe(self): + diag = variables_module.Variable([[3., 6., 2.], [2., 4., 2.], [5., 1., 2.]]) + operator = linalg_lib.LinearOperatorTridiag( + diag, diagonals_format='compact') + self.check_tape_safe(operator) + + +@test_util.run_all_in_graph_and_eager_modes +class LinearOperatorTriDiagSequenceTest( + _LinearOperatorTriDiagBase, + linear_operator_test_util.SquareLinearOperatorDerivedClassTest): + """Most tests done in the base class LinearOperatorDerivedClassTest.""" + + def operator_and_matrix( + self, build_info, dtype, use_placeholder, + ensure_self_adjoint_and_pd=False): + return self.build_operator_and_matrix( + build_info, dtype, use_placeholder, + ensure_self_adjoint_and_pd=ensure_self_adjoint_and_pd, + diagonals_format='sequence') + + def test_tape_safe(self): + diagonals = [ + variables_module.Variable([3., 6., 2.]), + variables_module.Variable([2., 4., 2.]), + variables_module.Variable([5., 1., 2.])] + operator = linalg_lib.LinearOperatorTridiag( + diagonals, diagonals_format='sequence') + # Skip the diagonal part and trace since this only dependent on the + # middle variable. We test this below. + self.check_tape_safe(operator, skip_options=['diag_part', 'trace']) + + diagonals = [ + [3., 6., 2.], + variables_module.Variable([2., 4., 2.]), + [5., 1., 2.] + ] + operator = linalg_lib.LinearOperatorTridiag( + diagonals, diagonals_format='sequence') + + +@test_util.run_all_in_graph_and_eager_modes +class LinearOperatorTriDiagMatrixTest( + _LinearOperatorTriDiagBase, + linear_operator_test_util.SquareLinearOperatorDerivedClassTest): + """Most tests done in the base class LinearOperatorDerivedClassTest.""" + + def operator_and_matrix( + self, build_info, dtype, use_placeholder, + ensure_self_adjoint_and_pd=False): + return self.build_operator_and_matrix( + build_info, dtype, use_placeholder, + ensure_self_adjoint_and_pd=ensure_self_adjoint_and_pd, + diagonals_format='matrix') + + def test_tape_safe(self): + matrix = variables_module.Variable([[3., 2., 0.], [1., 6., 4.], [0., 2, 2]]) + operator = linalg_lib.LinearOperatorTridiag( + matrix, diagonals_format='matrix') + self.check_tape_safe(operator) + + +if __name__ == '__main__': + linear_operator_test_util.add_tests(LinearOperatorTriDiagCompactTest) + linear_operator_test_util.add_tests(LinearOperatorTriDiagSequenceTest) + linear_operator_test_util.add_tests(LinearOperatorTriDiagMatrixTest) + test.main() diff --git a/tensorflow/python/kernel_tests/linalg/sparse/BUILD b/tensorflow/python/kernel_tests/linalg/sparse/BUILD index 79290a3ca84..e5a8a93fbf7 100644 --- a/tensorflow/python/kernel_tests/linalg/sparse/BUILD +++ b/tensorflow/python/kernel_tests/linalg/sparse/BUILD @@ -11,15 +11,15 @@ cuda_py_test( name = "conjugate_gradient_test", size = "medium", srcs = ["conjugate_gradient_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg", - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/linalg", + "//third_party/py/numpy", ], ) @@ -27,58 +27,58 @@ cuda_py_test( name = "csr_sparse_matrix_test", size = "medium", srcs = ["csr_sparse_matrix_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg/sparse", - ], main = "csr_sparse_matrix_test.py", tags = ["no_rocm"], + deps = [ + "//tensorflow/python/ops/linalg/sparse", + ], ) cuda_py_test( name = "csr_sparse_matrix_ops_test", size = "medium", srcs = ["csr_sparse_matrix_ops_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg/sparse", - "//tensorflow/python/ops/linalg/sparse:gen_sparse_csr_matrix_ops", - ], main = "csr_sparse_matrix_ops_test.py", shard_count = 10, tags = ["no_rocm"], + deps = [ + "//tensorflow/python/ops/linalg/sparse", + "//tensorflow/python/ops/linalg/sparse:gen_sparse_csr_matrix_ops", + ], ) cuda_py_test( name = "csr_sparse_matrix_grad_test", size = "medium", srcs = ["csr_sparse_matrix_grad_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg/sparse", - ], main = "csr_sparse_matrix_grad_test.py", shard_count = 50, tags = ["no_rocm"], + deps = [ + "//tensorflow/python/ops/linalg/sparse", + ], ) cuda_py_test( name = "csr_sparse_matrix_dense_mat_mul_grad_test", size = "medium", srcs = ["csr_sparse_matrix_dense_mat_mul_grad_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg/sparse", - ], main = "csr_sparse_matrix_dense_mat_mul_grad_test.py", shard_count = 50, tags = ["no_rocm"], + deps = [ + "//tensorflow/python/ops/linalg/sparse", + ], ) cuda_py_test( name = "csr_sparse_matrix_sparse_mat_mul_grad_test", size = "medium", srcs = ["csr_sparse_matrix_sparse_mat_mul_grad_test.py"], - additional_deps = [ - "//tensorflow/python/ops/linalg/sparse", - ], main = "csr_sparse_matrix_sparse_mat_mul_grad_test.py", shard_count = 50, tags = ["no_rocm"], + deps = [ + "//tensorflow/python/ops/linalg/sparse", + ], ) diff --git a/tensorflow/python/kernel_tests/proto/BUILD b/tensorflow/python/kernel_tests/proto/BUILD index 389612c9827..d9643f3d125 100644 --- a/tensorflow/python/kernel_tests/proto/BUILD +++ b/tensorflow/python/kernel_tests/proto/BUILD @@ -19,11 +19,6 @@ tf_py_test( name = "decode_proto_op_test", size = "small", srcs = ["decode_proto_op_test.py"], - additional_deps = [ - ":decode_proto_op_test_base", - ":py_test_deps", - "//tensorflow/python:proto_ops", - ], data = if_static( [], otherwise = [":libtestexample.so"], @@ -33,17 +28,17 @@ tf_py_test( "no_pip", # TODO(b/78026780) "no_windows", # TODO(b/78028010) ], + deps = [ + ":decode_proto_op_test_base", + ":py_test_deps", + "//tensorflow/python:proto_ops", + ], ) tf_py_test( name = "encode_proto_op_test", size = "small", srcs = ["encode_proto_op_test.py"], - additional_deps = [ - ":encode_proto_op_test_base", - ":py_test_deps", - "//tensorflow/python:proto_ops", - ], data = if_static( [], otherwise = [":libtestexample.so"], @@ -53,6 +48,11 @@ tf_py_test( "no_pip", # TODO(b/78026780) "no_windows", # TODO(b/78028010) ], + deps = [ + ":encode_proto_op_test_base", + ":py_test_deps", + "//tensorflow/python:proto_ops", + ], ) py_library( @@ -122,13 +122,13 @@ tf_py_test( name = "descriptor_source_test", size = "small", srcs = ["descriptor_source_test.py"], - additional_deps = [ - ":descriptor_source_test_base", - "//tensorflow/python:proto_ops", - "//tensorflow/python:client_testlib", - ], python_version = "PY3", tags = [ "no_pip", ], + deps = [ + ":descriptor_source_test_base", + "//tensorflow/python:client_testlib", + "//tensorflow/python:proto_ops", + ], ) diff --git a/tensorflow/python/kernel_tests/proto/decode_proto_op_test_base.py b/tensorflow/python/kernel_tests/proto/decode_proto_op_test_base.py index 20bc6991d4f..fb46f835e26 100644 --- a/tensorflow/python/kernel_tests/proto/decode_proto_op_test_base.py +++ b/tensorflow/python/kernel_tests/proto/decode_proto_op_test_base.py @@ -330,7 +330,7 @@ class DecodeProtoOpTestBase(test_base.ProtoOpTestBase, parameterized.TestCase): # Test against all 3! permutations of fragments, and for each permutation # test parsing all possible combination of 2 fields. for indices in itertools.permutations(range(len(fragments))): - proto = b''.join([fragments[i] for i in indices]) + proto = b''.join(fragments[i] for i in indices) for i in indices: if i == 1: expected_message_values = [ diff --git a/tensorflow/python/kernel_tests/random/BUILD b/tensorflow/python/kernel_tests/random/BUILD index 9e395370662..6e68f7f12a6 100644 --- a/tensorflow/python/kernel_tests/random/BUILD +++ b/tensorflow/python/kernel_tests/random/BUILD @@ -24,14 +24,14 @@ tf_py_test( name = "random_shuffle_queue_test", size = "small", srcs = ["random_shuffle_queue_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:data_flow_ops", "//tensorflow/python:errors", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", + "//third_party/py/numpy", ], ) @@ -39,8 +39,7 @@ cuda_py_test( name = "multinomial_op_test", size = "small", srcs = ["multinomial_op_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -50,6 +49,7 @@ cuda_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], ) @@ -57,8 +57,8 @@ cuda_py_test( name = "multinomial_op_big_test", size = "medium", srcs = ["multinomial_op_big_test.py"], - additional_deps = [ - "//third_party/py/numpy", + shard_count = 3, + deps = [ "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", "//tensorflow/python:client", @@ -68,18 +68,18 @@ cuda_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], - shard_count = 3, ) cuda_py_test( name = "random_crop_test", size = "small", srcs = ["random_crop_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], ) @@ -87,12 +87,12 @@ cuda_py_test( name = "random_ops_test", size = "medium", srcs = ["random_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], ) @@ -100,14 +100,14 @@ cuda_py_test( name = "stateless_random_ops_test", size = "medium", srcs = ["stateless_random_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:platform", "//tensorflow/python:random_ops", "//tensorflow/python:stateless_random_ops", + "//third_party/py/numpy", ], ) @@ -115,26 +115,25 @@ cuda_py_test( name = "random_gamma_test", size = "medium", srcs = ["random_gamma_test.py"], - additional_deps = [ + shard_count = 4, + tags = ["nozapfhahn"], + deps = [ ":util", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], - shard_count = 4, - tags = ["nozapfhahn"], ) cuda_py_test( name = "random_grad_test", size = "small", srcs = ["random_grad_test.py"], - additional_deps = [ - "//third_party/py/numpy", + deps = [ "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -143,6 +142,7 @@ cuda_py_test( "//tensorflow/python:platform", "//tensorflow/python:random_grad", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], ) @@ -151,32 +151,32 @@ tf_py_test( name = "random_binomial_test", size = "medium", srcs = ["random_binomial_test.py"], - additional_deps = [ + shard_count = 3, + tags = ["no_oss"], + deps = [ ":util", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", "//tensorflow/python:stateful_random_ops", + "//third_party/py/numpy", ], - shard_count = 3, - tags = ["no_oss"], ) cuda_py_test( name = "random_poisson_test", size = "medium", srcs = ["random_poisson_test.py"], - additional_deps = [ + deps = [ ":util", - "//third_party/py/numpy", "//tensorflow/python:array_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", "//tensorflow/python:random_ops", + "//third_party/py/numpy", ], ) diff --git a/tensorflow/python/kernel_tests/signal/BUILD b/tensorflow/python/kernel_tests/signal/BUILD index e8ef9e3bd7b..49076bdec95 100644 --- a/tensorflow/python/kernel_tests/signal/BUILD +++ b/tensorflow/python/kernel_tests/signal/BUILD @@ -24,142 +24,142 @@ py_library( cuda_py_tests( name = "dct_ops_test", srcs = ["dct_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + python_version = "PY3", + tags = ["no_rocm"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python/ops/signal", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", - tags = ["no_rocm"], ) cuda_py_tests( name = "fft_ops_test", size = "medium", srcs = ["fft_ops_test.py"], - additional_deps = [ - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:math_ops", - "//tensorflow/python/ops/signal", - ], python_version = "PY3", shard_count = 8, tags = [ "no_rocm", "optonly", ], + deps = [ + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:math_ops", + "//tensorflow/python/ops/signal", + "//third_party/py/numpy", + ], ) cuda_py_tests( name = "mel_ops_test", srcs = ["mel_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":test_util", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python/ops/signal", + "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_tests( name = "mfcc_ops_test", srcs = ["mfcc_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + python_version = "PY3", + tags = ["no_rocm"], + deps = [ "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", "//tensorflow/python/ops/signal", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", - tags = ["no_rocm"], ) cuda_py_tests( name = "reconstruction_ops_test", srcs = ["reconstruction_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", + python_version = "PY3", + deps = [ "//tensorflow/python:array_ops", - "//tensorflow/python:gradients", - "//tensorflow/python:math_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", - "//tensorflow/python/ops/signal", + "//tensorflow/python:gradients", + "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/signal", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", ], - python_version = "PY3", ) cuda_py_tests( name = "shape_ops_test", srcs = ["shape_ops_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":test_util", - "//third_party/py/numpy", "//tensorflow/python:array_ops", - "//tensorflow/python:math_ops", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:framework_test_lib", - "//tensorflow/python/ops/signal", + "//tensorflow/python:math_ops", "//tensorflow/python:platform_test", + "//tensorflow/python/ops/signal", + "//third_party/py/numpy", ], - python_version = "PY3", ) cuda_py_tests( name = "spectral_ops_test", size = "large", srcs = ["spectral_ops_test.py"], - additional_deps = [ - "@absl_py//absl/testing:parameterized", - "//third_party/py/numpy", - "//tensorflow/python:array_ops", - "//tensorflow/python:gradients", - "//tensorflow/python:math_ops", - "//tensorflow/python:random_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python:platform_test", - "//tensorflow/python/ops/signal", - ], python_version = "PY3", tags = [ "no_rocm", "nomac", ], + deps = [ + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:gradients", + "//tensorflow/python:math_ops", + "//tensorflow/python:platform_test", + "//tensorflow/python:random_ops", + "//tensorflow/python/ops/signal", + "//third_party/py/numpy", + "@absl_py//absl/testing:parameterized", + ], ) cuda_py_tests( name = "window_ops_test", srcs = ["window_ops_test.py"], - additional_deps = [ - ":test_util", - "//third_party/py/numpy", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:framework_test_lib", - "//tensorflow/python/ops/signal", - "//tensorflow/python:platform_test", - ], python_version = "PY3", shard_count = 4, tags = [ "no_windows_gpu", ], + deps = [ + ":test_util", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + "//tensorflow/python/ops/signal", + "//third_party/py/numpy", + ], ) diff --git a/tensorflow/python/kernel_tests/variable_scope_test.py b/tensorflow/python/kernel_tests/variable_scope_test.py index 6e72f61a852..dc534f7cfec 100644 --- a/tensorflow/python/kernel_tests/variable_scope_test.py +++ b/tensorflow/python/kernel_tests/variable_scope_test.py @@ -118,7 +118,7 @@ class VariableScopeTest(test.TestCase): vs.get_variable("v2", [2]) expected_names = ["%s:0" % name for name in ["v1", "v2"]] self.assertEqual( - set(expected_names), set([v.name for v in vs._vars.values()])) + set(expected_names), set(v.name for v in vs._vars.values())) # TODO(mihaimaruseac): Not converted to use wrap_function because of # TypeError: Expected tf.group() expected Tensor arguments not 'None' with diff --git a/tensorflow/python/kernel_tests/while_v2_test.py b/tensorflow/python/kernel_tests/while_v2_test.py index c6e3edeabd7..267362dcba6 100644 --- a/tensorflow/python/kernel_tests/while_v2_test.py +++ b/tensorflow/python/kernel_tests/while_v2_test.py @@ -81,6 +81,19 @@ class WhileV2Test(test.TestCase, parameterized.TestCase): self.assertEqual(self.evaluate(ret), 16.) self.assertSequenceEqual(self.evaluate(grad), [32.]) + @test_util.run_deprecated_v1 + def testSingleLoopVarBackPropFalse(self): + x = constant_op.constant(2.) + ret = while_loop_v2( + lambda v: v < 8., + lambda v: v * v, [x], + return_same_structure=False, + back_prop=False) + grad = gradients_impl.gradients(ret, [x]) + self.assertEqual(grad, [None]) + with self.cached_session(): + self.assertEqual(self.evaluate(ret), 16.) + @test_util.run_deprecated_v1 def testCustomGradient(self): x = constant_op.constant(2.) diff --git a/tensorflow/python/layers/normalization.py b/tensorflow/python/layers/normalization.py index 9bf7aef530d..98f042637d0 100644 --- a/tensorflow/python/layers/normalization.py +++ b/tensorflow/python/layers/normalization.py @@ -20,7 +20,7 @@ from __future__ import division from __future__ import print_function -from tensorflow.python.keras import layers as keras_layers +from tensorflow.python.keras.layers import normalization as keras_normalization from tensorflow.python.layers import base from tensorflow.python.ops import init_ops from tensorflow.python.util import deprecation @@ -28,7 +28,7 @@ from tensorflow.python.util.tf_export import tf_export @tf_export(v1=['layers.BatchNormalization']) -class BatchNormalization(keras_layers.BatchNormalization, base.Layer): +class BatchNormalization(keras_normalization.BatchNormalization, base.Layer): """Batch Normalization layer from (Ioffe et al., 2015). Keras APIs handle BatchNormalization updates to the moving_mean and @@ -175,7 +175,7 @@ class BatchNormalization(keras_layers.BatchNormalization, base.Layer): @deprecation.deprecated( date=None, instructions='Use keras.layers.BatchNormalization instead. In ' 'particular, `tf.control_dependencies(tf.GraphKeys.UPDATE_OPS)` should not ' - 'be used (consult the `tf.keras.layers.batch_normalization` ' + 'be used (consult the `tf.keras.layers.BatchNormalization` ' 'documentation).') @tf_export(v1=['layers.batch_normalization']) def batch_normalization(inputs, diff --git a/tensorflow/python/layers/utils.py b/tensorflow/python/layers/utils.py index 8e4b274207a..af76ac50d61 100644 --- a/tensorflow/python/layers/utils.py +++ b/tensorflow/python/layers/utils.py @@ -231,7 +231,7 @@ def constant_value(pred): def object_list_uid(object_list): """Creates a single string from object ids.""" object_list = nest.flatten(object_list) - return ', '.join([str(abs(id(x))) for x in object_list]) + return ', '.join(str(abs(id(x))) for x in object_list) def static_shape(x): diff --git a/tensorflow/python/lib/core/pybind11_lib.h b/tensorflow/python/lib/core/pybind11_lib.h index 3642a4d73a0..9528efe1a4b 100644 --- a/tensorflow/python/lib/core/pybind11_lib.h +++ b/tensorflow/python/lib/core/pybind11_lib.h @@ -52,7 +52,7 @@ inline py::object pyo_or_throw(PyObject* ptr) { if (PyErr_Occurred() || ptr == nullptr) { throw py::error_already_set(); } - return py::reinterpret_steal(ptr); + return pyo(ptr); } void throwTypeError(const char* error_message) { diff --git a/tensorflow/python/module/BUILD b/tensorflow/python/module/BUILD index 5912d86fc5c..4585d39e592 100644 --- a/tensorflow/python/module/BUILD +++ b/tensorflow/python/module/BUILD @@ -23,11 +23,11 @@ py_library( tf_py_test( name = "module_test", srcs = ["module_test.py"], - additional_deps = [ + deps = [ ":module", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client_testlib", - "//tensorflow/python/compat:v2_compat", "//tensorflow/python:variables", + "//tensorflow/python/compat:v2_compat", + "@absl_py//absl/testing:parameterized", ], ) diff --git a/tensorflow/python/ops/array_grad.py b/tensorflow/python/ops/array_grad.py index 048080230aa..a08e81bbc49 100644 --- a/tensorflow/python/ops/array_grad.py +++ b/tensorflow/python/ops/array_grad.py @@ -18,8 +18,6 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import warnings - from tensorflow.python import pywrap_tensorflow from tensorflow.python.eager import context from tensorflow.python.framework import constant_op @@ -55,8 +53,8 @@ def _ConcatGradHelper(op, grad, start_value_index, end_value_index, dim_index): Args: op: An operation. - grad: `Tensor` or `IndexedSlices` representing the gradients with respect - to each output of the op. + grad: `Tensor` or `IndexedSlices` representing the gradients with respect to + each output of the op. start_value_index: An integer index of the first value in the op.inputs. end_value_index: An integer index of the last value in the op.inputs. dim_index: An interger index of concat_dim or axis parameter in op.inputs. @@ -209,8 +207,8 @@ def _ConcatGradHelper(op, grad, start_value_index, end_value_index, dim_index): else: raise TypeError("Expected Tensor or IndexedSlices, got %s" % type(grad)) - return (out_grads + [None] - if end_value_index <= dim_index else [None] + out_grads) + return (out_grads + [None] if end_value_index <= dim_index else [None] + + out_grads) @ops.RegisterGradient("Concat") @@ -406,9 +404,8 @@ def _MatrixSetDiagGrad(op, grad): matrix_shape = array_ops.slice(grad_shape, [grad_rank - 2], [2]) min_dim = math_ops.reduce_min(matrix_shape) diag_shape = array_ops.concat([batch_shape, [min_dim]], 0) - grad_input = array_ops.matrix_set_diag(grad, - array_ops.zeros( - diag_shape, dtype=grad.dtype)) + grad_input = array_ops.matrix_set_diag( + grad, array_ops.zeros(diag_shape, dtype=grad.dtype)) grad_diag = array_ops.matrix_diag_part(grad) return (grad_input, grad_diag) @@ -511,8 +508,22 @@ ops.NotDifferentiable("OnesLike") @ops.RegisterGradient("PreventGradient") def _PreventGradientGrad(op, _): - raise LookupError( - "Gradient explicitly disabled. Reason: %s" % op.get_attr("message")) + raise LookupError("Gradient explicitly disabled. Reason: %s" % + op.get_attr("message")) + + +def _IndexedSlicesToTensorNoWarning(indexed_slices): + """Converts an IndexedSlices to a Tensor without sparse->dense warnings.""" + if not isinstance(indexed_slices, ops.IndexedSlices): + # If it is not IndexedSlices, it's better be a tensor. + return indexed_slices + if indexed_slices.dense_shape is None: + raise ValueError( + "Tensor conversion requested for IndexedSlices without dense_shape: %s" + % str(indexed_slices)) + return math_ops.unsorted_segment_sum(indexed_slices.values, + indexed_slices.indices, + indexed_slices.dense_shape[0]) @ops.RegisterGradient("Gather") @@ -533,11 +544,8 @@ def _GatherGrad(op, grad): indices = op.inputs[1] size = array_ops.expand_dims(array_ops.size(indices), 0) values_shape = array_ops.concat([size, params_shape[1:]], 0) - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - values = array_ops.reshape(grad, values_shape) + values = array_ops.reshape( + _IndexedSlicesToTensorNoWarning(grad), values_shape) indices = array_ops.reshape(indices, size) return [ops.IndexedSlices(values, indices, params_shape), None] @@ -563,8 +571,8 @@ def _GetBatchIndices(params_shape, indices, batch_dims): return batch_indices -def _BatchGatherGrad( - params_shape, values, indices, batch_dims, gather_dim_size): +def _BatchGatherGrad(params_shape, values, indices, batch_dims, + gather_dim_size): """Returns the gradient of GatherV2 with batch dimensions.""" # Axis is the first non-batch dimension. @@ -579,12 +587,8 @@ def _BatchGatherGrad( gather_dim_size *= batch_size indices = _GetBatchIndices(params_shape, indices, batch_dims) - - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - values = array_ops.reshape(values, flat_values_shape) + values = array_ops.reshape( + _IndexedSlicesToTensorNoWarning(values), flat_values_shape) indices = array_ops.reshape(indices, indices_size) params_grad = math_ops.unsorted_segment_sum(values, indices, gather_dim_size) @@ -627,11 +631,8 @@ def _GatherV2Grad(op, grad): else: params_tail_shape = params_shape[1:] values_shape = array_ops.concat([indices_size, params_tail_shape], 0) - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - values = array_ops.reshape(grad, values_shape) + values = array_ops.reshape( + _IndexedSlicesToTensorNoWarning(grad), values_shape) indices = array_ops.reshape(indices, indices_size) params_grad = ops.IndexedSlices(values, indices, params_shape) else: @@ -648,17 +649,14 @@ def _GatherV2Grad(op, grad): batch_axis_indices = math_ops.range(batch_dims, axis_dims) inner_axes_indices = math_ops.range(axis_dims + 1, values_dims) - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - values = array_ops.reshape(grad, values_shape) + values = array_ops.reshape( + _IndexedSlicesToTensorNoWarning(grad), values_shape) # Move values[axis] up to values[batch_dims] - transpose_dims = array_ops.concat( - [outer_batches_indices, [axis_dims], batch_axis_indices, - inner_axes_indices], - 0) + transpose_dims = array_ops.concat([ + outer_batches_indices, [axis_dims], batch_axis_indices, + inner_axes_indices + ], 0) values_transpose = array_ops.transpose(values, transpose_dims) params_grad = _BatchGatherGrad(params_shape, values_transpose, indices, @@ -666,10 +664,10 @@ def _GatherV2Grad(op, grad): # Inverts the above transpose by moving dimension batch_dims back to its # original position. - invert_transpose_dims = array_ops.concat( - [outer_batches_indices, batch_axis_indices + 1, [batch_dims], - inner_axes_indices], - 0) + invert_transpose_dims = array_ops.concat([ + outer_batches_indices, batch_axis_indices + 1, [batch_dims], + inner_axes_indices + ], 0) params_grad = array_ops.transpose(params_grad, invert_transpose_dims) return [params_grad, None, None] @@ -740,11 +738,11 @@ ops.NotDifferentiable("StopGradient") @ops.RegisterGradient("Reshape") def _ReshapeGrad(op, grad): - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - return [array_ops.reshape(grad, array_ops.shape(op.inputs[0])), None] + return [ + array_ops.reshape( + _IndexedSlicesToTensorNoWarning(grad), array_ops.shape(op.inputs[0])), + None + ] ops.NotDifferentiable("InvertPermutation") @@ -752,11 +750,8 @@ ops.NotDifferentiable("InvertPermutation") def _ReshapeToInput(op, grad): """Reshapes the gradient to the shape of the original input.""" - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - return array_ops.reshape(grad, array_ops.shape(op.inputs[0])) + return array_ops.reshape( + _IndexedSlicesToTensorNoWarning(grad), array_ops.shape(op.inputs[0])) @ops.RegisterGradient("ExpandDims") @@ -814,9 +809,7 @@ def _TileGrad(op, grad): if isinstance(grad, ops.IndexedSlices): input_shape_0 = math_ops.cast(input_shape[0], grad.indices.dtype) grad = math_ops.unsorted_segment_sum( - grad.values, - math_ops.mod(grad.indices, input_shape_0), - input_shape_0) + grad.values, math_ops.mod(grad.indices, input_shape_0), input_shape_0) split_shape = array_ops.concat([[1], split_shape[1:]], axis=0) input_grad = math_ops.reduce_sum(array_ops.reshape(grad, split_shape), axes) # Fix shape inference @@ -973,15 +966,12 @@ def _ExtractImagePatchesGrad(op, grad): # Note that 0 is preserved for padding location, # so indices for input start from 1 to 1 + rows_in * cols_in. input_indices_num = 1 + rows_in * cols_in - input_idx = array_ops.reshape(math_ops.range(1, input_indices_num, - dtype=ops.dtypes.int64), - (1, rows_in, cols_in, 1)) + input_idx = array_ops.reshape( + math_ops.range(1, input_indices_num, dtype=ops.dtypes.int64), + (1, rows_in, cols_in, 1)) input_idx_patched = gen_array_ops.extract_image_patches( - input_idx, - op.get_attr("ksizes"), - op.get_attr("strides"), - op.get_attr("rates"), - op.get_attr("padding")) + input_idx, op.get_attr("ksizes"), op.get_attr("strides"), + op.get_attr("rates"), op.get_attr("padding")) # Create indices matrix for output tensor. output_bhwc = array_ops.shape(op.outputs[0], out_type=dtypes.int64) @@ -989,35 +979,30 @@ def _ExtractImagePatchesGrad(op, grad): _, ksize_r, ksize_c, _ = op.get_attr("ksizes") # Indices for output start from 0. output_indices_num = rows_out * cols_out * ksize_r * ksize_c - output_idx = array_ops.reshape(math_ops.range(output_indices_num, - dtype=ops.dtypes.int64), - (1, rows_out, cols_out, ksize_r * ksize_c)) + output_idx = array_ops.reshape( + math_ops.range(output_indices_num, dtype=ops.dtypes.int64), + (1, rows_out, cols_out, ksize_r * ksize_c)) # Construct mapping table for indices: (input -> output). - idx_matrix = array_ops.concat( - [array_ops.expand_dims(input_idx_patched, axis=-1), - array_ops.expand_dims(output_idx, axis=-1)], - axis=-1) + idx_matrix = array_ops.concat([ + array_ops.expand_dims(input_idx_patched, axis=-1), + array_ops.expand_dims(output_idx, axis=-1) + ], + axis=-1) idx_map = array_ops.reshape(idx_matrix, (-1, 2)) sp_shape = (input_indices_num, output_indices_num) sp_mat_full = sparse_tensor.SparseTensor( - idx_map, - array_ops.ones([output_indices_num], dtype=grad.dtype), - sp_shape) + idx_map, array_ops.ones([output_indices_num], dtype=grad.dtype), sp_shape) # Remove all padding locations [0, :]. - sp_mat = sparse_ops.sparse_slice(sp_mat_full, - (1, 0), + sp_mat = sparse_ops.sparse_slice(sp_mat_full, (1, 0), (input_indices_num - 1, output_indices_num)) - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - grad_expanded = array_ops.transpose( - array_ops.reshape( - grad, (batch_size, rows_out, cols_out, ksize_r, ksize_c, channels)), - (1, 2, 3, 4, 0, 5)) + grad_expanded = array_ops.transpose( + array_ops.reshape( + _IndexedSlicesToTensorNoWarning(grad), + (batch_size, rows_out, cols_out, ksize_r, ksize_c, channels)), + (1, 2, 3, 4, 0, 5)) grad_flat = array_ops.reshape(grad_expanded, (-1, batch_size * channels)) jac = sparse_ops.sparse_tensor_dense_matmul(sp_mat, grad_flat) @@ -1075,14 +1060,11 @@ def _ExtractVolumePatchesGrad(op, grad): sp_mat = sparse_ops.sparse_slice(sp_mat_full, (1, 0), (input_indices_num - 1, output_indices_num)) - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Converting sparse IndexedSlices to a dense Tensor.*") - grad_expanded = array_ops.transpose( - array_ops.reshape(grad, (batch_size, planes_out, rows_out, cols_out, - ksize_p, ksize_r, ksize_c, channels)), - (1, 2, 3, 4, 5, 6, 0, 7)) + grad_expanded = array_ops.transpose( + array_ops.reshape( + _IndexedSlicesToTensorNoWarning(grad), + (batch_size, planes_out, rows_out, cols_out, ksize_p, ksize_r, + ksize_c, channels)), (1, 2, 3, 4, 5, 6, 0, 7)) grad_flat = array_ops.reshape(grad_expanded, (-1, batch_size * channels)) jac = sparse_ops.sparse_tensor_dense_matmul(sp_mat, grad_flat) @@ -1146,10 +1128,9 @@ def _BroadcastToGrad(op, grad): if broadcast_shape_static.is_fully_defined(): broadcast_shape = constant_op.constant( broadcast_shape_static.as_list(), dtype=dtypes.int32) - _, reduction_axes = gen_array_ops.broadcast_gradient_args(broadcast_shape, - input_value_shape) - updates_grad_reshaped = math_ops.reduce_sum(grad, - axis=reduction_axes, - keepdims=True) + _, reduction_axes = gen_array_ops.broadcast_gradient_args( + broadcast_shape, input_value_shape) + updates_grad_reshaped = math_ops.reduce_sum( + grad, axis=reduction_axes, keepdims=True) updates_grad = array_ops.reshape(updates_grad_reshaped, input_value_shape) return [updates_grad, None] diff --git a/tensorflow/python/ops/array_ops.py b/tensorflow/python/ops/array_ops.py index 6bab9dcb196..125c9d68ce0 100644 --- a/tensorflow/python/ops/array_ops.py +++ b/tensorflow/python/ops/array_ops.py @@ -4197,38 +4197,89 @@ def where(condition, x=None, y=None, name=None): @tf_export("where", v1=["where_v2"]) def where_v2(condition, x=None, y=None, name=None): - """Return the elements, either from `x` or `y`, depending on the `condition`. + """Return the elements where `condition` is `True` (multiplexing `x` and `y`). - If both `x` and `y` are None, then this operation returns the coordinates of - true elements of `condition`. The coordinates are returned in a 2-D tensor - where the first dimension (rows) represents the number of true elements, and - the second dimension (columns) represents the coordinates of the true - elements. Keep in mind, the shape of the output tensor can vary depending on - how many true values there are in input. Indices are output in row-major - order. + This operator has two modes: in one mode both `x` and `y` are provided, in + another mode neither are provided. `condition` is always expected to be a + `tf.Tensor` of type `bool`. - If both non-None, `condition`, `x` and `y` must be broadcastable to the same - shape. + #### Retrieving indices of `True` elements - The `condition` tensor acts as a mask that chooses, based on the value at each - element, whether the corresponding element / row in the output should be taken - from `x` (if true) or `y` (if false). + If `x` and `y` are not provided (both are None): + + `tf.where` will return the indices of `condition` that are `True`, in + the form of a 2-D tensor with shape (n, d). + (Where n is the number of matching indices in `condition`, + and d is the number of dimensions in `condition`). + + Indices are output in row-major order. + + >>> tf.where([True, False, False, True]) + + + >>> tf.where([[True, False], [False, True]]) + + + >>> tf.where([[[True, False], [False, True], [True, True]]]) + + + #### Multiplexing between `x` and `y` + + If `x` and `y` are provided (both have non-None values): + + `tf.where` will choose an output shape from the shapes of `condition`, `x`, + and `y` that all three shapes are + [broadcastable](https://docs.scipy.org/doc/numpy/reference/ufuncs.html) to. + + The `condition` tensor acts as a mask that chooses whether the corresponding + element / row in the output should be taken from `x` + (if the elemment in `condition is True) or `y` (if it is false). + + >>> tf.where([True, False, False, True], [1,2,3,4], [100,200,300,400]) + + >>> tf.where([True, False, False, True], [1,2,3,4], [100]) + + >>> tf.where([True, False, False, True], [1,2,3,4], 100) + + >>> tf.where([True, False, False, True], 1, 100) + + + >>> tf.where(True, [1,2,3,4], 100) + + >>> tf.where(False, [1,2,3,4], 100) + Args: - condition: A `Tensor` of type `bool` - x: A Tensor which is of the same type as `y`, and may be broadcastable with - `condition` and `y`. - y: A Tensor which is of the same type as `x`, and may be broadcastable with - `condition` and `x`. + condition: A `tf.Tensor` of type `bool` + x: If provided, a Tensor which is of the same type as `y`, and has a shape + broadcastable with `condition` and `y`. + y: If provided, a Tensor which is of the same type as `y`, and has a shape + broadcastable with `condition` and `x`. name: A name of the operation (optional). Returns: - A `Tensor` with the same type as `x` and `y`, and shape that - is broadcast from `condition`, `x`, and `y`, if `x`, `y` are non-None. + If `x` and `y` are provided: + A `Tensor` with the same type as `x` and `y`, and shape that + is broadcast from `condition`, `x`, and `y`. Otherwise, a `Tensor` with shape `(num_true, dim_size(condition))`. Raises: - ValueError: When exactly one of `x` or `y` is non-None. + ValueError: When exactly one of `x` or `y` is non-None, or the shapes + are not all broadcastable. """ if x is None and y is None: with ops.name_scope(name, "Where", [condition]) as name: diff --git a/tensorflow/python/ops/check_ops.py b/tensorflow/python/ops/check_ops.py index 839da5fb90f..98c84aaf7c2 100644 --- a/tensorflow/python/ops/check_ops.py +++ b/tensorflow/python/ops/check_ops.py @@ -351,7 +351,7 @@ def _binary_assert(sym, opname, op_func, static_func, x, y, data, summarize, raise errors.InvalidArgumentError( node_def=None, op=None, - message=('\n'.join([_pretty_print(d, summarize) for d in data]))) + message=('\n'.join(_pretty_print(d, summarize) for d in data))) else: # not context.executing_eagerly() if data is None: diff --git a/tensorflow/python/ops/control_flow_ops.py b/tensorflow/python/ops/control_flow_ops.py index 5d0ca7c90de..c04c55457b1 100644 --- a/tensorflow/python/ops/control_flow_ops.py +++ b/tensorflow/python/ops/control_flow_ops.py @@ -2300,6 +2300,15 @@ class WhileContext(ControlFlowContext): # @TODO(b/133606651) Replace "shape_invariants" with "loop_vars_signature". # pylint: disable=redefined-outer-name @tf_export("while_loop", v1=[]) +@deprecation.deprecated_arg_values( + None, + """back_prop=False is deprecated. Consider using tf.stop_gradient instead. +Instead of: +results = tf.while_loop(c, b, vars, back_prop=False) +Use: +results = tf.nest.map_structure(tf.stop_gradient, tf.while_loop(c, b, vars))""", + warn_once=True, + back_prop=False) def while_loop_v2(cond, body, loop_vars, @@ -2377,7 +2386,8 @@ def while_loop_v2(cond, shape_invariants: The shape invariants for the loop variables. parallel_iterations: The number of iterations allowed to run in parallel. It must be a positive integer. - back_prop: Whether backprop is enabled for this while loop. + back_prop: (optional) Deprecated. False disables support for back + propagation. Prefer using `tf.stop_gradient` instead. swap_memory: Whether GPU-CPU memory swap is enabled for this loop. maximum_iterations: Optional maximum number of iterations of the while loop to run. If provided, the `cond` output is AND-ed with an additional diff --git a/tensorflow/python/ops/data_flow_ops.py b/tensorflow/python/ops/data_flow_ops.py index 778d4c05bd4..05020b8d64c 100644 --- a/tensorflow/python/ops/data_flow_ops.py +++ b/tensorflow/python/ops/data_flow_ops.py @@ -82,7 +82,7 @@ def _as_shape_list(shapes, if any(not shape.is_fully_defined() for shape in shapes): raise ValueError("All shapes must be fully defined: %s" % shapes) if not unknown_rank_allowed: - if any([shape.dims is None for shape in shapes]): + if any(shape.dims is None for shape in shapes): raise ValueError("All shapes must have a defined rank: %s" % shapes) return shapes @@ -171,7 +171,7 @@ class QueueBase(object): else: self._names = None self._queue_ref = queue_ref - if context.executing_eagerly(): + if isinstance(queue_ref, ops.EagerTensor): if context.context().scope_name: self._name = context.context().scope_name else: @@ -754,12 +754,13 @@ class FIFOQueue(QueueBase): dtypes = _as_type_list(dtypes) shapes = _as_shape_list(shapes, dtypes) names = _as_name_list(names, dtypes) - queue_ref = gen_data_flow_ops.fifo_queue_v2( - component_types=dtypes, - shapes=shapes, - capacity=capacity, - shared_name=_shared_name(shared_name), - name=name) + with ops.init_scope(): + queue_ref = gen_data_flow_ops.fifo_queue_v2( + component_types=dtypes, + shapes=shapes, + capacity=capacity, + shared_name=_shared_name(shared_name), + name=name) super(FIFOQueue, self).__init__(dtypes, shapes, names, queue_ref) diff --git a/tensorflow/python/ops/functional_ops.py b/tensorflow/python/ops/functional_ops.py index 57812d557a2..a4c3caadce4 100644 --- a/tensorflow/python/ops/functional_ops.py +++ b/tensorflow/python/ops/functional_ops.py @@ -36,13 +36,14 @@ from tensorflow.python.ops.gen_functional_ops import remote_call # pylint: enable=unused-import from tensorflow.python.ops.gen_functional_ops import symbolic_gradient from tensorflow.python.util import compat +from tensorflow.python.util import deprecation from tensorflow.python.util import function_utils from tensorflow.python.util import nest from tensorflow.python.util.tf_export import tf_export # TODO(yuanbyu, mrry): Handle stride to support sliding windows. -@tf_export("foldl") +@tf_export(v1=["foldl"]) def foldl(fn, elems, initializer=None, @@ -159,7 +160,83 @@ def foldl(fn, return r_a -@tf_export("foldr") +@tf_export("foldl", v1=[]) +@deprecation.deprecated_arg_values( + None, + """back_prop=False is deprecated. Consider using tf.stop_gradient instead. +Instead of: +results = tf.foldl(fn, elems, back_prop=False) +Use: +results = tf.nest.map_structure(tf.stop_gradient, tf.foldl(fn, elems))""", + warn_once=True, + back_prop=False) +def foldl_v2(fn, + elems, + initializer=None, + parallel_iterations=10, + back_prop=True, + swap_memory=False, + name=None): + """foldl on the list of tensors unpacked from `elems` on dimension 0. + + This foldl operator repeatedly applies the callable `fn` to a sequence + of elements from first to last. The elements are made of the tensors + unpacked from `elems` on dimension 0. The callable fn takes two tensors as + arguments. The first argument is the accumulated value computed from the + preceding invocation of fn, and the second is the value at the current + position of `elems`. If `initializer` is None, `elems` must contain at least + one element, and its first element is used as the initializer. + + Suppose that `elems` is unpacked into `values`, a list of tensors. The shape + of the result tensor is fn(initializer, values[0]).shape`. + + This method also allows multi-arity `elems` and output of `fn`. If `elems` + is a (possibly nested) list or tuple of tensors, then each of these tensors + must have a matching first (unpack) dimension. The signature of `fn` may + match the structure of `elems`. That is, if `elems` is + `(t1, [t2, t3, [t4, t5]])`, then an appropriate signature for `fn` is: + `fn = lambda (t1, [t2, t3, [t4, t5]]):`. + + Args: + fn: The callable to be performed. + elems: A tensor or (possibly nested) sequence of tensors, each of which will + be unpacked along their first dimension. The nested sequence of the + resulting slices will be the first argument to `fn`. + initializer: (optional) A tensor or (possibly nested) sequence of tensors, + as the initial value for the accumulator. + parallel_iterations: (optional) The number of iterations allowed to run in + parallel. + back_prop: (optional) Deprecated. False disables support for back + propagation. Prefer using `tf.stop_gradient` instead. + swap_memory: (optional) True enables GPU-CPU memory swapping. + name: (optional) Name prefix for the returned tensors. + + Returns: + A tensor or (possibly nested) sequence of tensors, resulting from applying + `fn` consecutively to the list of tensors unpacked from `elems`, from first + to last. + + Raises: + TypeError: if `fn` is not callable. + + Example: + ```python + elems = tf.constant([1, 2, 3, 4, 5, 6]) + sum = foldl(lambda a, x: a + x, elems) + # sum == 21 + ``` + """ + return foldl( + fn=fn, + elems=elems, + initializer=initializer, + parallel_iterations=parallel_iterations, + back_prop=back_prop, + swap_memory=swap_memory, + name=name) + + +@tf_export(v1=["foldr"]) def foldr(fn, elems, initializer=None, @@ -277,7 +354,83 @@ def foldr(fn, return r_a -@tf_export("scan") +@tf_export("foldr", v1=[]) +@deprecation.deprecated_arg_values( + None, + """back_prop=False is deprecated. Consider using tf.stop_gradient instead. +Instead of: +results = tf.foldr(fn, elems, back_prop=False) +Use: +results = tf.nest.map_structure(tf.stop_gradient, tf.foldr(fn, elems))""", + warn_once=True, + back_prop=False) +def foldr_v2(fn, + elems, + initializer=None, + parallel_iterations=10, + back_prop=True, + swap_memory=False, + name=None): + """foldr on the list of tensors unpacked from `elems` on dimension 0. + + This foldr operator repeatedly applies the callable `fn` to a sequence + of elements from last to first. The elements are made of the tensors + unpacked from `elems`. The callable fn takes two tensors as arguments. + The first argument is the accumulated value computed from the preceding + invocation of fn, and the second is the value at the current position of + `elems`. If `initializer` is None, `elems` must contain at least one element, + and its first element is used as the initializer. + + Suppose that `elems` is unpacked into `values`, a list of tensors. The shape + of the result tensor is `fn(initializer, values[0]).shape`. + + This method also allows multi-arity `elems` and output of `fn`. If `elems` + is a (possibly nested) list or tuple of tensors, then each of these tensors + must have a matching first (unpack) dimension. The signature of `fn` may + match the structure of `elems`. That is, if `elems` is + `(t1, [t2, t3, [t4, t5]])`, then an appropriate signature for `fn` is: + `fn = lambda (t1, [t2, t3, [t4, t5]]):`. + + Args: + fn: The callable to be performed. + elems: A tensor or (possibly nested) sequence of tensors, each of which will + be unpacked along their first dimension. The nested sequence of the + resulting slices will be the first argument to `fn`. + initializer: (optional) A tensor or (possibly nested) sequence of tensors, + as the initial value for the accumulator. + parallel_iterations: (optional) The number of iterations allowed to run in + parallel. + back_prop: (optional) Deprecated. False disables support for back + propagation. Prefer using `tf.stop_gradient` instead. + swap_memory: (optional) True enables GPU-CPU memory swapping. + name: (optional) Name prefix for the returned tensors. + + Returns: + A tensor or (possibly nested) sequence of tensors, resulting from applying + `fn` consecutively to the list of tensors unpacked from `elems`, from last + to first. + + Raises: + TypeError: if `fn` is not callable. + + Example: + ```python + elems = [1, 2, 3, 4, 5, 6] + sum = foldr(lambda a, x: a + x, elems) + # sum == 21 + ``` + """ + return foldr( + fn=fn, + elems=elems, + initializer=initializer, + parallel_iterations=parallel_iterations, + back_prop=back_prop, + swap_memory=swap_memory, + name=name) + + +@tf_export(v1=["scan"]) def scan(fn, elems, initializer=None, @@ -529,6 +682,129 @@ def scan(fn, return output_pack(results_flat) +@tf_export("scan", v1=[]) +@deprecation.deprecated_arg_values( + None, + """back_prop=False is deprecated. Consider using tf.stop_gradient instead. +Instead of: +results = tf.scan(fn, elems, back_prop=False) +Use: +results = tf.nest.map_structure(tf.stop_gradient, tf.scan(fn, elems))""", + warn_once=True, + back_prop=False) +def scan_v2(fn, + elems, + initializer=None, + parallel_iterations=10, + back_prop=True, + swap_memory=False, + infer_shape=True, + reverse=False, + name=None): + """scan on the list of tensors unpacked from `elems` on dimension 0. + + The simplest version of `scan` repeatedly applies the callable `fn` to a + sequence of elements from first to last. The elements are made of the tensors + unpacked from `elems` on dimension 0. The callable fn takes two tensors as + arguments. The first argument is the accumulated value computed from the + preceding invocation of fn, and the second is the value at the current + position of `elems`. If `initializer` is None, `elems` must contain at least + one element, and its first element is used as the initializer. + + Suppose that `elems` is unpacked into `values`, a list of tensors. The shape + of the result tensor is `[len(values)] + fn(initializer, values[0]).shape`. + If reverse=True, it's fn(initializer, values[-1]).shape. + + This method also allows multi-arity `elems` and accumulator. If `elems` + is a (possibly nested) list or tuple of tensors, then each of these tensors + must have a matching first (unpack) dimension. The second argument of + `fn` must match the structure of `elems`. + + If no `initializer` is provided, the output structure and dtypes of `fn` + are assumed to be the same as its input; and in this case, the first + argument of `fn` must match the structure of `elems`. + + If an `initializer` is provided, then the output of `fn` must have the same + structure as `initializer`; and the first argument of `fn` must match + this structure. + + For example, if `elems` is `(t1, [t2, t3])` and `initializer` is + `[i1, i2]` then an appropriate signature for `fn` in `python2` is: + `fn = lambda (acc_p1, acc_p2), (t1, [t2, t3]):` and `fn` must return a list, + `[acc_n1, acc_n2]`. An alternative correct signature for `fn`, and the + one that works in `python3`, is: + `fn = lambda a, t:`, where `a` and `t` correspond to the input tuples. + + Args: + fn: The callable to be performed. It accepts two arguments. The first will + have the same structure as `initializer` if one is provided, otherwise it + will have the same structure as `elems`. The second will have the same + (possibly nested) structure as `elems`. Its output must have the same + structure as `initializer` if one is provided, otherwise it must have the + same structure as `elems`. + elems: A tensor or (possibly nested) sequence of tensors, each of which will + be unpacked along their first dimension. The nested sequence of the + resulting slices will be the first argument to `fn`. + initializer: (optional) A tensor or (possibly nested) sequence of tensors, + initial value for the accumulator, and the expected output type of `fn`. + parallel_iterations: (optional) The number of iterations allowed to run in + parallel. + back_prop: (optional) Deprecated. False disables support for back + propagation. Prefer using `tf.stop_gradient` instead. + swap_memory: (optional) True enables GPU-CPU memory swapping. + infer_shape: (optional) False disables tests for consistent output shapes. + reverse: (optional) True scans the tensor last to first (instead of first to + last). + name: (optional) Name prefix for the returned tensors. + + Returns: + A tensor or (possibly nested) sequence of tensors. Each tensor packs the + results of applying `fn` to tensors unpacked from `elems` along the first + dimension, and the previous accumulator value(s), from first to last (or + last to first, if `reverse=True`). + + Raises: + TypeError: if `fn` is not callable or the structure of the output of + `fn` and `initializer` do not match. + ValueError: if the lengths of the output of `fn` and `initializer` + do not match. + + Examples: + ```python + elems = np.array([1, 2, 3, 4, 5, 6]) + sum = scan(lambda a, x: a + x, elems) + # sum == [1, 3, 6, 10, 15, 21] + sum = scan(lambda a, x: a + x, elems, reverse=True) + # sum == [21, 20, 18, 15, 11, 6] + ``` + + ```python + elems = np.array([1, 2, 3, 4, 5, 6]) + initializer = np.array(0) + sum_one = scan( + lambda a, x: x[0] - x[1] + a, (elems + 1, elems), initializer) + # sum_one == [1, 2, 3, 4, 5, 6] + ``` + + ```python + elems = np.array([1, 0, 0, 0, 0, 0]) + initializer = (np.array(0), np.array(1)) + fibonaccis = scan(lambda a, _: (a[1], a[0] + a[1]), elems, initializer) + # fibonaccis == ([1, 1, 2, 3, 5, 8], [1, 2, 3, 5, 8, 13]) + ``` + """ + return scan( + fn=fn, + elems=elems, + initializer=initializer, + parallel_iterations=parallel_iterations, + back_prop=back_prop, + swap_memory=swap_memory, + infer_shape=infer_shape, + reverse=reverse, + name=name) + + # pylint: disable=invalid-name def If(cond, inputs, then_branch, else_branch, name=None): r"""output = Cond(inputs) ? diff --git a/tensorflow/python/ops/gradients_util.py b/tensorflow/python/ops/gradients_util.py index 797f106a365..8a7be10ed83 100644 --- a/tensorflow/python/ops/gradients_util.py +++ b/tensorflow/python/ops/gradients_util.py @@ -834,9 +834,9 @@ def _LogOpGradients(op, out_grads, in_grads): return True logging.vlog(1, " in --> %s", - ", ".join([x.name for x in out_grads if _FilterGrad(x)])) + ", ".join(x.name for x in out_grads if _FilterGrad(x))) logging.vlog(1, " out --> %s", - ", ".join([x.name for x in in_grads if _FilterGrad(x)])) + ", ".join(x.name for x in in_grads if _FilterGrad(x))) def _MultiDeviceAddN(tensor_list, gradient_uid): diff --git a/tensorflow/python/ops/image_ops_impl.py b/tensorflow/python/ops/image_ops_impl.py index d33498c517c..80bcd54a650 100644 --- a/tensorflow/python/ops/image_ops_impl.py +++ b/tensorflow/python/ops/image_ops_impl.py @@ -1866,7 +1866,15 @@ def rgb_to_grayscale(images, name=None): Outputs a tensor of the same `DType` and rank as `images`. The size of the last dimension of the output is 1, containing the Grayscale value of the pixels. + + ```python + >>> original = tf.constant([[[1.0, 2.0, 3.0]]]) + >>> converted = tf.image.rgb_to_grayscale(original) + >>> print(converted.numpy()) + [[[1.81...]]] + ``` + Args: images: The RGB tensor to convert. The last dimension must have size 3 and should contain RGB values. @@ -1896,7 +1904,17 @@ def grayscale_to_rgb(images, name=None): Outputs a tensor of the same `DType` and rank as `images`. The size of the last dimension of the output is 3, containing the RGB value of the pixels. The input images' last dimension must be size 1. + + ```python + >>> original = tf.constant([[[1.0], [2.0], [3.0]]]) + >>> converted = tf.image.grayscale_to_rgb(original) + >>> print(converted.numpy()) + [[[1. 1. 1.] + [2. 2. 2.] + [3. 3. 3.]]] + ``` + Args: images: The Grayscale tensor to convert. The last dimension must be size 1. name: A name for the operation (optional). diff --git a/tensorflow/python/ops/linalg/linalg.py b/tensorflow/python/ops/linalg/linalg.py index 2bfbb371b7e..94d85cb7340 100644 --- a/tensorflow/python/ops/linalg/linalg.py +++ b/tensorflow/python/ops/linalg/linalg.py @@ -39,6 +39,7 @@ from tensorflow.python.ops.linalg.linear_operator_low_rank_update import * from tensorflow.python.ops.linalg.linear_operator_lower_triangular import * from tensorflow.python.ops.linalg.linear_operator_permutation import * from tensorflow.python.ops.linalg.linear_operator_toeplitz import * +from tensorflow.python.ops.linalg.linear_operator_tridiag import * from tensorflow.python.ops.linalg.linear_operator_zeros import * # pylint: enable=wildcard-import diff --git a/tensorflow/python/ops/linalg/linalg_impl.py b/tensorflow/python/ops/linalg/linalg_impl.py index 18d22968c94..3412486fb9e 100644 --- a/tensorflow/python/ops/linalg/linalg_impl.py +++ b/tensorflow/python/ops/linalg/linalg_impl.py @@ -28,10 +28,8 @@ from tensorflow.python.framework import tensor_shape from tensorflow.python.ops import array_ops from tensorflow.python.ops import check_ops from tensorflow.python.ops import control_flow_ops -from tensorflow.python.ops import gen_array_ops from tensorflow.python.ops import gen_linalg_ops from tensorflow.python.ops import linalg_ops -from tensorflow.python.ops import manip_ops from tensorflow.python.ops import map_fn from tensorflow.python.ops import math_ops from tensorflow.python.ops import special_math_ops @@ -486,14 +484,8 @@ def tridiagonal_solve(diagonals, 'Expected last two dimensions of diagonals to be same, got {} and {}' .format(m1, m2)) m = m1 or m2 - diagonals = gen_array_ops.matrix_diag_part_v2( - diagonals, k=(-1, 1), padding_value=0.) - # matrix_diag_part pads at the end. Because the subdiagonal has the - # convention of having the padding in the front, we need to rotate the last - # Tensor. - superdiag, d, subdiag = array_ops.unstack(diagonals, num=3, axis=-2) - subdiag = manip_ops.roll(subdiag, shift=1, axis=-1) - diagonals = array_ops.stack((superdiag, d, subdiag), axis=-2) + diagonals = array_ops.matrix_diag_part( + diagonals, k=(-1, 1), padding_value=0., align='LEFT_RIGHT') return _tridiagonal_solve_compact_format( diagonals, rhs, transpose_rhs, conjugate_rhs, partial_pivoting, name) @@ -614,20 +606,11 @@ def tridiagonal_matmul(diagonals, rhs, diagonals_format='compact', name=None): raise ValueError( 'Expected last two dimensions of diagonals to be same, got {} and {}' .format(m1, m2)) - - maindiag = array_ops.matrix_diag_part(diagonals) - superdiag = gen_array_ops.matrix_diag_part_v2( - diagonals, k=1, padding_value=0.) - superdiag = array_ops.concat( - [superdiag, - array_ops.zeros_like( - superdiag[..., 0])[..., array_ops.newaxis]], - axis=-1) - subdiag = gen_array_ops.matrix_diag_part_v2( - diagonals, k=-1, padding_value=0.) - subdiag = array_ops.concat([ - array_ops.zeros_like(subdiag[..., 0])[..., array_ops.newaxis], - subdiag], axis=-1) + diags = array_ops.matrix_diag_part( + diagonals, k=(-1, 1), padding_value=0., align='LEFT_RIGHT') + superdiag = diags[..., 0, :] + maindiag = diags[..., 1, :] + subdiag = diags[..., 2, :] else: raise ValueError('Unrecognized diagonals_format: %s' % diagonals_format) diff --git a/tensorflow/python/ops/linalg/linear_operator_tridiag.py b/tensorflow/python/ops/linalg/linear_operator_tridiag.py new file mode 100644 index 00000000000..422747848c0 --- /dev/null +++ b/tensorflow/python/ops/linalg/linear_operator_tridiag.py @@ -0,0 +1,373 @@ +# Copyright 2019 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. +# ============================================================================== +"""`LinearOperator` acting like a tridiagonal matrix.""" + +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 array_ops +from tensorflow.python.ops import check_ops +from tensorflow.python.ops import control_flow_ops +from tensorflow.python.ops import gen_array_ops +from tensorflow.python.ops import manip_ops +from tensorflow.python.ops import math_ops +from tensorflow.python.ops.linalg import linalg_impl as linalg +from tensorflow.python.ops.linalg import linear_operator +from tensorflow.python.ops.linalg import linear_operator_util +from tensorflow.python.util.tf_export import tf_export + +__all__ = ['LinearOperatorTridiag',] + +_COMPACT = 'compact' +_MATRIX = 'matrix' +_SEQUENCE = 'sequence' +_DIAGONAL_FORMATS = frozenset({_COMPACT, _MATRIX, _SEQUENCE}) + + +@tf_export('linalg.LinearOperatorTridiag') +class LinearOperatorTridiag(linear_operator.LinearOperator): + """`LinearOperator` acting like a [batch] square tridiagonal matrix. + + This operator acts like a [batch] square tridiagonal matrix `A` with shape + `[B1,...,Bb, N, N]` for some `b >= 0`. The first `b` indices index a + batch member. For every batch index `(i1,...,ib)`, `A[i1,...,ib, : :]` is + an `N x M` matrix. This matrix `A` is not materialized, but for + purposes of broadcasting this shape will be relevant. + + Example usage: + + Create a 3 x 3 tridiagonal linear operator. + + >>> superdiag = [3., 4., 5.] + >>> diag = [1., -1., 2.] + >>> subdiag = [6., 7., 8] + >>> operator = tf.linalg.LinearOperatorTridiag( + ... [superdiag, diag, subdiag], + ... diagonals_format='sequence') + >>> operator.to_dense() + + >>> operator.shape + TensorShape([3, 3]) + + Scalar Tensor output. + + >>> operator.log_abs_determinant() + + + Create a [2, 3] batch of 4 x 4 linear operators. + + >>> diagonals = tf.random.normal(shape=[2, 3, 3, 4]) + >>> operator = tf.linalg.LinearOperatorTridiag( + ... diagonals, + ... diagonals_format='compact') + + Create a shape [2, 1, 4, 2] vector. Note that this shape is compatible + since the batch dimensions, [2, 1], are broadcast to + operator.batch_shape = [2, 3]. + + >>> y = tf.random.normal(shape=[2, 1, 4, 2]) + >>> x = operator.solve(y) + >>> x + + + #### Shape compatibility + + This operator acts on [batch] matrix with compatible shape. + `x` is a batch matrix with compatible shape for `matmul` and `solve` if + + ``` + operator.shape = [B1,...,Bb] + [N, N], with b >= 0 + x.shape = [C1,...,Cc] + [N, R], + and [C1,...,Cc] broadcasts with [B1,...,Bb]. + ``` + + #### Performance + + Suppose `operator` is a `LinearOperatorTridiag` of shape `[N, N]`, + and `x.shape = [N, R]`. Then + + * `operator.matmul(x)` will take O(N * R) time. + * `operator.solve(x)` will take O(N * R) time. + + If instead `operator` and `x` have shape `[B1,...,Bb, N, N]` and + `[B1,...,Bb, N, R]`, every operation increases in complexity by `B1*...*Bb`. + + #### Matrix property hints + + This `LinearOperator` is initialized with boolean flags of the form `is_X`, + for `X = non_singular, self_adjoint, positive_definite, square`. + These have the following meaning: + + * If `is_X == True`, callers should expect the operator to have the + property `X`. This is a promise that should be fulfilled, but is *not* a + runtime assert. For example, finite floating point precision may result + in these promises being violated. + * If `is_X == False`, callers should expect the operator to not have `X`. + * If `is_X == None` (the default), callers should have no expectation either + way. + """ + + def __init__(self, + diagonals, + diagonals_format=_COMPACT, + is_non_singular=None, + is_self_adjoint=None, + is_positive_definite=None, + is_square=None, + name='LinearOperatorTridiag'): + r"""Initialize a `LinearOperatorTridiag`. + + Args: + diagonals: `Tensor` or list of `Tensor`s depending on `diagonals_format`. + + If `diagonals_format=sequence`, this is a list of three `Tensor`'s each + with shape `[B1, ..., Bb, N]`, `b >= 0, N >= 0`, representing the + superdiagonal, diagonal and subdiagonal in that order. Note the + superdiagonal is padded with an element in the last position, and the + subdiagonal is padded with an element in the front. + + If `diagonals_format=matrix` this is a `[B1, ... Bb, N, N]` shaped + `Tensor` representing the full tridiagonal matrix. + + If `diagonals_format=compact` this is a `[B1, ... Bb, 3, N]` shaped + `Tensor` with the second to last dimension indexing the + superdiagonal, diagonal and subdiagonal in that order. Note the + superdiagonal is padded with an element in the last position, and the + subdiagonal is padded with an element in the front. + + In every case, these `Tensor`s are all floating dtype. + diagonals_format: one of `matrix`, `sequence`, or `compact`. Default is + `compact`. + is_non_singular: Expect that this operator is non-singular. + is_self_adjoint: Expect that this operator is equal to its hermitian + transpose. If `diag.dtype` is real, this is auto-set to `True`. + is_positive_definite: Expect that this operator is positive definite, + meaning the quadratic form `x^H A x` has positive real part for all + nonzero `x`. Note that we do not require the operator to be + self-adjoint to be positive-definite. See: + https://en.wikipedia.org/wiki/Positive-definite_matrix#Extension_for_non-symmetric_matrices + is_square: Expect that this operator acts like square [batch] matrices. + name: A name for this `LinearOperator`. + + Raises: + TypeError: If `diag.dtype` is not an allowed type. + ValueError: If `diag.dtype` is real, and `is_self_adjoint` is not `True`. + """ + + with ops.name_scope(name, values=[diagonals]): + if diagonals_format not in _DIAGONAL_FORMATS: + raise ValueError( + 'Diagonals Format must be one of compact, matrix, sequence' + ', got : {}'.format(diagonals_format)) + if diagonals_format == _SEQUENCE: + self._diagonals = [linear_operator_util.convert_nonref_to_tensor( + d, name='diag_{}'.format(i)) for i, d in enumerate(diagonals)] + dtype = self._diagonals[0].dtype + else: + self._diagonals = linear_operator_util.convert_nonref_to_tensor( + diagonals, name='diagonals') + dtype = self._diagonals.dtype + self._diagonals_format = diagonals_format + + super(LinearOperatorTridiag, self).__init__( + dtype=dtype, + is_non_singular=is_non_singular, + is_self_adjoint=is_self_adjoint, + is_positive_definite=is_positive_definite, + is_square=is_square, + name=name) + + def _shape(self): + if self.diagonals_format == _MATRIX: + return self.diagonals.shape + if self.diagonals_format == _COMPACT: + # Remove the second to last dimension that contains the value 3. + d_shape = self.diagonals.shape[:-2].concatenate( + self.diagonals.shape[-1]) + else: + broadcast_shape = array_ops.broadcast_static_shape( + self.diagonals[0].shape[:-1], + self.diagonals[1].shape[:-1]) + broadcast_shape = array_ops.broadcast_static_shape( + broadcast_shape, + self.diagonals[2].shape[:-1]) + d_shape = broadcast_shape.concatenate(self.diagonals[1].shape[-1]) + return d_shape.concatenate(d_shape[-1]) + + def _shape_tensor(self, diagonals=None): + diagonals = diagonals if diagonals is not None else self.diagonals + if self.diagonals_format == _MATRIX: + return array_ops.shape(diagonals) + if self.diagonals_format == _COMPACT: + d_shape = array_ops.shape(diagonals[..., 0, :]) + else: + broadcast_shape = array_ops.broadcast_dynamic_shape( + array_ops.shape(self.diagonals[0])[:-1], + array_ops.shape(self.diagonals[1])[:-1]) + broadcast_shape = array_ops.broadcast_dynamic_shape( + broadcast_shape, + array_ops.shape(self.diagonals[2])[:-1]) + d_shape = array_ops.concat( + [broadcast_shape, [array_ops.shape(self.diagonals[1])[-1]]], axis=0) + return array_ops.concat([d_shape, [d_shape[-1]]], axis=-1) + + def _assert_self_adjoint(self): + # Check the diagonal has non-zero imaginary, and the super and subdiagonals + # are conjugate. + + asserts = [] + diag_message = ( + 'This tridiagonal operator contained non-zero ' + 'imaginary values on the diagonal.') + off_diag_message = ( + 'This tridiagonal operator has non-conjugate ' + 'subdiagonal and superdiagonal.') + + if self.diagonals_format == _MATRIX: + asserts += [check_ops.assert_equal( + self.diagonals, linalg.adjoint(self.diagonals), + message='Matrix was not equal to its adjoint.')] + elif self.diagonals_format == _COMPACT: + diagonals = ops.convert_to_tensor(self.diagonals) + asserts += [linear_operator_util.assert_zero_imag_part( + diagonals[..., 1, :], message=diag_message)] + # Roll the subdiagonal so the shifted argument is at the end. + subdiag = manip_ops.roll(diagonals[..., 2, :], shift=-1, axis=-1) + asserts += [check_ops.assert_equal( + math_ops.conj(subdiag[..., :-1]), + diagonals[..., 0, :-1], + message=off_diag_message)] + else: + asserts += [linear_operator_util.assert_zero_imag_part( + self.diagonals[1], message=diag_message)] + subdiag = manip_ops.roll(self.diagonals[2], shift=-1, axis=-1) + asserts += [check_ops.assert_equal( + math_ops.conj(subdiag[..., :-1]), + self.diagonals[0][..., :-1], + message=off_diag_message)] + return control_flow_ops.group(asserts) + + def _construct_adjoint_diagonals(self, diagonals): + # Constructs adjoint tridiagonal matrix from diagonals. + if self.diagonals_format == _SEQUENCE: + diagonals = [math_ops.conj(d) for d in reversed(diagonals)] + # The subdiag and the superdiag swap places, so we need to shift the + # padding argument. + diagonals[0] = manip_ops.roll(diagonals[0], shift=-1, axis=-1) + diagonals[2] = manip_ops.roll(diagonals[2], shift=1, axis=-1) + return diagonals + elif self.diagonals_format == _MATRIX: + return linalg.adjoint(diagonals) + else: + diagonals = math_ops.conj(diagonals) + superdiag, diag, subdiag = array_ops.unstack( + diagonals, num=3, axis=-2) + # The subdiag and the superdiag swap places, so we need + # to shift all arguments. + new_superdiag = manip_ops.roll(subdiag, shift=-1, axis=-1) + new_subdiag = manip_ops.roll(superdiag, shift=1, axis=-1) + return array_ops.stack([new_superdiag, diag, new_subdiag], axis=-2) + + def _matmul(self, x, adjoint=False, adjoint_arg=False): + diagonals = self.diagonals + if adjoint: + diagonals = self._construct_adjoint_diagonals(diagonals) + x = linalg.adjoint(x) if adjoint_arg else x + return linalg.tridiagonal_matmul( + diagonals, x, + diagonals_format=self.diagonals_format) + + def _solve(self, rhs, adjoint=False, adjoint_arg=False): + diagonals = self.diagonals + if adjoint: + diagonals = self._construct_adjoint_diagonals(diagonals) + + # TODO(b/144860784): Remove the broadcasting code below once + # tridiagonal_solve broadcasts. + + rhs_shape = array_ops.shape(rhs) + k = self._shape_tensor(diagonals)[-1] + broadcast_shape = array_ops.broadcast_dynamic_shape( + self._shape_tensor(diagonals)[:-2], rhs_shape[:-2]) + rhs = array_ops.broadcast_to( + rhs, array_ops.concat( + [broadcast_shape, rhs_shape[-2:]], axis=-1)) + if self.diagonals_format == _MATRIX: + diagonals = array_ops.broadcast_to( + diagonals, array_ops.concat( + [broadcast_shape, [k, k]], axis=-1)) + elif self.diagonals_format == _COMPACT: + diagonals = array_ops.broadcast_to( + diagonals, array_ops.concat( + [broadcast_shape, [3, k]], axis=-1)) + else: + diagonals = [ + array_ops.broadcast_to(d, array_ops.concat( + [broadcast_shape, [k]], axis=-1)) for d in diagonals] + + y = linalg.tridiagonal_solve( + diagonals, rhs, + diagonals_format=self.diagonals_format, + transpose_rhs=adjoint_arg, + conjugate_rhs=adjoint_arg) + return y + + def _diag_part(self): + if self.diagonals_format == _MATRIX: + return array_ops.matrix_diag_part(self.diagonals) + elif self.diagonals_format == _SEQUENCE: + diagonal = self.diagonals[1] + return array_ops.broadcast_to( + diagonal, self.shape_tensor()[:-1]) + else: + return self.diagonals[..., 1, :] + + def _to_dense(self): + if self.diagonals_format == _MATRIX: + return self.diagonals + + if self.diagonals_format == _COMPACT: + return gen_array_ops.matrix_diag_v3( + self.diagonals, + k=(-1, 1), + num_rows=-1, + num_cols=-1, + align='LEFT_RIGHT', + padding_value=0.) + + diagonals = [ops.convert_to_tensor(d) for d in self.diagonals] + diagonals = array_ops.stack(diagonals, axis=-2) + + return gen_array_ops.matrix_diag_v3( + diagonals, + k=(-1, 1), + num_rows=-1, + num_cols=-1, + align='LEFT_RIGHT', + padding_value=0.) + + @property + def diagonals(self): + return self._diagonals + + @property + def diagonals_format(self): + return self._diagonals_format diff --git a/tensorflow/python/ops/linalg_grad.py b/tensorflow/python/ops/linalg_grad.py index 1ca96e85339..3e6d22accec 100644 --- a/tensorflow/python/ops/linalg_grad.py +++ b/tensorflow/python/ops/linalg_grad.py @@ -275,7 +275,7 @@ def _EinsumGrad(op, grad): set(output_subs + other_subs + ".")) # Obtain the input subscripts with the reduced axis labels removed. E.g. # "ac" in the above example. - left_subs = "".join([s for s in input_subs if s not in reduced_label_set]) + left_subs = "".join(s for s in input_subs if s not in reduced_label_set) # Compute the gradient wrt the input, without accounting for the operation # "abc->ac". So, now we have the VJP of the operation "ac,cd->ad". diff --git a/tensorflow/python/ops/losses/util.py b/tensorflow/python/ops/losses/util.py index 45f754dc957..279fe0aba13 100644 --- a/tensorflow/python/ops/losses/util.py +++ b/tensorflow/python/ops/losses/util.py @@ -27,7 +27,6 @@ from tensorflow.python.ops import check_ops from tensorflow.python.ops import confusion_matrix from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import math_ops -from tensorflow.python.ops import weights_broadcast_ops from tensorflow.python.util import tf_contextlib from tensorflow.python.util.tf_export import tf_export @@ -143,9 +142,6 @@ def scale_losses_by_sample_weight(losses, sample_weight): # Update dimensions of `sample_weight` to match with `losses` if possible. losses, _, sample_weight = squeeze_or_expand_dimensions( losses, None, sample_weight) - - # Broadcast weights if possible. - sample_weight = weights_broadcast_ops.broadcast_weights(sample_weight, losses) return math_ops.multiply(losses, sample_weight) diff --git a/tensorflow/python/ops/map_fn.py b/tensorflow/python/ops/map_fn.py index 8bee9f2f0f6..457ab309435 100644 --- a/tensorflow/python/ops/map_fn.py +++ b/tensorflow/python/ops/map_fn.py @@ -30,11 +30,12 @@ from tensorflow.python.ops import control_flow_ops from tensorflow.python.ops import tensor_array_ops from tensorflow.python.ops import variable_scope as vs from tensorflow.python.platform import tf_logging as logging +from tensorflow.python.util import deprecation from tensorflow.python.util import nest from tensorflow.python.util.tf_export import tf_export -@tf_export("map_fn") +@tf_export(v1=["map_fn"]) def map_fn(fn, elems, dtype=None, parallel_iterations=None, back_prop=True, swap_memory=False, infer_shape=True, name=None): """map on the list of tensors unpacked from `elems` on dimension 0. @@ -284,3 +285,141 @@ def map_fn(fn, elems, dtype=None, parallel_iterations=None, back_prop=True, varscope.set_caching_device(None) return output_pack(results_flat) + + +@tf_export("map_fn", v1=[]) +@deprecation.deprecated_arg_values( + None, + """back_prop=False is deprecated. Consider using tf.stop_gradient instead. +Instead of: +results = tf.map_fn(fn, elems, back_prop=False) +Use: +results = tf.nest.map_structure(tf.stop_gradient, tf.map_fn(fn, elems))""", + warn_once=True, + back_prop=False) +def map_fn_v2(fn, + elems, + dtype=None, + parallel_iterations=None, + back_prop=True, + swap_memory=False, + infer_shape=True, + name=None): + """map on the list of tensors unpacked from `elems` on dimension 0. + + The simplest version of `map_fn` repeatedly applies the callable `fn` to a + sequence of elements from first to last. The elements are made of the + tensors unpacked from `elems`. `dtype` is the data type of the return + value of `fn`. Users must provide `dtype` if it is different from + the data type of `elems`. + + Suppose that `elems` is unpacked into `values`, a list of tensors. The shape + of the result tensor is `[values.shape[0]] + fn(values[0]).shape`. + + This method also allows multi-arity `elems` and output of `fn`. If `elems` + is a (possibly nested) list or tuple of tensors, then each of these tensors + must have a matching first (unpack) dimension. The signature of `fn` may + match the structure of `elems`. That is, if `elems` is + `(t1, [t2, t3, [t4, t5]])`, then an appropriate signature for `fn` is: + `fn = lambda (t1, [t2, t3, [t4, t5]]):`. + + Furthermore, `fn` may emit a different structure than its input. For example, + `fn` may look like: `fn = lambda t1: return (t1 + 1, t1 - 1)`. In this case, + the `dtype` parameter is not optional: `dtype` must be a type or (possibly + nested) tuple of types matching the output of `fn`. + + To apply a functional operation to the nonzero elements of a SparseTensor + one of the following methods is recommended. First, if the function is + expressible as TensorFlow ops, use + + ```python + result = SparseTensor(input.indices, fn(input.values), input.dense_shape) + ``` + + If, however, the function is not expressible as a TensorFlow op, then use + + ```python + result = SparseTensor( + input.indices, map_fn(fn, input.values), input.dense_shape) + ``` + + instead. + + When executing eagerly, map_fn does not execute in parallel even if + `parallel_iterations` is set to a value > 1. You can still get the + performance benefits of running a function in parallel by using the + `tf.function` decorator, + + ```python + # Assume the function being used in map_fn is fn. + # To ensure map_fn calls fn in parallel, use the tf.function decorator. + @tf.function + def func(tensor): + return tf.map_fn(fn, tensor) + ``` + + Note that if you use the `tf.function` decorator, any non-TensorFlow Python + code that you may have written in your function won't get executed. See + [`tf.function`](https://www.tensorflow.org/api_docs/python/tf/function) for + more details. The recommendation would be to debug without `tf.function` but + switch to it to get performance benefits of running `map_fn` in parallel. + + Args: + fn: The callable to be performed. It accepts one argument, which will have + the same (possibly nested) structure as `elems`. Its output must have the + same structure as `dtype` if one is provided, otherwise it must have the + same structure as `elems`. + elems: A tensor or (possibly nested) sequence of tensors, each of which will + be unpacked along their first dimension. The nested sequence of the + resulting slices will be applied to `fn`. + dtype: (optional) The output type(s) of `fn`. If `fn` returns a structure + of Tensors differing from the structure of `elems`, then `dtype` is not + optional and must have the same structure as the output of `fn`. + parallel_iterations: (optional) The number of iterations allowed to run in + parallel. When graph building, the default value is 10. While executing + eagerly, the default value is set to 1. + back_prop: (optional) Deprecated. False disables support for back + propagation. Prefer using `tf.stop_gradient` instead. + swap_memory: (optional) True enables GPU-CPU memory swapping. + infer_shape: (optional) False disables tests for consistent output shapes. + name: (optional) Name prefix for the returned tensors. + + Returns: + A tensor or (possibly nested) sequence of tensors. Each tensor packs the + results of applying `fn` to tensors unpacked from `elems` along the first + dimension, from first to last. + + Raises: + TypeError: if `fn` is not callable or the structure of the output of + `fn` and `dtype` do not match, or if elems is a SparseTensor. + ValueError: if the lengths of the output of `fn` and `dtype` do not match. + + Examples: + ```python + elems = np.array([1, 2, 3, 4, 5, 6]) + squares = map_fn(lambda x: x * x, elems) + # squares == [1, 4, 9, 16, 25, 36] + ``` + + ```python + elems = (np.array([1, 2, 3]), np.array([-1, 1, -1])) + alternate = map_fn(lambda x: x[0] * x[1], elems, dtype=tf.int64) + # alternate == [-1, 2, -3] + ``` + + ```python + elems = np.array([1, 2, 3]) + alternates = map_fn(lambda x: (x, -x), elems, dtype=(tf.int64, tf.int64)) + # alternates[0] == [1, 2, 3] + # alternates[1] == [-1, -2, -3] + ``` + """ + return map_fn( + fn=fn, + elems=elems, + dtype=dtype, + parallel_iterations=parallel_iterations, + back_prop=back_prop, + swap_memory=swap_memory, + infer_shape=infer_shape, + name=name) diff --git a/tensorflow/python/ops/math_grad.py b/tensorflow/python/ops/math_grad.py index f9a75f6aecc..e6b565b75d0 100644 --- a/tensorflow/python/ops/math_grad.py +++ b/tensorflow/python/ops/math_grad.py @@ -597,7 +597,7 @@ def _SqrtGradGrad(op, grad): a = op.inputs[0] y = op.outputs[0] # y = 0.5 * b / conj(a) with ops.control_dependencies([grad]): - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): ga = gen_math_ops.xdivy(grad, a) return -gen_math_ops.mul_no_nan(y, math_ops.conj(ga)), 0.5 * ga else: @@ -631,7 +631,7 @@ def _ExpGrad(op, grad): y = op.outputs[0] # y = e^x with ops.control_dependencies([grad]): y = math_ops.conj(y) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.mul_no_nan(y, grad) else: return grad * y @@ -644,7 +644,7 @@ def _Expm1Grad(op, grad): with ops.control_dependencies([grad]): x = math_ops.conj(x) y = math_ops.exp(x) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.mul_no_nan(y, grad) else: return grad * y @@ -656,7 +656,7 @@ def _LogGrad(op, grad): x = op.inputs[0] with ops.control_dependencies([grad]): x = math_ops.conj(x) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return gen_math_ops.xdivy(grad, x) else: return grad * math_ops.reciprocal(x) @@ -668,7 +668,7 @@ def _Log1pGrad(op, grad): x = op.inputs[0] with ops.control_dependencies([grad]): x = math_ops.conj(x) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return gen_math_ops.xdivy(grad, 1 + x) else: return grad * math_ops.reciprocal(1 + x) @@ -750,7 +750,7 @@ def _AcoshGrad(op, grad): y = op.outputs[0] with ops.control_dependencies([grad]): y = math_ops.conj(y) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.xdivy(grad, math_ops.sinh(y)) else: return grad / math_ops.sinh(y) @@ -821,7 +821,7 @@ def _LgammaGrad(op, grad): x = op.inputs[0] with ops.control_dependencies([grad]): x = math_ops.conj(x) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.mul_no_nan(math_ops.digamma(x), grad) else: return grad * math_ops.digamma(x) @@ -834,7 +834,7 @@ def _DigammaGrad(op, grad): with ops.control_dependencies([grad]): x = math_ops.conj(x) partial_x = math_ops.polygamma(array_ops.constant(1, dtype=x.dtype), x) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.mul_no_nan(partial_x, grad) else: return grad * partial_x @@ -847,7 +847,7 @@ def _BesselI0eGrad(op, grad): y = op.outputs[0] with ops.control_dependencies([grad]): partial_x = (math_ops.bessel_i1e(x) - math_ops.sign(x) * y) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.mul_no_nan(partial_x, grad) else: return grad * partial_x @@ -871,7 +871,7 @@ def _BesselI1eGrad(op, grad): dy_dx = math_ops.bessel_i0e(safe_x) - y * ( math_ops.sign(safe_x) + math_ops.reciprocal(safe_x)) dy_dx = array_ops.where_v2(x_is_not_tiny, dy_dx, 0.5 + zeros) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.mul_no_nan(dy_dx, grad) else: return grad * dy_dx @@ -892,7 +892,7 @@ def _IgammaGrad(op, grad): # and Gamma'(a) can grow large. partial_x = math_ops.exp(-x + (a - 1) * math_ops.log(x) - math_ops.lgamma(a)) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return (array_ops.reshape( math_ops.reduce_sum(math_ops.mul_no_nan(partial_a, grad), ra), sa), array_ops.reshape( @@ -931,7 +931,7 @@ def _BetaincGrad(op, grad): (a - 1) * math_ops.log(x) - log_beta) # TODO(b/36815900): Mark None return values as NotImplemented - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return ( None, # da None, # db @@ -960,7 +960,7 @@ def _ZetaGrad(op, grad): q = math_ops.conj(q) partial_q = -x * math_ops.zeta(x + 1, q) # TODO(b/36815900): Mark None return values as NotImplemented - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return (None, array_ops.reshape( math_ops.reduce_sum(math_ops.mul_no_nan(partial_q, grad), rq), @@ -986,7 +986,7 @@ def _PolygammaGrad(op, grad): x = math_ops.conj(x) partial_x = math_ops.polygamma(n + 1, x) # TODO(b/36815900): Mark None return values as NotImplemented - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return (None, array_ops.reshape( math_ops.reduce_sum(math_ops.mul_no_nan(partial_x, grad), rx), @@ -1047,7 +1047,7 @@ def _TanGrad(op, grad): x = math_ops.conj(x) secx = math_ops.reciprocal(math_ops.cos(x)) secx2 = math_ops.square(secx) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.mul_no_nan(secx2, grad) else: return secx2 * grad @@ -1062,7 +1062,7 @@ def _AsinGrad(op, grad): x2 = math_ops.square(x) one = constant_op.constant(1, dtype=grad.dtype) den = math_ops.sqrt(math_ops.subtract(one, x2)) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return math_ops.xdivy(grad, den) else: inv = math_ops.reciprocal(den) @@ -1078,7 +1078,7 @@ def _AcosGrad(op, grad): x2 = math_ops.square(x) one = constant_op.constant(1, dtype=grad.dtype) den = math_ops.sqrt(math_ops.subtract(one, x2)) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return -math_ops.xdivy(grad, den) else: inv = math_ops.reciprocal(den) @@ -1103,7 +1103,7 @@ def _Atan2Grad(op, grad): y = op.inputs[0] x = op.inputs[1] with ops.control_dependencies([grad]): - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): grad_inv = math_ops.xdivy(grad, (math_ops.square(x) + math_ops.square(y))) else: grad_inv = grad / (math_ops.square(x) + math_ops.square(y)) @@ -1265,7 +1265,7 @@ def _DivGrad(op, grad): rx, ry = gen_array_ops.broadcast_gradient_args(sx, sy) x = math_ops.conj(x) y = math_ops.conj(y) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return (array_ops.reshape( math_ops.reduce_sum(math_ops.xdivy(grad, y), rx), sx), array_ops.reshape( @@ -1318,7 +1318,7 @@ def _RealDivGrad(op, grad): rx, ry = gen_array_ops.broadcast_gradient_args(sx, sy) x = math_ops.conj(x) y = math_ops.conj(y) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return (array_ops.reshape( math_ops.reduce_sum(math_ops.xdivy(grad, y), rx), sx), array_ops.reshape( @@ -1345,7 +1345,7 @@ def _DivNoNanGrad(op, grad): rx, ry = gen_array_ops.broadcast_gradient_args(sx, sy) x = math_ops.conj(x) y = math_ops.conj(y) - if compat.forward_compatible(2019, 12, 14): + if compat.forward_compatible(2020, 3, 14): return (array_ops.reshape( math_ops.reduce_sum(math_ops.div_no_nan(grad, y), rx), sx), array_ops.reshape( @@ -1367,7 +1367,7 @@ def _PowGrad(op, grad): """Returns grad * (y*x^(y-1), z*log(x)).""" x = op.inputs[0] y = op.inputs[1] - use_mul_no_nan = compat.forward_compatible(2019, 12, 14) + use_mul_no_nan = compat.forward_compatible(2020, 3, 14) skip_input_indices = None try: skip_input_indices = op.skip_input_indices diff --git a/tensorflow/python/ops/math_ops.py b/tensorflow/python/ops/math_ops.py index 7cee4258793..d7772cb387c 100644 --- a/tensorflow/python/ops/math_ops.py +++ b/tensorflow/python/ops/math_ops.py @@ -4374,8 +4374,8 @@ def sqrt(x, name=None): # pylint: disable=redefined-builtin >>> z = tf.constant([[-1.0], [16.0]], dtype=tf.complex128) >>> tf.sqrt(z) + array([[0.0+1.j], + [4.0+0.j]])> Note: In order to support complex complex, please provide an input tensor of `complex64` or `complex128`. diff --git a/tensorflow/python/ops/nn_loss_scaling_utilities_test.py b/tensorflow/python/ops/nn_loss_scaling_utilities_test.py index cf2a7d2b289..427972f5ce1 100644 --- a/tensorflow/python/ops/nn_loss_scaling_utilities_test.py +++ b/tensorflow/python/ops/nn_loss_scaling_utilities_test.py @@ -26,6 +26,7 @@ from tensorflow.python.eager import context from tensorflow.python.framework import constant_op from tensorflow.python.framework import dtypes from tensorflow.python.framework import errors +from tensorflow.python.framework import errors_impl from tensorflow.python.ops import array_ops from tensorflow.python.ops import nn_impl from tensorflow.python.platform import test as test_lib @@ -96,8 +97,9 @@ class LossUtilitiesTest(test_lib.TestCase, parameterized.TestCase): self.evaluate(loss), (2. * 0.3 + 0.5 * 0.7 + 4. * 0.2 + 1. * 0.8) / 2) def testComputeAverageLossInvalidSampleWeights(self): - with self.assertRaisesRegex(ValueError, - "weights can not be broadcast to values"): + with self.assertRaisesRegexp((ValueError, errors_impl.InvalidArgumentError), + (r"Incompatible shapes: \[3\] vs. \[2\]|" + "Dimensions must be equal")): nn_impl.compute_average_loss([2.5, 6.2, 5.], sample_weight=[0.2, 0.8], global_batch_size=10) diff --git a/tensorflow/python/ops/op_selector.py b/tensorflow/python/ops/op_selector.py index 1ae43aa5bda..1f6d4e01cec 100644 --- a/tensorflow/python/ops/op_selector.py +++ b/tensorflow/python/ops/op_selector.py @@ -339,7 +339,7 @@ def _path_from(from_op, tensor, sources): if isinstance(from_op, ops.Tensor): from_op = from_op.op - visited_ops = set([x.op for x in sources]) + visited_ops = set(x.op for x in sources) ops_to_visit = [_as_operation(tensor)] some_op_output = {} while ops_to_visit: @@ -354,7 +354,7 @@ def _path_from(from_op, tensor, sources): while path_op != final_op: path_op = some_op_output[path_op] path.append(path_op) - return " <- ".join(["%s (%s)" % (x.name, x.type) for x in reversed(path)]) + return " <- ".join("%s (%s)" % (x.name, x.type) for x in reversed(path)) else: for inp in graph_inputs(op): if inp not in visited_ops and inp not in sources: diff --git a/tensorflow/python/ops/parallel_for/BUILD b/tensorflow/python/ops/parallel_for/BUILD index dff4b922036..d0c5f4f5593 100644 --- a/tensorflow/python/ops/parallel_for/BUILD +++ b/tensorflow/python/ops/parallel_for/BUILD @@ -107,7 +107,8 @@ py_library( cuda_py_test( name = "control_flow_ops_test", srcs = ["control_flow_ops_test.py"], - additional_deps = [ + tags = ["no_rocm"], + deps = [ ":control_flow_ops", ":test_util", "//tensorflow/core:protos_all_py", @@ -120,21 +121,11 @@ cuda_py_test( "//tensorflow/python:tensor_array_grad", "//tensorflow/python:util", ], - tags = ["no_rocm"], ) cuda_py_test( name = "xla_control_flow_ops_test", srcs = ["xla_control_flow_ops_test.py"], - additional_deps = [ - ":control_flow_ops", - ":test_util", - "//tensorflow/python:random_ops", - "//tensorflow/compiler/tf2xla/python:xla", - "//tensorflow/python/compiler/xla:xla", - "//tensorflow/python:array_ops", - "//tensorflow/python:math_ops", - ], tags = [ "no_rocm", # XLA is not enabled by default on Mac or Windows. @@ -142,16 +133,25 @@ cuda_py_test( "no_windows", ], xla_enabled = True, + deps = [ + ":control_flow_ops", + ":test_util", + "//tensorflow/compiler/tf2xla/python:xla", + "//tensorflow/python:array_ops", + "//tensorflow/python:math_ops", + "//tensorflow/python:random_ops", + "//tensorflow/python/compiler/xla", + ], ) cuda_py_test( name = "array_test", srcs = ["array_test.py"], - additional_deps = [ + deps = [ ":control_flow_ops", ":test_util", - "//tensorflow/python:client_testlib", "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", "//tensorflow/python:random_ops", "//tensorflow/python:util", "//tensorflow/python/eager:backprop", @@ -161,7 +161,9 @@ cuda_py_test( cuda_py_test( name = "math_test", srcs = ["math_test.py"], - additional_deps = [ + shard_count = 5, + tags = ["optonly"], # Too slow in non-opt mode + deps = [ ":control_flow_ops", ":test_util", "//tensorflow/python:client_testlib", @@ -169,8 +171,6 @@ cuda_py_test( "//tensorflow/python:random_ops", "//tensorflow/python:util", ], - shard_count = 5, - tags = ["optonly"], # Too slow in non-opt mode ) py_library( @@ -188,16 +188,16 @@ py_library( cuda_py_test( name = "gradients_test", srcs = ["gradients_test.py"], - additional_deps = [ + tags = ["optonly"], # Too slow in non-opt mode + deps = [ ":control_flow_ops", ":gradients", - "//third_party/py/numpy", - "//tensorflow/python:layers", "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", "//tensorflow/python:functional_ops", + "//tensorflow/python:layers", "//tensorflow/python:random_ops", "//tensorflow/python/ops/losses", + "//third_party/py/numpy", ], - tags = ["optonly"], # Too slow in non-opt mode ) diff --git a/tensorflow/python/ops/parallel_for/control_flow_ops.py b/tensorflow/python/ops/parallel_for/control_flow_ops.py index a1c7588ea8f..179e6640456 100644 --- a/tensorflow/python/ops/parallel_for/control_flow_ops.py +++ b/tensorflow/python/ops/parallel_for/control_flow_ops.py @@ -74,7 +74,7 @@ def for_loop(loop_fn, loop_fn_dtypes, iters, parallel_iterations=None): len(fn_output))) outputs = [] del is_none_list[:] - is_none_list.extend([x is None for x in fn_output]) + is_none_list.extend(x is None for x in fn_output) for out, ta in zip(fn_output, ta_list): # TODO(agarwal): support returning Operation objects from loop_fn. if out is not None: diff --git a/tensorflow/python/ops/parallel_for/control_flow_ops_test.py b/tensorflow/python/ops/parallel_for/control_flow_ops_test.py index d016393a39e..7f3930cb64a 100644 --- a/tensorflow/python/ops/parallel_for/control_flow_ops_test.py +++ b/tensorflow/python/ops/parallel_for/control_flow_ops_test.py @@ -315,7 +315,7 @@ class BitwiseTest(PForTestCase): y1 = array_ops.gather(y, i) outputs = [op(x, y), op(x1, y), op(x, y1), op(x1, y1), op(x1, x1)] del output_dtypes[:] - output_dtypes.extend([t.dtype for t in outputs]) + output_dtypes.extend(t.dtype for t in outputs) return outputs # pylint: enable=cell-var-from-loop self._test_loop_fn(loop_fn, 3) diff --git a/tensorflow/python/ops/parallel_for/gradients.py b/tensorflow/python/ops/parallel_for/gradients.py index 20a4fa06e37..3ab60a327cb 100644 --- a/tensorflow/python/ops/parallel_for/gradients.py +++ b/tensorflow/python/ops/parallel_for/gradients.py @@ -95,8 +95,11 @@ def batch_jacobian(output, inp, use_pfor=True, parallel_iterations=None): inp: A tensor with shape [b, x1, ..., x_m] use_pfor: If true, uses pfor for computing the Jacobian. Else uses a tf.while_loop. - parallel_iterations: A knob to control how many iterations and dispatched in - parallel. This knob can be used to control the total memory usage. + parallel_iterations: A knob to control how many iterations are vectorized + and dispatched in parallel. The default value of None, when use_pfor is + true, corresponds to vectorizing all the iterations. When use_pfor is + false, the default value of None corresponds to parallel_iterations=10. + This knob can be used to control the total memory usage. Returns: A tensor `t` with shape [b, y_1, ..., y_n, x1, ..., x_m] where `t[i, ...]` diff --git a/tensorflow/python/ops/parallel_for/math_test.py b/tensorflow/python/ops/parallel_for/math_test.py index cb701d2102a..dac633ffd37 100644 --- a/tensorflow/python/ops/parallel_for/math_test.py +++ b/tensorflow/python/ops/parallel_for/math_test.py @@ -65,7 +65,7 @@ class MathTest(PForTestCase, parameterized.TestCase): if grad is not None: outputs.append(grad) del output_dtypes[:] - output_dtypes.extend([t.dtype for t in outputs]) + output_dtypes.extend(t.dtype for t in outputs) return outputs # pylint: enable=cell-var-from-loop @@ -215,7 +215,7 @@ class MathTest(PForTestCase, parameterized.TestCase): y1 = array_ops.gather(y, i) outputs = [op(x, y), op(x1, y), op(x, y1), op(x1, y1), op(x1, x1)] del output_dtypes[:] - output_dtypes.extend([t.dtype for t in outputs]) + output_dtypes.extend(t.dtype for t in outputs) return outputs # pylint: enable=cell-var-from-loop diff --git a/tensorflow/python/ops/parallel_for/pfor.py b/tensorflow/python/ops/parallel_for/pfor.py index 0e861ffe2ab..c1965a8a0fd 100644 --- a/tensorflow/python/ops/parallel_for/pfor.py +++ b/tensorflow/python/ops/parallel_for/pfor.py @@ -121,7 +121,7 @@ class WhileOp(object): """ self._pfor_config = pfor_config self._pfor_ops = set(pfor_ops) - self._pfor_op_ids = set([x._id for x in pfor_ops]) + self._pfor_op_ids = set(x._id for x in pfor_ops) assert isinstance(exit_node, ops.Tensor) self._while_context = exit_node.op._get_control_flow_context() assert isinstance(self._while_context, control_flow_ops.WhileContext) @@ -1176,7 +1176,7 @@ class PFor(object): self._conversion_map = object_identity.ObjectIdentityDictionary() self._conversion_map[loop_var] = wrap(self.all_indices, True) self._pfor_ops = set(pfor_ops) - self._pfor_op_ids = set([x._id for x in pfor_ops]) + self._pfor_op_ids = set(x._id for x in pfor_ops) self._pfor_config = pfor_config def op_is_inside_loop(self, op): diff --git a/tensorflow/python/ops/ragged/BUILD b/tensorflow/python/ops/ragged/BUILD index 083953ee837..d61f120d1e3 100644 --- a/tensorflow/python/ops/ragged/BUILD +++ b/tensorflow/python/ops/ragged/BUILD @@ -6,7 +6,7 @@ package( "//intelligence/datum/prensor:__pkg__", "//learning/brain/contrib/text:__pkg__", "//nlp/nlx/bert:__pkg__", - "//nlp/nlx/i18n/saft:__subpackages__", + "//nlp/nlx/i18n/pangloss:__subpackages__", "//nlp/nlx/infrastructure/multiscale:__subpackages__", "//nlp/projects/atc/tf/ops:__pkg__", "//research/socrates:__subpackages__", diff --git a/tensorflow/python/ops/ragged/ragged_math_ops.py b/tensorflow/python/ops/ragged/ragged_math_ops.py index 2cf27f57045..01b872b36df 100644 --- a/tensorflow/python/ops/ragged/ragged_math_ops.py +++ b/tensorflow/python/ops/ragged/ragged_math_ops.py @@ -404,7 +404,7 @@ _RAGGED_REDUCE_MEAN_EXAMPLE = """ >>> tf.reduce_mean(rt, axis=0).numpy() array([3.75, 4. , 4. ]) >>> tf.reduce_mean(rt, axis=1).numpy() - array([2.6666..., 3. , 9. , 4. ]) + array([2.66666667, 3. , 9. , 4. ]) """ _RAGGED_REDUCE_ALL_EXAMPLE = """ >>> rt = tf.ragged.constant([[True, True], [True, True, False, True], [False, True]]) diff --git a/tensorflow/python/ops/ragged/ragged_tensor_shape.py b/tensorflow/python/ops/ragged/ragged_tensor_shape.py index 111e2c8962f..eacc397b2f7 100644 --- a/tensorflow/python/ops/ragged/ragged_tensor_shape.py +++ b/tensorflow/python/ops/ragged/ragged_tensor_shape.py @@ -126,8 +126,8 @@ class RaggedTensorDynamicShape(object): # Convert dimension size tensors to a single dtype. if dim_size_dtype is None: - dim_size_dtypes = set([p.dtype for p in partitioned_dim_sizes - if p.shape.ndims == 1]) + dim_size_dtypes = set( + p.dtype for p in partitioned_dim_sizes if p.shape.ndims == 1) if not dim_size_dtypes: dim_size_dtype = dtypes.int64 elif len(dim_size_dtypes) == 1: diff --git a/tensorflow/python/ops/random_ops.py b/tensorflow/python/ops/random_ops.py index 75c5fd5c5d2..4fadc8d7561 100644 --- a/tensorflow/python/ops/random_ops.py +++ b/tensorflow/python/ops/random_ops.py @@ -20,7 +20,6 @@ from __future__ import print_function import numpy as np -from tensorflow.python.compat import compat from tensorflow.python.eager import context from tensorflow.python.framework import dtypes from tensorflow.python.framework import ops @@ -281,38 +280,25 @@ def random_uniform(shape, maxval = 1 with ops.name_scope(name, "random_uniform", [shape, minval, maxval]) as name: shape = tensor_util.shape_tensor(shape) - # TODO(b/143079601): Remove this once the compatible window is passed. - if compat.forward_compatible(2019, 12, 3): - # In case of [0,1) floating results, minval and maxval is unused. We do an - # `is` comparison here since this is cheaper than isinstance or __eq__. - minval_is_zero = minval is 0 # pylint: disable=literal-comparison - maxval_is_one = maxval is 1 # pylint: disable=literal-comparison - if not minval_is_zero or not maxval_is_one or dtype.is_integer: - minval = ops.convert_to_tensor(minval, dtype=dtype, name="min") - maxval = ops.convert_to_tensor(maxval, dtype=dtype, name="max") - seed1, seed2 = random_seed.get_seed(seed) - if dtype.is_integer: - result = gen_random_ops.random_uniform_int( - shape, minval, maxval, seed=seed1, seed2=seed2, name=name) - else: - result = gen_random_ops.random_uniform( - shape, dtype, seed=seed1, seed2=seed2) - if minval_is_zero: - if not maxval_is_one: - result = result * maxval - else: - result = math_ops.add(result * (maxval - minval), minval, name=name) - else: + # In case of [0,1) floating results, minval and maxval is unused. We do an + # `is` comparison here since this is cheaper than isinstance or __eq__. + minval_is_zero = minval is 0 # pylint: disable=literal-comparison + maxval_is_one = maxval is 1 # pylint: disable=literal-comparison + if not minval_is_zero or not maxval_is_one or dtype.is_integer: minval = ops.convert_to_tensor(minval, dtype=dtype, name="min") maxval = ops.convert_to_tensor(maxval, dtype=dtype, name="max") - seed1, seed2 = random_seed.get_seed(seed) - if dtype.is_integer: - result = gen_random_ops.random_uniform_int( - shape, minval, maxval, seed=seed1, seed2=seed2, name=name) + seed1, seed2 = random_seed.get_seed(seed) + if dtype.is_integer: + result = gen_random_ops.random_uniform_int( + shape, minval, maxval, seed=seed1, seed2=seed2, name=name) + else: + result = gen_random_ops.random_uniform( + shape, dtype, seed=seed1, seed2=seed2) + if minval_is_zero: + if not maxval_is_one: + result = math_ops.multiply(result, maxval) else: - rnd = gen_random_ops.random_uniform( - shape, dtype, seed=seed1, seed2=seed2) - result = math_ops.add(rnd * (maxval - minval), minval, name=name) + result = math_ops.add(result * (maxval - minval), minval, name=name) # TODO(b/132092188): C++ shape inference inside functional ops does not # cross FuncGraph boundaries since that information is only available in # python. So we manually get the static shape using diff --git a/tensorflow/python/ops/resource_variable_ops.py b/tensorflow/python/ops/resource_variable_ops.py index ba9d974e52c..2bb9f9deee7 100644 --- a/tensorflow/python/ops/resource_variable_ops.py +++ b/tensorflow/python/ops/resource_variable_ops.py @@ -497,6 +497,9 @@ class BaseResourceVariable(variables.VariableV1): """The shape of this variable.""" return self._shape + def set_shape(self, shape): + self._shape = self._shape.merge_with(shape) + def _shape_as_list(self): if self.shape.ndims is None: return None @@ -718,10 +721,6 @@ class BaseResourceVariable(variables.VariableV1): return ResourceVariable( variable_def=variable_def, import_scope=import_scope) - def set_shape(self, shape): - """Unsupported.""" - raise NotImplementedError("ResourceVariable does not implement set_shape()") - __array_priority__ = 100 def is_initialized(self, name=None): diff --git a/tensorflow/python/ops/rnn_cell_impl.py b/tensorflow/python/ops/rnn_cell_impl.py index 77a9670daf3..719b06a41d5 100644 --- a/tensorflow/python/ops/rnn_cell_impl.py +++ b/tensorflow/python/ops/rnn_cell_impl.py @@ -1237,7 +1237,7 @@ class MultiRNNCell(RNNCell): if not nest.is_sequence(cells): raise TypeError("cells must be a list or tuple, but saw: %s." % cells) - if len(set([id(cell) for cell in cells])) < len(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) diff --git a/tensorflow/python/ops/special_math_ops.py b/tensorflow/python/ops/special_math_ops.py index f0a1cd8120c..257860195ff 100644 --- a/tensorflow/python/ops/special_math_ops.py +++ b/tensorflow/python/ops/special_math_ops.py @@ -360,8 +360,8 @@ def _einsum_v1_parse_and_resolve_equation(equation, input_shapes): # tensors of different length and unlabeled output. ellipsis_axes = '' if '...' in equation: - unused = ''.join([c for c in string.ascii_letters - if c not in ''.join(input_axis_labels)]) + unused = ''.join( + c for c in string.ascii_letters if c not in ''.join(input_axis_labels)) for i, ax in enumerate(input_axis_labels): if '...' in ax: parts = ax.split('...') @@ -381,7 +381,7 @@ def _einsum_v1_parse_and_resolve_equation(equation, input_shapes): if len(replace_axes) > len(ellipsis_axes): ellipsis_axes = replace_axes - if any(['.' in ax for ax in input_axis_labels]): + if any('.' in ax for ax in input_axis_labels): raise ValueError('period "." found outside of ellipsis') if output_axis_labels is not None: diff --git a/tensorflow/python/ops/stateful_random_ops.py b/tensorflow/python/ops/stateful_random_ops.py index d0f132d91b0..df60e013eb9 100644 --- a/tensorflow/python/ops/stateful_random_ops.py +++ b/tensorflow/python/ops/stateful_random_ops.py @@ -18,10 +18,12 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function -import sys +import enum # pylint: disable=g-bad-import-order import numpy as np +import six +from tensorflow.python.distribute import distribution_strategy_context as ds_context from tensorflow.python.eager import context from tensorflow.python.framework import composite_tensor from tensorflow.python.framework import dtypes @@ -57,15 +59,21 @@ SEED_SIZE = 16 # in units of SEED_TYPE STATE_TYPE = SEED_TYPE ALGORITHM_TYPE = STATE_TYPE -RNG_ALG_PHILOX = 1 -RNG_ALG_THREEFRY = 2 -DEFAULT_ALGORITHM = RNG_ALG_PHILOX - - PHILOX_STATE_SIZE = 3 THREEFRY_STATE_SIZE = 2 +@tf_export("random.experimental.Algorithm") +class Algorithm(enum.Enum): + PHILOX = 1 + THREEFRY = 2 + + +RNG_ALG_PHILOX = Algorithm.PHILOX.value +RNG_ALG_THREEFRY = Algorithm.THREEFRY.value +DEFAULT_ALGORITHM = RNG_ALG_PHILOX + + def non_deterministic_ints(shape, dtype=dtypes.int64): """Non-deterministically generates some integers. @@ -99,8 +107,7 @@ def _make_1d_state(state_size, seed): Returns: a 1-D tensor of shape [state_size] and dtype STATE_TYPE. """ - int_types = (int,) if sys.version_info >= (3, 0) else (int, long) - if isinstance(seed, int_types): + if isinstance(seed, six.integer_types): # chop the Python integer (infinite precision) into chunks of SEED_TYPE ls = [] for _ in range(state_size): @@ -148,18 +155,56 @@ def _make_state_from_seed(seed, alg): return _make_1d_state(_get_state_size(alg), seed) -@tf_export("random.experimental.create_rng_state") -def create_rng_state(seed, algorithm): - """Creates a RNG state. +def _convert_alg_to_int(alg): + """Converts algorithm to an integer. Args: - seed: an integer or 1-D tensor. - algorithm: an integer representing the RNG algorithm. + alg: can be one of these types: integer, Algorithm, Tensor, string. Allowed + strings are "philox" and "threefry". Returns: - a 1-D tensor whose size depends on the algorithm. + An integer, unless the input is a Tensor in which case a Tensor is returned. """ - return _make_state_from_seed(seed, algorithm) + if isinstance(alg, six.integer_types): + return alg + if isinstance(alg, Algorithm): + return alg.value + if isinstance(alg, ops.Tensor): + return alg + if isinstance(alg, str): + if alg == "philox": + return RNG_ALG_PHILOX + elif alg == "threefry": + return RNG_ALG_THREEFRY + else: + raise ValueError("Unknown algorithm name: %s" % alg) + else: + raise TypeError("Can't convert algorithm %s of type %s to int" % + (alg, type(alg))) + + +@tf_export("random.experimental.create_rng_state") +def create_rng_state(seed, alg): + """Creates a RNG state from an integer or a vector. + + Example: + + >>> tf.random.experimental.create_rng_state( + ... 1234, "philox") + array([1234, 0, 0]) + >>> tf.random.experimental.create_rng_state( + ... [12, 34], "threefry") + array([12, 34]) + + Args: + seed: an integer or 1-D numpy array. + alg: the RNG algorithm. Can be a string, an `Algorithm` or an integer. + + Returns: + a 1-D numpy array whose size depends on the algorithm. + """ + alg = _convert_alg_to_int(alg) + return _make_state_from_seed(seed, alg) def _shape_tensor(shape): @@ -212,16 +257,81 @@ class GeneratorSpec(type_spec.TypeSpec): return (self.shape, self.dtype) +def _create_variable(*args, **kwargs): + """Creates a variable, and check that it's not MirroredVariable. + + Args: + *args: positional arguments passed along to `variables.Variable. + **kwargs: keyword arguments passed along to `variables.Variable. + + Returns: + The created variable. + """ + if ds_context.has_strategy(): + raise ValueError( + "Creating a generator within a strategy scope is disallowed, because " + "there is ambiguity on how to replicate a generator (e.g. should it be " + "copied so such each replica will get the same random numbers, or " + "should it be 'split' into different generators that generate " + "different random numbers).") + # TODO(wangpeng): Link to the RNG guide for solutions in such cases. + var = variables.Variable(*args, **kwargs) + return var + + @tf_export("random.experimental.Generator") class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): """Random-number generator. - It uses Variable to manage its internal state, and allows choosing an - Random-Number-Generation (RNG) algorithm. + Example: + + Creating a generator from a seed: + + >>> g = tf.random.experimental.Generator.from_seed(1234) + >>> g.normal(shape=(2, 3)) + + + Creating a generator from a non-deterministic state: + + >>> g = tf.random.experimental.Generator.from_non_deterministic_state() + >>> g.normal(shape=(2, 3)) + + + All the constructors allow explicitly choosing an Random-Number-Generation + (RNG) algorithm. Supported algorithms are `"philox"` and `"threefry"`. For + example: + + >>> g = tf.random.experimental.Generator.from_seed(123, alg="philox") + >>> g.normal(shape=(2, 3)) + CPU, GPU and TPU with the same algorithm and seed will generate the same integer random numbers. Float-point results (such as the output of `normal`) - may have small numerical discrepancies between CPU and GPU. + may have small numerical discrepancies between different devices. + + This class uses a `tf.Variable` to manage its internal state. Every time + random numbers are generated, the state of the generator will change. For + example: + + >>> g = tf.random.experimental.Generator.from_seed(1234) + >>> g.state + + >>> g.normal(shape=(2, 3)) + <...> + >>> g.state + + + The shape of the state is algorithm-specific. + + There is also a global generator: + + >>> g = tf.random.experimental.get_global_generator() + >>> g.normal(shape=(2, 3)) + """ def __init__(self, copy_from=None, state=None, alg=None): @@ -231,7 +341,7 @@ class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): decreasing precedence: (1) If `copy_from` is not None, the new generator is initialized by copying information from another generator. - (3) If `state` and `alg` are not None (they must be set together), the new + (2) If `state` and `alg` are not None (they must be set together), the new generator is initialized by a state. Args: @@ -240,30 +350,41 @@ class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): RNG, whose length and semantics are algorithm-specific. If it's a variable, the generator will reuse it instead of creating a new variable. - alg: the RNG algorithm. Possible values are `RNG_ALG_PHILOX` for the - Philox algorithm and `RNG_ALG_THREEFRY` for the ThreeFry - algorithm (see paper 'Parallel Random Numbers: As Easy as 1, 2, 3' + alg: the RNG algorithm. Possible values are + `tf.random.experimental.Algorithm.PHILOX` for the Philox algorithm and + `tf.random.experimental.Algorithm.THREEFRY` for the ThreeFry algorithm + (see paper 'Parallel Random Numbers: As Easy as 1, 2, 3' [https://www.thesalmons.org/john/random123/papers/random123sc11.pdf]). - Note `RNG_ALG_PHILOX` guarantees the same numbers are produced (given + The string names `"philox"` and `"threefry"` can also be used. + Note `PHILOX` guarantees the same numbers are produced (given the same random state) across all architextures (CPU, GPU, XLA etc). + + Throws: + ValueError: if the generator is created inside a synchronous + `tf.distribute` strategy such as `MirroredStrategy` or `TPUStrategy`, + because there is ambiguity on how to replicate a generator (e.g. should + it be copied so such each replica will get the same random numbers, or + should it be "split" into different generators that generate + different random numbers). """ if copy_from is not None: # All other arguments should be None assert (alg or state) is None - self._state_var = variables.Variable(copy_from.state, dtype=STATE_TYPE, - trainable=False) + self._state_var = _create_variable(copy_from.state, dtype=STATE_TYPE, + trainable=False) self._alg = copy_from.algorithm else: assert alg is not None and state is not None + alg = _convert_alg_to_int(alg) if isinstance(state, variables.Variable): _check_state_shape(state.shape, alg) self._state_var = state else: state = _convert_to_state_tensor(state) _check_state_shape(state.shape, alg) - self._state_var = variables.Variable(state, dtype=STATE_TYPE, - trainable=False) + self._state_var = _create_variable(state, dtype=STATE_TYPE, + trainable=False) self._alg = alg @classmethod @@ -278,6 +399,14 @@ class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): Returns: The new generator. + + Throws: + ValueError: if the generator is created inside a synchronous + `tf.distribute` strategy such as `MirroredStrategy` or `TPUStrategy`, + because there is ambiguity on how to replicate a generator (e.g. should + it be copied so such each replica will get the same random numbers, or + should it be "split" into different generators that generate + different random numbers). """ return cls(alg=alg, state=state) @@ -299,10 +428,19 @@ class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): Returns: The new generator. + + Throws: + ValueError: if the generator is created inside a synchronous + `tf.distribute` strategy such as `MirroredStrategy` or `TPUStrategy`, + because there is ambiguity on how to replicate a generator (e.g. should + it be copied so such each replica will get the same random numbers, or + should it be "split" into different generators that generate + different random numbers). """ if alg is None: # TODO(wangpeng): more sophisticated algorithm selection alg = DEFAULT_ALGORITHM + alg = _convert_alg_to_int(alg) state = create_rng_state(seed, alg) return cls(state=state, alg=alg) @@ -318,10 +456,19 @@ class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): Returns: The new generator. + + Throws: + ValueError: if the generator is created inside a synchronous + `tf.distribute` strategy such as `MirroredStrategy` or `TPUStrategy`, + because there is ambiguity on how to replicate a generator (e.g. should + it be copied so such each replica will get the same random numbers, or + should it be "split" into different generators that generate + different random numbers). """ if alg is None: # TODO(wangpeng): more sophisticated algorithm selection alg = DEFAULT_ALGORITHM + alg = _convert_alg_to_int(alg) state = non_deterministic_ints(shape=[_get_state_size(alg)], dtype=SEED_TYPE) return cls(state=state, alg=alg) @@ -342,9 +489,18 @@ class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): Returns: The new generator. + + Throws: + ValueError: if the generator is created inside a synchronous + `tf.distribute` strategy such as `MirroredStrategy` or `TPUStrategy`, + because there is ambiguity on how to replicate a generator (e.g. should + it be copied so such each replica will get the same random numbers, or + should it be "split" into different generators that generate + different random numbers). """ counter = _convert_to_state_tensor(counter) key = _convert_to_state_tensor(key) + alg = _convert_alg_to_int(alg) counter.shape.assert_is_compatible_with([_get_state_size(alg) - 1]) key.shape.assert_is_compatible_with([]) key = array_ops.reshape(key, [1]) @@ -403,7 +559,7 @@ class Generator(tracking.AutoTrackable, composite_tensor.CompositeTensor): @property def algorithm(self): - """The RNG algorithm.""" + """The RNG algorithm id (a Python integer or scalar integer Tensor).""" return self._alg def _standard_normal(self, shape, dtype): @@ -743,6 +899,16 @@ global_generator = None @tf_export("random.experimental.get_global_generator") def get_global_generator(): + """Retrieves the global generator. + + This function will create the global generator the first time it is called, + and the generator will be placed at the default device at that time, so one + needs to be careful when this function is first called. Using a generator + placed on a less-ideal device will incur performance regression. + + Returns: + The global `tf.random.experimental.Generator` object. + """ global global_generator if global_generator is None: with ops.init_scope(): diff --git a/tensorflow/python/ops/stateful_random_ops_test.py b/tensorflow/python/ops/stateful_random_ops_test.py index 499698b7d57..87b4a88a69b 100644 --- a/tensorflow/python/ops/stateful_random_ops_test.py +++ b/tensorflow/python/ops/stateful_random_ops_test.py @@ -197,6 +197,17 @@ class StatefulRandomOpsTest(test.TestCase, parameterized.TestCase): check_results(expected_normal1, f(constructor)) check_results(expected_normal2, f(constructor)) + @parameterized.parameters([ + ("philox", random.RNG_ALG_PHILOX, random.Algorithm.PHILOX), + ("threefry", random.RNG_ALG_THREEFRY, random.Algorithm.THREEFRY)]) + @test_util.run_v2_only + def testAlg(self, name, int_id, enum_id): + g_by_name = random.Generator.from_seed(1234, name) + g_by_int = random.Generator.from_seed(1234, int_id) + g_by_enum = random.Generator.from_seed(1234, enum_id) + self.assertEqual(g_by_name.algorithm, g_by_int.algorithm) + self.assertEqual(g_by_name.algorithm, g_by_enum.algorithm) + @test_util.run_v2_only def testGeneratorCreationWithVar(self): """Tests creating generator with a variable. @@ -659,12 +670,8 @@ class StatefulRandomOpsTest(test.TestCase, parameterized.TestCase): self.assertAllDifferent(values) @test_util.run_v2_only - def testMirroredStratParaSync(self): - """Tests RNG/MirrorStrategy interaction #2. - - If an RNG is created (either seeded or unseeded) inside strategy.scope(), - each replica gets an mirror of this RNG. If they access their RNGs in the - same manner, their random-number streams are the same. + def testMirroredStratParaSyncDisallowed(self): + """Tests that generator creation in MirroredStrategy is disallowed. """ creators = [ lambda: random.Generator.from_seed(1234), @@ -675,47 +682,19 @@ class StatefulRandomOpsTest(test.TestCase, parameterized.TestCase): strat = MirroredStrategy(devices=["cpu:0", "cpu:1"]) for creator in creators: with strat.scope(): - gen = creator() - def f(): - t1 = gen.uniform_full_int(shape=shape, dtype=dtype) # pylint: disable=cell-var-from-loop - t2 = gen.uniform_full_int(shape=shape, dtype=dtype) # pylint: disable=cell-var-from-loop - t = array_ops.stack([t1, t2]) - return t - results = strat.extended.call_for_each_replica(fn=f) - values = results.values - self.assertAllEqual(2, len(values)) - self.assertAllEqual(values[0], values[1]) - - @test_util.run_v2_only - def testMirroredStratParaSyncWithinFun(self): - """Tests RNG/MirrorStrategy interaction #2b. - - This is a slight variation of case #2 above. If the RNG is created within - `f`, its behavior is the same as when it is created out of `f` but within - the strategy scope. - """ - creators = [ - lambda: random.Generator.from_seed(1234), - random.Generator.from_non_deterministic_state, - ] - shape = [3, 4] - dtype = dtypes.int32 - strat = MirroredStrategy(devices=["cpu:0", "cpu:1"]) - for creator in creators: + with self.assertRaisesWithPredicateMatch( + ValueError, "disallowed"): + creator() # pylint: disable=cell-var-from-loop def f(): gen = creator() # pylint: disable=cell-var-from-loop - t1 = gen.uniform_full_int(shape=shape, dtype=dtype) - t2 = gen.uniform_full_int(shape=shape, dtype=dtype) - t = array_ops.stack([t1, t2]) - return t - results = strat.extended.call_for_each_replica(fn=f) - values = results.values - self.assertAllEqual(2, len(values)) - self.assertAllEqual(values[0], values[1]) + return gen.uniform_full_int(shape=shape, dtype=dtype) + with self.assertRaisesWithPredicateMatch( + ValueError, "disallowed"): + strat.extended.call_for_each_replica(fn=f) @test_util.run_v2_only def testMirroredStratParaAsync(self): - """Tests RNG/MirrorStrategy interaction #3. + """Tests RNG/MirrorStrategy interaction #2. The user can create n independent RNGs outside strategy.scope(), where n is the number of replicas, and give one to each replica. The replicas can @@ -742,30 +721,6 @@ class StatefulRandomOpsTest(test.TestCase, parameterized.TestCase): self.assertAllEqual(2, len(values)) self.assertAllDifferent(values) - @test_util.run_v2_only - @test_util.run_cuda_only - def testMirroredVarAsFunctionArg(self): - """Tests that RNG with MirroredVariable can be used as tf.function's arg. - """ - shape = [3, 4] - dtype = dtypes.int32 - strat = MirroredStrategy(devices=["/cpu:0", test_util.gpu_device_name()]) - with strat.scope(): - gen = random.Generator.from_seed(1234) - @def_function.function - def f(gen): - t1 = gen.uniform_full_int(shape=shape, dtype=dtype) - t2 = gen.uniform_full_int(shape=shape, dtype=dtype) - t = array_ops.stack([t1, t2]) - return t - def g(): - return f(gen) - for _ in range(2): - results = strat.extended.call_for_each_replica(fn=g) - values = results.values - self.assertAllEqual(2, len(values)) - self.assertAllEqual(values[0], values[1]) - if __name__ == "__main__": test.main() diff --git a/tensorflow/python/ops/tensor_array_ops.py b/tensorflow/python/ops/tensor_array_ops.py index 74db51826c7..869f90330e5 100644 --- a/tensorflow/python/ops/tensor_array_ops.py +++ b/tensorflow/python/ops/tensor_array_ops.py @@ -804,7 +804,7 @@ class _EagerTensorArray(object): None, None, "Tried to write to index %d but array is not resizeable and size " "is: %d" % (index, size)) - self._tensor_array.extend([None for _ in range(index - size + 1)]) + self._tensor_array.extend(None for _ in range(index - size + 1)) if not isinstance(value, ops.EagerTensor): # TODO(b/129870929): Fix after all callers provide proper init dtype. diff --git a/tensorflow/python/ops/variables.py b/tensorflow/python/ops/variables.py index 7b233ce331f..eae00217214 100644 --- a/tensorflow/python/ops/variables.py +++ b/tensorflow/python/ops/variables.py @@ -1324,9 +1324,9 @@ class Variable(six.with_metaclass(VariableMetaclass, trackable.Trackable)): @property def spec(self): """Computes the spec string used for saving.""" - full_shape_str = " ".join(["%d" % d for d in self.full_shape]) + " " + full_shape_str = " ".join("%d" % d for d in self.full_shape) + " " sl_spec = ":".join( - ["%d,%d" % (o, s) for o, s in zip(self.var_offset, self.var_shape)]) + "%d,%d" % (o, s) for o, s in zip(self.var_offset, self.var_shape)) return full_shape_str + sl_spec def to_proto(self, export_scope=None): diff --git a/tensorflow/python/ops/while_v2.py b/tensorflow/python/ops/while_v2.py index a4ef39038a1..e0964cd4482 100644 --- a/tensorflow/python/ops/while_v2.py +++ b/tensorflow/python/ops/while_v2.py @@ -304,9 +304,11 @@ def while_loop(cond, # exit op as input. outputs = tuple(array_ops.identity(t) for t in outputs) - outputs = _pack_sequence_as( - orig_loop_vars, outputs[first_loop_var_index:first_loop_var_index + - num_flattened_outputs]) + output_loop_vars = outputs[first_loop_var_index:first_loop_var_index + + num_flattened_outputs] + if not back_prop: + output_loop_vars = [array_ops.stop_gradient(t) for t in output_loop_vars] + outputs = _pack_sequence_as(orig_loop_vars, output_loop_vars) if return_same_structure: return outputs diff --git a/tensorflow/python/platform/gfile.py b/tensorflow/python/platform/gfile.py index dd2c615e9e0..74f76595c7a 100644 --- a/tensorflow/python/platform/gfile.py +++ b/tensorflow/python/platform/gfile.py @@ -41,11 +41,23 @@ from tensorflow.python.util.tf_export import tf_export class GFile(_FileIO): """File I/O wrappers without thread locking. - Note, that this is somewhat like builtin Python file I/O, but - there are semantic differences to make it more efficient for - some backing filesystems. For example, a write mode file will - not be opened until the first write call (to minimize RPC - invocations in network filesystems). + The main roles of the `tf.gfile` module are: + + 1. To provide an API that is close to Python's file I/O objects, and + 2. To provide an implementation based on TensorFlow's C++ FileSystem API. + + The C++ FileSystem API supports multiple file system implementations, + including local files, Google Cloud Storage (using a `gs://` prefix, and + HDFS (using an `hdfs://` prefix). TensorFlow exports these as `tf.gfile`, + so that you can use these implementations for saving and loading checkpoints, + writing to TensorBoard logs, and accessing training data (among other uses). + However, if all your files are local, you can use the regular Python file + API without any problem. + + *Note*: though similar to Python's I/O implementation, there are semantic + differences to make `tf.gfile` more efficient for backing filesystems. For + example, a write mode file will not be opened until the first write call, to + minimize RPC invocations in network filesystems. """ def __init__(self, name, mode='r'): diff --git a/tensorflow/python/platform/self_check.py b/tensorflow/python/platform/self_check.py index 33aed306467..f6cf7705e13 100644 --- a/tensorflow/python/platform/self_check.py +++ b/tensorflow/python/platform/self_check.py @@ -42,17 +42,22 @@ def preload_check(): # we load the Python extension, so that we can raise an actionable error # message if they are not found. import ctypes # pylint: disable=g-import-not-at-top - if hasattr(build_info, "msvcp_dll_name"): - try: - ctypes.WinDLL(build_info.msvcp_dll_name) - except OSError: + if hasattr(build_info, "msvcp_dll_names"): + missing = [] + for dll_name in build_info.msvcp_dll_names.split(","): + try: + ctypes.WinDLL(dll_name) + except OSError: + missing.append(dll_name) + if missing: raise ImportError( - "Could not find %r. TensorFlow requires that this DLL be " - "installed in a directory that is named in your %%PATH%% " - "environment variable. You may install this DLL by downloading " - "Visual C++ 2015 Redistributable Update 3 from this URL: " - "https://www.microsoft.com/en-us/download/details.aspx?id=53587" - % build_info.msvcp_dll_name) + "Could not find the DLL(s) %r. TensorFlow requires that these DLLs " + "be installed in a directory that is named in your %%PATH%% " + "environment variable. You may install these DLLs by downloading " + '"Microsoft C++ Redistributable for Visual Studio 2015, 2017 and ' + '2019" for your platform from this URL: ' + "https://support.microsoft.com/help/2977003/the-latest-supported-visual-c-downloads" + % " or ".join(missing)) else: # TODO(mrry): Consider adding checks for the Linux and Mac OS X builds. pass diff --git a/tensorflow/python/profiler/BUILD b/tensorflow/python/profiler/BUILD index 4853d0e6be7..88478b9d968 100644 --- a/tensorflow/python/profiler/BUILD +++ b/tensorflow/python/profiler/BUILD @@ -46,17 +46,6 @@ py_library( cuda_py_test( name = "model_analyzer_test", srcs = ["model_analyzer_test.py"], - additional_deps = [ - ":profile_context", - ":model_analyzer", - "//tensorflow/python/profiler/internal:model_analyzer_testlib", - "//tensorflow/python:client", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:distributed_framework_test_lib", - "//tensorflow/python:platform", - "//tensorflow/python:variables", - ], python_version = "PY3", tags = [ "no_pip", @@ -64,23 +53,34 @@ cuda_py_test( "oss_serial", ], xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ + ":model_analyzer", + ":profile_context", + "//tensorflow/python:client", + "//tensorflow/python:client_testlib", + "//tensorflow/python:distributed_framework_test_lib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:platform", + "//tensorflow/python:variables", + "//tensorflow/python/profiler/internal:model_analyzer_testlib", + ], ) cuda_py_test( name = "profiler_test", srcs = ["profiler_test.py"], - additional_deps = [ + python_version = "PY3", + tags = ["no_pip"], + xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ ":model_analyzer", "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:platform", - "//tensorflow/python/profiler/internal:model_analyzer_testlib", "//tensorflow/python:variables", + "//tensorflow/python/profiler/internal:model_analyzer_testlib", ], - python_version = "PY3", - tags = ["no_pip"], - xla_enable_strict_auto_jit = False, # Node names are different with autojit ) py_library( @@ -100,7 +100,8 @@ tf_py_test( name = "tfprof_logger_test", size = "small", srcs = ["tfprof_logger_test.py"], - additional_deps = [ + python_version = "PY3", + deps = [ ":tfprof_logger", "//tensorflow/core:protos_all_py", "//tensorflow/core/profiler:protos_all_py", @@ -110,7 +111,6 @@ tf_py_test( "//tensorflow/python:framework_for_generated_wrappers", "//tensorflow/python:math_ops", ], - python_version = "PY3", ) py_library( @@ -126,21 +126,21 @@ py_library( cuda_py_test( name = "profile_context_test", srcs = ["profile_context_test.py"], - additional_deps = [ - ":profile_context", - "//tensorflow/python:client", - "//tensorflow/python:client_testlib", - "//tensorflow/python:framework_for_generated_wrappers", - "//tensorflow/python:platform", - "//tensorflow/python/profiler/internal:model_analyzer_testlib", - "//tensorflow/python:variables", - ], python_version = "PY3", tags = [ "no_gpu", # b/136036359 "no_pip", ], xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ + ":profile_context", + "//tensorflow/python:client", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:platform", + "//tensorflow/python:variables", + "//tensorflow/python/profiler/internal:model_analyzer_testlib", + ], ) py_library( diff --git a/tensorflow/python/profiler/internal/BUILD b/tensorflow/python/profiler/internal/BUILD index 6e0f5a6f456..8cab716b71b 100644 --- a/tensorflow/python/profiler/internal/BUILD +++ b/tensorflow/python/profiler/internal/BUILD @@ -58,21 +58,21 @@ py_test( cuda_py_test( name = "run_metadata_test", srcs = ["run_metadata_test.py"], - additional_deps = [ - ":model_analyzer_testlib", - "//tensorflow/core/profiler:protos_all_py", - "//tensorflow/python:array_ops", - "//tensorflow/python:client_testlib", - "//tensorflow/python:math_ops", - "//tensorflow/python/profiler:model_analyzer", - "//tensorflow/python:random_ops", - ], python_version = "PY3", tags = [ "no_gpu", # b/138442728 "no_pip", ], xla_enable_strict_auto_jit = False, # Node names are different with autojit + deps = [ + ":model_analyzer_testlib", + "//tensorflow/core/profiler:protos_all_py", + "//tensorflow/python:array_ops", + "//tensorflow/python:client_testlib", + "//tensorflow/python:math_ops", + "//tensorflow/python:random_ops", + "//tensorflow/python/profiler:model_analyzer", + ], ) pybind_extension( diff --git a/tensorflow/python/profiler/model_analyzer_test.py b/tensorflow/python/profiler/model_analyzer_test.py index c06310310f9..aa0e8539246 100644 --- a/tensorflow/python/profiler/model_analyzer_test.py +++ b/tensorflow/python/profiler/model_analyzer_test.py @@ -229,7 +229,7 @@ class PrintModelAnalysisTest(test.TestCase): with gfile.Open(outfile, 'r') as f: lines = f.read().split('\n') self.assertGreater(len(lines), 5) - result = '\n'.join([l[:min(len(l), 80)] for l in lines]) + result = '\n'.join(l[:min(len(l), 80)] for l in lines) self.assertTrue( compat.as_text(lib.CheckAndRemoveDoc(result)) .startswith('node name | # parameters | # float_ops')) diff --git a/tensorflow/python/saved_model/BUILD b/tensorflow/python/saved_model/BUILD index fca87c5c178..e8132813d4f 100644 --- a/tensorflow/python/saved_model/BUILD +++ b/tensorflow/python/saved_model/BUILD @@ -100,12 +100,11 @@ tf_py_test( name = "loader_test", size = "small", srcs = ["loader_test.py"], - additional_deps = [ + deps = [ ":builder", ":loader", ":signature_def_utils", ":utils", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:client", "//tensorflow/python:client_testlib", "//tensorflow/python:control_flow_ops", @@ -115,6 +114,7 @@ tf_py_test( "//tensorflow/python:state_ops", "//tensorflow/python:training", "//tensorflow/python:variables", + "@absl_py//absl/testing:parameterized", ], ) @@ -155,7 +155,9 @@ tf_py_test( name = "saved_model_test", size = "small", srcs = ["saved_model_test.py"], - additional_deps = [ + data = ["//tensorflow/cc/saved_model:saved_model_half_plus_two"], + tags = ["no_windows"], + deps = [ ":builder", ":constants", ":loader", @@ -178,8 +180,6 @@ tf_py_test( "//tensorflow/python:util", "//tensorflow/python:variables", ], - data = ["//tensorflow/cc/saved_model:saved_model_half_plus_two"], - tags = ["no_windows"], ) py_library( @@ -204,7 +204,7 @@ tf_py_test( name = "utils_test", size = "small", srcs = ["utils_test.py"], - additional_deps = [ + deps = [ ":utils", "//tensorflow/core:protos_all_py", "//tensorflow/python:array_ops", @@ -234,7 +234,7 @@ tf_py_test( name = "signature_def_utils_test", size = "small", srcs = ["signature_def_utils_test.py"], - additional_deps = [ + deps = [ ":signature_constants", ":signature_def_utils", ":utils", @@ -249,7 +249,7 @@ tf_py_test( name = "simple_save_test", size = "small", srcs = ["simple_save_test.py"], - additional_deps = [ + deps = [ ":loader", ":signature_constants", ":simple_save", @@ -320,19 +320,19 @@ py_library( tf_py_test( name = "save_test", srcs = ["save_test.py"], - additional_deps = [ + tags = ["no_rocm"], + deps = [ ":loader", ":save", ":save_options", ":signature_constants", ":tag_constants", - "@absl_py//absl/testing:parameterized", "//tensorflow/core:protos_all_py", "//tensorflow/python:error_interpolation", "//tensorflow/python/eager:def_function", "//tensorflow/python/eager:test", + "@absl_py//absl/testing:parameterized", ], - tags = ["no_rocm"], ) py_library( @@ -386,40 +386,39 @@ py_library( cuda_py_test( name = "load_test", srcs = ["load_test.py"], - additional_deps = [ - ":load", - ":save", - "@absl_py//absl/testing:parameterized", - "//tensorflow/python:constant_op", - "//tensorflow/python:control_flow_ops", - "//tensorflow/python:cond_v2", # b/118513001 - "//tensorflow/python:while_v2", # b/118513001 - "//tensorflow/python:dtypes", - "//tensorflow/python:lib", - "//tensorflow/python:tensor_spec", - "//tensorflow/python/eager:def_function", - "//tensorflow/python/eager:test", - "//tensorflow/python/module", - "//tensorflow/python/training/tracking:tracking", - ], shard_count = 10, tags = [ "no_gpu", # TODO(b/136560979): flaky "no_mac", # TODO(b/124822121): Re-enable this test. ], + deps = [ + ":load", + ":save", + "//tensorflow/python:cond_v2", # b/118513001 + "//tensorflow/python:constant_op", + "//tensorflow/python:control_flow_ops", + "//tensorflow/python:dtypes", + "//tensorflow/python:lib", + "//tensorflow/python:tensor_spec", + "//tensorflow/python:while_v2", # b/118513001 + "//tensorflow/python/eager:def_function", + "//tensorflow/python/eager:test", + "//tensorflow/python/module", + "//tensorflow/python/training/tracking", + "@absl_py//absl/testing:parameterized", + ], ) tf_py_test( name = "load_v1_in_v2_test", srcs = ["load_v1_in_v2_test.py"], - additional_deps = [ + deps = [ ":builder", ":load", ":save", ":signature_def_utils", ":simple_save", ":utils", - "@absl_py//absl/testing:parameterized", "//tensorflow/python:array_ops", "//tensorflow/python:constant_op", "//tensorflow/python:dtypes", @@ -428,10 +427,11 @@ tf_py_test( "//tensorflow/python:resource_variable_ops", "//tensorflow/python:session", "//tensorflow/python:tensor_spec", + "//tensorflow/python:variables", "//tensorflow/python/eager:def_function", "//tensorflow/python/eager:test", - "//tensorflow/python/training/tracking:tracking", - "//tensorflow/python:variables", + "//tensorflow/python/training/tracking", + "@absl_py//absl/testing:parameterized", ], ) @@ -449,7 +449,7 @@ py_library( tf_py_test( name = "revived_types_test", srcs = ["revived_types_test.py"], - additional_deps = [ + deps = [ ":revived_types", "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", @@ -501,7 +501,7 @@ py_library( tf_py_test( name = "nested_structure_coder_test", srcs = ["nested_structure_coder_test.py"], - additional_deps = [ + deps = [ ":nested_structure_coder", "//tensorflow/core:protos_all_py", "//tensorflow/python:framework", diff --git a/tensorflow/python/saved_model/function_deserialization.py b/tensorflow/python/saved_model/function_deserialization.py index 3ae6c8414f1..0a5ee9d04d5 100644 --- a/tensorflow/python/saved_model/function_deserialization.py +++ b/tensorflow/python/saved_model/function_deserialization.py @@ -244,8 +244,7 @@ def recreate_function(saved_function, concrete_functions): def _pretty_format_positional(positional): return "Positional arguments ({} total):\n * {}".format( - len(positional), - "\n * ".join([str(a) for a in positional])) + len(positional), "\n * ".join(str(a) for a in positional)) for index, function_name in enumerate(saved_function.concrete_functions): concrete_function = concrete_functions[function_name] diff --git a/tensorflow/python/saved_model/model_utils/export_test.py b/tensorflow/python/saved_model/model_utils/export_test.py index c87d2ee6ae7..c5f7f404545 100644 --- a/tensorflow/python/saved_model/model_utils/export_test.py +++ b/tensorflow/python/saved_model/model_utils/export_test.py @@ -203,8 +203,19 @@ class ExportTest(test_util.TensorFlowTestCase): time_3 = os.path.basename(export_dir_3) self.assertEqual(10, len(time_3)) - self.assertTrue(int(time_1) < int(time_2)) - self.assertTrue(int(time_2) < int(time_3)) + self.assertLess(int(time_1), int(time_2)) + self.assertLess(int(time_2), int(time_3)) + + def test_get_temp_export_dir(self): + export_dir = os.path.join("tmp", "export", "1576013284") + tmp_export_dir = export_utils.get_temp_export_dir(export_dir) + self.assertEqual(tmp_export_dir, + os.path.join(b"tmp", b"export", b"temp-1576013284")) + + export_dir = os.path.join(b"tmp", b"export", b"1576013284") + tmp_export_dir = export_utils.get_temp_export_dir(export_dir) + self.assertEqual(tmp_export_dir, + os.path.join(b"tmp", b"export", b"temp-1576013284")) @test_util.deprecated_graph_mode_only def test_build_all_signature_defs_serving_only(self): diff --git a/tensorflow/python/saved_model/model_utils/export_utils.py b/tensorflow/python/saved_model/model_utils/export_utils.py index 737f76edf08..e2faebe8f4d 100644 --- a/tensorflow/python/saved_model/model_utils/export_utils.py +++ b/tensorflow/python/saved_model/model_utils/export_utils.py @@ -242,7 +242,8 @@ def get_temp_export_dir(timestamped_export_dir): """ (dirname, basename) = os.path.split(timestamped_export_dir) temp_export_dir = os.path.join( - compat.as_bytes(dirname), compat.as_bytes('temp-{}'.format(basename))) + compat.as_bytes(dirname), + compat.as_bytes('temp-{}'.format(six.ensure_text(basename)))) return temp_export_dir diff --git a/tensorflow/python/tools/BUILD b/tensorflow/python/tools/BUILD index f9a8a6494b0..3d7f911b9f2 100644 --- a/tensorflow/python/tools/BUILD +++ b/tensorflow/python/tools/BUILD @@ -47,7 +47,7 @@ py_test( name = "saved_model_utils_test", size = "small", srcs = ["saved_model_utils_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = ["no_windows"], # TODO: needs investigation on Windows visibility = ["//visibility:private"], @@ -81,7 +81,7 @@ py_library( py_binary( name = "freeze_graph", srcs = ["freeze_graph.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":freeze_graph_main_lib"], ) @@ -98,7 +98,7 @@ py_library( py_binary( name = "import_pb_to_tensorboard", srcs = ["import_pb_to_tensorboard.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":import_pb_to_tensorboard_lib"], ) @@ -122,7 +122,7 @@ py_test( name = "freeze_graph_test", size = "small", srcs = ["freeze_graph_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":freeze_graph_lib", @@ -141,7 +141,7 @@ py_test( py_binary( name = "inspect_checkpoint", srcs = ["inspect_checkpoint.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":inspect_checkpoint_lib"], ) @@ -177,7 +177,7 @@ py_library( py_binary( name = "strip_unused", srcs = ["strip_unused.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":strip_unused_lib", @@ -191,7 +191,7 @@ py_test( name = "strip_unused_test", size = "small", srcs = ["strip_unused_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", tags = ["notap"], deps = [ @@ -224,7 +224,7 @@ py_library( py_binary( name = "optimize_for_inference", srcs = ["optimize_for_inference.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":optimize_for_inference_main_lib"], ) @@ -248,7 +248,7 @@ py_test( name = "optimize_for_inference_test", size = "small", srcs = ["optimize_for_inference_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":optimize_for_inference_lib", @@ -280,7 +280,7 @@ py_library( py_binary( name = "print_selective_registration_header", srcs = ["print_selective_registration_header.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = [":print_selective_registration_header_lib"], @@ -300,7 +300,7 @@ py_library( py_test( name = "print_selective_registration_header_test", srcs = ["print_selective_registration_header_test.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [ ":selective_registration_header_lib", @@ -312,7 +312,7 @@ py_test( py_binary( name = "saved_model_cli", srcs = ["saved_model_cli.py"], - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", deps = [":saved_model_cli_lib"], ) diff --git a/tensorflow/python/tools/api/generator/api_gen.bzl b/tensorflow/python/tools/api/generator/api_gen.bzl index b567a229177..ec5317d16f8 100644 --- a/tensorflow/python/tools/api/generator/api_gen.bzl +++ b/tensorflow/python/tools/api/generator/api_gen.bzl @@ -92,7 +92,7 @@ def gen_api_init_files( name = api_gen_binary_target, srcs = ["//tensorflow/python/tools/api/generator:create_python_api.py"], main = "//tensorflow/python/tools/api/generator:create_python_api.py", - python_version = "PY2", + python_version = "PY3", srcs_version = "PY2AND3", visibility = ["//visibility:public"], deps = package_deps + [ diff --git a/tensorflow/python/tools/selective_registration_header_lib.py b/tensorflow/python/tools/selective_registration_header_lib.py index 879ce6cbf00..da34da594e3 100644 --- a/tensorflow/python/tools/selective_registration_header_lib.py +++ b/tensorflow/python/tools/selective_registration_header_lib.py @@ -96,7 +96,7 @@ def get_header_from_ops_and_kernels(ops_and_kernels, Returns: the string of the header that should be written as ops_to_register.h. """ - ops = set([op for op, _ in ops_and_kernels]) + ops = set(op for op, _ in ops_and_kernels) result_list = [] def append(s): diff --git a/tensorflow/python/tpu/BUILD b/tensorflow/python/tpu/BUILD index 0f708fb420e..82dbc04de0b 100644 --- a/tensorflow/python/tpu/BUILD +++ b/tensorflow/python/tpu/BUILD @@ -257,37 +257,37 @@ tf_py_test( name = "datasets_test", size = "medium", srcs = ["datasets_test.py"], - additional_deps = [ - "//tensorflow/python:client_testlib", - ":datasets", - ], grpc_enabled = True, shard_count = 4, tags = ["no_oss"], + deps = [ + ":datasets", + "//tensorflow/python:client_testlib", + ], ) tf_py_test( name = "tpu_test", size = "small", srcs = ["tpu_test.py"], - additional_deps = [ + tags = [ + "no_oss", # TODO(b/131157871): Reenable in OSS when fixed + "no_windows", # TODO: needs investigation on Windows + ], + deps = [ ":tpu", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:framework", "//tensorflow/python:layers", ], - tags = [ - "no_oss", # TODO(b/131157871): Reenable in OSS when fixed - "no_windows", # TODO: needs investigation on Windows - ], ) tf_py_test( name = "tpu_sharding_test", size = "small", srcs = ["tpu_sharding_test.py"], - additional_deps = [ + deps = [ ":tpu", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -298,7 +298,7 @@ tf_py_test( name = "bfloat16_test", size = "small", srcs = ["bfloat16_test.py"], - additional_deps = [ + deps = [ ":tpu", "//tensorflow/python:client_testlib", "//tensorflow/python:framework", @@ -309,7 +309,7 @@ tf_py_test( name = "tpu_infeed_test", size = "small", srcs = ["tpu_infeed_test.py"], - additional_deps = [ + deps = [ ":tpu", "//tensorflow/python:framework", "//tensorflow/python:framework_test_lib", @@ -320,7 +320,7 @@ tf_py_test( name = "topology_test", size = "medium", srcs = ["topology_test.py"], - additional_deps = [ + deps = [ ":tpu", "//tensorflow/python:framework_test_lib", ], @@ -395,9 +395,9 @@ tf_py_test( srcs = [ "feature_column_test.py", ], - additional_deps = [ + main = "feature_column_test.py", + deps = [ ":feature_column", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:framework_ops", @@ -408,8 +408,8 @@ tf_py_test( "//tensorflow/python:variables", "//tensorflow/python/feature_column", "//tensorflow/python/feature_column:feature_column_py", + "//third_party/py/numpy", ], - main = "feature_column_test.py", ) tf_py_test( @@ -417,9 +417,9 @@ tf_py_test( srcs = [ "feature_column_v2_test.py", ], - additional_deps = [ + main = "feature_column_v2_test.py", + deps = [ ":feature_column_v2", - "//third_party/py/numpy", "//tensorflow/python:client_testlib", "//tensorflow/python:dtypes", "//tensorflow/python:framework_ops", @@ -430,8 +430,8 @@ tf_py_test( "//tensorflow/python:variables", "//tensorflow/python/feature_column", "//tensorflow/python/feature_column:feature_column_py", + "//third_party/py/numpy", ], - main = "feature_column_v2_test.py", ) tf_proto_library( diff --git a/tensorflow/python/tpu/client/BUILD b/tensorflow/python/tpu/client/BUILD new file mode 100644 index 00000000000..3201c7fccb0 --- /dev/null +++ b/tensorflow/python/tpu/client/BUILD @@ -0,0 +1,68 @@ +# Cloud TPU Client. + +load("//tensorflow:tensorflow.bzl", "tf_py_test") + +package( + default_visibility = [ + "//tensorflow:internal", + ], + licenses = ["notice"], # Apache 2.0 +) + +py_library( + name = "client", + srcs = [ + "client.py", + "version.py", + ], + srcs_version = "PY2AND3", + deps = [ + "@six_archive//:six", + ], +) + +py_library( + name = "client_lib", + srcs = [ + "__init__.py", + ], + srcs_version = "PY2AND3", + deps = [ + ":client", + "@six_archive//:six", + ], +) + +tf_py_test( + name = "client_py_test", + size = "small", + srcs = ["client_test.py"], + grpc_enabled = True, + main = "client_test.py", + python_version = "PY3", + deps = [ + ":client", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + "//tensorflow/python:training_server_lib", + ], +) + +tf_py_test( + name = "client_py2_test", + size = "small", + srcs = ["client_test.py"], + grpc_enabled = True, + main = "client_test.py", + python_version = "PY2", + deps = [ + ":client", + "//tensorflow/python:client_testlib", + "//tensorflow/python:framework_for_generated_wrappers", + "//tensorflow/python:framework_test_lib", + "//tensorflow/python:platform_test", + "//tensorflow/python:training_server_lib", + ], +) diff --git a/tensorflow/python/tpu/client/__init__.py b/tensorflow/python/tpu/client/__init__.py new file mode 100644 index 00000000000..04d4faf9c68 --- /dev/null +++ b/tensorflow/python/tpu/client/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2019 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. +# ============================================================================= +"""Cloud TPU Client.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from tensorflow.python.tpu.client.client import Client diff --git a/tensorflow/python/distribute/cluster_resolver/cloud_tpu_client.py b/tensorflow/python/tpu/client/client.py similarity index 82% rename from tensorflow/python/distribute/cluster_resolver/cloud_tpu_client.py rename to tensorflow/python/tpu/client/client.py index c8a9db7285a..7644dfb4f82 100644 --- a/tensorflow/python/distribute/cluster_resolver/cloud_tpu_client.py +++ b/tensorflow/python/tpu/client/client.py @@ -23,12 +23,10 @@ import os from six.moves.urllib import request -from tensorflow.python.util import compat - _GOOGLE_API_CLIENT_INSTALLED = True try: from apiclient import discovery # pylint: disable=g-import-not-at-top - from oauth2client.client import GoogleCredentials # pylint: disable=g-import-not-at-top + from oauth2client import client # pylint: disable=g-import-not-at-top except ImportError: _GOOGLE_API_CLIENT_INSTALLED = False @@ -49,23 +47,23 @@ def _request_compute_metadata(path): '%s/computeMetadata/v1/%s' % (_GCE_METADATA_ENDPOINT, path), headers={'Metadata-Flavor': 'Google'}) resp = request.urlopen(req) - return compat.as_bytes(resp.read()) + return _as_text(resp.read()) def _environment_var_to_network_endpoints(endpoints): """Yields a dict with ip address and port.""" - for endpoint in endpoints.split(compat.as_text(',')): - grpc_prefix = compat.as_text('grpc://') + for endpoint in endpoints.split(','): + grpc_prefix = 'grpc://' if endpoint.startswith(grpc_prefix): endpoint = endpoint.split(grpc_prefix)[1] - parts = endpoint.split(compat.as_text(':')) + parts = endpoint.split(':') ip_address = parts[0] port = _DEFAULT_ENDPOINT_PORT if len(parts) > 1: port = parts[1] yield { - 'ipAddress': compat.as_text(ip_address), - 'port': compat.as_text(port) + 'ipAddress': ip_address, + 'port': port } @@ -79,7 +77,13 @@ def _get_tpu_name(tpu): return None -class CloudTPUClient(object): +def _as_text(s): + if isinstance(s, bytes): + return s.decode('utf-8') + return s + + +class Client(object): """Client for working with the Cloud TPU API. This client is intended to be used for resolving tpu name to ip addresses. @@ -108,9 +112,9 @@ class CloudTPUClient(object): if tpu is None: raise ValueError('Please provide a TPU Name to connect to.') - self._tpu = compat.as_text(tpu) + self._tpu = _as_text(tpu) - self._use_api = not tpu.startswith('grpc://') + self._use_api = not self._tpu.startswith('grpc://') self._service = service self._credentials = None @@ -120,16 +124,15 @@ class CloudTPUClient(object): if self._use_api: if credentials != 'default': self._credentials = credentials - # Automaically detect project and zone if unspecified. + # Automatically detect project and zone if unspecified. if project: - self._project = project + self._project = _as_text(project) else: - self._project = compat.as_str( - _request_compute_metadata('project/project-id')) + self._project = _request_compute_metadata('project/project-id') if zone: - self._zone = zone + self._zone = _as_text(zone) else: - zone_path = compat.as_str(_request_compute_metadata('instance/zone')) + zone_path = _request_compute_metadata('instance/zone') self._zone = zone_path.split('/')[-1] self._discovery_url = _environment_discovery_url() or discovery_url @@ -141,15 +144,21 @@ class CloudTPUClient(object): this object call this method to get a new API object whenever they need to communicate with the Cloud API. + Raises: + RuntimeError: If the dependent Python packages are missing. + Returns: A Google Cloud TPU API object. """ if self._service: return self._service + if not _GOOGLE_API_CLIENT_INSTALLED: + raise RuntimeError('Missing runtime dependency on the Google API client.') + credentials = self._credentials if credentials is None or credentials == 'default': - credentials = GoogleCredentials.get_application_default() + credentials = client.GoogleCredentials.get_application_default() if self._discovery_url: return discovery.build( @@ -162,13 +171,16 @@ class CloudTPUClient(object): return discovery.build( 'tpu', 'v1', credentials=credentials, cache_discovery=False) + def _full_name(self): + """Returns the full Cloud name for this TPU.""" + return 'projects/%s/locations/%s/nodes/%s' % ( + self._project, self._zone, self._tpu) + def _fetch_cloud_tpu_metadata(self): """Returns the TPU metadata object from the TPU Get API call.""" + service = self._tpu_service() try: - full_name = 'projects/%s/locations/%s/nodes/%s' % ( - self._project, self._zone, compat.as_text(self._tpu)) - service = self._tpu_service() - r = service.projects().locations().nodes().get(name=full_name) + r = service.projects().locations().nodes().get(name=self._full_name()) return r.execute() except Exception as e: raise ValueError("Could not lookup TPU metadata from name '%s'. Please " @@ -220,7 +232,7 @@ class CloudTPUClient(object): if 'state' in response and response['state'] != 'READY': raise RuntimeError('TPU "%s" is not yet ready; state: "%s"' % - (compat.as_text(self._tpu), response['state'])) + (self._tpu, response['state'])) if 'networkEndpoints' in response: return response['networkEndpoints'] else: diff --git a/tensorflow/python/distribute/cluster_resolver/cloud_tpu_client_test.py b/tensorflow/python/tpu/client/client_test.py similarity index 68% rename from tensorflow/python/distribute/cluster_resolver/cloud_tpu_client_test.py rename to tensorflow/python/tpu/client/client_test.py index 39ec963c143..133e79a2cf7 100644 --- a/tensorflow/python/distribute/cluster_resolver/cloud_tpu_client_test.py +++ b/tensorflow/python/tpu/client/client_test.py @@ -13,7 +13,7 @@ # limitations under the License. # ============================================================================== # Lint as: python3 -"""Tests for cloud_tpu_client.""" +"""Tests for cloud tpu client.""" from __future__ import absolute_import from __future__ import division @@ -21,8 +21,8 @@ from __future__ import print_function import os -from tensorflow.python.distribute.cluster_resolver import cloud_tpu_client from tensorflow.python.platform import test +from tensorflow.python.tpu.client import client mock = test.mock @@ -85,19 +85,19 @@ class CloudTpuClientTest(test.TestCase): def testEnvironmentDiscoveryUrl(self): os.environ['TPU_API_DISCOVERY_URL'] = 'https://{api}.internal/{apiVersion}' self.assertEqual('https://{api}.internal/{apiVersion}', - (cloud_tpu_client._environment_discovery_url())) + (client._environment_discovery_url())) def testEnvironmentVarToNetworkEndpointsSingleIp(self): self.assertEqual( [{'ipAddress': '1.2.3.4', 'port': '1234'}], - list(cloud_tpu_client._environment_var_to_network_endpoints( + list(client._environment_var_to_network_endpoints( '1.2.3.4:1234'))) def testEnvironmentVarToNetworkEndpointsSingleGrpcAddress(self): self.assertEqual( [{'ipAddress': '1.2.3.4', 'port': '2000'}], list( - cloud_tpu_client._environment_var_to_network_endpoints( + client._environment_var_to_network_endpoints( 'grpc://1.2.3.4:2000'))) def testEnvironmentVarToNetworkEndpointsMultipleIps(self): @@ -105,47 +105,47 @@ class CloudTpuClientTest(test.TestCase): [{'ipAddress': '1.2.3.4', 'port': '2000'}, {'ipAddress': '5.6.7.8', 'port': '1234'}], list( - cloud_tpu_client._environment_var_to_network_endpoints( + client._environment_var_to_network_endpoints( '1.2.3.4:2000,5.6.7.8:1234'))) def testEnvironmentVarToNetworkEndpointsMultipleGrpcAddresses(self): self.assertEqual( [{'ipAddress': '1.2.3.4', 'port': '2000'}, {'ipAddress': '5.6.7.8', 'port': '1234'}], - list(cloud_tpu_client._environment_var_to_network_endpoints( + list(client._environment_var_to_network_endpoints( 'grpc://1.2.3.4:2000,grpc://5.6.7.8:1234'))) def testEnvironmentVarToNetworkEndpointsMissingPortAndMixed(self): self.assertEqual( [{'ipAddress': '1.2.3.4', 'port': '2000'}, {'ipAddress': '5.6.7.8', 'port': '8470'}], - list(cloud_tpu_client._environment_var_to_network_endpoints( + list(client._environment_var_to_network_endpoints( '1.2.3.4:2000,grpc://5.6.7.8'))) def testInitializeNoArguments(self): with self.assertRaisesRegex( ValueError, 'Please provide a TPU Name to connect to.'): - cloud_tpu_client.CloudTPUClient() + client.Client() def testInitializeMultiElementTpuArray(self): with self.assertRaisesRegex( NotImplementedError, 'Using multiple TPUs in a single session is not yet implemented'): - cloud_tpu_client.CloudTPUClient(tpu=['multiple', 'elements']) + client.Client(tpu=['multiple', 'elements']) - def assertClientContains(self, client): - self.assertEqual('tpu_name', client._tpu) - self.assertEqual(True, client._use_api) - self.assertEqual(None, client._credentials) - self.assertEqual('test-project', client._project) - self.assertEqual('us-central1-c', client._zone) - self.assertEqual(None, client._discovery_url) + def assertClientContains(self, c): + self.assertEqual('tpu_name', c._tpu) + self.assertEqual(True, c._use_api) + self.assertEqual(None, c._credentials) + self.assertEqual('test-project', c._project) + self.assertEqual('us-central1-c', c._zone) + self.assertEqual(None, c._discovery_url) self.assertEqual([{ 'ipAddress': '10.1.2.3', 'port': '8470' - }], client.network_endpoints()) + }], c.network_endpoints()) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testInitializeNoArgumentsWithEnvironmentVariable(self): os.environ['TPU_NAME'] = 'tpu_name' @@ -156,11 +156,11 @@ class CloudTpuClientTest(test.TestCase): 'health': 'HEALTHY' } } - client = cloud_tpu_client.CloudTPUClient( + c = client.Client( service=self.mock_service_client(tpu_map=tpu_map)) - self.assertClientContains(client) + self.assertClientContains(c) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testInitializeTpuName(self): tpu_map = { @@ -170,42 +170,42 @@ class CloudTpuClientTest(test.TestCase): 'health': 'HEALTHY' } } - client = cloud_tpu_client.CloudTPUClient( + c = client.Client( tpu='tpu_name', service=self.mock_service_client(tpu_map=tpu_map)) - self.assertClientContains(client) + self.assertClientContains(c) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testInitializeIpAddress(self): - client = cloud_tpu_client.CloudTPUClient(tpu='grpc://1.2.3.4:8470') - self.assertEqual('grpc://1.2.3.4:8470', client._tpu) - self.assertEqual(False, client._use_api) - self.assertEqual(None, client._service) - self.assertEqual(None, client._credentials) - self.assertEqual(None, client._project) - self.assertEqual(None, client._zone) - self.assertEqual(None, client._discovery_url) + c = client.Client(tpu='grpc://1.2.3.4:8470') + self.assertEqual('grpc://1.2.3.4:8470', c._tpu) + self.assertEqual(False, c._use_api) + self.assertEqual(None, c._service) + self.assertEqual(None, c._credentials) + self.assertEqual(None, c._project) + self.assertEqual(None, c._zone) + self.assertEqual(None, c._discovery_url) self.assertEqual([{ 'ipAddress': '1.2.3.4', 'port': '8470' - }], client.network_endpoints()) + }], c.network_endpoints()) def testInitializeWithoutMetadata(self): - client = cloud_tpu_client.CloudTPUClient( + c = client.Client( tpu='tpu_name', project='project', zone='zone') - self.assertEqual('tpu_name', client._tpu) - self.assertEqual(True, client._use_api) - self.assertEqual(None, client._service) - self.assertEqual(None, client._credentials) - self.assertEqual('project', client._project) - self.assertEqual('zone', client._zone) - self.assertEqual(None, client._discovery_url) + self.assertEqual('tpu_name', c._tpu) + self.assertEqual(True, c._use_api) + self.assertEqual(None, c._service) + self.assertEqual(None, c._credentials) + self.assertEqual('project', c._project) + self.assertEqual('zone', c._zone) + self.assertEqual(None, c._discovery_url) def testRecoverableNoApiAccess(self): - client = cloud_tpu_client.CloudTPUClient(tpu='grpc://1.2.3.4:8470') - self.assertEqual(True, client.recoverable()) + c = client.Client(tpu='grpc://1.2.3.4:8470') + self.assertEqual(True, c.recoverable()) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testRecoverableNoState(self): tpu_map = { @@ -214,11 +214,11 @@ class CloudTpuClientTest(test.TestCase): 'port': '8470', } } - client = cloud_tpu_client.CloudTPUClient( + c = client.Client( tpu='tpu_name', service=self.mock_service_client(tpu_map=tpu_map)) - self.assertEqual(True, client.recoverable()) + self.assertEqual(True, c.recoverable()) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testRecoverableReady(self): tpu_map = { @@ -228,11 +228,11 @@ class CloudTpuClientTest(test.TestCase): 'state': 'READY', } } - client = cloud_tpu_client.CloudTPUClient( + c = client.Client( tpu='tpu_name', service=self.mock_service_client(tpu_map=tpu_map)) - self.assertEqual(True, client.recoverable()) + self.assertEqual(True, c.recoverable()) - @mock.patch.object(cloud_tpu_client, '_request_compute_metadata', + @mock.patch.object(client, '_request_compute_metadata', mock_request_compute_metadata) def testRecoverablePreempted(self): tpu_map = { @@ -242,9 +242,17 @@ class CloudTpuClientTest(test.TestCase): 'state': 'PREEMPTED', } } - client = cloud_tpu_client.CloudTPUClient( + c = client.Client( tpu='tpu_name', service=self.mock_service_client(tpu_map=tpu_map)) - self.assertEqual(False, client.recoverable()) + self.assertEqual(False, c.recoverable()) + + def testHandlesByteStrings(self): + self.assertEqual( + client.Client( + tpu='tpu_name', zone='zone', project='project')._full_name(), + client.Client( + tpu=b'tpu_name', zone=b'zone', project=b'project')._full_name(), + ) if __name__ == '__main__': diff --git a/tensorflow/python/tpu/client/pip_package/BUILD b/tensorflow/python/tpu/client/pip_package/BUILD new file mode 100644 index 00000000000..9e979f1c31e --- /dev/null +++ b/tensorflow/python/tpu/client/pip_package/BUILD @@ -0,0 +1,15 @@ +# Description: +# Tools for building the Cloud TPU Client pip package. + +package(default_visibility = ["//visibility:private"]) + +licenses(["notice"]) # Apache 2.0 + +sh_binary( + name = "build_pip_package", + srcs = ["build_pip_package.sh"], + data = [ + "setup.py", + "//tensorflow/python/tpu/client:client_lib", + ], +) diff --git a/tensorflow/python/tpu/client/pip_package/README b/tensorflow/python/tpu/client/pip_package/README new file mode 100644 index 00000000000..301365c906e --- /dev/null +++ b/tensorflow/python/tpu/client/pip_package/README @@ -0,0 +1,3 @@ +Client responsible for communicating the Cloud TPU API. Released seperately from tensorflow. + +https://pypi.org/project/cloud-tpu-client/ \ No newline at end of file diff --git a/tensorflow/python/tpu/client/pip_package/build_pip_package.sh b/tensorflow/python/tpu/client/pip_package/build_pip_package.sh new file mode 100755 index 00000000000..0a90f429e8d --- /dev/null +++ b/tensorflow/python/tpu/client/pip_package/build_pip_package.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Copyright 2019 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. +# ============================================================================= + + +set -e + +if [ "$(uname)" = "Darwin" ]; then + sedi="sed -i ''" +else + sedi="sed -i" +fi + +PACKAGE_NAME="cloud_tpu_client" +PIP_PACKAGE="tensorflow/python/tpu/client/pip_package" +RUNFILES="bazel-bin/tensorflow/python/tpu/client/pip_package/build_pip_package.runfiles/org_tensorflow/tensorflow/python/tpu/client" + +function main() { + if [ $# -lt 1 ] ; then + echo "No destination dir provided" + exit 1 + fi + + DEST=$1 + TMPDIR=$(mktemp -d -t tmp.XXXXXXXXXX) + + echo $(date) : "=== Using tmpdir: ${TMPDIR}" + + cp ${PIP_PACKAGE}/README ${TMPDIR} + cp ${PIP_PACKAGE}/setup.py ${TMPDIR} + mkdir ${TMPDIR}/${PACKAGE_NAME} + cp -a ${RUNFILES}/. ${TMPDIR}/${PACKAGE_NAME}/ + + # Fix the import statements to reflect the copied over path. + find ${TMPDIR}/${PACKAGE_NAME} -name \*.py | + xargs $sedi -e ' + s/^from tensorflow.python.tpu.client/from '${PACKAGE_NAME}'/ + ' + echo $(ls $TMPDIR) + + pushd ${TMPDIR} + echo $(date) : "=== Building wheel" + echo $(pwd) + python setup.py bdist_wheel >/dev/null + python3 setup.py bdist_wheel >/dev/null + mkdir -p ${DEST} + cp dist/* ${DEST} + popd + rm -rf ${TMPDIR} + echo $(date) : "=== Output wheel file is in: ${DEST}" +} + +main "$@" diff --git a/tensorflow/python/tpu/client/pip_package/setup.py b/tensorflow/python/tpu/client/pip_package/setup.py new file mode 100644 index 00000000000..9b8593615db --- /dev/null +++ b/tensorflow/python/tpu/client/pip_package/setup.py @@ -0,0 +1,56 @@ +# Copyright 2019 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. +# ============================================================================= +"""Cloud TPU Client package.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from cloud_tpu_client.version import __version__ +from setuptools import find_packages +from setuptools import setup + +setup( + name='cloud-tpu-client', + version=__version__.replace('-', ''), + description='Client for using Cloud TPUs', + long_description='Client for using Cloud TPUs', + url='https://cloud.google.com/tpu/', + author='Google Inc.', + author_email='packages@tensorflow.org', + packages=find_packages(), + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Mathematics', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'Topic :: Software Development', + 'Topic :: Software Development :: Libraries', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + license='Apache 2.0', + keywords='tensorflow tpu', + install_requires=['google-api-python-client', 'oauth2client'] +) diff --git a/tensorflow/python/tpu/client/version.py b/tensorflow/python/tpu/client/version.py new file mode 100644 index 00000000000..f9cc53c8906 --- /dev/null +++ b/tensorflow/python/tpu/client/version.py @@ -0,0 +1,21 @@ +# Copyright 2019 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. +# ============================================================================= +"""Cloud TPU Client version information.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__version__ = "0.2" diff --git a/tensorflow/python/tpu/profiler/BUILD b/tensorflow/python/tpu/profiler/BUILD index 157bd930ada..eb77e5de742 100644 --- a/tensorflow/python/tpu/profiler/BUILD +++ b/tensorflow/python/tpu/profiler/BUILD @@ -48,7 +48,7 @@ py_binary( name = "capture_tpu_profile_bin", srcs = ["capture_tpu_profile.py"], main = "capture_tpu_profile.py", - python_version = "PY2", + python_version = "PY3", deps = [ ":capture_tpu_profile_lib", "@absl_py//absl/flags", diff --git a/tensorflow/python/tpu/session_support.py b/tensorflow/python/tpu/session_support.py index 48a3e5f4003..650e2f29305 100644 --- a/tensorflow/python/tpu/session_support.py +++ b/tensorflow/python/tpu/session_support.py @@ -222,7 +222,7 @@ class WatchdogManager(threading.Thread): self._session = None self._worker_manager = None - def _reset_manager(self): + def _reset_manager(self, stopping=False): """Reset the graph, session and worker manager.""" self._graph = ops.Graph() self._session = session_lib.Session( @@ -238,11 +238,17 @@ class WatchdogManager(threading.Thread): self._worker_manager = WorkerHeartbeatManager.from_devices( self._session, self._devices) + if stopping: + timeout_ms = -1 + shutdown_mode = event_pb2.NOT_CONFIGURED + else: + timeout_ms = self.shutdown_timeout * 1000 + shutdown_mode = event_pb2.WAIT_FOR_COORDINATOR + self._worker_manager.configure( event_pb2.WorkerHeartbeatRequest( - watchdog_config=event_pb2.WatchdogConfig( - timeout_ms=self.shutdown_timeout * 1000,), - shutdown_mode=event_pb2.WAIT_FOR_COORDINATOR)) + watchdog_config=event_pb2.WatchdogConfig(timeout_ms=timeout_ms), + shutdown_mode=shutdown_mode)) def configure_and_run(self): logging.info( @@ -255,10 +261,7 @@ class WatchdogManager(threading.Thread): def stop(self): logging.info('Stopping worker watchdog.') - self._worker_manager.configure( - event_pb2.WorkerHeartbeatRequest( - watchdog_config=event_pb2.WatchdogConfig(timeout_ms=-1,), - shutdown_mode=event_pb2.NOT_CONFIGURED)) + self._reset_manager(stopping=True) self._running = False self.join() @@ -296,6 +299,14 @@ def start_worker_watchdog(session, _WATCHDOG.configure_and_run() +def stop_worker_watchdog(): + """Stop global worker watchdog.""" + global _WATCHDOG + if _WATCHDOG is not None: + _WATCHDOG.stop() + _WATCHDOG = None + + class GracefulShutdownHook(session_run_hook.SessionRunHook): """Session hook that watches for shutdown events. diff --git a/tensorflow/python/tpu/tensor_tracer.py b/tensorflow/python/tpu/tensor_tracer.py index aa8c8dc01f1..8db25b3d10a 100644 --- a/tensorflow/python/tpu/tensor_tracer.py +++ b/tensorflow/python/tpu/tensor_tracer.py @@ -288,8 +288,7 @@ class TensorTracer(object): Raises: ValueError: If the given trace mode is not supported for the device. """ - if trace_mode in (tensor_tracer_flags.TRACE_MODE_SUMMARY, - tensor_tracer_flags.TRACE_MODE_FULL_TENSOR_SUMMARY): + if trace_mode == tensor_tracer_flags.TRACE_MODE_FULL_TENSOR_SUMMARY: if device_type != _DEVICE_TYPE_TPU: raise ValueError('Device_type "%s" is not yet supported for ' 'trace mode "%s"' % (device_type, trace_mode)) @@ -1344,9 +1343,12 @@ class TensorTracer(object): # Expand 1 more dimension so that it will match with the expected # structure num_cores x num_traced_tensors x num_signatures. value = array_ops.expand_dims(transposed_signatures, axis=0) - summary_write_ops.append(summary.write( - _TT_SUMMARY_TAG + '/' + key, value, metadata=summary_metadata, - step=step[0])) + + with ops.control_dependencies( + summary.summary_writer_initializer_op()): + summary_write_ops.append(summary.write( + _TT_SUMMARY_TAG + '/' + key, value, metadata=summary_metadata, + step=step[0])) return control_flow_ops.group(summary_write_ops) step = array_ops.reshape(training_util.get_or_create_global_step(), [1]) @@ -1547,8 +1549,14 @@ class TensorTracer(object): processed_t_fetches = control_flow_ops.tuple(processed_t_fetches, control_inputs=tracing_ops) if self._use_tensor_values_cache() or self._use_tensor_buffer(): - if self._create_host_call() and on_tpu: + if self._create_host_call(): self._prepare_host_call_fn(processed_t_fetches, op_fetches) + if not on_tpu: + write_cache, caches_to_write = self._host_call_fn[_TT_HOSTCALL_KEY] + cache_write_op = write_cache(**caches_to_write) + processed_t_fetches = control_flow_ops.tuple( + processed_t_fetches, control_inputs=[cache_write_op]) + del self._host_call_fn[_TT_HOSTCALL_KEY] else: processed_t_fetches = self._flush_tensor_values_cache( processed_t_fetches, op_fetches, on_tpu=on_tpu) diff --git a/tensorflow/python/tpu/tensor_tracer_report.py b/tensorflow/python/tpu/tensor_tracer_report.py index 1ac696418d7..e8a122d981f 100644 --- a/tensorflow/python/tpu/tensor_tracer_report.py +++ b/tensorflow/python/tpu/tensor_tracer_report.py @@ -111,7 +111,7 @@ def topological_sort(g): if op_in_degree[consumer] < 0: raise ValueError('consumer:%s degree mismatch'%consumer.name) - left_ops = set([op for (op, degree) in op_in_degree.items() if degree > 0]) + left_ops = set(op for (op, degree) in op_in_degree.items() if degree > 0) if left_ops: return (True, left_ops) else: diff --git a/tensorflow/python/tpu/tpu.py b/tensorflow/python/tpu/tpu.py index 2af31a9dd58..b20e1a6d6f8 100644 --- a/tensorflow/python/tpu/tpu.py +++ b/tensorflow/python/tpu/tpu.py @@ -321,8 +321,8 @@ class TPUReplicateContext(control_flow_ops.XLAControlFlowContext): def report_unsupported_operations(self): if self._unsupported_ops: - op_str = "\n".join([" %s (%s)" % (op.type, op.name) - for op in self._unsupported_ops[:_MAX_WARNING_LINES]]) + op_str = "\n".join(" %s (%s)" % (op.type, op.name) + for op in self._unsupported_ops[:_MAX_WARNING_LINES]) logging.warning("%d unsupported operations found: \n%s", len(self._unsupported_ops), op_str) if len(self._unsupported_ops) > _MAX_WARNING_LINES: @@ -334,7 +334,7 @@ class TPUReplicateContext(control_flow_ops.XLAControlFlowContext): self._gradient_colocation_stack.append(op) if not self._outside_compilation_cluster: try: - outside_attr = op.get_attr(_OUTSIDE_COMPILATION_ATTR) + outside_attr = op.get_attr(_OUTSIDE_COMPILATION_ATTR).decode("ascii") if self._in_gradient_colocation: raise NotImplementedError( "Cannot nest gradient colocation operations outside compilation" @@ -1200,7 +1200,7 @@ def split_compile_and_replicate(computation, if host_compute_core: attr_value = attr_value_pb2.AttrValue() - attr_value.list.s.extend([compat.as_bytes(x) for x in host_compute_core]) + attr_value.list.s.extend(compat.as_bytes(x) for x in host_compute_core) metadata._set_attr("host_compute_core", attr_value) # pylint: disable=protected-access with ops.control_dependencies([metadata]): @@ -1727,20 +1727,22 @@ def under_tpu_inference_context(): class _TPUInferenceContext(control_flow_ops.XLAControlFlowContext): """A `ControlFlowContext` for nodes inside a TPU inference computation. - The primary role of `TPUReplicateContext` is to sanity check operators inside - a tpu.rewrite_for_inference() computation. + The primary role of `_TPUInferenceContext` is to indicate the mode of + operation and possibly sanity check operators inside a + tpu.rewrite_for_inference() computation. """ - def __init__(self, name): + def __init__(self, name, check_ops=True): super(_TPUInferenceContext, self).__init__() self._name = name + self._check_ops = check_ops def AddOp(self, op): self._AddOpInternal(op) def _AddOpInternal(self, op): # pylint: disable=protected-access - if op.type in _BLACKLISTED_INFERENCE_OPS: + if self._check_ops and op.type in _BLACKLISTED_INFERENCE_OPS: raise NotImplementedError( "Operation of type %s (%s) is not supported on the TPU for inference." " Execution will fail if this op is used in the graph. Make sure your" diff --git a/tensorflow/python/tpu/tpu_embedding.py b/tensorflow/python/tpu/tpu_embedding.py index d6e77815041..130f4d1fe2f 100644 --- a/tensorflow/python/tpu/tpu_embedding.py +++ b/tensorflow/python/tpu/tpu_embedding.py @@ -967,10 +967,9 @@ class TPUEmbedding(object): i, feature)) if enqueue_data.sample_indices is None and combiner: - raise ValueError('`enqueue_datas_list[{}]` has a feature that has ' - 'neither `EnqueueData` or `combiner`.' - '`feature`: {}, combiner: {}.'.format( - i, feature, combiner)) + logging.warn('No sample indices set for features %f table %f but ' + 'combiner is set to %s.', feature, + self._feature_to_config_dict[feature].table_id, combiner) if (enqueue_data.sample_indices is not None and enqueue_data.sample_indices.device != diff --git a/tensorflow/python/tpu/tpu_system_metadata.py b/tensorflow/python/tpu/tpu_system_metadata.py index 1998e0e0aeb..e7f9b79bbd3 100644 --- a/tensorflow/python/tpu/tpu_system_metadata.py +++ b/tensorflow/python/tpu/tpu_system_metadata.py @@ -199,7 +199,7 @@ def master_job(master, cluster_def): if (not cluster_def or not cluster_def.job): return _DEFAULT_JOB_NAME - job_names = set([job.name for job in cluster_def.job]) + job_names = set(job.name for job in cluster_def.job) if _DEFAULT_JOB_NAME in job_names: # b/37868888 tracks allowing ClusterSpec propagation to reuse job names. raise ValueError('Currently, tpu_worker is not an allowed job name.') diff --git a/tensorflow/python/training/adam.py b/tensorflow/python/training/adam.py index 9ae86bbbe72..615ac587c21 100644 --- a/tensorflow/python/training/adam.py +++ b/tensorflow/python/training/adam.py @@ -57,11 +57,13 @@ class AdamOptimizer(optimizer.Optimizer): described at the end of section 2 of the paper: $$t := t + 1$$ - $$lr_t := \text{learning\_rate} * \sqrt{1 - beta_2^t} / (1 - beta_1^t)$$ + $$\text{lr}_t := \mathrm{learning_rate} * + \sqrt{1 - \beta_2^t} / (1 - \beta_1^t)$$ - $$m_t := beta_1 * m_{t-1} + (1 - beta_1) * g$$ - $$v_t := beta_2 * v_{t-1} + (1 - beta_2) * g * g$$ - $$variable := variable - lr_t * m_t / (\sqrt{v_t} + \epsilon)$$ + $$m_t := \beta_1 * m_{t-1} + (1 - \beta_1) * g$$ + $$v_t := \beta_2 * v_{t-1} + (1 - \beta_2) * g * g$$ + $$\text{variable} := \text{variable} - + \text{lr}_t * m_t / (\sqrt{v_t} + \epsilon)$$ The default value of 1e-8 for epsilon might not be a good default in general. For example, when training an Inception network on ImageNet a diff --git a/tensorflow/python/training/checkpoint_utils.py b/tensorflow/python/training/checkpoint_utils.py index 0a7f4d6e5f7..64ba099ce79 100644 --- a/tensorflow/python/training/checkpoint_utils.py +++ b/tensorflow/python/training/checkpoint_utils.py @@ -330,7 +330,7 @@ def _init_from_checkpoint(ckpt_dir_or_file, assignment_map): )) var_name = var.name else: - var_name = ",".join([v.name for v in var]) + var_name = ",".join(v.name for v in var) _set_variable_or_list_initializer(var, ckpt_file, tensor_name_in_ckpt) logging.debug("Initialize variable %s from checkpoint %s with %s", var_name, ckpt_dir_or_file, tensor_name_in_ckpt) diff --git a/tensorflow/python/training/experimental/loss_scaling_gradient_tape.py b/tensorflow/python/training/experimental/loss_scaling_gradient_tape.py index caae7052b84..0afe4c78caf 100644 --- a/tensorflow/python/training/experimental/loss_scaling_gradient_tape.py +++ b/tensorflow/python/training/experimental/loss_scaling_gradient_tape.py @@ -25,10 +25,9 @@ from tensorflow.python.ops import math_ops from tensorflow.python.ops.unconnected_gradients import UnconnectedGradients from tensorflow.python.training.experimental import loss_scale as loss_scale_module from tensorflow.python.util import nest -from tensorflow.python.util.tf_export import tf_export -@tf_export("mixed_precision.experimental.LossScaleGradientTape", v1=[]) +# TODO(reedwm): Expose this. Currently it doesn't work with DistributionStrategy class LossScaleGradientTape(backprop.GradientTape): """A gradient tape that scales losses and unscales resulting gradients. diff --git a/tensorflow/python/training/input_test.py b/tensorflow/python/training/input_test.py index b82a2611d54..e874aaa3fa8 100644 --- a/tensorflow/python/training/input_test.py +++ b/tensorflow/python/training/input_test.py @@ -401,7 +401,7 @@ class SliceInputProducerTest(test_lib.TestCase): frequency[e] = 0 for _ in range(num_epochs): output = [self.evaluate(slices) for _ in range(len(source_strings))] - key = b",".join([s + compat.as_bytes(str(i)) for s, i in output]) + key = b",".join(s + compat.as_bytes(str(i)) for s, i in output) self.assertIn(key, expected) frequency[key] += 1 @@ -1083,7 +1083,7 @@ class BatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), batch_size) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) self.assertAllEqual([99] * len(which_b), [results[0][i] for i in which_b]) @@ -1185,7 +1185,7 @@ class BatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), batch_size) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) self.assertAllEqual([99] * len(which_b), [results[0][i] for i in which_b]) @@ -1271,7 +1271,7 @@ class BatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), batch_size) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) self.assertAllEqual([99] * len(which_b), [results[0][i] for i in which_b]) @@ -1291,7 +1291,7 @@ class BatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), 2 * extra_elements) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) # We'd like to see some minimum level of mixing of the results of both @@ -1369,7 +1369,7 @@ class BatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), batch_size) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) self.assertAllEqual([99] * len(which_b), [results[0][i] for i in which_b]) @@ -1389,7 +1389,7 @@ class BatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), 2 * extra_elements) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) # We'd like to see some minimum level of mixing of the results of both @@ -2099,7 +2099,7 @@ class ShuffleBatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), batch_size) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) self.assertAllEqual([99] * len(which_b), [results[0][i] for i in which_b]) @@ -2194,7 +2194,7 @@ class ShuffleBatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), batch_size) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) self.assertAllEqual([99] * len(which_b), [results[0][i] for i in which_b]) @@ -2213,7 +2213,7 @@ class ShuffleBatchJoinTest(test_lib.TestCase): self.assertEqual(len(which_a) + len(which_b), 2 * extra_elements) if which_a and which_b: saw_both += 1 - all_a.extend([results[0][i] for i in which_a]) + all_a.extend(results[0][i] for i in which_a) seen_b += len(which_b) # Some minimum level of mixing of the results of both threads. diff --git a/tensorflow/python/training/moving_averages.py b/tensorflow/python/training/moving_averages.py index afd7a040db9..cc5bcbb9574 100644 --- a/tensorflow/python/training/moving_averages.py +++ b/tensorflow/python/training/moving_averages.py @@ -554,7 +554,7 @@ class ExponentialMovingAverage(object): for v in moving_avg_variables: name_map[self.average_name(v)] = v # Make sure we restore variables without moving averages as well. - moving_avg_variable_names = set([v.name for v in moving_avg_variables]) + moving_avg_variable_names = set(v.name for v in moving_avg_variables) for v in list(set(variables.global_variables())): if v.name not in moving_avg_variable_names and v.op.name not in name_map: name_map[v.op.name] = v diff --git a/tensorflow/python/training/saving/BUILD b/tensorflow/python/training/saving/BUILD index 83913ddd06f..a8f595f3ac6 100644 --- a/tensorflow/python/training/saving/BUILD +++ b/tensorflow/python/training/saving/BUILD @@ -30,7 +30,7 @@ cuda_py_test( srcs = [ "functional_saver_test.py", ], - additional_deps = [ + deps = [ ":functional_saver", ":saveable_hook", "//tensorflow/python/eager:test", diff --git a/tensorflow/python/training/tracking/BUILD b/tensorflow/python/training/tracking/BUILD index 1b266226268..3d646075426 100644 --- a/tensorflow/python/training/tracking/BUILD +++ b/tensorflow/python/training/tracking/BUILD @@ -39,7 +39,7 @@ py_library( tf_py_test( name = "base_test", srcs = ["base_test.py"], - additional_deps = [ + deps = [ ":base", "//tensorflow/python:client_testlib", ], @@ -58,7 +58,7 @@ py_library( tf_py_test( name = "tracking_test", srcs = ["tracking_test.py"], - additional_deps = [ + deps = [ ":base", ":tracking", "//tensorflow/python:client_testlib", @@ -86,7 +86,11 @@ py_library( tf_py_test( name = "data_structures_test", srcs = ["data_structures_test.py"], - additional_deps = [ + tags = [ + "no_windows", + "nomac", + ], + deps = [ ":data_structures", "//tensorflow/python:array_ops", "//tensorflow/python:framework_test_lib", @@ -97,10 +101,6 @@ tf_py_test( "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers", ], - tags = [ - "no_windows", - "nomac", - ], ) py_library( @@ -159,14 +159,12 @@ py_library( tf_py_test( name = "util_test", srcs = ["util_test.py"], - additional_deps = [ + tags = ["notsan"], # b/74395663 + deps = [ ":base", ":graph_view", ":tracking", ":util", - "@absl_py//absl/testing:parameterized", - "@six_archive//:six", - "//tensorflow/python/keras/optimizer_v2", "//tensorflow/python:checkpoint_management", "//tensorflow/python:client_testlib", "//tensorflow/python:constant_op", @@ -184,6 +182,7 @@ tf_py_test( "//tensorflow/python:template", "//tensorflow/python:training_util", "//tensorflow/python:variable_scope", + "//tensorflow/python:variables", "//tensorflow/python/eager:backprop", "//tensorflow/python/eager:context", "//tensorflow/python/eager:def_function", @@ -191,14 +190,16 @@ tf_py_test( "//tensorflow/python/keras:backend", "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers", - "//tensorflow/python:variables", + "//tensorflow/python/keras/optimizer_v2", + "@absl_py//absl/testing:parameterized", + "@six_archive//:six", ], - tags = ["notsan"], # b/74395663 ) tf_xla_py_test( name = "util_xla_test", srcs = ["util_xla_test.py"], + python_version = "PY3", tags = [ "no_pip", "no_windows", @@ -223,13 +224,14 @@ tf_xla_py_test( tf_py_test( name = "util_with_v1_optimizers_test", srcs = ["util_with_v1_optimizers_test.py"], - additional_deps = [ + tags = [ + "notsan", # b/74395663 + ], + deps = [ ":base", ":graph_view", ":tracking", ":util", - "@absl_py//absl/testing:parameterized", - "@six_archive//:six", "//tensorflow/python:checkpoint_management", "//tensorflow/python:constant_op", "//tensorflow/python:control_flow_ops", @@ -253,9 +255,8 @@ tf_py_test( "//tensorflow/python/eager:test", "//tensorflow/python/keras:engine", "//tensorflow/python/keras:layers", - ], - tags = [ - "notsan", # b/74395663 + "@absl_py//absl/testing:parameterized", + "@six_archive//:six", ], ) @@ -271,22 +272,22 @@ py_library( tf_py_test( name = "python_state_test", srcs = ["python_state_test.py"], - additional_deps = [ + deps = [ ":base", ":util", - "//tensorflow/python/module", "//tensorflow/python:client_testlib", "//tensorflow/python:framework_test_lib", + "//tensorflow/python/module", ], ) tf_py_test( name = "benchmarks_test", srcs = ["benchmarks_test.py"], - additional_deps = [ + deps = [ ":util", - "//tensorflow/python:platform_test", "//tensorflow/python:framework_ops", + "//tensorflow/python:platform_test", ], ) diff --git a/tensorflow/python/util/all_util.py b/tensorflow/python/util/all_util.py index 50d480f8707..f2e4499b64b 100644 --- a/tensorflow/python/util/all_util.py +++ b/tensorflow/python/util/all_util.py @@ -46,8 +46,8 @@ def make_all(module_name, doc_string_modules=None): """ if doc_string_modules is None: doc_string_modules = [_sys.modules[module_name]] - cur_members = set([name for name, _ - in _tf_inspect.getmembers(_sys.modules[module_name])]) + cur_members = set( + name for name, _ in _tf_inspect.getmembers(_sys.modules[module_name])) results = set() for doc_module in doc_string_modules: diff --git a/tensorflow/python/util/object_identity.py b/tensorflow/python/util/object_identity.py index a5ad1e77245..37f24c4831f 100644 --- a/tensorflow/python/util/object_identity.py +++ b/tensorflow/python/util/object_identity.py @@ -172,7 +172,7 @@ class ObjectIdentitySet(collections_abc.MutableSet): """Like the built-in set, but compares objects with "is".""" def __init__(self, *args): - self._storage = set([self._wrap_key(obj) for obj in list(*args)]) + self._storage = set(self._wrap_key(obj) for obj in list(*args)) @staticmethod def _from_storage(storage): diff --git a/tensorflow/security/advisory/tfsa-2019-002.md b/tensorflow/security/advisory/tfsa-2019-002.md new file mode 100644 index 00000000000..74760c11f64 --- /dev/null +++ b/tensorflow/security/advisory/tfsa-2019-002.md @@ -0,0 +1,33 @@ +## TFSA-2019-002: Heap buffer overflow in `UnsortedSegmentSum` + +### CVE Number + +CVE-2019-16778 + +### Issue Description + +A heap buffer overflow in `UnsortedSegmentSum` can be produced when the `Index` +template argument is `int32`. In this case `data_size` and `num_segments` fields +are truncated from `int64` to `int32` and can produce negative numbers, +resulting in accessing out of bounds heap memory. + +### Impact + +This is unlikely to be exploitable and was detected and fixed internally. We are +making the security advisory only to notify users that it is better to update to +TensorFlow 1.15 or 2.0 or later as these versions already have this fixed. + +### Vulnerable Versions + +TensorFlow 1.0.0, 1.0.1, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, 1.4.0, 1.4.1, 1.5.0, +1.5.1, 1.6.0, 1.7.0, 1.7.1, 1.8.0, 1.9.0, 1.10.0, 1.10.1, 1.11.0, 1.12.0, +1.12.1, 1.12.2, 1.12.3, 1.13.0, 1.13.1, 1.13.2, 1.14.0. + +### Mitigation + +We have patched the vulnerability in GitHub commit +[db4f971](https://github.com/tensorflow/tensorflow/commit/db4f9717c41bccc3ce10099ab61996b246099892). + +We encourage users to switch to TensorFlow 1.15 or 2.0 as these versions contain +the fix. If switching is undesirable, consider cherry-picking the above commit +and building from source. diff --git a/tensorflow/security/index.md b/tensorflow/security/index.md index e28f8ff0f87..2a496c2f087 100644 --- a/tensorflow/security/index.md +++ b/tensorflow/security/index.md @@ -8,6 +8,7 @@ in [SECURITY.md](https://github.com/tensorflow/tensorflow/blob/master/SECURITY.m | Advisory Number | Type | Versions affected | Reported by | Additional Information | |-----------------|--------------------|:-----------------:|-----------------------|-----------------------------| +| [TFSA-2019-002](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/security/advisory/tfsa-2019-002.md) | Heap buffer overflow in `UnsortedSegmentSum` | <= 1.14 | (found internally) | | | [TFSA-2019-001](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/security/advisory/tfsa-2019-001.md) | Null Pointer Dereference Error in Decoding GIF Files | <= 1.12 | Baidu Security Lab | | | [TFSA-2018-006](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/security/advisory/tfsa-2018-006.md) | Crafted Configuration File results in Invalid Memory Access | <= 1.7 | Blade Team of Tencent | | | [TFSA-2018-005](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/security/advisory/tfsa-2018-005.md) | Old Snappy Library Usage Resulting in Memcpy Parameter Overlap | <= 1.7 | Blade Team of Tencent | | diff --git a/tensorflow/stream_executor/cuda/BUILD b/tensorflow/stream_executor/cuda/BUILD index e3f55720048..08c6686a3c0 100644 --- a/tensorflow/stream_executor/cuda/BUILD +++ b/tensorflow/stream_executor/cuda/BUILD @@ -11,7 +11,7 @@ load( ) load("//tensorflow:tensorflow.bzl", "tf_copts") load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load( diff --git a/tensorflow/stream_executor/device_memory_allocator.h b/tensorflow/stream_executor/device_memory_allocator.h index fa5c4d3abfc..983274816a1 100644 --- a/tensorflow/stream_executor/device_memory_allocator.h +++ b/tensorflow/stream_executor/device_memory_allocator.h @@ -252,9 +252,10 @@ class StreamExecutorMemoryAllocator : public DeviceMemoryAllocator { // stream executor. port::StatusOr GetStream(int device_ordinal) override; - private: + // Gets the stream executor for given device ordinal. port::StatusOr GetStreamExecutor(int device_ordinal) const; + private: // Available stream executors. Each stream executor has a different device // ordinal. std::vector stream_executors_; diff --git a/tensorflow/stream_executor/gpu/BUILD b/tensorflow/stream_executor/gpu/BUILD index d2a79579fb2..70ebfd14bb5 100644 --- a/tensorflow/stream_executor/gpu/BUILD +++ b/tensorflow/stream_executor/gpu/BUILD @@ -6,7 +6,7 @@ load( "if_gpu_is_configured", ) load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load("//tensorflow:tensorflow.bzl", "tf_copts") diff --git a/tensorflow/stream_executor/platform/BUILD b/tensorflow/stream_executor/platform/BUILD index f9540db0103..e2ada9d387e 100644 --- a/tensorflow/stream_executor/platform/BUILD +++ b/tensorflow/stream_executor/platform/BUILD @@ -34,11 +34,3 @@ cc_library( "//tensorflow/stream_executor/platform/default:dso_loader", ], ) - -filegroup( - name = "c_srcs", - data = glob([ - "**/*.cc", - "**/*.h", - ]), -) diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl index 3adf5fe9a4e..61aad4dd8ee 100644 --- a/tensorflow/tensorflow.bzl +++ b/tensorflow/tensorflow.bzl @@ -16,7 +16,7 @@ load( "if_tensorrt", ) load( - "//tensorflow/core/platform:default/cuda_build_defs.bzl", + "//tensorflow/core/platform/default:cuda_build_defs.bzl", "if_cuda_is_configured", ) load( @@ -473,7 +473,8 @@ def tf_shared_library_deps(): def tf_binary_dynamic_kernel_dsos(): return if_dynamic_kernels( extra_deps = [ - "//tensorflow/core/kernels:libtfkernel_all_kernels.so", + # TODO(gunan): Remove dependencies on these, and make them load dynamically. + # "//tensorflow/core/kernels:libtfkernel_all_kernels.so", ], otherwise = [], ) @@ -1176,7 +1177,8 @@ register_extension_info( label_regex_for_dep = "{extension_name}_gpu", ) -# Create a cc_test for each of the tensorflow tests listed in "tests" +# Create a cc_test for each of the tensorflow tests listed in "tests", along +# with a test suite of the given name, if provided. def tf_cc_tests( srcs, deps, @@ -1186,10 +1188,14 @@ def tf_cc_tests( size = "medium", args = None, linkopts = [], - kernels = []): + kernels = [], + create_named_test_suite = False, + visibility = None): + test_names = [] for src in srcs: + test_name = src_to_test_name(src) tf_cc_test( - name = src_to_test_name(src), + name = test_name, size = size, srcs = [src], args = args, @@ -1198,6 +1204,17 @@ def tf_cc_tests( linkstatic = linkstatic, tags = tags, deps = deps, + visibility = visibility, + ) + test_names.append(test_name) + + # Add a test suite with the generated tests if a name was provided and + # it does not conflict any of the test names. + if create_named_test_suite: + native.test_suite( + name = name, + tests = test_names, + visibility = visibility, ) def tf_cc_test_mkl( @@ -2048,9 +2065,9 @@ def py_test(deps = [], data = [], kernels = [], **kwargs): clean_dep("//tensorflow:no_tensorflow_py_deps"): [], }), data = data + select({ - "//conditions:default": [], + "//conditions:default": kernels, clean_dep("//tensorflow:no_tensorflow_py_deps"): ["//tensorflow/tools/pip_package:win_pip_package_marker"], - }) + tf_binary_dynamic_kernel_dsos(), + }), exec_compatible_with = tf_exec_compatible_with(kwargs), **kwargs ) @@ -2094,7 +2111,6 @@ def tf_py_test( args = [], tags = [], shard_count = 1, - additional_deps = [], additional_visibility = [], kernels = [], flaky = 0, @@ -2104,6 +2120,7 @@ def tf_py_test( **kwargs): """Create one or more python tests with extra tensorflow dependencies.""" xla_test_true_list = [] + additional_deps = kwargs.pop("additional_deps", []) + kwargs.pop("deps", []) # xla_enable_strict_auto_jit is used to run Tensorflow unit tests with all XLA compilable # kernels compiled with XLA. @@ -2139,7 +2156,7 @@ def tf_py_test( register_extension_info( extension_name = "tf_py_test", - label_regex_map = {"additional_deps": "deps:{extension_name}"}, + label_regex_map = {"deps": "deps:{extension_name}"}, ) def gpu_py_test( @@ -2150,7 +2167,6 @@ def gpu_py_test( main = None, args = [], shard_count = 1, - additional_deps = [], kernels = [], tags = [], flaky = 0, @@ -2163,6 +2179,7 @@ def gpu_py_test( _ignored = [xla_enable_strict_auto_jit] if main == None: main = name + ".py" + additional_deps = kwargs.pop("additional_deps", []) + kwargs.pop("deps", []) for config in ["cpu", "gpu"]: test_name = name test_tags = tags @@ -2189,7 +2206,7 @@ def gpu_py_test( register_extension_info( extension_name = "gpu_py_test", - label_regex_map = {"additional_deps": "additional_deps:{extension_name}"}, + label_regex_map = {"deps": "deps:{extension_name}"}, ) # terminology changes: saving cuda_* definition for compatibility @@ -2198,7 +2215,7 @@ def cuda_py_test(*args, **kwargs): register_extension_info( extension_name = "cuda_py_test", - label_regex_map = {"additional_deps": "additional_deps:{extension_name}"}, + label_regex_map = {"deps": "deps:{extension_name}"}, ) def sycl_py_test( @@ -2209,13 +2226,14 @@ def sycl_py_test( main = None, args = [], shard_count = 1, - additional_deps = [], kernels = [], tags = [], flaky = 0, xla_enabled = False, - grpc_enabled = False): + grpc_enabled = False, + **kwargs): test_tags = tags + tf_sycl_tests_tags() + additional_deps = kwargs.pop("additional_deps", []) + kwargs.pop("deps", []) tf_py_test( name = name, size = size, @@ -2230,18 +2248,18 @@ def sycl_py_test( shard_count = shard_count, tags = test_tags, xla_enabled = xla_enabled, + **kwargs ) register_extension_info( extension_name = "sycl_py_test", - label_regex_map = {"additional_deps": "additional_deps:{extension_name}"}, + label_regex_map = {"deps": "deps:{extension_name}"}, ) def py_tests( name, srcs, size = "medium", - additional_deps = [], kernels = [], data = [], tags = [], @@ -2251,6 +2269,7 @@ def py_tests( xla_enabled = False, grpc_enabled = False, **kwargs): + additional_deps = kwargs.pop("additional_deps", []) + kwargs.pop("deps", []) for src in srcs: test_name = src.split("/")[-1].split(".")[0] if prefix: @@ -2275,7 +2294,6 @@ def gpu_py_tests( name, srcs, size = "medium", - additional_deps = [], kernels = [], data = [], shard_count = 1, @@ -2289,6 +2307,7 @@ def gpu_py_tests( # XLA tests once enough compute resources are available. _ignored = [xla_enable_strict_auto_jit] test_tags = tags + tf_gpu_tests_tags() + additional_deps = kwargs.pop("additional_deps", []) + kwargs.pop("deps", []) py_tests( name = name, size = size, @@ -2379,7 +2398,7 @@ def tf_py_build_info_genrule(name, out, **kwargs): " --is_config_rocm " + if_rocm("True", "False") + " --key_value " + if_cuda(" cuda_version_number=$${TF_CUDA_VERSION:-} cudnn_version_number=$${TF_CUDNN_VERSION:-} ", "") + - if_windows(" msvcp_dll_name=msvcp140.dll ", "") + + if_windows(" msvcp_dll_names=msvcp140.dll,msvcp140_1.dll ", "") + if_windows_cuda(" ".join([ "nvcuda_dll_name=nvcuda.dll", "cudart_dll_name=cudart64_$$(echo $${TF_CUDA_VERSION:-} | sed \"s/\\.//\").dll", diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-tridiag.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-tridiag.pbtxt new file mode 100644 index 00000000000..0609904bbb3 --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.-linear-operator-tridiag.pbtxt @@ -0,0 +1,185 @@ +path: "tensorflow.linalg.LinearOperatorTridiag" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "H" + mtype: "" + } + member { + name: "batch_shape" + mtype: "" + } + member { + name: "diagonals" + mtype: "" + } + member { + name: "diagonals_format" + mtype: "" + } + member { + name: "domain_dimension" + mtype: "" + } + member { + name: "dtype" + mtype: "" + } + member { + name: "graph_parents" + mtype: "" + } + member { + name: "is_non_singular" + mtype: "" + } + member { + name: "is_positive_definite" + mtype: "" + } + member { + name: "is_self_adjoint" + mtype: "" + } + member { + name: "is_square" + mtype: "" + } + member { + name: "name" + mtype: "" + } + member { + name: "name_scope" + mtype: "" + } + member { + name: "range_dimension" + mtype: "" + } + member { + name: "shape" + mtype: "" + } + member { + name: "submodules" + mtype: "" + } + member { + name: "tensor_rank" + mtype: "" + } + member { + name: "trainable_variables" + mtype: "" + } + member { + name: "variables" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'diagonals\', \'diagonals_format\', \'is_non_singular\', \'is_self_adjoint\', \'is_positive_definite\', \'is_square\', \'name\'], varargs=None, keywords=None, defaults=[\'compact\', \'None\', \'None\', \'None\', \'None\', \'LinearOperatorTridiag\'], " + } + member_method { + name: "add_to_tensor" + argspec: "args=[\'self\', \'x\', \'name\'], varargs=None, keywords=None, defaults=[\'add_to_tensor\'], " + } + member_method { + name: "adjoint" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'adjoint\'], " + } + member_method { + name: "assert_non_singular" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_non_singular\'], " + } + member_method { + name: "assert_positive_definite" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_positive_definite\'], " + } + member_method { + name: "assert_self_adjoint" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_self_adjoint\'], " + } + member_method { + name: "batch_shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'batch_shape_tensor\'], " + } + member_method { + name: "cholesky" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'cholesky\'], " + } + member_method { + name: "cond" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'cond\'], " + } + member_method { + name: "determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'det\'], " + } + member_method { + name: "diag_part" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'diag_part\'], " + } + member_method { + name: "domain_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " + } + member_method { + name: "eigvals" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'eigvals\'], " + } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } + member_method { + name: "log_abs_determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " + } + member_method { + name: "matmul" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'matmul\'], " + } + member_method { + name: "matvec" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'matvec\'], " + } + member_method { + name: "range_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'range_dimension_tensor\'], " + } + member_method { + name: "shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'shape_tensor\'], " + } + member_method { + name: "solve" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'solve\'], " + } + member_method { + name: "solvevec" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'solve\'], " + } + member_method { + name: "tensor_rank_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'tensor_rank_tensor\'], " + } + member_method { + name: "to_dense" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'to_dense\'], " + } + member_method { + name: "trace" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'trace\'], " + } + member_method { + name: "with_name_scope" + argspec: "args=[\'cls\', \'method\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt index 632400c6570..264294d1a9b 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.linalg.pbtxt @@ -72,6 +72,10 @@ tf_module { name: "LinearOperatorToeplitz" mtype: "" } + member { + name: "LinearOperatorTridiag" + mtype: "" + } member { name: "LinearOperatorZeros" mtype: "" diff --git a/tensorflow/tools/api/golden/v1/tensorflow.random.experimental.-algorithm.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.random.experimental.-algorithm.pbtxt new file mode 100644 index 00000000000..ba4de088a7e --- /dev/null +++ b/tensorflow/tools/api/golden/v1/tensorflow.random.experimental.-algorithm.pbtxt @@ -0,0 +1,12 @@ +path: "tensorflow.random.experimental.Algorithm" +tf_class { + is_instance: "" + member { + name: "PHILOX" + mtype: "" + } + member { + name: "THREEFRY" + mtype: "" + } +} diff --git a/tensorflow/tools/api/golden/v1/tensorflow.random.experimental.pbtxt b/tensorflow/tools/api/golden/v1/tensorflow.random.experimental.pbtxt index 210717909ba..73f7497934e 100644 --- a/tensorflow/tools/api/golden/v1/tensorflow.random.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v1/tensorflow.random.experimental.pbtxt @@ -1,12 +1,16 @@ path: "tensorflow.random.experimental" tf_module { + member { + name: "Algorithm" + mtype: "" + } member { name: "Generator" mtype: "" } member_method { name: "create_rng_state" - argspec: "args=[\'seed\', \'algorithm\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'seed\', \'alg\'], varargs=None, keywords=None, defaults=None" } member_method { name: "get_global_generator" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-tridiag.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-tridiag.pbtxt new file mode 100644 index 00000000000..0609904bbb3 --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.-linear-operator-tridiag.pbtxt @@ -0,0 +1,185 @@ +path: "tensorflow.linalg.LinearOperatorTridiag" +tf_class { + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + is_instance: "" + member { + name: "H" + mtype: "" + } + member { + name: "batch_shape" + mtype: "" + } + member { + name: "diagonals" + mtype: "" + } + member { + name: "diagonals_format" + mtype: "" + } + member { + name: "domain_dimension" + mtype: "" + } + member { + name: "dtype" + mtype: "" + } + member { + name: "graph_parents" + mtype: "" + } + member { + name: "is_non_singular" + mtype: "" + } + member { + name: "is_positive_definite" + mtype: "" + } + member { + name: "is_self_adjoint" + mtype: "" + } + member { + name: "is_square" + mtype: "" + } + member { + name: "name" + mtype: "" + } + member { + name: "name_scope" + mtype: "" + } + member { + name: "range_dimension" + mtype: "" + } + member { + name: "shape" + mtype: "" + } + member { + name: "submodules" + mtype: "" + } + member { + name: "tensor_rank" + mtype: "" + } + member { + name: "trainable_variables" + mtype: "" + } + member { + name: "variables" + mtype: "" + } + member_method { + name: "__init__" + argspec: "args=[\'self\', \'diagonals\', \'diagonals_format\', \'is_non_singular\', \'is_self_adjoint\', \'is_positive_definite\', \'is_square\', \'name\'], varargs=None, keywords=None, defaults=[\'compact\', \'None\', \'None\', \'None\', \'None\', \'LinearOperatorTridiag\'], " + } + member_method { + name: "add_to_tensor" + argspec: "args=[\'self\', \'x\', \'name\'], varargs=None, keywords=None, defaults=[\'add_to_tensor\'], " + } + member_method { + name: "adjoint" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'adjoint\'], " + } + member_method { + name: "assert_non_singular" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_non_singular\'], " + } + member_method { + name: "assert_positive_definite" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_positive_definite\'], " + } + member_method { + name: "assert_self_adjoint" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'assert_self_adjoint\'], " + } + member_method { + name: "batch_shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'batch_shape_tensor\'], " + } + member_method { + name: "cholesky" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'cholesky\'], " + } + member_method { + name: "cond" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'cond\'], " + } + member_method { + name: "determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'det\'], " + } + member_method { + name: "diag_part" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'diag_part\'], " + } + member_method { + name: "domain_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'domain_dimension_tensor\'], " + } + member_method { + name: "eigvals" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'eigvals\'], " + } + member_method { + name: "inverse" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'inverse\'], " + } + member_method { + name: "log_abs_determinant" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'log_abs_det\'], " + } + member_method { + name: "matmul" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'matmul\'], " + } + member_method { + name: "matvec" + argspec: "args=[\'self\', \'x\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'matvec\'], " + } + member_method { + name: "range_dimension_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'range_dimension_tensor\'], " + } + member_method { + name: "shape_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'shape_tensor\'], " + } + member_method { + name: "solve" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'adjoint_arg\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'False\', \'solve\'], " + } + member_method { + name: "solvevec" + argspec: "args=[\'self\', \'rhs\', \'adjoint\', \'name\'], varargs=None, keywords=None, defaults=[\'False\', \'solve\'], " + } + member_method { + name: "tensor_rank_tensor" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'tensor_rank_tensor\'], " + } + member_method { + name: "to_dense" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'to_dense\'], " + } + member_method { + name: "trace" + argspec: "args=[\'self\', \'name\'], varargs=None, keywords=None, defaults=[\'trace\'], " + } + member_method { + name: "with_name_scope" + argspec: "args=[\'cls\', \'method\'], varargs=None, keywords=None, defaults=None" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt index 041041f60ed..7d6f02aa224 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.linalg.pbtxt @@ -72,6 +72,10 @@ tf_module { name: "LinearOperatorToeplitz" mtype: "" } + member { + name: "LinearOperatorTridiag" + mtype: "" + } member { name: "LinearOperatorZeros" mtype: "" diff --git a/tensorflow/tools/api/golden/v2/tensorflow.mixed_precision.experimental.-loss-scale-gradient-tape.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.mixed_precision.experimental.-loss-scale-gradient-tape.pbtxt deleted file mode 100644 index 7f4715832e2..00000000000 --- a/tensorflow/tools/api/golden/v2/tensorflow.mixed_precision.experimental.-loss-scale-gradient-tape.pbtxt +++ /dev/null @@ -1,38 +0,0 @@ -path: "tensorflow.mixed_precision.experimental.LossScaleGradientTape" -tf_class { - is_instance: "" - is_instance: "" - is_instance: "" - member_method { - name: "__init__" - argspec: "args=[\'self\', \'loss_scale\', \'persistent\', \'watch_accessed_variables\'], varargs=None, keywords=None, defaults=[\'False\', \'True\'], " - } - member_method { - name: "batch_jacobian" - argspec: "args=[\'self\', \'target\', \'source\', \'unconnected_gradients\', \'parallel_iterations\', \'experimental_use_pfor\'], varargs=None, keywords=None, defaults=[\'UnconnectedGradients.NONE\', \'None\', \'True\'], " - } - member_method { - name: "gradient" - argspec: "args=[\'self\', \'target\', \'sources\', \'output_gradients\', \'unconnected_gradients\'], varargs=None, keywords=None, defaults=[\'None\', \'UnconnectedGradients.NONE\'], " - } - member_method { - name: "jacobian" - argspec: "args=[\'self\', \'target\', \'sources\', \'unconnected_gradients\', \'parallel_iterations\', \'experimental_use_pfor\'], varargs=None, keywords=None, defaults=[\'UnconnectedGradients.NONE\', \'None\', \'True\'], " - } - member_method { - name: "reset" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "stop_recording" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "watch" - argspec: "args=[\'self\', \'tensor\'], varargs=None, keywords=None, defaults=None" - } - member_method { - name: "watched_variables" - argspec: "args=[\'self\'], varargs=None, keywords=None, defaults=None" - } -} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.mixed_precision.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.mixed_precision.experimental.pbtxt index 5abfdcd109d..61700226fbb 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.mixed_precision.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.mixed_precision.experimental.pbtxt @@ -12,8 +12,4 @@ tf_module { name: "LossScale" mtype: "" } - member { - name: "LossScaleGradientTape" - mtype: "" - } } diff --git a/tensorflow/tools/api/golden/v2/tensorflow.random.experimental.-algorithm.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.random.experimental.-algorithm.pbtxt new file mode 100644 index 00000000000..ba4de088a7e --- /dev/null +++ b/tensorflow/tools/api/golden/v2/tensorflow.random.experimental.-algorithm.pbtxt @@ -0,0 +1,12 @@ +path: "tensorflow.random.experimental.Algorithm" +tf_class { + is_instance: "" + member { + name: "PHILOX" + mtype: "" + } + member { + name: "THREEFRY" + mtype: "" + } +} diff --git a/tensorflow/tools/api/golden/v2/tensorflow.random.experimental.pbtxt b/tensorflow/tools/api/golden/v2/tensorflow.random.experimental.pbtxt index 210717909ba..73f7497934e 100644 --- a/tensorflow/tools/api/golden/v2/tensorflow.random.experimental.pbtxt +++ b/tensorflow/tools/api/golden/v2/tensorflow.random.experimental.pbtxt @@ -1,12 +1,16 @@ path: "tensorflow.random.experimental" tf_module { + member { + name: "Algorithm" + mtype: "" + } member { name: "Generator" mtype: "" } member_method { name: "create_rng_state" - argspec: "args=[\'seed\', \'algorithm\'], varargs=None, keywords=None, defaults=None" + argspec: "args=[\'seed\', \'alg\'], varargs=None, keywords=None, defaults=None" } member_method { name: "get_global_generator" diff --git a/tensorflow/tools/ci_build/Dockerfile.cmake b/tensorflow/tools/ci_build/Dockerfile.cmake index ef0024fdb41..e2e9a869fda 100644 --- a/tensorflow/tools/ci_build/Dockerfile.cmake +++ b/tensorflow/tools/ci_build/Dockerfile.cmake @@ -28,7 +28,6 @@ RUN pip install --upgrade astor RUN pip install --upgrade gast RUN pip install --upgrade numpy RUN pip install --upgrade termcolor -RUN pip install --upgrade keras_applications RUN pip install --upgrade keras_preprocessing # Install golang diff --git a/tensorflow/tools/ci_build/Dockerfile.custom_op_ubuntu_16_gpu b/tensorflow/tools/ci_build/Dockerfile.custom_op_ubuntu_16_cuda10.0 similarity index 100% rename from tensorflow/tools/ci_build/Dockerfile.custom_op_ubuntu_16_gpu rename to tensorflow/tools/ci_build/Dockerfile.custom_op_ubuntu_16_cuda10.0 diff --git a/tensorflow/tools/ci_build/Dockerfile.custom_op_ubuntu_16_cuda10.1 b/tensorflow/tools/ci_build/Dockerfile.custom_op_ubuntu_16_cuda10.1 new file mode 100644 index 00000000000..c7b8093d523 --- /dev/null +++ b/tensorflow/tools/ci_build/Dockerfile.custom_op_ubuntu_16_cuda10.1 @@ -0,0 +1,77 @@ +# Dockerfile for Ubuntu 16.04 manylinux2010 custom ops with GPU. + +FROM nvidia/cuda:10.1-cudnn7-devel-ubuntu16.04 as devtoolset + +LABEL maintainer="Amit Patankar " + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y \ + cpio \ + file \ + flex \ + g++ \ + make \ + rpm2cpio \ + unar \ + wget \ + && \ + rm -rf /var/lib/apt/lists/* + +ADD devtoolset/fixlinks.sh fixlinks.sh +ADD devtoolset/build_devtoolset.sh build_devtoolset.sh +ADD devtoolset/rpm-patch.sh rpm-patch.sh + +# Set up a sysroot for glibc 2.12 / libstdc++ 4.4 / devtoolset-7 in /dt7. +RUN /build_devtoolset.sh devtoolset-7 /dt7 +# Set up a sysroot for glibc 2.12 / libstdc++ 4.4 / devtoolset-8 in /dt8. +RUN /build_devtoolset.sh devtoolset-8 /dt8 + +# TODO(klimek): Split up into two different docker images. +FROM nvidia/cuda:10.1-cudnn7-devel-ubuntu16.04 + +LABEL maintainer="Amit Patankar " + +COPY --from=devtoolset /dt7 /dt7 +COPY --from=devtoolset /dt8 /dt8 + +# Install TensorRT. +RUN apt-get update && apt-get install -y \ + libnvinfer-dev=6.0.1-1+cuda10.1 \ + libnvinfer6=6.0.1-1+cuda10.1 \ + libnvinfer-plugin-dev=6.0.1-1+cuda10.1 \ + libnvinfer-plugin6=6.0.1-1+cuda10.1 \ + && \ + rm -rf /var/lib/apt/lists/* + +# Copy and run the install scripts. +COPY install/*.sh /install/ +ARG DEBIAN_FRONTEND=noninteractive +RUN /install/install_bootstrap_deb_packages.sh +RUN /install/install_deb_packages.sh +RUN /install/install_clang.sh +RUN /install/install_bazel.sh +RUN /install/install_buildifier.sh + +ENV TF_NEED_CUDA=1 + +# Install python 3.6. +RUN add-apt-repository ppa:jonathonf/python-3.6 && \ + apt-get update && apt-get install -y \ + python3.6 python3.6-dev python3-pip python3.6-venv && \ + rm -rf /var/lib/apt/lists/* && \ + python3.6 -m pip install pip --upgrade && \ + update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.6 0 + +# Install python 3.7 +RUN /install/install_python37.sh + +# Install pip3.5 +RUN wget https://bootstrap.pypa.io/get-pip.py && python3.5 get-pip.py && rm get-pip.py + +RUN /install/install_pip_packages.sh +RUN /install/install_auditwheel.sh + +# TODO(klimek): Figure out a better way to get the right include paths +# forwarded when we install new packages. +RUN ln -s "/usr/include/x86_64-linux-gnu/python3.6m" "/dt7/usr/include/x86_64-linux-gnu/python3.6m" +RUN ln -s "/usr/include/x86_64-linux-gnu/python3.6m" "/dt8/usr/include/x86_64-linux-gnu/python3.6m" diff --git a/tensorflow/tools/ci_build/builds/nightly_release_smoke_test.sh b/tensorflow/tools/ci_build/builds/nightly_release_smoke_test.sh index a420b598b36..551e2ee3b4a 100644 --- a/tensorflow/tools/ci_build/builds/nightly_release_smoke_test.sh +++ b/tensorflow/tools/ci_build/builds/nightly_release_smoke_test.sh @@ -53,10 +53,16 @@ function test_tf_imports() { return 1 fi - # test basic keras and estimator are available. - RET_VAL=$(python -c "import tensorflow as tf; a = (tf.keras.__name__, tf.estimator.__name__); print (a)") + # test basic keras is available + RET_VAL=$(python -c "import tensorflow as tf; print(tf.keras.__name__)") + if ! [[ ${RET_VAL} == *'tensorflow_core.python.keras.api._v2.keras'* ]]; then + echo "PIP test on virtualenv FAILED, will not upload ${WHL_NAME} package." + return 1 + fi - if ! [[ ${RET_VAL} == *'('\''tensorflow_core.keras'\'', '\''tensorflow_core.estimator'\'')'* ]]; then + # similar test for estimator + RET_VAL=$(python -c "import tensorflow as tf; print(tf.estimator.__name__)") + if ! [[ ${RET_VAL} == *'tensorflow_estimator.python.estimator.api._v2.estimator'* ]]; then echo "PIP test on virtualenv FAILED, will not upload ${WHL_NAME} package." return 1 fi diff --git a/tensorflow/tools/ci_build/builds/pip_new.sh b/tensorflow/tools/ci_build/builds/pip_new.sh index 2559dacd915..e8f0e581d7c 100755 --- a/tensorflow/tools/ci_build/builds/pip_new.sh +++ b/tensorflow/tools/ci_build/builds/pip_new.sh @@ -273,7 +273,7 @@ PYTHON_BIN_PATH_INIT=${PYTHON_BIN_PATH} PIP_BIN_PATH="$(which pip${PY_MAJOR_MINOR_VER})" # PIP packages -INSTALL_EXTRA_PIP_PACKAGES="portpicker scipy scikit-learn ${TF_BUILD_INSTALL_EXTRA_PIP_PACKAGES}" +INSTALL_EXTRA_PIP_PACKAGES="h5py portpicker scipy scikit-learn ${TF_BUILD_INSTALL_EXTRA_PIP_PACKAGES}" ########################################################################### # Build TF PIP Package diff --git a/tensorflow/tools/ci_build/ctpu/ctpu.sh b/tensorflow/tools/ci_build/ctpu/ctpu.sh index 782a2b0bddb..35a4bd6d248 100644 --- a/tensorflow/tools/ci_build/ctpu/ctpu.sh +++ b/tensorflow/tools/ci_build/ctpu/ctpu.sh @@ -21,10 +21,12 @@ function install_ctpu { PIP_CMD="${1:-pip}" - # TPUClusterResolver has a runtime dependency on these Python libraries when - # resolving a Cloud TPU. It's very likely we want these installed if we're + # TPUClusterResolver has a runtime dependency cloud-tpu-client when + # resolving a Cloud TPU. It's very likely we want this installed if we're # using CTPU. - "${PIP_CMD}" install --user --upgrade google-api-python-client oauth2client + # Replace cloud-tpu-client with google-api-python-client oauth2client to test + # the client at head. + "${PIP_CMD}" install --user --upgrade cloud-tpu-client wget -nv "https://dl.google.com/cloud_tpu/ctpu/latest/linux/ctpu" chmod a+x ctpu diff --git a/tensorflow/tools/ci_build/install/install_centos_pip_packages.sh b/tensorflow/tools/ci_build/install/install_centos_pip_packages.sh index 7cd8d9f4418..94e2aaad505 100755 --- a/tensorflow/tools/ci_build/install/install_centos_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_centos_pip_packages.sh @@ -99,8 +99,6 @@ pip2 install --upgrade termcolor pip3 install --upgrade termcolor # Keras -pip2 install keras_applications==1.0.8 --no-deps -pip3 install keras_applications==1.0.8 --no-deps pip2 install keras_preprocessing==1.0.5 --no-deps pip3 install keras_preprocessing==1.0.5 --no-deps pip2 install --upgrade h5py==2.8.0 diff --git a/tensorflow/tools/ci_build/install/install_pip_packages.sh b/tensorflow/tools/ci_build/install/install_pip_packages.sh index 3f9c0e671e5..d0c922bb947 100755 --- a/tensorflow/tools/ci_build/install/install_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_pip_packages.sh @@ -127,8 +127,6 @@ pip2 install --upgrade termcolor pip3 install --upgrade termcolor # Keras -pip2 install keras_applications==1.0.8 --no-deps -pip3 install keras_applications==1.0.8 --no-deps pip2 install keras_preprocessing==1.1.0 --no-deps pip3 install keras_preprocessing==1.1.0 --no-deps pip2 install --upgrade h5py==2.8.0 diff --git a/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh b/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh index 135e8e81add..1053d999492 100755 --- a/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_python3.5_pip_packages.sh @@ -86,7 +86,6 @@ pip3.5 install --upgrade gast pip3.5 install --upgrade termcolor # Keras -pip3.5 install keras_applications==1.0.8 pip3.5 install keras_preprocessing==1.0.5 pip3.5 install --upgrade h5py==2.8.0 diff --git a/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh b/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh index af62d9efc78..3a288908df6 100755 --- a/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh +++ b/tensorflow/tools/ci_build/install/install_python3.6_pip_packages.sh @@ -104,7 +104,6 @@ pip3 install --upgrade termcolor pip3 install --upgrade h5py==2.8.0 # Keras -pip3 install keras_applications==1.0.8 pip3 install keras_preprocessing==1.0.5 # Estimator diff --git a/tensorflow/tools/ci_build/linux/mkl/Dockerfile.devel-mkl b/tensorflow/tools/ci_build/linux/mkl/Dockerfile.devel-mkl index 302574aadb2..4fce0ecef7c 100755 --- a/tensorflow/tools/ci_build/linux/mkl/Dockerfile.devel-mkl +++ b/tensorflow/tools/ci_build/linux/mkl/Dockerfile.devel-mkl @@ -11,7 +11,17 @@ ARG WHL_DIR="/tmp/pip" ARG PIP="pip" ARG TARGET_PLATFORM="haswell" ARG CONFIG_V2_DISABLE="" +ARG CONFIG_BFLOAT16_BUILD="" ARG ENABLE_SECURE_BUILD +ARG BAZEL_VERSION="" + +# Upgrade Bazel version if argument is passed +RUN if [ "${BAZEL_VERSION}" != "" ]; then \ + curl -fSsL -O https://github.com/bazelbuild/bazel/releases/download/$BAZEL_VERSION/bazel-$BAZEL_VERSION-installer-linux-x86_64.sh && \ + chmod +x bazel-$BAZEL_VERSION-installer-linux-x86_64.sh && \ + ./bazel-$BAZEL_VERSION-installer-linux-x86_64.sh && \ + rm -rf bazel-$BAZEL_VERSION-installer-linux-x86_64.sh; \ + fi # Download and build TensorFlow from the latest sources found in the root container # make sure that if they pass in a tag, that it is loaded or we'll get an error @@ -29,7 +39,7 @@ ENV CI_BUILD_PYTHON ${PYTHON} # compiler flags based on parameters ADD set-build-env.py . RUN ${PYTHON} set-build-env.py -p ${TARGET_PLATFORM} -f /root/.mkl.bazelrc \ - ${CONFIG_V2_DISABLE} ${ENABLE_SECURE_BUILD} + ${CONFIG_V2_DISABLE} ${ENABLE_SECURE_BUILD} ${CONFIG_BFLOAT16_BUILD} # Pull the compiler flags we just wrote into root user's .bazelrc file RUN echo "import /root/.mkl.bazelrc" >>/root/.bazelrc @@ -50,4 +60,3 @@ EXPOSE 6006 EXPOSE 8888 WORKDIR /root - diff --git a/tensorflow/tools/ci_build/linux/mkl/build-dev-container.sh b/tensorflow/tools/ci_build/linux/mkl/build-dev-container.sh index 52b51a8c479..7fb239d4630 100755 --- a/tensorflow/tools/ci_build/linux/mkl/build-dev-container.sh +++ b/tensorflow/tools/ci_build/linux/mkl/build-dev-container.sh @@ -59,7 +59,9 @@ BUILD_SKX_CONTAINERS=${BUILD_SKX_CONTAINERS:-no} BUILD_CLX_CONTAINERS=${BUILD_CLX_CONTAINERS:-no} CONTAINER_PORT=${TF_DOCKER_BUILD_PORT:-8888} BUILD_TF_V2_CONTAINERS=${BUILD_TF_V2_CONTAINERS:-yes} +BUILD_TF_BFLOAT16_CONTAINERS=${BUILD_TF_BFLOAT16_CONTAINERS:-no} ENABLE_SECURE_BUILD=${ENABLE_SECURE_BUILD:-no} +BAZEL_VERSION=${BAZEL_VERSION} debug "ROOT_CONTAINER=${ROOT_CONTAINER}" debug "TF_ROOT_CONTAINER_TAG=${TF_ROOT_CONTAINER_TAG}" @@ -72,8 +74,10 @@ debug "BUILD_AVX2_CONTAINERS=${BUILD_AVX2_CONTAINERS}" debug "BUILD_SKX_CONTAINERS=${BUILD_SKX_CONTAINERS}" debug "BUILD_CLX_CONTAINERS=${BUILD_CLX_CONTAINERS}" debug "BUILD_TF_V2_CONTAINERS=${BUILD_TF_V2_CONTAINERS}" +debug "BUILD_TF_BFLOAT16_CONTAINERS=${BUILD_TF_BFLOAT16_CONTAINERS}" debug "ENABLE_SECURE_BUILD=${ENABLE_SECURE_BUILD}" debug "TMP_DIR=${TMP_DIR}" +debug "BAZEL_VERSION=${BAZEL_VERSION}" function build_container() { @@ -103,11 +107,21 @@ function build_container() TF_DOCKER_BUILD_ARGS+=("--build-arg CONFIG_V2_DISABLE=--disable-v2") fi + #Add build arg for bfloat16 build + if [[ ${BUILD_TF_BFLOAT16_CONTAINERS} == "yes" ]]; then + TF_DOCKER_BUILD_ARGS+=("--build-arg CONFIG_BFLOAT16_BUILD=--enable-bfloat16") + fi + #Add build arg for Secure Build if [[ ${ENABLE_SECURE_BUILD} == "yes" ]]; then TF_DOCKER_BUILD_ARGS+=("--build-arg ENABLE_SECURE_BUILD=--secure-build") fi + # BAZEL Version + if [[ ${BAZEL_VERSION} != "" ]]; then + TF_DOCKER_BUILD_ARGS+=("--build-arg BAZEL_VERSION=${BAZEL_VERSION}") + fi + # Perform docker build debug "Building docker image with image name and tag: ${TEMP_IMAGE_NAME}" CMD="${DOCKER_BINARY} build ${TF_DOCKER_BUILD_ARGS[@]} --no-cache --pull -t ${TEMP_IMAGE_NAME} -f Dockerfile.devel-mkl ." @@ -155,9 +169,11 @@ function test_container() debug "ID of the running docker container: ${CONTAINER_ID}" debug "Performing basic sanity checks on the running container..." - TEST_CMD=$(${DOCKER_BINARY} exec ${CONTAINER_ID} bash -c "${PYTHON} -c 'from tensorflow.python import _pywrap_util_port; print(_pywrap_util_port.IsMklEnabled())'") - debug "Running test command: ${TEST_CMD}" - if [ "${TEST_CMD}" = "True" ] ; then + TEST_CMD_1=$(${DOCKER_BINARY} exec ${CONTAINER_ID} bash -c "${PYTHON} -c 'from tensorflow.python import _pywrap_util_port; print(_pywrap_util_port.IsMklEnabled())'") + # Make TEST_CMD backward compatible with older code + TEST_CMD_2=$(${DOCKER_BINARY} exec ${CONTAINER_ID} bash -c "${PYTHON} -c 'from tensorflow.python import pywrap_tensorflow; print(pywrap_tensorflow.IsMklEnabled())'") + + if [ "${TEST_CMD_1}" = "True" -o "${TEST_CMD_2}" = "True" ] ; then echo "PASS: MKL enabled test in ${TEMP_IMAGE_NAME}" else die "FAIL: MKL enabled test in ${TEMP_IMAGE_NAME}" diff --git a/tensorflow/tools/ci_build/linux/mkl/set-build-env.py b/tensorflow/tools/ci_build/linux/mkl/set-build-env.py index 1c6f1dffde2..dd7997c0d93 100755 --- a/tensorflow/tools/ci_build/linux/mkl/set-build-env.py +++ b/tensorflow/tools/ci_build/linux/mkl/set-build-env.py @@ -154,6 +154,12 @@ class BuildEnvSetter(object): help="Don't build TensorFlow v2. By default the " " compiler flag --config=v2 is enabled.", action="store_true") + arg_parser.add_argument( + "--enable-bfloat16", + dest="enable_bfloat16", + help="Enable bfloat16 build. By default it is " + " disabled if no parameter is passed.", + action="store_true") arg_parser.add_argument( "-s", "--secure-build", @@ -202,6 +208,8 @@ class BuildEnvSetter(object): self.bazel_flags_ += "--config=mkl " if not self.args.disable_v2: self.bazel_flags_ += "--config=v2 " + if self.args.enable_bfloat16: + self.bazel_flags_ += "--copt=-DENABLE_INTEL_MKL_BFLOAT16 " def write_build_args(self): self._debug("Writing build flags: {}".format(self.bazel_flags_)) diff --git a/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh b/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh index be84b994482..0343efa3b74 100755 --- a/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh +++ b/tensorflow/tools/ci_build/pi/build_raspberry_pi.sh @@ -82,6 +82,7 @@ if [[ $1 == "PI_ONE" ]]; then --copt=-DUSE_GEMM_FOR_CONV --copt=-DUSE_OPENBLAS --copt=-isystem --copt=${OPENBLAS_INSTALL_PATH}/include/ --copt=-std=gnu11 --copt=-DS_IREAD=S_IRUSR --copt=-DS_IWRITE=S_IWUSR + --copt=-fpermissive --linkopt=-L${OPENBLAS_INSTALL_PATH}/lib/ --linkopt=-l:libopenblas.a" echo "Building for the Pi One/Zero, with no NEON support" @@ -89,7 +90,7 @@ if [[ $1 == "PI_ONE" ]]; then else PI_COPTS="--copt=-march=armv7-a --copt=-mfpu=neon-vfpv4 --copt=-std=gnu11 --copt=-DS_IREAD=S_IRUSR --copt=-DS_IWRITE=S_IWUSR - --copt=-O3 --copt=-fno-tree-pre + --copt=-O3 --copt=-fno-tree-pre --copt=-fpermissive --copt=-U__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 --copt=-U__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 --copt=-U__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 diff --git a/tensorflow/tools/ci_build/release/common.sh b/tensorflow/tools/ci_build/release/common.sh index 8842bd21540..3cac1ff8e4d 100644 --- a/tensorflow/tools/ci_build/release/common.sh +++ b/tensorflow/tools/ci_build/release/common.sh @@ -129,7 +129,6 @@ function install_pip_deps { # LINT.IfChange(ubuntu_pip_installations) # TODO(aselle): Change all these to be --user instead of sudo. - ${SUDO_CMD} ${PIP_CMD} install keras_applications==1.0.8 --no-deps ${SUDO_CMD} ${PIP_CMD} install keras_preprocessing==1.1.0 --no-deps ${SUDO_CMD} ${PIP_CMD} install gast==0.2.2 ${SUDO_CMD} ${PIP_CMD} install h5py==2.8.0 @@ -161,7 +160,6 @@ function install_ubuntu_16_pip_deps { # LINT.IfChange(ubuntu_16_pip_installations) "${PIP_CMD}" install --user --upgrade attrs - "${PIP_CMD}" install keras_applications==1.0.8 --no-deps --user "${PIP_CMD}" install keras_preprocessing==1.1.0 --no-deps --user "${PIP_CMD}" install numpy==1.14.5 --user "${PIP_CMD}" install --user --upgrade "future>=0.17.1" @@ -205,7 +203,6 @@ function install_macos_pip_deps { # TODO(aselle): Change all these to be --user instead of sudo. ${SUDO_CMD} ${PIP_CMD} install --upgrade setuptools==39.1.0 - ${SUDO_CMD} ${PIP_CMD} install keras_applications==1.0.8 --no-deps ${SUDO_CMD} ${PIP_CMD} install keras_preprocessing==1.1.0 --no-deps ${SUDO_CMD} ${PIP_CMD} install --upgrade mock portpicker scipy grpcio ${SUDO_CMD} ${PIP_CMD} install six==1.12.0 diff --git a/tensorflow/tools/ci_build/release/common_win.bat b/tensorflow/tools/ci_build/release/common_win.bat index dd712460135..baddfd0fab9 100644 --- a/tensorflow/tools/ci_build/release/common_win.bat +++ b/tensorflow/tools/ci_build/release/common_win.bat @@ -34,7 +34,6 @@ SET PATH=%PATH%;C:\%PYTHON_DIRECTORY% %PIP_EXE% install opt_einsum --upgrade %PIP_EXE% install pandas --upgrade --no-deps %PIP_EXE% install protobuf --upgrade --no-deps -%PIP_EXE% install keras_applications==1.0.8 --upgrade --no-deps %PIP_EXE% install keras_preprocessing==1.1.0 --upgrade --no-deps %PIP_EXE% install wrapt --upgrade --no-deps diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/custom_op/nightly.sh b/tensorflow/tools/ci_build/release/ubuntu_16/custom_op/nightly.sh index 3f80dd42536..84ae1150395 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/custom_op/nightly.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/custom_op/nightly.sh @@ -17,17 +17,20 @@ set -e # 1. Build the test server UBUNTU16_CPU_IMAGE="tensorflow/tensorflow:nightly-custom-op-ubuntu16" +UBUNTU16_GPU_CUDA10P0_IMAGE="tensorflow/tensorflow:nightly-custom-op-gpu-ubuntu16-cuda10.0" UBUNTU16_GPU_IMAGE="tensorflow/tensorflow:nightly-custom-op-gpu-ubuntu16" # Build the docker image cd tensorflow/tools/ci_build docker build --no-cache -t "${UBUNTU16_CPU_IMAGE}" -f Dockerfile.custom_op_ubuntu_16 . -docker build --no-cache -t "${UBUNTU16_GPU_IMAGE}" -f Dockerfile.custom_op_ubuntu_16_gpu . +docker build --no-cache -t "${UBUNTU16_GPU_IMAGE}" -f Dockerfile.custom_op_ubuntu_16_cuda10.1 . +docker build --no-cache -t "${UBUNTU16_GPU_CUDA10P0_IMAGE}" -f Dockerfile.custom_op_ubuntu_16_cuda10.0 . # Log into docker hub, push the image and log out docker login -u "${TF_DOCKER_USERNAME}" -p "${TF_DOCKER_PASSWORD}" docker push "${UBUNTU16_CPU_IMAGE}" docker push "${UBUNTU16_GPU_IMAGE}" +docker push "${UBUNTU16_GPU_CUDA10P0_IMAGE}" docker logout#!/bin/bash diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nightly_release.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nightly_release.sh index b8eba43b01c..992cf964c00 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nightly_release.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nightly_release.sh @@ -31,7 +31,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_CUDA_COMPUTE_CAPABILITIES=3.5,3.7,5.2,6.0,6.1,7.0 export TF_NEED_TENSORRT=1 diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip.sh index b5b2cbb3c56..9d300efd8cc 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip_v1.sh index fe4cec92c20..2c29e1d8e14 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/nonpip_v1.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip.sh index 01e1eb17568..7161e7f3abb 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip_v1.sh index 53a402385c5..76a29e8705e 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py2_full/pip_v1.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nightly_release.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nightly_release.sh index 0daf7d5e0f8..7d2c1ee4fa7 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nightly_release.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nightly_release.sh @@ -31,7 +31,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_CUDA_COMPUTE_CAPABILITIES=3.5,3.7,5.2,6.0,6.1,7.0 export TF_NEED_TENSORRT=1 diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip.sh index 465d193b437..4637838db06 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip_v1.sh index 8ec3913c86c..be864c019f9 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/nonpip_v1.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip.sh index 0e3f4469e64..f54a366330b 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip_v1.sh index c91875f210f..204943026a7 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py35_full/pip_v1.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nightly_release.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nightly_release.sh index 08fb3de0e3b..9c2c0d0ff2c 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nightly_release.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nightly_release.sh @@ -31,7 +31,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_CUDA_COMPUTE_CAPABILITIES=3.5,3.7,5.2,6.0,6.1,7.0 export TF_NEED_TENSORRT=1 diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip.sh index 4bfda9c7a51..c690ab6f6bb 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip_v1.sh index dddf6c36aa0..8b10fe8edc9 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/nonpip_v1.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip.sh index ca760184744..6428e5c526c 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip_v1.sh index 2ca36d4c20e..a2623e57ded 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py36_full/pip_v1.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nightly_release.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nightly_release.sh index b0a231716c7..d9c4e107855 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nightly_release.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nightly_release.sh @@ -31,7 +31,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_CUDA_COMPUTE_CAPABILITIES=3.5,3.7,5.2,6.0,6.1,7.0 export TF_NEED_TENSORRT=1 diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip.sh index 20f42717c7b..040cab4e533 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip_v1.sh index bd29f27397f..02b5f2ebfe9 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/nonpip_v1.sh @@ -27,7 +27,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip.sh index 840047ae6cf..07e90a4cdab 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip_v1.sh b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip_v1.sh index 721ab43dcd6..84a1421dcb1 100644 --- a/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip_v1.sh +++ b/tensorflow/tools/ci_build/release/ubuntu_16/gpu_py37_full/pip_v1.sh @@ -32,7 +32,7 @@ export TF_NEED_GCP=1 export TF_NEED_HDFS=1 export TF_NEED_S3=1 export TF_NEED_CUDA=1 -export TF_CUDA_VERSION=10.1 +export TF_CUDA_VERSION=10 export TF_CUDNN_VERSION=7 export TF_NEED_TENSORRT=1 export TENSORRT_INSTALL_PATH=/usr/local/tensorrt diff --git a/tensorflow/tools/compatibility/all_renames_v2.py b/tensorflow/tools/compatibility/all_renames_v2.py index 0093eafbf08..c9edc3c9819 100644 --- a/tensorflow/tools/compatibility/all_renames_v2.py +++ b/tensorflow/tools/compatibility/all_renames_v2.py @@ -556,6 +556,15 @@ manual_symbol_renames = { } # pylint: enable=line-too-long + +def add_contrib_direct_import_support(symbol_dict): + """Add support for `tf.contrib.*` alias `contrib_*.` Updates dict in place.""" + for symbol_name in list(symbol_dict.keys()): + symbol_alias = symbol_name.replace("tf.contrib.", "contrib_") + symbol_dict[symbol_alias] = symbol_dict[symbol_name] + +add_contrib_direct_import_support(manual_symbol_renames) + symbol_renames = renames_v2.renames symbol_renames.update(manual_symbol_renames) @@ -613,3 +622,5 @@ addons_symbol_mappings = { "tf.contrib.rnn.LayerNormBasicLSTMCell": "tfa.rnn.LayerNormLSTMCell" } + +add_contrib_direct_import_support(addons_symbol_mappings) diff --git a/tensorflow/tools/compatibility/ast_edits.py b/tensorflow/tools/compatibility/ast_edits.py index 71fb2aee770..71ebcef1fa6 100644 --- a/tensorflow/tools/compatibility/ast_edits.py +++ b/tensorflow/tools/compatibility/ast_edits.py @@ -899,12 +899,13 @@ class ASTCodeUpgrader(object): type(api_change_spec)) self._api_change_spec = api_change_spec - def process_file(self, in_filename, out_filename): + def process_file(self, in_filename, out_filename, no_out_on_error=False): """Process the given python file for incompatible changes. Args: in_filename: filename to parse out_filename: output file to write to + no_out_on_error: do not modify the output file when errors happen Returns: A tuple representing number of files processed, log of actions, errors """ @@ -917,7 +918,10 @@ class ASTCodeUpgrader(object): temp_file) # pylint: enable=g-backslash-continuation - shutil.move(temp_file.name, out_filename) + if no_out_on_error and ret[-1]: + os.remove(temp_file.name) + else: + shutil.move(temp_file.name, out_filename) return ret def format_log(self, log, in_filename): diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2.py b/tensorflow/tools/compatibility/tf_upgrade_v2.py index 3acef688828..a8c507900cf 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2.py @@ -508,6 +508,8 @@ class TFAPIChangeSpec(ast_edits.NoUpdateSpec): "checkpoint_dir": "ckpt_dir_or_file", } } + all_renames_v2.add_contrib_direct_import_support( + self.function_keyword_renames) # Mapping from function to the new name of the function # Add additional renames not in renames_v2.py to all_renames_v2.py. @@ -1240,6 +1242,7 @@ class TFAPIChangeSpec(ast_edits.NoUpdateSpec): "tf.summary.tensor_summary": summary_api_comment, "tf.summary.text": summary_api_comment, } + all_renames_v2.add_contrib_direct_import_support(self.function_warnings) for symbol, replacement in all_renames_v2.addons_symbol_mappings.items(): warning = ( @@ -1385,6 +1388,7 @@ class TFAPIChangeSpec(ast_edits.NoUpdateSpec): "compat.v1.image.resize_nearest_neighbor."), }, } + all_renames_v2.add_contrib_direct_import_support(self.function_arg_warnings) # Specially handled functions # Each transformer is a callable which will be called with the arguments @@ -1604,6 +1608,7 @@ class TFAPIChangeSpec(ast_edits.NoUpdateSpec): arg_name="save_format", arg_value_ast=ast.Str("h5")), } + all_renames_v2.add_contrib_direct_import_support(self.function_transformers) self.module_deprecations = module_deprecations_v2.MODULE_DEPRECATIONS diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py index 124f837e9fb..7dcbfe19c39 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_main.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_main.py @@ -35,6 +35,9 @@ _DEFAULT_MODE = "DEFAULT" # Convert to use compat.v1. _SAFETY_MODE = "SAFETY" +# Whether to rename to compat.v2 +_IMPORT_RENAME_DEFAULT = False + def process_file(in_filename, out_filename, upgrader): """Process a file of type `.py` or `.ipynb`.""" @@ -96,9 +99,9 @@ Simple usage: "input files."), action="store_true") parser.add_argument( - "--import_rename", - dest="import_rename", - help=("Whether to rename import to compact.v2 explicitly."), + "--no_import_rename", + dest="no_import_rename", + help=("Not to rename import to compact.v2 explicitly."), action="store_true") parser.add_argument( "--reportfile", @@ -128,7 +131,11 @@ Simple usage: if args.mode == _SAFETY_MODE: change_spec = tf_upgrade_v2_safety.TFAPIChangeSpec() else: - change_spec = tf_upgrade_v2.TFAPIChangeSpec(args.import_rename) + if args.no_import_rename: + change_spec = tf_upgrade_v2.TFAPIChangeSpec(import_rename=False) + else: + change_spec = tf_upgrade_v2.TFAPIChangeSpec( + import_rename=_IMPORT_RENAME_DEFAULT) upgrade = ast_edits.ASTCodeUpgrader(change_spec) report_text = None diff --git a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py index 281d6880623..e41d8d0b927 100644 --- a/tensorflow/tools/compatibility/tf_upgrade_v2_test.py +++ b/tensorflow/tools/compatibility/tf_upgrade_v2_test.py @@ -515,68 +515,69 @@ bazel-bin/tensorflow/tools/compatibility/update/generate_v2_reorders_map initializers, ns_prefix="keras.initializers") def testContribXavierInitializer(self): - text = "tf.contrib.layers.xavier_initializer()\n" - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual( - new_text, - "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " - "mode=\"fan_avg\", " - "distribution=\"uniform\")\n", - ) + for contrib_alias in ["tf.contrib.", "contrib_"]: + text = contrib_alias + "layers.xavier_initializer()\n" + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " + "mode=\"fan_avg\", " + "distribution=\"uniform\")\n", + ) - text = "slim.xavier_initializer(True or False)\n" - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual( - new_text, - "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " - "mode=\"fan_avg\", " - "distribution=(\"uniform\" if True or False else " - "\"truncated_normal\"))\n", - ) + text = "slim.xavier_initializer(True or False)\n" + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " + "mode=\"fan_avg\", " + "distribution=(\"uniform\" if True or False else " + "\"truncated_normal\"))\n", + ) - text = "slim.xavier_initializer(uniform=(True or False))\n" - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual( - new_text, - "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " - "mode=\"fan_avg\", " - "distribution=(\"uniform\" if True or False else " - "\"truncated_normal\"))\n", - ) + text = "slim.xavier_initializer(uniform=(True or False))\n" + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " + "mode=\"fan_avg\", " + "distribution=(\"uniform\" if True or False else " + "\"truncated_normal\"))\n", + ) - text = "tf.contrib.layers.xavier_initializer_conv2d(False, 12)\n" - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual( - new_text, - "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " - "mode=\"fan_avg\", " - "distribution=(\"uniform\" if False else \"truncated_normal\"), " - "seed=12)\n", - ) + text = contrib_alias + "layers.xavier_initializer_conv2d(False, 12)\n" + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " + "mode=\"fan_avg\", " + "distribution=(\"uniform\" if False else \"truncated_normal\"), " + "seed=12)\n", + ) - text = ("tf.contrib.layers.xavier_initializer_conv2d(" - "False, 12, tf.float32)\n") - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual( - new_text, - "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " - "mode=\"fan_avg\", " - "distribution=(\"uniform\" if False else \"truncated_normal\"), " - "seed=12, " - "dtype=tf.float32)\n", - ) + text = (contrib_alias + "layers.xavier_initializer_conv2d(" + "False, 12, tf.float32)\n") + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " + "mode=\"fan_avg\", " + "distribution=(\"uniform\" if False else \"truncated_normal\"), " + "seed=12, " + "dtype=tf.float32)\n", + ) - text = ("tf.contrib.layers.xavier_initializer(" - "False, 12, dtypes=tf.float32)\n") - _, unused_report, unused_errors, new_text = self._upgrade(text) - self.assertEqual( - new_text, - "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " - "mode=\"fan_avg\", " - "distribution=(\"uniform\" if False else \"truncated_normal\"), " - "seed=12, " - "dtypes=tf.float32)\n", - ) + text = (contrib_alias + "layers.xavier_initializer(" + "False, 12, dtypes=tf.float32)\n") + _, unused_report, unused_errors, new_text = self._upgrade(text) + self.assertEqual( + new_text, + "tf.compat.v1.keras.initializers.VarianceScaling(scale=1.0, " + "mode=\"fan_avg\", " + "distribution=(\"uniform\" if False else \"truncated_normal\"), " + "seed=12, " + "dtypes=tf.float32)\n", + ) def testVarianceScalingInitializer(self): text = ("tf.contrib.layers.variance_scaling_initializer(" @@ -1638,6 +1639,12 @@ def _log_prob(self, x): _, _, _, new_text = self._upgrade(text) self.assertEqual(expected, new_text) + def test_is_tensor_direct_import_upgrade(self): + text = "contrib_framework.is_tensor(x)" + expected = "tf.is_tensor(x)" + _, _, _, new_text = self._upgrade(text) + self.assertEqual(expected, new_text) + def test_CriticalSection_upgrade(self): text = "tf.contrib.framework.CriticalSection(shared_name='blah')" expected = "tf.CriticalSection(shared_name='blah')" @@ -1674,17 +1681,21 @@ def _log_prob(self, x): self.assertIn("tf.flags and tf.app.flags have been removed", errors[0]) def test_contrib_estimator_head_deprecation(self): - api_symbols = ["binary_classification_head", "logistic_regression_head", - "multi_class_head", "multi_head", "multi_label_head", - "poisson_regression_head", "regression_head"] - for symbol in api_symbols: - text = "tf.contrib.estimator." + symbol - _, report, _, _ = self._upgrade(text) - self.assertIn("`tf.contrib.estimator.*_head` has been deprecated", report) + for contrib_alias in ["tf.contrib.", "contrib_"]: + api_symbols = ["binary_classification_head", "logistic_regression_head", + "multi_class_head", "multi_head", "multi_label_head", + "poisson_regression_head", "regression_head"] + for symbol in api_symbols: + text = contrib_alias + "estimator." + symbol + _, report, _, _ = self._upgrade(text) + self.assertIn("`tf.contrib.estimator.*_head` has been deprecated", + report) def test_contrib_layers_layer_norm_deprecation(self): - _, report, _, _ = self._upgrade("tf.contrib.layers.layer_norm") - self.assertIn("`tf.contrib.layers.layer_norm` has been deprecated", report) + for contrib_alias in ["tf.contrib.", "contrib_"]: + _, report, _, _ = self._upgrade(contrib_alias + "layers.layer_norm") + self.assertIn( + "`tf.contrib.layers.layer_norm` has been deprecated", report) def test_contrib_rnn_deprecation(self): _, report, _, _ = self._upgrade("tf.contrib.rnn") @@ -1701,15 +1712,17 @@ def _log_prob(self, x): self.assertEqual(expected_text, new_text) def test_contrib_estimator_early_stopping(self): - api_symbols = [ - "make_early_stopping_hook", "stop_if_higher_hook", "stop_if_lower_hook", - "stop_if_no_decrease_hook", "stop_if_no_increase_hook" - ] - for symbol in api_symbols: - text = "tf.contrib.estimator." + symbol - expected_text = "tf.estimator.experimental." + symbol - _, _, _, new_text = self._upgrade(text) - self.assertEqual(expected_text, new_text) + for contrib_alias in ["tf.contrib.", "contrib_"]: + api_symbols = [ + "make_early_stopping_hook", "stop_if_higher_hook", + "stop_if_lower_hook", + "stop_if_no_decrease_hook", "stop_if_no_increase_hook" + ] + for symbol in api_symbols: + text = contrib_alias + "estimator." + symbol + expected_text = "tf.estimator.experimental." + symbol + _, _, _, new_text = self._upgrade(text) + self.assertEqual(expected_text, new_text) def test_contrib_rnn_cell(self): api_symbols = ["RNNCell", "BasicLSTMCell", "BasicRNNCell", "GRUCell", diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile index 69301026861..19732f36e03 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu-jupyter.Dockerfile @@ -86,7 +86,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile index 25961432011..05528a1d4b5 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-cpu.Dockerfile @@ -86,7 +86,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile index 1ba94edae90..4df7f841d51 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu-jupyter.Dockerfile @@ -119,7 +119,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile index aaf119da580..79787ad3440 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/devel-gpu.Dockerfile @@ -119,7 +119,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod-jupyter.Dockerfile index 92118f0ade8..b5bb5d69d19 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod-jupyter.Dockerfile @@ -84,7 +84,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod.Dockerfile index 338474678d2..f4162a28f7a 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/mkl_horovod/devel-horovod.Dockerfile @@ -84,7 +84,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile index b85f157e5e4..8006db472af 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le-jupyter.Dockerfile @@ -85,7 +85,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile index e33fa789edf..06f2b779be4 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-cpu-ppc64le.Dockerfile @@ -85,7 +85,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile index 49110036a1a..5fc850a34ff 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le-jupyter.Dockerfile @@ -118,7 +118,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile index 733404a5ce1..21cd014c75f 100644 --- a/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile +++ b/tensorflow/tools/dockerfiles/dockerfiles/ppc64le/devel-gpu-ppc64le.Dockerfile @@ -118,7 +118,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/partials/ubuntu/bazel.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/ubuntu/bazel.partial.Dockerfile index 1ec261c008b..c1b07798326 100644 --- a/tensorflow/tools/dockerfiles/partials/ubuntu/bazel.partial.Dockerfile +++ b/tensorflow/tools/dockerfiles/partials/ubuntu/bazel.partial.Dockerfile @@ -11,7 +11,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile b/tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile index 471f7f8c196..27d49a18337 100644 --- a/tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile +++ b/tensorflow/tools/dockerfiles/partials/ubuntu/bazelbuild.partial.Dockerfile @@ -10,7 +10,6 @@ RUN apt-get update && apt-get install -y \ RUN ${PIP} --no-cache-dir install \ Pillow \ h5py \ - keras_applications \ keras_preprocessing \ matplotlib \ mock \ diff --git a/tensorflow/tools/docs/BUILD b/tensorflow/tools/docs/BUILD index d844a271b72..811a6181c33 100644 --- a/tensorflow/tools/docs/BUILD +++ b/tensorflow/tools/docs/BUILD @@ -10,18 +10,47 @@ package( exports_files(["LICENSE"]) +py_library( + name = "tf_doctest_lib", + srcs = ["tf_doctest_lib.py"], + srcs_version = "PY2AND3", + deps = [ + "//third_party/py/numpy", + ], +) + py_test( name = "tf_doctest", srcs = ["tf_doctest.py"], python_version = "PY3", tags = [ "no_oss_py2", + "no_pip", "noasan", "nomsan", "notsan", ], deps = [ + ":tf_doctest_lib", "//tensorflow:tensorflow_py", + "//third_party/py/numpy", + ], +) + +py_test( + name = "tf_doctest_test", + srcs = ["tf_doctest_test.py"], + python_version = "PY3", + tags = [ + "no_oss_py2", + "no_pip", + "noasan", + "nomsan", + "notsan", + ], + deps = [ + ":tf_doctest_lib", + "@absl_py//absl/testing:parameterized", ], ) diff --git a/tensorflow/tools/docs/generate2.py b/tensorflow/tools/docs/generate2.py index acbec299c18..4c41e5cf57c 100644 --- a/tensorflow/tools/docs/generate2.py +++ b/tensorflow/tools/docs/generate2.py @@ -78,10 +78,8 @@ flags.DEFINE_string("site_path", "", if tf.__version__.startswith('1'): PRIVATE_MAP = { - 'tf.contrib.autograph': ['utils', 'operators'], 'tf.test': ['mock'], - 'tf.contrib.estimator': ['python'], - 'tf': ['python', 'core', 'compiler', 'examples', 'tools'], + 'tf': ['python', 'core', 'compiler', 'examples', 'tools', 'contrib'], # There's some aliasing between the compats and v1/2s, so it's easier to # block by name and location than by deleting, or hiding objects. 'tf.compat.v1.compat': ['v1', 'v2'], @@ -89,45 +87,7 @@ if tf.__version__.startswith('1'): } DO_NOT_DESCEND_MAP = { - 'tf': ['cli', 'lib', 'wrappers'], - 'tf.contrib': [ - 'compiler', - 'grid_rnn', - # Block contrib.keras to de-clutter the docs - 'keras', - 'labeled_tensor', - 'quantization', - 'session_bundle', - 'slim', - 'solvers', - 'specs', - 'tensor_forest', - 'tensorboard', - 'testing', - 'tfprof', - ], - 'tf.contrib.bayesflow': [ - 'special_math', 'stochastic_gradient_estimators', - 'stochastic_variables' - ], - 'tf.contrib.ffmpeg': ['ffmpeg_ops'], - 'tf.contrib.graph_editor': [ - 'edit', 'match', 'reroute', 'subgraph', 'transform', 'select', 'util' - ], - 'tf.contrib.keras': ['api', 'python'], - 'tf.contrib.layers': ['feature_column', 'summaries'], - 'tf.contrib.learn': [ - 'datasets', - 'head', - 'graph_actions', - 'io', - 'models', - 'monitors', - 'ops', - 'preprocessing', - 'utils', - ], - 'tf.contrib.util': ['loader'], + 'tf': ['cli', 'lib', 'wrappers', 'contrib'], } else: PRIVATE_MAP = { diff --git a/tensorflow/tools/docs/generate_lib.py b/tensorflow/tools/docs/generate_lib.py index 77a685062ae..3bd0fd615f4 100644 --- a/tensorflow/tools/docs/generate_lib.py +++ b/tensorflow/tools/docs/generate_lib.py @@ -229,10 +229,9 @@ def add_dict_to_dict(add_from, add_to): # Exclude some libraries in contrib from the documentation altogether. def _get_default_private_map(): return { - 'tf.contrib.autograph': ['utils', 'operators'], 'tf.test': ['mock'], + 'tf': ['contrib'], 'tf.compat': ['v1', 'v2'], - 'tf.contrib.estimator': ['python'], } @@ -241,44 +240,6 @@ def _get_default_do_not_descend_map(): # TODO(markdaoust): Use docs_controls decorators, locally, instead. return { 'tf': ['cli', 'lib', 'wrappers'], - 'tf.contrib': [ - 'compiler', - 'grid_rnn', - # Block contrib.keras to de-clutter the docs - 'keras', - 'labeled_tensor', - 'quantization', - 'session_bundle', - 'slim', - 'solvers', - 'specs', - 'tensor_forest', - 'tensorboard', - 'testing', - 'tfprof', - ], - 'tf.contrib.bayesflow': [ - 'special_math', 'stochastic_gradient_estimators', - 'stochastic_variables' - ], - 'tf.contrib.ffmpeg': ['ffmpeg_ops'], - 'tf.contrib.graph_editor': [ - 'edit', 'match', 'reroute', 'subgraph', 'transform', 'select', 'util' - ], - 'tf.contrib.keras': ['api', 'python'], - 'tf.contrib.layers': ['feature_column', 'summaries'], - 'tf.contrib.learn': [ - 'datasets', - 'head', - 'graph_actions', - 'io', - 'models', - 'monitors', - 'ops', - 'preprocessing', - 'utils', - ], - 'tf.contrib.util': ['loader'], } diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index d6426cfd6de..61518bcbd46 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -1660,8 +1660,7 @@ class _GeneratedFile(object): return True def __str__(self): - return 'Defined in generated file: `%s%s`.\n\n' % (self.path_prefix, - self.path) + return '' def _get_defined_in(py_object, parser_config): diff --git a/tensorflow/tools/docs/pretty_docs.py b/tensorflow/tools/docs/pretty_docs.py index d7237b1a39a..e215d19f403 100644 --- a/tensorflow/tools/docs/pretty_docs.py +++ b/tensorflow/tools/docs/pretty_docs.py @@ -61,10 +61,7 @@ def _build_function_page(page_info): """Given a FunctionPageInfo object Return the page as an md string.""" parts = ['# %s\n\n' % page_info.full_name] - if len(page_info.aliases) > 1: - parts.append('### Aliases:\n\n') - parts.extend('* `%s`\n' % name for name in page_info.aliases) - parts.append('\n') + parts.append(_build_aliases(page_info.aliases)) if page_info.signature is not None: parts.append(_build_signature(page_info)) @@ -105,10 +102,7 @@ def _build_class_page(page_info): method for method in page_info.methods if method.short_name not in constructor_names) - if len(page_info.aliases) > 1: - parts.append('### Aliases:\n\n') - parts.extend('* Class `%s`\n' % name for name in page_info.aliases) - parts.append('\n') + parts.append(_build_aliases(page_info.aliases)) if page_info.defined_in is not None: parts.append('\n\n') @@ -203,10 +197,7 @@ def _build_module_page(page_info): """Given a ClassPageInfo object Return the page as an md string.""" parts = ['# Module: {full_name}\n\n'.format(full_name=page_info.full_name)] - if len(page_info.aliases) > 1: - parts.append('### Aliases:\n\n') - parts.extend('* Module `%s`\n' % name for name in page_info.aliases) - parts.append('\n') + parts.append(_build_aliases(page_info.aliases)) if page_info.defined_in is not None: parts.append('\n\n') @@ -323,3 +314,14 @@ def _build_function_details(function_details): parts.append(''.join(sub)) return '\n'.join(parts) + + +def _build_aliases(aliases): + aliases = sorted(aliases, key=lambda x: ('compat.v' in x, x)) + parts = [] + if len(aliases) > 1: + parts.append('**Aliases**: ') + parts.extend(', '.join('`{}`'.format(name) for name in aliases)) + parts.append('\n\n') + + return ''.join(parts) diff --git a/tensorflow/tools/docs/tf_doctest.py b/tensorflow/tools/docs/tf_doctest.py index b33e9c306f4..2dce63541f5 100644 --- a/tensorflow/tools/docs/tf_doctest.py +++ b/tensorflow/tools/docs/tf_doctest.py @@ -1,3 +1,4 @@ +# Lint as: python3 # Copyright 2019 The TensorFlow Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,20 +20,21 @@ from __future__ import division from __future__ import print_function import os -import re import sys -import textwrap -import numpy as np from absl import flags from absl.testing import absltest +import numpy as np import tensorflow.compat.v2 as tf -tf.compat.v1.enable_v2_behavior() + +from tensorflow.tools.docs import tf_doctest_lib # We put doctest after absltest so that it picks up the unittest monkeypatch. # Otherwise doctest tests aren't runnable at all. -import doctest # pylint: disable=g-import-not-at-top, g-bad-import-order +import doctest # pylint: disable=g-bad-import-order + +tf.compat.v1.enable_v2_behavior() FLAGS = flags.FLAGS @@ -78,8 +80,7 @@ def filter_on_submodules(all_modules, submodule): """ filtered_modules = [ - mod for mod in all_modules - if PACKAGE + submodule in mod.__name__ + mod for mod in all_modules if PACKAGE + submodule in mod.__name__ ] return filtered_modules @@ -116,32 +117,6 @@ class TfTestCase(tf.test.TestCase): self.tearDown() -class CustomOutputChecker(doctest.OutputChecker): - """Changes the `want` and `got` strings. - - This allows it to be customized before they are compared. - """ - - ADDRESS_RE = re.compile(r'\bat 0x[0-9a-f]*?>') - - def check_output(self, want, got, optionflags): - # Replace python's addresses with ellipsis (`...`) since it can change on - # each execution. - want = self.ADDRESS_RE.sub('at ...>', want) - return doctest.OutputChecker.check_output(self, want, got, optionflags) - - _MESSAGE = textwrap.dedent("""\n - ############################################################# - Check the documentation - (https://www.tensorflow.org/community/contribute/docs_ref) on how to write testable docstrings. - #############################################################""") - - def output_difference(self, example, got, optionflags): - got = got + self._MESSAGE - return doctest.OutputChecker.output_difference(self, example, got, - optionflags) - - def load_tests(unused_loader, tests, unused_ignore): """Loads all the tests in the docstrings and runs them.""" @@ -173,11 +148,10 @@ def load_tests(unused_loader, tests, unused_ignore): }, setUp=testcase.set_up, tearDown=testcase.tear_down, - checker=CustomOutputChecker(), - optionflags=(doctest.ELLIPSIS | - doctest.NORMALIZE_WHITESPACE | - doctest.IGNORE_EXCEPTION_DETAIL | - doctest.DONT_ACCEPT_BLANKLINE), + checker=tf_doctest_lib.TfDoctestOutputChecker(), + optionflags=(doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE + | doctest.IGNORE_EXCEPTION_DETAIL + | doctest.DONT_ACCEPT_BLANKLINE), )) return tests diff --git a/tensorflow/tools/docs/tf_doctest_lib.py b/tensorflow/tools/docs/tf_doctest_lib.py new file mode 100644 index 00000000000..b33d14a0a40 --- /dev/null +++ b/tensorflow/tools/docs/tf_doctest_lib.py @@ -0,0 +1,200 @@ +# Lint as: python3 +# Copyright 2019 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. +# ============================================================================== +"""Run doctests for tensorflow.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import doctest +import re +import textwrap + +import numpy as np + + +class _FloatExtractor(object): + """Class for extracting floats from a string. + + For example: + + >>> text_parts, floats = _FloatExtractor()("Text 1.0 Text") + >>> text_parts + ["Text ", " Text"] + >>> floats + np.array([1.0]) + """ + + # Note: non-capturing groups "(?" are not returned in matched groups, or by + # re.split. + _FLOAT_RE = re.compile( + r""" + ( # Captures the float value. + (?: + [-+]| # Start with a sign is okay anywhere. + (?: # Otherwise: + ^| # Start after the start of string + (?<=[^\w.]) # Not after a word char, or a . + ) + ) + (?: # Digits and exponent - something like: + {digits_dot_maybe_digits}{exponent}?| # "1.0" "1." "1.0e3", "1.e3" + {dot_digits}{exponent}?| # ".1" ".1e3" + {digits}{exponent}| # "1e3" + {digits}(?=j) # "300j" + ) + ) + j? # Optional j for cplx numbers, not captured. + (?= # Only accept the match if + $| # * At the end of the string, or + [^\w.] # * Next char is not a word char or "." + ) + """.format( + # Digits, a "." and optional more digits: "1.1". + digits_dot_maybe_digits=r'(?:[0-9]+\.(?:[0-9]*))', + # A "." with trailing digits ".23" + dot_digits=r'(?:\.[0-9]+)', + # digits: "12" + digits=r'(?:[0-9]+)', + # The exponent: An "e" or "E", optional sign, and at least one digit. + # "e-123", "E+12", "e12" + exponent=r'(?:[eE][-+]?[0-9]+)'), + re.VERBOSE) + + def __call__(self, string): + """Extracts floats from a string. + + >>> text_parts, floats = _FloatExtractor()("Text 1.0 Text") + >>> text_parts + ["Text ", " Text"] + >>> floats + np.array([1.0]) + + Args: + string: the string to extract floats from. + + Returns: + A (string, array) pair, where `string` has each float replaced by "..." + and `array` is a `float32` `numpy.array` containing the extracted floats. + """ + texts = [] + floats = [] + for i, part in enumerate(self._FLOAT_RE.split(string)): + if i % 2 == 0: + texts.append(part) + else: + floats.append(float(part)) + + return texts, np.array(floats) + + +class TfDoctestOutputChecker(doctest.OutputChecker, object): + """Changes the `want` and `got` strings. + + This allows it to be customized before they are compared. + """ + + def __init__(self, *args, **kwargs): + super(TfDoctestOutputChecker, self).__init__(*args, **kwargs) + self.extract_floats = _FloatExtractor() + self.text_good = None + self.float_size_good = None + + _ADDRESS_RE = re.compile(r'\bat 0x[0-9a-f]*?>') + + def _allclose(self, want, got, rtol=1e-6, atol=1e-6): + # Same default as: tensorflow/python/framework/test_util.py "assertAllClose" + return np.allclose(want, got, rtol=rtol, atol=atol) + + def check_output(self, want, got, optionflags): + """Compares the docstring output to the output gotten by running the code. + + Python addresses in the output are replaced with wildcards. + + Float values in the output compared as using `np.allclose`: + + * Float values are extracted from the text and replaced with wildcards. + * The wildcard text is compared to the actual output. + * The float values are compared using `np.allclose`. + + The method returns `True` if both the text comparison and the numeric + comparison are successful. + + The numeric comparison will fail if either: + + * The wrong number of floats are found. + * The float values are not within tolerence. + + Args: + want: The output in the docstring. + got: The output generated after running the snippet. + optionflags: Flags passed to the doctest. + + Returns: + A bool, indicating if the check was successful or not. + """ + + # Replace python's addresses with ellipsis (`...`) since it can change on + # each execution. + want = self._ADDRESS_RE.sub('at ...>', want) + + # Separate out the floats, and replace `want` with the wild-card version + # "result=7.0" => "result=..." + want_text_parts, self.want_floats = self.extract_floats(want) + want_text_wild = '...'.join(want_text_parts) + + # Find the floats in the string returned by the test + _, self.got_floats = self.extract_floats(got) + + self.text_good = super(TfDoctestOutputChecker, self).check_output( + want=want_text_wild, got=got, optionflags=optionflags) + if not self.text_good: + return False + + if self.want_floats.size == 0: + # If there are no floats in the "want" string, ignore all the floats in + # the result. "np.array([ ... ])" matches "np.array([ 1.0, 2.0 ])" + return True + + self.float_size_good = (self.want_floats.size == self.got_floats.size) + + if self.float_size_good: + return self._allclose(self.want_floats, self.got_floats) + else: + return False + + def output_difference(self, example, got, optionflags): + got = [got] + + # If the some of the float output is hidden with `...`, `float_size_good` + # will be False. This is because the floats extracted from the string is + # converted into a 1-D numpy array. Hence hidding floats is not allowed + # anymore. + if self.text_good: + if not self.float_size_good: + got.append("\n\nCAUTION: tf_doctest doesn't work if *some* of the " + "*float output* is hidden with a \"...\".") + + message = textwrap.dedent("""\n + ############################################################# + Check the documentation + (https://www.tensorflow.org/community/contribute/docs_ref) on how to write testable docstrings. + #############################################################""") + + got.append(message) + got = '\n'.join(got) + return (super(TfDoctestOutputChecker, + self).output_difference(example, got, optionflags)) diff --git a/tensorflow/tools/docs/tf_doctest_test.py b/tensorflow/tools/docs/tf_doctest_test.py new file mode 100644 index 00000000000..9d4fbc61e9f --- /dev/null +++ b/tensorflow/tools/docs/tf_doctest_test.py @@ -0,0 +1,176 @@ +# Lint as: python3 +# Copyright 2019 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 tf_doctest.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import doctest + +from absl.testing import absltest +from absl.testing import parameterized + +from tensorflow.tools.docs import tf_doctest_lib + + +class TfDoctestOutputCheckerTest(parameterized.TestCase): + + @parameterized.parameters( + # Don't match ints. + ['result = 1', []], + # Match floats. + ['0.0', [0.]], + ['text 1.0 text', [1.]], + ['text 1. text', [1.]], + ['text .1 text', [.1]], + ['text 1e3 text', [1000.]], + ['text 1.e3 text', [1000.]], + ['text +1. text', [1.]], + ['text -1. text', [-1.]], + ['text 1e+3 text', [1000.]], + ['text 1e-3 text', [0.001]], + ['text +1E3 text', [1000.]], + ['text -1E3 text', [-1000.]], + ['text +1e-3 text', [0.001]], + ['text -1e+3 text', [-1000.]], + # Match at the start and end of a string. + ['.1', [.1]], + ['.1 text', [.1]], + ['text .1', [.1]], + ['0.1 text', [.1]], + ['text 0.1', [.1]], + ['0. text', [0.]], + ['text 0.', [0.]], + ['1e-1 text', [.1]], + ['text 1e-1', [.1]], + # Don't match floats mixed into text + ['text1.0 text', []], + ['text 1.0text', []], + ['text1.0text', []], + ['0x12e4', []], # not 12000 + ['TensorBoard: http://128.0.0.1:8888', []], + # With a newline + ['1.0 text\n 2.0 3.0 text', [1., 2., 3.]], + # With ints and a float. + ['shape (1,2,3) value -1e9', [-1e9]], + # "." after a float. + ['No floats at end of sentence: 1.0.', []], + ['No floats with ellipsis: 1.0...', []], + # A numpy array + ["""array([[1., 2., 3.], + [4., 5., 6.]], dtype=float32)""", [1, 2, 3, 4, 5, 6] + ], + # Match both parts of a complex number + # python style + ['(0.0002+30000j)', [0.0002, 30000]], + ['(2.3e-10-3.34e+9j)', [2.3e-10, -3.34e+9]], + # numpy style + ['array([1.27+5.j])', [1.27, 5]], + ['(2.3e-10+3.34e+9j)', [2.3e-10, 3.34e+9]], + ["""array([1.27e-09+5.e+00j, + 2.30e+01-1.e-03j])""", [1.27e-09, 5.e+00, 2.30e+01, -1.e-03]], + # Check examples in tolerence. + ['1e-6', [0]], + ['0.0', [1e-6]], + ['1.000001e9', [1e9]], + ['1e9', [1.000001e9]], + ) + def test_extract_floats(self, text, expected_floats): + extract_floats = tf_doctest_lib._FloatExtractor() + output_checker = tf_doctest_lib.TfDoctestOutputChecker() + + (text_parts, extracted_floats) = extract_floats(text) + text_with_wildcards = '...'.join(text_parts) + + # Check that the lengths match before doing anything else. + try: + self.assertLen(extracted_floats, len(expected_floats)) + except AssertionError as e: + msg = '\n\n expected: {}\n found: {}'.format( + expected_floats, extracted_floats) + e.args = (e.args[0] + msg,) + raise e + + # The floats should match according to allclose + try: + self.assertTrue( + output_checker._allclose(expected_floats, extracted_floats)) + except AssertionError as e: + msg = '\n\nexpected: {}\nfound: {}'.format(expected_floats, + extracted_floats) + e.args = (e.args[0] + msg,) + raise e + + # The wildcard text should match the input text, according to the + # OutputChecker base class. + try: + self.assertTrue(doctest.OutputChecker().check_output( + want=text_with_wildcards, got=text, optionflags=doctest.ELLIPSIS)) + except AssertionError as e: + msg = '\n\n expected: {}\n found: {}'.format( + text_with_wildcards, text) + e.args = (e.args[0] + msg,) + raise e + + @parameterized.parameters( + # CHeck examples out of tolerence. + ['1.001e-6', [0]], + ['0.0', [1.001e-6]], + ['1.000001001e9', [1e9]], + ['1e9', [1.000001001e9]], + ) + def test_fail_tolerences(self, text, expected_floats): + extract_floats = tf_doctest_lib._FloatExtractor() + output_checker = tf_doctest_lib.TfDoctestOutputChecker() + + (_, extracted_floats) = extract_floats(text) + + # These floats should not match according to allclose + try: + self.assertFalse( + output_checker._allclose(expected_floats, extracted_floats)) + except AssertionError as e: + msg = ('\n\nThese matched! They should not have.\n' + '\n\n Expected: {}\n found: {}'.format( + expected_floats, extracted_floats)) + e.args = (e.args[0] + msg,) + raise e + + def test_no_floats(self): + want = 'text ... text' + got = 'text 1.0 1.2 1.9 text' + output_checker = tf_doctest_lib.TfDoctestOutputChecker() + self.assertTrue( + output_checker.check_output( + want=want, got=got, optionflags=doctest.ELLIPSIS)) + + @parameterized.parameters(['1.0, ..., 1.0', '1.0, 1.0, 1.0'], + ['1.0, 1.0..., 1.0', '1.0, 1.002, 1.0']) + def test_warning_messages(self, want, got): + output_checker = tf_doctest_lib.TfDoctestOutputChecker() + + output_checker.check_output( + want=want, got=got, optionflags=doctest.ELLIPSIS) + + example = doctest.Example('None', want=want) + result = output_checker.output_difference( + example=example, got=got, optionflags=doctest.ELLIPSIS) + self.assertIn("doesn't work if *some* of the", result) + + +if __name__ == '__main__': + absltest.main() diff --git a/tensorflow/tools/graph_transforms/BUILD b/tensorflow/tools/graph_transforms/BUILD index 06390ce9945..198859598eb 100644 --- a/tensorflow/tools/graph_transforms/BUILD +++ b/tensorflow/tools/graph_transforms/BUILD @@ -339,7 +339,9 @@ tf_py_test( name = "transform_graph_py_test", size = "small", srcs = ["python/transform_graph_test.py"], - additional_deps = [ + main = "python/transform_graph_test.py", + tags = ["v1only"], + deps = [ ":transform_graph_py", "//tensorflow/core:protos_all_py", "//tensorflow/python:client_testlib", @@ -347,6 +349,4 @@ tf_py_test( "//tensorflow/python:math_ops", "//tensorflow/python:variables", ], - main = "python/transform_graph_test.py", - tags = ["v1only"], ) diff --git a/tensorflow/tools/lib_package/BUILD b/tensorflow/tools/lib_package/BUILD index 133a906c9e3..d110cd114b5 100644 --- a/tensorflow/tools/lib_package/BUILD +++ b/tensorflow/tools/lib_package/BUILD @@ -136,10 +136,10 @@ pkg_tar( genrule( name = "clicenses_generate", srcs = [ - "//third_party/icu/data:LICENSE", - "//third_party/hadoop:LICENSE.txt", "//third_party/eigen3:LICENSE", "//third_party/fft2d:LICENSE", + "//third_party/hadoop:LICENSE.txt", + "//third_party/icu/data:LICENSE", "@boringssl//:LICENSE", "@com_googlesource_code_re2//:LICENSE", "@curl//:COPYING", @@ -207,10 +207,10 @@ genrule( genrule( name = "jnilicenses_generate", srcs = [ - "//third_party/icu/data:LICENSE", - "//third_party/hadoop:LICENSE.txt", "//third_party/eigen3:LICENSE", "//third_party/fft2d:LICENSE", + "//third_party/hadoop:LICENSE.txt", + "//third_party/icu/data:LICENSE", "@boringssl//:LICENSE", "@com_googlesource_code_re2//:LICENSE", "@curl//:COPYING", diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD index a57bd0cf7a4..891866aaa07 100644 --- a/tensorflow/tools/pip_package/BUILD +++ b/tensorflow/tools/pip_package/BUILD @@ -45,6 +45,10 @@ py_binary( deps = ["//tensorflow:tensorflow_py"], ) +# Add dynamic kernel dso files here. +DYNAMIC_LOADED_KERNELS = [ +] + COMMON_PIP_DEPS = [ ":licenses", "MANIFEST.in", @@ -118,10 +122,10 @@ filegroup( name = "licenses", data = [ "//:LICENSE", - "//third_party/icu/data:LICENSE", "//third_party/eigen3:LICENSE", "//third_party/fft2d:LICENSE", "//third_party/hadoop:LICENSE.txt", + "//third_party/icu/data:LICENSE", "@arm_neon_2_x86_sse//:LICENSE", "@astor_archive//:LICENSE", "@boringssl//:LICENSE", @@ -141,7 +145,6 @@ filegroup( "@highwayhash//:LICENSE", "@hwloc//:COPYING", "@icu//:icu4c/LICENSE", - "@keras_applications_archive//:LICENSE", "@kissfft//:COPYING", "@libjpeg_turbo//:LICENSE.md", "@lmdb//:LICENSE", @@ -224,6 +227,10 @@ sh_binary( "//conditions:default": [ ":simple_console", ], + }) + + select({ + "//tensorflow:dynamic_loaded_kernels": DYNAMIC_LOADED_KERNELS, + "//conditions:default": [], }) + if_mkl_ml(["//third_party/mkl:intel_binary_blob"]), ) diff --git a/tensorflow/tools/pip_package/setup.py b/tensorflow/tools/pip_package/setup.py index 42ffaa857fc..f33f7bd2c26 100644 --- a/tensorflow/tools/pip_package/setup.py +++ b/tensorflow/tools/pip_package/setup.py @@ -56,7 +56,7 @@ REQUIRED_PACKAGES = [ 'enum34 >= 1.1.6;python_version<"3.4"', 'gast == 0.2.2', 'google_pasta >= 0.1.8', - 'keras_applications >= 1.0.8', + 'h5py >= 2.10.0, < 2.11.0', 'keras_preprocessing >= 1.1.0', 'numpy >= 1.16.0, < 2.0', 'opt_einsum >= 2.3.2', diff --git a/tensorflow/tools/proto_text/BUILD b/tensorflow/tools/proto_text/BUILD index 611deca324f..85fba7e54fb 100644 --- a/tensorflow/tools/proto_text/BUILD +++ b/tensorflow/tools/proto_text/BUILD @@ -39,8 +39,8 @@ cc_binary( deps = [ ":gen_proto_text_functions_lib", "@com_google_protobuf//:protobuf", - "//tensorflow/core:lib_proto_parsing", "//tensorflow/core/platform:protobuf_compiler", + "//tensorflow/core:lib_proto_parsing", ] + if_ios(["//tensorflow/core/platform/default/build_config:logging"]), ) diff --git a/tensorflow/tools/test/BUILD b/tensorflow/tools/test/BUILD index b21f3742c23..936f35f1435 100644 --- a/tensorflow/tools/test/BUILD +++ b/tensorflow/tools/test/BUILD @@ -87,7 +87,7 @@ py_library( #cuda_py_test( # name = "run_and_gather_logs_test", # srcs = ["run_and_gather_logs.py"], -# additional_deps = [ +# deps = [ # ":run_and_gather_logs", # ], # args = [ diff --git a/tensorflow/tools/test/performance.bzl b/tensorflow/tools/test/performance.bzl index 538d3d870a6..c496b971db6 100644 --- a/tensorflow/tools/test/performance.bzl +++ b/tensorflow/tools/test/performance.bzl @@ -39,7 +39,7 @@ def tf_cc_logged_benchmark( target, ], main = "run_and_gather_logs.py", - additional_deps = [ + deps = [ "//tensorflow/tools/test:run_and_gather_logs", ], **kwargs diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl index 1fb148e078a..1ac2b795b7a 100755 --- a/tensorflow/workspace.bzl +++ b/tensorflow/workspace.bzl @@ -22,8 +22,12 @@ load( "def_file_filter_configure", ) load("//third_party/FP16:workspace.bzl", FP16 = "repo") +load("//third_party/FXdiv:workspace.bzl", FXdiv = "repo") load("//third_party/aws:workspace.bzl", aws = "repo") +load("//third_party/clog:workspace.bzl", clog = "repo") +load("//third_party/cpuinfo:workspace.bzl", cpuinfo = "repo") load("//third_party/flatbuffers:workspace.bzl", flatbuffers = "repo") +load("//third_party/hexagon:workspace.bzl", hexagon_nn = "repo") load("//third_party/highwayhash:workspace.bzl", highwayhash = "repo") load("//third_party/hwloc:workspace.bzl", hwloc = "repo") load("//third_party/icu:workspace.bzl", icu = "repo") @@ -31,23 +35,29 @@ load("//third_party/jpeg:workspace.bzl", jpeg = "repo") load("//third_party/nasm:workspace.bzl", nasm = "repo") load("//third_party/opencl_headers:workspace.bzl", opencl_headers = "repo") load("//third_party/kissfft:workspace.bzl", kissfft = "repo") -load("//third_party/keras_applications_archive:workspace.bzl", keras_applications = "repo") load("//third_party/pasta:workspace.bzl", pasta = "repo") +load("//third_party/psimd:workspace.bzl", psimd = "repo") +load("//third_party/pthreadpool:workspace.bzl", pthreadpool = "repo") def initialize_third_party(): """ Load third party repositories. See above load() statements. """ FP16() + FXdiv() aws() + clog() + cpuinfo() flatbuffers() + hexagon_nn() highwayhash() hwloc() icu() - keras_applications() kissfft() jpeg() nasm() opencl_headers() pasta() + psimd() + pthreadpool() # Sanitize a dependency so that it works correctly from code that includes # TensorFlow as a submodule. @@ -127,6 +137,16 @@ def tf_repositories(path_prefix = "", tf_repo_name = ""): print("path_prefix was specified to tf_workspace but is no longer used " + "and will be removed in the future.") + tf_http_archive( + name = "XNNPACK", + sha256 = "24b6285c679dece8805d2a7d63cc567413b7670279bc0c66a99e555123fe4700", + strip_prefix = "XNNPACK-9a88efe2d84fef93eb2b8acb6f0ac8f3cacee8b5", + urls = [ + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/google/XNNPACK/archive/9a88efe2d84fef93eb2b8acb6f0ac8f3cacee8b5.zip", + "https://github.com/google/XNNPACK/archive/9a88efe2d84fef93eb2b8acb6f0ac8f3cacee8b5.zip", + ], + ) + # Important: If you are upgrading MKL-DNN, then update the version numbers # in third_party/mkl_dnn/mkldnn.BUILD. In addition, the new version of # MKL-DNN might require upgrading MKL ML libraries also. If they need to be @@ -172,11 +192,11 @@ def tf_repositories(path_prefix = "", tf_repo_name = ""): name = "eigen_archive", build_file = clean_dep("//third_party:eigen.BUILD"), patch_file = clean_dep("//third_party/eigen3:gpu_packet_math.patch"), - sha256 = "65d732985b593b553c20566e1f236f48dcc626730c418aed7b2aa1d0e3f1a0af", - strip_prefix = "eigen-4e696901f873a2347f76d931cf2f701e31e15d05", + sha256 = "f64712872161624ae80c0d3e53f7336e125e833660c10505961905b47728673d", + strip_prefix = "eigen-963ba1015bd448a9128ccb24f07232f36962e488", urls = [ - "https://storage.googleapis.com/mirror.tensorflow.org/gitlab.com/libeigen/eigen/-/archive/4e696901f873a2347f76d931cf2f701e31e15d05/eigen-4e696901f873a2347f76d931cf2f701e31e15d05.tar.gz", - "https://gitlab.com/libeigen/eigen/-/archive/4e696901f873a2347f76d931cf2f701e31e15d05/eigen-4e696901f873a2347f76d931cf2f701e31e15d05.tar.gz", + "https://storage.googleapis.com/mirror.tensorflow.org/gitlab.com/libeigen/eigen/-/archive/963ba1015bd448a9128ccb24f07232f36962e488/eigen-963ba1015bd448a9128ccb24f07232f36962e488.tar.gz", + "https://gitlab.com/libeigen/eigen/-/archive/963ba1015bd448a9128ccb24f07232f36962e488/eigen-963ba1015bd448a9128ccb24f07232f36962e488.tar.gz", ], ) @@ -215,15 +235,15 @@ def tf_repositories(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "com_github_googlecloudplatform_google_cloud_cpp", - sha256 = "295754023a44eb69d0ff5ee2c0ac11ff1b7adcd617f122d57fc7a5a49fac612d", - strip_prefix = "google-cloud-cpp-0.14.0", + sha256 = "e86a7190e87371259083595d756399f494b2257706a2b773c2917ec796f41d9a", + strip_prefix = "google-cloud-cpp-0.16.0", system_build_file = clean_dep("//third_party/systemlibs:google_cloud_cpp.BUILD"), system_link_files = { "//third_party/systemlibs:google_cloud_cpp.google.cloud.bigtable.BUILD": "google/cloud/bigtable/BUILD", }, urls = [ - "https://storage.googleapis.com/mirror.tensorflow.org/github.com/googleapis/google-cloud-cpp/archive/v0.14.0.tar.gz", - "https://github.com/googleapis/google-cloud-cpp/archive/v0.14.0.tar.gz", + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/googleapis/google-cloud-cpp/archive/v0.16.0.tar.gz", + "https://github.com/googleapis/google-cloud-cpp/archive/v0.16.0.tar.gz", ], ) @@ -549,11 +569,11 @@ def tf_repositories(path_prefix = "", tf_repo_name = ""): tf_http_archive( name = "llvm", build_file = clean_dep("//third_party/llvm:llvm.autogenerated.BUILD"), - sha256 = "6bafd092643a7208a6539d355e5f99fc7bbd6424e14288d9354515a5231fd2e8", - strip_prefix = "llvm-project-4e8231b5cf0f5f62c7a51a857e29f5be5cb55734/llvm", + sha256 = "b4acae8e7d9c66521dbb30db3edc221a4ef6f3ef48b783a0141094d9de0c7380", + strip_prefix = "llvm-project-5bcd34a03ff343674c106b9a6a0406bf249b9b31/llvm", urls = [ - "https://storage.googleapis.com/mirror.tensorflow.org/github.com/llvm/llvm-project/archive/4e8231b5cf0f5f62c7a51a857e29f5be5cb55734.tar.gz", - "https://github.com/llvm/llvm-project/archive/4e8231b5cf0f5f62c7a51a857e29f5be5cb55734.tar.gz", + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/llvm/llvm-project/archive/5bcd34a03ff343674c106b9a6a0406bf249b9b31.tar.gz", + "https://github.com/llvm/llvm-project/archive/5bcd34a03ff343674c106b9a6a0406bf249b9b31.tar.gz", ], ) diff --git a/third_party/keras_applications_archive/BUILD b/third_party/FXdiv/BUILD similarity index 100% rename from third_party/keras_applications_archive/BUILD rename to third_party/FXdiv/BUILD diff --git a/third_party/FXdiv/BUILD.bazel b/third_party/FXdiv/BUILD.bazel new file mode 100644 index 00000000000..ef2853b7ceb --- /dev/null +++ b/third_party/FXdiv/BUILD.bazel @@ -0,0 +1,15 @@ +# Description: +# C99/C++ library for division via fixed-point multiplication by inverse + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +cc_library( + name = "FXdiv", + hdrs = glob(["include/fxdiv.h"]), + includes = ["include"], + strip_include_prefix = "include", +) diff --git a/third_party/FXdiv/workspace.bzl b/third_party/FXdiv/workspace.bzl new file mode 100644 index 00000000000..91a6a6ce860 --- /dev/null +++ b/third_party/FXdiv/workspace.bzl @@ -0,0 +1,15 @@ +"""Loads the FXdiv library, used by XNNPACK & pthreadpool.""" + +load("//third_party:repo.bzl", "third_party_http_archive") + +def repo(): + third_party_http_archive( + name = "FXdiv", + strip_prefix = "FXdiv-f8c5354679ec2597792bc70a9e06eff50c508b9a", + sha256 = "7d3215bea832fe77091ec5666200b91156df6724da1e348205078346325fc45e", + urls = [ + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/Maratyszcza/FXdiv/archive/f8c5354679ec2597792bc70a9e06eff50c508b9a.tar.gz", + "https://github.com/Maratyszcza/FXdiv/archive/f8c5354679ec2597792bc70a9e06eff50c508b9a.tar.gz", + ], + build_file = "//third_party/FXdiv:BUILD.bazel", + ) diff --git a/third_party/clog/BUILD b/third_party/clog/BUILD new file mode 100644 index 00000000000..82bab3ffd96 --- /dev/null +++ b/third_party/clog/BUILD @@ -0,0 +1 @@ +# This empty BUILD file is required to make Bazel treat this directory as a package. diff --git a/third_party/clog/BUILD.bazel b/third_party/clog/BUILD.bazel new file mode 100644 index 00000000000..e2b20a8946d --- /dev/null +++ b/third_party/clog/BUILD.bazel @@ -0,0 +1,35 @@ +# Description: +# C-style (a-la printf) logging library + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +cc_library( + name = "clog", + srcs = [ + "deps/clog/src/clog.c", + ], + hdrs = [ + "deps/clog/include/clog.h", + ], + copts = [ + "-Wno-unused-result", + ], + linkopts = select({ + ":android": [ + "-llog", + ], + "//conditions:default": [ + ], + }), + strip_include_prefix = "deps/clog/include", +) + +config_setting( + name = "android", + values = {"crosstool_top": "//external:android/crosstool"}, + visibility = ["//visibility:public"], +) diff --git a/third_party/clog/workspace.bzl b/third_party/clog/workspace.bzl new file mode 100644 index 00000000000..05b29805328 --- /dev/null +++ b/third_party/clog/workspace.bzl @@ -0,0 +1,15 @@ +"""Loads the clog library, used by cpuinfo and XNNPACK.""" + +load("//third_party:repo.bzl", "third_party_http_archive") + +def repo(): + third_party_http_archive( + name = "clog", + strip_prefix = "cpuinfo-d5e37adf1406cf899d7d9ec1d317c47506ccb970", + sha256 = "3f2dc1970f397a0e59db72f9fca6ff144b216895c1d606f6c94a507c1e53a025", + urls = [ + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/pytorch/cpuinfo/archive/d5e37adf1406cf899d7d9ec1d317c47506ccb970.tar.gz", + "https://github.com/pytorch/cpuinfo/archive/d5e37adf1406cf899d7d9ec1d317c47506ccb970.tar.gz", + ], + build_file = "//third_party/clog:BUILD.bazel", + ) diff --git a/third_party/cpuinfo/BUILD b/third_party/cpuinfo/BUILD new file mode 100644 index 00000000000..82bab3ffd96 --- /dev/null +++ b/third_party/cpuinfo/BUILD @@ -0,0 +1 @@ +# This empty BUILD file is required to make Bazel treat this directory as a package. diff --git a/third_party/cpuinfo/BUILD.bazel b/third_party/cpuinfo/BUILD.bazel new file mode 100644 index 00000000000..ccec4c2d872 --- /dev/null +++ b/third_party/cpuinfo/BUILD.bazel @@ -0,0 +1,222 @@ +# cpuinfo, a library to detect information about the host CPU +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +C99OPTS = [ + "-std=gnu99", # gnu99, not c99, because dprintf is used + "-Wno-vla", + "-D_GNU_SOURCE=1", # to use CPU_SETSIZE + "-DCPUINFO_INTERNAL=", + "-DCPUINFO_PRIVATE=", +] + +# Source code common to all platforms. +COMMON_SRCS = [ + "src/api.c", + "src/init.c", +] + +# Architecture-specific sources and headers. +X86_SRCS = [ + "src/x86/cache/descriptor.c", + "src/x86/cache/deterministic.c", + "src/x86/cache/init.c", + "src/x86/info.c", + "src/x86/init.c", + "src/x86/isa.c", + "src/x86/name.c", + "src/x86/topology.c", + "src/x86/uarch.c", + "src/x86/vendor.c", +] + +ARM_SRCS = [ + "src/arm/cache.c", + "src/arm/uarch.c", +] + +# Platform-specific sources and headers +LINUX_SRCS = [ + "src/linux/cpulist.c", + "src/linux/current.c", + "src/linux/multiline.c", + "src/linux/processors.c", + "src/linux/smallfile.c", +] + +MOCK_LINUX_SRCS = [ + "src/linux/mockfile.c", +] + +MACH_SRCS = [ + "src/mach/topology.c", +] + +EMSCRIPTEN_SRCS = [ + "src/emscripten/init.c", +] + +PNACL_SRCS = [ + "src/pnacl/init.c", +] + +LINUX_X86_SRCS = [ + "src/x86/linux/cpuinfo.c", + "src/x86/linux/init.c", +] + +LINUX_ARM_SRCS = [ + "src/arm/linux/chipset.c", + "src/arm/linux/clusters.c", + "src/arm/linux/cpuinfo.c", + "src/arm/linux/hwcap.c", + "src/arm/linux/init.c", + "src/arm/linux/midr.c", +] + +LINUX_ARM32_SRCS = LINUX_ARM_SRCS + ["src/arm/linux/aarch32-isa.c"] + +LINUX_ARM64_SRCS = LINUX_ARM_SRCS + ["src/arm/linux/aarch64-isa.c"] + +ANDROID_ARM_SRCS = [ + "src/arm/android/properties.c", +] + +WINDOWS_X86_SRCS = [ + "src/x86/windows/init.c", +] + +MACH_X86_SRCS = [ + "src/x86/mach/init.c", +] + +MACH_ARM_SRCS = [ + "src/arm/mach/init.c", +] + +cc_library( + name = "cpuinfo_impl", + srcs = select({ + ":linux_x86_64": COMMON_SRCS + X86_SRCS + LINUX_SRCS + LINUX_X86_SRCS, + ":macos_x86_64": COMMON_SRCS + X86_SRCS + MACH_SRCS + MACH_X86_SRCS, + ":android_armv7": COMMON_SRCS + ARM_SRCS + LINUX_SRCS + LINUX_ARM32_SRCS + ANDROID_ARM_SRCS, + ":android_arm64": COMMON_SRCS + ARM_SRCS + LINUX_SRCS + LINUX_ARM64_SRCS + ANDROID_ARM_SRCS, + ":android_x86": COMMON_SRCS + X86_SRCS + LINUX_SRCS + LINUX_X86_SRCS, + ":android_x86_64": COMMON_SRCS + X86_SRCS + LINUX_SRCS + LINUX_X86_SRCS, + ":emscripten_wasm": COMMON_SRCS + EMSCRIPTEN_SRCS, + }), + copts = C99OPTS + [ + "-Iexternal/cpuinfo/include", + "-Iexternal/cpuinfo/src", + ], + linkstatic = True, + # Headers must be in textual_hdrs to allow us to set the standard to C99 + textual_hdrs = [ + "include/cpuinfo.h", + "src/linux/api.h", + "src/mach/api.h", + "src/cpuinfo/common.h", + "src/cpuinfo/internal-api.h", + "src/cpuinfo/log.h", + "src/cpuinfo/utils.h", + "src/x86/api.h", + "src/x86/cpuid.h", + "src/x86/linux/api.h", + "src/arm/android/api.h", + "src/arm/linux/api.h", + "src/arm/linux/cp.h", + "src/arm/api.h", + "src/arm/midr.h", + ], + deps = [ + "@clog", + ], +) + +cc_library( + name = "cpuinfo", + hdrs = [ + "include/cpuinfo.h", + ], + strip_include_prefix = "include", + deps = [ + ":cpuinfo_impl", + ], +) + +############################# Build configurations ############################# + +config_setting( + name = "linux_x86_64", + values = {"cpu": "k8"}, + visibility = ["//visibility:public"], +) + +config_setting( + name = "macos_x86_64", + values = { + "apple_platform_type": "macos", + "cpu": "darwin", + }, +) + +config_setting( + name = "android_armv7", + values = { + "crosstool_top": "//external:android/crosstool", + "cpu": "armeabi-v7a", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "android_arm64", + values = { + "crosstool_top": "//external:android/crosstool", + "cpu": "arm64-v8a", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "android_x86", + values = { + "crosstool_top": "//external:android/crosstool", + "cpu": "x86", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "android_x86_64", + values = { + "crosstool_top": "//external:android/crosstool", + "cpu": "x86_64", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "emscripten_wasm", + values = { + "cpu": "wasm", + }, +) + +config_setting( + name = "emscripten_wasmsimd", + values = { + "cpu": "wasm", + "features": "wasm_simd", + }, +) + +config_setting( + name = "emscripten_asmjs", + values = { + "cpu": "asmjs", + }, +) diff --git a/third_party/cpuinfo/workspace.bzl b/third_party/cpuinfo/workspace.bzl new file mode 100644 index 00000000000..25b85c645b2 --- /dev/null +++ b/third_party/cpuinfo/workspace.bzl @@ -0,0 +1,15 @@ +"""Loads the cpuinfo library, used by XNNPACK.""" + +load("//third_party:repo.bzl", "third_party_http_archive") + +def repo(): + third_party_http_archive( + name = "cpuinfo", + strip_prefix = "cpuinfo-d5e37adf1406cf899d7d9ec1d317c47506ccb970", + sha256 = "3f2dc1970f397a0e59db72f9fca6ff144b216895c1d606f6c94a507c1e53a025", + urls = [ + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/pytorch/cpuinfo/archive/d5e37adf1406cf899d7d9ec1d317c47506ccb970.tar.gz", + "https://github.com/pytorch/cpuinfo/archive/d5e37adf1406cf899d7d9ec1d317c47506ccb970.tar.gz", + ], + build_file = "//third_party/cpuinfo:BUILD.bazel", + ) diff --git a/third_party/eigen3/gpu_packet_math.patch b/third_party/eigen3/gpu_packet_math.patch index 50ac056df79..1b6131abd41 100644 --- a/third_party/eigen3/gpu_packet_math.patch +++ b/third_party/eigen3/gpu_packet_math.patch @@ -22,4 +22,161 @@ return res; } }; - \ No newline at end of file +--- a/unsupported/Eigen/SpecialFunctions ++++ b/unsupported/Eigen/SpecialFunctions +@@ -48,6 +48,9 @@ + } + + #include "src/SpecialFunctions/SpecialFunctionsImpl.h" ++#if defined(EIGEN_HIPCC) ++#include "src/SpecialFunctions/HipVectorCompatibility.h" ++#endif + #include "src/SpecialFunctions/SpecialFunctionsPacketMath.h" + #include "src/SpecialFunctions/SpecialFunctionsHalf.h" + #include "src/SpecialFunctions/SpecialFunctionsFunctors.h" +--- /dev/null ++++ b/unsupported/Eigen/src/SpecialFunctions/HipVectorCompatibility.h +@@ -0,0 +1,143 @@ ++#ifndef HIP_VECTOR_COMPATIBILITY_H ++#define HIP_VECTOR_COMPATIBILITY_H ++ ++namespace hip_impl { ++ template struct Scalar_accessor; ++} // end namespace hip_impl ++ ++namespace Eigen { ++namespace internal { ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct lgamma_impl> : lgamma_impl {}; ++#endif ++ ++template ++struct digamma_impl_maybe_poly> ++ : digamma_impl_maybe_poly {}; ++ ++template ++struct digamma_impl> : digamma_impl {}; ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct erf_impl> : erf_impl {}; ++#endif // EIGEN_HAS_C99_MATH ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct erfc_impl> : erfc_impl {}; ++#endif // EIGEN_HAS_C99_MATH ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct ndtri_impl> : ndtri_impl {}; ++#endif // EIGEN_HAS_C99_MATH ++ ++template ++struct igammac_cf_impl, mode> ++ : igammac_cf_impl {}; ++ ++template ++struct igamma_series_impl, mode> ++ : igamma_series_impl {}; ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct igammac_impl> : igammac_impl {}; ++#endif // EIGEN_HAS_C99_MATH ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct igamma_generic_impl, mode> ++ : igamma_generic_impl {}; ++#endif // EIGEN_HAS_C99_MATH ++ ++template ++struct igamma_impl> : igamma_impl {}; ++ ++template ++struct igamma_der_a_retval> ++ : igamma_der_a_retval {}; ++ ++template ++struct igamma_der_a_impl> ++ : igamma_der_a_impl {}; ++ ++template ++struct gamma_sample_der_alpha_retval> ++ : gamma_sample_der_alpha_retval {}; ++ ++template ++struct gamma_sample_der_alpha_impl> ++ : gamma_sample_der_alpha_impl {}; ++ ++template ++struct zeta_impl_series> ++ : zeta_impl_series {}; ++ ++template ++struct zeta_impl> : zeta_impl {}; ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct polygamma_impl> ++ : polygamma_impl {}; ++#endif // EIGEN_HAS_C99_MATH ++ ++#if EIGEN_HAS_C99_MATH ++template ++struct betainc_impl> : betainc_impl {}; ++ ++template ++struct incbeta_cfe> : incbeta_cfe {}; ++ ++template ++struct betainc_helper> ++ : betainc_helper {}; ++#else ++template ++struct betainc_impl> : betainc_impl {}; ++#endif // EIGEN_HAS_C99_MATH ++ ++template ++struct bessel_i0e_impl> : bessel_i0e_impl {}; ++ ++template ++struct bessel_i0_impl> : bessel_i0_impl {}; ++ ++template ++struct bessel_i1e_impl> : bessel_i1e_impl {}; ++ ++template ++struct bessel_i1_impl> : bessel_i1_impl {}; ++ ++template ++struct bessel_k0e_impl> : bessel_k0e_impl {}; ++ ++template ++struct bessel_k0_impl> : bessel_k0_impl {}; ++ ++template ++struct bessel_k1e_impl> : bessel_k1e_impl {}; ++ ++template ++struct bessel_k1_impl> : bessel_k1_impl {}; ++ ++template ++struct bessel_j0_impl> : bessel_j0_impl {}; ++ ++template ++struct bessel_y0_impl> : bessel_y0_impl {}; ++ ++template ++struct bessel_j1_impl> : bessel_j1_impl {}; ++ ++template ++struct bessel_y1_impl> : bessel_y1_impl {}; ++ ++} // end namespace internal ++} // end namespace Eigen ++ ++#endif // HIP_VECTOR_COMPATIBILITY_H diff --git a/third_party/hexagon/BUILD b/third_party/hexagon/BUILD new file mode 100644 index 00000000000..719cc9100e7 --- /dev/null +++ b/third_party/hexagon/BUILD @@ -0,0 +1,48 @@ +# Copyright 2019 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(default_visibility = [ + "//visibility:public", +]) + +licenses([ + "notice", # BSD-3-Clause-Clear +]) + +exports_files(glob(["hexagon/**/*.so"])) + +#Just header file, needed for data types in the interface. +cc_library( + name = "hexagon_nn_header", + hdrs = [ + "hexagon/hexagon_nn.h", + ], + tags = [ + "manual", + "nobuilder", + ], +) + +cc_library( + name = "hexagon_nn_ops", + hdrs = [ + "hexagon/hexagon_nn_ops.h", + "hexagon/ops.def", + ], + tags = [ + "manual", + "nobuilder", + ], +) diff --git a/third_party/hexagon/LICENSE b/third_party/hexagon/LICENSE new file mode 100644 index 00000000000..624e16e8fbf --- /dev/null +++ b/third_party/hexagon/LICENSE @@ -0,0 +1,35 @@ + +/* + * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted (subject to the limitations in the + * disclaimer below) provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE + * GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT + * HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ diff --git a/third_party/hexagon/workspace.bzl b/third_party/hexagon/workspace.bzl new file mode 100644 index 00000000000..0e7fb527e42 --- /dev/null +++ b/third_party/hexagon/workspace.bzl @@ -0,0 +1,13 @@ +"""Loads the Hexagon NN Header files library, used by TF Lite.""" + +load("//third_party:repo.bzl", "third_party_http_archive") + +def repo(): + third_party_http_archive( + name = "hexagon_nn", + sha256 = "e972f86eb8bcfb1ee93ff3dc7aa4518948e3941b5ea0945f5c9307b2d3334225", + urls = [ + "http://mirror.tensorflow.org/storage.cloud.google.com/download.tensorflow.org/tflite/hexagon_nn_headers_v1.10.3.1.0.tgz", + ], + build_file = "//third_party/hexagon:BUILD", + ) diff --git a/third_party/hwloc/BUILD.bazel b/third_party/hwloc/BUILD.bazel index 3f93910ed75..091ec7059df 100644 --- a/third_party/hwloc/BUILD.bazel +++ b/third_party/hwloc/BUILD.bazel @@ -34,10 +34,10 @@ _INCLUDE_HWLOC_AUTOIGEN_CONFIG_H_COMMON_SUBS = { "#undef HWLOC_VERSION": "#define HWLOC_VERSION \"2.0.3\"", "#undef hwloc_pid_t": "#define hwloc_pid_t pid_t", "#undef hwloc_thread_t": "#define hwloc_thread_t pthread_t", - "# undef HWLOC_HAVE_STDINT_H": "# define HWLOC_HAVE_STDINT_H 1 ", + "# undef HWLOC_HAVE_STDINT_H": "# define HWLOC_HAVE_STDINT_H 1", "#undef HWLOC_SYM_TRANSFORM": "#define HWLOC_SYM_TRANSFORM 0", - "#undef HWLOC_SYM_PREFIX": "#define HWLOC_SYM_PREFIX hwloc_", "#undef HWLOC_SYM_PREFIX_CAPS": "#define HWLOC_SYM_PREFIX_CAPS HWLOC_", + "#undef HWLOC_SYM_PREFIX": "#define HWLOC_SYM_PREFIX hwloc_", } _INCLUDE_HWLOC_AUTOIGEN_CONFIG_H_LINUX_SUBS = dict(_INCLUDE_HWLOC_AUTOIGEN_CONFIG_H_COMMON_SUBS) @@ -83,8 +83,8 @@ _INCLUDE_PRIVATE_HWLOC_AUTOIGEN_CONFIG_H_COMMON_SUBS = { "#undef HAVE_DECL__STRDUP": "#define HAVE_DECL__STRDUP 0", "#undef HAVE_DIRENT_H": "#define HAVE_DIRENT_H 1", "#undef HAVE_DLFCN_H": "#define HAVE_DLFCN_H 1", - "#undef HAVE_FFS": "#define HAVE_FFS 1", "#undef HAVE_FFSL": "#define HAVE_FFSL 1", + "#undef HAVE_FFS": "#define HAVE_FFS 1", "#undef HAVE_GETPAGESIZE": "#define HAVE_GETPAGESIZE 1", "#undef HAVE_INTTYPES_H": "#define HAVE_INTTYPES_H 1", "#undef HAVE_LANGINFO_H": "#define HAVE_LANGINFO_H 1", @@ -123,7 +123,6 @@ _INCLUDE_PRIVATE_HWLOC_AUTOIGEN_CONFIG_H_COMMON_SUBS = { "#undef HAVE_X11_XUTIL_H": "#define HAVE_X11_XUTIL_H 1", "#undef HAVE___PROGNAME": "#define HAVE___PROGNAME 1", "#undef HWLOC_C_HAVE_VISIBILITY": "#define HWLOC_C_HAVE_VISIBILITY 1", - "#undef HWLOC_HAVE_ATTRIBUTE": "#define HWLOC_HAVE_ATTRIBUTE 1", "#undef HWLOC_HAVE_ATTRIBUTE_ALIGNED": "#define HWLOC_HAVE_ATTRIBUTE_ALIGNED 1", "#undef HWLOC_HAVE_ATTRIBUTE_ALWAYS_INLINE": "#define HWLOC_HAVE_ATTRIBUTE_ALWAYS_INLINE 1", "#undef HWLOC_HAVE_ATTRIBUTE_COLD": "#define HWLOC_HAVE_ATTRIBUTE_COLD 1", @@ -142,14 +141,15 @@ _INCLUDE_PRIVATE_HWLOC_AUTOIGEN_CONFIG_H_COMMON_SUBS = { "#undef HWLOC_HAVE_ATTRIBUTE_UNUSED": "#define HWLOC_HAVE_ATTRIBUTE_UNUSED 1", "#undef HWLOC_HAVE_ATTRIBUTE_WARN_UNUSED_RESULT": "#define HWLOC_HAVE_ATTRIBUTE_WARN_UNUSED_RESULT 1", "#undef HWLOC_HAVE_ATTRIBUTE_WEAK_ALIAS": "#define HWLOC_HAVE_ATTRIBUTE_WEAK_ALIAS 1", - "#undef HWLOC_HAVE_CPU_SET": "#define HWLOC_HAVE_CPU_SET 1", + "#undef HWLOC_HAVE_ATTRIBUTE": "#define HWLOC_HAVE_ATTRIBUTE 1", "#undef HWLOC_HAVE_CPU_SET_S": "#define HWLOC_HAVE_CPU_SET_S 1", - "#undef HWLOC_HAVE_DECL_FFS": "#define HWLOC_HAVE_DECL_FFS 1", + "#undef HWLOC_HAVE_CPU_SET": "#define HWLOC_HAVE_CPU_SET 1", "#undef HWLOC_HAVE_DECL_FFSL": "#define HWLOC_HAVE_DECL_FFSL 1", + "#undef HWLOC_HAVE_DECL_FFS": "#define HWLOC_HAVE_DECL_FFS 1", "#undef HWLOC_HAVE_DECL_STRCASECMP": "#define HWLOC_HAVE_DECL_STRCASECMP 1", "#undef HWLOC_HAVE_DECL_STRNCASECMP": "#define HWLOC_HAVE_DECL_STRNCASECMP 1", - "#undef HWLOC_HAVE_FFS": "#define HWLOC_HAVE_FFS 1", "#undef HWLOC_HAVE_FFSL": "#define HWLOC_HAVE_FFSL 1", + "#undef HWLOC_HAVE_FFS": "#define HWLOC_HAVE_FFS 1", "#undef HWLOC_HAVE_LIBTERMCAP": "#define HWLOC_HAVE_LIBTERMCAP 1", "#undef HWLOC_HAVE_LINUXIO": "#define HWLOC_HAVE_LINUXIO 1", "#undef HWLOC_HAVE_PTHREAD_MUTEX": "#define HWLOC_HAVE_PTHREAD_MUTEX 1", @@ -160,24 +160,24 @@ _INCLUDE_PRIVATE_HWLOC_AUTOIGEN_CONFIG_H_COMMON_SUBS = { "#undef HWLOC_HAVE_X86_CPUID": "#define HWLOC_HAVE_X86_CPUID 1", "#undef HWLOC_SIZEOF_UNSIGNED_INT": "#define HWLOC_SIZEOF_UNSIGNED_INT 4", "#undef HWLOC_SIZEOF_UNSIGNED_LONG": "#define HWLOC_SIZEOF_UNSIGNED_LONG 8", - "#undef HWLOC_SYM_PREFIX": "#define HWLOC_SYM_PREFIX hwloc_", "#undef HWLOC_SYM_PREFIX_CAPS": "#define HWLOC_SYM_PREFIX_CAPS HWLOC_", + "#undef HWLOC_SYM_PREFIX": "#define HWLOC_SYM_PREFIX hwloc_", "#undef HWLOC_SYM_TRANSFORM": "#define HWLOC_SYM_TRANSFORM 0", "#undef HWLOC_USE_NCURSES": "#define HWLOC_USE_NCURSES 1", - "#undef HWLOC_VERSION": "#define HWLOC_VERSION \"2.0.3\"", "#undef HWLOC_VERSION_GREEK": "#define HWLOC_VERSION_GREEK \"\"", "#undef HWLOC_VERSION_MAJOR": "#define HWLOC_VERSION_MAJOR 2", "#undef HWLOC_VERSION_MINOR": "#define HWLOC_VERSION_MINOR 0", "#undef HWLOC_VERSION_RELEASE": "#define HWLOC_VERSION_RELEASE 3", + "#undef HWLOC_VERSION": "#define HWLOC_VERSION \"2.0.3\"", "#undef HWLOC_X86_64_ARCH": "#define HWLOC_X86_64_ARCH 1", "#undef LT_OBJDIR": "#define LT_OBJDIR \".libs/\"", - "#undef PACKAGE": "#define PACKAGE \"hwloc\"", - "#undef PACKAGE_BUGREPORT": "#define PACKAGE_BUGREPORT \"http://github.com/open-mpi/hwloc/i", + "#undef PACKAGE_BUGREPORT": "#define PACKAGE_BUGREPORT \"http://github.com/open-mpi/hwloc/issues", "#undef PACKAGE_NAME": "#define PACKAGE_NAME \"hwloc\"", "#undef PACKAGE_STRING": "#define PACKAGE_STRING \"hwloc 2.0.3\"", "#undef PACKAGE_TARNAME": "#define PACKAGE_TARNAME \"hwloc\"", "#undef PACKAGE_URL": "#define PACKAGE_URL \"\"", "#undef PACKAGE_VERSION": "#define PACKAGE_VERSION \"2.0.3\"", + "#undef PACKAGE": "#define PACKAGE \"hwloc\"", "#undef SIZEOF_UNSIGNED_INT": "#define SIZEOF_UNSIGNED_INT 4", "#undef SIZEOF_UNSIGNED_LONG": "#define SIZEOF_UNSIGNED_LONG 8", "#undef SIZEOF_VOID_P": "#define SIZEOF_VOID_P 8", diff --git a/third_party/hwloc/static-components.h b/third_party/hwloc/static-components.h index 0c9a621fd74..cc2948cabb2 100644 --- a/third_party/hwloc/static-components.h +++ b/third_party/hwloc/static-components.h @@ -22,7 +22,7 @@ static const struct hwloc_component* hwloc_static_components[] = { &hwloc_xml_component, &hwloc_synthetic_component, &hwloc_xml_nolibxml_component, -#ifdef __Linux__ +#ifdef __linux__ &hwloc_linux_component, &hwloc_linuxio_component, #endif diff --git a/third_party/keras_applications_archive/BUILD.bazel b/third_party/keras_applications_archive/BUILD.bazel deleted file mode 100644 index 8578f63eec8..00000000000 --- a/third_party/keras_applications_archive/BUILD.bazel +++ /dev/null @@ -1,35 +0,0 @@ -# Description: Keras Applications: set of pre-trained deep learning models. - -package( - default_visibility = ["//visibility:public"], -) - -licenses(["notice"]) # MIT - -exports_files(["LICENSE"]) - -py_library( - name = "keras_applications", - srcs = [ - "keras_applications/__init__.py", - "keras_applications/densenet.py", - "keras_applications/imagenet_utils.py", - "keras_applications/inception_resnet_v2.py", - "keras_applications/inception_v3.py", - "keras_applications/mobilenet.py", - "keras_applications/mobilenet_v2.py", - "keras_applications/nasnet.py", - "keras_applications/resnet.py", - "keras_applications/resnet50.py", - "keras_applications/resnet_common.py", - "keras_applications/resnet_v2.py", - "keras_applications/resnext.py", - "keras_applications/vgg16.py", - "keras_applications/vgg19.py", - "keras_applications/xception.py", - ], - deps = [ - "@org_tensorflow//third_party/py/numpy", - "@six_archive//:six", - ], -) diff --git a/third_party/keras_applications_archive/BUILD.system b/third_party/keras_applications_archive/BUILD.system deleted file mode 100644 index a3b58f15030..00000000000 --- a/third_party/keras_applications_archive/BUILD.system +++ /dev/null @@ -1,13 +0,0 @@ -# Description: Keras Applications: set of pre-trained deep learning models. - -licenses(["notice"]) # MIT - -filegroup( - name = "LICENSE", - visibility = ["//visibility:public"], -) - -py_library( - name = "keras_applications", - visibility = ["//visibility:public"], -) diff --git a/third_party/keras_applications_archive/workspace.bzl b/third_party/keras_applications_archive/workspace.bzl deleted file mode 100644 index bd92f18a9f2..00000000000 --- a/third_party/keras_applications_archive/workspace.bzl +++ /dev/null @@ -1,16 +0,0 @@ -"""Loads Keras-applications python package.""" - -load("//third_party:repo.bzl", "third_party_http_archive") - -def repo(): - third_party_http_archive( - name = "keras_applications_archive", - strip_prefix = "keras-applications-1.0.8", - sha256 = "7c37f9e9ef93efac9b4956301cb21ce46c474ce9da41fac9a46753bab6823dfc", - urls = [ - "https://storage.googleapis.com/mirror.tensorflow.org/github.com/keras-team/keras-applications/archive/1.0.8.tar.gz", - "https://github.com/keras-team/keras-applications/archive/1.0.8.tar.gz", - ], - build_file = "//third_party/keras_applications_archive:BUILD.bazel", - system_build_file = "//third_party/keras_applications_archive:BUILD.system", - ) diff --git a/third_party/llvm/llvm.autogenerated.BUILD b/third_party/llvm/llvm.autogenerated.BUILD index 875d7ae627b..692f007a8e7 100644 --- a/third_party/llvm/llvm.autogenerated.BUILD +++ b/third_party/llvm/llvm.autogenerated.BUILD @@ -276,8 +276,6 @@ llvm_target_list = [ ("-gen-dag-isel", "lib/Target/AMDGPU/AMDGPUGenDAGISel.inc"), ("-gen-callingconv", "lib/Target/AMDGPU/AMDGPUGenCallingConv.inc"), ("-gen-subtarget", "lib/Target/AMDGPU/AMDGPUGenSubtargetInfo.inc"), - ("-gen-tgt-intrinsic-impl", "lib/Target/AMDGPU/AMDGPUGenIntrinsicImpl.inc"), - ("-gen-tgt-intrinsic-enums", "lib/Target/AMDGPU/AMDGPUGenIntrinsicEnums.inc"), ("-gen-emitter", "lib/Target/AMDGPU/AMDGPUGenMCCodeEmitter.inc"), ("-gen-dfa-packetizer", "lib/Target/AMDGPU/AMDGPUGenDFAPacketizer.inc"), ("-gen-asm-writer", "lib/Target/AMDGPU/AMDGPUGenAsmWriter.inc"), @@ -1739,6 +1737,28 @@ cc_library( ], ) +cc_library( + name = "frontend_open_mp", + srcs = glob([ + "lib/Frontend/OpenMP/*.c", + "lib/Frontend/OpenMP/*.cpp", + "lib/Frontend/OpenMP/*.inc", + "lib/Frontend/OpenMP/*.h", + ]), + hdrs = glob([ + "include/llvm/Frontend/OpenMP/*.h", + "include/llvm/Frontend/OpenMP/*.def", + "include/llvm/Frontend/OpenMP/*.inc", + ]), + copts = llvm_copts, + deps = [ + ":config", + ":core", + ":support", + ":transform_utils", + ], +) + cc_library( name = "fuzz_mutate", srcs = glob([ diff --git a/third_party/mlir/BUILD b/third_party/mlir/BUILD index 1740ffc8641..131afd54224 100644 --- a/third_party/mlir/BUILD +++ b/third_party/mlir/BUILD @@ -12,7 +12,6 @@ package_group( packages = ["//..."], ) -# Before adding a project here, please read go/mlir-sla # In particular the OWNERS file of the dependent project should be updated. # TODO(b/140669524): Use proper MLIR tests instead of end-to-end tests for # tf_runtime and tf_runtime_google. @@ -107,6 +106,7 @@ cc_library( "include/mlir/IR/Attributes.h", "include/mlir/IR/Block.h", "include/mlir/IR/BlockAndValueMapping.h", + "include/mlir/IR/BlockSupport.h", "include/mlir/IR/Builders.h", "include/mlir/IR/Diagnostics.h", "include/mlir/IR/Dialect.h", @@ -342,6 +342,21 @@ cc_library( ], ) +cc_library( + name = "DialectUtils", + srcs = [ + ], + hdrs = [ + "include/mlir/Dialect/Utils/StructuredOpsUtils.h", + ], + includes = ["include"], + deps = [ + ":IR", + ":Support", + "@llvm//:support", + ], +) + cc_library( name = "AffineOps", srcs = [ @@ -475,7 +490,7 @@ cc_library( name = "VectorOps", srcs = [ "lib/Dialect/VectorOps/VectorOps.cpp", - "lib/Dialect/VectorOps/VectorToVector.cpp", + "lib/Dialect/VectorOps/VectorTransforms.cpp", ], hdrs = [ "include/mlir/Dialect/VectorOps/Utils.h", @@ -484,12 +499,13 @@ cc_library( ], includes = ["include"], deps = [ + ":DialectUtils", ":EDSC", ":IR", ":StandardOps", ":Support", ":VectorOpsIncGen", - ":VectorTransformPatterns", + ":VectorTransformPatternsIncGen", "@llvm//:support", ], ) @@ -580,6 +596,7 @@ filegroup( name = "GPUOpsTdFiles", srcs = [ "include/mlir/Dialect/GPU/GPUOps.td", + "include/mlir/Dialect/LLVMIR/LLVMOpBase.td", ":OpBaseTdFiles", ], ) @@ -760,6 +777,7 @@ cc_library( includes = ["include"], deps = [ ":GPUDialect", + ":IR", ":LoopOps", ":Pass", ":SPIRVDialect", @@ -1105,6 +1123,7 @@ cc_library( ], includes = ["include"], deps = [ + ":CommonFolders", ":IR", ":Parser", ":SPIRVCanonicalizationIncGen", @@ -1472,6 +1491,7 @@ cc_library( "lib/Analysis/CallGraph.cpp", "lib/Analysis/Dominance.cpp", "lib/Analysis/InferTypeOpInterface.cpp", + "lib/Analysis/Liveness.cpp", "lib/Analysis/LoopAnalysis.cpp", "lib/Analysis/MemRefBoundCheck.cpp", "lib/Analysis/NestedMatcher.cpp", @@ -1489,6 +1509,7 @@ cc_library( "include/mlir/Analysis/CallInterfaces.h", "include/mlir/Analysis/Dominance.h", "include/mlir/Analysis/InferTypeOpInterface.h", + "include/mlir/Analysis/Liveness.h", "include/mlir/Analysis/LoopAnalysis.h", "include/mlir/Analysis/NestedMatcher.h", "include/mlir/Analysis/Passes.h", @@ -1579,8 +1600,8 @@ cc_library( ":LLVMIRModuleTranslation", ":Support", ":Translation", - "//third_party/llvm/llvm-project/llvm:ir_reader", "@llvm//:core", + "@llvm//:ir_reader", "@llvm//:support", ], alwayslink = 1, @@ -2253,6 +2274,7 @@ cc_library( name = "Linalg", srcs = [ "lib/Dialect/Linalg/Analysis/DependenceAnalysis.cpp", + "lib/Dialect/Linalg/EDSC/Builders.cpp", "lib/Dialect/Linalg/IR/LinalgOps.cpp", "lib/Dialect/Linalg/IR/LinalgTypes.cpp", "lib/Dialect/Linalg/Transforms/Fusion.cpp", @@ -2264,6 +2286,8 @@ cc_library( ], hdrs = [ "include/mlir/Dialect/Linalg/Analysis/DependenceAnalysis.h", + "include/mlir/Dialect/Linalg/EDSC/Builders.h", + "include/mlir/Dialect/Linalg/EDSC/Intrinsics.h", "include/mlir/Dialect/Linalg/IR/LinalgOps.h", "include/mlir/Dialect/Linalg/IR/LinalgTraits.h", "include/mlir/Dialect/Linalg/IR/LinalgTypes.h", @@ -2274,17 +2298,18 @@ cc_library( ], includes = ["include"], deps = [ - "LinalgTransformPatternsIncGen", ":AffineOps", ":AffineToStandardTransforms", ":Analysis", ":CFGTransforms", + ":DialectUtils", ":EDSC", ":IR", ":LLVMDialect", ":LLVMTransforms", ":LinalgLibraryOpsIncGen", ":LinalgOpsIncGen", + ":LinalgTransformPatternsIncGen", ":LoopOps", ":Parser", ":Pass", @@ -2292,6 +2317,7 @@ cc_library( ":Support", ":TransformUtils", ":Transforms", + ":VectorOps", "@llvm//:core", "@llvm//:support", ], @@ -2394,8 +2420,21 @@ gentbl( ], ) +filegroup( + name = "VectorTransformPatternsTdFiles", + srcs = [ + "include/mlir/Dialect/VectorOps/VectorTransformPatterns.td", + ":AffineOpsTdFiles", + ":LinalgLibraryOpsTdFiles", + ":LinalgOpsTdFiles", + ":OpBaseTdFiles", + ":StdOpsTdFiles", + ":VectorOpsTdFiles", + ], +) + gentbl( - name = "VectorTransformPatterns", + name = "VectorTransformPatternsIncGen", tbl_outs = [ ( "-gen-rewriters", @@ -2405,9 +2444,7 @@ gentbl( tblgen = ":mlir-tblgen", td_file = "include/mlir/Dialect/VectorOps/VectorTransformPatterns.td", td_srcs = [ - ":OpBaseTdFiles", - ":StdOpsTdFiles", - ":VectorOpsTdFiles", + ":VectorTransformPatternsTdFiles", ], ) diff --git a/third_party/mlir/bindings/python/pybind.cpp b/third_party/mlir/bindings/python/pybind.cpp index 7d3ac044391..eb9984feb1c 100644 --- a/third_party/mlir/bindings/python/pybind.cpp +++ b/third_party/mlir/bindings/python/pybind.cpp @@ -1122,6 +1122,20 @@ PYBIND11_MODULE(pybind, m) { [](PythonAffineExpr lhs, PythonAffineExpr rhs) -> PythonAffineExpr { return PythonAffineExpr(lhs.get() % rhs.get()); }) + .def("compose", + [](PythonAffineExpr self, PythonAffineMap map) -> PythonAffineExpr { + return PythonAffineExpr(self.get().compose(map)); + }) + .def( + "get_constant_value", + [](PythonAffineExpr self) -> py::object { + auto const_expr = self.get().dyn_cast(); + if (const_expr) + return py::cast(const_expr.getValue()); + return py::none(); + }, + "Returns the constant value for the affine expression if any, or " + "returns None.") .def("__str__", &PythonAffineExpr::str); py::class_(m, "AffineMap", diff --git a/third_party/mlir/bindings/python/test/test_py2and3.py b/third_party/mlir/bindings/python/test/test_py2and3.py index 7849b08f19b..02ff4ab3061 100644 --- a/third_party/mlir/bindings/python/test/test_py2and3.py +++ b/third_party/mlir/bindings/python/test/test_py2and3.py @@ -195,6 +195,22 @@ class EdscTest: # CHECK-LABEL: testCondBr # CHECK: cond_br %{{.*}}, ^bb{{.*}}, ^bb{{.*}}(%{{.*}} : index) + def testConstantAffineExpr(self): + self.setUp() + with self.module.function_context("constant_affine", [], []) as fun: + a1 = self.module.affine_dim_expr(0) + a2 = self.module.affine_dim_expr(1) + a3 = a1 + a2 + 3 + composedExpr = a3.compose( + self.module.affine_map(2, 0, [ + self.module.affine_constant_expr(4), + self.module.affine_constant_expr(7) + ])) + printWithCurrentFunctionName(str(fun)) + print("constant value : %d" % composedExpr.get_constant_value()) + # CHECK-LABEL: testConstantAffineExpr + # CHECK: constant value : 14 + def testConstants(self): self.setUp() with self.module.function_context("constants", [], []) as fun: diff --git a/third_party/mlir/g3doc/ConversionToLLVMDialect.md b/third_party/mlir/g3doc/ConversionToLLVMDialect.md index 595049ad440..3564d4c86a3 100644 --- a/third_party/mlir/g3doc/ConversionToLLVMDialect.md +++ b/third_party/mlir/g3doc/ConversionToLLVMDialect.md @@ -79,7 +79,7 @@ resulting in a struct containing two pointers + offset. Examples: -```mlir {.mlir} +```mlir memref -> !llvm.type<"{ float*, float*, i64 }"> memref<1 x f32> -> !llvm.type<"{ float*, float*, i64, [1 x i64], [1 x i64] }"> memref -> !llvm.type<"{ float*, float*, i64, [1 x i64], [1 x i64] }"> @@ -102,11 +102,23 @@ descriptor pointer. Examples: -```mlir {.mlir} +```mlir // unranked descriptor memref<*xf32> -> !llvm.type<"{i64, i8*}"> ``` +**In function signatures,** `memref` is passed as a _pointer_ to the structured +defined above to comply with the calling convention. + +Example: + +```mlir +// A function type with memref as argument +(memref) -> () +// is transformed into the LLVM function with pointer-to-structure argument. +!llvm.type<"void({ float*, float*, i64, [1 x i64], [1 x i64]}*) "> +``` + ### Function Types Function types get converted to LLVM function types. The arguments are converted @@ -125,7 +137,7 @@ converted using these rules. Examples: -```mlir {.mlir} +```mlir // zero-ary function type with no results. () -> () // is converted to a zero-ary function with `void` result @@ -162,7 +174,7 @@ definition operation uses MLIR syntax. Examples: -```mlir {.mlir} +```mlir // zero-ary function type with no results. func @foo() -> () // gets LLVM type void(). @@ -195,7 +207,7 @@ defines and uses of the values being returned. Example: -```mlir {.mlir} +```mlir func @foo(%arg0: i32, %arg1: i64) -> (i32, i64) { return %arg0, %arg1 : i32, i64 } @@ -231,6 +243,67 @@ func @bar() { "use_i32"(%3) : (!llvm.type<"i32">) -> () "use_i64"(%4) : (!llvm.type<"i64">) -> () } +``` + +### Calling Convention for `memref` + +For function _arguments_ of `memref` type, ranked or unranked, the type of the +argument is a _pointer_ to the memref descriptor type defined above. The caller +of such function is required to store the descriptor in memory and guarantee +that the storage remains live until the callee returns. The caller can than pass +the pointer to that memory as function argument. The callee loads from the +pointers it was passed as arguments in the entry block of the function, making +the descriptor passed in as argument available for use similarly to +ocally-defined descriptors. + +This convention is implemented in the conversion of `std.func` and `std.call` to +the LLVM dialect. Conversions from other dialects should take it into account. +The motivation for this convention is to simplify the ABI for interfacing with +other LLVM modules, in particular those generated from C sources, while avoiding +platform-specific aspects until MLIR has a proper ABI modeling. + +Example: + +```mlir + +func @foo(memref) -> () { + %c0 = constant 0 : index + load %arg0[%c0] : memref + return +} + +func @bar(%arg0: index) { + %0 = alloc(%arg0) : memref + call @foo(%0) : (memref)-> () + return +} + +// Gets converted to the following IR. +// Accepts a pointer to the memref descriptor. +llvm.func @foo(!llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }*">) { + // Loads the descriptor so that it can be used similarly to locally + // created descriptors. + %0 = llvm.load %arg0 : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }*"> +} + +llvm.func @bar(%arg0: !llvm.i64) { + // ... Allocation ... + // Definition of the descriptor. + %7 = llvm.mlir.undef : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }"> + // ... Filling in the descriptor ... + %14 = // The final value of the allocated descriptor. + // Allocate the memory for the descriptor and store it. + %15 = llvm.mlir.constant(1 : index) : !llvm.i64 + %16 = llvm.alloca %15 x !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }"> + : (!llvm.i64) -> !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }*"> + llvm.store %14, %16 : !llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }*"> + // Pass the pointer to the function. + llvm.call @foo(%16) : (!llvm<"{ float*, float*, i64, [1 x i64], [1 x i64] }*">) -> () + llvm.return +} + + + ``` ## Repeated Successor Removal @@ -249,7 +322,7 @@ are used instead of them in the original terminator operation. Example: -```mlir {.mlir} +```mlir cond_br %0, ^bb1(%1 : i32), ^bb1(%2 : i32) ^bb1(%3 : i32) "use"(%3) : (i32) -> () @@ -257,7 +330,7 @@ Example: leads to a new basic block being inserted, -```mlir {.mlir} +```mlir cond_br %0, ^bb1(%1 : i32), ^dummy ^bb1(%3 : i32): "use"(%3) : (i32) -> () @@ -267,7 +340,7 @@ leads to a new basic block being inserted, before the conversion to the LLVM IR dialect: -```mlir {.mlir} +```mlir llvm.cond_br %0, ^bb1(%1 : !llvm.type<"i32">), ^dummy ^bb1(%3 : !llvm.type<"i32">): "use"(%3) : (!llvm.type<"i32">) -> () @@ -307,7 +380,7 @@ Examples: An access to a zero-dimensional memref is converted into a plain load: -```mlir {.mlir} +```mlir // before %0 = load %m[] : memref @@ -317,13 +390,13 @@ An access to a zero-dimensional memref is converted into a plain load: An access to a memref with indices: -```mlir {.mlir} +```mlir %0 = load %m[1,2,3,4] : memref<10x?x13x?xf32> ``` is transformed into the equivalent of the following code: -```mlir {.mlir} +```mlir // obtain the buffer pointer %b = llvm.extractvalue %m[0] : !llvm.type<"{float*, i64, i64}"> diff --git a/third_party/mlir/g3doc/DeveloperGuide.md b/third_party/mlir/g3doc/DeveloperGuide.md index a4ba9ec87ad..74500995925 100644 --- a/third_party/mlir/g3doc/DeveloperGuide.md +++ b/third_party/mlir/g3doc/DeveloperGuide.md @@ -51,3 +51,57 @@ pipeline. ## Testing guidelines See here for the [testing guide](TestingGuide.md). + +## Guidelines on contributing a new dialect (or important components) + +To contribute a dialect (or a major component in MLIR), it is usual to write an +overview "RFC" (it can be just a few informal paragraphs) and send it to the +MLIR mailing list. When accepting a new component to MLIR, the community is also +accepting the burden of maintaining it. The following points should be +considered when evaluating whether a dialect is a good fit for the core MLIR +repository: + +* What is the overall goal of the dialect? What is the first implementation + milestone? +* How does it fit into the MLIR dialect ecosystem? + * Connection: how does it connect to the existing dialects in a + compilation pipeline(s)? + * Consolidation: is there already a dialect with a similar goal or + matching abstractions; if so, can it be improved instead of adding a new + one? + * Reuse: how does it generalize to similar but slightly different + use-cases? +* What is the community of users that it is serving? +* Who are the future contributors/maintainers beyond those who propose the + dialect? + +On a practical aspect, we will expect the code to follow the other sections of +this document, with an emphasis on the documentation alongside the source code. + +It is prefered to upstream your dialects/components in small incremental patches +that can be individually reviewed. That is, after the initial RFC has been +agreed on, we encourage dialects to be built progressively by faster iterations +in-tree; as long as it is clear they evolve towards their milestones and goals. + +We have seen the following broad categories of dialects: + +* Edge dialects that model a representation external to MLIR. Examples include + LLVM, SPIR-V dialects, TensorFlow, XLA/HLO, ... Such dialects may be a + better fit for the project that contains the original representation instead + of being added to the MLIR repository. In particular, because MLIR will not + take an external dependency on another project. +* Structured Abstraction dialects that generalize common features of several + other dialects or introduce a programming model. Generalization is sometimes + demonstrated by having several dialects lower to or originate from a new + dialect. While additional abstractions may be useful, they should be traded + off against the additional complexity of the dialect ecosystem. Examples of + abstraction dialects include the GPU and Loop dialects. +* Transformation dialects that serve as input/output for program + transformations. These dialects are commonly introduced to materialize + transformation pre- and post-conditions in the IR, while conditions can be + obtained through analysis or through operation semantics. Examples include + Affine and Linalg dialects. + +While it can be useful to frame the goals of a proposal, this categorization is +not exhaustive or absolute, and the community is open to discussing any new +dialect beyond this taxonomy. diff --git a/third_party/mlir/g3doc/Diagnostics.md b/third_party/mlir/g3doc/Diagnostics.md index 0c6ef7a24fa..69a30942c00 100644 --- a/third_party/mlir/g3doc/Diagnostics.md +++ b/third_party/mlir/g3doc/Diagnostics.md @@ -16,7 +16,7 @@ different location types depending on the situational need. ### CallSite Location -``` {.ebnf} +``` callsite-location ::= 'callsite' '(' location 'at' location ')' ``` @@ -26,7 +26,7 @@ location usages. This connects a location of a `callee` with the location of a ### FileLineCol Location -``` {.ebnf} +``` filelinecol-location ::= string-literal ':' integer-literal ':' integer-literal ``` @@ -36,7 +36,7 @@ languages. ### Fused Location -``` {.ebnf} +``` fused-location ::= `fused` fusion-metadata? '[' location (location ',')* ']' fusion-metadata ::= '<' attribute-value '>' ``` @@ -49,7 +49,7 @@ loss of location information. With `fused` locations, this is a non-issue. ### Name Location -``` {.ebnf} +``` name-location ::= string-literal ('(' location ')')? ``` @@ -66,7 +66,7 @@ optional location is used during serialization. ### Unknown Location -``` {.ebnf} +``` unknown-location ::= `unknown` ``` @@ -311,7 +311,7 @@ annotate your source file with expected diagnostics in the form of: A few examples are shown below: -```mlir {.mlir} +```mlir // Expect an error on the same line. func @bad_branch() { br ^missing // expected-error {{reference to an undefined block}} diff --git a/third_party/mlir/g3doc/Dialects/Affine.md b/third_party/mlir/g3doc/Dialects/Affine.md index 11cb93c10ea..c5dcf6a6790 100644 --- a/third_party/mlir/g3doc/Dialects/Affine.md +++ b/third_party/mlir/g3doc/Dialects/Affine.md @@ -19,7 +19,7 @@ brackets. Examples: -```mlir {.mlir} +```mlir // A 2d to 3d affine mapping. // d0/d1 are dimensions, s0 is a symbol #affine_map2to3 = (d0, d1)[s0] -> (d0, d1 + s0, d1 - s0) @@ -36,7 +36,7 @@ use the same parenthesized vs square bracket list to distinguish the two. Syntax: -``` {.ebnf} +``` // Uses of SSA values that are passed to dimensional identifiers. dim-use-list ::= `(` ssa-use-list? `)` @@ -51,7 +51,7 @@ SSA values bound to dimensions and symbols must always have 'index' type. Example: -```mlir {.mlir} +```mlir #affine_map2to3 = (d0, d1)[s0] -> (d0, d1 + s0, d1 - s0) // Binds %N to the s0 symbol in affine_map2to3. %x = alloc()[%N] : memref<40x50xf32, #affine_map2to3> @@ -79,7 +79,7 @@ other dimensions and symbols). Syntax: -``` {.ebnf} +``` affine-expr ::= `(` affine-expr `)` | affine-expr `+` affine-expr | affine-expr `-` affine-expr @@ -131,7 +131,7 @@ i^2)$$, $$(i \mod j, i/j)$$ are not affine functions of $$(i, j)$$. Syntax: -``` {.ebnf} +``` affine-map-inline ::= dim-and-symbol-id-lists `->` multi-dim-affine-expr ``` @@ -158,7 +158,7 @@ the representation closed with respect to several operations of interest. Syntax: -``` {.ebnf} +``` affine-map-id ::= `#` suffix-id // Definitions of affine maps are at the top of the file. @@ -175,7 +175,7 @@ name. Examples: -```mlir {.mlir} +```mlir // Affine map out-of-line definition and usage example. #affine_map42 = (d0, d1)[s0] -> (d0, d0 + d1 + s0 floordiv 2) @@ -195,7 +195,7 @@ Semi-affine maps are thus a strict superset of affine maps. Syntax of semi-affine expressions: -``` {.ebnf} +``` semi-affine-expr ::= `(` semi-affine-expr `)` | semi-affine-expr `+` semi-affine-expr | semi-affine-expr `-` semi-affine-expr @@ -216,7 +216,7 @@ as that for [affine expressions](#affine-expressions). Syntax of semi-affine maps: -``` {.ebnf} +``` semi-affine-map-inline ::= dim-and-symbol-id-lists `->` multi-dim-semi-affine-expr ``` @@ -225,7 +225,7 @@ Semi-affine maps may be defined inline at the point of use, or may be hoisted to the top of the file and given a name with a semi-affine map definition, and used by name. -``` {.ebnf} +``` semi-affine-map-id ::= `#` suffix-id // Definitions of semi-affine maps are at the top of file. @@ -247,7 +247,7 @@ while its symbols are enclosed in square brackets. Syntax of affine constraints: -``` {.ebnf} +``` affine-constraint ::= affine-expr `>=` `0` | affine-expr `==` `0` affine-constraint-conjunction ::= affine-constraint (`,` affine-constraint)* @@ -257,7 +257,7 @@ Integer sets may be defined inline at the point of use, or may be hoisted to the top of the file and given a name with an integer set definition, and used by name. -``` {.ebnf} +``` integer-set-id ::= `#` suffix-id integer-set-inline @@ -278,7 +278,7 @@ dimensions. Example: -```mlir {.mlir} +```mlir // A example two-dimensional integer set with two symbols. #set42 = (d0, d1)[s0, s1] : (d0 >= 0, -d0 + s0 - 1 >= 0, d1 >= 0, -d1 + s1 - 1 >= 0) @@ -298,7 +298,7 @@ affine.if #set42(%i, %j)[%M, %N] { Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `affine.apply` affine-map dim-and-symbol-use-list ``` @@ -311,7 +311,7 @@ value. The input operands and result must all have 'index' type. Example: -```mlir {.mlir} +```mlir #map10 = (d0, d1) -> (d0 floordiv 8 + d1 floordiv 128) ... %1 = affine.apply #map10 (%s, %t) @@ -324,7 +324,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= `affine.for` ssa-id `=` lower-bound `to` upper-bound (`step` integer-literal)? `{` op* `}` @@ -365,7 +365,7 @@ nullary mapping function that returns the constant value (e.g. `()->(-42)()`). Example showing reverse iteration of the inner loop: -```mlir {.mlir} +```mlir #map57 = (d0)[s0] -> (s0 - d0 - 1) func @simple_example(%A: memref, %B: memref) { @@ -385,7 +385,7 @@ func @simple_example(%A: memref, %B: memref) { Syntax: -``` {.ebnf} +``` operation ::= `affine.if` if-op-cond `{` op* `}` (`else` `{` op* `}`)? if-op-cond ::= integer-set dim-and-symbol-use-list ``` @@ -409,7 +409,7 @@ blocks must not have any arguments. Example: -```mlir {.mlir} +```mlir #set = (d0, d1)[s0]: (d0 - 10 >= 0, s0 - d0 - 9 >= 0, d1 - 10 >= 0, s0 - d1 - 9 >= 0) func @reduced_domain_example(%A, %X, %N) : (memref<10xi32>, i32, i32) { @@ -431,19 +431,20 @@ func @reduced_domain_example(%A, %X, %N) : (memref<10xi32>, i32, i32) { Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `affine.load` ssa-use `[` multi-dim-affine-map-of-ssa-ids `]` `:` memref-type ``` -The `affine.load` op reads an element from a memref, where the index -for each memref dimension is an affine expression of loop induction -variables and symbols. The output of 'affine.load' is a new value with the -same type as the elements of the memref. An affine expression of loop IVs -and symbols must be specified for each dimension of the memref. The keyword -'symbol' can be used to indicate SSA identifiers which are symbolic. + +The `affine.load` op reads an element from a memref, where the index for each +memref dimension is an affine expression of loop induction variables and +symbols. The output of 'affine.load' is a new value with the same type as the +elements of the memref. An affine expression of loop IVs and symbols must be +specified for each dimension of the memref. The keyword 'symbol' can be used to +indicate SSA identifiers which are symbolic. Example: -```mlir {.mlir} +```mlir Example 1: @@ -460,19 +461,20 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `affine.store` ssa-use, ssa-use `[` multi-dim-affine-map-of-ssa-ids `]` `:` memref-type ``` -The `affine.store` op writes an element to a memref, where the index -for each memref dimension is an affine expression of loop induction -variables and symbols. The 'affine.store' op stores a new value which is the -same type as the elements of the memref. An affine expression of loop IVs -and symbols must be specified for each dimension of the memref. The keyword -'symbol' can be used to indicate SSA identifiers which are symbolic. + +The `affine.store` op writes an element to a memref, where the index for each +memref dimension is an affine expression of loop induction variables and +symbols. The 'affine.store' op stores a new value which is the same type as the +elements of the memref. An affine expression of loop IVs and symbols must be +specified for each dimension of the memref. The keyword 'symbol' can be used to +indicate SSA identifiers which are symbolic. Example: -```mlir {.mlir} +```mlir Example 1: @@ -489,7 +491,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= `affine.dma_Start` ssa-use `[` multi-dim-affine-map-of-ssa-ids `]`, `[` multi-dim-affine-map-of-ssa-ids `]`, `[` multi-dim-affine-map-of-ssa-ids `]`, ssa-use `:` memref-type ``` @@ -515,7 +517,7 @@ specified. The value of 'num_elements' must be a multiple of Example: -```mlir {.mlir} +```mlir For example, a DmaStartOp operation that transfers 256 elements of a memref '%src' in memory space 0 at indices [%i + 3, %j] to memref '%dst' in memory @@ -536,11 +538,12 @@ space 1 at indices [%k + 7, %l], would be specified as follows: %stride, %num_elt_per_stride : ... ``` + #### 'affine.dma_wait' operation Syntax: -``` {.ebnf} +``` operation ::= `affine.dma_Start` ssa-use `[` multi-dim-affine-map-of-ssa-ids `]`, `[` multi-dim-affine-map-of-ssa-ids `]`, `[` multi-dim-affine-map-of-ssa-ids `]`, ssa-use `:` memref-type ``` @@ -553,7 +556,7 @@ associated with the DMA operation. For example: Example: -```mlir {.mlir} +```mlir affine.dma_start %src[%i, %j], %dst[%k, %l], %tag[%index], %num_elements : memref<2048xf32, 0>, memref<256xf32, 1>, memref<1xi32, 2> @@ -567,7 +570,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `affine.min` affine-map dim-and-symbol-use-list ``` @@ -580,7 +583,7 @@ returns one value. The input operands and result must all have 'index' type. Example: -```mlir {.mlir} +```mlir %0 = affine.min (d0)[s0] -> (1000, d0 + 512, s0) (%arg0)[%arg1] @@ -590,7 +593,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= `"affine.terminator"() : () -> ()` ``` diff --git a/third_party/mlir/g3doc/Dialects/GPU.md b/third_party/mlir/g3doc/Dialects/GPU.md index bcb677d7660..7dcd8f6053c 100644 --- a/third_party/mlir/g3doc/Dialects/GPU.md +++ b/third_party/mlir/g3doc/Dialects/GPU.md @@ -43,7 +43,7 @@ x, y, or z `dimension`. Example: -```mlir {.mlir} +```mlir %bDimX = "gpu.block_dim"() {dimension = "x"} : () -> (index) ``` @@ -54,7 +54,7 @@ the x, y, or z `dimension`. Example: -```mlir {.mlir} +```mlir %bIdY = "gpu.block_id"() {dimension = "y"} : () -> (index) ``` @@ -65,74 +65,10 @@ Returns the number of thread blocks in the grid along the x, y, or z Example: -```mlir {.mlir} +```mlir %gDimZ = "gpu.grid_dim"() {dimension = "z"} : () -> (index) ``` -### `gpu.launch_func` - -Launch a kernel function on the specified grid of thread blocks. `gpu.launch` -operations are lowered to `gpu.launch_func` operations by outlining the kernel -body into a function in a dedicated module, which reflects the separate -compilation process. The kernel function is required to have the `gpu.kernel` -attribute. The module containing the kernel function is required to have the -`gpu.kernel_module` attribute and must be named. And finally, the module -containing the kernel module (which thus cannot be the top-level module) is -required to have the `gpu.container_module` attribute. The `gpu.launch_func` -operation has a string attribute named `kernel` to specify the name of the -kernel function to launch and an attribute named `kernel_module` to specify the -name of the module containing that kernel function. - -The operation takes at least six operands, with the first three operands being -grid sizes along x,y,z dimensions and the following three being block sizes -along x,y,z dimensions. When a lower-dimensional kernel is required, unused -sizes must be explicitly set to `1`. The remaining operands are passed as -arguments to the kernel function. - -A custom syntax for this operation is currently not available. - -Example: - -```mlir {.mlir} -module attributes {gpu.container_module} { - - // This module creates a separate compilation unit for the GPU compiler. - module @kernels attributes {gpu.kernel_module} { - func @kernel_1(%arg0 : f32, %arg1 : !llvm<"float*">) - attributes { nvvm.kernel = true } { - - // Operations that produce block/thread IDs and dimensions are injected when - // outlining the `gpu.launch` body to a function called by `gpu.launch_func`. - %tIdX = "gpu.thread_id"() {dimension = "x"} : () -> (index) - %tIdY = "gpu.thread_id"() {dimension = "y"} : () -> (index) - %tIdZ = "gpu.thread_id"() {dimension = "z"} : () -> (index) - - %bDimX = "gpu.block_dim"() {dimension = "x"} : () -> (index) - %bDimY = "gpu.block_dim"() {dimension = "y"} : () -> (index) - %bDimZ = "gpu.block_dim"() {dimension = "z"} : () -> (index) - - %bIdX = "gpu.block_id"() {dimension = "x"} : () -> (index) - %bIdY = "gpu.block_id"() {dimension = "y"} : () -> (index) - %bIdZ = "gpu.block_id"() {dimension = "z"} : () -> (index) - - %gDimX = "gpu.grid_dim"() {dimension = "x"} : () -> (index) - %gDimY = "gpu.grid_dim"() {dimension = "y"} : () -> (index) - %gDimZ = "gpu.grid_dim"() {dimension = "z"} : () -> (index) - - "some_op"(%bx, %tx) : (index, index) -> () - %42 = load %arg1[%bx] : memref - } - } - - "gpu.launch_func"(%cst, %cst, %cst, // Grid sizes. - %cst, %cst, %cst, // Block sizes. - %arg0, %arg1) // Arguments passed to the kernel function. - { kernel_module = @kernels, // Module containing the kernel function. - kernel = "kernel_1" } // Kernel function. - : (index, index, index, index, index, index, f32, !llvm<"float*">) -> () -} -``` - ### `gpu.thread_id` Returns the thread id, i.e. the index of the current thread within the block @@ -140,7 +76,7 @@ along the x, y, or z `dimension`. Example: -```mlir {.mlir} +```mlir %tIdX = "gpu.thread_id"() {dimension = "x"} : () -> (index) ``` @@ -151,11 +87,10 @@ returns values to the immediately enclosing gpu op. Example: -```mlir {.mlir} +```mlir gpu.yield %f0, %f1 : f32, f32 ``` - ### `gpu.all_reduce` The "all_reduce" op reduces the value of every work item across a local @@ -163,7 +98,7 @@ workgroup. The result is equal for all work items of a workgroup. For example, both -```mlir {.mlir} +```mlir %1 = "gpu.all_reduce"(%0) ({}) { op = "add" } : (f32) -> (f32) %2 = "gpu.all_reduce"(%0) ({ ^bb(%lhs : f32, %rhs : f32): @@ -171,10 +106,10 @@ For example, both "gpu.yield"(%sum) : (f32) -> () }) : (f32) -> (f32) ``` -compute the sum of each work item's %0 value. The first version specifies -the accumulation as operation, whereas the second version specifies the -accumulation as code region. The accumulation operation must either be -`add` or `mul`. + +compute the sum of each work item's %0 value. The first version specifies the +accumulation as operation, whereas the second version specifies the accumulation +as code region. The accumulation operation must either be `add` or `mul`. Either none or all work items of a workgroup need to execute this op in convergence. @@ -184,14 +119,14 @@ in convergence. The "barrier" op synchronizes all work items of a workgroup. It is used to coordinate communication between the work items of the workgroup. -```mlir {.mlir} +```mlir gpu.barrier ``` -waits until all work items in the workgroup have reached this point -and all memory accesses made by these work items prior to the op are -visible to all work items in the workgroup. Data hazards between work items -accessing the same memory can be avoided by synchronizing work items -in-between these accesses. + +waits until all work items in the workgroup have reached this point and all +memory accesses made by these work items prior to the op are visible to all work +items in the workgroup. Data hazards between work items accessing the same +memory can be avoided by synchronizing work items in-between these accesses. Either none or all work items of a workgroup need to execute this op in convergence. diff --git a/third_party/mlir/g3doc/Dialects/LLVM.md b/third_party/mlir/g3doc/Dialects/LLVM.md index 9791352aa56..00d0fa02fec 100644 --- a/third_party/mlir/g3doc/Dialects/LLVM.md +++ b/third_party/mlir/g3doc/Dialects/LLVM.md @@ -25,7 +25,7 @@ must exist in the dialect's context. The LLVM IR dialect defines a single MLIR type, `LLVM::LLVMType`, that can wrap any existing LLVM IR type. Its syntax is as follows -``` {.ebnf} +``` type ::= `!llvm<"` llvm-canonical-type `"> llvm-canonical-type ::= ``` @@ -60,7 +60,7 @@ type. LLVM function operation is intended to capture additional properties of LLVM functions, such as linkage and calling convention, that may be modeled differently by the built-in MLIR function. -```mlir {.mlir} +```mlir // The type of @bar is !llvm<"i64 (i64)"> llvm.func @bar(%arg0: !llvm.i64) -> !llvm.i64 { llvm.return %arg0 : !llvm.i64 @@ -100,7 +100,7 @@ same type. Examples: -```mlir {.mlir} +```mlir // Integer addition. %0 = llvm.add %a, %b : !llvm.i32 @@ -121,7 +121,7 @@ the same type. Examples: -```mlir {.mlir} +```mlir // Float addition. %0 = llvm.fadd %a, %b : !llvm.float @@ -146,7 +146,7 @@ non-pointer arguments of LLVM IR's `getelementptr`. Examples: -```mlir {.mlir} +```mlir // Allocate an array of 4 floats on stack %c4 = llvm.mlir.constant(4) : !llvm.i64 %0 = llvm.alloca %c4 x !llvm.float : (!llvm.i64) -> !llvm<"float*"> @@ -179,7 +179,7 @@ they are modeled as array attributes. Examples: -```mlir {.mlir} +```mlir // Get the value third element of the second element of a structure. %0 = llvm.extractvalue %s[1, 2] : !llvm<"{i32, {i1, i8, i16}"> @@ -209,7 +209,7 @@ arguments. Examples: -```mlir {.mlir} +```mlir // Branch without arguments. ^bb0: llvm.br ^bb0 @@ -248,7 +248,7 @@ wrapped LLVM IR function type. Examples: -```mlir {.mlir} +```mlir // Direct call without arguments and with one result. %0 = llvm.call @foo() : () -> (!llvm.float) @@ -292,7 +292,7 @@ referenced. If the global value is a constant, storing into it is not allowed. Examples: -```mlir {.mlir} +```mlir func @foo() { // Get the address of a global. %0 = llvm.mlir.addressof @const : !llvm<"i32*"> @@ -319,7 +319,7 @@ to the attribute type converted to LLVM IR. Examples: -```mlir {.mlir} +```mlir // Integer constant, internal i32 is mandatory %0 = llvm.mlir.constant(42 : i32) : !llvm.i32 @@ -343,7 +343,7 @@ both cases. There are two forms of initialization syntax. Simple constants that can be represented as MLIR attributes can be given in-line: -```mlir {.mlir} +```mlir llvm.mlir.global @variable(32.0 : f32) : !llvm.float ``` @@ -354,7 +354,7 @@ types must be compatible. More complex constants that cannot be represented as MLIR attributes can be given in an initializer region: -```mlir {.mlir} +```mlir // This global is initialized with the equivalent of: // i32* getelementptr (i32* @g2, i32 2) llvm.mlir.global constant @int_gep() : !llvm<"i32*"> { @@ -374,7 +374,7 @@ other @-identifiers in it. Examples: -```mlir {.mlir} +```mlir // Global values use @-identifiers. llvm.mlir.global constant @cst(42 : i32) : !llvm.i32 @@ -406,7 +406,7 @@ type. Examples: -```mlir {.mlir} +```mlir // Null pointer to i8 value. %0 = llvm.mlir.null : !llvm<"i8*"> @@ -423,7 +423,7 @@ dialect type wrapping an LLVM IR structure type. Example: -```mlir {.mlir} +```mlir // Create a structure with a 32-bit integer followed by a float. %0 = llvm.mlir.undef : !llvm<"{i32, float}"> ``` diff --git a/third_party/mlir/g3doc/Dialects/SPIR-V.md b/third_party/mlir/g3doc/Dialects/SPIR-V.md index 58bd5ee828f..1413b181407 100644 --- a/third_party/mlir/g3doc/Dialects/SPIR-V.md +++ b/third_party/mlir/g3doc/Dialects/SPIR-V.md @@ -84,7 +84,7 @@ instructions are represented in the SPIR-V dialect. Notably, The SPIR-V dialect reuses standard integer, float, and vector types and defines the following dialect-specific types: -``` {.ebnf} +``` spirv-type ::= array-type | pointer-type | runtime-array-type @@ -94,7 +94,7 @@ spirv-type ::= array-type This corresponds to SPIR-V [array type][ArrayType]. Its syntax is -``` {.ebnf} +``` element-type ::= integer-type | floating-point-type | vector-type @@ -114,7 +114,7 @@ For example, This corresponds to SPIR-V [image type][ImageType]. Its syntax is -``` {.ebnf} +``` dim ::= `1D` | `2D` | `3D` | `Cube` | depth-info ::= `NoDepth` | `IsDepth` | `DepthUnknown` @@ -134,7 +134,7 @@ image-type ::= `!spv.image<` element-type `,` dim `,` depth-info `,` For example, -``` {.mlir} +``` !spv.image !spv.image ``` @@ -143,7 +143,7 @@ For example, This corresponds to SPIR-V [pointer type][PointerType]. Its syntax is -``` {.ebnf} +``` storage-class ::= `UniformConstant` | `Uniform` | `Workgroup` @@ -163,7 +163,7 @@ For example, This corresponds to SPIR-V [runtime array type][RuntimeArrayType]. Its syntax is -``` {.ebnf} +``` runtime-array-type ::= `!spv.rtarray<` element-type `>` ``` @@ -178,7 +178,7 @@ For example, This corresponds to SPIR-V [struct type][StructType]. Its syntax is -``` {.ebnf} +``` struct-member-decoration ::= integer-literal? spirv-decoration* struct-type ::= `!spv.struct<` spirv-type (`[` struct-member-decoration `]`)? (`, ` spirv-type (`[` struct-member-decoration `]`)? @@ -186,7 +186,7 @@ struct-type ::= `!spv.struct<` spirv-type (`[` struct-member-decoration `]`)? For Example, -``` {.mlir} +``` !spv.struct !spv.struct !spv.struct> diff --git a/third_party/mlir/g3doc/Dialects/Standard.md b/third_party/mlir/g3doc/Dialects/Standard.md index 9d53eba328e..05ec703b059 100644 --- a/third_party/mlir/g3doc/Dialects/Standard.md +++ b/third_party/mlir/g3doc/Dialects/Standard.md @@ -21,7 +21,7 @@ list of successors, i.e. other blocks to which the control flow will proceed. Syntax: -``` {.ebnf} +``` operation ::= `br` successor successor ::= bb-id branch-use-list? branch-use-list ::= `(` ssa-use-list `:` type-list-no-parens `)` @@ -37,7 +37,7 @@ The MLIR branch operation is not allowed to target the entry block for a region. Syntax: -``` {.ebnf} +``` operation ::= `cond_br` ssa-use `,` successor `,` successor ``` @@ -53,7 +53,7 @@ allowed to be the same. The following example illustrates a function with a conditional branch operation that targets the same block: -```mlir {.mlir} +```mlir func @select(i32, i32, i1) -> i32 { ^bb0(%a : i32, %b :i32, %flag : i1) : // Both targets are the same, operands differ @@ -68,7 +68,7 @@ func @select(i32, i32, i1) -> i32 { Syntax: -``` {.ebnf} +``` operation ::= `return` (ssa-use-list `:` type-list-no-parens)? ``` @@ -83,8 +83,8 @@ single function to return. Syntax: -``` {.ebnf} -operation ::= +``` +operation ::= (ssa-id `=`)? `call` symbol-ref-id `(` ssa-use-list? `)` `:` function-type ``` @@ -94,7 +94,7 @@ encoded as a function attribute named "callee". Example: -```mlir {.mlir} +```mlir // Calling the function my_add. %31 = call @my_add(%0, %1) : (tensor<16xf32>, tensor<16xf32>) -> tensor<16xf32> ``` @@ -103,7 +103,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= `call_indirect` ssa-use `(` ssa-use-list? `)` `:` function-type ``` @@ -117,7 +117,7 @@ Function values can be created with the Example: -```mlir {.mlir} +```mlir %31 = call_indirect %15(%0, %1) : (tensor<16xf32>, tensor<16xf32>) -> tensor<16xf32> ``` @@ -126,7 +126,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `dim` ssa-id `,` integer-literal `:` type ``` @@ -139,7 +139,7 @@ The `dim` operation is represented with a single integer attribute named Examples: -```mlir {.mlir} +```mlir // Always returns 4, can be constant folded: %x = dim %A, 0 : tensor<4 x ? x f32> @@ -157,7 +157,7 @@ Examples: Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `alloc` dim-and-symbol-use-list `:` memref-type ``` @@ -172,7 +172,7 @@ destroyed by the `dealloc` operation. Example: -```mlir {.mlir} +```mlir // Allocating memref for a fully static shape. %A = alloc() : memref<1024x64xf32, #layout_map0, memspace0> @@ -186,7 +186,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `alloc_static` `(` integer-literal `)` : memref-type ``` @@ -198,7 +198,7 @@ require dynamic symbols in their layout function (use the Example: -```mlir {.mlir} +```mlir %A = alloc_static(0x1232a00) : memref<1024 x 64 x f32, #layout_map0, memspace0> ``` @@ -209,7 +209,7 @@ has been performed. Syntax: -``` {.ebnf} +``` operation ::= `dealloc` ssa-use `:` memref-type ``` @@ -219,7 +219,7 @@ allocation. It is paired with an [`alloc`](#alloc-operation) or Example: -```mlir {.mlir} +```mlir dealloc %A : memref<128 x f32, #layout, memspace0> ``` @@ -227,7 +227,7 @@ dealloc %A : memref<128 x f32, #layout, memspace0> Syntax: -``` {.ebnf} +``` operation ::= `dma_start` ssa-use`[`ssa-use-list`]` `,` ssa-use`[`ssa-use-list`]` `,` ssa-use `,` ssa-use`[`ssa-use-list`]` (`,` ssa-use `,` ssa-use)? @@ -257,7 +257,7 @@ specified as shown below. Example: -```mlir {.mlir} +```mlir %size = constant 32 : index %tag = alloc() : memref<1 x i32, (d0) -> (d0), 4> %idx = constant 0 : index @@ -283,7 +283,7 @@ load/store indices. Example: -```mlir {.mlir} +```mlir dma_wait %tag[%idx], %size : memref<1 x i32, (d0) -> (d0), 4> ``` @@ -304,7 +304,7 @@ then 3 indices are required for the extract. The indices should all be of Examples: -```mlir {.mlir} +```mlir %3 = extract_element %v[%1, %2] : vector<4x4xi32> %4 = extract_element %t[%1, %2] : tensor<4x4xi32> %5 = extract_element %ut[%1, %2] : tensor<*xi32> @@ -314,7 +314,7 @@ Examples: Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `load` ssa-use `[` ssa-use-list `]` `:` memref-type ``` @@ -333,7 +333,7 @@ values or the recursively result of such an `affine.apply` operation. Example: -```mlir {.mlir} +```mlir %1 = affine.apply (d0, d1) -> (3*d0) (%i, %j) %2 = affine.apply (d0, d1) -> (d1+1) (%i, %j) %12 = load %A[%1, %2] : memref<8x?xi32, #layout, memspace0> @@ -356,7 +356,7 @@ in these contexts. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `splat` ssa-use `:` ( vector-type | tensor-type ) ``` @@ -366,23 +366,27 @@ it has to be statically shaped. Example: -```mlir {.mlir} +```mlir %s = load %A[%i] : memref<128xf32> %v = splat %s : vector<4xf32> %t = splat %s : tensor<8x16xi32> ``` TODO: This operation is easy to extend to broadcast to dynamically shaped -tensors in the same way dynamically shaped memrefs are handled. `mlir {.mlir} // -Broadcasts %s to a 2-d dynamically shaped tensor, with %m, %n binding // to the -sizes of the two dynamic dimensions. %m = "foo"() : () -> (index) %n = "bar"() : -() -> (index) %t = splat %s [%m, %n] : tensor` +tensors in the same way dynamically shaped memrefs are handled. +```mlir {.mlir} +// Broadcasts %s to a 2-d dynamically shaped tensor, with %m, %n binding +// to the sizes of the two dynamic dimensions. +%m = "foo"() : () -> (index) +%n = "bar"() : () -> (index) +%t = splat %s [%m, %n] : tensor +``` ### 'store' operation Syntax: -``` {.ebnf} +``` operation ::= `store` ssa-use `,` ssa-use `[` ssa-use-list `]` `:` memref-type ``` @@ -400,7 +404,7 @@ of such an `affine.apply` operation. Example: -```mlir {.mlir} +```mlir store %100, %A[%1, 1023] : memref<4x?xf32, #layout, memspace0> ``` @@ -417,7 +421,7 @@ in these contexts. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `tensor_load` ssa-use-and-type ``` @@ -427,7 +431,7 @@ operand. Example: -```mlir {.mlir} +```mlir // Produces a value of tensor<4x?xf32> type. %12 = tensor_load %10 : memref<4x?xf32, #layout, memspace0> ``` @@ -436,7 +440,7 @@ Example: Syntax: -``` {.ebnf} +``` operation ::= `tensor_store` ssa-use `,` ssa-use `:` memref-type ``` @@ -446,7 +450,7 @@ element types of these must match, and are specified by the memref type. Example: -```mlir {.mlir} +```mlir %9 = dim %8, 1 : tensor<4x?xf32> %10 = alloc(%9) : memref<4x?xf32, #layout, memspace0> tensor_store %8, %10 : memref<4x?xf32, #layout, memspace0> @@ -458,13 +462,13 @@ tensor_store %8, %10 : memref<4x?xf32, #layout, memspace0> Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `absf` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar absolute value. %a = absf %b : f64 @@ -484,13 +488,13 @@ attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `ceilf` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar ceiling value. %a = ceilf %b : f64 @@ -510,13 +514,13 @@ has no standard attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `cos` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar cosine value. %a = cos %b : f64 @@ -536,13 +540,13 @@ attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `exp` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar natural exponential. %a = exp %b : f64 @@ -561,13 +565,13 @@ tensor of floats. It has no standard attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `negf` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar negation value. %a = negf %b : f64 @@ -587,13 +591,13 @@ has no standard attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `tanh` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar hyperbolic tangent value. %a = tanh %b : f64 @@ -618,13 +622,13 @@ section. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `addi` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar addition. %a = addi %b, %c : i64 @@ -644,13 +648,13 @@ attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `addf` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar addition. %a = addf %b, %c : f64 @@ -676,13 +680,13 @@ Bitwise integer and. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `and` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar integer bitwise and. %a = and %b, %c : i64 @@ -702,13 +706,13 @@ attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `cmpi` string-literal `,` ssa-id `,` ssa-id `:` type ``` Examples: -```mlir {.mlir} +```mlir // Custom form of scalar "signed less than" comparison. %x = cmpi "slt", %lhs, %rhs : i32 @@ -772,7 +776,7 @@ positives Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `constant` attribute-value `:` type ``` @@ -786,7 +790,7 @@ The type specifies the result type of the operation. Examples: -```mlir {.mlir} +```mlir // Integer constant %1 = constant 42 : i32 @@ -809,13 +813,13 @@ function simplifies this Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `copysign` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar copysign value. %a = copysign %b %c : f64 @@ -841,13 +845,13 @@ value divided by -1) is TBD; do NOT assume any specific behavior. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `divis` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar signed integer division. %a = divis %b, %c : i64 @@ -874,13 +878,13 @@ behavior. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `diviu` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar unsigned integer division. %a = diviu %b, %c : i64 @@ -900,13 +904,13 @@ standard attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `memref_cast` ssa-use `:` type `to` type ``` Examples: -```mlir {.mlir} +```mlir // Discard static dimension information. %3 = memref_cast %2 : memref<4x?xf32> to memref @@ -932,13 +936,13 @@ if converting to a mismatching static rank. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `mulf` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar multiplication. %a = mulf %b, %c : f64 @@ -964,13 +968,13 @@ Bitwise integer or. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `or` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar integer bitwise or. %a = or %b, %c : i64 @@ -996,13 +1000,13 @@ behavior. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `remis` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar signed integer division remainder. %a = remis %b, %c : i64 @@ -1028,13 +1032,13 @@ behavior. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `remiu` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar unsigned integer division remainder. %a = remiu %b, %c : i64 @@ -1054,13 +1058,13 @@ standard attributes. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `select` ssa-use `,` ssa-use `,` ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Custom form of scalar selection. %x = select %cond, %true, %false : i32 @@ -1088,13 +1092,13 @@ implement `min` and `max` with signed or unsigned comparison semantics. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `tensor_cast` ssa-use `:` type `to` type ``` Examples: -```mlir {.mlir} +```mlir // Convert from unknown rank to rank 2 with unknown dimension sizes. %2 = "std.tensor_cast"(%1) : (tensor<*xf32>) -> tensor %2 = tensor_cast %1 : tensor<*xf32> to tensor @@ -1119,13 +1123,13 @@ Bitwise integer xor. Syntax: -``` {.ebnf} +``` operation ::= ssa-id `=` `xor` ssa-use, ssa-use `:` type ``` Examples: -```mlir {.mlir} +```mlir // Scalar integer bitwise xor. %a = xor %b, %c : i64 diff --git a/third_party/mlir/g3doc/EDSC.md b/third_party/mlir/g3doc/EDSC.md index 264dcaf33e4..afceac2dfc1 100644 --- a/third_party/mlir/g3doc/EDSC.md +++ b/third_party/mlir/g3doc/EDSC.md @@ -97,7 +97,7 @@ def AddOp : Op<"x.add">, Depending on the function signature on which this emitter is called, the generated IR resembles the following, for a 4-D memref of `vector<4xi8>`: -``` {.mlir} +``` // CHECK-LABEL: func @t1(%lhs: memref<3x4x5x6xvector<4xi8>>, %rhs: memref<3x4x5x6xvector<4xi8>>, %result: memref<3x4x5x6xvector<4xi8>>) -> () { // CHECK: affine.for {{.*}} = 0 to 3 { // CHECK: affine.for {{.*}} = 0 to 4 { @@ -111,7 +111,7 @@ generated IR resembles the following, for a 4-D memref of `vector<4xi8>`: or the following, for a 0-D `memref`: -``` {.mlir} +``` // CHECK-LABEL: func @t3(%lhs: memref, %rhs: memref, %result: memref) -> () { // CHECK: {{.*}} = load %arg1[] : memref // CHECK: {{.*}} = load %arg0[] : memref diff --git a/third_party/mlir/g3doc/LangRef.md b/third_party/mlir/g3doc/LangRef.md index a810330d37e..cd6d3314c7c 100644 --- a/third_party/mlir/g3doc/LangRef.md +++ b/third_party/mlir/g3doc/LangRef.md @@ -42,7 +42,7 @@ even arbitrary user-defined high-level operations including the Here's an example of an MLIR module: -```mlir {.mlir} +```mlir // Compute A*B using an implementation of multiply kernel and print the // result using a TensorFlow op. The dimensions of A and B are partially // known. The shapes are assumed to match. @@ -115,7 +115,7 @@ This document describes the grammar using This is the EBNF grammar used in this document, presented in yellow boxes. -``` {.ebnf} +``` alternation ::= expr0 | expr1 | expr2 // Either expr0 or expr1 or expr2. sequence ::= expr0 expr1 expr2 // Sequence of expr0 expr1 expr2. repetition0 ::= expr* // 0 or more occurrences. @@ -127,7 +127,7 @@ literal ::= `abcd` // Matches the literal `abcd`. Code examples are presented in blue boxes. -```mlir {.mlir} +```mlir // This is an example use of the grammar above: // This matches things like: ba, bana, boma, banana, banoma, bomana... example ::= `b` (`an` | `om`)* `a` @@ -137,7 +137,7 @@ example ::= `b` (`an` | `om`)* `a` The following core grammar productions are used in this document: -``` {.ebnf} +``` // TODO: Clarify the split between lexing (tokens) and parsing (grammar). digit ::= [0-9] hex_digit ::= [0-9a-fA-F] @@ -158,7 +158,7 @@ starting with a `//` and going until the end of the line. Syntax: -``` {.ebnf} +``` // Identifiers bare-id ::= (letter|[_]) (letter|digit|[_$.])* bare-id-list ::= bare-id (`,` bare-id)* @@ -226,7 +226,7 @@ with an "llvm." name. Example: -```mlir {.mlir} +```mlir // LLVM: %x = call {i16, i1} @llvm.sadd.with.overflow.i16(i16 %a, i16 %b) %x:2 = "llvm.sadd.with.overflow.i16"(%a, %b) : (i16, i16) -> (i16, i1) ``` @@ -238,7 +238,7 @@ GPUs), and are required to align with the LLVM definition of these intrinsics. Syntax: -``` {.ebnf} +``` operation ::= op-result-list? (generic-operation | custom-operation) trailing-location? generic-operation ::= string-literal '(' ssa-use-list? ')' attribute-dict? @@ -270,7 +270,7 @@ types of the results and operands. Example: -```mlir {.mlir} +```mlir // An operation that produces two results. // The results of %result can be accessed via the `#` syntax. %result:2 = "foo_div"() : () -> (f32, i32) @@ -295,7 +295,7 @@ also have a list of successors ([blocks](#blocks) and their arguments). Example: -```mlir {.mlir} +```mlir // Branch to ^bb1 or ^bb2 depending on the condition %cond. // Pass value %v to ^bb2, but not to ^bb1. "cond_br"(%cond)[^bb1, ^bb2(%v : index)] : (i1) -> () @@ -303,7 +303,7 @@ Example: ### Module -``` {.ebnf} +``` module ::= `module` symbol-ref-id? (`attributes` attribute-dict)? region ``` @@ -321,7 +321,7 @@ outside of the function, and all external references must use function arguments or attributes that establish a symbolic connection (e.g. symbols referenced by name via a string attribute like [SymbolRefAttr](#symbol-reference-attribute)): -``` {.ebnf} +``` function ::= `func` function-signature function-attributes? function-body? function-signature ::= symbol-ref-id `(` argument-list `)` @@ -352,7 +352,7 @@ function arguments, results, or the function itself. Examples: -```mlir {.mlir} +```mlir // External function definitions. func @abort() func @scribble(i32, i64, memref) -> f64 @@ -377,7 +377,7 @@ func @example_fn_attr() attributes {dialectName.attrName = false} Syntax: -``` {.ebnf} +``` block ::= block-label operation+ block-label ::= block-id block-arg-list? `:` block-id ::= caret-id @@ -402,7 +402,7 @@ provided for these block arguments by branches that go to the block. Here is a simple example function showing branches, returns, and block arguments: -```mlir {.mlir} +```mlir func @simple(i64, i1) -> i64 { ^bb0(%a: i64, %cond: i1): // Code dominated by ^bb0 may refer to %a cond_br %cond, ^bb1, ^bb2 @@ -447,7 +447,7 @@ attributes. The first block in the region cannot be a successor of any other block. The syntax for the region is as follows: -``` {.ebnf} +``` region ::= `{` block* `}` ``` @@ -468,7 +468,7 @@ would have been legal to use them as operands to the enclosing operation. Example: -```mlir {.mlir} +```mlir func @accelerator_compute(i64, i1) -> i64 { ^bb0(%a: i64, %cond: i1): // Code dominated by ^bb0 may refer to %a cond_br %cond, ^bb1, ^bb2 @@ -548,7 +548,7 @@ MLIR has an open type system (i.e. there is no fixed list of types), and types may have application-specific semantics. For example, MLIR supports a set of [dialect types](#dialect-types). -``` {.ebnf} +``` type ::= type-alias | dialect-type | standard-type type-list-no-parens ::= type (`,` type)* @@ -564,7 +564,7 @@ ssa-use-and-type-list ::= ssa-use-and-type (`,` ssa-use-and-type)* ### Type Aliases -``` {.ebnf} +``` type-alias-def ::= '!' alias-name '=' 'type' type type-alias ::= '!' alias-name ``` @@ -576,7 +576,7 @@ names are reserved for [dialect types](#dialect-types). Example: -```mlir {.mlir} +```mlir !avx_m128 = type vector<4 x f32> // Using the original type. @@ -591,7 +591,7 @@ Example: Similarly to operations, dialects may define custom extensions to the type system. -``` {.ebnf} +``` dialect-namespace ::= bare-id opaque-dialect-item ::= dialect-namespace '<' string-literal '>' @@ -613,7 +613,7 @@ dialect-type ::= '!' pretty-dialect-item Dialect types can be specified in a verbose form, e.g. like this: -```mlir {.mlir} +```mlir // LLVM type that wraps around llvm IR types. !llvm<"i32*"> @@ -630,7 +630,7 @@ Dialect types can be specified in a verbose form, e.g. like this: Dialect types that are simple enough can use the pretty format, which is a lighter weight syntax that is equivalent to the above forms: -```mlir {.mlir} +```mlir // Tensor flow string type. !tf.string @@ -651,7 +651,7 @@ See [here](DefiningAttributesAndTypes.md) to learn how to define dialect types. Standard types are a core set of [dialect types](#dialect-types) that are defined in a builtin dialect and thus available to all users of MLIR. -``` {.ebnf} +``` standard-type ::= complex-type | float-type | function-type @@ -668,7 +668,7 @@ standard-type ::= complex-type Syntax: -``` {.ebnf} +``` complex-type ::= `complex` `<` type `>` ``` @@ -678,7 +678,7 @@ type. The element must be a floating point or integer scalar type. Examples: -```mlir {.mlir} +```mlir complex complex ``` @@ -687,7 +687,7 @@ complex Syntax: -``` {.ebnf} +``` // Floating point. float-type ::= `f16` | `bf16` | `f32` | `f64` ``` @@ -699,7 +699,7 @@ above. Syntax: -``` {.ebnf} +``` // MLIR functions can return multiple values. function-result-type ::= type-list-parens | non-function-type @@ -721,7 +721,7 @@ Function types are also used to indicate the arguments and results of Syntax: -``` {.ebnf} +``` // Target word-sized integer. index-type ::= `index` ``` @@ -739,7 +739,7 @@ sizes, dimensionalities and subscripts. Syntax: -``` {.ebnf} +``` // Sized integers like i1, i4, i8, i16, i32. integer-type ::= `i` [1-9][0-9]* ``` @@ -759,7 +759,7 @@ TODO: Need to decide on a representation for quantized integers Syntax: -``` {.ebnf} +``` memref-type ::= ranked-memref-type | unranked-memref-type @@ -811,14 +811,10 @@ exposed to codegen but one may query the rank of an unranked memref (a special op will be needed for this purpose) and perform a switch and cast to a ranked memref as a prerequisite to codegen. -Example -```mlir {.mlir} -// With static ranks, we need a function for each -// possible argument type -%A = alloc() : memref<16x32xf32> -%B = alloc() : memref<16x32x64xf32> -call @helper_2D(%A) : (memref<16x32xf32>)->() -call @helper_3D(%B) : (memref<16x32x64xf32>)->() +Example ```mlir // With static ranks, we need a function for each // possible +argument type %A = alloc() : memref<16x32xf32> %B = alloc() : +memref<16x32x64xf32> call @helper_2D(%A) : (memref<16x32xf32>)->() call +@helper_3D(%B) : (memref<16x32x64xf32>)->() // With unknown rank, the functions can be unified under one unranked type %A = alloc() : memref<16x32xf32> @@ -852,7 +848,7 @@ and index maps. Examples of memref static type -```mlir {.mlir} +```mlir // Identity index/layout map #identity = (d0, d1) -> (d0, d1) @@ -920,7 +916,7 @@ multidimensional index space defined by the memref's dimension list. Examples -```mlir {.mlir} +```mlir // Allocates a memref with 2D index space: // { (i, j) : 0 <= i < 16, 0 <= j < 32 } %A = alloc() : memref<16x32xf32, #imapA, memspace0> @@ -959,7 +955,7 @@ maps, `memref`. Layout map examples: -```mlir {.mlir} +```mlir // MxN matrix stored in row major layout in memory: #layout_map_row_major = (i, j) -> (i, j) @@ -1024,7 +1020,7 @@ normalized memref descriptor when lowering to LLVM. Syntax: -``` {.ebnf} +``` none-type ::= `none` ``` @@ -1035,7 +1031,7 @@ where its value does not have a defined dynamic representation. Syntax: -``` {.ebnf} +``` tensor-type ::= `tensor` `<` dimension-list tensor-memref-element-type `>` tensor-memref-element-type ::= vector-element-type | vector-type | complex-type @@ -1066,7 +1062,7 @@ types, such tensors should be optimized away before lowering tensors to vectors. Examples: -```mlir {.mlir} +```mlir // Tensor with unknown rank. tensor<* x f32> @@ -1093,7 +1089,7 @@ tensor<0xf32> Syntax: -``` {.ebnf} +``` tuple-type ::= `tuple` `<` (type ( `,` type)*)? `>` ``` @@ -1106,7 +1102,7 @@ no standard operations for operating on `tuple` types Examples: -```mlir {.mlir} +```mlir // Empty tuple. tuple<> @@ -1121,7 +1117,7 @@ tuple, i5> Syntax: -``` {.ebnf} +``` vector-type ::= `vector` `<` static-dimension-list vector-element-type `>` vector-element-type ::= float-type | integer-type @@ -1143,7 +1139,7 @@ shape `(0, 42)` and zero shapes are not allowed. Syntax: -``` {.ebnf} +``` attribute-dict ::= `{` `}` | `{` attribute-entry (`,` attribute-entry)* `}` attribute-entry ::= dialect-attribute-entry | dependent-attribute-entry @@ -1170,13 +1166,13 @@ dialect and not the function argument. Attribute values are represented by the following forms: -``` {.ebnf} +``` attribute-value ::= attribute-alias | dialect-attribute | standard-attribute ``` ### Attribute Value Aliases -``` {.ebnf} +``` attribute-alias ::= '#' alias-name '=' attribute-value attribute-alias ::= '#' alias-name ``` @@ -1189,7 +1185,7 @@ These aliases *must* be defined before their uses. Alias names may not contain a Example: -```mlir {.mlir} +```mlir #map = (d0) -> (d0 + 10) // Using the original attribute. @@ -1206,14 +1202,14 @@ syntactic structure of these values is identical to custom dialect type values, except that dialect attributes values are distinguished with a leading '#', while dialect types are distinguished with a leading '!'. -``` {.ebnf} +``` dialect-attribute ::= '#' opaque-dialect-item dialect-attribute ::= '#' pretty-dialect-item ``` Dialect attributes can be specified in a verbose form, e.g. like this: -```mlir {.mlir} +```mlir // Complex attribute #foo<"something"> @@ -1224,7 +1220,7 @@ Dialect attributes can be specified in a verbose form, e.g. like this: Dialect attributes that are simple enough can use the pretty format, which is a lighter weight syntax that is equivalent to the above forms: -```mlir {.mlir} +```mlir // Complex attribute #foo.something ``` @@ -1244,7 +1240,7 @@ Standard attributes are a core set of [dialect attributes](#dialect-attribute-values) that are defined in a builtin dialect and thus available to all users of MLIR. -``` {.ebnf} +``` standard-attribute ::= affine-map-attribute | array-attribute | bool-attribute @@ -1263,7 +1259,7 @@ standard-attribute ::= affine-map-attribute Syntax: -``` {.ebnf} +``` affine-map-attribute ::= affine-map ``` @@ -1273,7 +1269,7 @@ An affine-map attribute is an attribute that represents a affine-map object. Syntax: -``` {.ebnf} +``` array-attribute ::= `[` (attribute-value (`,` attribute-value)*)? `]` ``` @@ -1284,7 +1280,7 @@ values. Syntax: -``` {.ebnf} +``` bool-attribute ::= bool-literal ``` @@ -1295,7 +1291,7 @@ value, true or false. Syntax: -``` {.ebnf} +``` dictionary-attribute ::= `{` (attribute-entry (`,` attribute-entry)*)? `}` ``` @@ -1307,7 +1303,7 @@ unique within the collection. Syntax: -``` {.ebnf} +``` elements-attribute ::= dense-elements-attribute | opaque-elements-attribute | sparse-elements-attribute @@ -1320,7 +1316,7 @@ An elements attribute is a literal attribute that represents a constant Syntax: -``` {.ebnf} +``` dense-elements-attribute ::= `dense` `<` attribute-value `>` `:` ( tensor-type | vector-type ) ``` @@ -1334,7 +1330,7 @@ floating point type. Syntax: -``` {.ebnf} +``` opaque-elements-attribute ::= `opaque` `<` dialect-namespace `,` hex-string-literal `>` `:` ( tensor-type | vector-type ) @@ -1351,7 +1347,7 @@ Note: The parsed string literal must be in hexadecimal form. Syntax: -``` {.ebnf} +``` sparse-elements-attribute ::= `sparse` `<` attribute-value `,` attribute-value `>` `:` ( tensor-type | vector-type ) ``` @@ -1368,7 +1364,7 @@ corresponding values for the indices. Example: -```mlir {.mlir} +```mlir sparse<[[0, 0], [1, 2]], [1, 5]> : tensor<3x4xi32> // This represents the following tensor: @@ -1381,7 +1377,7 @@ Example: Syntax: -``` {.ebnf} +``` float-attribute ::= (float-literal (`:` float-type)?) | (hexadecimal-literal `:` float-type) ``` @@ -1396,7 +1392,7 @@ attribute. Examples: -``` {.mlir} +``` 42.0 // float attribute defaults to f64 type 42.0 : f32 // float attribute of f32 type 0x7C00 : f16 // positive infinity @@ -1408,7 +1404,7 @@ Examples: Syntax: -``` {.ebnf} +``` integer-attribute ::= integer-literal ( `:` (index-type | integer-type) )? ``` @@ -1420,7 +1416,7 @@ is not specified, is a 64-bit integer. Syntax: -``` {.ebnf} +``` integer-set-attribute ::= affine-map ``` @@ -1430,7 +1426,7 @@ An integer-set attribute is an attribute that represents an integer-set object. Syntax: -``` {.ebnf} +``` string-attribute ::= string-literal (`:` type)? ``` @@ -1440,7 +1436,7 @@ A string attribute is an attribute that represents a string literal value. Syntax: -``` {.ebnf} +``` symbol-ref-attribute ::= symbol-ref-id (`::` symbol-ref-id)* ``` @@ -1468,7 +1464,7 @@ reference, we can always opaquely reason about a symbols usage characteristics. Syntax: -``` {.ebnf} +``` type-attribute ::= type ``` @@ -1476,7 +1472,7 @@ A type attribute is an attribute that represents a [type object](#type-system). #### Unit Attribute -``` {.ebnf} +``` unit-attribute ::= `unit` ``` @@ -1490,7 +1486,7 @@ could be represented as a [boolean attribute](#boolean-attribute)(true or false), but a value of false doesn't really bring any value. The parameter either is the self/context or it isn't. -```mlir {.mlir} +```mlir // A unit attribute defined with the `unit` value specifier. func @verbose_form(i1) attributes {dialectName.unitAttr = unit} diff --git a/third_party/mlir/g3doc/MLIRForGraphAlgorithms.md b/third_party/mlir/g3doc/MLIRForGraphAlgorithms.md index 9130d031b4f..ac26e5beb9b 100644 --- a/third_party/mlir/g3doc/MLIRForGraphAlgorithms.md +++ b/third_party/mlir/g3doc/MLIRForGraphAlgorithms.md @@ -207,7 +207,7 @@ test the dependence analysis infra in the code generator, Andy Davis wrote a simple pass that checks dependencies and emits them as "notes", allowing him to write tests like this: -```mlir {.mlir} +```mlir // RUN: mlir-opt %s -memref-dependence-check -verify-diagnostics func @different_memrefs() { %m.a = alloc() : memref<100xf32> @@ -241,7 +241,7 @@ with ShapeRefiner. The [MLIR Tensor Type](LangRef.md#tensor-type) directly captures shape information, so you can have things like: -```mlir {.mlir} +```mlir %x = tf.Add %x, %y : tensor<128 x 8 x ? x f32> ``` diff --git a/third_party/mlir/g3doc/OpDefinitions.md b/third_party/mlir/g3doc/OpDefinitions.md index 7fb0e53ea17..0e786a0a4e7 100644 --- a/third_party/mlir/g3doc/OpDefinitions.md +++ b/third_party/mlir/g3doc/OpDefinitions.md @@ -187,7 +187,6 @@ led by `ins`: let arguments = (ins :$, ... - :$, ... ); @@ -199,17 +198,18 @@ hierarchy. Similarly, `` is a TableGen `def` from the information. There is no requirements on the relative order of operands and attributes; they -can mix freely. But it is recommended to put all operands ahead of attributes, -and use an empty line to separate them to make it more visually distinguishable -if possible. The relative order of operands themselves matters. +can mix freely. The relative order of operands themselves matters. From each +named argument a named getter will be generated that returns the argument with +the return type (in the case of attributes the return type will be +constructed from the storage type, while for operands it will be `Value`). Each +attribute's raw value (e.g., as stored) can also be accessed via generated +`Attr` getters for use in transformation passes where the more user +friendly return type is less suitable. All the arguments should be named to 1) provide documentation, 2) drive auto-generation of getter methods, 3) provide a handle to reference for other places like constraints. -> * Place attributes after operands if possible -> * Give operands and attribute proper names - #### Variadic operands To declare a variadic operand, wrap the `TypeConstraint` for the operand with @@ -290,7 +290,7 @@ class. See [Constraints](#constraints) for more information. ### Operation interfaces [Operation interfaces](Interfaces.md#operation-interfaces) are a mechanism by -which to opaquely call methods and access information on an *Op instance, +which to opaquely call methods and access information on an *Op instance*, without knowing the exact operation type. Operation interfaces defined in C++ can be accessed in the ODS framework via the `OpInterfaceTrait` class. Aside from using pre-existing interfaces in the C++ API, the ODS framework also @@ -414,7 +414,7 @@ The following builders are generated: // All result-types/operands/attributes have one aggregate parameter. static void build(Builder *tblgen_builder, OperationState &tblgen_state, ArrayRef resultTypes, - ArrayRef operands, + ValueRange operands, ArrayRef attributes); // Each result-type/operand/attribute has a separate parameter. The parameters @@ -433,7 +433,19 @@ static void build(Builder *tblgen_builder, OperationState &tblgen_state, Value *i32_operand, Value *f32_operand, ..., APInt i32_attr, StringRef f32_attr, ...); -// (And potentially others depending on the specific op.) +// Each operand/attribute has a separate parameter but result type is aggregate. +static void build(Builder *tblgen_builder, OperationState &tblgen_state, + ArrayRef resultTypes, + Value *i32_operand, Value *f32_operand, ..., + IntegerAttr i32_attr, FloatAttr f32_attr, ...); + +// All operands/attributes have aggregate parameters. +// Generated if InferTypeOpInterface interface is specified. +static void build(Builder *tblgen_builder, OperationState &tblgen_state, + ValueRange operands, + ArrayRef attributes); + +// (And manually specified builders depending on the specific op.) ``` The first form provides basic uniformity so that we can create ops using the diff --git a/third_party/mlir/g3doc/Rationale.md b/third_party/mlir/g3doc/Rationale.md index efccf0752d7..66cf800621d 100644 --- a/third_party/mlir/g3doc/Rationale.md +++ b/third_party/mlir/g3doc/Rationale.md @@ -134,7 +134,7 @@ unknown dimension can be queried using the "dim" builtin as shown below. Example: -```mlir {.mlir} +```mlir func foo(...) { %A = alloc <8x?xf32, #lmap> (%N) ... @@ -532,7 +532,7 @@ the existing LLVM type parsing infrastructure. Example: -```mlir {.mlir} +```mlir %s = "foo"() : () -> !llvm<"i32*"> ``` @@ -588,7 +588,7 @@ represents computation. ### Non-affine control flow -```mlir {.mlir} +```mlir // A simple linear search in every row of a matrix for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { @@ -604,7 +604,7 @@ for (i = 0; i < N; i++) { The presence of dynamic control flow leads to an inner non-affine function nested in an outer function that using affine loops. -```mlir {.mlir} +```mlir func @search(%A: memref, %key : i32) { %ni = dim %A, 0 : memref // This loop can be parallelized @@ -662,7 +662,7 @@ for (i = 0; i < N; i++)        } ``` -```mlir {.mlir} +```mlir func @outer_nest(%n : index) { affine.for %i = 0 to %n { affine.for %j = 0 to %n { @@ -689,7 +689,7 @@ The following example illustrates a reference implementation of a 2D convolution, which uses an integer set `#domain` to represent valid input data in a dilated convolution. -```mlir {.mlir} +```mlir // Dilation factors S0 and S1 can be constant folded if constant at compile time. #domain = (d0, d1)[S0,S1,S2,S3]: (d0 % S0 == 0, d1 % S1 == 0, d0 >= 0, d1 >= 0, S3 - d0 - 1 >= 0, S4 - d1 - 1 >= 0) @@ -805,7 +805,7 @@ which is called a schedule tree. Each non-leaf node of the tree is an abstract polyhedral dimension corresponding to an abstract fused loop for each ML instruction that appears in that branch. Each leaf node is an ML Instruction. -```mlir {.mlir} +```mlir // A tiled matmul code (128x128x128) represented in schedule tree form // #map0 = (d0, d1, d2, d3, d4, d5) -> (128*d0 + d3, 128*d1 + d4, 128*d2 + d5) @@ -859,7 +859,7 @@ constraints on the identifiers. Syntax: -``` {.ebnf} +``` // Affine relation definition at the top of file affine-rel-def ::= affine-rel-id `=` affine-relation-inline @@ -890,7 +890,7 @@ dimensions. Example: -```mlir {.mlir} +```mlir // read relation: two elements ( d0 <= r0 <= d0+1 ) ##aff_rel9 = (d0) -> (r0) : r0 - d0 >= 0, d0 - r0 + 1 >= 0 @@ -966,7 +966,7 @@ TODO: Design this, and update to use function attribute syntax. Example: -```mlir {.mlir} +```mlir ##rel9 ( ) [s0] -> (r0, r1) : 0 <= r0 <= 1023, 0 <= r1 <= s0 - 1 func @cblas_reduce_ffi(%M: memref<1024 x ? x f32, #layout_map0, /*mem=*/0>) @@ -1027,7 +1027,7 @@ The abandoned design of supporting escaping scalars is as follows: Syntax: -``` {.ebnf} +``` [ =] for % = ... step [with ] { } @@ -1040,7 +1040,7 @@ is a list of instructions that may also include a yield instruction. Example: -```mlir {.mlir} +```mlir // Return sum of elements in 1-dimensional mref A func i32 @sum(%A : memref, %N : i32) -> (i32) { %init = 0 @@ -1057,7 +1057,7 @@ func i32 @sum(%A : memref, %N : i32) -> (i32) { Syntax: -``` {.ebnf} +``` = affine.if () {...} [else {...}] ``` @@ -1070,7 +1070,7 @@ this situation. Example: -```mlir {.mlir} +```mlir // Compute sum of half of the array func i32 @sum_half(%A : memref, %N : i32) -> (i32) { %s0 = 0 diff --git a/third_party/mlir/g3doc/TestingGuide.md b/third_party/mlir/g3doc/TestingGuide.md index 5a8c039fdd6..723b78bf0f5 100644 --- a/third_party/mlir/g3doc/TestingGuide.md +++ b/third_party/mlir/g3doc/TestingGuide.md @@ -27,7 +27,7 @@ different aspects of the IR - such as the output of a transformation pass. An example FileCheck test is shown below: -```mlir {.mlir} +```mlir // RUN: mlir-opt %s -cse | FileCheck %s // CHECK-LABEL: func @simple_constant @@ -52,7 +52,7 @@ brittle tests that are essentially `diff` tests. FileCheck tests should be as self-contained as possible and focus on testing the minimal set of functionalities needed. Let's see an example: -```mlir {.mlir} +```mlir // RUN: mlir-opt %s -cse | FileCheck %s // CHECK-LABEL: func @simple_constant() -> (i32, i32) @@ -89,7 +89,7 @@ IR output. If we naively remove the unrelated `CHECK` lines in our source file, we may end up with: -```mlir {.mlir} +```mlir // CHECK-LABEL: func @simple_constant func @simple_constant() -> (i32, i32) { // CHECK-NEXT: %result = constant 1 : i32 @@ -111,7 +111,7 @@ as well as named Utilizing the above, we end up with the example shown in the main [FileCheck tests](#filecheck-tests) section. -```mlir {.mlir} +```mlir // CHECK-LABEL: func @simple_constant func @simple_constant() -> (i32, i32) { /// Here we use a substitution variable as the output of the constant is @@ -140,7 +140,7 @@ accessible via the `verify-diagnostics` flag in mlir-opt. An example .mlir test running under `mlir-opt` is shown below: -```mlir {.mlir} +```mlir // RUN: mlir-opt %s -split-input-file -verify-diagnostics // Expect an error on the same line. diff --git a/third_party/mlir/g3doc/Traits.md b/third_party/mlir/g3doc/Traits.md index a3d91a7c606..25e20234691 100644 --- a/third_party/mlir/g3doc/Traits.md +++ b/third_party/mlir/g3doc/Traits.md @@ -78,7 +78,7 @@ Traits may be used when defining a derived operation type, by simply adding the name of the trait class to the `Op` class after the concrete operation type: ```c++ -/// Here we define 'MyOp' along with the 'MyTrait' and `MyParameteric trait +/// Here we define 'MyOp' along with the 'MyTrait' and `MyParametric trait /// classes we defined previously. class MyOp : public Op::Impl> {}; ``` diff --git a/third_party/mlir/g3doc/Tutorials/Toy/Ch-7.md b/third_party/mlir/g3doc/Tutorials/Toy/Ch-7.md index a55c5836a66..398983ac469 100644 --- a/third_party/mlir/g3doc/Tutorials/Toy/Ch-7.md +++ b/third_party/mlir/g3doc/Tutorials/Toy/Ch-7.md @@ -254,7 +254,7 @@ form available under certain circumstances. The responsibility of our `Toy` parser and printer is to provide the `type-data` bits. We will define our `StructType` as having the following form: -``` {.ebnf} +``` struct-type ::= `struct` `<` type (`,` type)* `>` ``` diff --git a/third_party/mlir/include/mlir/Analysis/InferTypeOpInterface.td b/third_party/mlir/include/mlir/Analysis/InferTypeOpInterface.td index 2fa1cc887ca..7f63b2b18bf 100644 --- a/third_party/mlir/include/mlir/Analysis/InferTypeOpInterface.td +++ b/third_party/mlir/include/mlir/Analysis/InferTypeOpInterface.td @@ -50,7 +50,7 @@ def InferTypeOpInterface : OpInterface<"InferTypeOpInterface"> { /*args=*/(ins "llvm::Optional":$location, "ValueRange":$operands, "ArrayRef":$attributes, - "ArrayRef":$regions, + "RegionRange":$regions, "SmallVectorImpl&":$inferedReturnTypes) >, StaticInterfaceMethod< diff --git a/third_party/mlir/include/mlir/Analysis/Liveness.h b/third_party/mlir/include/mlir/Analysis/Liveness.h new file mode 100644 index 00000000000..0bdb474fd92 --- /dev/null +++ b/third_party/mlir/include/mlir/Analysis/Liveness.h @@ -0,0 +1,157 @@ +//===- Liveness.h - Liveness analysis for MLIR ------------------*- C++ -*-===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// This file contains an analysis for computing liveness information from a +// given top-level operation. The current version of the analysis uses a +// traditional algorithm to resolve detailed live-range information about all +// values within the specified regions. It is also possible to query liveness +// information on block level. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_ANALYSIS_LIVENESS_H +#define MLIR_ANALYSIS_LIVENESS_H + +#include + +#include "mlir/Support/LLVM.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallPtrSet.h" + +namespace mlir { + +class Block; +class LivenessBlockInfo; +class Operation; +class Region; +class Value; + +/// Represents an analysis for computing liveness information from a +/// given top-level operation. The analysis iterates over all associated +/// regions that are attached to the given top-level operation. It +/// computes liveness information for every value and block that are +/// included in the mentioned regions. It relies on a fixpoint iteration +/// to compute all live-in and live-out values of all included blocks. +/// Sample usage: +/// Liveness liveness(topLevelOp); +/// auto &allInValues = liveness.getLiveIn(block); +/// auto &allOutValues = liveness.getLiveOut(block); +/// auto allOperationsInWhichValueIsLive = liveness.resolveLiveness(value); +/// bool lastUse = liveness.isLastUse(value, operation); +class Liveness { +public: + using OperationListT = std::vector; + using BlockMapT = DenseMap; + using ValueSetT = SmallPtrSet; + +public: + /// Creates a new Liveness analysis that computes liveness + /// information for all associated regions. + Liveness(Operation *op); + + /// Returns the operation this analysis was constructed from. + Operation *getOperation() const { return operation; } + + /// Gets liveness info (if any) for the given value. + /// This includes all operations in which the given value is live. + /// Note that the operations in this list are not ordered and the current + /// implementation is computationally expensive (as it iterates over all + /// blocks in which the given value is live). + OperationListT resolveLiveness(Value *value) const; + + /// Gets liveness info (if any) for the block. + const LivenessBlockInfo *getLiveness(Block *block) const; + + /// Returns a reference to a set containing live-in values (unordered). + const ValueSetT &getLiveIn(Block *block) const; + + /// Returns a reference to a set containing live-out values (unordered). + const ValueSetT &getLiveOut(Block *block) const; + + /// Returns true if the given operation represent the last use of the + /// given value. + bool isLastUse(Value *value, Operation *operation) const; + + /// Dumps the liveness information in a human readable format. + void dump() const; + + /// Dumps the liveness information to the given stream. + void print(raw_ostream &os) const; + +private: + /// Initializes the internal mappings. + void build(MutableArrayRef regions); + +private: + /// The operation this analysis was constructed from. + Operation *operation; + + /// Maps blocks to internal liveness information. + BlockMapT blockMapping; +}; + +/// This class represents liveness information on block level. +class LivenessBlockInfo { +public: + /// A typedef declaration of a value set. + using ValueSetT = Liveness::ValueSetT; + +public: + /// Returns the underlying block. + Block *getBlock() const { return block; } + + /// Returns all values that are live at the beginning + /// of the block (unordered). + const ValueSetT &in() const { return inValues; } + + /// Returns all values that are live at the end + /// of the block (unordered). + const ValueSetT &out() const { return outValues; } + + /// Returns true if the given value is in the live-in set. + bool isLiveIn(Value *value) const; + + /// Returns true if the given value is in the live-out set. + bool isLiveOut(Value *value) const; + + /// Gets the start operation for the given value. This is the first operation + /// the given value is considered to be live. This could either be the start + /// operation of the current block (in case the value is live-in) or the + /// operation that defines the given value (must be referenced in this block). + Operation *getStartOperation(Value *value) const; + + /// Gets the end operation for the given value using the start operation + /// provided (must be referenced in this block). + Operation *getEndOperation(Value *value, Operation *startOperation) const; + +private: + /// The underlying block. + Block *block; + + /// The set of all live in values. + ValueSetT inValues; + + /// The set of all live out values. + ValueSetT outValues; + + friend class Liveness; +}; + +} // end namespace mlir + +#endif // MLIR_ANALYSIS_LIVENESS_H diff --git a/third_party/mlir/include/mlir/Analysis/NestedMatcher.h b/third_party/mlir/include/mlir/Analysis/NestedMatcher.h index dd4022a2617..9af26e8842a 100644 --- a/third_party/mlir/include/mlir/Analysis/NestedMatcher.h +++ b/third_party/mlir/include/mlir/Analysis/NestedMatcher.h @@ -24,7 +24,7 @@ namespace mlir { -struct NestedPattern; +class NestedPattern; class Operation; /// An NestedPattern captures nested patterns in the IR. @@ -52,7 +52,8 @@ class Operation; /// A NestedMatch contains an Operation* and the children NestedMatch and is /// thus cheap to copy. NestedMatch is stored in a scoped bumper allocator whose /// lifetime is managed by an RAII NestedPatternContext. -struct NestedMatch { +class NestedMatch { +public: static NestedMatch build(Operation *operation, ArrayRef nestedMatches); NestedMatch(const NestedMatch &) = default; @@ -64,8 +65,8 @@ struct NestedMatch { ArrayRef getMatchedChildren() { return matchedChildren; } private: - friend struct NestedPattern; - friend struct NestedPatternContext; + friend class NestedPattern; + friend class NestedPatternContext; /// Underlying global bump allocator managed by a NestedPatternContext. static llvm::BumpPtrAllocator *&allocator(); @@ -97,7 +98,8 @@ private: /// implementation is competitive nonetheless. using FilterFunctionType = std::function; inline bool defaultFilterFunction(Operation &) { return true; } -struct NestedPattern { +class NestedPattern { +public: NestedPattern(ArrayRef nested, FilterFunctionType filter = defaultFilterFunction); NestedPattern(const NestedPattern &) = default; @@ -117,8 +119,8 @@ struct NestedPattern { unsigned getDepth() const; private: - friend struct NestedPatternContext; - friend struct NestedMatch; + friend class NestedPatternContext; + friend class NestedMatch; friend struct State; /// Underlying global bump allocator managed by a NestedPatternContext. @@ -153,7 +155,8 @@ private: /// RAII structure to transparently manage the bump allocator for /// NestedPattern and NestedMatch classes. This avoids passing a context to /// all the API functions. -struct NestedPatternContext { +class NestedPatternContext { +public: NestedPatternContext() { assert(NestedMatch::allocator() == nullptr && "Only a single NestedPatternContext is supported"); diff --git a/third_party/mlir/include/mlir/CMakeLists.txt b/third_party/mlir/include/mlir/CMakeLists.txt index 43eacfc91d5..84031a5e72a 100644 --- a/third_party/mlir/include/mlir/CMakeLists.txt +++ b/third_party/mlir/include/mlir/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(Analysis) add_subdirectory(Dialect) -add_subdirectory(EDSC) add_subdirectory(IR) add_subdirectory(Transforms) diff --git a/third_party/mlir/include/mlir/Conversion/GPUToNVVM/GPUToNVVMPass.h b/third_party/mlir/include/mlir/Conversion/GPUToNVVM/GPUToNVVMPass.h index 42a9faf40e9..635d4366e83 100644 --- a/third_party/mlir/include/mlir/Conversion/GPUToNVVM/GPUToNVVMPass.h +++ b/third_party/mlir/include/mlir/Conversion/GPUToNVVM/GPUToNVVMPass.h @@ -26,6 +26,10 @@ class OwningRewritePatternList; class ModuleOp; template class OpPassBase; +/// Collect a set of patterns to convert from the GPU dialect to NVVM. +void populateGpuToNVVMConversionPatterns(LLVMTypeConverter &converter, + OwningRewritePatternList &patterns); + /// Creates a pass that lowers GPU dialect operations to NVVM counterparts. std::unique_ptr> createLowerGpuOpsToNVVMOpsPass(); diff --git a/third_party/mlir/include/mlir/Conversion/LoopToStandard/ConvertLoopToStandard.h b/third_party/mlir/include/mlir/Conversion/LoopToStandard/ConvertLoopToStandard.h index e44e723376f..095c9f470b3 100644 --- a/third_party/mlir/include/mlir/Conversion/LoopToStandard/ConvertLoopToStandard.h +++ b/third_party/mlir/include/mlir/Conversion/LoopToStandard/ConvertLoopToStandard.h @@ -22,10 +22,9 @@ #include namespace mlir { -class FuncOp; struct LogicalResult; class MLIRContext; -template class OpPassBase; +class Pass; class RewritePattern; // Owning list of rewriting patterns. @@ -38,7 +37,7 @@ void populateLoopToStdConversionPatterns(OwningRewritePatternList &patterns, MLIRContext *ctx); /// Creates a pass to convert loop.for, loop.if and loop.terminator ops to CFG. -std::unique_ptr> createLowerToCFGPass(); +std::unique_ptr createLowerToCFGPass(); } // namespace mlir diff --git a/third_party/mlir/include/mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h b/third_party/mlir/include/mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h index cef80cf0b23..e8d16f064a8 100644 --- a/third_party/mlir/include/mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h +++ b/third_party/mlir/include/mlir/Conversion/StandardToLLVM/ConvertStandardToLLVM.h @@ -75,8 +75,8 @@ public: /// and use pointers to struct to avoid the complexity of the /// platform-specific C/C++ ABI lowering related to struct argument passing. SmallVector promoteMemRefDescriptors(Location loc, - ArrayRef opOperands, - ArrayRef operands, + ValueRange opOperands, + ValueRange operands, OpBuilder &builder); /// Promote the LLVM struct representation of one MemRef descriptor to stack diff --git a/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.h b/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.h index 8d36473674b..8268f81b856 100644 --- a/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.h +++ b/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.h @@ -47,6 +47,11 @@ class AffineOpsDialect : public Dialect { public: AffineOpsDialect(MLIRContext *context); static StringRef getDialectNamespace() { return "affine"; } + + /// Materialize a single constant operation from a given attribute value with + /// the desired resultant type. + Operation *materializeConstant(OpBuilder &builder, Attribute value, Type type, + Location loc) override; }; /// The "affine.apply" operation applies an affine map to a list of operands, @@ -290,8 +295,8 @@ public: static ParseResult parse(OpAsmParser &parser, OperationState &result); void print(OpAsmPrinter &p); LogicalResult verify(); - static void getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context); + LogicalResult fold(ArrayRef cstOperands, + SmallVectorImpl &results); /// Returns true if this DMA operation is strided, returns false otherwise. bool isStrided() { @@ -375,8 +380,8 @@ public: static ParseResult parse(OpAsmParser &parser, OperationState &result); void print(OpAsmPrinter &p); LogicalResult verify(); - static void getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context); + LogicalResult fold(ArrayRef cstOperands, + SmallVectorImpl &results); }; /// The "affine.load" op reads an element from a memref, where the index @@ -445,6 +450,7 @@ public: LogicalResult verify(); static void getCanonicalizationPatterns(OwningRewritePatternList &results, MLIRContext *context); + OpFoldResult fold(ArrayRef operands); }; /// The "affine.store" op writes an element to a memref, where the index @@ -515,6 +521,8 @@ public: LogicalResult verify(); static void getCanonicalizationPatterns(OwningRewritePatternList &results, MLIRContext *context); + LogicalResult fold(ArrayRef cstOperands, + SmallVectorImpl &results); }; /// Returns true if the given Value can be used as a dimension id. diff --git a/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.td b/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.td index 4d4060414dd..cea44b8dacd 100644 --- a/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.td +++ b/third_party/mlir/include/mlir/Dialect/AffineOps/AffineOps.td @@ -177,12 +177,13 @@ def AffineForOp : Affine_Op<"for", /// Sets the upper bound to the given constant value. void setConstantUpperBound(int64_t value); - /// Returns true if both the lower and upper bound have the same operand + /// Returns true if both the lower and upper bound have the same operand /// lists (same operands in the same order). bool matchingBoundOperandList(); }]; let hasCanonicalizer = 1; + let hasFolder = 1; } def AffineIfOp : Affine_Op<"if", [ImplicitAffineTerminator]> { @@ -239,7 +240,7 @@ def AffineIfOp : Affine_Op<"if", [ImplicitAffineTerminator]> { } }]; - let hasCanonicalizer = 1; + let hasFolder = 1; } def AffineMinOp : Affine_Op<"min"> { diff --git a/third_party/mlir/include/mlir/Dialect/GPU/GPUDialect.h b/third_party/mlir/include/mlir/Dialect/GPU/GPUDialect.h index e92cdccfaaa..8b62c70178b 100644 --- a/third_party/mlir/include/mlir/Dialect/GPU/GPUDialect.h +++ b/third_party/mlir/include/mlir/Dialect/GPU/GPUDialect.h @@ -77,58 +77,6 @@ struct KernelDim3 { Value *z; }; -/// Operation to launch a kernel given as outlined function. -class LaunchFuncOp : public Op::Impl, - OpTrait::ZeroResult> { -public: - using Op::Op; - - static void build(Builder *builder, OperationState &result, FuncOp kernelFunc, - Value *gridSizeX, Value *gridSizeY, Value *gridSizeZ, - Value *blockSizeX, Value *blockSizeY, Value *blockSizeZ, - ValueRange kernelOperands); - - static void build(Builder *builder, OperationState &result, FuncOp kernelFunc, - KernelDim3 gridSize, KernelDim3 blockSize, - ValueRange kernelOperands); - - /// The kernel function specified by the operation's `kernel` attribute. - StringRef kernel(); - /// The number of operands passed to the kernel function. - unsigned getNumKernelOperands(); - /// The name of the kernel module specified by the operation's `kernel_module` - /// attribute. - StringRef getKernelModuleName(); - /// The i-th operand passed to the kernel function. - Value *getKernelOperand(unsigned i); - - /// Get the SSA values passed as operands to specify the grid size. - KernelDim3 getGridSizeOperandValues(); - /// Get the SSA values passed as operands to specify the block size. - KernelDim3 getBlockSizeOperandValues(); - - LogicalResult verify(); - - static StringRef getOperationName() { return "gpu.launch_func"; } - - /// The number of launch configuration operands, placed at the leading - /// positions of the operand list. - static constexpr unsigned kNumConfigOperands = 6; - -private: - // This needs to quietly verify if attributes with names defined below are - // present since it is run before the verifier of this op. - friend LogicalResult GPUDialect::verifyOperationAttribute(Operation *, - NamedAttribute); - - /// The name of the symbolRef attribute specifying the kernel to launch. - static StringRef getKernelAttrName() { return "kernel"; } - - /// The name of the symbolRef attribute specifying the name of the module - /// containing the kernel to launch. - static StringRef getKernelModuleAttrName() { return "kernel_module"; } -}; - #define GET_OP_CLASSES #include "mlir/Dialect/GPU/GPUOps.h.inc" diff --git a/third_party/mlir/include/mlir/Dialect/GPU/GPUOps.td b/third_party/mlir/include/mlir/Dialect/GPU/GPUOps.td index 26f2b4da3a8..5f7bab3040f 100644 --- a/third_party/mlir/include/mlir/Dialect/GPU/GPUOps.td +++ b/third_party/mlir/include/mlir/Dialect/GPU/GPUOps.td @@ -23,6 +23,17 @@ #define GPU_OPS include "mlir/IR/OpBase.td" +include "mlir/Dialect/LLVMIR/LLVMOpBase.td" + +// Type constraint accepting standard integers, indices and wrapped LLVM integer +// types. +def IntLikeOrLLVMInt : TypeConstraint< + Or<[AnyInteger.predicate, Index.predicate, LLVMInt.predicate]>, + "integer, index or LLVM dialect equivalent">; + +//===----------------------------------------------------------------------===// +// GPU Dialect operations. +//===----------------------------------------------------------------------===// def GPU_Dialect : Dialect { let name = "gpu"; @@ -68,7 +79,7 @@ def GPU_GPUFuncOp : GPU_Op<"func", [FunctionLike, IsolatedFromAbove, Symbol]> { Syntax: - ``` {.ebnf} + ``` op ::= `gpu.func` symbol-ref-id `(` argument-list `)` (`->` function-result-list)? memory-attribution `kernel`? function-attributes? region @@ -109,9 +120,9 @@ def GPU_GPUFuncOp : GPU_Op<"func", [FunctionLike, IsolatedFromAbove, Symbol]> { let builders = [ OpBuilder<"Builder *builder, OperationState &result, StringRef name, " - "FunctionType type, ArrayRef workgroupAttributions, " - "ArrayRef privateAttributions, " - "ArrayRef attrs"> + "FunctionType type, ArrayRef workgroupAttributions = {}, " + "ArrayRef privateAttributions = {}, " + "ArrayRef attrs = {}"> ]; let extraClassDeclaration = [{ @@ -127,6 +138,17 @@ def GPU_GPUFuncOp : GPU_Op<"func", [FunctionLike, IsolatedFromAbove, Symbol]> { return getTypeAttr().getValue().cast(); } + /// Change the type of this function in place. This is an extremely + /// dangerous operation and it is up to the caller to ensure that this is + /// legal for this function, and to restore invariants: + /// - the entry block args must be updated to match the function params. + /// - the argument/result attributes may need an update: if the new type + /// has less parameters we drop the extra attributes, if there are more + /// parameters they won't have any attributes. + // TODO(b/146349912): consider removing this function thanks to rewrite + // patterns. + void setType(FunctionType newType); + /// Returns the number of buffers located in the workgroup memory. unsigned getNumWorkgroupAttributions() { return getAttrOfType(getNumWorkgroupAttributionsAttrName()) @@ -154,7 +176,7 @@ def GPU_GPUFuncOp : GPU_Op<"func", [FunctionLike, IsolatedFromAbove, Symbol]> { /// Returns the name of the attribute containing the number of buffers /// located in the workgroup memory. static StringRef getNumWorkgroupAttributionsAttrName() { - return "workgroup_attibutions"; + return "workgroup_attributions"; } // FunctionLike trait needs access to the functions below. @@ -181,6 +203,133 @@ def GPU_GPUFuncOp : GPU_Op<"func", [FunctionLike, IsolatedFromAbove, Symbol]> { let parser = [{ return parseGPUFuncOp(parser, result); }]; } +def GPU_LaunchFuncOp : GPU_Op<"launch_func">, + Arguments<(ins IntLikeOrLLVMInt:$gridSizeX, IntLikeOrLLVMInt:$gridSizeY, + IntLikeOrLLVMInt:$gridSizeZ, IntLikeOrLLVMInt:$blockSizeX, + IntLikeOrLLVMInt:$blockSizeY, IntLikeOrLLVMInt:$blockSizeZ, + Variadic:$operands)>, + Results<(outs)> { + let summary = "Launches a function as a GPU kerneel"; + + let description = [{ + Launch a kernel function on the specified grid of thread blocks. + `gpu.launch` operations are lowered to `gpu.launch_func` operations by + outlining the kernel body into a function in a dedicated module, which + reflects the separate compilation process. The kernel function is required + to have the `gpu.kernel` attribute. The module containing the kernel + function is required to have the `gpu.kernel_module` attribute and must be + named. And finally, the module containing the kernel module (which thus + cannot be the top-level module) is required to have the + `gpu.container_module` attribute. The `gpu.launch_func` operation has a + string attribute named `kernel` to specify the name of the kernel function + to launch and an attribute named `kernel_module` to specify the name of the + module containing that kernel function. + + The operation takes at least six operands, with the first three operands + being grid sizes along x,y,z dimensions and the following three being block + sizes along x,y,z dimensions. When a lower-dimensional kernel is required, + unused sizes must be explicitly set to `1`. The remaining operands are + passed as arguments to the kernel function. + + A custom syntax for this operation is currently not available. + + Example: + + ```mlir + module attributes {gpu.container_module} { + + // This module creates a separate compilation unit for the GPU compiler. + module @kernels attributes {gpu.kernel_module} { + func @kernel_1(%arg0 : f32, %arg1 : !llvm<"float*">) + attributes { nvvm.kernel = true } { + + // Operations that produce block/thread IDs and dimensions are + // injected when outlining the `gpu.launch` body to a function called + // by `gpu.launch_func`. + %tIdX = "gpu.thread_id"() {dimension = "x"} : () -> (index) + %tIdY = "gpu.thread_id"() {dimension = "y"} : () -> (index) + %tIdZ = "gpu.thread_id"() {dimension = "z"} : () -> (index) + + %bDimX = "gpu.block_dim"() {dimension = "x"} : () -> (index) + %bDimY = "gpu.block_dim"() {dimension = "y"} : () -> (index) + %bDimZ = "gpu.block_dim"() {dimension = "z"} : () -> (index) + + %bIdX = "gpu.block_id"() {dimension = "x"} : () -> (index) + %bIdY = "gpu.block_id"() {dimension = "y"} : () -> (index) + %bIdZ = "gpu.block_id"() {dimension = "z"} : () -> (index) + + %gDimX = "gpu.grid_dim"() {dimension = "x"} : () -> (index) + %gDimY = "gpu.grid_dim"() {dimension = "y"} : () -> (index) + %gDimZ = "gpu.grid_dim"() {dimension = "z"} : () -> (index) + + "some_op"(%bx, %tx) : (index, index) -> () + %42 = load %arg1[%bx] : memref + } + } + + "gpu.launch_func"(%cst, %cst, %cst, // Grid sizes. + %cst, %cst, %cst, // Block sizes. + %arg0, %arg1) // Arguments passed to the kernel. + { kernel_module = @kernels, // Module containing the kernel. + kernel = "kernel_1" } // Kernel function. + : (index, index, index, index, index, index, f32, !llvm<"float*">) + -> () + } + ``` + }]; + + let skipDefaultBuilders = 1; + + let builders = [ + OpBuilder<"Builder *builder, OperationState &result, GPUFuncOp kernelFunc, " + "Value *gridSizeX, Value *gridSizeY, Value *gridSizeZ, " + "Value *blockSizeX, Value *blockSizeY, Value *blockSizeZ, " + "ValueRange kernelOperands">, + OpBuilder<"Builder *builder, OperationState &result, GPUFuncOp kernelFunc, " + "KernelDim3 gridSize, KernelDim3 blockSize, " + "ValueRange kernelOperands"> + ]; + + let extraClassDeclaration = [{ + /// The kernel function specified by the operation's `kernel` attribute. + StringRef kernel(); + + /// The number of operands passed to the kernel function. + unsigned getNumKernelOperands(); + + /// The name of the kernel module specified by the operation's + /// `kernel_module` attribute. + StringRef getKernelModuleName(); + + /// The i-th operand passed to the kernel function. + Value *getKernelOperand(unsigned i); + + /// Get the SSA values passed as operands to specify the grid size. + KernelDim3 getGridSizeOperandValues(); + + /// Get the SSA values passed as operands to specify the block size. + KernelDim3 getBlockSizeOperandValues(); + + /// The number of launch configuration operands, placed at the leading + /// positions of the operand list. + static constexpr unsigned kNumConfigOperands = 6; + + // This needs to quietly verify if attributes with names defined below are + // present since it is run before the verifier of this op. + friend LogicalResult GPUDialect::verifyOperationAttribute(Operation *, + NamedAttribute); + + /// The name of the symbolRef attribute specifying the kernel to launch. + static StringRef getKernelAttrName() { return "kernel"; } + + /// The name of the symbolRef attribute specifying the name of the module + /// containing the kernel to launch. + static StringRef getKernelModuleAttrName() { return "kernel_module"; } + }]; + + let verifier = [{ return ::verify(*this); }]; +} + def GPU_LaunchOp : GPU_Op<"launch", [IsolatedFromAbove]>, Arguments<(ins Index:$gridSizeX, Index:$gridSizeY, Index:$gridSizeZ, Index:$blockSizeX, Index:$blockSizeY, Index:$blockSizeZ, @@ -213,7 +362,7 @@ def GPU_LaunchOp : GPU_Op<"launch", [IsolatedFromAbove]>, Syntax: - ``` {.ebnf} + ``` operation ::= `gpu.launch` `block` `(` ssa-id-list `)` `in` ssa-reassignment `threads` `(` ssa-id-list `)` `in` ssa-reassignment (`args` ssa-reassignment `:` type-list)? @@ -223,7 +372,7 @@ def GPU_LaunchOp : GPU_Op<"launch", [IsolatedFromAbove]>, Example: - ```mlir {.mlir} + ```mlir gpu.launch blocks(%bx, %by, %bz) in (%sz_bx = %0, %sz_by = %1, %sz_bz = %2) threads(%tx, %ty, %tz) in (%sz_tx = %3, %sz_ty = %4, %sz_tz = %5) args(%arg0 = %6, %arg1 = 7) : f32, memref { diff --git a/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMDialect.h b/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMDialect.h index 83c30e64b9f..5332a7479ad 100644 --- a/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMDialect.h +++ b/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMDialect.h @@ -198,6 +198,10 @@ Value *createGlobalString(Location loc, OpBuilder &builder, StringRef name, StringRef value, LLVM::Linkage linkage, LLVM::LLVMDialect *llvmDialect); +/// LLVM requires some operations to be inside of a Module operation. This +/// function confirms that the Operation has the desired properties. +bool satisfiesLLVMModule(Operation *op); + } // end namespace LLVM } // end namespace mlir diff --git a/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td b/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td index f8c8277db22..6257b4a51d9 100644 --- a/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td +++ b/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOpBase.td @@ -34,6 +34,12 @@ def LLVM_Dialect : Dialect { def LLVM_Type : Type()">, "LLVM dialect type">; +// Type constraint accepting only wrapped LLVM integer types. +def LLVMInt : TypeConstraint< + And<[LLVM_Type.predicate, + CPred<"$_self.cast<::mlir::LLVM::LLVMType>().isIntegerTy()">]>, + "LLVM dialect integer">; + // Base class for LLVM operations. Defines the interface to the llvm::IRBuilder // used to translate to LLVM IR proper. class LLVM_OpBase traits = []> : diff --git a/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td index 166cc5c4f9f..a7119147fc5 100644 --- a/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td +++ b/third_party/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td @@ -684,4 +684,38 @@ def LLVM_ExpOp : LLVM_Op<"intr.exp", [NoSideEffect]>, $res = builder.CreateCall(fn, {$in}); }]; } + +def LLVM_LogOp : LLVM_Op<"intr.log", [NoSideEffect]>, + Arguments<(ins LLVM_Type:$in)>, + Results<(outs LLVM_Type:$res)> { + let llvmBuilder = [{ + llvm::Module *module = builder.GetInsertBlock()->getModule(); + llvm::Function *fn = llvm::Intrinsic::getDeclaration( + module, llvm::Intrinsic::log, {$in->getType()}); + $res = builder.CreateCall(fn, {$in}); + }]; +} + +def LLVM_Log10Op : LLVM_Op<"intr.log10", [NoSideEffect]>, + Arguments<(ins LLVM_Type:$in)>, + Results<(outs LLVM_Type:$res)> { + let llvmBuilder = [{ + llvm::Module *module = builder.GetInsertBlock()->getModule(); + llvm::Function *fn = llvm::Intrinsic::getDeclaration( + module, llvm::Intrinsic::log10, {$in->getType()}); + $res = builder.CreateCall(fn, {$in}); + }]; +} + +def LLVM_Log2Op : LLVM_Op<"intr.log2", [NoSideEffect]>, + Arguments<(ins LLVM_Type:$in)>, + Results<(outs LLVM_Type:$res)> { + let llvmBuilder = [{ + llvm::Module *module = builder.GetInsertBlock()->getModule(); + llvm::Function *fn = llvm::Intrinsic::getDeclaration( + module, llvm::Intrinsic::log2, {$in->getType()}); + $res = builder.CreateCall(fn, {$in}); + }]; +} + #endif // LLVMIR_OPS diff --git a/third_party/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td b/third_party/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td index c6e89afa3f8..bc6887da8e4 100644 --- a/third_party/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td +++ b/third_party/mlir/include/mlir/Dialect/LLVMIR/NVVMOps.td @@ -140,7 +140,7 @@ def NVVM_MmaOp : }]; let parser = [{ return parseNVVMMmaOp(parser, result); }]; let printer = [{ printNVVMMmaOp(p, *this); }]; - let verifier = [{ return mlir::NVVM::verify(*this); }]; + let verifier = [{ return ::verify(*this); }]; } #endif // NVVMIR_OPS diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/EDSC/Builders.h b/third_party/mlir/include/mlir/Dialect/Linalg/EDSC/Builders.h new file mode 100644 index 00000000000..421342038c9 --- /dev/null +++ b/third_party/mlir/include/mlir/Dialect/Linalg/EDSC/Builders.h @@ -0,0 +1,240 @@ +//===- Builders.h - MLIR Declarative Linalg Builders ------------*- C++ -*-===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// Provides intuitive composable interfaces for building structured MLIR +// snippets in a declarative fashion. +// +//===----------------------------------------------------------------------===// +#ifndef MLIR_DIALECT_LINALG_EDSC_BUILDERS_H_ +#define MLIR_DIALECT_LINALG_EDSC_BUILDERS_H_ + +#include "mlir/Dialect/Linalg/EDSC/Intrinsics.h" +#include "mlir/Dialect/Utils/StructuredOpsUtils.h" +#include "mlir/EDSC/Builders.h" +#include "mlir/EDSC/Intrinsics.h" +#include "mlir/IR/AffineExpr.h" +#include "mlir/IR/Builders.h" + +namespace mlir { +class BlockArgument; + +namespace edsc { +enum class IterType { Parallel, Reduction }; + +inline StringRef toString(IterType t) { + switch (t) { + case IterType::Parallel: + return getParallelIteratorTypeName(); + case IterType::Reduction: + return getReductionIteratorTypeName(); + default: + llvm_unreachable("Unsupport IterType"); + } +} + +/// A StructuredIndexed represents a captured value that can be indexed and +/// passed to the `makeLinalgGenericOp`. It allows writing intuitive index +/// expressions such as: +/// +/// ``` +/// StructuredIndexed A(vA), B(vB), C(vC); +/// makeLinalgGenericOp({A({m, n}), B({k, n})}, {C({m, n})}, ... ); +/// ``` +struct StructuredIndexed { + StructuredIndexed(Value *v) : value(v) {} + StructuredIndexed operator()(ArrayRef indexings) { + return StructuredIndexed(value, indexings); + } + + operator Value *() const /* implicit */ { return value; } + ArrayRef getExprs() { return exprs; } + +private: + StructuredIndexed(Value *v, ArrayRef indexings) + : value(v), exprs(indexings.begin(), indexings.end()) { + assert(v->getType().isa() && "MemRefType expected"); + } + StructuredIndexed(ValueHandle v, ArrayRef indexings) + : StructuredIndexed(v.getValue(), indexings) {} + + Value *value; + SmallVector exprs; +}; + +inline void defaultRegionBuilder(ArrayRef args) {} + +Operation *makeLinalgGenericOp( + ArrayRef iteratorTypes, ArrayRef inputs, + ArrayRef outputs, + llvm::function_ref)> regionBuilder = + defaultRegionBuilder, + ArrayRef otherValues = {}, + ArrayRef otherAttributes = {}); + +namespace ops { +using edsc::StructuredIndexed; +using edsc::ValueHandle; +using edsc::intrinsics::linalg_yield; + +//===----------------------------------------------------------------------===// +// EDSC builders for linalg generic operations. +//===----------------------------------------------------------------------===// + +/// Build the body of a region to compute a multiply-accumulate, under the +/// current ScopedContext, at the current insert point. +void macRegionBuilder(ArrayRef args); + +/// TODO(ntv): In the future we should tie these implementations to something in +/// Tablegen that generates the proper interfaces and the proper sugared named +/// ops. + +/// Build a linalg.pointwise, under the current ScopedContext, at the current +/// insert point, that computes: +/// ``` +/// (i0, ..., in) = (par, ..., par) +/// | +/// | O...(some_subset...(i0, ..., in)) = +/// | some_pointwise_func...(I...(some_other_subset...(i0, ..., in))) +/// ``` +/// +/// This is a very generic entry point that can be configured in many ways to +/// build a perfect loop nest of parallel loops with arbitrarily complex +/// innermost loop code and whatever (explicit) broadcast semantics. +/// +/// This can be used with both out-of-place and in-place semantics. +/// The client is responsible for ensuring the region operations are compatible +/// with in-place semantics and parallelism. + +/// Unary pointwise operation (with broadcast) entry point. +using UnaryPointwiseOpBuilder = llvm::function_ref; +Operation *linalg_pointwise(UnaryPointwiseOpBuilder unaryOp, + StructuredIndexed I, StructuredIndexed O); + +/// Build a linalg.pointwise with all `parallel` iterators and a region that +/// computes `O = tanh(I)`. The client is responsible for specifying the proper +/// indexings when creating the StructuredIndexed. +Operation *linalg_pointwise_tanh(StructuredIndexed I, StructuredIndexed O); + +/// Binary pointwise operation (with broadcast) entry point. +using BinaryPointwiseOpBuilder = + llvm::function_ref; +Operation *linalg_pointwise(BinaryPointwiseOpBuilder binaryOp, + StructuredIndexed I1, StructuredIndexed I2, + StructuredIndexed O); + +/// Build a linalg.pointwise with all `parallel` iterators and a region that +/// computes `O = I1 + I2`. The client is responsible for specifying the proper +/// indexings when creating the StructuredIndexed. +Operation *linalg_pointwise_add(StructuredIndexed I1, StructuredIndexed I2, + StructuredIndexed O); + +/// Build a linalg.pointwise with all `parallel` iterators and a region that +/// computes `O = max(I!, I2)`. The client is responsible for specifying the +/// proper indexings when creating the StructuredIndexed. +Operation *linalg_pointwise_max(StructuredIndexed I1, StructuredIndexed I2, + StructuredIndexed O); + +// TODO(ntv): Implement more useful pointwise operations on a per-need basis. + +/// Build a linalg.generic, under the current ScopedContext, at the current +/// insert point, that computes: +/// ``` +/// (m, n, k) = (par, par, seq) +/// | +/// | C(m, n) += A(m, k) * B(k, n) +/// ``` +Operation *linalg_matmul(ValueHandle vA, ValueHandle vB, ValueHandle vC); + +template Operation *linalg_matmul(Container values) { + assert(values.size() == 3 && "Expected exactly 3 values"); + return linalg_matmul(values[0], values[1], values[2]); +} + +/// Build a linalg.generic, under the current ScopedContext, at the current +/// insert point, that computes: +/// ``` +/// (batch, f, [h, w, ...], [kh, kw, ...], c) = +/// | (par, par, [par, par, ...], [red, red, ...], red) +/// | +/// | O(batch, [h, w, ...], f) += +/// | I(batch, +/// | [ +/// | stride[0] * h + dilations[0] * kh, +/// | stride[1] * w + dilations[1] * kw, ... +/// ], +/// | c) +/// | * +/// | W([kh, kw, ...], c, f) +/// ``` +/// If `dilations` or `strides` are left empty, the default value of `1` is used +/// along each relevant dimension. +/// +/// For now `...` must be empty (i.e. only 2-D convolutions are supported). +/// +// TODO(ntv) Extend convolution rank with some template magic. +Operation *linalg_conv_nhwc(ValueHandle vI, ValueHandle vW, ValueHandle vO, + ArrayRef strides = {}, + ArrayRef dilations = {}); + +template +Operation *linalg_conv_nhwc(Container values, ArrayRef strides = {}, + ArrayRef dilations = {}) { + assert(values.size() == 3 && "Expected exactly 3 values"); + return linalg_conv_nhwc(values[0], values[1], values[2], strides, dilations); +} + +/// Build a linalg.generic, under the current ScopedContext, at the current +/// insert point, that computes: +/// ``` +/// (batch, dm, c, [h, w, ...], [kh, kw, ...]) = +/// | (par, par, par, [par, par, ...], [red, red, ...]) +/// | +/// | O(batch, [h, w, ...], c * depth_multiplier) += +/// | I(batch, +/// | [ +/// | stride[0] * h + dilations[0] * kh, +/// | stride[1] * w + dilations[1] * kw, ... +/// ], +/// | c) +/// | * +/// | W([kh, kw, ...], c, depth_multiplier) +/// ``` +/// If `dilations` or `strides` are left empty, the default value of `1` is used +/// along each relevant dimension. +/// +/// For now `...` must be empty (i.e. only 2-D convolutions are supported). +/// +// TODO(ntv) Extend convolution rank with some template magic. +Operation *linalg_dilated_conv_nhwc(ValueHandle vI, ValueHandle vW, + ValueHandle vO, int depth_multiplier = 1, + ArrayRef strides = {}, + ArrayRef dilations = {}); + +template +Operation *linalg_dilated_conv_nhwc(Container values, int depth_multiplier, + ArrayRef strides = {}, + ArrayRef dilations = {}) { + assert(values.size() == 3 && "Expected exactly 3 values"); + return linalg_dilated_conv_nhwc(values[0], values[1], values[2], + depth_multiplier, strides, dilations); +} + +} // namespace ops +} // namespace edsc +} // namespace mlir + +#endif // MLIR_DIALECT_LINALG_EDSC_BUILDERS_H_ diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/EDSC/Intrinsics.h b/third_party/mlir/include/mlir/Dialect/Linalg/EDSC/Intrinsics.h new file mode 100644 index 00000000000..f1acab69a4d --- /dev/null +++ b/third_party/mlir/include/mlir/Dialect/Linalg/EDSC/Intrinsics.h @@ -0,0 +1,35 @@ +//===- Intrinsics.h - MLIR EDSC Intrinsics for Linalg -----------*- C++ -*-===// +// +// Copyright 2019 The MLIR Authors. +// +// 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 MLIR_DIALECT_LINALG_EDSC_INTRINSICS_H_ +#define MLIR_DIALECT_LINALG_EDSC_INTRINSICS_H_ + +#include "mlir/Dialect/Linalg/IR/LinalgOps.h" +#include "mlir/EDSC/Builders.h" +#include "mlir/EDSC/Intrinsics.h" + +namespace mlir { +namespace edsc { +namespace intrinsics { + +using linalg_fill = OperationBuilder; +using linalg_yield = OperationBuilder; + +} // namespace intrinsics +} // namespace edsc +} // namespace mlir + +#endif // MLIR_DIALECT_LINALG_EDSC_INTRINSICS_H_ diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgLibraryOps.td b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgLibraryOps.td index 14d782fb6d6..1f24a903e41 100644 --- a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgLibraryOps.td +++ b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgLibraryOps.td @@ -26,37 +26,17 @@ include "mlir/Dialect/AffineOps/AffineOpsBase.td" include "mlir/Dialect/Linalg/IR/LinalgBase.td" -class LinalgParametricNativeOpTrait : - NativeOpTrait<"linalg::" # prop # parameters> -{} - -class LinalgParametricIntNativeOpTrait parameters> : - LinalgParametricNativeOpTrait< - prop, - !strconcat("<", - !cast(!head(parameters)), - !foldl("", - !tail(parameters), - sum, - param, - sum # "," # !cast(param)), - ">::Impl")> -{} - -// The Linalg `NInputsAndOutputs` trait provides the API for ops that are known -// to have a specified number of inputs and outputs, all passed as operands. +// The Linalg `NInputs` trait provides the API for ops that are known +// to have a specified number of inputs, all passed as operands. // See Linalg/LinalgTraits.h for implementation details an usage. -class NInputsAndOutputs : - LinalgParametricIntNativeOpTrait<"NInputsAndOutputs", [n_ins, n_outs]> -{} +class NInputs : + NativeOpTrait<"linalg::NInputs<" # !cast(args_in) # ">::Impl"> {} -// The linalg `NLoopTypes` trait provides the API for ops that are known to have -// a specified number of parallel (n_par), reduction (n_red) and window (n_win) -// loops. +// The Linalg `NOutputs` trait provides the API for ops that are known +// to have a specified number of outputs, all passed as operands. // See Linalg/LinalgTraits.h for implementation details an usage. -class NLoopTypes : -LinalgParametricIntNativeOpTrait<"NLoopTypes", [n_par, n_red, n_win]> -{} +class NOutputs : + NativeOpTrait<"linalg::NOutputs<" # !cast(args_out) # ">::Impl"> {} def ViewTraits : NativeOpTrait<"linalg::ViewTraits">; @@ -88,6 +68,14 @@ def LinalgLibraryInterface : OpInterface<"LinalgOp"> { "Query the input and output operands from the current operation.", "Operation::operand_range", "getInputsAndOutputs" >, + InterfaceMethod< + "Query the iterator types attribute within the current operation.", + "ArrayAttr", "iterator_types" + >, + InterfaceMethod< + "Query the indexing maps attribute within the current operation.", + "ArrayAttr", "indexing_maps" + >, InterfaceMethod< "Query the number of parallel loops within the current operation.", "unsigned", "getNumParallelLoops" @@ -102,10 +90,7 @@ def LinalgLibraryInterface : OpInterface<"LinalgOp"> { >, InterfaceMethod< "Query the number of loops within the current operation.", - "unsigned", "getNumLoops", (ins), [{ - return op.getNumParallelLoops() + op.getNumReductionLoops() + - op.getNumWindowLoops(); - }]>, + "unsigned", "getNumLoops">, InterfaceMethod<"Query the input view at the given index.", "Value *", "getInput", (ins "unsigned":$i) >, @@ -188,7 +173,7 @@ class LinalgLibrary_Op props> //////////////////////////////////////////////////////////////////////////////// // Concrete Linalg ops. //////////////////////////////////////////////////////////////////////////////// -def CopyOp : LinalgLibrary_Op<"copy", [NInputsAndOutputs<1, 1>]> { +def CopyOp : LinalgLibrary_Op<"copy", [NInputs<1>, NOutputs<1>]> { let description = [{ Copies the data in the input view into the output view. @@ -248,61 +233,87 @@ def CopyOp : LinalgLibrary_Op<"copy", [NInputsAndOutputs<1, 1>]> { builder, result, input, output, AffineMapAttr(), AffineMapAttr()); }]>]; let extraClassDeclaration = libraryCallName # [{ - unsigned getNumParallelLoops() { - auto *view = *(getOperands().begin()); - return view->getType().cast().getRank(); + ArrayAttr indexing_maps(); + + ArrayAttr iterator_types() { + unsigned nPar = input()->getType().cast().getRank(); + MLIRContext *ctx = getContext(); + SmallVector iters( + nPar, StringAttr::get(getParallelIteratorTypeName(), ctx)); + return ArrayAttr::get(iters, ctx); } - unsigned getNumReductionLoops() { return 0; } - unsigned getNumWindowLoops() { return 0; } }]; let verifier = [{ return ::verify(*this); }]; } -def FillOp : LinalgLibrary_Op<"fill", [NInputsAndOutputs<0, 1>]> { - let arguments = (ins AnyStridedMemRef, - AnyTypeOf<[AnyFloat, AnyInteger, AnyVector]>); +def FillOp : LinalgLibrary_Op<"fill", [NInputs<0>, NOutputs<1>]> { + let arguments = (ins AnyStridedMemRef:$output, + AnyTypeOf<[AnyFloat, AnyInteger, AnyVector]>:$value); let extraClassDeclaration = libraryCallName # [{ - unsigned getNumParallelLoops() { - auto *view = *(getOperands().begin()); - return view->getType().cast().getRank(); - } - unsigned getNumReductionLoops() { return 0; } - unsigned getNumWindowLoops() { return 0; } - Value *getValue() { - return *(getOperands().begin() + getNumInputsAndOutputs()); + ArrayAttr indexing_maps(); + + ArrayAttr iterator_types() { + unsigned nPar = output()->getType().cast().getRank(); + MLIRContext *ctx = getContext(); + SmallVector iters( + nPar, StringAttr::get(getParallelIteratorTypeName(), ctx)); + return ArrayAttr::get(iters, ctx); } }]; let verifier = [{ return ::verify(*this); }]; } -def DotOp : LinalgLibrary_Op<"dot", - [NInputsAndOutputs<2, 1>, - NLoopTypes<0, 1, 0>]> { +def DotOp : LinalgLibrary_Op<"dot", [NInputs<2>, NOutputs<1>]> { let arguments = (ins AnyStridedMemRefOfRank<1>, AnyStridedMemRefOfRank<1>, AnyStridedMemRefOfRank<0>); - let extraClassDeclaration = libraryCallName; + let extraClassDeclaration = libraryCallName # [{ + ArrayAttr indexing_maps(); + + ArrayAttr iterator_types() { + MLIRContext *ctx = getContext(); + return ArrayAttr::get( + StringAttr::get(getReductionIteratorTypeName(), ctx), ctx); + } + }]; } -def MatvecOp : LinalgLibrary_Op<"matvec", - [NInputsAndOutputs<2, 1>, - NLoopTypes<1, 1, 0>]> { +def MatvecOp : LinalgLibrary_Op<"matvec", [NInputs<2>, NOutputs<1>]> { let arguments = (ins AnyStridedMemRefOfRank<2>, AnyStridedMemRefOfRank<1>, AnyStridedMemRefOfRank<1>); - let extraClassDeclaration = libraryCallName; + let extraClassDeclaration = libraryCallName # [{ + ArrayAttr indexing_maps(); + + ArrayAttr iterator_types() { + MLIRContext *ctx = getContext(); + Attribute iters[2]{ + StringAttr::get(getParallelIteratorTypeName(), ctx), + StringAttr::get(getReductionIteratorTypeName(), ctx)}; + return ArrayAttr::get(iters, ctx); + } + }]; } -def MatmulOp : LinalgLibrary_Op<"matmul", - [NInputsAndOutputs<2, 1>, - NLoopTypes<2, 1, 0>]> { +def MatmulOp : LinalgLibrary_Op<"matmul", [NInputs<2>, NOutputs<1>]> { let arguments = (ins AnyStridedMemRefOfRank<2>, AnyStridedMemRefOfRank<2>, AnyStridedMemRefOfRank<2>); - let extraClassDeclaration = libraryCallName; + let extraClassDeclaration = libraryCallName # [{ + ArrayAttr indexing_maps(); + + ArrayAttr iterator_types() { + MLIRContext *ctx = getContext(); + Attribute iters[3]{ + StringAttr::get(getParallelIteratorTypeName(), ctx), + StringAttr::get(getParallelIteratorTypeName(), ctx), + StringAttr::get(getReductionIteratorTypeName(), ctx)}; + return ArrayAttr::get(iters, ctx); + } + }]; } -def ConvOp : LinalgLibrary_Op<"conv", [NInputsAndOutputs<2, 1>]> { +def ConvOp : LinalgLibrary_Op<"conv", [NInputs<2>, NOutputs<1>]> { let description = [{ Generic n-D convolution as described in the TF documentation: https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/nn/convolution @@ -333,21 +344,27 @@ def ConvOp : LinalgLibrary_Op<"conv", [NInputsAndOutputs<2, 1>]> { unsigned getNumInputFeatureDimensions() { return 1; } unsigned getNumOutputFeatureDimensions() { return 1; } - // Outer parallel loops are always the number of output dimensions; i.e. - // [ b, xs, q] in the TF notation above. - unsigned getNumParallelLoops() { return getOutputViewType(0).getRank(); } + ArrayAttr indexing_maps(); - // Window loops are a special kind of reduction that is neither tiled or - // parallelized across; i.e. [zs] in the TF notation above whose number - // match `xs` (i.e. 1 window loop per "image" dimension). - unsigned getNumWindowLoops() { - return getNumParallelLoops() - getNumBatchDimensions() - - getNumInputFeatureDimensions(); } - - // Reduction loops are exactly the non-parallel, non-window loops (i.e. `q`) - // We distinguish between reduction loops and convolution window loops for - // now. That distinction may disappear in the future. - unsigned getNumReductionLoops() { return getNumInputFeatureDimensions(); } + ArrayAttr iterator_types() { + // Outer parallel loops are always the number of output dimensions; i.e. + // [ b, xs, q] in the TF notation above. + unsigned nPar = getOutputViewType(0).getRank(); + unsigned nRed = getNumInputFeatureDimensions(); + // Window loops are a special kind of reduction that is never tiled or + // parallelized across; i.e. [zs] in the TF notation above whose number + // match `xs` (i.e. 1 window loop per "image" dimension). + // This may evolve in the future. + unsigned nWin = + nPar - getNumBatchDimensions() - getNumInputFeatureDimensions(); + MLIRContext *ctx = getContext(); + SmallVector iters( + nPar, StringAttr::get(getParallelIteratorTypeName(), ctx)); + iters.reserve(nPar + nRed + nWin); + iters.append(nRed, StringAttr::get(getReductionIteratorTypeName(), ctx)); + iters.append(nWin, StringAttr::get(getWindowIteratorTypeName(), ctx)); + return ArrayAttr::get(iters, ctx); + } int64_t getStride(unsigned i) { assert(i < getNumWindowLoops()); @@ -368,9 +385,10 @@ def ConvOp : LinalgLibrary_Op<"conv", [NInputsAndOutputs<2, 1>]> { class GenericOpBase : LinalgLibraryBase_Op { let arguments = (ins Variadic:$views, + I64Attr:$args_in, + I64Attr:$args_out, AffineMapArrayAttr:$indexing_maps, ArrayAttr:$iterator_types, - I64ArrayAttr:$n_views, OptionalAttr:$doc, OptionalAttr:$fun, OptionalAttr:$library_call); @@ -378,57 +396,13 @@ class GenericOpBase : LinalgLibraryBase_Op { let extraClassDeclaration = [{ SmallVector linalgTraitAttrNames() { return SmallVector{ - "doc", "fun", "indexing_maps", "library_call", "iterator_types", "n_views" + getArgsInAttrName(), getArgsOutAttrName(), getDocAttrName(), + getFunAttrName(), getIndexingMapsAttrName(), getLibraryCallAttrName(), + getIteratorTypesAttrName() }; } - unsigned getNumInputs() { - if (!getAttr("n_views") || n_views().getValue().size() != 2) - return 0; - auto val = n_views().getValue()[0].cast().getValue(); - assert(val.getSExtValue() >= 0); - return val.getZExtValue(); - } - unsigned getNumOutputs() { - if (!getAttr("n_views") || n_views().getValue().size() != 2) - return 0; - auto val = n_views().getValue()[1].cast().getValue(); - assert(val.getSExtValue() >= 0); - return val.getZExtValue(); - } - unsigned getNumParallelLoops() { - if (!getAttr("iterator_types") || iterator_types().getValue().size() == 0) - return 0; - unsigned nPar = 0; - for (auto ty : iterator_types()) { - if (ty.cast().getValue() == "parallel") - nPar++; - } - return nPar; - } - unsigned getNumReductionLoops() { - if (!getAttr("iterator_types") || iterator_types().getValue().size() == 0) - return 0; - unsigned nRed = 0; - for (auto ty : iterator_types()) { - if (ty.cast().getValue() == "reduction") - nRed++; - } - return nRed; - } - unsigned getNumWindowLoops() { - if (!getAttr("iterator_types") || iterator_types().getValue().size() == 0) - return 0; - unsigned nWin = 0; - for (auto ty : iterator_types()) { - if (ty.cast().getValue() == "window") - nWin++; - } - return nWin; - } - unsigned getNumLoops() { - return getNumParallelLoops() + getNumReductionLoops() + - getNumWindowLoops(); - } + unsigned getNumInputs() { return args_in().getSExtValue(); } + unsigned getNumOutputs() { return args_out().getSExtValue(); } FuncOp getFunction() { auto moduleOp = getParentOfType(); return fun().hasValue() ? @@ -468,10 +442,12 @@ def GenericOp : GenericOpBase<"generic"> { ``` Where #trait_attributes is an alias of a dictionary attribute containing: + - args_in: an I64Attr representing the number of input (readonly) views + - args_out: an I64Attr representing the number of output (readwrite) views - doc [optional]: a documentation string - - fun: a FlatSymbolRefAttr that must resolve to an existing function symbol. - To support inplace updates in a generic fashion, the signature of the - function must be: + - fun: a FlatSymbolRefAttr that must resolve to an existing function + symbol. To support inplace updates in a generic fashion, the signature + of the function must be: ``` fun([input views element types], [output views element types]) -> ([output views element types]) @@ -484,11 +460,10 @@ def GenericOp : GenericOpBase<"generic"> { The external library is assumed to be dynamically linked and no strong compile-time guarantees are provided. In the absence of such a library call, linalg.generic will always lower to loops. - - iterator_types: an ArrayAttr they type of the enclosing loops; Each element of - the list represents and iterator of one of the following types: - parallel, reduction, window - - n_views: a pair of I64Attr representing the number of input (readonly) - and output (readwrite) views. + - iterator_types: an ArrayAttr specifying the type of the enclosing loops. + Each element of the list represents and iterator of one of the following + types: + parallel, reduction, window Example: Defining a #matmul_trait attribute in MLIR can be done as follows: @@ -563,12 +538,14 @@ def IndexedGenericOp : GenericOpBase<"indexed_generic"> { ``` Where #trait_attributes is an alias of a dictionary attribute containing: + - args_in: an I64Attr representing the number of input (readonly) views + - args_out: an I64Attr representing the number of output (readwrite) views - doc [optional]: a documentation string - - fun: a FlatSymbolRefAttr that must resolve to an existing function symbol. - To support inplace updates in a generic fashion, the signature of the - function must be: + - fun: a FlatSymbolRefAttr that must resolve to an existing function + symbol. To support inplace updates in a generic fashion, the signature + of the function must be: ``` - fun([index types for induction variables], [input views element types], + fun([index types of induction variables], [input views element types], [output views element types]) -> ([output views element types]) ``` - indexing_maps: a list of AffineMapAttr, one AffineMapAttr per each input @@ -579,16 +556,17 @@ def IndexedGenericOp : GenericOpBase<"indexed_generic"> { maps to. The external library is assumed to be dynamically linked and no strong compile-time guarantees are provided. In the absence of such a library call, linalg.indexed_generic will always lower to loops. - - iterator_types: an ArrayAttr they type of the enclosing loops; Each element of - the list represents and iterator of one of the following types: - parallel, reduction, window - - n_views: a pair of I64Attr representing the number of input (readonly) - and output (readwrite) views. + - iterator_types: an ArrayAttr they type of the enclosing loops; Each + element of the list represents and iterator of one of the following + types: + parallel, reduction, window Example: Defining a #matmul_trait attribute in MLIR can be done as follows: ```mlir - func @fma(%i: index, %j: index, %k: index, %a: f32, %b: f32, %c: f32) -> f32 { + func @fma(%i: index, %j: index, %k: index, %a: f32, %b: f32, %c: f32) + -> f32 + { %d = mulf %a, %b: f32 %e = addf %c, %d: f32 return %e: f32 diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgOps.h b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgOps.h index 0e4201cd84e..41155701b8d 100644 --- a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgOps.h +++ b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgOps.h @@ -20,6 +20,7 @@ #include "mlir/Dialect/Linalg/IR/LinalgTraits.h" #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" +#include "mlir/Dialect/Utils/StructuredOpsUtils.h" #include "mlir/IR/AffineMap.h" #include "mlir/IR/BlockAndValueMapping.h" #include "mlir/IR/Builders.h" @@ -85,7 +86,6 @@ SmallVector loopToOperandRangesMaps(Operation *op); #define GET_OP_CLASSES #include "mlir/Dialect/Linalg/IR/LinalgLibraryOps.h.inc" - } // namespace linalg } // namespace mlir diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTraits.h b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTraits.h index 5456debdcd3..fa5f9e740cb 100644 --- a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTraits.h +++ b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTraits.h @@ -19,6 +19,7 @@ #define MLIR_DIALECT_LINALG_LINALGTRAITS_H_ #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" +#include "mlir/Dialect/Utils/StructuredOpsUtils.h" #include "mlir/IR/OpDefinition.h" #include "mlir/IR/StandardTypes.h" #include "mlir/Support/LLVM.h" @@ -28,23 +29,30 @@ namespace OpTrait { namespace linalg { /// This class provides the API for ops that are known to have a specified -/// number of inputs and outputs, all passed as operands. This is used as a -/// trait like this: +/// number of inputs, all passed as operands. This is used as a trait like this: /// -/// class DotOp : public Op::Impl> { +/// class DotOp : public Op::Impl> { /// -template class NInputsAndOutputs { +template class NInputs { public: template - class Impl - : public OpTrait::TraitBase::Impl> { + class Impl : public OpTrait::TraitBase::Impl> { public: - static unsigned getNumInputs() { return NInputs; } - static unsigned getNumOutputs() { return NOutputs; } - static LogicalResult verifyTrait(Operation *op) { - return OpTrait::impl::verifyAtLeastNOperands(op, NInputs + NOutputs); - } + static unsigned getNumInputs() { return N; } + }; +}; + +/// This class provides the API for ops that are known to have a specified +/// number of inputs, all passed as operands. This is used as a trait like this: +/// +/// class DotOp : public Op::Impl> { +/// +template class NOutputs { +public: + template + class Impl : public OpTrait::TraitBase::Impl> { + public: + static unsigned getNumOutputs() { return N; } }; }; @@ -124,6 +132,25 @@ public: auto range = this->getOperation()->getOperands(); return {range.begin(), range.begin() + getNumInputsAndOutputs()}; } + unsigned getNumParallelLoops() { + return getNumIterators( + getParallelIteratorTypeName(), + cast(this->getOperation()).iterator_types()); + } + unsigned getNumReductionLoops() { + return getNumIterators( + getReductionIteratorTypeName(), + cast(this->getOperation()).iterator_types()); + } + unsigned getNumWindowLoops() { + return getNumIterators( + getWindowIteratorTypeName(), + cast(this->getOperation()).iterator_types()); + } + unsigned getNumLoops() { + return getNumIterators( + cast(this->getOperation()).iterator_types()); + } static LogicalResult verifyTrait(Operation *op) { auto nViews = cast(op).getNumInputsAndOutputs(); if (failed(OpTrait::impl::verifyAtLeastNOperands(op, nViews))) @@ -132,27 +159,6 @@ public: } }; -/// This class provides the API for ops that are known to have a specified -/// number of parallel, reduction and window loops. This is used as a trait like -/// this: -/// -/// class MatmulOp : public Op::Impl> { -/// -template -class NLoopTypes { -public: - template - class Impl - : public OpTrait::TraitBase< - ConcreteType, NLoopTypes::Impl> { - public: - static unsigned getNumParallelLoops() { return NParallel; } - static unsigned getNumReductionLoops() { return NReduction; } - static unsigned getNumWindowLoops() { return NWindow; } - static unsigned getNumLoops() { return NParallel + NReduction + NWindow; } - }; -}; - } // namespace linalg } // namespace OpTrait } // namespace mlir diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTypes.h b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTypes.h index 81c309704b8..181a79ce38d 100644 --- a/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTypes.h +++ b/third_party/mlir/include/mlir/Dialect/Linalg/IR/LinalgTypes.h @@ -26,8 +26,7 @@ class MLIRContext; namespace linalg { enum LinalgTypes { - Buffer = Type::FIRST_LINALG_TYPE, - Range, + Range = Type::FIRST_LINALG_TYPE, LAST_USED_LINALG_TYPE = Range, }; @@ -43,26 +42,6 @@ public: void printType(Type type, DialectAsmPrinter &os) const override; }; -/// A BufferType represents a contiguous block of memory that can be allocated -/// and deallocated. A buffer cannot be indexed directly, a view must be -/// laid out on a buffer to give it indexing semantics. -struct BufferTypeStorage; -class BufferType : public Type::TypeBase { -public: - // Used for generic hooks in TypeBase. - using Base::Base; - /// Construction hook. - static BufferType get(MLIRContext *context, Type elementType, - int64_t bufferSize = -1); - /// Used to implement llvm-style cast. - static bool kindof(unsigned kind) { return kind == LinalgTypes::Buffer; } - - // Type-specific functionality. - Type getElementType(); - bool hasConstantSize(); - Optional getBufferSize(); -}; - /// A RangeType represents a minimal range abstraction (min, max, step). /// It is constructed by calling the linalg.range op with three values index of /// index type: diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransformPatterns.td b/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransformPatterns.td index f558fa5da48..d92eb77107f 100644 --- a/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransformPatterns.td +++ b/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransformPatterns.td @@ -24,18 +24,32 @@ include "mlir/Dialect/Linalg/IR/LinalgOps.td" include "mlir/Dialect/Linalg/IR/LinalgLibraryOps.td" +include "mlir/Dialect/AffineOps/AffineOps.td" def HasNoLinalgTransformMarker : CPred<[{ !$0.getAttrOfType(LinalgTransforms::kLinalgTransformMarker) }]>; class HasLinalgTransformMarker : CPred<[{ + $0.getAttrOfType( + LinalgTransforms::kLinalgTransformMarker) && $0.getAttrOfType( LinalgTransforms::kLinalgTransformMarker).getValue() == "}] # str # [{"}]>; class IsProducedByOpOfType : CPred<"isProducedByOpOfType<" # str # ">($0, $1)">; +class AffineMapDomainHasDim : CPred<[{ + $0.getAttrOfType(getIndexingMapsAttrName()).getValue()[0]. + cast().getValue().getNumDims() ==}] # n # [{}]>; + +class HasOperandsOfType: CPred<[{ + llvm::any_of($0.getOperands(), + [](Value* v) { + return dyn_cast_or_null<}] # type # [{>(v->getDefiningOp()); + }) +}]>; + //===----------------------------------------------------------------------===// // Linalg fusion patterns. //===----------------------------------------------------------------------===// @@ -60,11 +74,12 @@ class TileAndFuseLinalgOp< // `permutation` is an optional parameter to specify the ordering of the // tiled loops. If provided, it must be a list of integers with the same number // of elements as `sizes`. -class TileLinalgOp sizes, string value, list permutation=[]> : NativeCodeCall< - "if (failed(tileLinalgOpAndSetMarker($_builder, $0, {" # - StrJoinInt.result # "}, \"" # value # "\", {" # - StrJoinInt.result # "})))" # - " return matchFailure();">; +class TileLinalgOp sizes, string value, list permutation=[]> : + NativeCodeCall< + "if (failed(tileLinalgOpAndSetMarker($_builder, $0, {" # + StrJoinInt.result # "}, \"" # value # "\", {" # + StrJoinInt.result # "})))" # + " return matchFailure();">; //===----------------------------------------------------------------------===// // Linalg to loop patterns. @@ -77,4 +92,26 @@ class LinalgOpToAffineLoops : NativeCodeCall< "if (failed(linalgOpToAffineLoops<" # OpType # ">($_builder, $0))) " # " return matchFailure();">; +//===----------------------------------------------------------------------===// +// Linalg to vector contraction patterns. +//===----------------------------------------------------------------------===// +class LinalgOpToVectorContraction : NativeCodeCall< + "if (failed(vectorizeGenericOp($_builder, $0))) " # + " return matchFailure();">; + +//===----------------------------------------------------------------------===// +// Linalg generic permutation patterns. +//===----------------------------------------------------------------------===// +class PermuteGenericLinalgOp permutation, string value> : + NativeCodeCall< + "if (failed(permuteGenericLinalgOp($_builder, $0, {" # + StrJoinInt.result # "}, \"" # value # "\"))) " # + " return matchFailure();">; + +//===----------------------------------------------------------------------===// +// Linalg promote subview operands. +//===----------------------------------------------------------------------===// +class LinalgOpPromoteSubviews : NativeCodeCall< + "if (failed(linalgOpPromoteSubviews($_builder, $0))) " # + " return matchFailure();">; #endif // LINALG_TRANSFORMS diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransforms.h b/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransforms.h index 89615e113c7..9682948dbd7 100644 --- a/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransforms.h +++ b/third_party/mlir/include/mlir/Dialect/Linalg/Transforms/LinalgTransforms.h @@ -73,20 +73,32 @@ LogicalResult tileLinalgOpAndSetMarker(PatternRewriter &rewriter, Operation *op, StringRef linalgMarker, ArrayRef permutation); -// Tiles `op` by `sizes`, fuses the producers of `operandIndicesToFuse` and sets -// the attribute `kLinalgTransformMarker` to `linalgMarker`. +/// Tiles `op` by `sizes`, fuses the producers of `operandIndicesToFuse` and +/// sets the attribute `kLinalgTransformMarker` to `linalgMarker`. LogicalResult tileAndFuseLinalgOpAndSetMarker( PatternRewriter &rewriter, Operation *op, ArrayRef sizes, ArrayRef operandIndicesToFuse, StringRef linalgMarker); -// Emits a loop nest of `loop.for` with the proper body for `op`. +/// Emits a loop nest of `loop.for` with the proper body for `op`. template LogicalResult linalgOpToLoops(PatternRewriter &rewriter, Operation *op); -// Emits a loop nest of `affine.for` with the proper body for `op`. +/// Emits a loop nest of `affine.for` with the proper body for `op`. template LogicalResult linalgOpToAffineLoops(PatternRewriter &rewriter, Operation *op); +/// Rewrite a linalg.generic into a suitable vector.contraction op. +LogicalResult vectorizeGenericOp(PatternRewriter &rewriter, Operation *op); + +/// Emits a `generic` or `indexed_generic` operation with the `indexing_maps` +/// and `iterator_types` permutated according to `permutation`. +LogicalResult permuteGenericLinalgOp(PatternRewriter &rewriter, Operation *op, + ArrayRef permutation, + StringRef linalgMarker); + +/// Promote std.subviews feeding linalg operations +LogicalResult linalgOpPromoteSubviews(PatternRewriter &rewriter, Operation *op); + } // namespace linalg } // namespace mlir diff --git a/third_party/mlir/include/mlir/Dialect/Linalg/Utils/Utils.h b/third_party/mlir/include/mlir/Dialect/Linalg/Utils/Utils.h index 8dc78458c87..9f1a8342252 100644 --- a/third_party/mlir/include/mlir/Dialect/Linalg/Utils/Utils.h +++ b/third_party/mlir/include/mlir/Dialect/Linalg/Utils/Utils.h @@ -23,6 +23,8 @@ #include "mlir/Dialect/StandardOps/Ops.h" #include "mlir/EDSC/Helpers.h" +#include "llvm/ADT/SetVector.h" + namespace mlir { class AffineExpr; class AffineMap; @@ -205,6 +207,29 @@ promoteSubViews(OpBuilder &b, Location loc, ArrayRef subViews, /// tiling to just use the values when cloning `linalgOp`. llvm::SmallVector getAssumedNonViewOperands(LinalgOp linalgOp); +/// Apply the permutation defined by `permutation` to `inVec`. +/// Element `i` in `inVec` is mapped to location `j = permutation[i]`. +/// E.g.: for an input vector `inVec = ['a', 'b', 'c']` and a permutation vector +/// `permutation = [2, 0, 1]`, this function leaves `inVec = ['c', 'a', 'b']`. +template +void applyPermutationToVector(SmallVector &inVec, + ArrayRef permutation) { + SmallVector auxVec(inVec.size()); + for (unsigned i = 0; i < permutation.size(); ++i) + auxVec[i] = inVec[permutation[i]]; + inVec = auxVec; +} + +/// Prepares the SubView promotion later performed by `promoteSubViews` +/// (where most of the transformation happens). It arranges the new +/// operands for `LinalgOp op` and deallocates the new buffer(s) +/// It is the entry point for declarative transformation +/// Returns the cloned `LinalgOp` with the new operands +LinalgOp promoteSubViewOperands(OpBuilder &b, LinalgOp op, + llvm::SetVector subViews, + bool dynamicBuffers = false, + OperationFolder *folder = nullptr); + } // namespace linalg } // namespace mlir diff --git a/third_party/mlir/include/mlir/Dialect/QuantOps/QuantOps.td b/third_party/mlir/include/mlir/Dialect/QuantOps/QuantOps.td index 85d5cd2ee90..072715d65aa 100644 --- a/third_party/mlir/include/mlir/Dialect/QuantOps/QuantOps.td +++ b/third_party/mlir/include/mlir/Dialect/QuantOps/QuantOps.td @@ -93,7 +93,7 @@ def quant_DequantizeCastOp : quant_Op<"dcast", [NoSideEffect]> { def quant_StorageCastOp : quant_Op<"scast", [NoSideEffect]> { let arguments = (ins quant_RealOrStorageValueType:$arg); let results = (outs quant_RealOrStorageValueType); - let hasCanonicalizer = 0b1; + let hasFolder = 1; } //===----------------------------------------------------------------------===// diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/LayoutUtils.h b/third_party/mlir/include/mlir/Dialect/SPIRV/LayoutUtils.h index a65ee415d9d..7537e5f654b 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/LayoutUtils.h +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/LayoutUtils.h @@ -63,7 +63,7 @@ public: /// Checks whether a type is legal in terms of Vulkan layout info /// decoration. A type is dynamically illegal if it's a composite type in the /// StorageBuffer, PhysicalStorageBuffer, Uniform, and PushConstant Storage - /// Classes without layout informtation. + /// Classes without layout information. static bool isLegalType(Type type); private: diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVArithmeticOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVArithmeticOps.td index 00ce72f5b2a..f15d274922a 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVArithmeticOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVArithmeticOps.td @@ -53,7 +53,7 @@ def SPV_FAddOp : SPV_ArithmeticBinaryOp<"FAdd", SPV_Float, [Commutative]> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fadd-op ::= ssa-id `=` `spv.FAdd` ssa-use, ssa-use @@ -83,7 +83,7 @@ def SPV_FDivOp : SPV_ArithmeticBinaryOp<"FDiv", SPV_Float, []> { if Operand 2 is 0. ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fdiv-op ::= ssa-id `=` `spv.FDiv` ssa-use, ssa-use @@ -118,7 +118,7 @@ def SPV_FModOp : SPV_ArithmeticBinaryOp<"FMod", SPV_Float, []> { sign of Operand 2. ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fmod-op ::= ssa-id `=` `spv.FMod` ssa-use, ssa-use @@ -148,7 +148,7 @@ def SPV_FMulOp : SPV_ArithmeticBinaryOp<"FMul", SPV_Float, [Commutative]> { ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fmul-op ::= `spv.FMul` ssa-use, ssa-use @@ -178,7 +178,7 @@ def SPV_FNegateOp : SPV_ArithmeticUnaryOp<"FNegate", SPV_Float, []> { ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fmul-op ::= `spv.FNegate` ssa-use `:` float-scalar-vector-type @@ -212,7 +212,7 @@ def SPV_FRemOp : SPV_ArithmeticBinaryOp<"FRem", SPV_Float, []> { sign of Operand 1. ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` frem-op ::= ssa-id `=` `spv.FRemOp` ssa-use, ssa-use @@ -242,7 +242,7 @@ def SPV_FSubOp : SPV_ArithmeticBinaryOp<"FSub", SPV_Float, []> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fsub-op ::= ssa-id `=` `spv.FRemOp` ssa-use, ssa-use @@ -277,7 +277,7 @@ def SPV_IAddOp : SPV_ArithmeticBinaryOp<"IAdd", SPV_Integer, [Commutative]> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` iadd-op ::= ssa-id `=` `spv.IAdd` ssa-use, ssa-use @@ -315,7 +315,7 @@ def SPV_IMulOp : SPV_ArithmeticBinaryOp<"IMul", SPV_Integer, [Commutative]> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` imul-op ::= ssa-id `=` `spv.IMul` ssa-use, ssa-use @@ -353,7 +353,7 @@ def SPV_ISubOp : SPV_ArithmeticBinaryOp<"ISub", SPV_Integer, []> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` isub-op ::= `spv.ISub` ssa-use, ssa-use @@ -368,6 +368,8 @@ def SPV_ISubOp : SPV_ArithmeticBinaryOp<"ISub", SPV_Integer, []> { ``` }]; + + let hasFolder = 1; } // ----- @@ -386,7 +388,7 @@ def SPV_SDivOp : SPV_ArithmeticBinaryOp<"SDiv", SPV_Integer, []> { if Operand 2 is 0. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` sdiv-op ::= ssa-id `=` `spv.SDiv` ssa-use, ssa-use @@ -424,7 +426,7 @@ def SPV_SModOp : SPV_ArithmeticBinaryOp<"SMod", SPV_Integer, []> { sign of Operand 2. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` smod-op ::= ssa-id `=` `spv.SMod` ssa-use, ssa-use @@ -461,7 +463,7 @@ def SPV_SRemOp : SPV_ArithmeticBinaryOp<"SRem", SPV_Integer, []> { sign of Operand 1. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` srem-op ::= ssa-id `=` `spv.SRem` ssa-use, ssa-use @@ -493,7 +495,7 @@ def SPV_UDivOp : SPV_ArithmeticBinaryOp<"UDiv", SPV_Integer, []> { if Operand 2 is 0. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` udiv-op ::= ssa-id `=` `spv.UDiv` ssa-use, ssa-use @@ -525,7 +527,7 @@ def SPV_UModOp : SPV_ArithmeticBinaryOp<"UMod", SPV_Integer> { if Operand 2 is 0. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` umod-op ::= ssa-id `=` `spv.UMod` ssa-use, ssa-use diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVAtomicOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVAtomicOps.td index 7042bf2cd3e..15b6ab0105c 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVAtomicOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVAtomicOps.td @@ -23,6 +23,84 @@ #ifndef SPIRV_ATOMIC_OPS #define SPIRV_ATOMIC_OPS +class SPV_AtomicUpdateOp traits = []> : + SPV_Op { + let parser = [{ return ::parseAtomicUpdateOp(parser, result, false); }]; + let printer = [{ return ::printAtomicUpdateOp(getOperation(), p); }]; + let verifier = [{ return ::verifyAtomicUpdateOp(getOperation()); }]; + + let arguments = (ins + SPV_AnyPtr:$pointer, + SPV_ScopeAttr:$memory_scope, + SPV_MemorySemanticsAttr:$semantics + ); + let results = (outs + SPV_Integer:$result + ); +} + +class SPV_AtomicUpdateWithValueOp traits = []> : + SPV_Op { + let parser = [{ return ::parseAtomicUpdateOp(parser, result, true); }]; + let printer = [{ return ::printAtomicUpdateOp(getOperation(), p); }]; + let verifier = [{ return ::verifyAtomicUpdateOp(getOperation()); }]; + + let arguments = (ins + SPV_AnyPtr:$pointer, + SPV_ScopeAttr:$memory_scope, + SPV_MemorySemanticsAttr:$semantics, + SPV_Integer:$value + ); + let results = (outs + SPV_Integer:$result + ); +} + +// ----- + +def SPV_AtomicAndOp : SPV_AtomicUpdateWithValueOp<"AtomicAnd", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by the bitwise AND of Original Value and Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + scope ::= `"CrossDevice"` | `"Device"` | `"Workgroup"` | ... + + memory-semantics ::= `"None"` | `"Acquire"` | "Release"` | ... + + atomic-and-op ::= + `spv.AtomicAnd` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicAnd "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + // ----- def SPV_AtomicCompareExchangeWeakOp : SPV_Op<"AtomicCompareExchangeWeak", []> { @@ -35,11 +113,7 @@ def SPV_AtomicCompareExchangeWeakOp : SPV_Op<"AtomicCompareExchangeWeak", []> { ### Custom assembly form - ``` {.ebnf} - scope ::= `"CrossDevice"` | `"Device"` | `"Workgroup"` | ... - - memory-semantics ::= `"None"` | `"Acquire"` | "Release"` | ... - + ``` atomic-compare-exchange-weak-op ::= `spv.AtomicCompareExchangeWeak` scope memory-semantics memory-semantics ssa-use `,` ssa-use `,` ssa-use @@ -71,4 +145,417 @@ def SPV_AtomicCompareExchangeWeakOp : SPV_Op<"AtomicCompareExchangeWeak", []> { // ----- +def SPV_AtomicIAddOp : SPV_AtomicUpdateWithValueOp<"AtomicIAdd", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by integer addition of Original Value and Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-iadd-op ::= + `spv.AtomicIAdd` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicIAdd "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicIDecrementOp : SPV_AtomicUpdateOp<"AtomicIDecrement", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value through integer subtraction of 1 from Original Value, + and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. The type of the value + pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-idecrement-op ::= + `spv.AtomicIDecrement` scope memory-semantics ssa-use + `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicIDecrement "Device" "None" %pointer : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicIIncrementOp : SPV_AtomicUpdateOp<"AtomicIIncrement", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value through integer addition of 1 to Original Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. The type of the value + pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-iincrement-op ::= + `spv.AtomicIIncrement` scope memory-semantics ssa-use + `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicIncrement "Device" "None" %pointer : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicISubOp : SPV_AtomicUpdateWithValueOp<"AtomicISub", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by integer subtraction of Value from Original Value, + and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-isub-op ::= + `spv.AtomicISub` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicISub "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicOrOp : SPV_AtomicUpdateWithValueOp<"AtomicOr", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by the bitwise OR of Original Value and Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-or-op ::= + `spv.AtomicOr` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicOr "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicSMaxOp : SPV_AtomicUpdateWithValueOp<"AtomicSMax", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by finding the largest signed integer of Original + Value and Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-smax-op ::= + `spv.AtomicSMax` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicSMax "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicSMinOp : SPV_AtomicUpdateWithValueOp<"AtomicSMin", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by finding the smallest signed integer of Original + Value and Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-smin-op ::= + `spv.AtomicSMin` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicSMin "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicUMaxOp : SPV_AtomicUpdateWithValueOp<"AtomicUMax", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by finding the largest unsigned integer of Original + Value and Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-umax-op ::= + `spv.AtomicUMax` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicUMax "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicUMinOp : SPV_AtomicUpdateWithValueOp<"AtomicUMin", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by finding the smallest unsigned integer of Original + Value and Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-umin-op ::= + `spv.AtomicUMin` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicUMin "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + +def SPV_AtomicXorOp : SPV_AtomicUpdateWithValueOp<"AtomicXor", []> { + let summary = [{ + Perform the following steps atomically with respect to any other atomic + accesses within Scope to the same location: + }]; + + let description = [{ + 1) load through Pointer to get an Original Value, + + 2) get a New Value by the bitwise exclusive OR of Original Value and + Value, and + + 3) store the New Value back through Pointer. + + The instruction’s result is the Original Value. + + Result Type must be an integer type scalar. + + The type of Value must be the same as Result Type. The type of the + value pointed to by Pointer must be the same as Result Type. + + Memory must be a valid memory Scope. + + ### Custom assembly form + + ``` + atomic-xor-op ::= + `spv.AtomicXor` scope memory-semantics + ssa-use `,` ssa-use `:` spv-pointer-type + ``` + + For example: + + ``` + %0 = spv.AtomicXor "Device" "None" %pointer, %value : + !spv.ptr + ``` + }]; +} + +// ----- + #endif // SPIRV_ATOMIC_OPS diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBase.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBase.td index 62095a518e9..838398823ad 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBase.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBase.td @@ -1075,6 +1075,7 @@ def SPV_OC_OpStore : I32EnumAttrCase<"OpStore", 62>; def SPV_OC_OpAccessChain : I32EnumAttrCase<"OpAccessChain", 65>; def SPV_OC_OpDecorate : I32EnumAttrCase<"OpDecorate", 71>; def SPV_OC_OpMemberDecorate : I32EnumAttrCase<"OpMemberDecorate", 72>; +def SPV_OC_OpCompositeConstruct : I32EnumAttrCase<"OpCompositeConstruct", 80>; def SPV_OC_OpCompositeExtract : I32EnumAttrCase<"OpCompositeExtract", 81>; def SPV_OC_OpCompositeInsert : I32EnumAttrCase<"OpCompositeInsert", 82>; def SPV_OC_OpConvertFToU : I32EnumAttrCase<"OpConvertFToU", 109>; @@ -1143,6 +1144,17 @@ def SPV_OC_OpBitCount : I32EnumAttrCase<"OpBitCount", 205>; def SPV_OC_OpControlBarrier : I32EnumAttrCase<"OpControlBarrier", 224>; def SPV_OC_OpMemoryBarrier : I32EnumAttrCase<"OpMemoryBarrier", 225>; def SPV_OC_OpAtomicCompareExchangeWeak : I32EnumAttrCase<"OpAtomicCompareExchangeWeak", 231>; +def SPV_OC_OpAtomicIIncrement : I32EnumAttrCase<"OpAtomicIIncrement", 232>; +def SPV_OC_OpAtomicIDecrement : I32EnumAttrCase<"OpAtomicIDecrement", 233>; +def SPV_OC_OpAtomicIAdd : I32EnumAttrCase<"OpAtomicIAdd", 234>; +def SPV_OC_OpAtomicISub : I32EnumAttrCase<"OpAtomicISub", 235>; +def SPV_OC_OpAtomicSMin : I32EnumAttrCase<"OpAtomicSMin", 236>; +def SPV_OC_OpAtomicUMin : I32EnumAttrCase<"OpAtomicUMin", 237>; +def SPV_OC_OpAtomicSMax : I32EnumAttrCase<"OpAtomicSMax", 238>; +def SPV_OC_OpAtomicUMax : I32EnumAttrCase<"OpAtomicUMax", 239>; +def SPV_OC_OpAtomicAnd : I32EnumAttrCase<"OpAtomicAnd", 240>; +def SPV_OC_OpAtomicOr : I32EnumAttrCase<"OpAtomicOr", 241>; +def SPV_OC_OpAtomicXor : I32EnumAttrCase<"OpAtomicXor", 242>; def SPV_OC_OpPhi : I32EnumAttrCase<"OpPhi", 245>; def SPV_OC_OpLoopMerge : I32EnumAttrCase<"OpLoopMerge", 246>; def SPV_OC_OpSelectionMerge : I32EnumAttrCase<"OpSelectionMerge", 247>; @@ -1171,20 +1183,21 @@ def SPV_OpcodeAttr : SPV_OC_OpSpecConstantComposite, SPV_OC_OpFunction, SPV_OC_OpFunctionParameter, SPV_OC_OpFunctionEnd, SPV_OC_OpFunctionCall, SPV_OC_OpVariable, SPV_OC_OpLoad, SPV_OC_OpStore, SPV_OC_OpAccessChain, SPV_OC_OpDecorate, - SPV_OC_OpMemberDecorate, SPV_OC_OpCompositeExtract, SPV_OC_OpCompositeInsert, - SPV_OC_OpConvertFToU, SPV_OC_OpConvertFToS, SPV_OC_OpConvertSToF, - SPV_OC_OpConvertUToF, SPV_OC_OpUConvert, SPV_OC_OpSConvert, SPV_OC_OpFConvert, - SPV_OC_OpBitcast, SPV_OC_OpFNegate, SPV_OC_OpIAdd, SPV_OC_OpFAdd, - SPV_OC_OpISub, SPV_OC_OpFSub, SPV_OC_OpIMul, SPV_OC_OpFMul, SPV_OC_OpUDiv, - SPV_OC_OpSDiv, SPV_OC_OpFDiv, SPV_OC_OpUMod, SPV_OC_OpSRem, SPV_OC_OpSMod, - SPV_OC_OpFRem, SPV_OC_OpFMod, SPV_OC_OpLogicalEqual, SPV_OC_OpLogicalNotEqual, - SPV_OC_OpLogicalOr, SPV_OC_OpLogicalAnd, SPV_OC_OpLogicalNot, SPV_OC_OpSelect, - SPV_OC_OpIEqual, SPV_OC_OpINotEqual, SPV_OC_OpUGreaterThan, - SPV_OC_OpSGreaterThan, SPV_OC_OpUGreaterThanEqual, SPV_OC_OpSGreaterThanEqual, - SPV_OC_OpULessThan, SPV_OC_OpSLessThan, SPV_OC_OpULessThanEqual, - SPV_OC_OpSLessThanEqual, SPV_OC_OpFOrdEqual, SPV_OC_OpFUnordEqual, - SPV_OC_OpFOrdNotEqual, SPV_OC_OpFUnordNotEqual, SPV_OC_OpFOrdLessThan, - SPV_OC_OpFUnordLessThan, SPV_OC_OpFOrdGreaterThan, SPV_OC_OpFUnordGreaterThan, + SPV_OC_OpMemberDecorate, SPV_OC_OpCompositeConstruct, + SPV_OC_OpCompositeExtract, SPV_OC_OpCompositeInsert, SPV_OC_OpConvertFToU, + SPV_OC_OpConvertFToS, SPV_OC_OpConvertSToF, SPV_OC_OpConvertUToF, + SPV_OC_OpUConvert, SPV_OC_OpSConvert, SPV_OC_OpFConvert, SPV_OC_OpBitcast, + SPV_OC_OpFNegate, SPV_OC_OpIAdd, SPV_OC_OpFAdd, SPV_OC_OpISub, SPV_OC_OpFSub, + SPV_OC_OpIMul, SPV_OC_OpFMul, SPV_OC_OpUDiv, SPV_OC_OpSDiv, SPV_OC_OpFDiv, + SPV_OC_OpUMod, SPV_OC_OpSRem, SPV_OC_OpSMod, SPV_OC_OpFRem, SPV_OC_OpFMod, + SPV_OC_OpLogicalEqual, SPV_OC_OpLogicalNotEqual, SPV_OC_OpLogicalOr, + SPV_OC_OpLogicalAnd, SPV_OC_OpLogicalNot, SPV_OC_OpSelect, SPV_OC_OpIEqual, + SPV_OC_OpINotEqual, SPV_OC_OpUGreaterThan, SPV_OC_OpSGreaterThan, + SPV_OC_OpUGreaterThanEqual, SPV_OC_OpSGreaterThanEqual, SPV_OC_OpULessThan, + SPV_OC_OpSLessThan, SPV_OC_OpULessThanEqual, SPV_OC_OpSLessThanEqual, + SPV_OC_OpFOrdEqual, SPV_OC_OpFUnordEqual, SPV_OC_OpFOrdNotEqual, + SPV_OC_OpFUnordNotEqual, SPV_OC_OpFOrdLessThan, SPV_OC_OpFUnordLessThan, + SPV_OC_OpFOrdGreaterThan, SPV_OC_OpFUnordGreaterThan, SPV_OC_OpFOrdLessThanEqual, SPV_OC_OpFUnordLessThanEqual, SPV_OC_OpFOrdGreaterThanEqual, SPV_OC_OpFUnordGreaterThanEqual, SPV_OC_OpShiftRightLogical, SPV_OC_OpShiftRightArithmetic, @@ -1192,11 +1205,14 @@ def SPV_OpcodeAttr : SPV_OC_OpBitwiseAnd, SPV_OC_OpNot, SPV_OC_OpBitFieldInsert, SPV_OC_OpBitFieldSExtract, SPV_OC_OpBitFieldUExtract, SPV_OC_OpBitReverse, SPV_OC_OpBitCount, SPV_OC_OpControlBarrier, SPV_OC_OpMemoryBarrier, - SPV_OC_OpAtomicCompareExchangeWeak, SPV_OC_OpPhi, SPV_OC_OpLoopMerge, - SPV_OC_OpSelectionMerge, SPV_OC_OpLabel, SPV_OC_OpBranch, - SPV_OC_OpBranchConditional, SPV_OC_OpReturn, SPV_OC_OpReturnValue, - SPV_OC_OpUnreachable, SPV_OC_OpModuleProcessed, SPV_OC_OpGroupNonUniformBallot, - SPV_OC_OpSubgroupBallotKHR + SPV_OC_OpAtomicCompareExchangeWeak, SPV_OC_OpAtomicIIncrement, + SPV_OC_OpAtomicIDecrement, SPV_OC_OpAtomicIAdd, SPV_OC_OpAtomicISub, + SPV_OC_OpAtomicSMin, SPV_OC_OpAtomicUMin, SPV_OC_OpAtomicSMax, + SPV_OC_OpAtomicUMax, SPV_OC_OpAtomicAnd, SPV_OC_OpAtomicOr, SPV_OC_OpAtomicXor, + SPV_OC_OpPhi, SPV_OC_OpLoopMerge, SPV_OC_OpSelectionMerge, SPV_OC_OpLabel, + SPV_OC_OpBranch, SPV_OC_OpBranchConditional, SPV_OC_OpReturn, + SPV_OC_OpReturnValue, SPV_OC_OpUnreachable, SPV_OC_OpModuleProcessed, + SPV_OC_OpGroupNonUniformBallot, SPV_OC_OpSubgroupBallotKHR ]> { let cppNamespace = "::mlir::spirv"; } diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBitOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBitOps.td index ba748f8ad04..d76a1e3854b 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBitOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVBitOps.td @@ -83,7 +83,7 @@ def SPV_BitCountOp : SPV_BitUnaryOp<"BitCount", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` bitcount-op ::= ssa-id `=` `spv.BitCount` ssa-use @@ -132,7 +132,7 @@ def SPV_BitFieldInsertOp : SPV_Op<"BitFieldInsert", [NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` bitfield-insert-op ::= ssa-id `=` `spv.BitFieldInsert` ssa-use `,` ssa-use @@ -190,10 +190,10 @@ def SPV_BitFieldSExtractOp : SPV_BitFieldExtractOp<"BitFieldSExtract", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` - bitfield-exctact-s-op ::= ssa-id `=` `spv.BitFieldSExtract` ssa-use + bitfield-extract-s-op ::= ssa-id `=` `spv.BitFieldSExtract` ssa-use `,` ssa-use `,` ssa-use `:` integer-scalar-vector-type `,` integer-type `,` integer-type @@ -219,10 +219,10 @@ def SPV_BitFieldUExtractOp : SPV_BitFieldExtractOp<"BitFieldUExtract", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` - bitfield-exctact-u-op ::= ssa-id `=` `spv.BitFieldUExtract` ssa-use + bitfield-extract-u-op ::= ssa-id `=` `spv.BitFieldUExtract` ssa-use `,` ssa-use `,` ssa-use `:` integer-scalar-vector-type `,` integer-type `,` integer-type @@ -253,7 +253,7 @@ def SPV_BitReverseOp : SPV_BitUnaryOp<"BitReverse", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` bitreverse-op ::= ssa-id `=` `spv.BitReverse` ssa-use @@ -287,7 +287,7 @@ def SPV_BitwiseAndOp : SPV_BitBinaryOp<"BitwiseAnd", [Commutative]> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` bitwise-and-op ::= ssa-id `=` `spv.BitwiseAnd` ssa-use, ssa-use @@ -321,7 +321,7 @@ def SPV_BitwiseOrOp : SPV_BitBinaryOp<"BitwiseOr", [Commutative]> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` bitwise-or-op ::= ssa-id `=` `spv.BitwiseOr` ssa-use, ssa-use @@ -355,7 +355,7 @@ def SPV_BitwiseXorOp : SPV_BitBinaryOp<"BitwiseXor", [Commutative]> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` bitwise-xor-op ::= ssa-id `=` `spv.BitwiseXor` ssa-use, ssa-use @@ -397,7 +397,7 @@ def SPV_ShiftLeftLogicalOp : SPV_ShiftOp<"ShiftLeftLogical", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` shift-left-logical-op ::= ssa-id `=` `spv.ShiftLeftLogical` @@ -438,7 +438,7 @@ def SPV_ShiftRightArithmeticOp : SPV_ShiftOp<"ShiftRightArithmetic", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` shift-right-arithmetic-op ::= ssa-id `=` `spv.ShiftRightArithmetic` @@ -480,7 +480,7 @@ def SPV_ShiftRightLogicalOp : SPV_ShiftOp<"ShiftRightLogical", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` shift-right-logical-op ::= ssa-id `=` `spv.ShiftRightLogical` @@ -514,7 +514,7 @@ def SPV_NotOp : SPV_BitUnaryOp<"Not", []> { ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` not-op ::= ssa-id `=` `spv.BitNot` ssa-use `:` integer-scalar-vector-type diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCastOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCastOps.td index 2faffb6280b..e4fe526e420 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCastOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCastOps.td @@ -72,7 +72,7 @@ def SPV_BitcastOp : SPV_Op<"Bitcast", [NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` bitcast-op ::= ssa-id `=` `spv.Bitcast` ssa-use `:` operand-type `to` result-type ``` @@ -118,7 +118,7 @@ def SPV_ConvertFToSOp : SPV_CastOp<"ConvertFToS", SPV_Integer, SPV_Float, []> { ### Custom assembly form - ``` {.ebnf} + ``` convert-f-to-s-op ::= ssa-id `=` `spv.ConvertFToSOp` ssa-use `:` operand-type `to` result-type ``` @@ -151,7 +151,7 @@ def SPV_ConvertFToUOp : SPV_CastOp<"ConvertFToU", SPV_Integer, SPV_Float, []> { ### Custom assembly form - ``` {.ebnf} + ``` convert-f-to-u-op ::= ssa-id `=` `spv.ConvertFToUOp` ssa-use `:` operand-type `to` result-type ``` @@ -182,7 +182,7 @@ def SPV_ConvertSToFOp : SPV_CastOp<"ConvertSToF", SPV_Float, SPV_Integer, []> { ### Custom assembly form - ``` {.ebnf} + ``` convert-s-to-f-op ::= ssa-id `=` `spv.ConvertSToFOp` ssa-use `:` operand-type `to` result-type ``` @@ -213,7 +213,7 @@ def SPV_ConvertUToFOp : SPV_CastOp<"ConvertUToF", SPV_Float, SPV_Integer, []> { ### Custom assembly form - ``` {.ebnf} + ``` convert-u-to-f-op ::= ssa-id `=` `spv.ConvertUToFOp` ssa-use `:` operand-type `to` result-type ``` @@ -246,7 +246,7 @@ def SPV_FConvertOp : SPV_CastOp<"FConvert", SPV_Float, SPV_Float, []> { ### Custom assembly form - ``` {.ebnf} + ``` f-convert-op ::= ssa-id `=` `spv.FConvertOp` ssa-use `:` operand-type `to` result-type ``` @@ -280,7 +280,7 @@ def SPV_SConvertOp : SPV_CastOp<"SConvert", SPV_Integer, SPV_Integer, []> { ### Custom assembly form - ``` {.ebnf} + ``` s-convert-op ::= ssa-id `=` `spv.SConvertOp` ssa-use `:` operand-type `to` result-type ``` @@ -315,7 +315,7 @@ def SPV_UConvertOp : SPV_CastOp<"UConvert", SPV_Integer, SPV_Integer, []> { ### Custom assembly form - ``` {.ebnf} + ``` u-convert-op ::= ssa-id `=` `spv.UConvertOp` ssa-use `:` operand-type `to` result-type ``` diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCompositeOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCompositeOps.td index 71650504741..d6e2e1c6fda 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCompositeOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVCompositeOps.td @@ -25,6 +25,58 @@ include "mlir/Dialect/SPIRV/SPIRVBase.td" +// ----- + +def SPV_CompositeConstructOp : SPV_Op<"CompositeConstruct", [NoSideEffect]> { + let summary = [{ + Construct a new composite object from a set of constituent objects that + will fully form it. + }]; + + let description = [{ + Result Type must be a composite type, whose top-level + members/elements/components/columns have the same type as the types of + the operands, with one exception. The exception is that for constructing + a vector, the operands may also be vectors with the same component type + as the Result Type component type. When constructing a vector, the total + number of components in all the operands must equal the number of + components in Result Type. + + Constituents will become members of a structure, or elements of an + array, or components of a vector, or columns of a matrix. There must be + exactly one Constituent for each top-level + member/element/component/column of the result, with one exception. The + exception is that for constructing a vector, a contiguous subset of the + scalars consumed can be represented by a vector operand instead. The + Constituents must appear in the order needed by the definition of the + type of the result. When constructing a vector, there must be at least + two Constituent operands. + + ### Custom assembly form + + ``` + composite-construct-op ::= ssa-id `=` `spv.CompositeConstruct` + (ssa-use (`,` ssa-use)* )? `:` composite-type + ``` + + For example: + + ``` + %0 = spv.CompositeConstruct %1, %2, %3 : vector<3xf32> + ``` + }]; + + let arguments = (ins + Variadic:$constituents + ); + + let results = (outs + SPV_Composite:$result + ); +} + +// ----- + def SPV_CompositeExtractOp : SPV_Op<"CompositeExtract", [NoSideEffect]> { let summary = "Extract a part of a composite object."; @@ -41,7 +93,7 @@ def SPV_CompositeExtractOp : SPV_Op<"CompositeExtract", [NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` composite-extract-op ::= ssa-id `=` `spv.CompositeExtract` ssa-use `[` integer-literal (',' integer-literal)* `]` `:` composite-type @@ -66,6 +118,11 @@ def SPV_CompositeExtractOp : SPV_Op<"CompositeExtract", [NoSideEffect]> { SPV_Type:$component ); + let builders = [ + OpBuilder<[{Builder *builder, OperationState &state, + Value *composite, ArrayRef indices}]> + ]; + let hasFolder = 1; } @@ -91,7 +148,7 @@ def SPV_CompositeInsertOp : SPV_Op<"CompositeInsert", [NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` composite-insert-op ::= ssa-id `=` `spv.CompositeInsert` ssa-use, ssa-use `[` integer-literal (',' integer-literal)* `]` `:` object-type `into` composite-type diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVControlFlowOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVControlFlowOps.td index ab9d51b05e7..464b670dae9 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVControlFlowOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVControlFlowOps.td @@ -36,7 +36,7 @@ def SPV_BranchOp : SPV_Op<"Branch", [InFunctionScope, Terminator]> { ### Custom assembly form - ``` {.ebnf} + ``` branch-op ::= `spv.Branch` successor successor ::= bb-id branch-use-list? branch-use-list ::= `(` ssa-use-list `:` type-list-no-parens `)` @@ -106,7 +106,7 @@ def SPV_BranchConditionalOp : SPV_Op<"BranchConditional", ### Custom assembly form - ``` {.ebnf} + ``` branch-conditional-op ::= `spv.BranchConditional` ssa-use (`[` integer-literal, integer-literal `]`)? `,` successor `,` successor @@ -231,7 +231,7 @@ def SPV_FunctionCallOp : SPV_Op<"FunctionCall", [ ### Custom assembly form - ``` {.ebnf} + ``` function-call-op ::= `spv.FunctionCall` function-id `(` ssa-use-list `)` `:` function-type ``` @@ -355,7 +355,7 @@ def SPV_ReturnOp : SPV_Op<"Return", [InFunctionScope, Terminator]> { ### Custom assembly form - ``` {.ebnf} + ``` return-op ::= `spv.Return` ``` }]; @@ -378,7 +378,7 @@ def SPV_UnreachableOp : SPV_Op<"Unreachable", [InFunctionScope, Terminator]> { ### Custom assembly form - ``` {.ebnf} + ``` unreachable-op ::= `spv.Unreachable` ``` }]; @@ -405,7 +405,7 @@ def SPV_ReturnValueOp : SPV_Op<"ReturnValue", [InFunctionScope, Terminator]> { ### Custom assembly form - ``` {.ebnf} + ``` return-value-op ::= `spv.ReturnValue` ssa-use `:` spirv-type ``` diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGLSLOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGLSLOps.td index 2a1e8f32807..a031facdf5a 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGLSLOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGLSLOps.td @@ -100,7 +100,7 @@ def SPV_GLSLFAbsOp : SPV_GLSLUnaryArithmeticOp<"FAbs", 4, SPV_Float> { per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` abs-op ::= ssa-id `=` `spv.GLSL.FAbs` ssa-use `:` @@ -129,7 +129,7 @@ def SPV_GLSLSAbsOp : SPV_GLSLUnaryArithmeticOp<"SAbs", 5, SPV_Integer> { with the same component width. Results are computed per component. ### Custom assembly format - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` abs-op ::= ssa-id `=` `spv.GLSL.SAbs` ssa-use `:` @@ -160,7 +160,7 @@ def SPV_GLSLCeilOp : SPV_GLSLUnaryArithmeticOp<"Ceil", 9, SPV_Float> { per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` ceil-op ::= ssa-id `=` `spv.GLSL.Ceil` ssa-use `:` @@ -190,7 +190,7 @@ def SPV_GLSLCosOp : SPV_GLSLUnaryArithmeticOp<"Cos", 14, SPV_Float16or32> { per component. ### Custom assembly format - ``` {.ebnf} + ``` restricted-float-scalar-type ::= `f16` | `f32` restricted-float-scalar-vector-type ::= restricted-float-scalar-type | @@ -222,7 +222,7 @@ def SPV_GLSLExpOp : SPV_GLSLUnaryArithmeticOp<"Exp", 27, SPV_Float16or32> { computed per component."; ### Custom assembly format - ``` {.ebnf} + ``` restricted-float-scalar-type ::= `f16` | `f32` restricted-float-scalar-vector-type ::= restricted-float-scalar-type | @@ -255,7 +255,7 @@ def SPV_GLSLFloorOp : SPV_GLSLUnaryArithmeticOp<"Floor", 8, SPV_Float> { per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` floor-op ::= ssa-id `=` `spv.GLSL.Floor` ssa-use `:` @@ -285,7 +285,7 @@ def SPV_GLSLInverseSqrtOp : SPV_GLSLUnaryArithmeticOp<"InverseSqrt", 32, SPV_Flo per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` rsqrt-op ::= ssa-id `=` `spv.GLSL.InverseSqrt` ssa-use `:` @@ -316,7 +316,7 @@ def SPV_GLSLLogOp : SPV_GLSLUnaryArithmeticOp<"Log", 28, SPV_Float16or32> { per component. ### Custom assembly format - ``` {.ebnf} + ``` restricted-float-scalar-type ::= `f16` | `f32` restricted-float-scalar-vector-type ::= restricted-float-scalar-type | @@ -349,7 +349,7 @@ def SPV_GLSLFMaxOp : SPV_GLSLBinaryArithmeticOp<"FMax", 40, SPV_Float> { type. Results are computed per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fmax-op ::= ssa-id `=` `spv.GLSL.FMax` ssa-use `:` @@ -379,7 +379,7 @@ def SPV_GLSLSMaxOp : SPV_GLSLBinaryArithmeticOp<"SMax", 42, SPV_Integer> { component. ### Custom assembly format - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` smax-op ::= ssa-id `=` `spv.GLSL.SMax` ssa-use `:` @@ -410,7 +410,7 @@ def SPV_GLSLFMinOp : SPV_GLSLBinaryArithmeticOp<"FMin", 37, SPV_Float> { computed per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fmin-op ::= ssa-id `=` `spv.GLSL.FMin` ssa-use `:` @@ -440,7 +440,7 @@ def SPV_GLSLSMinOp : SPV_GLSLBinaryArithmeticOp<"SMin", 39, SPV_Integer> { component. ### Custom assembly format - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` smin-op ::= ssa-id `=` `spv.GLSL.SMin` ssa-use `:` @@ -470,7 +470,7 @@ def SPV_GLSLFSignOp : SPV_GLSLUnaryArithmeticOp<"FSign", 6, SPV_Float> { per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` sign-op ::= ssa-id `=` `spv.GLSL.FSign` ssa-use `:` @@ -499,7 +499,7 @@ def SPV_GLSLSSignOp : SPV_GLSLUnaryArithmeticOp<"SSign", 7, SPV_Integer> { with the same component width. Results are computed per component. ### Custom assembly format - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` sign-op ::= ssa-id `=` `spv.GLSL.SSign` ssa-use `:` @@ -529,7 +529,7 @@ def SPV_GLSLSqrtOp : SPV_GLSLUnaryArithmeticOp<"Sqrt", 31, SPV_Float> { per component. ### Custom assembly format - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` sqrt-op ::= ssa-id `=` `spv.GLSL.Sqrt` ssa-use `:` @@ -559,7 +559,7 @@ def SPV_GLSLTanhOp : SPV_GLSLUnaryArithmeticOp<"Tanh", 21, SPV_Float16or32> { per component. ### Custom assembly format - ``` {.ebnf} + ``` restricted-float-scalar-type ::= `f16` | `f32` restricted-float-scalar-vector-type ::= restricted-float-scalar-type | diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGroupOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGroupOps.td index 5f60e6b0135..c0388fe4e23 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGroupOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVGroupOps.td @@ -46,7 +46,7 @@ def SPV_SubgroupBallotKHROp : SPV_Op<"SubgroupBallotKHR", []> { ### Custom assembly form - ``` {.ebnf} + ``` subgroup-ballot-op ::= ssa-id `=` `spv.SubgroupBallotKHR` ssa-use `:` `vector` `<` 4 `x` `i32` `>` ``` diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLogicalOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLogicalOps.td index be9825a89ab..0c4b2902a12 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLogicalOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLogicalOps.td @@ -62,7 +62,7 @@ def SPV_FOrdEqualOp : SPV_LogicalBinaryOp<"FOrdEqual", SPV_Float, [Commutative]> ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fordequal-op ::= ssa-id `=` `spv.FOrdEqual` ssa-use, ssa-use @@ -96,7 +96,7 @@ def SPV_FOrdGreaterThanOp : SPV_LogicalBinaryOp<"FOrdGreaterThan", SPV_Float, [] ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fordgt-op ::= ssa-id `=` `spv.FOrdGreaterThan` ssa-use, ssa-use @@ -130,7 +130,7 @@ def SPV_FOrdGreaterThanEqualOp : SPV_LogicalBinaryOp<"FOrdGreaterThanEqual", SPV ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fordgte-op ::= ssa-id `=` `spv.FOrdGreaterThanEqual` ssa-use, ssa-use @@ -164,7 +164,7 @@ def SPV_FOrdLessThanOp : SPV_LogicalBinaryOp<"FOrdLessThan", SPV_Float, []> { ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fordlt-op ::= ssa-id `=` `spv.FOrdLessThan` ssa-use, ssa-use @@ -198,7 +198,7 @@ def SPV_FOrdLessThanEqualOp : SPV_LogicalBinaryOp<"FOrdLessThanEqual", SPV_Float ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fordlte-op ::= ssa-id `=` `spv.FOrdLessThanEqual` ssa-use, ssa-use @@ -229,7 +229,7 @@ def SPV_FOrdNotEqualOp : SPV_LogicalBinaryOp<"FOrdNotEqual", SPV_Float, [Commuta ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` fordneq-op ::= ssa-id `=` `spv.FOrdNotEqual` ssa-use, ssa-use @@ -260,7 +260,7 @@ def SPV_FUnordEqualOp : SPV_LogicalBinaryOp<"FUnordEqual", SPV_Float, [Commutati ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` funordequal-op ::= ssa-id `=` `spv.FUnordEqual` ssa-use, ssa-use @@ -294,7 +294,7 @@ def SPV_FUnordGreaterThanOp : SPV_LogicalBinaryOp<"FUnordGreaterThan", SPV_Float ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` funordgt-op ::= ssa-id `=` `spv.FUnordGreaterThan` ssa-use, ssa-use @@ -328,7 +328,7 @@ def SPV_FUnordGreaterThanEqualOp : SPV_LogicalBinaryOp<"FUnordGreaterThanEqual", ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` funordgte-op ::= ssa-id `=` `spv.FUnordGreaterThanEqual` ssa-use, ssa-use @@ -362,7 +362,7 @@ def SPV_FUnordLessThanOp : SPV_LogicalBinaryOp<"FUnordLessThan", SPV_Float, []> ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` funordlt-op ::= ssa-id `=` `spv.FUnordLessThan` ssa-use, ssa-use @@ -396,7 +396,7 @@ def SPV_FUnordLessThanEqualOp : SPV_LogicalBinaryOp<"FUnordLessThanEqual", SPV_F ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` funordlte-op ::= ssa-id `=` `spv.FUnordLessThanEqual` ssa-use, ssa-use @@ -427,7 +427,7 @@ def SPV_FUnordNotEqualOp : SPV_LogicalBinaryOp<"FUnordNotEqual", SPV_Float, [Com ### Custom assembly form - ``` {.ebnf} + ``` float-scalar-vector-type ::= float-type | `vector<` integer-literal `x` float-type `>` funordneq-op ::= ssa-id `=` `spv.FUnordNotEqual` ssa-use, ssa-use @@ -457,7 +457,7 @@ def SPV_IEqualOp : SPV_LogicalBinaryOp<"IEqual", SPV_Integer, [Commutative]> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` iequal-op ::= ssa-id `=` `spv.IEqual` ssa-use, ssa-use @@ -488,7 +488,7 @@ def SPV_INotEqualOp : SPV_LogicalBinaryOp<"INotEqual", SPV_Integer, [Commutative Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` inot-equal-op ::= ssa-id `=` `spv.INotEqual` ssa-use, ssa-use @@ -523,7 +523,7 @@ def SPV_LogicalAndOp : SPV_LogicalBinaryOp<"LogicalAnd", SPV_Bool, [Commutative] ### Custom assembly form - ``` {.ebnf} + ``` logical-and ::= `spv.LogicalAnd` ssa-use `,` ssa-use `:` operand-type ``` @@ -556,7 +556,7 @@ def SPV_LogicalEqualOp : SPV_LogicalBinaryOp<"LogicalEqual", SPV_Bool, [Commutat ### Custom assembly form - ``` {.ebnf} + ``` logical-equal ::= `spv.LogicalEqual` ssa-use `,` ssa-use `:` operand-type ``` @@ -586,7 +586,7 @@ def SPV_LogicalNotOp : SPV_LogicalUnaryOp<"LogicalNot", SPV_Bool, []> { ### Custom assembly form - ``` {.ebnf} + ``` logical-not ::= `spv.LogicalNot` ssa-use `:` operand-type ``` @@ -620,7 +620,7 @@ def SPV_LogicalNotEqualOp : SPV_LogicalBinaryOp<"LogicalNotEqual", SPV_Bool, [Co ### Custom assembly form - ``` {.ebnf} + ``` logical-not-equal ::= `spv.LogicalNotEqual` ssa-use `,` ssa-use `:` operand-type ``` @@ -653,7 +653,7 @@ def SPV_LogicalOrOp : SPV_LogicalBinaryOp<"LogicalOr", SPV_Bool, [Commutative]> ### Custom assembly form - ``` {.ebnf} + ``` logical-or ::= `spv.LogicalOr` ssa-use `,` ssa-use `:` operand-type ``` @@ -684,7 +684,7 @@ def SPV_SGreaterThanOp : SPV_LogicalBinaryOp<"SGreaterThan", SPV_Integer, []> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` sgreater-than-op ::= ssa-id `=` `spv.SGreaterThan` ssa-use, ssa-use @@ -718,7 +718,7 @@ def SPV_SGreaterThanEqualOp : SPV_LogicalBinaryOp<"SGreaterThanEqual", SPV_Integ Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` sgreater-than-equal-op ::= ssa-id `=` `spv.SGreaterThanEqual` ssa-use, ssa-use @@ -751,7 +751,7 @@ def SPV_SLessThanOp : SPV_LogicalBinaryOp<"SLessThan", SPV_Integer, []> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` sless-than-op ::= ssa-id `=` `spv.SLessThan` ssa-use, ssa-use @@ -785,7 +785,7 @@ def SPV_SLessThanEqualOp : SPV_LogicalBinaryOp<"SLessThanEqual", SPV_Integer, [] Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` sless-than-equal-op ::= ssa-id `=` `spv.SLessThanEqual` ssa-use, ssa-use @@ -827,7 +827,7 @@ def SPV_SelectOp : SPV_Op<"Select", [NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` scalar-type ::= integer-type | float-type | boolean-type select-object-type ::= scalar-type | `vector<` integer-literal `x` scalar-type `>` @@ -879,7 +879,7 @@ def SPV_UGreaterThanOp : SPV_LogicalBinaryOp<"UGreaterThan", SPV_Integer, []> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` ugreater-than-op ::= ssa-id `=` `spv.UGreaterThan` ssa-use, ssa-use @@ -913,7 +913,7 @@ def SPV_UGreaterThanEqualOp : SPV_LogicalBinaryOp<"UGreaterThanEqual", SPV_Integ Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` ugreater-than-equal-op ::= ssa-id `=` `spv.UGreaterThanEqual` ssa-use, ssa-use @@ -946,7 +946,7 @@ def SPV_ULessThanOp : SPV_LogicalBinaryOp<"ULessThan", SPV_Integer, []> { Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` uless-than-op ::= ssa-id `=` `spv.ULessThan` ssa-use, ssa-use @@ -981,7 +981,7 @@ def SPV_ULessThanEqualOp : Results are computed per component. ### Custom assembly form - ``` {.ebnf} + ``` integer-scalar-vector-type ::= integer-type | `vector<` integer-literal `x` integer-type `>` uless-than-equal-op ::= ssa-id `=` `spv.ULessThanEqual` ssa-use, ssa-use diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLowering.h b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLowering.h index 306f2b9f309..f48a1d0b129 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLowering.h +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVLowering.h @@ -30,7 +30,7 @@ namespace mlir { -/// Type conversion from stdandard types to SPIR-V types for shader interface. +/// Type conversion from standard types to SPIR-V types for shader interface. /// /// For composite types, this converter additionally performs type wrapping to /// satisfy shader interface requirements: shader interface types must be @@ -39,10 +39,10 @@ class SPIRVTypeConverter final : public TypeConverter { public: using TypeConverter::TypeConverter; - /// Converts the given standard `type` to SPIR-V correspondance. + /// Converts the given standard `type` to SPIR-V correspondence. Type convertType(Type type) override; - /// Gets the SPIR-V correspondance for the standard index type. + /// Gets the SPIR-V correspondence for the standard index type. static Type getIndexType(MLIRContext *context); }; @@ -67,12 +67,6 @@ namespace spirv { Value *getBuiltinVariableValue(Operation *op, spirv::BuiltIn builtin, OpBuilder &builder); -/// Legalizes a function as an entry function. -FuncOp lowerAsEntryFunction(FuncOp funcOp, SPIRVTypeConverter &typeConverter, - ConversionPatternRewriter &rewriter, - ArrayRef argABIInfo, - spirv::EntryPointABIAttr entryPointInfo); - /// Attribute name for specifying argument ABI information. StringRef getInterfaceVarABIAttrName(); @@ -89,6 +83,12 @@ StringRef getEntryPointABIAttrName(); EntryPointABIAttr getEntryPointABIAttr(ArrayRef localSize, MLIRContext *context); +/// Sets the InterfaceVarABIAttr and EntryPointABIAttr for a function and its +/// arguments +LogicalResult setABIAttrs(FuncOp funcOp, + spirv::EntryPointABIAttr entryPointInfo, + ArrayRef argABIInfo); + } // namespace spirv } // namespace mlir diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVNonUniformOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVNonUniformOps.td index a37f5b576fd..1b3174c9e9f 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVNonUniformOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVNonUniformOps.td @@ -49,7 +49,7 @@ def SPV_GroupNonUniformBallotOp : SPV_Op<"GroupNonUniformBallot", []> { ### Custom assembly form - ``` {.ebnf} + ``` scope ::= `"Workgroup"` | `"Subgroup"` non-uniform-ballot-op ::= ssa-id `=` `spv.GroupNonUniformBallot` scope ssa-use `:` `vector` `<` 4 `x` `integer-type` `>` diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVOps.td index 7a1be77bcd9..91ea8d7d676 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVOps.td @@ -76,7 +76,7 @@ def SPV_AccessChainOp : SPV_Op<"AccessChain", [NoSideEffect]> { - must be an OpConstant when indexing into a structure. ### Custom assembly form - ``` {.ebnf} + ``` access-chain-op ::= ssa-id `=` `spv.AccessChain` ssa-use `[` ssa-use (',' ssa-use)* `]` `:` pointer-type @@ -144,7 +144,7 @@ def SPV_ControlBarrierOp : SPV_Op<"ControlBarrier", []> { ### Custom assembly form - ``` {.ebnf} + ``` scope ::= `"CrossDevice"` | `"Device"` | `"Workgroup"` | ... memory-semantics ::= `"None"` | `"Acquire"` | "Release"` | ... @@ -190,7 +190,7 @@ def SPV_ExecutionModeOp : SPV_Op<"ExecutionMode", [InModuleScope]> { ### Custom assembly form - ``` {.ebnf} + ``` execution-mode ::= "Invocations" | "SpacingEqual" | @@ -243,7 +243,7 @@ def SPV_LoadOp : SPV_Op<"Load", []> { ### Custom assembly form - ``` {.ebnf} + ``` memory-access ::= `"None"` | `"Volatile"` | `"Aligned", ` integer-literal | `"NonTemporal"` @@ -298,7 +298,7 @@ def SPV_MemoryBarrierOp : SPV_Op<"MemoryBarrier", []> { ### Custom assembly form - ``` {.ebnf} + ``` scope ::= `"CrossDevice"` | `"Device"` | `"Workgroup"` | ... memory-semantics ::= `"None"` | `"Acquire"` | `"Release"` | ... @@ -343,7 +343,7 @@ def SPV_StoreOp : SPV_Op<"Store", []> { ### Custom assembly form - ``` {.ebnf} + ``` store-op ::= `spv.Store ` storage-class ssa-use `, ` ssa-use `, ` (`[` memory-access `]`)? `:` spirv-element-type ``` @@ -391,8 +391,8 @@ def SPV_UndefOp : SPV_Op<"undef", []> { ### Custom assembly form - ``` {.ebnf} - undef-op ::= `spv.undef` `:` sprirv-type + ``` + undef-op ::= `spv.undef` `:` spirv-type ``` For example: @@ -439,7 +439,7 @@ def SPV_VariableOp : SPV_Op<"Variable", []> { ### Custom assembly form - ``` {.ebnf} + ``` variable-op ::= ssa-id `=` `spv.Variable` (`init(` ssa-use `)`)? (`bind(` integer-literal, integer-literal `)`)? (`built_in(` string-literal `)`)? diff --git a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td index 34b386ebc17..d1dacf3d63d 100644 --- a/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td +++ b/third_party/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td @@ -40,7 +40,7 @@ def SPV_AddressOfOp : SPV_Op<"_address_of", [InFunctionScope, NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` spv-address-of-op ::= ssa-id `=` `spv._address_of` symbol-ref-id `:` spirv-pointer-type ``` @@ -89,7 +89,7 @@ def SPV_ConstantOp : SPV_Op<"constant", [NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` spv-constant-op ::= ssa-id `=` `spv.constant` attribute-value (`:` spirv-type)? ``` @@ -160,7 +160,7 @@ def SPV_EntryPointOp : SPV_Op<"EntryPoint", [InModuleScope]> { ### Custom assembly form - ``` {.ebnf} + ``` execution-model ::= "Vertex" | "TesellationControl" | @@ -217,7 +217,7 @@ def SPV_GlobalVariableOp : SPV_Op<"globalVariable", [InModuleScope, Symbol]> { ### Custom assembly form - ``` {.ebnf} + ``` variable-op ::= `spv.globalVariable` spirv-type symbol-ref-id (`initializer(` symbol-ref-id `)`)? (`bind(` integer-literal, integer-literal `)`)? @@ -253,7 +253,9 @@ def SPV_GlobalVariableOp : SPV_Op<"globalVariable", [InModuleScope, Symbol]> { }]>, OpBuilder<[{Builder *builder, OperationState &state, Type type, StringRef name, unsigned descriptorSet, - unsigned binding}]> + unsigned binding}]>, + OpBuilder<[{Builder *builder, OperationState &state, + Type type, StringRef name, spirv::BuiltIn builtin}]> ]; let results = (outs); @@ -293,7 +295,7 @@ def SPV_ModuleOp : SPV_Op<"module", ### Custom assembly form - ``` {.ebnf} + ``` addressing-model ::= `"Logical"` | `"Physical32"` | `"Physical64"` memory-model ::= `"Simple"` | `"GLSL450"` | `"OpenCL"` | `"VulkanKHR"` spv-module-op ::= `spv.module` addressing-model memory-model @@ -329,13 +331,17 @@ def SPV_ModuleOp : SPV_Op<"module", let regions = (region SizedRegion<1>:$body); - let builders = [OpBuilder<"Builder *, OperationState &state">, - OpBuilder<[{Builder *, OperationState &state, - IntegerAttr addressing_model, - IntegerAttr memory_model, - /*optional*/ArrayAttr capabilities = nullptr, - /*optional*/ArrayAttr extensions = nullptr, - /*optional*/ArrayAttr extended_instruction_sets = nullptr}]>]; + let builders = + [OpBuilder<"Builder *, OperationState &state">, + OpBuilder<[{Builder *, OperationState &state, + IntegerAttr addressing_model, + IntegerAttr memory_model}]>, + OpBuilder<[{Builder *, OperationState &state, + spirv::AddressingModel addressing_model, + spirv::MemoryModel memory_model, + /*optional*/ ArrayRef capabilities = {}, + /*optional*/ ArrayRef extensions = {}, + /*optional*/ ArrayAttr extended_instruction_sets = nullptr}]>]; // We need to ensure the block inside the region is properly terminated; // the auto-generated builders do not guarantee that. @@ -389,7 +395,7 @@ def SPV_ReferenceOfOp : SPV_Op<"_reference_of", [NoSideEffect]> { ### Custom assembly form - ``` {.ebnf} + ``` spv-reference-of-op ::= ssa-id `=` `spv._reference_of` symbol-ref-id `:` spirv-scalar-type ``` @@ -430,7 +436,7 @@ def SPV_SpecConstantOp : SPV_Op<"specConstant", [InModuleScope, Symbol]> { ### Custom assembly form - ``` {.ebnf} + ``` spv-spec-constant-op ::= `spv.specConstant` symbol-ref-id `spec_id(` integer `)` `=` attribute-value (`:` spirv-type)? diff --git a/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.h b/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.h index b201917efe4..fcf16c05c33 100644 --- a/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.h +++ b/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.h @@ -42,6 +42,11 @@ class StandardOpsDialect : public Dialect { public: StandardOpsDialect(MLIRContext *context); static StringRef getDialectNamespace() { return "std"; } + + /// Materialize a single constant operation from a given attribute value with + /// the desired resultant type. + Operation *materializeConstant(OpBuilder &builder, Attribute value, Type type, + Location loc) override; }; /// The predicate indicates the type of the comparison to perform: @@ -190,7 +195,7 @@ public: unsigned getSrcMemRefRank() { return getSrcMemRef()->getType().cast().getRank(); } - // Returns the source memerf indices for this DMA operation. + // Returns the source memref indices for this DMA operation. operand_range getSrcIndices() { return {getOperation()->operand_begin() + 1, getOperation()->operand_begin() + 1 + getSrcMemRefRank()}; @@ -263,8 +268,8 @@ public: void print(OpAsmPrinter &p); LogicalResult verify(); - static void getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context); + LogicalResult fold(ArrayRef cstOperands, + SmallVectorImpl &results); bool isStrided() { return getNumOperands() != 1 + getSrcMemRefRank() + 1 + getDstMemRefRank() + @@ -326,8 +331,8 @@ public: static ParseResult parse(OpAsmParser &parser, OperationState &result); void print(OpAsmPrinter &p); - static void getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context); + LogicalResult fold(ArrayRef cstOperands, + SmallVectorImpl &results); }; /// Prints dimension and symbol list. diff --git a/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.td b/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.td index 45d447da1c8..2cba150560c 100644 --- a/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.td +++ b/third_party/mlir/include/mlir/Dialect/StandardOps/Ops.td @@ -659,6 +659,7 @@ def DeallocOp : Std_Op<"dealloc"> { let arguments = (ins AnyMemRef:$memref); let hasCanonicalizer = 1; + let hasFolder = 1; } def DimOp : Std_Op<"dim", [NoSideEffect]> { @@ -691,7 +692,6 @@ def DimOp : Std_Op<"dim", [NoSideEffect]> { }]; let hasFolder = 1; - let hasCanonicalizer = 1; } def DivFOp : FloatArithmeticOp<"divf"> { @@ -834,7 +834,19 @@ def LoadOp : Std_Op<"load"> { operand_range getIndices() { return {operand_begin() + 1, operand_end()}; } }]; - let hasCanonicalizer = 1; + let hasFolder = 1; +} + +def LogOp : FloatUnaryOp<"log"> { + let summary = "base-e logarithm of the specified value"; +} + +def Log10Op : FloatUnaryOp<"log10"> { + let summary = "base-10 logarithm of the specified value"; +} + +def Log2Op : FloatUnaryOp<"log2"> { + let summary = "base-2 logarithm of the specified value"; } def MemRefCastOp : CastOp<"memref_cast"> { @@ -1074,7 +1086,16 @@ def SplatOp : Std_Op<"splat", [NoSideEffect]> { %1 = splat %0 : vector<8xi32> %2 = splat %0 : tensor<4x8xi32> - // TODO: handle broadcast to dynamically shaped tensors. + TODO: Extend this operation to broadcast to dynamically shaped tensors in + the same way dynamically shaped memrefs are handled. + + // Broadcasts %s to a 2-d dynamically shaped tensor, with %m, %n binding + // to the sizes of the two dynamic dimensions. + + %m = "foo"() : () -> (index) + %n = "bar"() : () -> (index) + %t = splat %s [%m, %n] : tensor + }]; let arguments = (ins AnyTypeOf<[AnyInteger, AnyFloat], @@ -1125,7 +1146,7 @@ def StoreOp : Std_Op<"store"> { } }]; - let hasCanonicalizer = 1; + let hasFolder = 1; } def SubFOp : FloatArithmeticOp<"subf"> { @@ -1236,7 +1257,7 @@ def SubViewOp : Std_Op<"subview", [AttrSizedOperandSegments, NoSideEffect]> { : memref (d0 * s1 + d1 * s2 + s0)> to memref<4x4xf32, (d0, d1)[r0, r1, r2] -> (d0 * r1 + d1 * r2 + r0)> - // Note that the subview op does not gaurantee that the result + // Note that the subview op does not guarantee that the result // memref is "inbounds" w.r.t to base memref. It is upto the client // to ensure that the subview is accessed in a manner that is // in-bounds. diff --git a/third_party/mlir/include/mlir/Dialect/Utils/StructuredOpsUtils.h b/third_party/mlir/include/mlir/Dialect/Utils/StructuredOpsUtils.h new file mode 100644 index 00000000000..b7e3990a333 --- /dev/null +++ b/third_party/mlir/include/mlir/Dialect/Utils/StructuredOpsUtils.h @@ -0,0 +1,114 @@ +//===- StructuredOpsUtils.h - Utilities used by structured ops --*- C++ -*-===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// This header file define utilities that operate on standard types and are +// useful across multiple dialects that use structured ops abstractions. These +// abstractions consist of define custom operations that encode and transport +// information about their semantics (e.g. type of iterators like parallel, +// reduction, etc..) as attributes. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_UTILS_STRUCTUREDOPSUTILS_H +#define MLIR_DIALECT_UTILS_STRUCTUREDOPSUTILS_H + +#include "mlir/IR/Attributes.h" +#include "mlir/Support/LLVM.h" +#include "llvm/ADT/StringRef.h" + +namespace mlir { +/// Attribute name for the AffineArrayAttr which encodes the relationship +/// between a structured op iterators' and its operands. +static constexpr StringLiteral getIndexingMapsAttrName() { + return StringLiteral("indexing_maps"); +} + +/// Attribute name for the StrArrayAttr which encodes the type of a structured +/// op's iterators. +static constexpr StringLiteral getIteratorTypesAttrName() { + return StringLiteral("iterator_types"); +} + +/// Attribute name for the IntegerAttr which encodes the number of input buffer +/// arguments. +static constexpr StringLiteral getArgsInAttrName() { + return StringLiteral("args_in"); +} + +/// Attribute name for the IntegerAttr which encodes the number of input buffer +/// arguments. +static constexpr StringLiteral getArgsOutAttrName() { + return StringLiteral("args_out"); +} + +/// Attribute name for the StringAttr which encodes an optional documentation +/// string of the structured op. +static constexpr StringLiteral getDocAttrName() { return StringLiteral("doc"); } + +/// Attribute name for the StrArrayAttr which encodes the SymbolAttr for the +/// MLIR function that implements the body of the structured op. +static constexpr StringLiteral getFunAttrName() { return StringLiteral("fun"); } + +/// Attribute name for the StrArrayAttr which encodes the external library +/// function that implements the structured op. +static constexpr StringLiteral getLibraryCallAttrName() { + return StringLiteral("library_call"); +} + +/// Use to encode that a particular iterator type has parallel semantics. +inline static constexpr StringLiteral getParallelIteratorTypeName() { + return StringLiteral("parallel"); +} + +/// Use to encode that a particular iterator type has reduction semantics. +inline static constexpr StringLiteral getReductionIteratorTypeName() { + return StringLiteral("reduction"); +} + +/// Use to encode that a particular iterator type has window semantics. +inline static constexpr StringLiteral getWindowIteratorTypeName() { + return StringLiteral("window"); +} + +/// Use to encode that a particular iterator type has window semantics. +inline static ArrayRef getAllIteratorTypeNames() { + static StringRef names[3] = {getParallelIteratorTypeName(), + getReductionIteratorTypeName(), + getWindowIteratorTypeName()}; + return llvm::makeArrayRef(names); +} + +/// Returns the iterator of a certain type. +inline unsigned getNumIterators(StringRef name, ArrayAttr iteratorTypes) { + auto names = getAllIteratorTypeNames(); + (void)names; + assert(llvm::is_contained(names, name)); + return llvm::count_if(iteratorTypes, [name](Attribute a) { + return a.cast().getValue() == name; + }); +} + +inline unsigned getNumIterators(ArrayAttr iteratorTypes) { + unsigned res = 0; + for (auto n : getAllIteratorTypeNames()) + res += getNumIterators(n, iteratorTypes); + return res; +} + +} // end namespace mlir + +#endif // MLIR_UTILS_STRUCTUREDOPSUTILS_H diff --git a/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.h b/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.h index 8cb0d8516b4..06672c7ea73 100644 --- a/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.h +++ b/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.h @@ -37,12 +37,21 @@ class VectorOpsDialect : public Dialect { public: VectorOpsDialect(MLIRContext *context); static StringRef getDialectNamespace() { return "vector"; } + + /// Materialize a single constant operation from a given attribute value with + /// the desired resultant type. + Operation *materializeConstant(OpBuilder &builder, Attribute value, Type type, + Location loc) override; }; /// Collect a set of vector-to-vector canonicalization patterns. void populateVectorToVectorCanonicalizationPatterns( OwningRewritePatternList &patterns, MLIRContext *context); +/// Collect a set of vector-to-vector transformation patterns. +void populateVectorToVectorTransformationPatterns( + OwningRewritePatternList &patterns, MLIRContext *context); + #define GET_OP_CLASSES #include "mlir/Dialect/VectorOps/VectorOps.h.inc" diff --git a/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.td b/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.td index 6c2b4e6bb16..50bf581f5a2 100644 --- a/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.td +++ b/third_party/mlir/include/mlir/Dialect/VectorOps/VectorOps.td @@ -127,6 +127,9 @@ def Vector_ContractionOp : %5 = vector.contract #contraction_trait %0, %1, %2, %lhs_mask, %rhs_mask : vector<7x8x16x15xf32>, vector<8x16x7x5xf32> into vector<8x15x8x5xf32> }]; + let builders = [OpBuilder< + "Builder *builder, OperationState &result, Value *lhs, Value *rhs, " + "Value *acc, ArrayAttr indexingMaps, ArrayAttr iteratorTypes">]; let extraClassDeclaration = [{ VectorType getLhsType() { return lhs()->getType().cast(); @@ -148,14 +151,9 @@ def Vector_ContractionOp : VectorType getResultType() { return getResult()->getType().cast(); } - SmallVector getTraitAttrNames(); + ArrayRef getTraitAttrNames(); SmallVector getIndexingMaps(); - static StringRef getReductionIteratorTypeName() { - return "reduction"; - } - static StringRef getParallelIteratorTypeName() { - return "parallel"; - } + static unsigned getAccOperandIndex() { return 2; } // Returns the bounds of each dimension in the iteration space spanned // by the iterator types of this operation. @@ -197,7 +195,7 @@ def Vector_BroadcastOp : ``` The source operand is duplicated over all the missing leading dimensions - and streched over the trailing dimensions where the source has a non-equal + and stretched over the trailing dimensions where the source has a non-equal dimension of 1. These rules imply that any scalar broadcast (k=0) to any shaped vector with the same element type is always legal. @@ -216,6 +214,86 @@ def Vector_BroadcastOp : }]; } +def Vector_ShuffleOp : + Vector_Op<"shuffle", [NoSideEffect, + PredOpTrait<"first operand v1 and result have same element type", + TCresVTEtIsSameAsOpBase<0, 0>>, + PredOpTrait<"second operand v2 and result have same element type", + TCresVTEtIsSameAsOpBase<0, 1>>]>, + Arguments<(ins AnyVector:$v1, AnyVector:$v2, I32ArrayAttr:$mask)>, + Results<(outs AnyVector:$vector)> { + let summary = "shuffle operation"; + let description = [{ + The shuffle operation constructs a permutation (or duplication) of elements + from two input vectors, returning a vector with the same element type as + the input and a length that is the same as the shuffle mask. The two input + vectors must have the same element type, rank, and trailing dimension sizes + and shuffles their values in the leading dimension (which may differ in size) + according to the given mask. The legality rules are: + * the two operands must have the same element type as the result + * the two operands and the result must have the same rank and trailing + dimension sizes, viz. given two k-D operands + v1 : and + v2 : + we have s_i = t_i for all 1 < i <= k + * the mask length equals the leading dimension size of the result + * numbering the input vector indices left to right accross the operands, all + mask values must be within range, viz. given two k-D operands v1 and v2 + above, all mask values are in the range [0,s_1+t_1) + + Examples: + ``` + %0 = vector.shuffle %a, %b[0:i32, 3:i32] + : vector<2xf32>, vector<2xf32> ; yields vector<2xf32> + %1 = vector.shuffle %c, %b[0:i32, 1:i32, 2:i32] + : vector<2x16xf32>, vector<1x16xf32> ; yields vector<3x16xf32> + %2 = vector.shuffle %a, %b[3:i32, 2:i32, 1:i32 : 0:i32] + : vector<2xf32>, vector<2xf32> ; yields vector<4xf32> + + ``` + }]; + let builders = [OpBuilder<"Builder *builder, OperationState &result, Value *v1, Value *v2, ArrayRef">]; + let extraClassDeclaration = [{ + static StringRef getMaskAttrName() { return "mask"; } + VectorType getV1VectorType() { + return v1()->getType().cast(); + } + VectorType getV2VectorType() { + return v2()->getType().cast(); + } + VectorType getVectorType() { + return vector()->getType().cast(); + } + }]; +} + +def Vector_ExtractElementOp : + Vector_Op<"extractelement", [NoSideEffect, + PredOpTrait<"operand and result have same element type", + TCresVTEtIsSameAsOpBase<0, 0>>]>, + Arguments<(ins AnyVector:$vector, Index:$position)>, + Results<(outs AnyType)> { + let summary = "extractelement operation"; + let description = [{ + Takes an 1-D vector and a dynamic index position and extracts the + scalar at that position. Note that this instruction resembles + vector.extract, but is restricted to 1-D vectors and relaxed + to dynamic indices. It is meant to be closer to LLVM's version: + https://llvm.org/docs/LangRef.html#extractelement-instruction + + Example: + ``` + %c = constant 15 : i32 + %1 = vector.extractelement %0[%c : i32]: vector<16xf32> + ``` + }]; + let extraClassDeclaration = [{ + VectorType getVectorType() { + return vector()->getType().cast(); + } + }]; +} + def Vector_ExtractOp : Vector_Op<"extract", [NoSideEffect, PredOpTrait<"operand and result have same element type", @@ -243,6 +321,90 @@ def Vector_ExtractOp : }]; } +def Vector_ExtractSlicesOp : + Vector_Op<"extract_slices", [NoSideEffect]>, + Arguments<(ins AnyVector:$vector, I64ArrayAttr:$sizes, + I64ArrayAttr:$strides)>, + Results<(outs TupleOf<[AnyVector]>)> { + let summary = "vector extract slices operation"; + let description = [{ + Takes an N-d vector and returns a tuple of vector slices of 'vector', + based on 'sizes' and 'strides' parameters. + + The arguments 'sizes' and 'strides' represent a specification for + generating the unrolling of 'vector' shape, which has all slices of shape + 'sizes' except for slices at dimension boundaries when 'vector' dimension + sizes are not a multiple of 'sizes'. + + Each slice is returned at the tuple element index corresponding to the + linear index of the slice w.r.t the unrolling scheme represented by 'sizes'. + Currently, only unit strides are supported. + + Examples: + ``` + %0 = vector.transfer_read ...: vector<4x2xf32> + + %1 = vector.extract_slices %0, [2, 2], [1, 1] + : vector<4x2xf32> into tuple, vector<2x2xf32>> + + // Example with partial slices at dimension boundaries. + %2 = vector.transfer_read ...: vector<4x3xf32> + + %3 = vector.extract_slices %2, [2, 2], [1, 1] + : vector<4x3xf32> into tuple, vector<2x1xf32>, + vector<2x2xf32>, vector<2x1xf32>> + ``` + }]; + let builders = [OpBuilder< + "Builder *builder, OperationState &result, TupleType tupleType, " # + "Value *vector, ArrayRef sizes, " # + "ArrayRef strides">]; + let extraClassDeclaration = [{ + VectorType getSourceVectorType() { + return vector()->getType().cast(); + } + TupleType getResultTupleType() { + return getResult()->getType().cast(); + } + void getSizes(SmallVectorImpl &results); + void getStrides(SmallVectorImpl &results); + static StringRef getSizesAttrName() { return "sizes"; } + static StringRef getStridesAttrName() { return "strides"; } + }]; +} + +def Vector_InsertElementOp : + Vector_Op<"insertelement", [NoSideEffect, + PredOpTrait<"source operand and result have same element type", + TCresVTEtIsSameAsOpBase<0, 0>>, + PredOpTrait<"dest operand and result have same type", + TCresIsSameAsOpBase<0, 1>>]>, + Arguments<(ins AnyType:$source, AnyVector:$dest, Index:$position)>, + Results<(outs AnyVector)> { + let summary = "insertelement operation"; + let description = [{ + Takes a scalar source, an 1-D destination vector and a dynamic index + position and inserts the source into the destination at the proper + position. Note that this instruction resembles vector.insert, but + is restricted to 1-D vectors and relaxed to dynamic indices. It is + meant to be closer to LLVM's version: + https://llvm.org/docs/LangRef.html#insertelement-instruction + + Example: + ``` + %c = constant 15 : i32 + %f = constant 0.0f : f32 + %1 = vector.insertelement %f, %0[%c : i32]: vector<16xf32> + ``` + }]; + let extraClassDeclaration = [{ + Type getSourceType() { return source()->getType(); } + VectorType getDestVectorType() { + return dest()->getType().cast(); + } + }]; +} + def Vector_InsertOp : Vector_Op<"insert", [NoSideEffect, PredOpTrait<"source operand and result have same element type", @@ -277,6 +439,58 @@ def Vector_InsertOp : }]; } +def Vector_InsertSlicesOp : + Vector_Op<"insert_slices", [NoSideEffect]>, + Arguments<(ins TupleOf<[AnyVector]>:$vectors, I64ArrayAttr:$sizes, + I64ArrayAttr:$strides)>, + Results<(outs AnyVector)> { + let summary = "vector insert slices operation"; + let description = [{ + Takes a tuple of vector slices and inserts them into the vector result + according to the 'sizes' and 'strides' parameters. + + The arguments 'sizes' and 'strides' represent a specification for + generating the unrolling of 'vector' shape, which has all slices of shape + 'sizes' except for slices at dimension boundaries when 'vector' dimension + sizes are not a multiple of 'sizes'. + + Each slice in 'vectors' is at the tuple element index corresponding to the + linear index of the slice w.r.t the unrolling scheme represented by 'sizes'. + Currently, only unit strides are supported. + + Examples: + ``` + %0 = vector.extract_slices %0, [2, 2], [1, 1] + : vector<4x2xf32> into tuple, vector<2x2xf32>> + + %1 = vector.insert_slices %0, [2, 2], [1, 1] + : tuple, vector<2x2xf32>> into vector<4x2xf32> + + // Example with partial slices at dimension boundaries. + %3 = vector.extract_slices %2, [2, 2], [1, 1] + : vector<4x3xf32> into tuple, vector<2x1xf32>, + vector<2x2xf32>, vector<2x1xf32>> + + %4 = vector.insert_slices %3, [2, 2], [1, 1] + : tuple, vector<2x1xf32>, + vector<2x2xf32>, vector<2x1xf32>> into vector<4x3xf32> + ``` + }]; + + let extraClassDeclaration = [{ + TupleType getSourceTupleType() { + return vectors()->getType().cast(); + } + VectorType getResultVectorType() { + return getResult()->getType().cast(); + } + void getSizes(SmallVectorImpl &results); + void getStrides(SmallVectorImpl &results); + static StringRef getSizesAttrName() { return "sizes"; } + static StringRef getStridesAttrName() { return "strides"; } + }]; +} + def Vector_InsertStridedSliceOp : Vector_Op<"insert_strided_slice", [NoSideEffect, PredOpTrait<"operand #0 and result have same element type", @@ -400,6 +614,7 @@ def Vector_StridedSliceOp : static StringRef getSizesAttrName() { return "sizes"; } static StringRef getStridesAttrName() { return "strides"; } VectorType getVectorType(){ return vector()->getType().cast(); } + void getOffsets(SmallVectorImpl &results); }]; let hasCanonicalizer = 1; } @@ -433,7 +648,7 @@ def Vector_TransferReadOp : More precisely, let's dive deeper into the permutation_map for the following MLIR: - ```mlir {.mlir} + ```mlir vector.transfer_read %A[%expr1, %expr2, %expr3, %expr4] { permutation_map : (d0,d1,d2,d3) -> (d2,0,d0) } : memref, vector<3x4x5xf32> @@ -493,7 +708,7 @@ def Vector_TransferReadOp : implemented using a warp-shuffle if loop `j` were mapped to `threadIdx.x`. Syntax - ``` {.ebnf} + ``` operation ::= ssa-id `=` `vector.transfer_read` ssa-use-list `{` attribute-entry `} :` memref-type `,` vector-type ``` @@ -564,14 +779,14 @@ def Vector_TransferWriteOp : Syntax: - ``` {.ebnf} + ``` operation ::= `vector.transfer_write` ssa-use-list `{` attribute-entry `} : ` vector-type ', ' memref-type ' ``` Examples: - ```mlir {.mlir} + ```mlir // write vector<16x32x64xf32> into the slice // `%A[%i0, %i1:%i1+32, %i2:%i2+64, %i3:%i3+16]`: for %i0 = 0 to %0 { @@ -610,7 +825,7 @@ def Vector_TypeCastOp : Syntax: - ``` {.ebnf} + ``` operation ::= `vector.type_cast` ssa-use : memref-type to memref-type ``` @@ -649,7 +864,7 @@ def Vector_ConstantMaskOp : are set to '0' or '1', based on whether the element indices are contained within a hyper-rectangular region specified by the 'mask_dim_sizes' array attribute argument. Each element of the 'mask_dim_sizes' array, - specifices an exclusive upper bound [0, mask-dim-size-element-value) + specifies an exclusive upper bound [0, mask-dim-size-element-value) for a unique dimension in the vector result. The conjunction of the ranges define a hyper-rectangular region within which elements values are set to 1 (otherwise element values are set to 0). @@ -705,4 +920,71 @@ def Vector_CreateMaskOp : let hasCanonicalizer = 1; } +def Vector_TupleOp : + Vector_Op<"tuple", [NoSideEffect]>, + Arguments<(ins Variadic:$vectors)>, + Results<(outs TupleOf<[AnyVector]>)> { + let summary = "make tuple of vectors operation"; + let description = [{ + Returns a tuple of its operands 'vectors'. + + Note that this operation is used during the vector op unrolling + transformation and should be removed before lowering to lower-level + dialects. + + + Examples: + ``` + %0 = vector.transfer_read ... : vector<2x2xf32> + %1 = vector.transfer_read ... : vector<2x1xf32> + %2 = vector.transfer_read ... : vector<2x2xf32> + %3 = vector.transfer_read ... : vector<2x1xf32> + + %4 = vector.tuple %0, %1, %2, %3 + : vector<2x2xf32>, vector<2x1xf32>, vector<2x2xf32>, vector<2x1xf32> + + ``` + }]; + + let extraClassDeclaration = [{ + TupleType getResultTupleType() { + return getResult()->getType().cast(); + } + }]; +} + +def Vector_TupleGetOp : + Vector_Op<"tuple_get", [NoSideEffect]>, + Arguments<(ins TupleOf<[AnyVector]>:$vectors, APIntAttr:$index)>, + Results<(outs AnyVector)> { + let summary = "vector tuple get operation"; + let description = [{ + Returns the tuple element of 'vectors' at 'index'. + + Note that this operation is used during the vector op unrolling + transformation and should be removed before lowering to lower-level + dialects. + + Examples: + ``` + %4 = vector.tuple %0, %1, %2, %3 + : vector<2x2xf32>, vector<2x1xf32>, vector<2x2xf32>, vector<2x1xf32>> + + %5 = vector.tuple_get %4, 1 + : tuple, vector<2x1xf32>, + vector<2x2xf32>, vector<2x1xf32>> + ``` + }]; + + let extraClassDeclaration = [{ + VectorType getResultVectorType() { + return getResult()->getType().cast(); + } + unsigned getIndex() { + return getAttrOfType("index").getValue().getZExtValue(); + } + static StringRef getIndexAttrName() { return "index"; } + }]; +} + #endif // VECTOR_OPS diff --git a/third_party/mlir/include/mlir/Dialect/VectorOps/VectorTransformPatterns.td b/third_party/mlir/include/mlir/Dialect/VectorOps/VectorTransformPatterns.td index e71679620d6..86ff9b505d5 100644 --- a/third_party/mlir/include/mlir/Dialect/VectorOps/VectorTransformPatterns.td +++ b/third_party/mlir/include/mlir/Dialect/VectorOps/VectorTransformPatterns.td @@ -19,30 +19,17 @@ // //===----------------------------------------------------------------------===// -#ifndef VECTOR_TRANSFORMS -#define VECTOR_TRANSFORMS +#ifndef VECTOR_TRANSFORM_PATTERNS +#define VECTOR_TRANSFORM_PATTERNS -include "mlir/Dialect/StandardOps/Ops.td" -include "mlir/Dialect/VectorOps/VectorOps.td" +include "mlir/IR/OpBase.td" class HasShape shape> : - CPred<"hasShape($0, {" # StrJoinInt.result # "})">; + CPred<"$0->getType().cast().hasStaticShape({" # + StrJoinInt.result # "})">; class UnrollVectorOp factors> : NativeCodeCall< "unrollSingleResultOpMatchingType($_builder, $0->getDefiningOp(), " # "{" # StrJoinInt.result # "})">; -def : Pat<(AddFOp:$op_results $a, $b), - (UnrollVectorOp<[2, 2]> $op_results, $a, $b), - [(Constraint> $a)]>; - -def : Pat<(AddFOp:$op_results $a, $b), - (UnrollVectorOp<[2, 2]> $op_results, $a, $b), - [(Constraint> $a)]>; - -// TODO(andydavis) Add Constraints on lhs/rhs shapes. -def : Pat<(Vector_ContractionOp:$op_results $a, $b, $c, $masks, $attr0, $attr1), - (UnrollVectorOp<[2, 2, 2]> $op_results, $a, $b, $c), - [(Constraint> $c)]>; - -#endif // VECTOR_TRANSFORMS +#endif // VECTOR_TRANSFORM_PATTERNS diff --git a/third_party/mlir/include/mlir/EDSC/Builders.h b/third_party/mlir/include/mlir/EDSC/Builders.h index 5940f1c244f..740b5cd5c23 100644 --- a/third_party/mlir/include/mlir/EDSC/Builders.h +++ b/third_party/mlir/include/mlir/EDSC/Builders.h @@ -209,7 +209,7 @@ private: /// ``` class AffineLoopNestBuilder { public: - // This entry point accomodates the fact that AffineForOp implicitly uses + // This entry point accommodates the fact that AffineForOp implicitly uses // multiple `lbs` and `ubs` with one single `iv` and `step` to encode `max` // and and `min` constraints respectively. AffineLoopNestBuilder(ValueHandle *iv, ArrayRef lbs, @@ -529,6 +529,18 @@ ValueHandle operator>(ValueHandle lhs, ValueHandle rhs); ValueHandle operator>=(ValueHandle lhs, ValueHandle rhs); } // namespace op + +/// Entry point to build multiple ValueHandle from a `Container` of Value* or +/// Type. +template +inline SmallVector makeValueHandles(Container values) { + SmallVector res; + res.reserve(values.size()); + for (auto v : values) + res.push_back(ValueHandle(v)); + return res; +} + } // namespace edsc } // namespace mlir diff --git a/third_party/mlir/include/mlir/EDSC/CMakeLists.txt b/third_party/mlir/include/mlir/EDSC/CMakeLists.txt deleted file mode 100644 index 0b6f249ae2f..00000000000 --- a/third_party/mlir/include/mlir/EDSC/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -set(LLVM_TARGET_DEFINITIONS "${MLIR_SOURCE_DIR}/test/mlir-tblgen/reference-impl.td") -mlir_tablegen("reference-impl.inc" -gen-reference-implementations) -add_public_tablegen_target(MLIRReferenceImplementationTestGen) diff --git a/third_party/mlir/include/mlir/EDSC/Intrinsics.h b/third_party/mlir/include/mlir/EDSC/Intrinsics.h index 468cc1c4240..6dbb3432bf6 100644 --- a/third_party/mlir/include/mlir/EDSC/Intrinsics.h +++ b/third_party/mlir/include/mlir/EDSC/Intrinsics.h @@ -67,8 +67,10 @@ inline SmallVector makeIndexHandles(unsigned rank) { return SmallVector(rank); } +/// Entry point to build multiple ValueHandle* from a mutable list `ivs` of T. +template inline SmallVector -makeIndexHandlePointers(MutableArrayRef ivs) { +makeHandlePointers(MutableArrayRef ivs) { SmallVector pivs; pivs.reserve(ivs.size()); for (auto &iv : ivs) { @@ -152,22 +154,22 @@ template struct ValueBuilder : public ValueHandle { /// Folder-based template - ValueBuilder(OperationFolder &folder, Args... args) + ValueBuilder(OperationFolder *folder, Args... args) : ValueHandle(ValueHandle::create(folder, detail::unpack(args)...)) {} - ValueBuilder(OperationFolder &folder, ArrayRef vs) + ValueBuilder(OperationFolder *folder, ArrayRef vs) : ValueBuilder(ValueBuilder::create(folder, detail::unpack(vs))) {} template - ValueBuilder(OperationFolder &folder, ArrayRef vs, Args... args) + ValueBuilder(OperationFolder *folder, ArrayRef vs, Args... args) : ValueHandle(ValueHandle::create(folder, detail::unpack(vs), detail::unpack(args)...)) {} template - ValueBuilder(OperationFolder &folder, T t, ArrayRef vs, + ValueBuilder(OperationFolder *folder, T t, ArrayRef vs, Args... args) : ValueHandle(ValueHandle::create(folder, detail::unpack(t), detail::unpack(vs), detail::unpack(args)...)) {} template - ValueBuilder(OperationFolder &folder, T1 t1, T2 t2, ArrayRef vs, + ValueBuilder(OperationFolder *folder, T1 t1, T2 t2, ArrayRef vs, Args... args) : ValueHandle(ValueHandle::create( folder, detail::unpack(t1), detail::unpack(t2), detail::unpack(vs), @@ -198,6 +200,7 @@ template struct OperationBuilder : public OperationHandle { OperationBuilder() : OperationHandle(OperationHandle::create()) {} }; +using addf = ValueBuilder; using affine_apply = ValueBuilder; using affine_if = OperationBuilder; using affine_load = ValueBuilder; @@ -210,11 +213,14 @@ using constant_int = ValueBuilder; using dealloc = OperationBuilder; using dim = ValueBuilder; using muli = ValueBuilder; +using mulf = ValueBuilder; +using memref_cast = ValueBuilder; using ret = OperationBuilder; using select = ValueBuilder; using std_load = ValueBuilder; using std_store = OperationBuilder; using subi = ValueBuilder; +using tanh = ValueBuilder; using view = ValueBuilder; /// Branches into the mlir::Block* captured by BlockHandle `b` with `operands`. diff --git a/third_party/mlir/include/mlir/IR/AffineExpr.h b/third_party/mlir/include/mlir/IR/AffineExpr.h index cca7eac536f..37e7592697c 100644 --- a/third_party/mlir/include/mlir/IR/AffineExpr.h +++ b/third_party/mlir/include/mlir/IR/AffineExpr.h @@ -114,8 +114,9 @@ public: /// floordiv, ceildiv, and mod is only allowed w.r.t constants. bool isPureAffine() const; - /// Returns the greatest known integral divisor of this affine expression. - uint64_t getLargestKnownDivisor() const; + /// Returns the greatest known integral divisor of this affine expression. The + /// result is always positive. + int64_t getLargestKnownDivisor() const; /// Return true if the affine expression is a multiple of 'factor'. bool isMultipleOf(int64_t factor) const; @@ -285,6 +286,23 @@ bool getFlattenedAffineExprs( bool getFlattenedAffineExprs( IntegerSet set, std::vector> *flattenedExprs); +namespace detail { +template void bindDims(MLIRContext *ctx) {} + +template +void bindDims(MLIRContext *ctx, AffineExprTy &e, AffineExprTy2 &... exprs) { + e = getAffineDimExpr(N, ctx); + bindDims(ctx, exprs...); +} +} // namespace detail + +/// Bind a list of AffineExpr references to DimExpr at positions: +/// [0 .. sizeof...(exprs)] +template +void bindDims(MLIRContext *ctx, AffineExprTy &... exprs) { + detail::bindDims<0>(ctx, exprs...); +} + } // namespace mlir namespace llvm { diff --git a/third_party/mlir/include/mlir/IR/Attributes.h b/third_party/mlir/include/mlir/IR/Attributes.h index 3968d44dd37..59df75dc483 100644 --- a/third_party/mlir/include/mlir/IR/Attributes.h +++ b/third_party/mlir/include/mlir/IR/Attributes.h @@ -641,12 +641,12 @@ protected: /// Return the current index for this iterator, adjusted for the case of a /// splat. ptrdiff_t getDataIndex() const { - bool isSplat = this->object.getInt(); + bool isSplat = this->base.getInt(); return isSplat ? 0 : this->index; } - /// Return the data object pointer. - const char *getData() const { return this->object.getPointer(); } + /// Return the data base pointer. + const char *getData() const { return this->base.getPointer(); } }; } // namespace detail diff --git a/third_party/mlir/include/mlir/IR/Block.h b/third_party/mlir/include/mlir/IR/Block.h index f01f1915d44..2ef7bf392f4 100644 --- a/third_party/mlir/include/mlir/IR/Block.h +++ b/third_party/mlir/include/mlir/IR/Block.h @@ -22,60 +22,10 @@ #ifndef MLIR_IR_BLOCK_H #define MLIR_IR_BLOCK_H -#include "mlir/IR/Value.h" +#include "mlir/IR/BlockSupport.h" #include "mlir/IR/Visitors.h" -#include "llvm/ADT/PointerUnion.h" -#include "llvm/ADT/ilist.h" -#include "llvm/ADT/ilist_node.h" - -//===----------------------------------------------------------------------===// -// ilist_traits for Operation -//===----------------------------------------------------------------------===// - -namespace llvm { -namespace ilist_detail { -// Explicitly define the node access for the operation list so that we can -// break the dependence on the Operation class in this header. This allows for -// operations to have trailing Regions without a circular include -// dependence. -template <> -struct SpecificNodeAccess< - typename compute_node_options<::mlir::Operation>::type> : NodeAccess { -protected: - using OptionsT = typename compute_node_options::type; - using pointer = typename OptionsT::pointer; - using const_pointer = typename OptionsT::const_pointer; - using node_type = ilist_node_impl; - - static node_type *getNodePtr(pointer N); - static const node_type *getNodePtr(const_pointer N); - - static pointer getValuePtr(node_type *N); - static const_pointer getValuePtr(const node_type *N); -}; -} // end namespace ilist_detail - -template <> struct ilist_traits<::mlir::Operation> { - using Operation = ::mlir::Operation; - using op_iterator = simple_ilist::iterator; - - static void deleteNode(Operation *op); - void addNodeToList(Operation *op); - void removeNodeFromList(Operation *op); - void transferNodesFromList(ilist_traits &otherList, - op_iterator first, op_iterator last); - -private: - mlir::Block *getContainingBlock(); -}; -} // end namespace llvm namespace mlir { -using BlockOperand = IROperandImpl; - -class PredecessorIterator; -class SuccessorIterator; - /// `Block` represents an ordered list of `Operation`s. class Block : public IRObjectWithUseList, public llvm::ilist_node_with_parent { @@ -106,10 +56,14 @@ public: /// Return if this block is the entry block in the parent region. bool isEntryBlock(); - /// Insert this block (which must not already be in a function) right before + /// Insert this block (which must not already be in a region) right before /// the specified block. void insertBefore(Block *block); + /// Unlink this block from its current region and insert it right before the + /// specific block. + void moveBefore(Block *block); + /// Unlink this Block from its parent region and delete it. void erase(); @@ -272,9 +226,13 @@ public: // Predecessor iteration. using pred_iterator = PredecessorIterator; - pred_iterator pred_begin(); - pred_iterator pred_end(); - llvm::iterator_range getPredecessors(); + pred_iterator pred_begin() { + return pred_iterator((BlockOperand *)getFirstUse()); + } + pred_iterator pred_end() { return pred_iterator(nullptr); } + llvm::iterator_range getPredecessors() { + return {pred_begin(), pred_end()}; + } /// Return true if this block has no predecessors. bool hasNoPredecessors(); @@ -292,10 +250,10 @@ public: Block *getSuccessor(unsigned i); // Successor iteration. - using succ_iterator = SuccessorIterator; - succ_iterator succ_begin(); - succ_iterator succ_end(); - llvm::iterator_range getSuccessors(); + using succ_iterator = SuccessorRange::iterator; + succ_iterator succ_begin() { return getSuccessors().begin(); } + succ_iterator succ_end() { return getSuccessors().end(); } + SuccessorRange getSuccessors() { return SuccessorRange(this); } //===--------------------------------------------------------------------===// // Operation Walkers @@ -381,105 +339,6 @@ private: friend struct llvm::ilist_traits; }; - -} // end namespace mlir - -//===----------------------------------------------------------------------===// -// ilist_traits for Block -//===----------------------------------------------------------------------===// - -namespace llvm { - -template <> -struct ilist_traits<::mlir::Block> : public ilist_alloc_traits<::mlir::Block> { - using Block = ::mlir::Block; - using block_iterator = simple_ilist<::mlir::Block>::iterator; - - void addNodeToList(Block *block); - void removeNodeFromList(Block *block); - void transferNodesFromList(ilist_traits &otherList, - block_iterator first, block_iterator last); - -private: - mlir::Region *getParentRegion(); -}; -} // end namespace llvm - -namespace mlir { -//===----------------------------------------------------------------------===// -// Predecessors -//===----------------------------------------------------------------------===// - -/// Implement a predecessor iterator for blocks. This works by walking the use -/// lists of the blocks. The entries on this list are the BlockOperands that -/// are embedded into terminator operations. From the operand, we can get the -/// terminator that contains it, and its parent block is the predecessor. -class PredecessorIterator final - : public llvm::mapped_iterator, - Block *(*)(BlockOperand &)> { - static Block *unwrap(BlockOperand &value); - -public: - using reference = Block *; - - /// Initializes the operand type iterator to the specified operand iterator. - PredecessorIterator(ValueUseIterator it) - : llvm::mapped_iterator, - Block *(*)(BlockOperand &)>(it, &unwrap) {} - explicit PredecessorIterator(BlockOperand *operand) - : PredecessorIterator(ValueUseIterator(operand)) {} - - /// Get the successor number in the predecessor terminator. - unsigned getSuccessorIndex() const; -}; - -inline auto Block::pred_begin() -> pred_iterator { - return pred_iterator((BlockOperand *)getFirstUse()); -} - -inline auto Block::pred_end() -> pred_iterator { - return pred_iterator(nullptr); -} - -inline auto Block::getPredecessors() -> llvm::iterator_range { - return {pred_begin(), pred_end()}; -} - -//===----------------------------------------------------------------------===// -// Successors -//===----------------------------------------------------------------------===// - -/// This template implements the successor iterators for Block. -class SuccessorIterator final - : public indexed_accessor_iterator { -public: - /// Initializes the result iterator to the specified index. - SuccessorIterator(Block *object, unsigned index) - : indexed_accessor_iterator(object, index) {} - - SuccessorIterator(const SuccessorIterator &other) - : SuccessorIterator(other.object, other.index) {} - - Block *operator*() const { return this->object->getSuccessor(this->index); } - - /// Get the successor number in the terminator. - unsigned getSuccessorIndex() const { return this->index; } -}; - -inline auto Block::succ_begin() -> succ_iterator { - return succ_iterator(this, 0); -} - -inline auto Block::succ_end() -> succ_iterator { - return succ_iterator(this, getNumSuccessors()); -} - -inline auto Block::getSuccessors() -> llvm::iterator_range { - return {succ_begin(), succ_end()}; -} - } // end namespace mlir #endif // MLIR_IR_BLOCK_H diff --git a/third_party/mlir/include/mlir/IR/BlockSupport.h b/third_party/mlir/include/mlir/IR/BlockSupport.h new file mode 100644 index 00000000000..fd30c36aaa3 --- /dev/null +++ b/third_party/mlir/include/mlir/IR/BlockSupport.h @@ -0,0 +1,152 @@ +//===- BlockSupport.h -------------------------------------------*- C++ -*-===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// This file defines a number of support types for the Block class. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_IR_BLOCK_SUPPORT_H +#define MLIR_IR_BLOCK_SUPPORT_H + +#include "mlir/IR/Value.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/ilist.h" +#include "llvm/ADT/ilist_node.h" + +namespace mlir { +class Block; + +using BlockOperand = IROperandImpl; + +//===----------------------------------------------------------------------===// +// Predecessors +//===----------------------------------------------------------------------===// + +/// Implement a predecessor iterator for blocks. This works by walking the use +/// lists of the blocks. The entries on this list are the BlockOperands that +/// are embedded into terminator operations. From the operand, we can get the +/// terminator that contains it, and its parent block is the predecessor. +class PredecessorIterator final + : public llvm::mapped_iterator, + Block *(*)(BlockOperand &)> { + static Block *unwrap(BlockOperand &value); + +public: + using reference = Block *; + + /// Initializes the operand type iterator to the specified operand iterator. + PredecessorIterator(ValueUseIterator it) + : llvm::mapped_iterator, + Block *(*)(BlockOperand &)>(it, &unwrap) {} + explicit PredecessorIterator(BlockOperand *operand) + : PredecessorIterator(ValueUseIterator(operand)) {} + + /// Get the successor number in the predecessor terminator. + unsigned getSuccessorIndex() const; +}; + +//===----------------------------------------------------------------------===// +// Successors +//===----------------------------------------------------------------------===// + +/// This class implements the successor iterators for Block. +class SuccessorRange final + : public detail::indexed_accessor_range_base { +public: + using RangeBaseT::RangeBaseT; + SuccessorRange(Block *block); + +private: + /// See `detail::indexed_accessor_range_base` for details. + static BlockOperand *offset_base(BlockOperand *object, ptrdiff_t index) { + return object + index; + } + /// See `detail::indexed_accessor_range_base` for details. + static Block *dereference_iterator(BlockOperand *object, ptrdiff_t index) { + return object[index].get(); + } + + /// Allow access to `offset_base` and `dereference_iterator`. + friend RangeBaseT; +}; + +} // end namespace mlir + +namespace llvm { + +//===----------------------------------------------------------------------===// +// ilist_traits for Operation +//===----------------------------------------------------------------------===// + +namespace ilist_detail { +// Explicitly define the node access for the operation list so that we can +// break the dependence on the Operation class in this header. This allows for +// operations to have trailing Regions without a circular include +// dependence. +template <> +struct SpecificNodeAccess< + typename compute_node_options<::mlir::Operation>::type> : NodeAccess { +protected: + using OptionsT = typename compute_node_options::type; + using pointer = typename OptionsT::pointer; + using const_pointer = typename OptionsT::const_pointer; + using node_type = ilist_node_impl; + + static node_type *getNodePtr(pointer N); + static const node_type *getNodePtr(const_pointer N); + + static pointer getValuePtr(node_type *N); + static const_pointer getValuePtr(const node_type *N); +}; +} // end namespace ilist_detail + +template <> struct ilist_traits<::mlir::Operation> { + using Operation = ::mlir::Operation; + using op_iterator = simple_ilist::iterator; + + static void deleteNode(Operation *op); + void addNodeToList(Operation *op); + void removeNodeFromList(Operation *op); + void transferNodesFromList(ilist_traits &otherList, + op_iterator first, op_iterator last); + +private: + mlir::Block *getContainingBlock(); +}; + +//===----------------------------------------------------------------------===// +// ilist_traits for Block +//===----------------------------------------------------------------------===// + +template <> +struct ilist_traits<::mlir::Block> : public ilist_alloc_traits<::mlir::Block> { + using Block = ::mlir::Block; + using block_iterator = simple_ilist<::mlir::Block>::iterator; + + void addNodeToList(Block *block); + void removeNodeFromList(Block *block); + void transferNodesFromList(ilist_traits &otherList, + block_iterator first, block_iterator last); + +private: + mlir::Region *getParentRegion(); +}; + +} // end namespace llvm + +#endif // MLIR_IR_BLOCK_SUPPORT_H diff --git a/third_party/mlir/include/mlir/IR/Builders.h b/third_party/mlir/include/mlir/IR/Builders.h index c5ed7b16b56..766902fabfa 100644 --- a/third_party/mlir/include/mlir/IR/Builders.h +++ b/third_party/mlir/include/mlir/IR/Builders.h @@ -281,6 +281,9 @@ public: /// Returns the current insertion point of the builder. Block::iterator getInsertionPoint() const { return insertPoint; } + /// Insert the given operation at the current insertion point and return it. + virtual Operation *insert(Operation *op); + /// Add new block and set the insertion point to the end of it. The block is /// inserted at the provided insertion point of 'parent'. Block *createBlock(Region *parent, Region::iterator insertPt = {}); @@ -293,7 +296,7 @@ public: Block *getBlock() const { return block; } /// Creates an operation given the fields represented as an OperationState. - virtual Operation *createOperation(const OperationState &state); + Operation *createOperation(const OperationState &state); /// Create an operation of specific op type at the current insertion point. template @@ -312,8 +315,17 @@ public: template void createOrFold(SmallVectorImpl &results, Location location, Args &&... args) { - auto op = create(location, std::forward(args)...); - tryFold(op.getOperation(), results); + // Create the operation without using 'createOperation' as we don't want to + // insert it yet. + OperationState state(location, OpTy::getOperationName()); + OpTy::build(this, state, std::forward(args)...); + Operation *op = Operation::create(state); + + // Fold the operation. If successful destroy it, otherwise insert it. + if (succeeded(tryFold(op, results))) + op->destroy(); + else + insert(op); } /// Overload to create or fold a single result operation. @@ -340,44 +352,35 @@ public: return op; } + /// Attempts to fold the given operation and places new results within + /// 'results'. Returns success if the operation was folded, failure otherwise. + /// Note: This function does not erase the operation on a successful fold. + LogicalResult tryFold(Operation *op, SmallVectorImpl &results); + /// Creates a deep copy of the specified operation, remapping any operands /// that use values outside of the operation using the map that is provided /// ( leaving them alone if no entry is present). Replaces references to /// cloned sub-operations to the corresponding operation that is copied, /// and adds those mappings to the map. Operation *clone(Operation &op, BlockAndValueMapping &mapper) { - Operation *cloneOp = op.clone(mapper); - insert(cloneOp); - return cloneOp; - } - Operation *clone(Operation &op) { - Operation *cloneOp = op.clone(); - insert(cloneOp); - return cloneOp; + return insert(op.clone(mapper)); } + Operation *clone(Operation &op) { return insert(op.clone()); } /// Creates a deep copy of this operation but keep the operation regions /// empty. Operands are remapped using `mapper` (if present), and `mapper` is /// updated to contain the results. Operation *cloneWithoutRegions(Operation &op, BlockAndValueMapping &mapper) { - Operation *cloneOp = op.cloneWithoutRegions(mapper); - insert(cloneOp); - return cloneOp; + return insert(op.cloneWithoutRegions(mapper)); } Operation *cloneWithoutRegions(Operation &op) { - Operation *cloneOp = op.cloneWithoutRegions(); - insert(cloneOp); - return cloneOp; + return insert(op.cloneWithoutRegions()); + } + template OpT cloneWithoutRegions(OpT op) { + return cast(cloneWithoutRegions(*op.getOperation())); } private: - /// Attempts to fold the given operation and places new results within - /// 'results'. - void tryFold(Operation *op, SmallVectorImpl &results); - - /// Insert the given operation at the current insertion point. - void insert(Operation *op); - Block *block = nullptr; Block::iterator insertPoint; }; diff --git a/third_party/mlir/include/mlir/IR/Dialect.h b/third_party/mlir/include/mlir/IR/Dialect.h index 4880fd0ca18..a1855e797e8 100644 --- a/third_party/mlir/include/mlir/IR/Dialect.h +++ b/third_party/mlir/include/mlir/IR/Dialect.h @@ -308,7 +308,7 @@ template void registerDialect() { }); } -/// DialectRegistration provides a global initialiser that registers a Dialect +/// DialectRegistration provides a global initializer that registers a Dialect /// allocation routine. /// /// Usage: diff --git a/third_party/mlir/include/mlir/IR/DialectHooks.h b/third_party/mlir/include/mlir/IR/DialectHooks.h index f368988b5b4..c51fafb6180 100644 --- a/third_party/mlir/include/mlir/IR/DialectHooks.h +++ b/third_party/mlir/include/mlir/IR/DialectHooks.h @@ -50,7 +50,7 @@ public: /// based on information coming from DialectHooksRegistration. void registerDialectHooksSetter(const DialectHooksSetter &function); -/// DialectHooksRegistration provides a global initialiser that registers +/// DialectHooksRegistration provides a global initializer that registers /// a dialect hooks setter routine. /// Usage: /// diff --git a/third_party/mlir/include/mlir/IR/DialectSymbolRegistry.def b/third_party/mlir/include/mlir/IR/DialectSymbolRegistry.def index 29671ea9bfe..c1056bd4da0 100644 --- a/third_party/mlir/include/mlir/IR/DialectSymbolRegistry.def +++ b/third_party/mlir/include/mlir/IR/DialectSymbolRegistry.def @@ -32,6 +32,7 @@ DEFINE_SYM_KIND_RANGE(FIR) // Flang Fortran IR Dialect DEFINE_SYM_KIND_RANGE(OPENMP) // OpenMP IR Dialect DEFINE_SYM_KIND_RANGE(TOY) // Toy language (tutorial) Dialect DEFINE_SYM_KIND_RANGE(SPIRV) // SPIR-V dialect +DEFINE_SYM_KIND_RANGE(XLA_HLO) // XLA HLO dialect // The following ranges are reserved for experimenting with MLIR dialects in a // private context without having to register them here. diff --git a/third_party/mlir/include/mlir/IR/Matchers.h b/third_party/mlir/include/mlir/IR/Matchers.h index 99a33b624bd..1261916dae2 100644 --- a/third_party/mlir/include/mlir/IR/Matchers.h +++ b/third_party/mlir/include/mlir/IR/Matchers.h @@ -261,8 +261,8 @@ auto m_Op(Matchers... matchers) { } namespace matchers { -inline auto m_any() { return detail::AnyValueMatcher(); } -inline auto m_val(Value *v) { return detail::PatternMatcherValue(v); } +inline auto m_Any() { return detail::AnyValueMatcher(); } +inline auto m_Val(Value *v) { return detail::PatternMatcherValue(v); } } // namespace matchers } // end namespace mlir diff --git a/third_party/mlir/include/mlir/IR/OpImplementation.h b/third_party/mlir/include/mlir/IR/OpImplementation.h index 3052f797ab6..05beaeaee59 100644 --- a/third_party/mlir/include/mlir/IR/OpImplementation.h +++ b/third_party/mlir/include/mlir/IR/OpImplementation.h @@ -154,6 +154,18 @@ inline OpAsmPrinter &operator<<(OpAsmPrinter &p, Value &value) { p.printOperand(&value); return p; } +inline OpAsmPrinter &operator<<(OpAsmPrinter &p, Value *value) { + return p << *value; +} + +template ::value && + !std::is_convertible::value, + T>::type * = nullptr> +inline OpAsmPrinter &operator<<(OpAsmPrinter &p, const T &values) { + p.printOperands(values); + return p; +} inline OpAsmPrinter &operator<<(OpAsmPrinter &p, Type type) { p.printType(type); @@ -170,14 +182,29 @@ inline OpAsmPrinter &operator<<(OpAsmPrinter &p, Attribute attr) { // FunctionType with the Type version above, not have it match this. template ::value && + !std::is_convertible::value && !std::is_convertible::value && - !std::is_convertible::value, + !std::is_convertible::value && + !std::is_convertible::value && + !llvm::is_one_of::value, T>::type * = nullptr> inline OpAsmPrinter &operator<<(OpAsmPrinter &p, const T &other) { p.getStream() << other; return p; } +inline OpAsmPrinter &operator<<(OpAsmPrinter &p, bool value) { + return p << (value ? StringRef("true") : "false"); +} + +template +inline OpAsmPrinter & +operator<<(OpAsmPrinter &p, + const iterator_range> &types) { + interleaveComma(types, p); + return p; +} + //===----------------------------------------------------------------------===// // OpAsmParser //===----------------------------------------------------------------------===// diff --git a/third_party/mlir/include/mlir/IR/Operation.h b/third_party/mlir/include/mlir/IR/Operation.h index f2c94bc539c..ac78647e4d3 100644 --- a/third_party/mlir/include/mlir/IR/Operation.h +++ b/third_party/mlir/include/mlir/IR/Operation.h @@ -29,15 +29,6 @@ #include "llvm/ADT/Twine.h" namespace mlir { -class BlockAndValueMapping; -class Location; -class MLIRContext; -class OperandIterator; -class OperandTypeIterator; -struct OperationState; -class ResultIterator; -class ResultTypeIterator; - /// Terminator operations can have Block operands to represent successors. using BlockOperand = IROperandImpl; @@ -71,13 +62,11 @@ public: static Operation *create(const OperationState &state); /// Create a new Operation with the specific fields. - static Operation *create(Location location, OperationName name, - ArrayRef resultTypes, - ArrayRef operands, - NamedAttributeList attributes, - ArrayRef successors = {}, - ArrayRef> regions = {}, - bool resizableOperandList = false); + static Operation * + create(Location location, OperationName name, ArrayRef resultTypes, + ArrayRef operands, NamedAttributeList attributes, + ArrayRef successors = {}, RegionRange regions = {}, + bool resizableOperandList = false); /// The name of an operation is the key identifier for it. OperationName getName() { return name; } @@ -232,14 +221,14 @@ public: } // Support operand iteration. - using operand_iterator = OperandIterator; - using operand_range = llvm::iterator_range; + using operand_range = OperandRange; + using operand_iterator = operand_range::iterator; - operand_iterator operand_begin(); - operand_iterator operand_end(); + operand_iterator operand_begin() { return getOperands().begin(); } + operand_iterator operand_end() { return getOperands().end(); } /// Returns an iterator on the underlying Value's (Value *). - operand_range getOperands(); + operand_range getOperands() { return operand_range(this); } /// Erase the operand at position `idx`. void eraseOperand(unsigned idx) { getOperandStorage().eraseOperand(idx); } @@ -251,11 +240,11 @@ public: OpOperand &getOpOperand(unsigned idx) { return getOpOperands()[idx]; } // Support operand type iteration. - using operand_type_iterator = OperandTypeIterator; - using operand_type_range = llvm::iterator_range; - operand_type_iterator operand_type_begin(); - operand_type_iterator operand_type_end(); - operand_type_range getOperandTypes(); + using operand_type_iterator = operand_range::type_iterator; + using operand_type_range = iterator_range; + operand_type_iterator operand_type_begin() { return operand_begin(); } + operand_type_iterator operand_type_end() { return operand_end(); } + operand_type_range getOperandTypes() { return getOperands().getTypes(); } //===--------------------------------------------------------------------===// // Results @@ -268,14 +257,13 @@ public: Value *getResult(unsigned idx) { return &getOpResult(idx); } - // Support result iteration. - using result_iterator = ResultIterator; - using result_range = llvm::iterator_range; + /// Support result iteration. + using result_range = ResultRange; + using result_iterator = result_range::iterator; - result_iterator result_begin(); - result_iterator result_end(); - - result_range getResults(); + result_iterator result_begin() { return getResults().begin(); } + result_iterator result_end() { return getResults().end(); } + result_range getResults() { return result_range(this); } MutableArrayRef getOpResults() { return {getTrailingObjects(), numResults}; @@ -283,12 +271,12 @@ public: OpResult &getOpResult(unsigned idx) { return getOpResults()[idx]; } - // Support result type iteration. - using result_type_iterator = ResultTypeIterator; - using result_type_range = llvm::iterator_range; - result_type_iterator result_type_begin(); - result_type_iterator result_type_end(); - result_type_range getResultTypes(); + /// Support result type iteration. + using result_type_iterator = result_range::type_iterator; + using result_type_range = iterator_range; + result_type_iterator result_type_begin() { return result_begin(); } + result_type_iterator result_type_end() { return result_end(); } + result_type_range getResultTypes() { return getResults().getTypes(); } //===--------------------------------------------------------------------===// // Attributes @@ -659,91 +647,6 @@ inline raw_ostream &operator<<(raw_ostream &os, Operation &op) { return os; } -/// This class implements the const/non-const operand iterators for the -/// Operation class in terms of getOperand(idx). -class OperandIterator final - : public indexed_accessor_iterator { -public: - /// Initializes the operand iterator to the specified operand index. - OperandIterator(Operation *object, unsigned index) - : indexed_accessor_iterator(object, index) {} - - Value *operator*() const { return this->object->getOperand(this->index); } -}; - -/// This class implements the operand type iterators for the Operation -/// class in terms of operand_iterator->getType(). -class OperandTypeIterator final - : public llvm::mapped_iterator { - static Type unwrap(Value *value) { return value->getType(); } - -public: - using reference = Type; - - /// Provide a const deference method. - Type operator*() const { return unwrap(*I); } - - /// Initializes the operand type iterator to the specified operand iterator. - OperandTypeIterator(OperandIterator it) - : llvm::mapped_iterator(it, &unwrap) { - } -}; - -// Implement the inline operand iterator methods. -inline auto Operation::operand_begin() -> operand_iterator { - return operand_iterator(this, 0); -} - -inline auto Operation::operand_end() -> operand_iterator { - return operand_iterator(this, getNumOperands()); -} - -inline auto Operation::getOperands() -> operand_range { - return {operand_begin(), operand_end()}; -} - -inline auto Operation::operand_type_begin() -> operand_type_iterator { - return operand_type_iterator(operand_begin()); -} - -inline auto Operation::operand_type_end() -> operand_type_iterator { - return operand_type_iterator(operand_end()); -} - -inline auto Operation::getOperandTypes() -> operand_type_range { - return {operand_type_begin(), operand_type_end()}; -} - -/// This class implements the result iterators for the Operation class -/// in terms of getResult(idx). -class ResultIterator final - : public indexed_accessor_iterator { -public: - /// Initializes the result iterator to the specified index. - ResultIterator(Operation *object, unsigned index) - : indexed_accessor_iterator(object, index) {} - - Value *operator*() const { return this->object->getResult(this->index); } -}; - -/// This class implements the result type iterators for the Operation -/// class in terms of result_iterator->getType(). -class ResultTypeIterator final - : public llvm::mapped_iterator { - static Type unwrap(Value *value) { return value->getType(); } - -public: - using reference = Type; - - /// Initializes the result type iterator to the specified result iterator. - ResultTypeIterator(ResultIterator it) - : llvm::mapped_iterator(it, &unwrap) {} -}; - /// This class implements use iterator for the Operation. This iterates over all /// uses of all results of an Operation. class UseIterator final @@ -770,102 +673,6 @@ private: /// The use of the result. Value::use_iterator use; }; - -// Implement the inline result iterator methods. -inline auto Operation::result_begin() -> result_iterator { - return result_iterator(this, 0); -} - -inline auto Operation::result_end() -> result_iterator { - return result_iterator(this, getNumResults()); -} - -inline auto Operation::getResults() -> llvm::iterator_range { - return {result_begin(), result_end()}; -} - -inline auto Operation::result_type_begin() -> result_type_iterator { - return result_type_iterator(result_begin()); -} - -inline auto Operation::result_type_end() -> result_type_iterator { - return result_type_iterator(result_end()); -} - -inline auto Operation::getResultTypes() -> result_type_range { - return {result_type_begin(), result_type_end()}; -} - -/// This class provides an abstraction over the different types of ranges over -/// Value*s. In many cases, this prevents the need to explicitly materialize a -/// SmallVector/std::vector. This class should be used in places that are not -/// suitable for a more derived type(e.g. ArrayRef) or a template range -/// parameter. -class ValueRange { - /// The type representing the owner of this range. This is either a list of - /// values, operands, or results. - using OwnerT = llvm::PointerUnion; - -public: - ValueRange(const ValueRange &) = default; - ValueRange(ValueRange &&) = default; - ValueRange &operator=(const ValueRange &) = default; - - template , Arg>::value && - !std::is_convertible::value>> - ValueRange(Arg &&arg) - : ValueRange(ArrayRef(std::forward(arg))) {} - ValueRange(Value *const &value) : ValueRange(&value, /*count=*/1) {} - ValueRange(const std::initializer_list &values) - : ValueRange(ArrayRef(values)) {} - ValueRange(ArrayRef values = llvm::None); - ValueRange(iterator_range values); - ValueRange(iterator_range values); - - /// An iterator element of this range. - class Iterator : public indexed_accessor_iterator { - public: - Value *operator*() const; - - private: - Iterator(OwnerT owner, unsigned curIndex); - - /// Allow access to the constructor. - friend ValueRange; - }; - - Iterator begin() const { return Iterator(owner, 0); } - Iterator end() const { return Iterator(owner, count); } - Value *operator[](unsigned index) const { - assert(index < size() && "invalid index for value range"); - return *std::next(begin(), index); - } - - /// Return the size of this range. - size_t size() const { return count; } - - /// Return if the range is empty. - bool empty() const { return size() == 0; } - - /// Drop the first N elements, and keep M elements. - ValueRange slice(unsigned n, unsigned m) const; - /// Drop the first n elements. - ValueRange drop_front(unsigned n = 1) const; - /// Drop the last n elements. - ValueRange drop_back(unsigned n = 1) const; - -private: - ValueRange(OwnerT owner, unsigned count) : owner(owner), count(count) {} - - /// The object that owns the provided range of values. - OwnerT owner; - /// The size from the owning range. - unsigned count; -}; - } // end namespace mlir namespace llvm { diff --git a/third_party/mlir/include/mlir/IR/OperationSupport.h b/third_party/mlir/include/mlir/IR/OperationSupport.h index 38f35cca97f..0a0e1acc358 100644 --- a/third_party/mlir/include/mlir/IR/OperationSupport.h +++ b/third_party/mlir/include/mlir/IR/OperationSupport.h @@ -60,6 +60,10 @@ template using OperandAdaptor = typename OpTy::OperandAdaptor; class OwningRewritePatternList; +//===----------------------------------------------------------------------===// +// AbstractOperation +//===----------------------------------------------------------------------===// + enum class OperationProperty { /// This bit is set for an operation if it is a commutative operation: that /// is a binary operator (two inputs) where "a op b" and "b op a" produce the @@ -201,6 +205,10 @@ private: bool (&hasRawTrait)(ClassID *traitID); }; +//===----------------------------------------------------------------------===// +// OperationName +//===----------------------------------------------------------------------===// + class OperationName { public: using RepresentationUnion = @@ -251,6 +259,10 @@ inline llvm::hash_code hash_value(OperationName arg) { return llvm::hash_value(arg.getAsOpaquePointer()); } +//===----------------------------------------------------------------------===// +// OperationState +//===----------------------------------------------------------------------===// + /// This represents an operation in an abstracted form, suitable for use with /// the builder APIs. This object is a large and heavy weight object meant to /// be used as a temporary object on the stack. It is generally unwise to put @@ -274,7 +286,7 @@ public: OperationState(Location location, OperationName name); - OperationState(Location location, StringRef name, ArrayRef operands, + OperationState(Location location, StringRef name, ValueRange operands, ArrayRef types, ArrayRef attributes, ArrayRef successors = {}, MutableArrayRef> regions = {}, @@ -322,6 +334,10 @@ public: MLIRContext *getContext() { return location->getContext(); } }; +//===----------------------------------------------------------------------===// +// OperandStorage +//===----------------------------------------------------------------------===// + namespace detail { /// A utility class holding the information necessary to dynamically resize /// operands. @@ -445,6 +461,10 @@ private: }; } // end namespace detail +//===----------------------------------------------------------------------===// +// OpPrintingFlags +//===----------------------------------------------------------------------===// + /// Set of flags used to control the behavior of the various IR print methods /// (e.g. Operation::Print). class OpPrintingFlags { @@ -504,6 +524,138 @@ private: bool printLocalScope : 1; }; +//===----------------------------------------------------------------------===// +// Operation Value-Iterators +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// ValueTypeRange + +/// This class implements iteration on the types of a given range of values. +template +class ValueTypeIterator final + : public llvm::mapped_iterator { + static Type unwrap(Value *value) { return value->getType(); } + +public: + using reference = Type; + + /// Provide a const dereference method. + Type operator*() const { return unwrap(*this->I); } + + /// Initializes the type iterator to the specified value iterator. + ValueTypeIterator(ValueIteratorT it) + : llvm::mapped_iterator(it, &unwrap) {} +}; + +//===----------------------------------------------------------------------===// +// OperandRange + +/// This class implements the operand iterators for the Operation class. +class OperandRange final + : public detail::indexed_accessor_range_base { +public: + using RangeBaseT::RangeBaseT; + OperandRange(Operation *op); + + /// Returns the types of the values within this range. + using type_iterator = ValueTypeIterator; + iterator_range getTypes() const { return {begin(), end()}; } + +private: + /// See `detail::indexed_accessor_range_base` for details. + static OpOperand *offset_base(OpOperand *object, ptrdiff_t index) { + return object + index; + } + /// See `detail::indexed_accessor_range_base` for details. + static Value *dereference_iterator(OpOperand *object, ptrdiff_t index) { + return object[index].get(); + } + + /// Allow access to `offset_base` and `dereference_iterator`. + friend RangeBaseT; +}; + +//===----------------------------------------------------------------------===// +// ResultRange + +/// This class implements the result iterators for the Operation class. +class ResultRange final + : public detail::indexed_accessor_range_base { +public: + using RangeBaseT::RangeBaseT; + ResultRange(Operation *op); + + /// Returns the types of the values within this range. + using type_iterator = ValueTypeIterator; + iterator_range getTypes() const { return {begin(), end()}; } + +private: + /// See `detail::indexed_accessor_range_base` for details. + static OpResult *offset_base(OpResult *object, ptrdiff_t index) { + return object + index; + } + /// See `detail::indexed_accessor_range_base` for details. + static Value *dereference_iterator(OpResult *object, ptrdiff_t index) { + return &object[index]; + } + + /// Allow access to `offset_base` and `dereference_iterator`. + friend RangeBaseT; +}; + +//===----------------------------------------------------------------------===// +// ValueRange + +/// This class provides an abstraction over the different types of ranges over +/// Value*s. In many cases, this prevents the need to explicitly materialize a +/// SmallVector/std::vector. This class should be used in places that are not +/// suitable for a more derived type (e.g. ArrayRef) or a template range +/// parameter. +class ValueRange final + : public detail::indexed_accessor_range_base< + ValueRange, + llvm::PointerUnion, Value *, + Value *, Value *> { +public: + using RangeBaseT::RangeBaseT; + + template , Arg>::value && + !std::is_convertible::value>> + ValueRange(Arg &&arg) + : ValueRange(ArrayRef(std::forward(arg))) {} + ValueRange(Value *const &value) : ValueRange(&value, /*count=*/1) {} + ValueRange(const std::initializer_list &values) + : ValueRange(ArrayRef(values)) {} + ValueRange(iterator_range values) + : ValueRange(OperandRange(values)) {} + ValueRange(iterator_range values) + : ValueRange(ResultRange(values)) {} + ValueRange(ArrayRef values = llvm::None); + ValueRange(OperandRange values); + ValueRange(ResultRange values); + + /// Returns the types of the values within this range. + using type_iterator = ValueTypeIterator; + iterator_range getTypes() const { return {begin(), end()}; } + +private: + /// The type representing the owner of this range. This is either a list of + /// values, operands, or results. + using OwnerT = llvm::PointerUnion; + + /// See `detail::indexed_accessor_range_base` for details. + static OwnerT offset_base(const OwnerT &owner, ptrdiff_t index); + /// See `detail::indexed_accessor_range_base` for details. + static Value *dereference_iterator(const OwnerT &owner, ptrdiff_t index); + + /// Allow access to `offset_base` and `dereference_iterator`. + friend RangeBaseT; +}; } // end namespace mlir namespace llvm { diff --git a/third_party/mlir/include/mlir/IR/PatternMatch.h b/third_party/mlir/include/mlir/IR/PatternMatch.h index 366d2b893af..4805152cf4c 100644 --- a/third_party/mlir/include/mlir/IR/PatternMatch.h +++ b/third_party/mlir/include/mlir/IR/PatternMatch.h @@ -302,9 +302,9 @@ public: return OpTy(); } - /// This is implemented to create the specified operations and serves as a + /// This is implemented to insert the specified operation and serves as a /// notification hook for rewriters that want to know about new operations. - virtual Operation *createOperation(const OperationState &state) = 0; + virtual Operation *insert(Operation *op) = 0; /// Move the blocks that belong to "region" before the given position in /// another region "parent". The two regions must be different. The caller diff --git a/third_party/mlir/include/mlir/IR/Region.h b/third_party/mlir/include/mlir/IR/Region.h index 249ba9562f2..27b20c2eaf6 100644 --- a/third_party/mlir/include/mlir/IR/Region.h +++ b/third_party/mlir/include/mlir/IR/Region.h @@ -160,6 +160,43 @@ private: Operation *container; }; +/// This class provides an abstraction over the different types of ranges over +/// Regions. In many cases, this prevents the need to explicitly materialize a +/// SmallVector/std::vector. This class should be used in places that are not +/// suitable for a more derived type (e.g. ArrayRef) or a template range +/// parameter. +class RegionRange + : public detail::indexed_accessor_range_base< + RegionRange, + llvm::PointerUnion *>, + Region *, Region *, Region *> { + /// The type representing the owner of this range. This is either a list of + /// values, operands, or results. + using OwnerT = llvm::PointerUnion *>; + +public: + using RangeBaseT::RangeBaseT; + + RegionRange(MutableArrayRef regions = llvm::None); + + template >, Arg>::value>> + RegionRange(Arg &&arg) + : RegionRange(ArrayRef>(std::forward(arg))) { + } + RegionRange(ArrayRef> regions); + +private: + /// See `detail::indexed_accessor_range_base` for details. + static OwnerT offset_base(const OwnerT &owner, ptrdiff_t index); + /// See `detail::indexed_accessor_range_base` for details. + static Region *dereference_iterator(const OwnerT &owner, ptrdiff_t index); + + /// Allow access to `offset_base` and `dereference_iterator`. + friend RangeBaseT; +}; + } // end namespace mlir #endif // MLIR_IR_REGION_H diff --git a/third_party/mlir/include/mlir/IR/StandardTypes.h b/third_party/mlir/include/mlir/IR/StandardTypes.h index 23a1ff2177e..5634f86254f 100644 --- a/third_party/mlir/include/mlir/IR/StandardTypes.h +++ b/third_party/mlir/include/mlir/IR/StandardTypes.h @@ -220,6 +220,9 @@ public: /// has static shape. bool hasStaticShape() const; + /// If this has a static shape and the shape is equal to `shape` return true. + bool hasStaticShape(ArrayRef shape) const; + /// If this is a ranked type, return the number of dimensions with dynamic /// size. Otherwise, abort. int64_t getNumDynamicDims() const; diff --git a/third_party/mlir/include/mlir/IR/SymbolTable.h b/third_party/mlir/include/mlir/IR/SymbolTable.h index ea7986172cb..e04beac6bc6 100644 --- a/third_party/mlir/include/mlir/IR/SymbolTable.h +++ b/third_party/mlir/include/mlir/IR/SymbolTable.h @@ -127,7 +127,7 @@ public: /// of the symbol table, and not the op itself. This function will also return /// false if there are any unknown operations that may potentially be symbol /// tables. This doesn't necessarily mean that there are no uses, we just - /// can't convervatively prove it. + /// can't conservatively prove it. static bool symbolKnownUseEmpty(StringRef symbol, Operation *from); /// Attempt to replace all uses of the given symbol 'oldSymbol' with the diff --git a/third_party/mlir/include/mlir/IR/TypeUtilities.h b/third_party/mlir/include/mlir/IR/TypeUtilities.h index 49d57e894ca..c1d1095d8ea 100644 --- a/third_party/mlir/include/mlir/IR/TypeUtilities.h +++ b/third_party/mlir/include/mlir/IR/TypeUtilities.h @@ -52,6 +52,12 @@ SmallVector getFlattenedTypes(TupleType t); /// dialect and typeData. bool isOpaqueTypeWithName(Type type, StringRef dialect, StringRef typeData); +/// Returns success if the given two shapes are compatible. That is, they have +/// the same size and each pair of the elements are equal or one of them is +/// dynamic. +LogicalResult verifyCompatibleShape(ArrayRef shape1, + ArrayRef shape2); + /// Returns success if the given two types have compatible shape. That is, /// they are both scalars (not shaped), or they are both shaped types and at /// least one is unranked or they have compatible dimensions. Dimensions are @@ -65,13 +71,14 @@ LogicalResult verifyCompatibleShape(Type type1, Type type2); // An iterator for the element types of an op's operands of shaped types. class OperandElementTypeIterator final - : public llvm::mapped_iterator { + : public llvm::mapped_iterator { public: using reference = Type; /// Initializes the result element type iterator to the specified operand /// iterator. - explicit OperandElementTypeIterator(OperandIterator it); + explicit OperandElementTypeIterator(Operation::operand_iterator it); private: static Type unwrap(Value *value); @@ -82,13 +89,14 @@ using OperandElementTypeRange = // An iterator for the tensor element types of an op's results of shaped types. class ResultElementTypeIterator final - : public llvm::mapped_iterator { + : public llvm::mapped_iterator { public: using reference = Type; /// Initializes the result element type iterator to the specified result /// iterator. - explicit ResultElementTypeIterator(ResultIterator it); + explicit ResultElementTypeIterator(Operation::result_iterator it); private: static Type unwrap(Value *value); diff --git a/third_party/mlir/include/mlir/Pass/PassManager.h b/third_party/mlir/include/mlir/Pass/PassManager.h index 5762d684b06..d7c73d889ae 100644 --- a/third_party/mlir/include/mlir/Pass/PassManager.h +++ b/third_party/mlir/include/mlir/Pass/PassManager.h @@ -97,7 +97,7 @@ public: /// Returns the internal implementation instance. detail::OpPassManagerImpl &getImpl(); - /// Prints out the passes of the pass mangager as the textual representation + /// Prints out the passes of the pass manager as the textual representation /// of pipelines. /// Note: The quality of the string representation depends entirely on the /// the correctness of per-pass overrides of Pass::printAsTextualPipeline. diff --git a/third_party/mlir/include/mlir/Support/STLExtras.h b/third_party/mlir/include/mlir/Support/STLExtras.h index 95e52f94e22..c98f925e04e 100644 --- a/third_party/mlir/include/mlir/Support/STLExtras.h +++ b/third_party/mlir/include/mlir/Support/STLExtras.h @@ -147,9 +147,9 @@ using is_invocable = is_detected; // Extra additions to //===----------------------------------------------------------------------===// -/// A utility class used to implement an iterator that contains some object and -/// an index. The iterator moves the index but keeps the object constant. -template class indexed_accessor_iterator : public llvm::iterator_facade_base { public: ptrdiff_t operator-(const indexed_accessor_iterator &rhs) const { - assert(object == rhs.object && "incompatible iterators"); + assert(base == rhs.base && "incompatible iterators"); return index - rhs.index; } bool operator==(const indexed_accessor_iterator &rhs) const { - return object == rhs.object && index == rhs.index; + return base == rhs.base && index == rhs.index; } bool operator<(const indexed_accessor_iterator &rhs) const { - assert(object == rhs.object && "incompatible iterators"); + assert(base == rhs.base && "incompatible iterators"); return index < rhs.index; } @@ -180,16 +180,156 @@ public: /// Returns the current index of the iterator. ptrdiff_t getIndex() const { return index; } - /// Returns the current object of the iterator. - const ObjectType &getObject() const { return object; } + /// Returns the current base of the iterator. + const BaseT &getBase() const { return base; } protected: - indexed_accessor_iterator(ObjectType object, ptrdiff_t index) - : object(object), index(index) {} - ObjectType object; + indexed_accessor_iterator(BaseT base, ptrdiff_t index) + : base(base), index(index) {} + BaseT base; ptrdiff_t index; }; +namespace detail { +/// The class represents the base of a range of indexed_accessor_iterators. It +/// provides support for many different range functionalities, e.g. +/// drop_front/slice/etc.. Derived range classes must implement the following +/// static methods: +/// * ReferenceT dereference_iterator(const BaseT &base, ptrdiff_t index) +/// - Derefence an iterator pointing to the base object at the given index. +/// * BaseT offset_base(const BaseT &base, ptrdiff_t index) +/// - Return a new base that is offset from the provide base by 'index' +/// elements. +template +class indexed_accessor_range_base { +public: + using RangeBaseT = + indexed_accessor_range_base; + + /// An iterator element of this range. + class iterator : public indexed_accessor_iterator { + public: + // Index into this iterator, invoking a static method on the derived type. + ReferenceT operator*() const { + return DerivedT::dereference_iterator(this->getBase(), this->getIndex()); + } + + private: + iterator(BaseT owner, ptrdiff_t curIndex) + : indexed_accessor_iterator( + owner, curIndex) {} + + /// Allow access to the constructor. + friend indexed_accessor_range_base; + }; + + indexed_accessor_range_base(iterator begin, iterator end) + : base(DerivedT::offset_base(begin.getBase(), begin.getIndex())), + count(end.getIndex() - begin.getIndex()) {} + indexed_accessor_range_base(const iterator_range &range) + : indexed_accessor_range_base(range.begin(), range.end()) {} + + iterator begin() const { return iterator(base, 0); } + iterator end() const { return iterator(base, count); } + ReferenceT operator[](unsigned index) const { + assert(index < size() && "invalid index for value range"); + return DerivedT::dereference_iterator(base, index); + } + + /// Return the size of this range. + size_t size() const { return count; } + + /// Return if the range is empty. + bool empty() const { return size() == 0; } + + /// Drop the first N elements, and keep M elements. + DerivedT slice(size_t n, size_t m) const { + assert(n + m <= size() && "invalid size specifiers"); + return DerivedT(DerivedT::offset_base(base, n), m); + } + + /// Drop the first n elements. + DerivedT drop_front(size_t n = 1) const { + assert(size() >= n && "Dropping more elements than exist"); + return slice(n, size() - n); + } + /// Drop the last n elements. + DerivedT drop_back(size_t n = 1) const { + assert(size() >= n && "Dropping more elements than exist"); + return DerivedT(base, size() - n); + } + + /// Take the first n elements. + DerivedT take_front(size_t n = 1) const { + return n < size() ? drop_back(size() - n) + : static_cast(*this); + } + + /// Allow conversion to SmallVector if necessary. + /// TODO(riverriddle) Remove this when SmallVector accepts different range + /// types in its constructor. + template operator SmallVector() const { + return {begin(), end()}; + } + +protected: + indexed_accessor_range_base(BaseT base, ptrdiff_t count) + : base(base), count(count) {} + indexed_accessor_range_base(const indexed_accessor_range_base &) = default; + indexed_accessor_range_base(indexed_accessor_range_base &&) = default; + indexed_accessor_range_base & + operator=(const indexed_accessor_range_base &) = default; + + /// The base that owns the provided range of values. + BaseT base; + /// The size from the owning range. + ptrdiff_t count; +}; +} // end namespace detail + +/// This class provides an implementation of a range of +/// indexed_accessor_iterators where the base is not indexable. Ranges with +/// bases that are offsetable should derive from indexed_accessor_range_base +/// instead. Derived range classes are expected to implement the following +/// static method: +/// * ReferenceT dereference_iterator(const BaseT &base, ptrdiff_t index) +/// - Derefence an iterator pointing to a parent base at the given index. +template +class indexed_accessor_range + : public detail::indexed_accessor_range_base< + indexed_accessor_range, + std::pair, T, PointerT, ReferenceT> { +protected: + indexed_accessor_range(BaseT base, ptrdiff_t startIndex, ptrdiff_t count) + : detail::indexed_accessor_range_base< + DerivedT, std::pair, T, PointerT, ReferenceT>( + std::make_pair(base, startIndex), count) {} + +private: + /// See `detail::indexed_accessor_range_base` for details. + static std::pair + offset_base(const std::pair &base, ptrdiff_t index) { + // We encode the internal base as a pair of the derived base and a start + // index into the derived base. + return std::make_pair(base.first, base.second + index); + } + /// See `detail::indexed_accessor_range_base` for details. + static ReferenceT + dereference_iterator(const std::pair &base, + ptrdiff_t index) { + return DerivedT::dereference_iterator(base.first, base.second + index); + } + + /// Allow access to `offset_base` and `dereference_iterator`. + friend detail::indexed_accessor_range_base< + indexed_accessor_range, + std::pair, T, PointerT, ReferenceT>; +}; + /// Given a container of pairs, return a range over the second elements. template auto make_second_range(ContainerTy &&c) { return llvm::map_range( diff --git a/third_party/mlir/include/mlir/TableGen/Format.h b/third_party/mlir/include/mlir/TableGen/Format.h index 3d0248f26c0..6f02c283cad 100644 --- a/third_party/mlir/include/mlir/TableGen/Format.h +++ b/third_party/mlir/include/mlir/TableGen/Format.h @@ -44,7 +44,7 @@ namespace tblgen { /// named as $, and we can potentially support referencing those entities /// directly in the format template in the future. // -/// Custom ones are registered by dialect-specific TablGen backends and use the +/// Custom ones are registered by dialect-specific TableGen backends and use the /// same unified setter. class FmtContext { public: diff --git a/third_party/mlir/include/mlir/TableGen/Pattern.h b/third_party/mlir/include/mlir/TableGen/Pattern.h index 024563fce54..e9456f4ba07 100644 --- a/third_party/mlir/include/mlir/TableGen/Pattern.h +++ b/third_party/mlir/include/mlir/TableGen/Pattern.h @@ -338,8 +338,8 @@ public: const char *separator = ", ") const; // Splits the given `symbol` into a value pack name and an index. Returns the - // value pack name and writes the index to `index` on sucess. Returns `symbol` - // itself if it does not contain an index. + // value pack name and writes the index to `index` on success. Returns + // `symbol` itself if it does not contain an index. // // We can use `name__N` to access the `N`-th value in the value pack bound to // `name`. `name` is typically the results of an multi-result op. diff --git a/third_party/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h b/third_party/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h index b957c824be1..288901221db 100644 --- a/third_party/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h +++ b/third_party/mlir/include/mlir/Target/LLVMIR/ModuleTranslation.h @@ -23,6 +23,7 @@ #ifndef MLIR_TARGET_LLVMIR_MODULETRANSLATION_H #define MLIR_TARGET_LLVMIR_MODULETRANSLATION_H +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/IR/Block.h" #include "mlir/IR/Module.h" #include "mlir/IR/Value.h" @@ -50,7 +51,9 @@ class LLVMFuncOp; class ModuleTranslation { public: template - static std::unique_ptr translateModule(ModuleOp m) { + static std::unique_ptr translateModule(Operation *m) { + if (!satisfiesLLVMModule(m)) + return nullptr; if (failed(checkSupportedModuleOps(m))) return nullptr; auto llvmModule = prepareLLVMModule(m); @@ -66,23 +69,30 @@ public: return std::move(translator.llvmModule); } + /// A helper method to get the single Block in an operation honoring LLVM's + /// module requirements. + static Block &getModuleBody(Operation *m) { return m->getRegion(0).front(); } + protected: // Translate the given MLIR module expressed in MLIR LLVM IR dialect into an // LLVM IR module. The MLIR LLVM IR dialect holds a pointer to an // LLVMContext, the LLVM IR module will be created in that context. - explicit ModuleTranslation(ModuleOp module) : mlirModule(module) {} + explicit ModuleTranslation(Operation *module) : mlirModule(module) { + assert(satisfiesLLVMModule(mlirModule) && + "mlirModule should honor LLVM's module semantics."); + } virtual ~ModuleTranslation() {} virtual LogicalResult convertOperation(Operation &op, llvm::IRBuilder<> &builder); - static std::unique_ptr prepareLLVMModule(ModuleOp m); + static std::unique_ptr prepareLLVMModule(Operation *m); template SmallVector lookupValues(Range &&values); private: /// Check whether the module contains only supported ops directly in its body. - static LogicalResult checkSupportedModuleOps(ModuleOp m); + static LogicalResult checkSupportedModuleOps(Operation *m); LogicalResult convertFunctions(); void convertGlobals(); @@ -94,7 +104,7 @@ private: Location loc); // Original and translated module. - ModuleOp mlirModule; + Operation *mlirModule; std::unique_ptr llvmModule; // Mappings between llvm.mlir.global definitions and corresponding globals. diff --git a/third_party/mlir/include/mlir/Target/NVVMIR.h b/third_party/mlir/include/mlir/Target/NVVMIR.h index 3a4442ebf87..ec9858e0fd7 100644 --- a/third_party/mlir/include/mlir/Target/NVVMIR.h +++ b/third_party/mlir/include/mlir/Target/NVVMIR.h @@ -30,14 +30,14 @@ class Module; } // namespace llvm namespace mlir { -class ModuleOp; +class Operation; -/// Convert the given MLIR module into NVVM IR. This conversion requires the -/// registration of the LLVM IR dialect and will extract the LLVM context -/// from the registered LLVM IR dialect. In case of error, report it -/// to the error handler registered with the MLIR context, if any (obtained from +/// Convert the given LLVM-module-like operation into NVVM IR. This conversion +/// requires the registration of the LLVM IR dialect and will extract the LLVM +/// context from the registered LLVM IR dialect. In case of error, report it to +/// the error handler registered with the MLIR context, if any (obtained from /// the MLIR module), and return `nullptr`. -std::unique_ptr translateModuleToNVVMIR(ModuleOp m); +std::unique_ptr translateModuleToNVVMIR(Operation *m); } // namespace mlir diff --git a/third_party/mlir/include/mlir/Target/ROCDLIR.h b/third_party/mlir/include/mlir/Target/ROCDLIR.h index 6295a1b0d98..fd00e9458ef 100644 --- a/third_party/mlir/include/mlir/Target/ROCDLIR.h +++ b/third_party/mlir/include/mlir/Target/ROCDLIR.h @@ -31,14 +31,14 @@ class Module; } // namespace llvm namespace mlir { -class ModuleOp; +class Operation; -/// Convert the given MLIR module into ROCDL IR. This conversion requires the -/// registration of the LLVM IR dialect and will extract the LLVM context -/// from the registered LLVM IR dialect. In case of error, report it -/// to the error handler registered with the MLIR context, if any (obtained from +/// Convert the given LLVM-module-like operation into ROCDL IR. This conversion +/// requires the registration of the LLVM IR dialect and will extract the LLVM +/// context from the registered LLVM IR dialect. In case of error, report it to +/// the error handler registered with the MLIR context, if any (obtained from /// the MLIR module), and return `nullptr`. -std::unique_ptr translateModuleToROCDLIR(ModuleOp m); +std::unique_ptr translateModuleToROCDLIR(Operation *m); } // namespace mlir diff --git a/third_party/mlir/include/mlir/Transforms/DialectConversion.h b/third_party/mlir/include/mlir/Transforms/DialectConversion.h index fee58a4904a..249b4c114c9 100644 --- a/third_party/mlir/include/mlir/Transforms/DialectConversion.h +++ b/third_party/mlir/include/mlir/Transforms/DialectConversion.h @@ -332,12 +332,6 @@ public: /// Replace all the uses of the block argument `from` with value `to`. void replaceUsesOfBlockArgument(BlockArgument *from, Value *to); - /// Clone the given operation without cloning its regions. - Operation *cloneWithoutRegions(Operation *op); - template OpT cloneWithoutRegions(OpT op) { - return cast(cloneWithoutRegions(op.getOperation())); - } - /// Return the converted value that replaces 'key'. Return 'key' if there is /// no such a converted value. Value *getRemappedValue(Value *key); @@ -376,8 +370,8 @@ public: BlockAndValueMapping &mapping) override; using PatternRewriter::cloneRegionBefore; - /// PatternRewriter hook for creating a new operation. - Operation *createOperation(const OperationState &state) override; + /// PatternRewriter hook for inserting a new operation. + Operation *insert(Operation *op) override; /// PatternRewriter hook for updating the root operation in-place. void notifyRootUpdated(Operation *op) override; diff --git a/third_party/mlir/include/mlir/Translation.h b/third_party/mlir/include/mlir/Translation.h index b7c277ccdd3..0bf8178146a 100644 --- a/third_party/mlir/include/mlir/Translation.h +++ b/third_party/mlir/include/mlir/Translation.h @@ -64,7 +64,7 @@ using TranslateFromMLIRFunction = using TranslateFunction = std::function; -/// Use Translate[ToMLIR|FromMLIR]Registration as a global initialiser that +/// Use Translate[ToMLIR|FromMLIR]Registration as a global initializer that /// registers a function and associates it with name. This requires that a /// translation has not been registered to a given name. /// diff --git a/third_party/mlir/lib/Analysis/AffineStructures.cpp b/third_party/mlir/lib/Analysis/AffineStructures.cpp index e57045c639d..7f6da8eb418 100644 --- a/third_party/mlir/lib/Analysis/AffineStructures.cpp +++ b/third_party/mlir/lib/Analysis/AffineStructures.cpp @@ -659,9 +659,8 @@ LogicalResult FlatAffineConstraints::composeMap(const AffineValueMap *vMap) { // Add localCst information. if (localCst.getNumLocalIds() > 0) { - SmallVector values(vMap->getOperands().begin(), - vMap->getOperands().end()); - localCst.setIdValues(0, localCst.getNumDimAndSymbolIds(), values); + localCst.setIdValues(0, /*end=*/localCst.getNumDimAndSymbolIds(), + /*values=*/vMap->getOperands()); // Align localCst and this. mergeAndAlignIds(/*offset=*/0, &localCst, this); // Finally, append localCst to this constraint set. diff --git a/third_party/mlir/lib/Analysis/CMakeLists.txt b/third_party/mlir/lib/Analysis/CMakeLists.txt index c16ad3f3d1f..96c2928b17f 100644 --- a/third_party/mlir/lib/Analysis/CMakeLists.txt +++ b/third_party/mlir/lib/Analysis/CMakeLists.txt @@ -4,6 +4,7 @@ add_llvm_library(MLIRAnalysis STATIC CallGraph.cpp Dominance.cpp InferTypeOpInterface.cpp + Liveness.cpp LoopAnalysis.cpp MemRefBoundCheck.cpp NestedMatcher.cpp diff --git a/third_party/mlir/lib/Analysis/Liveness.cpp b/third_party/mlir/lib/Analysis/Liveness.cpp new file mode 100644 index 00000000000..6aaec4cc719 --- /dev/null +++ b/third_party/mlir/lib/Analysis/Liveness.cpp @@ -0,0 +1,382 @@ +//===- Liveness.cpp - Liveness analysis for MLIR --------------------------===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// Implementation of the liveness analysis. +// +//===----------------------------------------------------------------------===// + +#include "mlir/Analysis/Liveness.h" +#include "mlir/IR/Block.h" +#include "mlir/IR/Operation.h" +#include "mlir/IR/Region.h" +#include "mlir/IR/Value.h" +#include "llvm/ADT/SetOperations.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/Support/raw_ostream.h" + +using namespace mlir; + +/// Builds and holds block information during the construction phase. +struct BlockInfoBuilder { + using ValueSetT = Liveness::ValueSetT; + + /// Constructs an empty block builder. + BlockInfoBuilder() : block(nullptr) {} + + /// Fills the block builder with initial liveness information. + BlockInfoBuilder(Block *block) : block(block) { + // Mark all block arguments (phis) as defined. + for (BlockArgument *argument : block->getArguments()) + defValues.insert(argument); + + // Check all result values and whether their uses + // are inside this block or not (see outValues). + for (Operation &operation : *block) + for (Value *result : operation.getResults()) { + defValues.insert(result); + + // Check whether this value will be in the outValues + // set (its uses escape this block). Due to the SSA + // properties of the program, the uses must occur after + // the definition. Therefore, we do not have to check + // additional conditions to detect an escaping value. + for (OpOperand &use : result->getUses()) + if (use.getOwner()->getBlock() != block) { + outValues.insert(result); + break; + } + } + + // Check all operations for used operands. + for (Operation &operation : block->getOperations()) + for (Value *operand : operation.getOperands()) { + // If the operand is already defined in the scope of this + // block, we can skip the value in the use set. + if (!defValues.count(operand)) + useValues.insert(operand); + } + } + + /// Updates live-in information of the current block. + /// To do so it uses the default liveness-computation formula: + /// newIn = use union out \ def. + /// The methods returns true, if the set has changed (newIn != in), + /// false otherwise. + bool updateLiveIn() { + ValueSetT newIn = useValues; + llvm::set_union(newIn, outValues); + llvm::set_subtract(newIn, defValues); + + // It is sufficient to check the set sizes (instead of their contents) + // since the live-in set can only grow monotonically during all update + // operations. + if (newIn.size() == inValues.size()) + return false; + + inValues = newIn; + return true; + } + + /// Updates live-out information of the current block. + /// It iterates over all successors and unifies their live-in + /// values with the current live-out values. + template void updateLiveOut(SourceT &source) { + for (Block *succ : block->getSuccessors()) { + BlockInfoBuilder &builder = source[succ]; + llvm::set_union(outValues, builder.inValues); + } + } + + /// The current block. + Block *block; + + /// The set of all live in values. + ValueSetT inValues; + + /// The set of all live out values. + ValueSetT outValues; + + /// The set of all defined values. + ValueSetT defValues; + + /// The set of all used values. + ValueSetT useValues; +}; + +/// Builds the internal liveness block mapping. +static void buildBlockMapping(MutableArrayRef regions, + DenseMap &builders) { + llvm::SetVector toProcess; + + // Initialize all block structures + for (Region ®ion : regions) + for (Block &block : region) { + BlockInfoBuilder &builder = + builders.try_emplace(&block, &block).first->second; + + if (builder.updateLiveIn()) + toProcess.insert(block.pred_begin(), block.pred_end()); + } + + // Propagate the in and out-value sets (fixpoint iteration) + while (!toProcess.empty()) { + Block *current = toProcess.pop_back_val(); + BlockInfoBuilder &builder = builders[current]; + + // Update the current out values. + builder.updateLiveOut(builders); + + // Compute (potentially) updated live in values. + if (builder.updateLiveIn()) + toProcess.insert(current->pred_begin(), current->pred_end()); + } +} + +//===----------------------------------------------------------------------===// +// Liveness +//===----------------------------------------------------------------------===// + +/// Creates a new Liveness analysis that computes liveness +/// information for all associated regions. +Liveness::Liveness(Operation *op) : operation(op) { build(op->getRegions()); } + +/// Initializes the internal mappings. +void Liveness::build(MutableArrayRef regions) { + + // Build internal block mapping. + DenseMap builders; + buildBlockMapping(regions, builders); + + // Store internal block data. + for (auto &entry : builders) { + BlockInfoBuilder &builder = entry.second; + LivenessBlockInfo &info = blockMapping[entry.first]; + + info.block = builder.block; + info.inValues = std::move(builder.inValues); + info.outValues = std::move(builder.outValues); + } +} + +/// Gets liveness info (if any) for the given value. +Liveness::OperationListT Liveness::resolveLiveness(Value *value) const { + OperationListT result; + SmallPtrSet visited; + SmallVector toProcess; + + // Start with the defining block + Block *currentBlock; + if (Operation *defOp = value->getDefiningOp()) + currentBlock = defOp->getBlock(); + else + currentBlock = cast(value)->getOwner(); + toProcess.push_back(currentBlock); + visited.insert(currentBlock); + + // Start with all associated blocks + for (OpOperand &use : value->getUses()) { + Block *useBlock = use.getOwner()->getBlock(); + if (visited.insert(useBlock).second) + toProcess.push_back(useBlock); + } + + while (!toProcess.empty()) { + // Get block and block liveness information. + Block *block = toProcess.back(); + toProcess.pop_back(); + const LivenessBlockInfo *blockInfo = getLiveness(block); + + // Note that start and end will be in the same block. + Operation *start = blockInfo->getStartOperation(value); + Operation *end = blockInfo->getEndOperation(value, start); + + result.push_back(start); + while (start != end) { + start = start->getNextNode(); + result.push_back(start); + } + + for (Block *successor : block->getSuccessors()) { + if (getLiveness(successor)->isLiveIn(value) && + visited.insert(successor).second) + toProcess.push_back(successor); + } + } + + return result; +} + +/// Gets liveness info (if any) for the block. +const LivenessBlockInfo *Liveness::getLiveness(Block *block) const { + auto it = blockMapping.find(block); + return it == blockMapping.end() ? nullptr : &it->second; +} + +/// Returns a reference to a set containing live-in values. +const Liveness::ValueSetT &Liveness::getLiveIn(Block *block) const { + return getLiveness(block)->in(); +} + +/// Returns a reference to a set containing live-out values. +const Liveness::ValueSetT &Liveness::getLiveOut(Block *block) const { + return getLiveness(block)->out(); +} + +/// Returns true if the given operation represent the last use of the +/// given value. +bool Liveness::isLastUse(Value *value, Operation *operation) const { + Block *block = operation->getBlock(); + const LivenessBlockInfo *blockInfo = getLiveness(block); + + // The given value escapes the associated block. + if (blockInfo->isLiveOut(value)) + return false; + + Operation *endOperation = blockInfo->getEndOperation(value, operation); + // If the operation is a real user of `value` the first check is sufficient. + // If not, we will have to test whether the end operation is executed before + // the given operation in the block. + return endOperation == operation || endOperation->isBeforeInBlock(operation); +} + +/// Dumps the liveness information in a human readable format. +void Liveness::dump() const { print(llvm::errs()); } + +/// Dumps the liveness information to the given stream. +void Liveness::print(raw_ostream &os) const { + os << "// ---- Liveness -----\n"; + + // Builds unique block/value mappings for testing purposes. + DenseMap blockIds; + DenseMap operationIds; + DenseMap valueIds; + for (Region ®ion : operation->getRegions()) + for (Block &block : region) { + blockIds.insert({&block, blockIds.size()}); + for (BlockArgument *argument : block.getArguments()) + valueIds.insert({argument, valueIds.size()}); + for (Operation &operation : block) { + operationIds.insert({&operation, operationIds.size()}); + for (Value *result : operation.getResults()) + valueIds.insert({result, valueIds.size()}); + } + } + + // Local printing helpers + auto printValueRef = [&](Value *value) { + if (Operation *defOp = value->getDefiningOp()) + os << "val_" << defOp->getName(); + else { + auto blockArg = cast(value); + os << "arg" << blockArg->getArgNumber() << "@" + << blockIds[blockArg->getOwner()]; + } + os << " "; + }; + + auto printValueRefs = [&](const ValueSetT &values) { + std::vector orderedValues(values.begin(), values.end()); + std::sort(orderedValues.begin(), orderedValues.end(), + [&](Value *left, Value *right) { + return valueIds[left] < valueIds[right]; + }); + for (Value *value : orderedValues) + printValueRef(value); + }; + + // Dump information about in and out values. + for (Region ®ion : operation->getRegions()) + for (Block &block : region) { + os << "// - Block: " << blockIds[&block] << "\n"; + auto liveness = getLiveness(&block); + os << "// --- LiveIn: "; + printValueRefs(liveness->inValues); + os << "\n// --- LiveOut: "; + printValueRefs(liveness->outValues); + os << "\n"; + + // Print liveness intervals. + os << "// --- BeginLiveness"; + for (Operation &op : block) { + if (op.getNumResults() < 1) + continue; + os << "\n"; + for (Value *result : op.getResults()) { + os << "// "; + printValueRef(result); + os << ":"; + auto liveOperations = resolveLiveness(result); + std::sort(liveOperations.begin(), liveOperations.end(), + [&](Operation *left, Operation *right) { + return operationIds[left] < operationIds[right]; + }); + for (Operation *operation : liveOperations) { + os << "\n// "; + operation->print(os); + } + } + } + os << "\n// --- EndLiveness\n"; + } + os << "// -------------------\n"; +} + +//===----------------------------------------------------------------------===// +// LivenessBlockInfo +//===----------------------------------------------------------------------===// + +/// Returns true if the given value is in the live-in set. +bool LivenessBlockInfo::isLiveIn(Value *value) const { + return inValues.count(value); +} + +/// Returns true if the given value is in the live-out set. +bool LivenessBlockInfo::isLiveOut(Value *value) const { + return outValues.count(value); +} + +/// Gets the start operation for the given value +/// (must be referenced in this block). +Operation *LivenessBlockInfo::getStartOperation(Value *value) const { + Operation *definingOp = value->getDefiningOp(); + // The given value is either live-in or is defined + // in the scope of this block. + if (isLiveIn(value) || !definingOp) + return &block->front(); + return definingOp; +} + +/// Gets the end operation for the given value using the start operation +/// provided (must be referenced in this block). +Operation *LivenessBlockInfo::getEndOperation(Value *value, + Operation *startOperation) const { + // The given value is either dying in this block or live-out. + if (isLiveOut(value)) + return &block->back(); + + // Resolve the last operation (must exist by definition). + Operation *endOperation = startOperation; + for (OpOperand &use : value->getUses()) { + Operation *useOperation = use.getOwner(); + // Check whether the use is in our block and after + // the current end operation. + if (useOperation->getBlock() == block && + endOperation->isBeforeInBlock(useOperation)) + endOperation = useOperation; + } + return endOperation; +} diff --git a/third_party/mlir/lib/Analysis/Utils.cpp b/third_party/mlir/lib/Analysis/Utils.cpp index 23361e38745..3ba27bbb299 100644 --- a/third_party/mlir/lib/Analysis/Utils.cpp +++ b/third_party/mlir/lib/Analysis/Utils.cpp @@ -151,7 +151,7 @@ LogicalResult MemRefRegion::unionBoundingBox(const MemRefRegion &other) { } /// Computes the memory region accessed by this memref with the region -/// represented as constraints symbolic/parameteric in 'loopDepth' loops +/// represented as constraints symbolic/parametric in 'loopDepth' loops /// surrounding opInst and any additional Function symbols. // For example, the memref region for this load operation at loopDepth = 1 will // be as below: diff --git a/third_party/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp b/third_party/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp index 5c772cc85f2..f41c0c45e96 100644 --- a/third_party/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp +++ b/third_party/mlir/lib/Conversion/GPUToNVVM/LowerGpuOpsToNVVMOps.cpp @@ -308,8 +308,6 @@ private: ConversionPatternRewriter &rewriter) const { Value *warpSize = rewriter.create( loc, int32Type, rewriter.getI32IntegerAttr(kWarpSize)); - Value *maskAndClamp = rewriter.create( - loc, int32Type, rewriter.getI32IntegerAttr(kWarpSize - 1)); Value *isPartialWarp = rewriter.create( loc, LLVM::ICmpPredicate::slt, activeWidth, warpSize); auto type = operand->getType().cast(); @@ -326,6 +324,9 @@ private: loc, int32Type, rewriter.create(loc, int32Type, one, activeWidth), one); + // Clamp lane: `activeWidth - 1` + Value *maskAndClamp = + rewriter.create(loc, int32Type, activeWidth, one); auto dialect = lowering.getDialect(); auto predTy = LLVM::LLVMType::getInt1Ty(dialect); auto shflTy = LLVM::LLVMType::getStructTy(dialect, {type, predTy}); @@ -363,6 +364,8 @@ private: Value *value = operand; Value *activeMask = rewriter.create( loc, int32Type, rewriter.getI32IntegerAttr(~0u)); + Value *maskAndClamp = rewriter.create( + loc, int32Type, rewriter.getI32IntegerAttr(kWarpSize - 1)); for (int i = 1; i < kWarpSize; i <<= 1) { Value *offset = rewriter.create( loc, int32Type, rewriter.getI32IntegerAttr(i)); @@ -450,10 +453,8 @@ private: static constexpr int kWarpSize = 32; }; -namespace { - -struct FuncOpLowering : LLVMOpLowering { - explicit FuncOpLowering(LLVMTypeConverter &typeConverter) +struct GPUFuncOpLowering : LLVMOpLowering { + explicit GPUFuncOpLowering(LLVMTypeConverter &typeConverter) : LLVMOpLowering(gpu::GPUFuncOp::getOperationName(), typeConverter.getDialect()->getContext(), typeConverter) {} @@ -488,8 +489,6 @@ struct FuncOpLowering : LLVMOpLowering { } // Rewrite the original GPU function to an LLVM function. - // TODO(zinenko): there is a hack in the std->llvm lowering that promotes - // structs to pointers that probably needs to be replicated here. auto funcType = lowering.convertType(gpuFuncOp.getType()) .cast() .getPointerElementTy(); @@ -575,17 +574,50 @@ struct FuncOpLowering : LLVMOpLowering { } } + // Move the region to the new function, update the entry block signature. rewriter.inlineRegionBefore(gpuFuncOp.getBody(), llvmFuncOp.getBody(), llvmFuncOp.end()); rewriter.applySignatureConversion(&llvmFuncOp.getBody(), signatureConversion); + { + // For memref-typed arguments, insert the relevant loads in the beginning + // of the block to comply with the LLVM dialect calling convention. This + // needs to be done after signature conversion to get the right types. + OpBuilder::InsertionGuard guard(rewriter); + Block &block = llvmFuncOp.front(); + rewriter.setInsertionPointToStart(&block); + + for (auto en : llvm::enumerate(gpuFuncOp.getType().getInputs())) { + if (!en.value().isa() && + !en.value().isa()) + continue; + + BlockArgument *arg = block.getArgument(en.index()); + Value *loaded = rewriter.create(loc, arg); + rewriter.replaceUsesOfBlockArgument(arg, loaded); + } + } + rewriter.eraseOp(gpuFuncOp); return matchSuccess(); } }; -} // end namespace +struct GPUReturnOpLowering : public LLVMOpLowering { + GPUReturnOpLowering(LLVMTypeConverter &typeConverter) + : LLVMOpLowering(gpu::ReturnOp::getOperationName(), + typeConverter.getDialect()->getContext(), + typeConverter) {} + + PatternMatchResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, operands, + ArrayRef()); + return matchSuccess(); + } +}; /// Import the GPU Ops to NVVM Patterns. #include "GPUToNVVM.cpp.inc" @@ -605,19 +637,7 @@ public: OwningRewritePatternList patterns; LLVMTypeConverter converter(m.getContext()); populateStdToLLVMConversionPatterns(converter, patterns); - populateWithGenerated(&getContext(), &patterns); - patterns.insert< - GPUIndexIntrinsicOpLowering, - GPUIndexIntrinsicOpLowering, - GPUIndexIntrinsicOpLowering, - GPUIndexIntrinsicOpLowering, - GPUAllReduceOpLowering, FuncOpLowering>(converter); - patterns.insert>(converter, "__nv_expf", - "__nv_exp"); + populateGpuToNVVMConversionPatterns(converter, patterns); ConversionTarget target(getContext()); target.addIllegalDialect(); target.addIllegalOp(); @@ -633,6 +653,24 @@ public: } // anonymous namespace +void mlir::populateGpuToNVVMConversionPatterns( + LLVMTypeConverter &converter, OwningRewritePatternList &patterns) { + populateWithGenerated(converter.getDialect()->getContext(), &patterns); + patterns + .insert, + GPUIndexIntrinsicOpLowering, + GPUIndexIntrinsicOpLowering, + GPUIndexIntrinsicOpLowering, + GPUAllReduceOpLowering, GPUFuncOpLowering, GPUReturnOpLowering>( + converter); + patterns.insert>(converter, "__nv_expf", + "__nv_exp"); +} + std::unique_ptr> mlir::createLowerGpuOpsToNVVMOpsPass() { return std::make_unique(); } diff --git a/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRV.cpp b/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRV.cpp index 2c1847d99ed..92cc02660a2 100644 --- a/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRV.cpp +++ b/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRV.cpp @@ -23,6 +23,7 @@ #include "mlir/Dialect/SPIRV/SPIRVDialect.h" #include "mlir/Dialect/SPIRV/SPIRVLowering.h" #include "mlir/Dialect/SPIRV/SPIRVOps.h" +#include "mlir/IR/Module.h" using namespace mlir; @@ -50,29 +51,67 @@ public: ConversionPatternRewriter &rewriter) const override; }; -/// Pattern to convert a kernel function in GPU dialect (a FuncOp with the -/// attribute gpu.kernel) within a spv.module. -class KernelFnConversion final : public SPIRVOpLowering { +/// Pattern to convert a kernel function in GPU dialect within a spv.module. +class KernelFnConversion final : public SPIRVOpLowering { public: KernelFnConversion(MLIRContext *context, SPIRVTypeConverter &converter, ArrayRef workGroupSize, PatternBenefit benefit = 1) - : SPIRVOpLowering(context, converter, benefit) { + : SPIRVOpLowering(context, converter, benefit) { auto config = workGroupSize.take_front(3); workGroupSizeAsInt32.assign(config.begin(), config.end()); workGroupSizeAsInt32.resize(3, 1); } PatternMatchResult - matchAndRewrite(FuncOp funcOp, ArrayRef operands, + matchAndRewrite(gpu::GPUFuncOp funcOp, ArrayRef operands, ConversionPatternRewriter &rewriter) const override; private: SmallVector workGroupSizeAsInt32; }; +/// Pattern to convert a module with gpu.kernel_module attribute to a +/// spv.module. +class KernelModuleConversion final : public SPIRVOpLowering { +public: + using SPIRVOpLowering::SPIRVOpLowering; + + PatternMatchResult + matchAndRewrite(ModuleOp moduleOp, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override; +}; + +/// Pattern to convert a module terminator op to a terminator of spv.module op. +// TODO: Move this into DRR, but that requires ModuleTerminatorOp to be defined +// in ODS. +class KernelModuleTerminatorConversion final + : public SPIRVOpLowering { +public: + using SPIRVOpLowering::SPIRVOpLowering; + + PatternMatchResult + matchAndRewrite(ModuleTerminatorOp terminatorOp, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override; +}; + +/// Pattern to convert a gpu.return into a SPIR-V return. +// TODO: This can go to DRR when GPU return has operands. +class GPUReturnOpConversion final : public SPIRVOpLowering { +public: + using SPIRVOpLowering::SPIRVOpLowering; + + PatternMatchResult + matchAndRewrite(gpu::ReturnOp returnOp, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override; +}; + } // namespace +//===----------------------------------------------------------------------===// +// loop::ForOp. +//===----------------------------------------------------------------------===// + PatternMatchResult ForOpConversion::matchAndRewrite(loop::ForOp forOp, ArrayRef operands, ConversionPatternRewriter &rewriter) const { @@ -142,6 +181,10 @@ ForOpConversion::matchAndRewrite(loop::ForOp forOp, ArrayRef operands, return matchSuccess(); } +//===----------------------------------------------------------------------===// +// Builtins. +//===----------------------------------------------------------------------===// + template PatternMatchResult LaunchConfigConversion::matchAndRewrite( SourceOp op, ArrayRef operands, @@ -170,8 +213,60 @@ PatternMatchResult LaunchConfigConversion::matchAndRewrite( return this->matchSuccess(); } +//===----------------------------------------------------------------------===// +// GPUFuncOp +//===----------------------------------------------------------------------===// + +// Legalizes a GPU function as an entry SPIR-V function. +static FuncOp +lowerAsEntryFunction(gpu::GPUFuncOp funcOp, SPIRVTypeConverter &typeConverter, + ConversionPatternRewriter &rewriter, + spirv::EntryPointABIAttr entryPointInfo, + ArrayRef argABIInfo) { + auto fnType = funcOp.getType(); + if (fnType.getNumResults()) { + funcOp.emitError("SPIR-V lowering only supports entry functions" + "with no return values right now"); + return nullptr; + } + if (fnType.getNumInputs() != argABIInfo.size()) { + funcOp.emitError( + "lowering as entry functions requires ABI info for all arguments"); + return nullptr; + } + // Update the signature to valid SPIR-V types and add the ABI + // attributes. These will be "materialized" by using the + // LowerABIAttributesPass. + TypeConverter::SignatureConversion signatureConverter(fnType.getNumInputs()); + { + for (auto argType : enumerate(funcOp.getType().getInputs())) { + auto convertedType = typeConverter.convertType(argType.value()); + signatureConverter.addInputs(argType.index(), convertedType); + } + } + auto newFuncOp = rewriter.create( + funcOp.getLoc(), funcOp.getName(), + rewriter.getFunctionType(signatureConverter.getConvertedTypes(), + llvm::None), + ArrayRef()); + for (const auto &namedAttr : funcOp.getAttrs()) { + if (namedAttr.first.is(impl::getTypeAttrName()) || + namedAttr.first.is(SymbolTable::getSymbolAttrName())) + continue; + newFuncOp.setAttr(namedAttr.first, namedAttr.second); + } + rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(), + newFuncOp.end()); + rewriter.applySignatureConversion(&newFuncOp.getBody(), signatureConverter); + rewriter.eraseOp(funcOp); + + spirv::setABIAttrs(newFuncOp, entryPointInfo, argABIInfo); + return newFuncOp; +} + PatternMatchResult -KernelFnConversion::matchAndRewrite(FuncOp funcOp, ArrayRef operands, +KernelFnConversion::matchAndRewrite(gpu::GPUFuncOp funcOp, + ArrayRef operands, ConversionPatternRewriter &rewriter) const { if (!gpu::GPUDialect::isKernel(funcOp)) { return matchFailure(); @@ -186,8 +281,8 @@ KernelFnConversion::matchAndRewrite(FuncOp funcOp, ArrayRef operands, auto context = rewriter.getContext(); auto entryPointAttr = spirv::getEntryPointABIAttr(workGroupSizeAsInt32, context); - FuncOp newFuncOp = spirv::lowerAsEntryFunction( - funcOp, typeConverter, rewriter, argABI, entryPointAttr); + FuncOp newFuncOp = lowerAsEntryFunction(funcOp, typeConverter, rewriter, + entryPointAttr, argABI); if (!newFuncOp) { return matchFailure(); } @@ -196,6 +291,65 @@ KernelFnConversion::matchAndRewrite(FuncOp funcOp, ArrayRef operands, return matchSuccess(); } +//===----------------------------------------------------------------------===// +// ModuleOp with gpu.kernel_module. +//===----------------------------------------------------------------------===// + +PatternMatchResult KernelModuleConversion::matchAndRewrite( + ModuleOp moduleOp, ArrayRef operands, + ConversionPatternRewriter &rewriter) const { + if (!moduleOp.getAttrOfType( + gpu::GPUDialect::getKernelModuleAttrName())) { + return matchFailure(); + } + // TODO : Generalize this to account for different extensions, + // capabilities, extended_instruction_sets, other addressing models + // and memory models. + auto spvModule = rewriter.create( + moduleOp.getLoc(), spirv::AddressingModel::Logical, + spirv::MemoryModel::GLSL450, spirv::Capability::Shader, + spirv::Extension::SPV_KHR_storage_buffer_storage_class); + // Move the region from the module op into the SPIR-V module. + Region &spvModuleRegion = spvModule.getOperation()->getRegion(0); + rewriter.inlineRegionBefore(moduleOp.getBodyRegion(), spvModuleRegion, + spvModuleRegion.begin()); + // The spv.module build method adds a block with a terminator. Remove that + // block. The terminator of the module op in the remaining block will be + // legalized later. + spvModuleRegion.back().erase(); + rewriter.eraseOp(moduleOp); + return matchSuccess(); +} + +//===----------------------------------------------------------------------===// +// ModuleTerminatorOp for gpu.kernel_module. +//===----------------------------------------------------------------------===// + +PatternMatchResult KernelModuleTerminatorConversion::matchAndRewrite( + ModuleTerminatorOp terminatorOp, ArrayRef operands, + ConversionPatternRewriter &rewriter) const { + rewriter.replaceOpWithNewOp(terminatorOp); + return matchSuccess(); +} + +//===----------------------------------------------------------------------===// +// GPU return inside kernel functions to SPIR-V return. +//===----------------------------------------------------------------------===// + +PatternMatchResult GPUReturnOpConversion::matchAndRewrite( + gpu::ReturnOp returnOp, ArrayRef operands, + ConversionPatternRewriter &rewriter) const { + if (!operands.empty()) + return matchFailure(); + + rewriter.replaceOpWithNewOp(returnOp); + return matchSuccess(); +} + +//===----------------------------------------------------------------------===// +// GPU To SPIRV Patterns. +//===----------------------------------------------------------------------===// + namespace mlir { void populateGPUToSPIRVPatterns(MLIRContext *context, SPIRVTypeConverter &typeConverter, @@ -203,7 +357,8 @@ void populateGPUToSPIRVPatterns(MLIRContext *context, ArrayRef workGroupSize) { patterns.insert(context, typeConverter, workGroupSize); patterns.insert< - ForOpConversion, + GPUReturnOpConversion, ForOpConversion, KernelModuleConversion, + KernelModuleTerminatorConversion, LaunchConfigConversion, LaunchConfigConversion, LaunchConfigConversion, diff --git a/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRVPass.cpp b/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRVPass.cpp index cec71ca9b3f..b8fe27e92a2 100644 --- a/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRVPass.cpp +++ b/third_party/mlir/lib/Conversion/GPUToSPIRV/ConvertGPUToSPIRVPass.cpp @@ -67,34 +67,19 @@ void GPUToSPIRVPass::runOnModule() { auto context = &getContext(); auto module = getModule(); - SmallVector spirvModules; - module.walk([&module, &spirvModules](FuncOp funcOp) { - if (!gpu::GPUDialect::isKernel(funcOp)) { - return; + SmallVector kernelModules; + OpBuilder builder(context); + module.walk([&builder, &kernelModules](ModuleOp moduleOp) { + if (moduleOp.getAttrOfType( + gpu::GPUDialect::getKernelModuleAttrName())) { + // For each kernel module (should be only 1 for now, but that is not a + // requirement here), clone the module for conversion because the + // gpu.launch function still needs the kernel module. + builder.setInsertionPoint(moduleOp.getOperation()); + kernelModules.push_back(builder.clone(*moduleOp.getOperation())); } - OpBuilder builder(funcOp.getOperation()); - // Create a new spirv::ModuleOp for this function, and clone the - // function into it. - // TODO : Generalize this to account for different extensions, - // capabilities, extended_instruction_sets, other addressing models - // and memory models. - auto spvModule = builder.create( - funcOp.getLoc(), - builder.getI32IntegerAttr( - static_cast(spirv::AddressingModel::Logical)), - builder.getI32IntegerAttr( - static_cast(spirv::MemoryModel::GLSL450)), - builder.getStrArrayAttr( - spirv::stringifyCapability(spirv::Capability::Shader)), - builder.getStrArrayAttr(spirv::stringifyExtension( - spirv::Extension::SPV_KHR_storage_buffer_storage_class))); - // Hardwire the capability to be Shader. - OpBuilder moduleBuilder(spvModule.getOperation()->getRegion(0)); - moduleBuilder.clone(*funcOp.getOperation()); - spirvModules.push_back(spvModule); }); - /// Dialect conversion to lower the functions with the spirv::ModuleOps. SPIRVTypeConverter typeConverter; OwningRewritePatternList patterns; populateGPUToSPIRVPatterns(context, typeConverter, patterns, workGroupSize); @@ -105,7 +90,7 @@ void GPUToSPIRVPass::runOnModule() { target.addDynamicallyLegalOp( [&](FuncOp op) { return typeConverter.isSignatureLegal(op.getType()); }); - if (failed(applyFullConversion(spirvModules, target, patterns, + if (failed(applyFullConversion(kernelModules, target, patterns, &typeConverter))) { return signalPassFailure(); } diff --git a/third_party/mlir/lib/Conversion/LinalgToLLVM/LinalgToLLVM.cpp b/third_party/mlir/lib/Conversion/LinalgToLLVM/LinalgToLLVM.cpp index d161e99b30f..3eb23c19dc7 100644 --- a/third_party/mlir/lib/Conversion/LinalgToLLVM/LinalgToLLVM.cpp +++ b/third_party/mlir/lib/Conversion/LinalgToLLVM/LinalgToLLVM.cpp @@ -100,21 +100,6 @@ static Type convertLinalgType(Type t, LLVMTypeConverter &lowering) { auto int64Ty = lowering.convertType(IntegerType::get(64, context)) .cast(); - // A buffer descriptor contains the pointer to a flat region of storage and - // the size of the region. - // - // template - // struct { - // void *baseAlloc; - // Elem *ptr; - // int64_t size; - // }; - if (auto bufferType = t.dyn_cast()) { - auto voidPtrTy = LLVMType::getInt8Ty(lowering.getDialect()).getPointerTo(); - auto ptrTy = getPtrToElementType(bufferType, lowering); - return LLVMType::getStructTy(voidPtrTy, ptrTy, int64Ty); - } - // Range descriptor contains the range bounds and the step as 64-bit integers. // // struct { @@ -568,6 +553,6 @@ mlir::linalg::createConvertLinalgToLLVMPass() { return std::make_unique(); } -static PassRegistration - pass("convert-linalg-to-llvm", - "Convert the operations from the linalg dialect into the LLVM dialect"); +static PassRegistration pass( + "convert-linalg-to-llvm", + "Convert the operations from the linalg dialect into the LLVM dialect"); diff --git a/third_party/mlir/lib/Conversion/LoopToStandard/ConvertLoopToStandard.cpp b/third_party/mlir/lib/Conversion/LoopToStandard/ConvertLoopToStandard.cpp index 08ee320f7d9..ff93ce58fd4 100644 --- a/third_party/mlir/lib/Conversion/LoopToStandard/ConvertLoopToStandard.cpp +++ b/third_party/mlir/lib/Conversion/LoopToStandard/ConvertLoopToStandard.cpp @@ -38,8 +38,8 @@ using namespace mlir::loop; namespace { -struct LoopToStandardPass : public FunctionPass { - void runOnFunction() override; +struct LoopToStandardPass : public OperationPass { + void runOnOperation() override; }; // Create a CFG subgraph for the loop around its body blocks (if the body @@ -261,16 +261,16 @@ void mlir::populateLoopToStdConversionPatterns( patterns.insert(ctx); } -void LoopToStandardPass::runOnFunction() { +void LoopToStandardPass::runOnOperation() { OwningRewritePatternList patterns; populateLoopToStdConversionPatterns(patterns, &getContext()); ConversionTarget target(getContext()); target.addLegalDialect(); - if (failed(applyPartialConversion(getFunction(), target, patterns))) + if (failed(applyPartialConversion(getOperation(), target, patterns))) signalPassFailure(); } -std::unique_ptr> mlir::createLowerToCFGPass() { +std::unique_ptr mlir::createLowerToCFGPass() { return std::make_unique(); } diff --git a/third_party/mlir/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp b/third_party/mlir/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp index 60fa8dddd37..fa6512010c8 100644 --- a/third_party/mlir/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp +++ b/third_party/mlir/lib/Conversion/StandardToLLVM/ConvertStandardToLLVM.cpp @@ -123,9 +123,15 @@ LLVM::LLVMType LLVMTypeConverter::convertFunctionSignature( FunctionType type, bool isVariadic, LLVMTypeConverter::SignatureConversion &result) { // Convert argument types one by one and check for errors. - for (auto &en : llvm::enumerate(type.getInputs())) - if (failed(convertSignatureArg(en.index(), en.value(), result))) + for (auto &en : llvm::enumerate(type.getInputs())) { + Type type = en.value(); + auto converted = convertType(type).dyn_cast_or_null(); + if (!converted) return {}; + if (type.isa() || type.isa()) + converted = converted.getPointerTo(); + result.addInputs(en.index(), converted); + } SmallVector argTypes; argTypes.reserve(llvm::size(result.getConvertedTypes())); @@ -522,41 +528,23 @@ struct FuncOpConversion : public LLVMLegalizationPattern { ConversionPatternRewriter &rewriter) const override { auto funcOp = cast(op); FunctionType type = funcOp.getType(); - // Pack the result types into a struct. - Type packedResult; - if (type.getNumResults() != 0) - if (!(packedResult = lowering.packFunctionResults(type.getResults()))) - return matchFailure(); - LLVM::LLVMType resultType = packedResult - ? packedResult.cast() - : LLVM::LLVMType::getVoidTy(&dialect); - SmallVector argTypes; - argTypes.reserve(type.getNumInputs()); + // Store the positions of memref-typed arguments so that we can emit loads + // from them to follow the calling convention. SmallVector promotedArgIndices; promotedArgIndices.reserve(type.getNumInputs()); + for (auto en : llvm::enumerate(type.getInputs())) { + if (en.value().isa() || en.value().isa()) + promotedArgIndices.push_back(en.index()); + } // Convert the original function arguments. Struct arguments are promoted to // pointer to struct arguments to allow calling external functions with // various ABIs (e.g. compiled from C/C++ on platform X). auto varargsAttr = funcOp.getAttrOfType("std.varargs"); TypeConverter::SignatureConversion result(funcOp.getNumArguments()); - for (auto en : llvm::enumerate(type.getInputs())) { - auto t = en.value(); - auto converted = lowering.convertType(t).dyn_cast(); - if (!converted) - return matchFailure(); - if (t.isa() || t.isa()) { - converted = converted.getPointerTo(); - promotedArgIndices.push_back(en.index()); - } - argTypes.push_back(converted); - } - for (unsigned idx = 0, e = argTypes.size(); idx < e; ++idx) - result.addInputs(idx, argTypes[idx]); - - auto llvmType = LLVM::LLVMType::getFunctionTy( - resultType, argTypes, varargsAttr && varargsAttr.getValue()); + auto llvmType = lowering.convertFunctionSignature( + funcOp.getType(), varargsAttr && varargsAttr.getValue(), result); // Only retain those attributes that are not constructed by build. SmallVector attributes; @@ -610,12 +598,12 @@ struct NDVectorTypeInfo { } // namespace // For >1-D vector types, extracts the necessary information to iterate over all -// 1-D subvectors in the underlying llrepresentation of the n-D vecotr +// 1-D subvectors in the underlying llrepresentation of the n-D vector // Iterates on the llvm array type until we hit a non-array type (which is // asserted to be an llvm vector type). static NDVectorTypeInfo extractNDVectorTypeInfo(VectorType vectorType, LLVMTypeConverter &converter) { - assert(vectorType.getRank() > 1 && "extpected >1D vector type"); + assert(vectorType.getRank() > 1 && "expected >1D vector type"); NDVectorTypeInfo info; info.llvmArrayTy = converter.convertType(vectorType).dyn_cast(); @@ -812,6 +800,15 @@ using BinaryOpLLVMOpLowering = NaryOpLLVMOpLowering; struct ExpOpLowering : public UnaryOpLLVMOpLowering { using Super::Super; }; +struct LogOpLowering : public UnaryOpLLVMOpLowering { + using Super::Super; +}; +struct Log10OpLowering : public UnaryOpLLVMOpLowering { + using Super::Super; +}; +struct Log2OpLowering : public UnaryOpLLVMOpLowering { + using Super::Super; +}; struct AddIOpLowering : public BinaryOpLLVMOpLowering { using Super::Super; }; @@ -1110,9 +1107,8 @@ struct CallOpInterfaceLowering : public LLVMLegalizationPattern { return this->matchFailure(); } - SmallVector opOperands(op->getOperands()); auto promoted = this->lowering.promoteMemRefDescriptors( - op->getLoc(), opOperands, operands, rewriter); + op->getLoc(), /*opOperands=*/op->getOperands(), operands, rewriter); auto newOp = rewriter.create(op->getLoc(), packedResult, promoted, op->getAttrs()); @@ -1192,6 +1188,56 @@ struct DeallocOpLowering : public LLVMLegalizationPattern { bool useAlloca; }; +// A `tanh` is converted into a call to the `tanh` function. +struct TanhOpLowering : public LLVMLegalizationPattern { + using LLVMLegalizationPattern::LLVMLegalizationPattern; + + PatternMatchResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + + using LLVMFuncOpT = LLVM::LLVMFuncOp; + using LLVMTypeT = LLVM::LLVMType; + + OperandAdaptor transformed(operands); + LLVMTypeT operandType = + transformed.operand()->getType().dyn_cast_or_null(); + + if (!operandType) + return matchFailure(); + + std::string functionName; + if (operandType.isFloatTy()) + functionName = "tanhf"; + else if (operandType.isDoubleTy()) + functionName = "tanh"; + else + return matchFailure(); + + // Get a reference to the tanh function, inserting it if necessary. + Operation *tanhFunc = + SymbolTable::lookupNearestSymbolFrom(op, functionName); + + LLVMFuncOpT tanhLLVMFunc; + if (tanhFunc) { + tanhLLVMFunc = cast(tanhFunc); + } else { + PatternRewriter::InsertionGuard insertGuard(rewriter); + auto module = op->getParentOfType(); + rewriter.setInsertionPointToStart(module.getBody()); + tanhLLVMFunc = rewriter.create( + module.getLoc(), functionName, + LLVMTypeT::getFunctionTy(operandType, operandType, + /*isVarArg=*/false)); + } + + rewriter.replaceOpWithNewOp( + op, operandType, rewriter.getSymbolRefAttr(tanhLLVMFunc), + transformed.operand()); + return matchSuccess(); + } +}; + struct MemRefCastOpLowering : public LLVMLegalizationPattern { using LLVMLegalizationPattern::LLVMLegalizationPattern; @@ -1240,7 +1286,7 @@ struct MemRefCastOpLowering : public LLVMLegalizationPattern { } else if (srcType.isa() && dstType.isa()) { // Casting ranked to unranked memref type // Set the rank in the destination from the memref type - // Allocate space on the stack and copy the src memref decsriptor + // Allocate space on the stack and copy the src memref descriptor // Set the ptr in the destination to the stack space auto srcMemRefType = srcType.cast(); int64_t rank = srcMemRefType.getRank(); @@ -2005,6 +2051,9 @@ void mlir::populateStdToLLVMConversionPatterns( DivISOpLowering, DivIUOpLowering, ExpOpLowering, + LogOpLowering, + Log10OpLowering, + Log2OpLowering, FPExtLowering, FPTruncLowering, FuncOpConversion, @@ -2027,6 +2076,7 @@ void mlir::populateStdToLLVMConversionPatterns( SubFOpLowering, SubIOpLowering, SubViewOpLowering, + TanhOpLowering, TruncateIOpLowering, ViewOpLowering, XOrOpLowering, @@ -2078,9 +2128,10 @@ Value *LLVMTypeConverter::promoteOneMemRefDescriptor(Location loc, return allocated; } -SmallVector LLVMTypeConverter::promoteMemRefDescriptors( - Location loc, ArrayRef opOperands, ArrayRef operands, - OpBuilder &builder) { +SmallVector +LLVMTypeConverter::promoteMemRefDescriptors(Location loc, ValueRange opOperands, + ValueRange operands, + OpBuilder &builder) { SmallVector promotedOperands; promotedOperands.reserve(operands.size()); for (auto it : llvm::zip(opOperands, operands)) { diff --git a/third_party/mlir/lib/Conversion/StandardToSPIRV/StandardToSPIRV.td b/third_party/mlir/lib/Conversion/StandardToSPIRV/StandardToSPIRV.td index 9ebbea6ffa5..6f3a6a82476 100644 --- a/third_party/mlir/lib/Conversion/StandardToSPIRV/StandardToSPIRV.td +++ b/third_party/mlir/lib/Conversion/StandardToSPIRV/StandardToSPIRV.td @@ -21,7 +21,10 @@ class BinaryOpPattern : (tgt $l, $r)>; def : BinaryOpPattern; +def : BinaryOpPattern; def : BinaryOpPattern; +def : BinaryOpPattern; +def : BinaryOpPattern; // Constant Op // TODO(ravishankarm): Handle lowering other constant types. diff --git a/third_party/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp b/third_party/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp index 8adc415f820..71bed9516ca 100644 --- a/third_party/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp +++ b/third_party/mlir/lib/Conversion/VectorToLLVM/ConvertVectorToLLVM.cpp @@ -49,6 +49,50 @@ static LLVM::LLVMType getPtrToElementType(T containerType, .getPointerTo(); } +// Helper to reduce vector type by one rank at front. +static VectorType reducedVectorTypeFront(VectorType tp) { + assert((tp.getRank() > 1) && "unlowerable vector type"); + return VectorType::get(tp.getShape().drop_front(), tp.getElementType()); +} + +// Helper to reduce vector type by *all* but one rank at back. +static VectorType reducedVectorTypeBack(VectorType tp) { + assert((tp.getRank() > 1) && "unlowerable vector type"); + return VectorType::get(tp.getShape().take_back(), tp.getElementType()); +} + +// Helper that picks the proper sequence for inserting. +static Value *insertOne(ConversionPatternRewriter &rewriter, + LLVMTypeConverter &lowering, Location loc, Value *val1, + Value *val2, Type llvmType, int64_t rank, int64_t pos) { + if (rank == 1) { + auto idxType = rewriter.getIndexType(); + auto constant = rewriter.create( + loc, lowering.convertType(idxType), + rewriter.getIntegerAttr(idxType, pos)); + return rewriter.create(loc, llvmType, val1, val2, + constant); + } + return rewriter.create(loc, llvmType, val1, val2, + rewriter.getI64ArrayAttr(pos)); +} + +// Helper that picks the proper sequence for extracting. +static Value *extractOne(ConversionPatternRewriter &rewriter, + LLVMTypeConverter &lowering, Location loc, Value *val, + Type llvmType, int64_t rank, int64_t pos) { + if (rank == 1) { + auto idxType = rewriter.getIndexType(); + auto constant = rewriter.create( + loc, lowering.convertType(idxType), + rewriter.getIntegerAttr(idxType, pos)); + return rewriter.create(loc, llvmType, val, + constant); + } + return rewriter.create(loc, llvmType, val, + rewriter.getI64ArrayAttr(pos)); +} + class VectorBroadcastOpConversion : public LLVMOpLowering { public: explicit VectorBroadcastOpConversion(MLIRContext *context, @@ -65,11 +109,12 @@ public: return matchFailure(); // Rewrite when the full vector type can be lowered (which // implies all 'reduced' types can be lowered too). + auto adaptor = vector::BroadcastOpOperandAdaptor(operands); VectorType srcVectorType = broadcastOp.getSourceType().dyn_cast(); rewriter.replaceOp( - op, expandRanks(operands[0], // source value to be expanded - op->getLoc(), // location of original broadcast + op, expandRanks(adaptor.source(), // source value to be expanded + op->getLoc(), // location of original broadcast srcVectorType, dstVectorType, rewriter)); return matchSuccess(); } @@ -130,16 +175,19 @@ private: assert((llvmType != nullptr) && "unlowerable vector type"); if (rank == 1) { Value *undef = rewriter.create(loc, llvmType); - Value *expand = insertOne(undef, value, loc, llvmType, rank, 0, rewriter); + Value *expand = + insertOne(rewriter, lowering, loc, undef, value, llvmType, rank, 0); SmallVector zeroValues(dim, 0); return rewriter.create( loc, expand, undef, rewriter.getI32ArrayAttr(zeroValues)); } - Value *expand = expandRanks(value, loc, srcVectorType, - reducedVectorType(dstVectorType), rewriter); + Value *expand = + expandRanks(value, loc, srcVectorType, + reducedVectorTypeFront(dstVectorType), rewriter); Value *result = rewriter.create(loc, llvmType); for (int64_t d = 0; d < dim; ++d) { - result = insertOne(result, expand, loc, llvmType, rank, d, rewriter); + result = + insertOne(rewriter, lowering, loc, result, expand, llvmType, rank, d); } return result; } @@ -169,68 +217,86 @@ private: Value *result = rewriter.create(loc, llvmType); bool atStretch = dim != srcVectorType.getDimSize(0); if (rank == 1) { + assert(atStretch); Type redLlvmType = lowering.convertType(dstVectorType.getElementType()); - if (atStretch) { - Value *one = extractOne(value, loc, redLlvmType, rank, 0, rewriter); - Value *expand = - insertOne(result, one, loc, llvmType, rank, 0, rewriter); - SmallVector zeroValues(dim, 0); - return rewriter.create( - loc, expand, result, rewriter.getI32ArrayAttr(zeroValues)); - } - for (int64_t d = 0; d < dim; ++d) { - Value *one = extractOne(value, loc, redLlvmType, rank, d, rewriter); - result = insertOne(result, one, loc, llvmType, rank, d, rewriter); - } - } else { - VectorType redSrcType = reducedVectorType(srcVectorType); - VectorType redDstType = reducedVectorType(dstVectorType); - Type redLlvmType = lowering.convertType(redSrcType); - for (int64_t d = 0; d < dim; ++d) { - int64_t pos = atStretch ? 0 : d; - Value *one = extractOne(value, loc, redLlvmType, rank, pos, rewriter); - Value *expand = expandRanks(one, loc, redSrcType, redDstType, rewriter); - result = insertOne(result, expand, loc, llvmType, rank, d, rewriter); - } + Value *one = + extractOne(rewriter, lowering, loc, value, redLlvmType, rank, 0); + Value *expand = + insertOne(rewriter, lowering, loc, result, one, llvmType, rank, 0); + SmallVector zeroValues(dim, 0); + return rewriter.create( + loc, expand, result, rewriter.getI32ArrayAttr(zeroValues)); + } + VectorType redSrcType = reducedVectorTypeFront(srcVectorType); + VectorType redDstType = reducedVectorTypeFront(dstVectorType); + Type redLlvmType = lowering.convertType(redSrcType); + for (int64_t d = 0; d < dim; ++d) { + int64_t pos = atStretch ? 0 : d; + Value *one = + extractOne(rewriter, lowering, loc, value, redLlvmType, rank, pos); + Value *expand = expandRanks(one, loc, redSrcType, redDstType, rewriter); + result = + insertOne(rewriter, lowering, loc, result, expand, llvmType, rank, d); } return result; } +}; - // Picks the proper sequence for inserting. - Value *insertOne(Value *val1, Value *val2, Location loc, Type llvmType, - int64_t rank, int64_t pos, - ConversionPatternRewriter &rewriter) const { - if (rank == 1) { - auto idxType = rewriter.getIndexType(); - auto constant = rewriter.create( - loc, lowering.convertType(idxType), - rewriter.getIntegerAttr(idxType, pos)); - return rewriter.create(loc, llvmType, val1, val2, - constant); +class VectorShuffleOpConversion : public LLVMOpLowering { +public: + explicit VectorShuffleOpConversion(MLIRContext *context, + LLVMTypeConverter &typeConverter) + : LLVMOpLowering(vector::ShuffleOp::getOperationName(), context, + typeConverter) {} + + PatternMatchResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + auto loc = op->getLoc(); + auto adaptor = vector::ShuffleOpOperandAdaptor(operands); + auto shuffleOp = cast(op); + auto v1Type = shuffleOp.getV1VectorType(); + auto v2Type = shuffleOp.getV2VectorType(); + auto vectorType = shuffleOp.getVectorType(); + Type llvmType = lowering.convertType(vectorType); + auto maskArrayAttr = shuffleOp.mask(); + + // Bail if result type cannot be lowered. + if (!llvmType) + return matchFailure(); + + // Get rank and dimension sizes. + int64_t rank = vectorType.getRank(); + assert(v1Type.getRank() == rank); + assert(v2Type.getRank() == rank); + int64_t v1Dim = v1Type.getDimSize(0); + + // For rank 1, where both operands have *exactly* the same vector type, + // there is direct shuffle support in LLVM. Use it! + if (rank == 1 && v1Type == v2Type) { + Value *shuffle = rewriter.create( + loc, adaptor.v1(), adaptor.v2(), maskArrayAttr); + rewriter.replaceOp(op, shuffle); + return matchSuccess(); } - return rewriter.create(loc, llvmType, val1, val2, - rewriter.getI64ArrayAttr(pos)); - } - // Picks the proper sequence for extracting. - Value *extractOne(Value *value, Location loc, Type llvmType, int64_t rank, - int64_t pos, ConversionPatternRewriter &rewriter) const { - if (rank == 1) { - auto idxType = rewriter.getIndexType(); - auto constant = rewriter.create( - loc, lowering.convertType(idxType), - rewriter.getIntegerAttr(idxType, pos)); - return rewriter.create(loc, llvmType, value, - constant); + // For all other cases, insert the individual values individually. + Value *insert = rewriter.create(loc, llvmType); + int64_t insPos = 0; + for (auto en : llvm::enumerate(maskArrayAttr)) { + int64_t extPos = en.value().cast().getInt(); + Value *value = adaptor.v1(); + if (extPos >= v1Dim) { + extPos -= v1Dim; + value = adaptor.v2(); + } + Value *extract = + extractOne(rewriter, lowering, loc, value, llvmType, rank, extPos); + insert = insertOne(rewriter, lowering, loc, insert, extract, llvmType, + rank, insPos++); } - return rewriter.create(loc, llvmType, value, - rewriter.getI64ArrayAttr(pos)); - } - - // Helper to reduce vector type by one rank. - static VectorType reducedVectorType(VectorType tp) { - assert((tp.getRank() > 1) && "unlowerable vector type"); - return VectorType::get(tp.getShape().drop_front(), tp.getElementType()); + rewriter.replaceOp(op, insert); + return matchSuccess(); } }; @@ -238,6 +304,31 @@ class VectorExtractElementOpConversion : public LLVMOpLowering { public: explicit VectorExtractElementOpConversion(MLIRContext *context, LLVMTypeConverter &typeConverter) + : LLVMOpLowering(vector::ExtractElementOp::getOperationName(), context, + typeConverter) {} + + PatternMatchResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + auto adaptor = vector::ExtractElementOpOperandAdaptor(operands); + auto extractEltOp = cast(op); + auto vectorType = extractEltOp.getVectorType(); + auto llvmType = lowering.convertType(vectorType.getElementType()); + + // Bail if result type cannot be lowered. + if (!llvmType) + return matchFailure(); + + rewriter.replaceOpWithNewOp( + op, llvmType, adaptor.vector(), adaptor.position()); + return matchSuccess(); + } +}; + +class VectorExtractOpConversion : public LLVMOpLowering { +public: + explicit VectorExtractOpConversion(MLIRContext *context, + LLVMTypeConverter &typeConverter) : LLVMOpLowering(vector::ExtractOp::getOperationName(), context, typeConverter) {} @@ -247,11 +338,15 @@ public: auto loc = op->getLoc(); auto adaptor = vector::ExtractOpOperandAdaptor(operands); auto extractOp = cast(op); - auto vectorType = extractOp.vector()->getType().cast(); + auto vectorType = extractOp.getVectorType(); auto resultType = extractOp.getResult()->getType(); auto llvmResultType = lowering.convertType(resultType); - auto positionArrayAttr = extractOp.position(); + + // Bail if result type cannot be lowered. + if (!llvmResultType) + return matchFailure(); + // One-shot extraction of vector from array (only requires extractvalue). if (resultType.isa()) { Value *extracted = rewriter.create( @@ -260,15 +355,12 @@ public: return matchSuccess(); } - // Potential extraction of 1-D vector from struct. + // Potential extraction of 1-D vector from array. auto *context = op->getContext(); Value *extracted = adaptor.vector(); auto positionAttrs = positionArrayAttr.getValue(); - auto i32Type = rewriter.getIntegerType(32); if (positionAttrs.size() > 1) { - auto nDVectorType = vectorType; - auto oneDVectorType = VectorType::get(nDVectorType.getShape().take_back(), - nDVectorType.getElementType()); + auto oneDVectorType = reducedVectorTypeBack(vectorType); auto nMinusOnePositionAttrs = ArrayAttr::get(positionAttrs.drop_back(), context); extracted = rewriter.create( @@ -278,8 +370,8 @@ public: // Remaining extraction of element from 1-D LLVM vector auto position = positionAttrs.back().cast(); - auto constant = rewriter.create( - loc, lowering.convertType(i32Type), position); + auto i32Type = LLVM::LLVMType::getInt32Ty(lowering.getDialect()); + auto constant = rewriter.create(loc, i32Type, position); extracted = rewriter.create(loc, extracted, constant); rewriter.replaceOp(op, extracted); @@ -288,6 +380,98 @@ public: } }; +class VectorInsertElementOpConversion : public LLVMOpLowering { +public: + explicit VectorInsertElementOpConversion(MLIRContext *context, + LLVMTypeConverter &typeConverter) + : LLVMOpLowering(vector::InsertElementOp::getOperationName(), context, + typeConverter) {} + + PatternMatchResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + auto adaptor = vector::InsertElementOpOperandAdaptor(operands); + auto insertEltOp = cast(op); + auto vectorType = insertEltOp.getDestVectorType(); + auto llvmType = lowering.convertType(vectorType); + + // Bail if result type cannot be lowered. + if (!llvmType) + return matchFailure(); + + rewriter.replaceOpWithNewOp( + op, llvmType, adaptor.dest(), adaptor.source(), adaptor.position()); + return matchSuccess(); + } +}; + +class VectorInsertOpConversion : public LLVMOpLowering { +public: + explicit VectorInsertOpConversion(MLIRContext *context, + LLVMTypeConverter &typeConverter) + : LLVMOpLowering(vector::InsertOp::getOperationName(), context, + typeConverter) {} + + PatternMatchResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + auto loc = op->getLoc(); + auto adaptor = vector::InsertOpOperandAdaptor(operands); + auto insertOp = cast(op); + auto sourceType = insertOp.getSourceType(); + auto destVectorType = insertOp.getDestVectorType(); + auto llvmResultType = lowering.convertType(destVectorType); + auto positionArrayAttr = insertOp.position(); + + // Bail if result type cannot be lowered. + if (!llvmResultType) + return matchFailure(); + + // One-shot insertion of a vector into an array (only requires insertvalue). + if (sourceType.isa()) { + Value *inserted = rewriter.create( + loc, llvmResultType, adaptor.dest(), adaptor.source(), + positionArrayAttr); + rewriter.replaceOp(op, inserted); + return matchSuccess(); + } + + // Potential extraction of 1-D vector from array. + auto *context = op->getContext(); + Value *extracted = adaptor.dest(); + auto positionAttrs = positionArrayAttr.getValue(); + auto position = positionAttrs.back().cast(); + auto oneDVectorType = destVectorType; + if (positionAttrs.size() > 1) { + oneDVectorType = reducedVectorTypeBack(destVectorType); + auto nMinusOnePositionAttrs = + ArrayAttr::get(positionAttrs.drop_back(), context); + extracted = rewriter.create( + loc, lowering.convertType(oneDVectorType), extracted, + nMinusOnePositionAttrs); + } + + // Insertion of an element into a 1-D LLVM vector. + auto i32Type = LLVM::LLVMType::getInt32Ty(lowering.getDialect()); + auto constant = rewriter.create(loc, i32Type, position); + Value *inserted = rewriter.create( + loc, lowering.convertType(oneDVectorType), extracted, adaptor.source(), + constant); + + // Potential insertion of resulting 1-D vector into array. + if (positionAttrs.size() > 1) { + auto nMinusOnePositionAttrs = + ArrayAttr::get(positionAttrs.drop_back(), context); + inserted = rewriter.create(loc, llvmResultType, + adaptor.dest(), inserted, + nMinusOnePositionAttrs); + } + + rewriter.replaceOp(op, inserted); + return matchSuccess(); + } +}; + class VectorOuterProductOpConversion : public LLVMOpLowering { public: explicit VectorOuterProductOpConversion(MLIRContext *context, @@ -431,7 +615,9 @@ public: /// Populate the given list with patterns that convert from Vector to LLVM. void mlir::populateVectorToLLVMConversionPatterns( LLVMTypeConverter &converter, OwningRewritePatternList &patterns) { - patterns.insert( converter.getDialect()->getContext(), converter); } diff --git a/third_party/mlir/lib/Conversion/VectorToLoops/ConvertVectorToLoops.cpp b/third_party/mlir/lib/Conversion/VectorToLoops/ConvertVectorToLoops.cpp index 43ad91ce878..d4f362d685d 100644 --- a/third_party/mlir/lib/Conversion/VectorToLoops/ConvertVectorToLoops.cpp +++ b/third_party/mlir/lib/Conversion/VectorToLoops/ConvertVectorToLoops.cpp @@ -21,8 +21,8 @@ #include -#include "mlir/Dialect/VectorOps/VectorOps.h" #include "mlir/Conversion/VectorToLoops/ConvertVectorToLoops.h" +#include "mlir/Dialect/VectorOps/VectorOps.h" #include "mlir/EDSC/Builders.h" #include "mlir/EDSC/Helpers.h" #include "mlir/IR/AffineExpr.h" @@ -51,7 +51,7 @@ using vector_type_cast = edsc::intrinsics::ValueBuilder; /// /// Consider the case: /// -/// ```mlir {.mlir} +/// ```mlir /// // Read the slice `%A[%i0, %i1:%i1+256, %i2:%i2+32]` into /// // vector<32x256xf32> and pad with %f0 to handle the boundary case: /// %f0 = constant 0.0f : f32 @@ -68,7 +68,7 @@ using vector_type_cast = edsc::intrinsics::ValueBuilder; /// resembling the following (while guaranteeing an always full-tile /// abstraction): /// -/// ```mlir {.mlir} +/// ```mlir /// loop.for %d2 = 0 to %c256 { /// loop.for %d1 = 0 to %c32 { /// %s = %A[%i0, %i1 + %d1, %i2 + %d2] : f32 @@ -270,7 +270,7 @@ PatternMatchResult VectorTransferRewriter::matchAndRewrite( VectorView vectorView(transfer.vector()); SmallVector ivs = makeIndexHandles(vectorView.rank()); SmallVector pivs = - makeIndexHandlePointers(MutableArrayRef(ivs)); + makeHandlePointers(MutableArrayRef(ivs)); coalesceCopy(transfer, &pivs, &vectorView); auto lbs = vectorView.getLbs(); @@ -332,7 +332,8 @@ PatternMatchResult VectorTransferRewriter::matchAndRewrite( ValueHandle vectorValue(transfer.vector()); VectorView vectorView(transfer.vector()); SmallVector ivs = makeIndexHandles(vectorView.rank()); - SmallVector pivs = makeIndexHandlePointers(ivs); + SmallVector pivs = + makeHandlePointers(MutableArrayRef(ivs)); coalesceCopy(transfer, &pivs, &vectorView); auto lbs = vectorView.getLbs(); diff --git a/third_party/mlir/lib/Dialect/AffineOps/AffineOps.cpp b/third_party/mlir/lib/Dialect/AffineOps/AffineOps.cpp index 59e5afec9ce..e58f6f8d6ed 100644 --- a/third_party/mlir/lib/Dialect/AffineOps/AffineOps.cpp +++ b/third_party/mlir/lib/Dialect/AffineOps/AffineOps.cpp @@ -99,6 +99,14 @@ AffineOpsDialect::AffineOpsDialect(MLIRContext *context) addInterfaces(); } +/// Materialize a single constant operation from a given attribute value with +/// the desired resultant type. +Operation *AffineOpsDialect::materializeConstant(OpBuilder &builder, + Attribute value, Type type, + Location loc) { + return builder.create(loc, type, value); +} + /// A utility function to check if a given region is attached to a function. static bool isFunctionRegion(Region *region) { return llvm::isa(region->getParentOp()); @@ -806,33 +814,20 @@ void AffineApplyOp::getCanonicalizationPatterns( // Common canonicalization pattern support logic //===----------------------------------------------------------------------===// -namespace { /// This is a common class used for patterns of the form /// "someop(memrefcast) -> someop". It folds the source of any memref_cast /// into the root operation directly. -struct MemRefCastFolder : public RewritePattern { - /// The rootOpName is the name of the root operation to match against. - MemRefCastFolder(StringRef rootOpName, MLIRContext *context) - : RewritePattern(rootOpName, 1, context) {} - - PatternMatchResult match(Operation *op) const override { - for (auto *operand : op->getOperands()) - if (matchPattern(operand, m_Op())) - return matchSuccess(); - - return matchFailure(); +static LogicalResult foldMemRefCast(Operation *op) { + bool folded = false; + for (OpOperand &operand : op->getOpOperands()) { + auto cast = dyn_cast_or_null(operand.get()->getDefiningOp()); + if (cast && !cast.getOperand()->getType().isa()) { + operand.set(cast.getOperand()); + folded = true; + } } - - void rewrite(Operation *op, PatternRewriter &rewriter) const override { - for (unsigned i = 0, e = op->getNumOperands(); i != e; ++i) - if (auto *memref = op->getOperand(i)->getDefiningOp()) - if (auto cast = dyn_cast(memref)) - op->setOperand(i, cast.getOperand()); - rewriter.updatedRootInPlace(op); - } -}; - -} // end anonymous namespace. + return success(folded); +} //===----------------------------------------------------------------------===// // AffineDmaStartOp @@ -997,10 +992,10 @@ LogicalResult AffineDmaStartOp::verify() { return success(); } -void AffineDmaStartOp::getCanonicalizationPatterns( - OwningRewritePatternList &results, MLIRContext *context) { +LogicalResult AffineDmaStartOp::fold(ArrayRef cstOperands, + SmallVectorImpl &results) { /// dma_start(memrefcast) -> dma_start - results.insert(getOperationName(), context); + return foldMemRefCast(*this); } //===----------------------------------------------------------------------===// @@ -1073,10 +1068,10 @@ LogicalResult AffineDmaWaitOp::verify() { return success(); } -void AffineDmaWaitOp::getCanonicalizationPatterns( - OwningRewritePatternList &results, MLIRContext *context) { +LogicalResult AffineDmaWaitOp::fold(ArrayRef cstOperands, + SmallVectorImpl &results) { /// dma_wait(memrefcast) -> dma_wait - results.insert(getOperationName(), context); + return foldMemRefCast(*this); } //===----------------------------------------------------------------------===// @@ -1247,7 +1242,8 @@ static ParseResult parseBound(bool isLower, OperationState &result, "expected valid affine map representation for loop bounds"); } -ParseResult parseAffineForOp(OpAsmParser &parser, OperationState &result) { +static ParseResult parseAffineForOp(OpAsmParser &parser, + OperationState &result) { auto &builder = parser.getBuilder(); OpAsmParser::OperandType inductionVariable; // Parse the induction variable followed by '='. @@ -1336,7 +1332,7 @@ static void printBound(AffineMapAttr boundMap, map.getNumDims(), p); } -void print(OpAsmPrinter &p, AffineForOp op) { +static void print(OpAsmPrinter &p, AffineForOp op) { p << "affine.for "; p.printOperand(op.getBody()->getArgument(0)); p << " = "; @@ -1355,6 +1351,76 @@ void print(OpAsmPrinter &p, AffineForOp op) { op.getStepAttrName()}); } +/// Fold the constant bounds of a loop. +static LogicalResult foldLoopBounds(AffineForOp forOp) { + auto foldLowerOrUpperBound = [&forOp](bool lower) { + // Check to see if each of the operands is the result of a constant. If + // so, get the value. If not, ignore it. + SmallVector operandConstants; + auto boundOperands = + lower ? forOp.getLowerBoundOperands() : forOp.getUpperBoundOperands(); + for (auto *operand : boundOperands) { + Attribute operandCst; + matchPattern(operand, m_Constant(&operandCst)); + operandConstants.push_back(operandCst); + } + + AffineMap boundMap = + lower ? forOp.getLowerBoundMap() : forOp.getUpperBoundMap(); + assert(boundMap.getNumResults() >= 1 && + "bound maps should have at least one result"); + SmallVector foldedResults; + if (failed(boundMap.constantFold(operandConstants, foldedResults))) + return failure(); + + // Compute the max or min as applicable over the results. + assert(!foldedResults.empty() && "bounds should have at least one result"); + auto maxOrMin = foldedResults[0].cast().getValue(); + for (unsigned i = 1, e = foldedResults.size(); i < e; i++) { + auto foldedResult = foldedResults[i].cast().getValue(); + maxOrMin = lower ? llvm::APIntOps::smax(maxOrMin, foldedResult) + : llvm::APIntOps::smin(maxOrMin, foldedResult); + } + lower ? forOp.setConstantLowerBound(maxOrMin.getSExtValue()) + : forOp.setConstantUpperBound(maxOrMin.getSExtValue()); + return success(); + }; + + // Try to fold the lower bound. + bool folded = false; + if (!forOp.hasConstantLowerBound()) + folded |= succeeded(foldLowerOrUpperBound(/*lower=*/true)); + + // Try to fold the upper bound. + if (!forOp.hasConstantUpperBound()) + folded |= succeeded(foldLowerOrUpperBound(/*lower=*/false)); + return success(folded); +} + +/// Canonicalize the bounds of the given loop. +static LogicalResult canonicalizeLoopBounds(AffineForOp forOp) { + SmallVector lbOperands(forOp.getLowerBoundOperands()); + SmallVector ubOperands(forOp.getUpperBoundOperands()); + + auto lbMap = forOp.getLowerBoundMap(); + auto ubMap = forOp.getUpperBoundMap(); + auto prevLbMap = lbMap; + auto prevUbMap = ubMap; + + canonicalizeMapAndOperands(&lbMap, &lbOperands); + canonicalizeMapAndOperands(&ubMap, &ubOperands); + + // Any canonicalization change always leads to updated map(s). + if (lbMap == prevLbMap && ubMap == prevUbMap) + return failure(); + + if (lbMap != prevLbMap) + forOp.setLowerBound(lbOperands, lbMap); + if (ubMap != prevUbMap) + forOp.setUpperBound(ubOperands, ubMap); + return success(); +} + namespace { /// This is a pattern to fold trivially empty loops. struct AffineForEmptyLoopFolder : public OpRewritePattern { @@ -1369,101 +1435,18 @@ struct AffineForEmptyLoopFolder : public OpRewritePattern { return matchSuccess(); } }; - -/// This is a pattern to fold constant loop bounds. -struct AffineForOpBoundFolder : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - PatternMatchResult matchAndRewrite(AffineForOp forOp, - PatternRewriter &rewriter) const override { - auto foldLowerOrUpperBound = [&forOp](bool lower) { - // Check to see if each of the operands is the result of a constant. If - // so, get the value. If not, ignore it. - SmallVector operandConstants; - auto boundOperands = - lower ? forOp.getLowerBoundOperands() : forOp.getUpperBoundOperands(); - for (auto *operand : boundOperands) { - Attribute operandCst; - matchPattern(operand, m_Constant(&operandCst)); - operandConstants.push_back(operandCst); - } - - AffineMap boundMap = - lower ? forOp.getLowerBoundMap() : forOp.getUpperBoundMap(); - assert(boundMap.getNumResults() >= 1 && - "bound maps should have at least one result"); - SmallVector foldedResults; - if (failed(boundMap.constantFold(operandConstants, foldedResults))) - return failure(); - - // Compute the max or min as applicable over the results. - assert(!foldedResults.empty() && - "bounds should have at least one result"); - auto maxOrMin = foldedResults[0].cast().getValue(); - for (unsigned i = 1, e = foldedResults.size(); i < e; i++) { - auto foldedResult = foldedResults[i].cast().getValue(); - maxOrMin = lower ? llvm::APIntOps::smax(maxOrMin, foldedResult) - : llvm::APIntOps::smin(maxOrMin, foldedResult); - } - lower ? forOp.setConstantLowerBound(maxOrMin.getSExtValue()) - : forOp.setConstantUpperBound(maxOrMin.getSExtValue()); - return success(); - }; - - // Try to fold the lower bound. - bool folded = false; - if (!forOp.hasConstantLowerBound()) - folded |= succeeded(foldLowerOrUpperBound(/*lower=*/true)); - - // Try to fold the upper bound. - if (!forOp.hasConstantUpperBound()) - folded |= succeeded(foldLowerOrUpperBound(/*lower=*/false)); - - // If any of the bounds were folded we return success. - if (!folded) - return matchFailure(); - rewriter.updatedRootInPlace(forOp); - return matchSuccess(); - } -}; - -// This is a pattern to canonicalize affine for op loop bounds. -struct AffineForOpBoundCanonicalizer : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; - - PatternMatchResult matchAndRewrite(AffineForOp forOp, - PatternRewriter &rewriter) const override { - SmallVector lbOperands(forOp.getLowerBoundOperands()); - SmallVector ubOperands(forOp.getUpperBoundOperands()); - - auto lbMap = forOp.getLowerBoundMap(); - auto ubMap = forOp.getUpperBoundMap(); - auto prevLbMap = lbMap; - auto prevUbMap = ubMap; - - canonicalizeMapAndOperands(&lbMap, &lbOperands); - canonicalizeMapAndOperands(&ubMap, &ubOperands); - - // Any canonicalization change always leads to updated map(s). - if (lbMap == prevLbMap && ubMap == prevUbMap) - return matchFailure(); - - if (lbMap != prevLbMap) - forOp.setLowerBound(lbOperands, lbMap); - if (ubMap != prevUbMap) - forOp.setUpperBound(ubOperands, ubMap); - - rewriter.updatedRootInPlace(forOp); - return matchSuccess(); - } -}; - } // end anonymous namespace void AffineForOp::getCanonicalizationPatterns(OwningRewritePatternList &results, MLIRContext *context) { - results.insert(context); + results.insert(context); +} + +LogicalResult AffineForOp::fold(ArrayRef operands, + SmallVectorImpl &results) { + bool folded = succeeded(foldLoopBounds(*this)); + folded |= succeeded(canonicalizeLoopBounds(*this)); + return success(folded); } AffineBound AffineForOp::getLowerBound() { @@ -1733,37 +1716,23 @@ void AffineIfOp::build(Builder *builder, OperationState &result, IntegerSet set, AffineIfOp::ensureTerminator(*elseRegion, *builder, result.location); } -namespace { -// This is a pattern to canonicalize an affine if op's conditional (integer -// set + operands). -struct AffineIfOpCanonicalizer : public OpRewritePattern { - using OpRewritePattern::OpRewritePattern; +/// Canonicalize an affine if op's conditional (integer set + operands). +LogicalResult AffineIfOp::fold(ArrayRef, + SmallVectorImpl &) { + auto set = getIntegerSet(); + SmallVector operands(getOperands()); + canonicalizeSetAndOperands(&set, &operands); - PatternMatchResult matchAndRewrite(AffineIfOp ifOp, - PatternRewriter &rewriter) const override { - auto set = ifOp.getIntegerSet(); - SmallVector operands(ifOp.getOperands()); - - canonicalizeSetAndOperands(&set, &operands); - - // Any canonicalization change always leads to either a reduction in the - // number of operands or a change in the number of symbolic operands - // (promotion of dims to symbols). - if (operands.size() < ifOp.getIntegerSet().getNumInputs() || - set.getNumSymbols() > ifOp.getIntegerSet().getNumSymbols()) { - ifOp.setConditional(set, operands); - rewriter.updatedRootInPlace(ifOp); - return matchSuccess(); - } - - return matchFailure(); + // Any canonicalization change always leads to either a reduction in the + // number of operands or a change in the number of symbolic operands + // (promotion of dims to symbols). + if (operands.size() < getIntegerSet().getNumInputs() || + set.getNumSymbols() > getIntegerSet().getNumSymbols()) { + setConditional(set, operands); + return success(); } -}; -} // end anonymous namespace -void AffineIfOp::getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context) { - results.insert(context); + return failure(); } //===----------------------------------------------------------------------===// @@ -1858,11 +1827,16 @@ LogicalResult AffineLoadOp::verify() { void AffineLoadOp::getCanonicalizationPatterns( OwningRewritePatternList &results, MLIRContext *context) { - /// load(memrefcast) -> load - results.insert(getOperationName(), context); results.insert>(context); } +OpFoldResult AffineLoadOp::fold(ArrayRef cstOperands) { + /// load(memrefcast) -> load + if (succeeded(foldMemRefCast(*this))) + return getResult(); + return OpFoldResult(); +} + //===----------------------------------------------------------------------===// // AffineStoreOp //===----------------------------------------------------------------------===// @@ -1951,11 +1925,15 @@ LogicalResult AffineStoreOp::verify() { void AffineStoreOp::getCanonicalizationPatterns( OwningRewritePatternList &results, MLIRContext *context) { - /// load(memrefcast) -> load - results.insert(getOperationName(), context); results.insert>(context); } +LogicalResult AffineStoreOp::fold(ArrayRef cstOperands, + SmallVectorImpl &results) { + /// store(memrefcast) -> store + return foldMemRefCast(*this); +} + //===----------------------------------------------------------------------===// // AffineMinOp //===----------------------------------------------------------------------===// @@ -1985,18 +1963,12 @@ static ParseResult parseAffineMinOp(OpAsmParser &parser, static void print(OpAsmPrinter &p, AffineMinOp op) { p << op.getOperationName() << ' ' << op.getAttr(AffineMinOp::getMapAttrName()); - auto begin = op.operand_begin(); - auto end = op.operand_end(); + auto operands = op.getOperands(); unsigned numDims = op.map().getNumDims(); - p << '('; - p.printOperands(begin, begin + numDims); - p << ')'; + p << '(' << operands.take_front(numDims) << ')'; - if (begin + numDims != end) { - p << '['; - p.printOperands(begin + numDims, end); - p << ']'; - } + if (operands.size() != numDims) + p << '[' << operands.drop_front(numDims) << ']'; p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/{"map"}); } diff --git a/third_party/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp b/third_party/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp index 05dcd90ea45..46a568caac5 100644 --- a/third_party/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp +++ b/third_party/mlir/lib/Dialect/GPU/IR/GPUDialect.cpp @@ -46,10 +46,10 @@ bool GPUDialect::isKernel(Operation *op) { GPUDialect::GPUDialect(MLIRContext *context) : Dialect(getDialectName(), context) { - addOperations(); + >(); } LogicalResult GPUDialect::verifyOperationAttribute(Operation *op, @@ -94,9 +94,9 @@ LogicalResult GPUDialect::verifyOperationAttribute(Operation *op, // Check that `launch_func` refers to a well-formed kernel function. StringRef kernelName = launchOp.kernel(); Operation *kernelFunc = kernelModule.lookupSymbol(kernelName); - auto kernelStdFunction = dyn_cast_or_null<::mlir::FuncOp>(kernelFunc); + auto kernelGPUFunction = dyn_cast_or_null(kernelFunc); auto kernelLLVMFunction = dyn_cast_or_null(kernelFunc); - if (!kernelStdFunction && !kernelLLVMFunction) + if (!kernelGPUFunction && !kernelLLVMFunction) return launchOp.emitOpError("kernel function '") << kernelName << "' is undefined"; if (!kernelFunc->getAttrOfType( @@ -107,7 +107,7 @@ LogicalResult GPUDialect::verifyOperationAttribute(Operation *op, unsigned actualNumArguments = launchOp.getNumKernelOperands(); unsigned expectedNumArguments = kernelLLVMFunction ? kernelLLVMFunction.getNumArguments() - : kernelStdFunction.getNumArguments(); + : kernelGPUFunction.getNumArguments(); if (expectedNumArguments != actualNumArguments) return launchOp.emitOpError("got ") << actualNumArguments << " kernel operands but expected " @@ -289,8 +289,7 @@ void printLaunchOp(OpAsmPrinter &p, LaunchOp op) { // Print the launch configuration. p << LaunchOp::getOperationName() << ' ' << op.getBlocksKeyword(); - printSizeAssignment(p, op.getGridSize(), - operands.drop_back(operands.size() - 3), + printSizeAssignment(p, op.getGridSize(), operands.take_front(3), op.getBlockIds()); p << ' ' << op.getThreadsKeyword(); printSizeAssignment(p, op.getBlockSize(), operands.slice(3, 3), @@ -303,25 +302,17 @@ void printLaunchOp(OpAsmPrinter &p, LaunchOp op) { // Print the data argument remapping. if (!op.body().empty() && !operands.empty()) { p << ' ' << op.getArgsKeyword() << '('; - for (unsigned i = 0, e = operands.size(); i < e; ++i) { - if (i != 0) - p << ", "; - p << *op.body().front().getArgument(LaunchOp::kNumConfigRegionAttributes + - i) + Block *entryBlock = &op.body().front(); + interleaveComma(llvm::seq(0, operands.size()), p, [&](int i) { + p << *entryBlock->getArgument(LaunchOp::kNumConfigRegionAttributes + i) << " = " << *operands[i]; - } + }); p << ") "; } // Print the types of data arguments. - if (!operands.empty()) { - p << ": "; - for (unsigned i = 0, e = operands.size(); i < e; ++i) { - if (i != 0) - p << ", "; - p << operands[i]->getType(); - } - } + if (!operands.empty()) + p << ": " << operands.getTypes(); p.printRegion(op.body(), /*printEntryBlockArgs=*/false); p.printOptionalAttrDict(op.getAttrs()); @@ -497,7 +488,7 @@ void LaunchOp::getCanonicalizationPatterns(OwningRewritePatternList &results, //===----------------------------------------------------------------------===// void LaunchFuncOp::build(Builder *builder, OperationState &result, - ::mlir::FuncOp kernelFunc, Value *gridSizeX, + GPUFuncOp kernelFunc, Value *gridSizeX, Value *gridSizeY, Value *gridSizeZ, Value *blockSizeX, Value *blockSizeY, Value *blockSizeZ, ValueRange kernelOperands) { @@ -514,7 +505,7 @@ void LaunchFuncOp::build(Builder *builder, OperationState &result, } void LaunchFuncOp::build(Builder *builder, OperationState &result, - ::mlir::FuncOp kernelFunc, KernelDim3 gridSize, + GPUFuncOp kernelFunc, KernelDim3 gridSize, KernelDim3 blockSize, ValueRange kernelOperands) { build(builder, result, kernelFunc, gridSize.x, gridSize.y, gridSize.z, blockSize.x, blockSize.y, blockSize.z, kernelOperands); @@ -545,26 +536,26 @@ KernelDim3 LaunchFuncOp::getBlockSizeOperandValues() { return KernelDim3{getOperand(3), getOperand(4), getOperand(5)}; } -LogicalResult LaunchFuncOp::verify() { - auto module = getParentOfType(); +LogicalResult verify(LaunchFuncOp op) { + auto module = op.getParentOfType(); if (!module) - return emitOpError("expected to belong to a module"); + return op.emitOpError("expected to belong to a module"); if (!module.getAttrOfType(GPUDialect::getContainerModuleAttrName())) - return emitOpError("expected the closest surrounding module to have the '" + - GPUDialect::getContainerModuleAttrName() + - "' attribute"); + return op.emitOpError( + "expected the closest surrounding module to have the '" + + GPUDialect::getContainerModuleAttrName() + "' attribute"); - auto kernelAttr = getAttrOfType(getKernelAttrName()); + auto kernelAttr = op.getAttrOfType(op.getKernelAttrName()); if (!kernelAttr) - return emitOpError("string attribute '" + getKernelAttrName() + - "' must be specified"); + return op.emitOpError("string attribute '" + op.getKernelAttrName() + + "' must be specified"); auto kernelModuleAttr = - getAttrOfType(getKernelModuleAttrName()); + op.getAttrOfType(op.getKernelModuleAttrName()); if (!kernelModuleAttr) - return emitOpError("symbol reference attribute '" + - getKernelModuleAttrName() + "' must be specified"); + return op.emitOpError("symbol reference attribute '" + + op.getKernelModuleAttrName() + "' must be specified"); return success(); } @@ -701,7 +692,7 @@ static void printAttributions(OpAsmPrinter &p, StringRef keyword, return; p << ' ' << keyword << '('; - interleaveComma(values, p.getStream(), + interleaveComma(values, p, [&p](BlockArgument *v) { p << *v << " : " << v->getType(); }); p << ')'; } @@ -727,6 +718,18 @@ void printGPUFuncOp(OpAsmPrinter &p, GPUFuncOp op) { p.printRegion(op.getBody(), /*printEntryBlockArgs=*/false); } +void GPUFuncOp::setType(FunctionType newType) { + auto oldType = getType(); + assert(newType.getNumResults() == oldType.getNumResults() && + "unimplemented: changes to the number of results"); + + SmallVector nameBuf; + for (int i = newType.getNumInputs(), e = oldType.getNumInputs(); i < e; i++) + removeAttr(getArgAttrName(i, nameBuf)); + + setAttr(getTypeAttrName(), TypeAttr::get(newType)); +} + /// Hook for FunctionLike verifier. LogicalResult GPUFuncOp::verifyType() { Type type = getTypeAttr().getValue(); diff --git a/third_party/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp b/third_party/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp index b466cc280df..416a37b3270 100644 --- a/third_party/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp +++ b/third_party/mlir/lib/Dialect/GPU/Transforms/KernelOutlining.cpp @@ -39,19 +39,21 @@ static void createForAllDimensions(OpBuilder &builder, Location loc, } } -// Add operations generating block/thread ids and gird/block dimensions at the -// beginning of `kernelFunc` and replace uses of the respective function args. -static void injectGpuIndexOperations(Location loc, FuncOp kernelFunc) { - OpBuilder OpBuilder(kernelFunc.getBody()); +// Add operations generating block/thread ids and grid/block dimensions at the +// beginning of the `body` region and replace uses of the respective function +// arguments. +static void injectGpuIndexOperations(Location loc, Region &body) { + OpBuilder builder(loc->getContext()); + Block &firstBlock = body.front(); + builder.setInsertionPointToStart(&firstBlock); SmallVector indexOps; - createForAllDimensions(OpBuilder, loc, indexOps); - createForAllDimensions(OpBuilder, loc, indexOps); - createForAllDimensions(OpBuilder, loc, indexOps); - createForAllDimensions(OpBuilder, loc, indexOps); + createForAllDimensions(builder, loc, indexOps); + createForAllDimensions(builder, loc, indexOps); + createForAllDimensions(builder, loc, indexOps); + createForAllDimensions(builder, loc, indexOps); // Replace the leading 12 function args with the respective thread/block index // operations. Iterate backwards since args are erased and indices change. for (int i = 11; i >= 0; --i) { - auto &firstBlock = kernelFunc.front(); firstBlock.getArgument(i)->replaceAllUsesWith(indexOps[i]); firstBlock.eraseArgument(i); } @@ -63,7 +65,7 @@ static bool isInliningBeneficiary(Operation *op) { // Move arguments of the given kernel function into the function if this reduces // the number of kernel arguments. -static gpu::LaunchFuncOp inlineBeneficiaryOps(FuncOp kernelFunc, +static gpu::LaunchFuncOp inlineBeneficiaryOps(gpu::GPUFuncOp kernelFunc, gpu::LaunchFuncOp launch) { OpBuilder kernelBuilder(kernelFunc.getBody()); auto &firstBlock = kernelFunc.getBody().front(); @@ -107,31 +109,30 @@ static gpu::LaunchFuncOp inlineBeneficiaryOps(FuncOp kernelFunc, // Outline the `gpu.launch` operation body into a kernel function. Replace // `gpu.return` operations by `std.return` in the generated function. -static FuncOp outlineKernelFunc(gpu::LaunchOp launchOp) { +static gpu::GPUFuncOp outlineKernelFunc(gpu::LaunchOp launchOp) { Location loc = launchOp.getLoc(); + // Create a builder with no insertion point, insertion will happen separately + // due to symbol table manipulation. + OpBuilder builder(launchOp.getContext()); + SmallVector kernelOperandTypes(launchOp.getKernelOperandTypes()); FunctionType type = FunctionType::get(kernelOperandTypes, {}, launchOp.getContext()); std::string kernelFuncName = Twine(launchOp.getParentOfType().getName(), "_kernel").str(); - FuncOp outlinedFunc = FuncOp::create(loc, kernelFuncName, type); - outlinedFunc.getBody().takeBody(launchOp.body()); - Builder builder(launchOp.getContext()); + auto outlinedFunc = builder.create(loc, kernelFuncName, type); outlinedFunc.setAttr(gpu::GPUDialect::getKernelFuncAttrName(), builder.getUnitAttr()); - injectGpuIndexOperations(loc, outlinedFunc); - outlinedFunc.walk([](gpu::ReturnOp op) { - OpBuilder replacer(op); - replacer.create(op.getLoc()); - op.erase(); - }); + outlinedFunc.body().takeBody(launchOp.body()); + injectGpuIndexOperations(loc, outlinedFunc.body()); return outlinedFunc; } // Replace `gpu.launch` operations with an `gpu.launch_func` operation launching // `kernelFunc`. The kernel func contains the body of the `gpu.launch` with // constant region arguments inlined. -static void convertToLaunchFuncOp(gpu::LaunchOp &launchOp, FuncOp kernelFunc) { +static void convertToLaunchFuncOp(gpu::LaunchOp &launchOp, + gpu::GPUFuncOp kernelFunc) { OpBuilder builder(launchOp); auto launchFuncOp = builder.create( launchOp.getLoc(), kernelFunc, launchOp.getGridSizeOperandValues(), @@ -160,7 +161,7 @@ public: // Insert just after the function. Block::iterator insertPt(func.getOperation()->getNextNode()); func.walk([&](gpu::LaunchOp op) { - FuncOp outlinedFunc = outlineKernelFunc(op); + gpu::GPUFuncOp outlinedFunc = outlineKernelFunc(op); // Create nested module and insert outlinedFunc. The module will // originally get the same name as the function, but may be renamed on @@ -183,7 +184,7 @@ public: private: // Returns a module containing kernelFunc and all callees (recursive). - ModuleOp createKernelModule(FuncOp kernelFunc, + ModuleOp createKernelModule(gpu::GPUFuncOp kernelFunc, const SymbolTable &parentSymbolTable) { auto context = getModule().getContext(); Builder builder(context); diff --git a/third_party/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/third_party/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp index 78da9998c6d..9201da2322b 100644 --- a/third_party/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp +++ b/third_party/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp @@ -177,9 +177,8 @@ static void printGEPOp(OpAsmPrinter &p, GEPOp &op) { SmallVector types(op.getOperandTypes()); auto funcTy = FunctionType::get(types, op.getType(), op.getContext()); - p << op.getOperationName() << ' ' << *op.base() << '['; - p.printOperands(std::next(op.operand_begin()), op.operand_end()); - p << ']'; + p << op.getOperationName() << ' ' << *op.base() << '[' + << op.getOperands().drop_front() << ']'; p.printOptionalAttrDict(op.getAttrs()); p << " : " << funcTy; } @@ -312,10 +311,7 @@ static void printCallOp(OpAsmPrinter &p, CallOp &op) { else p << *op.getOperand(0); - p << '('; - p.printOperands(llvm::drop_begin(op.getOperands(), isDirect ? 0 : 1)); - p << ')'; - + p << '(' << op.getOperands().drop_front(isDirect ? 0 : 1) << ')'; p.printOptionalAttrDict(op.getAttrs(), {"callee"}); // Reconstruct the function MLIR function type from operand and result types. @@ -794,9 +790,12 @@ static ParseResult parseUndefOp(OpAsmParser &parser, OperationState &result) { //===----------------------------------------------------------------------===// GlobalOp AddressOfOp::getGlobal() { - auto module = getParentOfType(); + Operation *module = getParentOp(); + while (module && !satisfiesLLVMModule(module)) + module = module->getParentOp(); assert(module && "unexpected operation outside of a module"); - return module.lookupSymbol(global_name()); + return dyn_cast_or_null( + mlir::SymbolTable::lookupSymbolIn(module, global_name())); } static void printAddressOfOp(OpAsmPrinter &p, AddressOfOp op) { @@ -938,8 +937,7 @@ static void printGlobalOp(OpAsmPrinter &p, GlobalOp op) { // Print the trailing type unless it's a string global. if (op.getValueOrNull().dyn_cast_or_null()) return; - p << " : "; - p.printType(op.type()); + p << " : " << op.type(); Region &initializer = op.getInitializerRegion(); if (!initializer.empty()) @@ -1035,7 +1033,9 @@ static LogicalResult verify(GlobalOp op) { if (!llvm::PointerType::isValidElementType(op.getType().getUnderlyingType())) return op.emitOpError( "expects type to be a valid element type for an LLVM pointer"); - if (op.getParentOp() && !isa(op.getParentOp())) + if (op.getParentOp() && + !(op.getParentOp()->hasTrait() && + op.getParentOp()->hasTrait())) return op.emitOpError("must appear at the module level"); if (auto strAttr = op.getValueOrNull().dyn_cast_or_null()) { @@ -1346,8 +1346,7 @@ static LogicalResult verify(LLVMFuncOp op) { static void printNullOp(OpAsmPrinter &p, LLVM::NullOp op) { p << NullOp::getOperationName(); p.printOptionalAttrDict(op.getAttrs()); - p << " : "; - p.printType(op.getType()); + p << " : " << op.getType(); } // = `llvm.mlir.null` : type @@ -1681,3 +1680,8 @@ Value *mlir::LLVM::createGlobalString(Location loc, OpBuilder &builder, loc, LLVM::LLVMType::getInt8PtrTy(llvmDialect), globalPtr, ArrayRef({cst0, cst0})); } + +bool mlir::LLVM::satisfiesLLVMModule(Operation *op) { + return op->hasTrait() && + op->hasTrait(); +} diff --git a/third_party/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp b/third_party/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp index 0b10391f180..e4708fbe535 100644 --- a/third_party/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp +++ b/third_party/mlir/lib/Dialect/LLVMIR/IR/NVVMDialect.cpp @@ -37,18 +37,17 @@ #include "llvm/IR/Type.h" #include "llvm/Support/SourceMgr.h" -namespace mlir { -namespace NVVM { +using namespace mlir; +using namespace NVVM; //===----------------------------------------------------------------------===// // Printing/parsing for NVVM ops //===----------------------------------------------------------------------===// static void printNVVMIntrinsicOp(OpAsmPrinter &p, Operation *op) { - p << op->getName() << " "; - p.printOperands(op->getOperands()); + p << op->getName() << " " << op->getOperands(); if (op->getNumResults() > 0) - interleaveComma(op->getResultTypes(), p << " : "); + p << " : " << op->getResultTypes(); } // ::= `llvm.nvvm.XYZ` : type @@ -141,8 +140,7 @@ static ParseResult parseNVVMMmaOp(OpAsmParser &parser, OperationState &result) { } static void printNVVMMmaOp(OpAsmPrinter &p, MmaOp &op) { - p << op.getOperationName() << " "; - p.printOperands(op.getOperands()); + p << op.getOperationName() << " " << op.getOperands(); p.printOptionalAttrDict(op.getAttrs()); p << " : " << FunctionType::get(llvm::to_vector<12>(op.getOperandTypes()), @@ -210,10 +208,11 @@ NVVMDialect::NVVMDialect(MLIRContext *context) : Dialect("nvvm", context) { allowUnknownOperations(); } +namespace mlir { +namespace NVVM { #define GET_OP_CLASSES #include "mlir/Dialect/LLVMIR/NVVMOps.cpp.inc" - -static DialectRegistration nvvmDialect; - } // namespace NVVM } // namespace mlir + +static DialectRegistration nvvmDialect; diff --git a/third_party/mlir/lib/Dialect/LLVMIR/IR/ROCDLDialect.cpp b/third_party/mlir/lib/Dialect/LLVMIR/IR/ROCDLDialect.cpp index 487382bb364..30c55b52e59 100644 --- a/third_party/mlir/lib/Dialect/LLVMIR/IR/ROCDLDialect.cpp +++ b/third_party/mlir/lib/Dialect/LLVMIR/IR/ROCDLDialect.cpp @@ -36,18 +36,17 @@ #include "llvm/IR/Type.h" #include "llvm/Support/SourceMgr.h" -namespace mlir { -namespace ROCDL { +using namespace mlir; +using namespace ROCDL; //===----------------------------------------------------------------------===// // Printing/parsing for ROCDL ops //===----------------------------------------------------------------------===// static void printROCDLOp(OpAsmPrinter &p, Operation *op) { - p << op->getName() << " "; - p.printOperands(op->getOperands()); + p << op->getName() << " " << op->getOperands(); if (op->getNumResults() > 0) - interleaveComma(op->getResultTypes(), p << " : "); + p << " : " << op->getResultTypes(); } // ::= `rocdl.XYZ` : type @@ -73,10 +72,11 @@ ROCDLDialect::ROCDLDialect(MLIRContext *context) : Dialect("rocdl", context) { allowUnknownOperations(); } +namespace mlir { +namespace ROCDL { #define GET_OP_CLASSES #include "mlir/Dialect/LLVMIR/ROCDLOps.cpp.inc" - -static DialectRegistration rocdlDialect; - } // namespace ROCDL } // namespace mlir + +static DialectRegistration rocdlDialect; diff --git a/third_party/mlir/lib/Dialect/Linalg/Analysis/DependenceAnalysis.cpp b/third_party/mlir/lib/Dialect/Linalg/Analysis/DependenceAnalysis.cpp index 36882899297..d7e4d08527d 100644 --- a/third_party/mlir/lib/Dialect/Linalg/Analysis/DependenceAnalysis.cpp +++ b/third_party/mlir/lib/Dialect/Linalg/Analysis/DependenceAnalysis.cpp @@ -55,10 +55,7 @@ Value *Aliases::find(Value *v) { auto it = aliases.find(v); if (it != aliases.end()) { - assert(((isa(it->getSecond()) && - it->getSecond()->getType().isa()) || - it->getSecond()->getType().isa()) && - "Buffer or block argument expected"); + assert(it->getSecond()->getType().isa() && "Memref expected"); return it->getSecond(); } diff --git a/third_party/mlir/lib/Dialect/Linalg/CMakeLists.txt b/third_party/mlir/lib/Dialect/Linalg/CMakeLists.txt index a4ce5038891..9d2b0cdca93 100644 --- a/third_party/mlir/lib/Dialect/Linalg/CMakeLists.txt +++ b/third_party/mlir/lib/Dialect/Linalg/CMakeLists.txt @@ -1,6 +1,7 @@ add_llvm_library(MLIRLinalg LinalgRegistration.cpp Analysis/DependenceAnalysis.cpp + EDSC/Builders.cpp IR/LinalgOps.cpp IR/LinalgTypes.cpp Transforms/Fusion.cpp @@ -20,9 +21,11 @@ add_dependencies(MLIRLinalg MLIRAffineOps MLIRAnalysis + MLIREDSC MLIRLinalgOpsIncGen MLIRLinalgLibraryOpsIncGen MLIRLinalgTransformPatternsIncGen MLIRStandardOps MLIRStandardToLLVM + MLIRVectorOps ) diff --git a/third_party/mlir/lib/Dialect/Linalg/EDSC/Builders.cpp b/third_party/mlir/lib/Dialect/Linalg/EDSC/Builders.cpp new file mode 100644 index 00000000000..77e3a1e392f --- /dev/null +++ b/third_party/mlir/lib/Dialect/Linalg/EDSC/Builders.cpp @@ -0,0 +1,256 @@ +//===- Builders.cpp - MLIR Declarative Linalg Builders --------------------===// +// +// Copyright 2019 The MLIR Authors. +// +// 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 "mlir/Dialect/Linalg/EDSC/Builders.h" +#include "mlir/Dialect/Linalg/EDSC/Intrinsics.h" +#include "mlir/Dialect/Linalg/IR/LinalgOps.h" +#include "mlir/EDSC/Builders.h" +#include "mlir/EDSC/Intrinsics.h" +#include "mlir/IR/AffineExpr.h" +#include "mlir/IR/Builders.h" +#include "mlir/Support/Functional.h" + +using namespace mlir; +using namespace mlir::edsc; +using namespace mlir::edsc::intrinsics; +using namespace mlir::edsc::ops; + +static void getMaxDimIndex(ArrayRef structuredIndices, + unsigned &pos) { + for (auto sidx : structuredIndices) { + for (auto expr : sidx.getExprs()) { + expr.walk([&pos](AffineExpr e) { + if (auto d = e.dyn_cast()) + pos = std::max(pos, d.getPosition()); + }); + } + } +} + +Operation *mlir::edsc::makeLinalgGenericOp( + ArrayRef iteratorTypes, ArrayRef inputs, + ArrayRef outputs, + llvm::function_ref)> regionBuilder, + ArrayRef otherValues, ArrayRef otherAttributes) { + auto &builder = edsc::ScopedContext::getBuilder(); + auto *ctx = builder.getContext(); + unsigned nInputs = inputs.size(); + unsigned nOutputs = outputs.size(); + unsigned maxPos = 0; + getMaxDimIndex(inputs, maxPos); + getMaxDimIndex(outputs, maxPos); + // maxPos is 0 indexed, need to turn this into a count (i.e. +1) + unsigned nDims = maxPos + 1; + + SmallVector maps; + maps.reserve(nInputs + nOutputs); + for (auto in : inputs) + maps.push_back( + AffineMap::get(/*dimCount=*/nDims, /*symbolCount=*/0, in.getExprs())); + for (auto out : outputs) + maps.push_back( + AffineMap::get(/*dimCount=*/nDims, /*symbolCount=*/0, out.getExprs())); + + unsigned nViews = nInputs + nOutputs; + SmallVector values; + values.reserve(nViews); + values.append(inputs.begin(), inputs.end()); + values.append(outputs.begin(), outputs.end()); + + auto iteratorStrTypes = functional::map(toString, iteratorTypes); + // clang-format off + auto *op = + edsc::ScopedContext::getBuilder() + .create( + edsc::ScopedContext::getLocation(), + values, + IntegerAttr::get(IntegerType::get(64, ctx), nInputs), + IntegerAttr::get(IntegerType::get(64, ctx), nOutputs), + builder.getAffineMapArrayAttr(maps), + builder.getStrArrayAttr(iteratorStrTypes), + StringAttr() /*doc*/, + FlatSymbolRefAttr() /*fun*/, + StringAttr() /*library_call*/ + /* TODO: other attributes in op */ + ) + .getOperation(); + // clang-format on + + using namespace edsc; + SmallVector blockTypes; + blockTypes.reserve(values.size()); + for (auto it : llvm::enumerate(values)) + blockTypes.push_back((it.index() < nViews) + ? getElementTypeOrSelf(it.value()) + : it.value()->getType()); + + assert(op->getRegions().front().empty()); + op->getRegions().front().push_front(new Block); + OpBuilder bb(op->getRegions().front()); + ScopedContext scope(bb, op->getLoc()); + BlockHandle b; + auto handles = makeValueHandles(blockTypes); + BlockBuilder(&b, makeHandlePointers(MutableArrayRef(handles)))( + [&] { regionBuilder(b.getBlock()->getArguments()); }); + return op; +} + +void mlir::edsc::ops::macRegionBuilder(ArrayRef args) { + using edsc::op::operator+; + using edsc::op::operator*; + assert(args.size() == 3 && "expected 3 block arguments"); + ValueHandle a(args[0]), b(args[1]), c(args[2]); + linalg_yield((c + a * b).getValue()); +} + +Operation *mlir::edsc::ops::linalg_pointwise(UnaryPointwiseOpBuilder unaryOp, + StructuredIndexed I, + StructuredIndexed O) { + SmallVector iterTypes(O.getExprs().size(), + edsc::IterType::Parallel); + auto fun = [&unaryOp](ArrayRef args) { + assert(args.size() == 2 && "expected 2 block arguments"); + ValueHandle a(args[0]); + linalg_yield(unaryOp(a)); + }; + return makeLinalgGenericOp(iterTypes, {I}, {O}, fun); +} + +Operation *mlir::edsc::ops::linalg_pointwise_tanh(StructuredIndexed I, + StructuredIndexed O) { + ; + using edsc::intrinsics::tanh; + UnaryPointwiseOpBuilder unOp( + [](ValueHandle a) -> Value * { return tanh(a); }); + return linalg_pointwise(unOp, I, O); +} + +/// Binary pointwise operation (with broadcast) entry point. +Operation *mlir::edsc::ops::linalg_pointwise(BinaryPointwiseOpBuilder binaryOp, + StructuredIndexed I1, + StructuredIndexed I2, + StructuredIndexed O) { + SmallVector iterTypes(O.getExprs().size(), + edsc::IterType::Parallel); + auto fun = [&binaryOp](ArrayRef args) { + assert(args.size() == 3 && "expected 3 block arguments"); + ValueHandle a(args[0]), b(args[1]); + linalg_yield(binaryOp(a, b)); + }; + return makeLinalgGenericOp(iterTypes, {I1, I2}, {O}, fun); +} + +Operation *mlir::edsc::ops::linalg_pointwise_add(StructuredIndexed I1, + StructuredIndexed I2, + StructuredIndexed O) { + using edsc::op::operator+; + BinaryPointwiseOpBuilder binOp( + [](ValueHandle a, ValueHandle b) -> Value * { return a + b; }); + return linalg_pointwise(binOp, I1, I2, O); +} + +Operation *mlir::edsc::ops::linalg_pointwise_max(StructuredIndexed I1, + StructuredIndexed I2, + StructuredIndexed O) { + BinaryPointwiseOpBuilder binOp([](ValueHandle a, ValueHandle b) -> Value * { + using edsc::intrinsics::select; + using edsc::op::operator>; + return select(a > b, a, b).getValue(); + }); + return linalg_pointwise(binOp, I1, I2, O); +} + +Operation *mlir::edsc::ops::linalg_matmul(ValueHandle vA, ValueHandle vB, + ValueHandle vC) { + // clang-format off + AffineExpr m, n, k; + bindDims(ScopedContext::getContext(), m, n, k); + StructuredIndexed A(vA), B(vB), C(vC); + return makeLinalgGenericOp( + {IterType::Parallel, IterType::Parallel, IterType::Reduction}, + {A({m, k}), B({k, n})}, + {C({m, n})}, + macRegionBuilder); + // clang-format on +} + +Operation *mlir::edsc::ops::linalg_conv_nhwc(ValueHandle vI, ValueHandle vW, + ValueHandle vO, + ArrayRef strides, + ArrayRef dilations) { + MLIRContext *ctx = ScopedContext::getContext(); + // TODO(ntv) some template magic to make everything rank-polymorphic. + assert((dilations.empty() || dilations.size() == 2) && "only 2-D conv atm"); + assert((strides.empty() || strides.size() == 2) && "only 2-D conv atm"); + + // Some short names. + auto par = IterType::Parallel; + auto red = IterType::Reduction; + auto s = strides; + auto d = dilations; + + AffineExpr b, f, h, w, kh, kw, c; + bindDims(ctx, b, f, h, w, kh, kw, c); + unsigned numDims = c.cast().getPosition() + 1; + StructuredIndexed I(vI), W(vW), O(vO); + // clang-format off + return makeLinalgGenericOp( + {par, par, par, par, red, red, red}, { + I({b, + // Roundtrip to flattened form to serve as canonicalization and ensure + // consistent ordering of subexpressions. + simplifyAffineExpr(s[0] * h + d[0] * kh, numDims, 0), + simplifyAffineExpr(s[1] * w + d[1] * kw, numDims, 0), + c}), + W({kh, kw, c, f})}, { + O({b, h, w, f})}, + macRegionBuilder); + // clang-format on +} + +Operation *mlir::edsc::ops::linalg_dilated_conv_nhwc( + ValueHandle vI, ValueHandle vW, ValueHandle vO, int depth_multiplier, + ArrayRef strides, ArrayRef dilations) { + MLIRContext *ctx = ScopedContext::getContext(); + // TODO(ntv) some template magic to make everything rank-polymorphic. + assert((dilations.empty() || dilations.size() == 2) && "only 2-D conv atm"); + assert((strides.empty() || strides.size() == 2) && "only 2-D conv atm"); + + // Some short names. + auto par = IterType::Parallel; + auto red = IterType::Reduction; + auto s = strides; + auto d = dilations; + + // clang-format off + AffineExpr b, dm, c, h, w, kh, kw; + bindDims(ctx, b, dm, c, h, w, kh, kw); + unsigned numDims = kw.cast().getPosition() + 1; + StructuredIndexed I(vI), W(vW), O(vO); + return makeLinalgGenericOp( + {par, par, par, par, par, red, red}, { + I({b, + // Roundtrip to flattened form to serve as canonicalization and ensure + // consistent ordering of subexpressions. + simplifyAffineExpr(s[0] * h + d[0] * kh, numDims, 0), + simplifyAffineExpr(s[1] * w + d[1] * kw, numDims, 0), + c}), + W({kh, kw, c, dm})}, { + O({b, h, w, simplifyAffineExpr(c * depth_multiplier + dm, numDims, 0)})}, + macRegionBuilder); + // clang-format on +} diff --git a/third_party/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp b/third_party/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp index 340f18276b2..6adfeb592ef 100644 --- a/third_party/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp +++ b/third_party/mlir/lib/Dialect/Linalg/IR/LinalgOps.cpp @@ -60,18 +60,16 @@ static void printGenericOp(OpAsmPrinter &p, GenericOpType op) { llvm::StringSet<> linalgTraitAttrsSet; linalgTraitAttrsSet.insert(attrNames.begin(), attrNames.end()); SmallVector attrs; - for (auto attr : op.getAttrs()) { + for (auto attr : op.getAttrs()) if (linalgTraitAttrsSet.count(attr.first.strref()) > 0) attrs.push_back(attr); - } + auto dictAttr = DictionaryAttr::get(attrs, op.getContext()); - p << op.getOperationName() << " " << dictAttr << " "; - p.printOperands(op.getOperands()); + p << op.getOperationName() << " " << dictAttr << " " << op.getOperands(); if (!op.region().empty()) p.printRegion(op.region()); p.printOptionalAttrDict(op.getAttrs(), attrNames); - p << ": "; - interleaveComma(op.getOperandTypes(), p); + p << ": " << op.getOperandTypes(); } static void print(OpAsmPrinter &p, GenericOp op) { printGenericOp(p, op); } @@ -96,8 +94,8 @@ static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) { Region ®ion = *result.addRegion(); SmallVector operandTypes, regionTypes; // Optional attributes may be added. - // Either Optional "fun" attribute or region must be specified. - if (!dictAttr.get("fun") && + // Either Optional getFunAttrName() attribute or region must be specified. + if (!dictAttr.get(getFunAttrName()) && parser.parseOptionalRegion(region, regionOperandsInfo, regionTypes)) return failure(); if (parser.parseOptionalAttrDict(result.attributes) || @@ -342,14 +340,13 @@ void mlir::linalg::SliceOp::build(Builder *b, OperationState &result, } static void print(OpAsmPrinter &p, SliceOp op) { - p << SliceOp::getOperationName() << " " << *op.view() << "["; - p.printOperands(op.indexings()); - p << "] "; + auto indexings = op.indexings(); + p << SliceOp::getOperationName() << " " << *op.view() << "[" << indexings + << "] "; p.printOptionalAttrDict(op.getAttrs()); p << " : " << op.getBaseViewType(); - for (auto indexing : op.indexings()) { - p << ", " << indexing->getType(); - } + if (!indexings.empty()) + p << ", " << op.indexings().getTypes(); p << ", " << op.getType(); } @@ -455,16 +452,11 @@ static ParseResult parseTransposeOp(OpAsmParser &parser, static void print(OpAsmPrinter &p, YieldOp op) { p << op.getOperationName(); - if (op.getNumOperands() > 0) { - p << ' '; - p.printOperands(op.operand_begin(), op.operand_end()); - } + if (op.getNumOperands() > 0) + p << ' ' << op.getOperands(); p.printOptionalAttrDict(op.getAttrs()); - if (op.getNumOperands() > 0) { - p << " : "; - interleaveComma(op.getOperands(), p, - [&](Value *e) { p.printType(e->getType()); }); - } + if (op.getNumOperands() > 0) + p << " : " << op.getOperandTypes(); } static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) { @@ -536,12 +528,9 @@ static LogicalResult verify(YieldOp op) { // Where %0, %1 and %2 are ssa-values of type MemRefType with strides. static void printLinalgLibraryOp(OpAsmPrinter &p, Operation *op) { assert(op->getAbstractOperation() && "unregistered operation"); - p << op->getName().getStringRef() << "("; - interleaveComma(op->getOperands(), p, [&](Value *v) { p << *v; }); - p << ")"; + p << op->getName().getStringRef() << "(" << op->getOperands() << ")"; p.printOptionalAttrDict(op->getAttrs()); - p << " : "; - interleaveComma(op->getOperands(), p, [&](Value *v) { p << v->getType(); }); + p << " : " << op->getOperandTypes(); } static ParseResult parseLinalgLibraryOp(OpAsmParser &parser, @@ -557,7 +546,7 @@ static ParseResult parseLinalgLibraryOp(OpAsmParser &parser, static LogicalResult verify(FillOp op) { auto viewType = op.getOutputViewType(0); - auto fillType = op.getValue()->getType(); + auto fillType = op.value()->getType(); if (viewType.getElementType() != fillType) return op.emitOpError("expects fill type to match view elemental type"); return success(); @@ -813,3 +802,30 @@ std::string mlir::linalg::generateLibraryCallName(Operation *op) { [&]() { ss << "_"; }); return ss.str(); } + +static ArrayAttr getIndexingMaps(Operation *op) { + LinalgOp linalgOp = cast(op); + SmallVector maps; + maps.reserve(linalgOp.getNumInputsAndOutputs()); + for (AffineMap map : loopToOperandRangesMaps(op)) + maps.push_back(AffineMapAttr::get(map)); + return ArrayAttr::get(maps, op->getContext()); +} +ArrayAttr mlir::linalg::ConvOp::indexing_maps() { + return getIndexingMaps(getOperation()); +} +ArrayAttr mlir::linalg::CopyOp::indexing_maps() { + return getIndexingMaps(getOperation()); +} +ArrayAttr mlir::linalg::DotOp::indexing_maps() { + return getIndexingMaps(getOperation()); +} +ArrayAttr mlir::linalg::FillOp::indexing_maps() { + return getIndexingMaps(getOperation()); +} +ArrayAttr mlir::linalg::MatmulOp::indexing_maps() { + return getIndexingMaps(getOperation()); +} +ArrayAttr mlir::linalg::MatvecOp::indexing_maps() { + return getIndexingMaps(getOperation()); +} diff --git a/third_party/mlir/lib/Dialect/Linalg/IR/LinalgTypes.cpp b/third_party/mlir/lib/Dialect/Linalg/IR/LinalgTypes.cpp index 3e888f465dc..9fbb83be646 100644 --- a/third_party/mlir/lib/Dialect/Linalg/IR/LinalgTypes.cpp +++ b/third_party/mlir/lib/Dialect/Linalg/IR/LinalgTypes.cpp @@ -35,7 +35,7 @@ using namespace mlir::linalg; mlir::linalg::LinalgDialect::LinalgDialect(MLIRContext *context) : Dialect(getDialectNamespace(), context) { - addTypes(); + addTypes(); addOperations< #define GET_OP_LIST #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" @@ -45,69 +45,6 @@ mlir::linalg::LinalgDialect::LinalgDialect(MLIRContext *context) #include "mlir/Dialect/Linalg/IR/LinalgLibraryOps.cpp.inc" >(); } - -struct mlir::linalg::BufferTypeStorage : public TypeStorage { - /// Underlying Key type to transport the payload needed to construct a custom - /// type in a generic way. - struct Key { - Key(Type elementType, int64_t bufferSize = -1) - : elementType(elementType), bufferSize(bufferSize) {} - Type elementType; - int64_t bufferSize; - }; - /// `KeyTy` is a necessary typename hook for MLIR's custom type unique'ing. - using KeyTy = Key; - - /// Construction in the llvm::BumpPtrAllocator given a key. - static BufferTypeStorage *construct(TypeStorageAllocator &allocator, - const Key &key) { - return new (allocator.allocate()) BufferTypeStorage(key); - } - - /// Equality operator for hashing. - bool operator==(const Key &key) const { - return elementType == key.elementType && bufferSize == key.bufferSize; - } - - /// Hashing for unique'ing. - static unsigned hashKey(const Key &key) { - return llvm::hash_combine(key.elementType, key.bufferSize); - } - - Type getElementType() { return elementType; } - bool hasConstantSize() { return bufferSize >= 0; } - Optional getBufferSize() { - if (hasConstantSize()) { - return bufferSize; - } - return llvm::None; - } - -private: - BufferTypeStorage(const Key &key) - : elementType(key.elementType), bufferSize(key.bufferSize) {} - - Type elementType; - int64_t bufferSize; -}; - -BufferType mlir::linalg::BufferType::get(MLIRContext *context, Type elementType, - int64_t bufferSize) { - return Base::get(context, LinalgTypes::Buffer, elementType, bufferSize); -} - -Type mlir::linalg::BufferType::getElementType() { - return getImpl()->getElementType(); -} - -bool mlir::linalg::BufferType::hasConstantSize() { - return getImpl()->hasConstantSize(); -} - -Optional mlir::linalg::BufferType::getBufferSize() { - return getImpl()->getBufferSize(); -} - Type mlir::linalg::LinalgDialect::parseType(DialectAsmParser &parser) const { // Parse the main keyword for the type. StringRef keyword; @@ -119,39 +56,10 @@ Type mlir::linalg::LinalgDialect::parseType(DialectAsmParser &parser) const { if (keyword == "range") return RangeType::get(context); - // Handle 'buffer' types. - if (keyword == "buffer") { - llvm::SMLoc dimensionLoc; - SmallVector size; - Type type; - if (parser.parseLess() || parser.getCurrentLocation(&dimensionLoc) || - parser.parseDimensionList(size) || parser.parseType(type) || - parser.parseGreater()) - return Type(); - - if (size.size() != 1) { - parser.emitError(dimensionLoc, "expected single element in size list"); - return Type(); - } - - return (size.front() == -1 ? BufferType::get(context, type) - : BufferType::get(context, type, size.front())); - } - parser.emitError(parser.getNameLoc(), "unknown Linalg type: " + keyword); return Type(); } -/// BufferType prints as "buffer". -static void print(BufferType bt, DialectAsmPrinter &os) { - os << "buffer<"; - if (Optional bs = bt.getBufferSize()) - os << bs.getValue(); - else - os << "?"; - os << "x" << bt.getElementType() << ">"; -} - /// RangeType prints as just "range". static void print(RangeType rt, DialectAsmPrinter &os) { os << "range"; } @@ -160,9 +68,6 @@ void mlir::linalg::LinalgDialect::printType(Type type, switch (type.getKind()) { default: llvm_unreachable("Unhandled Linalg type"); - case LinalgTypes::Buffer: - print(type.cast(), os); - break; case LinalgTypes::Range: print(type.cast(), os); break; diff --git a/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgToLoops.cpp b/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgToLoops.cpp index cf0b235f57f..96a8a216536 100644 --- a/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgToLoops.cpp +++ b/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgToLoops.cpp @@ -130,8 +130,8 @@ public: IndexedValueType O(fillOp.getOutput(0)); // Emit the proper scalar assignment, whether we are dealing with a 0-D or // an n-D loop nest; with or without permutations. - nPar > 0 ? O(ivs) = ValueHandle(fillOp.getValue()) - : O() = ValueHandle(fillOp.getValue()); + nPar > 0 ? O(ivs) = ValueHandle(fillOp.value()) + : O() = ValueHandle(fillOp.value()); } }; @@ -430,7 +430,8 @@ LogicalResult LinalgOpToLoopsImpl::doit( auto nRed = linalgOp.getNumReductionLoops(); auto nWin = linalgOp.getNumWindowLoops(); SmallVector allIvs(nPar + nRed + nWin); - SmallVector allPIvs = makeIndexHandlePointers(allIvs); + SmallVector allPIvs = + makeHandlePointers(MutableArrayRef(allIvs)); auto loopRanges = emitLoopRanges(scope.getBuilder(), scope.getLocation(), invertedMap, getViewSizes(linalgOp)); assert(loopRanges.size() == allIvs.size()); diff --git a/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgTransforms.cpp b/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgTransforms.cpp index 1b4509ffc11..74000212373 100644 --- a/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgTransforms.cpp +++ b/third_party/mlir/lib/Dialect/Linalg/Transforms/LinalgTransforms.cpp @@ -22,12 +22,28 @@ #include "mlir/Dialect/Linalg/Transforms/LinalgTransforms.h" #include "mlir/Dialect/Linalg/Analysis/DependenceAnalysis.h" #include "mlir/Dialect/Linalg/IR/LinalgOps.h" +#include "mlir/Dialect/Linalg/Utils/Intrinsics.h" #include "mlir/Dialect/Linalg/Utils/Utils.h" +#include "mlir/Dialect/VectorOps/VectorOps.h" +#include "mlir/EDSC/Helpers.h" +#include "mlir/IR/AffineExpr.h" +#include "mlir/IR/Matchers.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Pass/Pass.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +#include + +#define DEBUG_TYPE "linalg-transforms" using namespace mlir; +using namespace mlir::edsc; +using namespace mlir::edsc::intrinsics; using namespace mlir::linalg; +using namespace mlir::linalg::intrinsics; + +using llvm::dbgs; +using llvm::SetVector; // Marker used as attribute name in generated Linalg rewriting transformations. const StringLiteral mlir::linalg::LinalgTransforms::kLinalgTransformMarker = @@ -106,3 +122,126 @@ bool mlir::linalg::detail::isProducedByOpOfTypeImpl( } return false; } + +static bool hasMultiplyAddBody(linalg::GenericOp op) { + auto &r = op.region(); + if (r.empty()) + return false; + if (r.getBlocks().size() != 1) + return false; + auto &ops = r.front().getOperations(); + if (ops.size() != 3) + return false; + + using mlir::matchers::m_Val; + auto a = m_Val(r.front().getArgument(0)); + auto b = m_Val(r.front().getArgument(1)); + auto c = m_Val(r.front().getArgument(2)); + // TODO(ntv) Update this detection once we have matcher support for + // specifying that any permutation of operands matches. + auto pattern1 = m_Op(m_Op(m_Op(a, b), c)); + auto pattern2 = m_Op(m_Op(c, m_Op(a, b))); + auto pattern3 = m_Op(m_Op(m_Op(b, a), c)); + auto pattern4 = m_Op(m_Op(c, m_Op(b, a))); + return pattern1.match(&ops.back()) || pattern2.match(&ops.back()) || + pattern3.match(&ops.back()) || pattern4.match(&ops.back()); +} + +// TODO(ntv) should be Tablegen'd from a single source that generates the op +// itself. +static bool isMatmul(linalg::GenericOp genericOp) { + auto *ctx = genericOp.getContext(); + auto m = getAffineDimExpr(0, ctx); + auto n = getAffineDimExpr(1, ctx); + auto k = getAffineDimExpr(2, ctx); + auto mapA = AffineMapAttr::get(AffineMap::get(3, 0, {m, k})); + auto mapB = AffineMapAttr::get(AffineMap::get(3, 0, {k, n})); + auto mapC = AffineMapAttr::get(AffineMap::get(3, 0, {m, n})); + auto maps = ArrayAttr::get({mapA, mapB, mapC}, ctx); + return genericOp.getNumInputs() == 2 && genericOp.getNumOutputs() == 1 && + genericOp.indexing_maps() == maps && hasMultiplyAddBody(genericOp); +} + +LogicalResult mlir::linalg::vectorizeGenericOp(PatternRewriter &rewriter, + Operation *op) { + LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE + "]: Rewrite linalg op as vector.contract: " + << *op << ":\n"); + + // TODO(ntv): This is in fact much more general than just vectorization for + // matmul ops. + auto genericOp = dyn_cast(op); + if (!genericOp || !isMatmul(genericOp)) + return failure(); + + // TODO(ntv): non-identity layout. + auto isStaticMemRefWithIdentityLayout = [](Value *v) { + auto m = v->getType().dyn_cast(); + if (!m || !m.hasStaticShape() || !m.getAffineMaps().empty()) + return false; + return true; + }; + if (!llvm::all_of(genericOp.getInputsAndOutputs(), + isStaticMemRefWithIdentityLayout)) + return failure(); + + edsc::ScopedContext scope(rewriter, op->getLoc()); + using edsc::intrinsics::std_load; + using edsc::intrinsics::std_store; + using vector_contract = edsc::intrinsics::ValueBuilder; + using vector_type_cast = edsc::intrinsics::ValueBuilder; + auto vA = std_load(vector_type_cast(genericOp.getInput(0))); + auto vB = std_load(vector_type_cast(genericOp.getInput(1))); + auto vectorMemRefC = vector_type_cast(genericOp.getOutput(0)); + auto vC = std_load(vectorMemRefC); + auto vRes = vector_contract(vA, vB, vC, genericOp.indexing_maps(), + genericOp.iterator_types()); + std_store(vRes, vectorMemRefC); + return success(); +} + +LogicalResult +mlir::linalg::permuteGenericLinalgOp(PatternRewriter &rewriter, Operation *op, + ArrayRef permutation, + StringRef linalgMarker) { + // If permutation is empty, there is nothing to be done. + if (permutation.empty()) + return failure(); + + auto linOp = cast(op); + auto permutationMap = inversePermutation( + AffineMap::getPermutationMap(permutation, rewriter.getContext())); + SmallVector newIndexingMap; + auto indexingMaps = linOp.indexing_maps().getValue(); + for (unsigned i = 0, e = linOp.getNumInputsAndOutputs(); i != e; ++i) { + AffineMap m = indexingMaps[i].cast().getValue().compose( + permutationMap); + newIndexingMap.push_back(m); + } + auto itTypes = linOp.iterator_types().getValue(); + SmallVector itTypesVector; + for (unsigned i = 0, e = itTypes.size(); i != e; ++i) + itTypesVector.push_back(itTypes[i]); + applyPermutationToVector(itTypesVector, permutation); + op->setAttr(getIndexingMapsAttrName(), + rewriter.getAffineMapArrayAttr(newIndexingMap)); + op->setAttr(getIteratorTypesAttrName(), rewriter.getArrayAttr(itTypesVector)); + op->setAttr(LinalgTransforms::kLinalgTransformMarker, + rewriter.getStringAttr(linalgMarker)); + linOp.clone(rewriter, linOp.getLoc(), op->getOperands()); + return success(); +} + +LogicalResult mlir::linalg::linalgOpPromoteSubviews(PatternRewriter &rewriter, + Operation *op) { + LinalgOp linOp = dyn_cast(op); + SetVector subViews; + for (auto it : linOp.getInputsAndOutputs()) + if (auto sv = dyn_cast_or_null(it->getDefiningOp())) + subViews.insert(sv); + if (!subViews.empty()) { + auto resOp = promoteSubViewOperands(rewriter, linOp, subViews); + return success(resOp); + } + return failure(); +} diff --git a/third_party/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp b/third_party/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp index 32b70346b97..c7fbebce383 100644 --- a/third_party/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp +++ b/third_party/mlir/lib/Dialect/Linalg/Transforms/Promotion.cpp @@ -160,11 +160,11 @@ mlir::linalg::promoteSubViews(OpBuilder &b, Location loc, return res; } -static void promoteSubViewOperands(LinalgOp op, SetVector subViews, - bool dynamicBuffers, - OperationFolder *folder) { +LinalgOp mlir::linalg::promoteSubViewOperands(OpBuilder &b, LinalgOp op, + SetVector subViews, + bool dynamicBuffers, + OperationFolder *folder) { // 1. Promote the specified views and use them in the new op. - OpBuilder b(op); ScopedContext scope(b, op.getLoc()); auto promotedBufferAndViews = promoteSubViews( b, op.getLoc(), subViews.getArrayRef(), dynamicBuffers, folder); @@ -189,11 +189,12 @@ static void promoteSubViewOperands(LinalgOp op, SetVector subViews, // extra scalars etc. auto operands = getAssumedNonViewOperands(op); opViews.append(operands.begin(), operands.end()); - op.clone(b, op.getLoc(), opViews); + LinalgOp res = op.clone(b, op.getLoc(), opViews); // 3. Emit write-back for the promoted output views: copy the partial view. for (auto viewAndPartialLocalView : writebackViews) { - // Note: use the old op to determine whether the operand view is an output. + // WARNING: MUST use the old op to determine whether the operand view is an + // output. bool isOutput = op.getIndexOfOutput(viewAndPartialLocalView.first).hasValue(); if (isOutput) @@ -203,6 +204,8 @@ static void promoteSubViewOperands(LinalgOp op, SetVector subViews, // 4. Dealloc local buffers. for (const auto &pi : promotedBufferAndViews) dealloc(pi.buffer); + + return res; } static void promoteSubViews(FuncOp f, bool dynamicBuffers) { @@ -212,11 +215,12 @@ static void promoteSubViews(FuncOp f, bool dynamicBuffers) { // TODO(ntv) some heuristic here to decide what to promote. Atm it is all or // nothing. SetVector subViews; + OpBuilder b(op); for (auto it : op.getInputsAndOutputs()) if (auto sv = dyn_cast_or_null(it->getDefiningOp())) subViews.insert(sv); if (!subViews.empty()) { - promoteSubViewOperands(op, subViews, dynamicBuffers, &folder); + promoteSubViewOperands(b, op, subViews, dynamicBuffers, &folder); toErase.push_back(op); } }); diff --git a/third_party/mlir/lib/Dialect/Linalg/Transforms/Tiling.cpp b/third_party/mlir/lib/Dialect/Linalg/Transforms/Tiling.cpp index 2c84eeecbba..435aa7245ba 100644 --- a/third_party/mlir/lib/Dialect/Linalg/Transforms/Tiling.cpp +++ b/third_party/mlir/lib/Dialect/Linalg/Transforms/Tiling.cpp @@ -58,14 +58,18 @@ static bool isZero(Value *v) { cast(v->getDefiningOp()).getValue() == 0; } +using LoopIndexToRangeIndexMap = DenseMap; + // Creates a number of ranges equal to the number of non-zero in `tileSizes`. // One for each loop of the LinalgOp that is tiled. The `tileSizes` argument has // one entry per surrounding loop. It uses zero as the convention that a // particular loop is not tiled. This convention simplifies implementations by // avoiding affine map manipulations. // The returned ranges correspond to the loop ranges, in the proper order, that -// are tiled and for which new loops will be created. -static SmallVector +// are tiled and for which new loops will be created. Also the function returns +// a map from loop indices of the LinalgOp to the corresponding non-empty range +// indices of newly created loops. +static std::tuple, LoopIndexToRangeIndexMap> makeTiledLoopRanges(OpBuilder &b, Location loc, AffineMap map, ArrayRef allViewSizes, ArrayRef allTileSizes, OperationFolder *folder) { @@ -75,11 +79,15 @@ makeTiledLoopRanges(OpBuilder &b, Location loc, AffineMap map, SmallVector tileSizes(allTileSizes.begin(), allTileSizes.end()); // Traverse the tile sizes, which are in loop order, erase zeros everywhere. - for (int idx = tileSizes.size() - 1; idx >= 0; --idx) { - if (isZero(tileSizes[idx])) { - viewSizes.erase(viewSizes.begin() + idx); - tileSizes.erase(tileSizes.begin() + idx); + LoopIndexToRangeIndexMap loopIndexToRangeIndex; + for (int idx = 0, e = tileSizes.size(), zerosCount = 0; idx < e; ++idx) { + if (isZero(tileSizes[idx - zerosCount])) { + viewSizes.erase(viewSizes.begin() + idx - zerosCount); + tileSizes.erase(tileSizes.begin() + idx - zerosCount); + ++zerosCount; + continue; } + loopIndexToRangeIndex[idx] = idx - zerosCount; } // Create a new range with the applied tile sizes. @@ -88,10 +96,11 @@ makeTiledLoopRanges(OpBuilder &b, Location loc, AffineMap map, res.push_back(SubViewOp::Range{constant_index(folder, 0), viewSizes[idx], tileSizes[idx]}); } - return res; + return std::make_tuple(res, loopIndexToRangeIndex); } namespace { + // Helper visitor to determine whether an AffineExpr is tiled. // This is achieved by traversing every AffineDimExpr with position `pos` and // checking whether the corresponding `tileSizes[pos]` is non-zero. @@ -117,8 +126,99 @@ struct TileCheck : public AffineExprVisitor { bool isTiled; ArrayRef tileSizes; }; + } // namespace +// IndexedGenericOp explicitly uses induction variables in the loop body. The +// values of the indices that are used in the loop body for any given access of +// input/output memref before `subview` op was applied should be invariant with +// respect to tiling. +// +// Therefore, if the operation is tiled, we have to transform the indices +// accordingly, i.e. offset them by the values of the corresponding induction +// variables that are captured implicitly in the body of the op. +// +// Example. `linalg.indexed_generic` before tiling: +// +// #id_2d = (i, j) -> (i, j) +// #pointwise_2d_trait = { +// indexing_maps = [#id_2d, #id_2d], +// iterator_types = ["parallel", "parallel"], +// n_views = [1, 1] +// } +// linalg.indexed_generic #pointwise_2d_trait %operand, %result { +// ^bb0(%i: index, %j: index, %operand_in: f32, %result_in: f32): +// +// }: memref<50x100xf32>, memref<50x100xf32> +// +// After tiling pass with tiles sizes 10 and 25: +// +// #strided = (i, j)[s0, s1, s2] -> (i * s1 + s0 + j * s2) +// +// %c1 = constant 1 : index +// %c0 = constant 0 : index +// %c25 = constant 25 : index +// %c10 = constant 10 : index +// operand_dim_0 = dim %operand, 0 : memref<50x100xf32> +// operand_dim_1 = dim %operand, 1 : memref<50x100xf32> +// loop.for %k = %c0 to operand_dim_0 step %c10 { +// loop.for %l = %c0 to operand_dim_1 step %c25 { +// %4 = std.subview %operand[%k, %l][%c10, %c25][%c1, %c1] +// : memref<50x100xf32> to memref +// %5 = std.subview %result[%k, %l][%c10, %c25][%c1, %c1] +// : memref<50x100xf32> to memref +// linalg.indexed_generic pointwise_2d_trait %4, %5 { +// ^bb0(%i: index, %j: index, %operand_in: f32, %result_in: f32): +// // Indices `k` and `l` are implicitly captured in the body. +// %transformed_i = addi %i, %k : index // index `i` is offset by %k +// %transformed_j = addi %j, %l : index // index `j` is offset by %l +// // Every use of %i, %j is replaced with %transformed_i, %transformed_j +// +// }: memref, memref +// } +// } +// +// TODO(pifon, ntv): Investigate whether mixing implicit and explicit indices +// does not lead to losing information. +void transformIndexedGenericOpIndices( + OpBuilder &b, LinalgOp op, ArrayRef pivs, + const LoopIndexToRangeIndexMap &loopIndexToRangeIndex) { + auto indexedGenericOp = dyn_cast(op.getOperation()); + if (!indexedGenericOp) + return; + + // `linalg.indexed_generic` comes in two flavours. One has a region with a + // single block that defines the loop body. The other has a `fun` attribute + // that refers to an existing function symbol. The `fun` function call will be + // inserted in the loop body in that case. + // + // TODO(pifon): Add support for `linalg.indexed_generic` with `fun` attribute. + auto ®ion = indexedGenericOp.region(); + if (region.empty()) { + indexedGenericOp.emitError("op expected a region"); + return; + } + auto &block = region.getBlocks().front(); + + OpBuilder::InsertionGuard g(b); + b.setInsertionPointToStart(&block); + for (unsigned i = 0; i < indexedGenericOp.getNumLoops(); ++i) { + auto rangeIndex = loopIndexToRangeIndex.find(i); + if (rangeIndex == loopIndexToRangeIndex.end()) + continue; + Value *oldIndex = block.getArgument(i); + // Offset the index argument `i` by the value of the corresponding induction + // variable and replace all uses of the previous value. + Value *newIndex = b.create(indexedGenericOp.getLoc(), oldIndex, + pivs[rangeIndex->second]->getValue()); + for (auto &use : oldIndex->getUses()) { + if (use.getOwner() == newIndex->getDefiningOp()) + continue; + use.set(newIndex); + } + } +} + static bool isTiled(AffineExpr expr, ArrayRef tileSizes) { if (!expr) return false; @@ -215,14 +315,6 @@ makeTiledViews(OpBuilder &b, Location loc, LinalgOp linalgOp, return res; } -void applyPermutationToLoopRanges(SmallVector &loopRanges, - ArrayRef permutation) { - SmallVector auxVec(loopRanges.size()); - for (unsigned i = 0; i < permutation.size(); ++i) - auxVec[i] = loopRanges[permutation[i]]; - loopRanges = auxVec; -} - llvm::Optional mlir::linalg::tileLinalgOp( OpBuilder &b, LinalgOp op, ArrayRef tileSizes, ArrayRef permutation, OperationFolder *folder) { @@ -252,16 +344,19 @@ llvm::Optional mlir::linalg::tileLinalgOp( auto viewSizesToLoopsMap = inversePermutation(concatAffineMaps(loopToOperandRangesMaps(op))); assert(viewSizesToLoopsMap && "expected invertible map"); - auto loopRanges = + + SmallVector loopRanges; + LoopIndexToRangeIndexMap loopIndexToRangeIndex; + std::tie(loopRanges, loopIndexToRangeIndex) = makeTiledLoopRanges(b, scope.getLocation(), viewSizesToLoopsMap, viewSizes, tileSizes, folder); if (!permutation.empty()) - applyPermutationToLoopRanges(loopRanges, permutation); + applyPermutationToVector(loopRanges, permutation); // 3. Create the tiled loops. LinalgOp res = op; SmallVector ivs(loopRanges.size()); - auto pivs = makeIndexHandlePointers(ivs); + auto pivs = makeHandlePointers(MutableArrayRef(ivs)); LoopNestRangeBuilder(pivs, loopRanges)([&] { auto b = ScopedContext::getBuilder(); auto loc = ScopedContext::getLocation(); @@ -282,7 +377,10 @@ llvm::Optional mlir::linalg::tileLinalgOp( res = op.clone(b, loc, views); }); - // 4. Gather the newly created loops and return them with the new op. + // 4. Transforms index arguments of `linalg.generic` w.r.t. to the tiling. + transformIndexedGenericOpIndices(b, res, pivs, loopIndexToRangeIndex); + + // 5. Gather the newly created loops and return them with the new op. SmallVector loops; loops.reserve(ivs.size()); for (auto iv : ivs) diff --git a/third_party/mlir/lib/Dialect/QuantOps/IR/QuantOps.cpp b/third_party/mlir/lib/Dialect/QuantOps/IR/QuantOps.cpp index b618ac07f17..51f19940dcb 100644 --- a/third_party/mlir/lib/Dialect/QuantOps/IR/QuantOps.cpp +++ b/third_party/mlir/lib/Dialect/QuantOps/IR/QuantOps.cpp @@ -32,38 +32,6 @@ using namespace mlir; using namespace mlir::quant; using namespace mlir::quant::detail; -#define GET_OP_CLASSES -#include "mlir/Dialect/QuantOps/QuantOps.cpp.inc" - -namespace { - -/// Matches x -> [scast -> scast] -> y, replacing the second scast with the -/// value of x if the casts invert each other. -class RemoveRedundantStorageCastsRewrite - : public OpRewritePattern { -public: - using OpRewritePattern::OpRewritePattern; - - PatternMatchResult matchAndRewrite(StorageCastOp op, - PatternRewriter &rewriter) const override { - if (!matchPattern(op.arg(), m_Op())) - return matchFailure(); - auto srcScastOp = cast(op.arg()->getDefiningOp()); - if (srcScastOp.arg()->getType() != op.getType()) - return matchFailure(); - - rewriter.replaceOp(op, srcScastOp.arg()); - return matchSuccess(); - } -}; - -} // end anonymous namespace - -void StorageCastOp::getCanonicalizationPatterns( - OwningRewritePatternList &patterns, MLIRContext *context) { - patterns.insert(context); -} - QuantizationDialect::QuantizationDialect(MLIRContext *context) : Dialect(/*name=*/"quant", context) { addTypes(); } + +OpFoldResult StorageCastOp::fold(ArrayRef operands) { + /// Matches x -> [scast -> scast] -> y, replacing the second scast with the + /// value of x if the casts invert each other. + auto srcScastOp = dyn_cast_or_null(arg()->getDefiningOp()); + if (!srcScastOp || srcScastOp.arg()->getType() != getType()) + return OpFoldResult(); + return srcScastOp.arg(); +} + +#define GET_OP_CLASSES +#include "mlir/Dialect/QuantOps/QuantOps.cpp.inc" diff --git a/third_party/mlir/lib/Dialect/SPIRV/SPIRVLowering.cpp b/third_party/mlir/lib/Dialect/SPIRV/SPIRVLowering.cpp index 694a98fd075..284fe915029 100644 --- a/third_party/mlir/lib/Dialect/SPIRV/SPIRVLowering.cpp +++ b/third_party/mlir/lib/Dialect/SPIRV/SPIRVLowering.cpp @@ -178,8 +178,9 @@ Type SPIRVTypeConverter::convertType(Type type) { return convertStdType(type); } static spirv::GlobalVariableOp getBuiltinVariable(spirv::ModuleOp &moduleOp, spirv::BuiltIn builtin) { for (auto varOp : moduleOp.getBlock().getOps()) { - if (auto builtinAttr = varOp.getAttrOfType(convertToSnakeCase( - stringifyDecoration(spirv::Decoration::BuiltIn)))) { + if (auto builtinAttr = varOp.getAttrOfType( + spirv::SPIRVDialect::getAttributeName( + spirv::Decoration::BuiltIn))) { auto varBuiltIn = spirv::symbolizeBuiltIn(builtinAttr.getValue()); if (varBuiltIn && varBuiltIn.getValue() == builtin) { return varOp; @@ -214,11 +215,8 @@ getOrInsertBuiltinVariable(spirv::ModuleOp &moduleOp, Location loc, auto ptrType = spirv::PointerType::get( VectorType::get({3}, builder.getIntegerType(32)), spirv::StorageClass::Input); - newVarOp = builder.create( - loc, TypeAttr::get(ptrType), builder.getStringAttr(name), nullptr); - newVarOp.setAttr( - convertToSnakeCase(stringifyDecoration(spirv::Decoration::BuiltIn)), - builder.getStringAttr(stringifyBuiltIn(builtin))); + newVarOp = + builder.create(loc, ptrType, name, builtin); break; } default: @@ -239,60 +237,26 @@ Value *mlir::spirv::getBuiltinVariableValue(Operation *op, op->emitError("expected operation to be within a SPIR-V module"); return nullptr; } - auto varOp = + spirv::GlobalVariableOp varOp = getOrInsertBuiltinVariable(moduleOp, op->getLoc(), builtin, builder); - auto ptr = builder - .create(op->getLoc(), varOp.type(), - builder.getSymbolRefAttr(varOp)) - .pointer(); - return builder.create( - op->getLoc(), - ptr->getType().template cast().getPointeeType(), ptr, - /*memory_access =*/nullptr, /*alignment =*/nullptr); + Value *ptr = builder.create(op->getLoc(), varOp); + return builder.create(op->getLoc(), ptr, + /*memory_access =*/nullptr, + /*alignment =*/nullptr); } //===----------------------------------------------------------------------===// -// Entry Function signature Conversion +// Set ABI attributes for lowering entry functions. //===----------------------------------------------------------------------===// -FuncOp mlir::spirv::lowerAsEntryFunction( - FuncOp funcOp, SPIRVTypeConverter &typeConverter, - ConversionPatternRewriter &rewriter, - ArrayRef argABIInfo, - spirv::EntryPointABIAttr entryPointInfo) { - auto fnType = funcOp.getType(); - if (fnType.getNumResults()) { - funcOp.emitError("SPIR-V lowering only supports entry functions" - "with no return values right now"); - return nullptr; - } - if (fnType.getNumInputs() != argABIInfo.size()) { - funcOp.emitError( - "lowering as entry functions requires ABI info for all arguments"); - return nullptr; - } - // For entry functions need to make the signature void(void). Compute the - // replacement value for all arguments and replace all uses. - TypeConverter::SignatureConversion signatureConverter(fnType.getNumInputs()); - { - for (auto argType : enumerate(funcOp.getType().getInputs())) { - auto convertedType = typeConverter.convertType(argType.value()); - signatureConverter.addInputs(argType.index(), convertedType); - } - } - auto newFuncOp = rewriter.cloneWithoutRegions(funcOp); - rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(), - newFuncOp.end()); - newFuncOp.setType(rewriter.getFunctionType( - signatureConverter.getConvertedTypes(), llvm::None)); - rewriter.applySignatureConversion(&newFuncOp.getBody(), signatureConverter); - rewriter.replaceOp(funcOp.getOperation(), llvm::None); - +LogicalResult +mlir::spirv::setABIAttrs(FuncOp funcOp, spirv::EntryPointABIAttr entryPointInfo, + ArrayRef argABIInfo) { // Set the attributes for argument and the function. StringRef argABIAttrName = spirv::getInterfaceVarABIAttrName(); - for (auto argIndex : llvm::seq(0, newFuncOp.getNumArguments())) { - newFuncOp.setArgAttr(argIndex, argABIAttrName, argABIInfo[argIndex]); + for (auto argIndex : llvm::seq(0, funcOp.getNumArguments())) { + funcOp.setArgAttr(argIndex, argABIAttrName, argABIInfo[argIndex]); } - newFuncOp.setAttr(spirv::getEntryPointABIAttrName(), entryPointInfo); - return newFuncOp; + funcOp.setAttr(spirv::getEntryPointABIAttrName(), entryPointInfo); + return success(); } diff --git a/third_party/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp b/third_party/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp index 66af4305858..140470b8df8 100644 --- a/third_party/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp +++ b/third_party/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp @@ -22,6 +22,7 @@ #include "mlir/Dialect/SPIRV/SPIRVOps.h" #include "mlir/Analysis/CallInterfaces.h" +#include "mlir/Dialect/CommonFolders.h" #include "mlir/Dialect/SPIRV/SPIRVDialect.h" #include "mlir/Dialect/SPIRV/SPIRVTypes.h" #include "mlir/IR/Builders.h" @@ -48,6 +49,7 @@ static constexpr const char kIndicesAttrName[] = "indices"; static constexpr const char kInitializerAttrName[] = "initializer"; static constexpr const char kInterfaceAttrName[] = "interface"; static constexpr const char kMemoryScopeAttrName[] = "memory_scope"; +static constexpr const char kSemanticsAttrName[] = "semantics"; static constexpr const char kSpecConstAttrName[] = "spec_const"; static constexpr const char kSpecIdAttrName[] = "spec_id"; static constexpr const char kTypeAttrName[] = "type"; @@ -75,6 +77,21 @@ static LogicalResult extractValueFromConstOp(Operation *op, return success(); } +template +static ArrayAttr +getStrArrayAttrForEnumList(Builder &builder, ArrayRef enumValues, + llvm::function_ref stringifyFn) { + if (enumValues.empty()) { + return nullptr; + } + SmallVector enumValStrs; + enumValStrs.reserve(enumValues.size()); + for (auto val : enumValues) { + enumValStrs.emplace_back(stringifyFn(val)); + } + return builder.getStrArrayAttr(enumValStrs); +} + template static ParseResult parseEnumAttribute(EnumClass &value, OpAsmParser &parser, @@ -380,31 +397,71 @@ static unsigned getBitWidth(Type type) { /// Walks the given type hierarchy with the given indices, potentially down /// to component granularity, to select an element type. Returns null type and /// emits errors with the given loc on failure. -static Type getElementType(Type type, ArrayAttr indices, Location loc) { - if (!indices.size()) { - emitError(loc, "expected at least one index"); +static Type +getElementType(Type type, ArrayRef indices, + llvm::function_ref emitErrorFn) { + if (indices.empty()) { + emitErrorFn("expected at least one index for spv.CompositeExtract"); return nullptr; } - int32_t index; - for (auto indexAttr : indices) { - index = indexAttr.dyn_cast().getInt(); + for (auto index : indices) { if (auto cType = type.dyn_cast()) { if (index < 0 || static_cast(index) >= cType.getNumElements()) { - emitError(loc, "index ") << index << " out of bounds for " << type; + emitErrorFn("index ") << index << " out of bounds for " << type; return nullptr; } type = cType.getElementType(index); } else { - emitError(loc, "cannot extract from non-composite type ") + emitErrorFn("cannot extract from non-composite type ") << type << " with index " << index; return nullptr; } } - return type; } +static Type +getElementType(Type type, Attribute indices, + llvm::function_ref emitErrorFn) { + auto indicesArrayAttr = indices.dyn_cast(); + if (!indicesArrayAttr) { + emitErrorFn("expected a 32-bit integer array attribute for 'indices'"); + return nullptr; + } + if (!indicesArrayAttr.size()) { + emitErrorFn("expected at least one index for spv.CompositeExtract"); + return nullptr; + } + + SmallVector indexVals; + for (auto indexAttr : indicesArrayAttr) { + auto indexIntAttr = indexAttr.dyn_cast(); + if (!indexIntAttr) { + emitErrorFn("expected an 32-bit integer for index, but found '") + << indexAttr << "'"; + return nullptr; + } + indexVals.push_back(indexIntAttr.getInt()); + } + return getElementType(type, indexVals, emitErrorFn); +} + +static Type getElementType(Type type, Attribute indices, Location loc) { + auto errorFn = [&](StringRef err) -> InFlightDiagnostic { + return ::mlir::emitError(loc, err); + }; + return getElementType(type, indices, errorFn); +} + +static Type getElementType(Type type, Attribute indices, OpAsmParser &parser, + llvm::SMLoc loc) { + auto errorFn = [&](StringRef err) -> InFlightDiagnostic { + return parser.emitError(loc, err); + }; + return getElementType(type, indices, errorFn); +} + /// Returns true if the given `block` only contains one `spv._merge` op. static inline bool isMergeBlock(Block &block) { return !block.empty() && std::next(block.begin()) == block.end() && @@ -444,11 +501,8 @@ static ParseResult parseBitFieldExtractOp(OpAsmParser &parser, } static void printBitFieldExtractOp(Operation *op, OpAsmPrinter &printer) { - printer << op->getName() << ' '; - printer.printOperands(op->getOperands()); - printer << " : " << op->getOperand(0)->getType() << ", " - << op->getOperand(1)->getType() << ", " - << op->getOperand(2)->getType(); + printer << op->getName() << ' ' << op->getOperands() << " : " + << op->getOperandTypes(); } static LogicalResult verifyBitFieldExtractOp(Operation *op) { @@ -461,6 +515,70 @@ static LogicalResult verifyBitFieldExtractOp(Operation *op) { return success(); } +// Parses an atomic update op. If the update op does not take a value (like +// AtomicIIncrement) `hasValue` must be false. +static ParseResult parseAtomicUpdateOp(OpAsmParser &parser, + OperationState &state, bool hasValue) { + spirv::Scope scope; + spirv::MemorySemantics memoryScope; + SmallVector operandInfo; + OpAsmParser::OperandType ptrInfo, valueInfo; + Type type; + llvm::SMLoc loc; + if (parseEnumAttribute(scope, parser, state, kMemoryScopeAttrName) || + parseEnumAttribute(memoryScope, parser, state, kSemanticsAttrName) || + parser.parseOperandList(operandInfo, (hasValue ? 2 : 1)) || + parser.getCurrentLocation(&loc) || parser.parseColonType(type)) + return failure(); + + auto ptrType = type.dyn_cast(); + if (!ptrType) + return parser.emitError(loc, "expected pointer type"); + + SmallVector operandTypes; + operandTypes.push_back(ptrType); + if (hasValue) + operandTypes.push_back(ptrType.getPointeeType()); + if (parser.resolveOperands(operandInfo, operandTypes, parser.getNameLoc(), + state.operands)) + return failure(); + return parser.addTypeToList(ptrType.getPointeeType(), state.types); +} + +// Prints an atomic update op. +static void printAtomicUpdateOp(Operation *op, OpAsmPrinter &printer) { + printer << op->getName() << " \""; + auto scopeAttr = op->getAttrOfType(kMemoryScopeAttrName); + printer << spirv::stringifyScope( + static_cast(scopeAttr.getInt())) + << "\" \""; + auto memorySemanticsAttr = op->getAttrOfType(kSemanticsAttrName); + printer << spirv::stringifyMemorySemantics( + static_cast( + memorySemanticsAttr.getInt())) + << "\" " << op->getOperands() << " : " + << op->getOperand(0)->getType(); +} + +// Verifies an atomic update op. +static LogicalResult verifyAtomicUpdateOp(Operation *op) { + auto ptrType = op->getOperand(0)->getType().cast(); + auto elementType = ptrType.getPointeeType(); + if (!elementType.isa()) + return op->emitOpError( + "pointer operand must point to an integer value, found ") + << elementType; + + if (op->getNumOperands() > 1) { + auto valueType = op->getOperand(1)->getType(); + if (valueType != elementType) + return op->emitOpError("expected value to have the same type as the " + "pointer operand's pointee type ") + << elementType << ", but found " << valueType; + } + return success(); +} + // Parses an op that has no inputs and no outputs. static ParseResult parseNoIOOp(OpAsmParser &parser, OperationState &state) { if (parser.parseOptionalAttrDict(state.attributes)) @@ -524,9 +642,8 @@ static ParseResult parseLogicalBinaryOp(OpAsmParser &parser, } static void printLogicalOp(Operation *logicalOp, OpAsmPrinter &printer) { - printer << logicalOp->getName() << ' '; - printer.printOperands(logicalOp->getOperands()); - printer << " : " << logicalOp->getOperand(0)->getType(); + printer << logicalOp->getName() << ' ' << logicalOp->getOperands() << " : " + << logicalOp->getOperand(0)->getType(); } static ParseResult parseShiftOp(OpAsmParser &parser, OperationState &state) { @@ -661,9 +778,7 @@ static ParseResult parseAccessChainOp(OpAsmParser &parser, static void print(spirv::AccessChainOp op, OpAsmPrinter &printer) { printer << spirv::AccessChainOp::getOperationName() << ' ' << *op.base_ptr() - << '['; - printer.printOperands(op.indices()); - printer << "] : " << op.base_ptr()->getType(); + << '[' << op.indices() << "] : " << op.base_ptr()->getType(); } static LogicalResult verify(spirv::AccessChainOp accessChainOp) { @@ -819,9 +934,8 @@ static void print(spirv::AtomicCompareExchangeWeakOp atomOp, printer << spirv::AtomicCompareExchangeWeakOp::getOperationName() << " \"" << stringifyScope(atomOp.memory_scope()) << "\" \"" << stringifyMemorySemantics(atomOp.equal_semantics()) << "\" \"" - << stringifyMemorySemantics(atomOp.unequal_semantics()) << "\" "; - printer.printOperands(atomOp.getOperands()); - printer << " : " << atomOp.pointer()->getType(); + << stringifyMemorySemantics(atomOp.unequal_semantics()) << "\" " + << atomOp.getOperands() << " : " << atomOp.pointer()->getType(); } static LogicalResult verify(spirv::AtomicCompareExchangeWeakOp atomOp) { @@ -919,9 +1033,9 @@ static ParseResult parseBitFieldInsertOp(OpAsmParser &parser, static void print(spirv::BitFieldInsertOp bitFieldInsertOp, OpAsmPrinter &printer) { - printer << spirv::BitFieldInsertOp::getOperationName() << ' '; - printer.printOperands(bitFieldInsertOp.getOperands()); - printer << " : " << bitFieldInsertOp.base()->getType() << ", " + printer << spirv::BitFieldInsertOp::getOperationName() << ' ' + << bitFieldInsertOp.getOperands() << " : " + << bitFieldInsertOp.base()->getType() << ", " << bitFieldInsertOp.offset()->getType() << ", " << bitFieldInsertOp.count()->getType(); } @@ -1016,8 +1130,8 @@ static ParseResult parseBranchConditionalOp(OpAsmParser &parser, } static void print(spirv::BranchConditionalOp branchOp, OpAsmPrinter &printer) { - printer << spirv::BranchConditionalOp::getOperationName() << ' '; - printer.printOperand(branchOp.condition()); + printer << spirv::BranchConditionalOp::getOperationName() << ' ' + << branchOp.condition(); if (auto weights = branchOp.branch_weights()) { printer << " ["; @@ -1053,17 +1167,95 @@ static LogicalResult verify(spirv::BranchConditionalOp branchOp) { return success(); } +//===----------------------------------------------------------------------===// +// spv.CompositeConstruct +//===----------------------------------------------------------------------===// + +static ParseResult parseCompositeConstructOp(OpAsmParser &parser, + OperationState &state) { + SmallVector operands; + Type type; + auto loc = parser.getCurrentLocation(); + + if (parser.parseOperandList(operands) || parser.parseColonType(type)) { + return failure(); + } + auto cType = type.dyn_cast(); + if (!cType) { + return parser.emitError( + loc, "result type must be a composite type, but provided ") + << type; + } + + if (operands.size() != cType.getNumElements()) { + return parser.emitError(loc, "has incorrect number of operands: expected ") + << cType.getNumElements() << ", but provided " << operands.size(); + } + // TODO: Add support for constructing a vector type from the vector operands. + // According to the spec: "for constructing a vector, the operands may + // also be vectors with the same component type as the Result Type component + // type". + SmallVector elementTypes; + elementTypes.reserve(cType.getNumElements()); + for (auto index : llvm::seq(0, cType.getNumElements())) { + elementTypes.push_back(cType.getElementType(index)); + } + state.addTypes(type); + return parser.resolveOperands(operands, elementTypes, loc, state.operands); +} + +static void print(spirv::CompositeConstructOp compositeConstructOp, + OpAsmPrinter &printer) { + printer << spirv::CompositeConstructOp::getOperationName() << " " + << compositeConstructOp.constituents() << " : " + << compositeConstructOp.getResult()->getType(); +} + +static LogicalResult verify(spirv::CompositeConstructOp compositeConstructOp) { + auto cType = compositeConstructOp.getType().cast(); + + SmallVector constituents(compositeConstructOp.constituents()); + if (constituents.size() != cType.getNumElements()) { + return compositeConstructOp.emitError( + "has incorrect number of operands: expected ") + << cType.getNumElements() << ", but provided " + << constituents.size(); + } + + for (auto index : llvm::seq(0, constituents.size())) { + if (constituents[index]->getType() != cType.getElementType(index)) { + return compositeConstructOp.emitError( + "operand type mismatch: expected operand type ") + << cType.getElementType(index) << ", but provided " + << constituents[index]->getType(); + } + } + + return success(); +} + //===----------------------------------------------------------------------===// // spv.CompositeExtractOp //===----------------------------------------------------------------------===// +void spirv::CompositeExtractOp::build(Builder *builder, OperationState &state, + Value *composite, + ArrayRef indices) { + auto indexAttr = builder->getI32ArrayAttr(indices); + auto elementType = + getElementType(composite->getType(), indexAttr, state.location); + if (!elementType) { + return; + } + build(builder, state, elementType, composite, indexAttr); +} + static ParseResult parseCompositeExtractOp(OpAsmParser &parser, OperationState &state) { OpAsmParser::OperandType compositeInfo; Attribute indicesAttr; Type compositeType; llvm::SMLoc attrLocation; - int32_t index; if (parser.parseOperand(compositeInfo) || parser.getCurrentLocation(&attrLocation) || @@ -1073,42 +1265,11 @@ static ParseResult parseCompositeExtractOp(OpAsmParser &parser, return failure(); } - auto indicesArrayAttr = indicesAttr.dyn_cast(); - if (!indicesArrayAttr) { - return parser.emitError( - attrLocation, - "expected an 32-bit integer array attribute for 'indices'"); + Type resultType = + getElementType(compositeType, indicesAttr, parser, attrLocation); + if (!resultType) { + return failure(); } - - if (!indicesArrayAttr.size()) { - return parser.emitError( - attrLocation, "expected at least one index for spv.CompositeExtract"); - } - - Type resultType = compositeType; - for (auto indexAttr : indicesArrayAttr) { - if (auto indexIntAttr = indexAttr.dyn_cast()) { - index = indexIntAttr.getInt(); - } else { - return parser.emitError( - attrLocation, - "expected an 32-bit integer for index, but found '") - << indexAttr << "'"; - } - - if (auto cType = resultType.dyn_cast()) { - if (index < 0 || static_cast(index) >= cType.getNumElements()) { - return parser.emitError(attrLocation, "index ") - << index << " out of bounds for " << resultType; - } - resultType = cType.getElementType(index); - } else { - return parser.emitError(attrLocation, - "cannot extract from non-composite type ") - << resultType << " with index " << index; - } - } - state.addTypes(resultType); return success(); } @@ -1219,9 +1380,8 @@ static ParseResult parseConstantOp(OpAsmParser &parser, OperationState &state) { static void print(spirv::ConstantOp constOp, OpAsmPrinter &printer) { printer << spirv::ConstantOp::getOperationName() << ' ' << constOp.value(); - if (constOp.getType().isa()) { + if (constOp.getType().isa()) printer << " : " << constOp.getType(); - } } static LogicalResult verify(spirv::ConstantOp constOp) { @@ -1474,9 +1634,8 @@ static void print(spirv::ExecutionModeOp execModeOp, OpAsmPrinter &printer) { << execModeOp.fn() << " \"" << stringifyExecutionMode(execModeOp.execution_mode()) << "\""; auto values = execModeOp.values(); - if (!values.size()) { + if (!values.size()) return; - } printer << ", "; interleaveComma(values, printer, [&](Attribute a) { printer << a.cast().getInt(); @@ -1523,9 +1682,8 @@ static void print(spirv::FunctionCallOp functionCallOp, OpAsmPrinter &printer) { FunctionType::get(argTypes, resultTypes, functionCallOp.getContext()); printer << spirv::FunctionCallOp::getOperationName() << ' ' - << functionCallOp.getAttr(kCallee) << '('; - printer.printOperands(functionCallOp.arguments()); - printer << ") : " << functionType; + << functionCallOp.getAttr(kCallee) << '(' + << functionCallOp.arguments() << ") : " << functionType; } static LogicalResult verify(spirv::FunctionCallOp functionCallOp) { @@ -1610,6 +1768,16 @@ void spirv::GlobalVariableOp::build(Builder *builder, OperationState &state, builder->getI32IntegerAttr(binding)); } +void spirv::GlobalVariableOp::build(Builder *builder, OperationState &state, + Type type, StringRef name, + spirv::BuiltIn builtin) { + build(builder, state, TypeAttr::get(type), builder->getStringAttr(name), + nullptr); + state.addAttribute( + spirv::SPIRVDialect::getAttributeName(spirv::Decoration::BuiltIn), + builder->getStringAttr(spirv::stringifyBuiltIn(builtin))); +} + static ParseResult parseGlobalVariableOp(OpAsmParser &parser, OperationState &state) { // Parse variable name. @@ -1716,9 +1884,8 @@ static ParseResult parseGroupNonUniformBallotOp(OpAsmParser &parser, static void print(spirv::GroupNonUniformBallotOp ballotOp, OpAsmPrinter &printer) { printer << spirv::GroupNonUniformBallotOp::getOperationName() << " \"" - << stringifyScope(ballotOp.execution_scope()) << "\" "; - printer.printOperand(ballotOp.predicate()); - printer << " : " << ballotOp.getType(); + << stringifyScope(ballotOp.execution_scope()) << "\" " + << ballotOp.predicate() << " : " << ballotOp.getType(); } static LogicalResult verify(spirv::GroupNonUniformBallotOp ballotOp) { @@ -1738,11 +1905,17 @@ static LogicalResult verify(spirv::GroupNonUniformBallotOp ballotOp) { OpFoldResult spirv::IAddOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "spv.IAdd expects two operands"); - // lhs + 0 = lhs + // x + 0 = x if (matchPattern(operand2(), m_Zero())) return operand1(); - return nullptr; + // According to the SPIR-V spec: + // + // The resulting value will equal the low-order N bits of the correct result + // R, where N is the component width and R is computed with enough precision + // to avoid overflow and underflow. + return constFoldBinaryOp(operands, + [](APInt a, APInt b) { return a + b; }); } //===----------------------------------------------------------------------===// @@ -1751,14 +1924,38 @@ OpFoldResult spirv::IAddOp::fold(ArrayRef operands) { OpFoldResult spirv::IMulOp::fold(ArrayRef operands) { assert(operands.size() == 2 && "spv.IMul expects two operands"); - // lhs * 0 == 0 + // x * 0 == 0 if (matchPattern(operand2(), m_Zero())) return operand2(); - // lhs * 1 = lhs + // x * 1 = x if (matchPattern(operand2(), m_One())) return operand1(); - return nullptr; + // According to the SPIR-V spec: + // + // The resulting value will equal the low-order N bits of the correct result + // R, where N is the component width and R is computed with enough precision + // to avoid overflow and underflow. + return constFoldBinaryOp(operands, + [](APInt a, APInt b) { return a * b; }); +} + +//===----------------------------------------------------------------------===// +// spv.ISub +//===----------------------------------------------------------------------===// + +OpFoldResult spirv::ISubOp::fold(ArrayRef operands) { + // x - x = 0 + if (operand1() == operand2()) + return Builder(getContext()).getIntegerAttr(getType(), 0); + + // According to the SPIR-V spec: + // + // The resulting value will equal the low-order N bits of the correct result + // R, where N is the component width and R is computed with enough precision + // to avoid overflow and underflow. + return constFoldBinaryOp(operands, + [](APInt a, APInt b) { return a - b; }); } //===----------------------------------------------------------------------===// @@ -1800,9 +1997,8 @@ static void print(spirv::LoadOp loadOp, OpAsmPrinter &printer) { SmallVector elidedAttrs; StringRef sc = stringifyStorageClass( loadOp.ptr()->getType().cast().getStorageClass()); - printer << spirv::LoadOp::getOperationName() << " \"" << sc << "\" "; - // Print the pointer operand. - printer.printOperand(loadOp.ptr()); + printer << spirv::LoadOp::getOperationName() << " \"" << sc << "\" " + << loadOp.ptr(); printMemoryAccessAttribute(loadOp, printer, elidedAttrs); @@ -2039,20 +2235,38 @@ void spirv::ModuleOp::build(Builder *builder, OperationState &state) { ensureTerminator(*state.addRegion(), *builder, state.location); } +// TODO(ravishankarm): This is only here for resolving some dependency outside +// of mlir. Remove once it is done. void spirv::ModuleOp::build(Builder *builder, OperationState &state, IntegerAttr addressing_model, - IntegerAttr memory_model, ArrayAttr capabilities, - ArrayAttr extensions, - ArrayAttr extended_instruction_sets) { + IntegerAttr memory_model) { state.addAttribute("addressing_model", addressing_model); state.addAttribute("memory_model", memory_model); - if (capabilities) - state.addAttribute("capabilities", capabilities); - if (extensions) - state.addAttribute("extensions", extensions); + build(builder, state); +} + +void spirv::ModuleOp::build(Builder *builder, OperationState &state, + spirv::AddressingModel addressing_model, + spirv::MemoryModel memory_model, + ArrayRef capabilities, + ArrayRef extensions, + ArrayAttr extended_instruction_sets) { + state.addAttribute( + "addressing_model", + builder->getI32IntegerAttr(static_cast(addressing_model))); + state.addAttribute("memory_model", builder->getI32IntegerAttr( + static_cast(memory_model))); + if (!capabilities.empty()) + state.addAttribute("capabilities", + getStrArrayAttrForEnumList( + *builder, capabilities, spirv::stringifyCapability)); + if (!extensions.empty()) + state.addAttribute("extensions", + getStrArrayAttrForEnumList( + *builder, extensions, spirv::stringifyExtension)); if (extended_instruction_sets) state.addAttribute("extended_instruction_sets", extended_instruction_sets); - ensureTerminator(*state.addRegion(), *builder, state.location); + build(builder, state); } static ParseResult parseModuleOp(OpAsmParser &parser, OperationState &state) { @@ -2077,26 +2291,26 @@ static ParseResult parseModuleOp(OpAsmParser &parser, OperationState &state) { } static void print(spirv::ModuleOp moduleOp, OpAsmPrinter &printer) { - auto *op = moduleOp.getOperation(); + printer << spirv::ModuleOp::getOperationName(); // Only print out addressing model and memory model in a nicer way if both - // presents. Otherwise, print them in the general form. This helps debugging - // ill-formed ModuleOp. + // presents. Otherwise, print them in the general form. This helps + // debugging ill-formed ModuleOp. SmallVector elidedAttrs; auto addressingModelAttrName = spirv::attributeName(); auto memoryModelAttrName = spirv::attributeName(); - if (op->getAttr(addressingModelAttrName) && - op->getAttr(memoryModelAttrName)) { - printer << spirv::ModuleOp::getOperationName() << " \"" + if (moduleOp.getAttr(addressingModelAttrName) && + moduleOp.getAttr(memoryModelAttrName)) { + printer << " \"" << spirv::stringifyAddressingModel(moduleOp.addressing_model()) << "\" \"" << spirv::stringifyMemoryModel(moduleOp.memory_model()) << '"'; elidedAttrs.assign({addressingModelAttrName, memoryModelAttrName}); } - printer.printRegion(op->getRegion(0), /*printEntryBlockArgs=*/false, + printer.printRegion(moduleOp.body(), /*printEntryBlockArgs=*/false, /*printBlockTerminators=*/false); - printer.printOptionalAttrDictWithKeyword(op->getAttrs(), elidedAttrs); + printer.printOptionalAttrDictWithKeyword(moduleOp.getAttrs(), elidedAttrs); } static LogicalResult verify(spirv::ModuleOp moduleOp) { @@ -2256,9 +2470,8 @@ static ParseResult parseReturnValueOp(OpAsmParser &parser, } static void print(spirv::ReturnValueOp retValOp, OpAsmPrinter &printer) { - printer << spirv::ReturnValueOp::getOperationName() << ' '; - printer.printOperand(retValOp.value()); - printer << " : " << retValOp.value()->getType(); + printer << spirv::ReturnValueOp::getOperationName() << ' ' << retValOp.value() + << " : " << retValOp.value()->getType(); } static LogicalResult verify(spirv::ReturnValueOp retValOp) { @@ -2310,13 +2523,8 @@ static ParseResult parseSelectOp(OpAsmParser &parser, OperationState &state) { } static void print(spirv::SelectOp op, OpAsmPrinter &printer) { - printer << spirv::SelectOp::getOperationName() << " "; - - // Print the operands. - printer.printOperands(op.getOperands()); - - // Print colon and types. - printer << " : " << op.condition()->getType() << ", " + printer << spirv::SelectOp::getOperationName() << " " << op.getOperands() + << " : " << op.condition()->getType() << ", " << op.result()->getType(); } @@ -2627,8 +2835,7 @@ static void print(spirv::SpecConstantOp constOp, OpAsmPrinter &printer) { printer.printSymbolName(constOp.sym_name()); if (auto specID = constOp.getAttrOfType(kSpecIdAttrName)) printer << ' ' << kSpecIdAttrName << '(' << specID.getInt() << ')'; - printer << " = "; - printer.printAttribute(constOp.default_value()); + printer << " = " << constOp.default_value(); } static LogicalResult verify(spirv::SpecConstantOp constOp) { @@ -2683,17 +2890,12 @@ static void print(spirv::StoreOp storeOp, OpAsmPrinter &printer) { SmallVector elidedAttrs; StringRef sc = stringifyStorageClass( storeOp.ptr()->getType().cast().getStorageClass()); - printer << spirv::StoreOp::getOperationName() << " \"" << sc << "\" "; - // Print the pointer operand - printer.printOperand(storeOp.ptr()); - printer << ", "; - // Print the value operand - printer.printOperand(storeOp.value()); + printer << spirv::StoreOp::getOperationName() << " \"" << sc << "\" " + << storeOp.ptr() << ", " << storeOp.value(); printMemoryAccessAttribute(storeOp, printer, elidedAttrs); printer << " : " << storeOp.value()->getType(); - printer.printOptionalAttrDict(op->getAttrs(), elidedAttrs); } @@ -2724,9 +2926,8 @@ static ParseResult parseSubgroupBallotKHROp(OpAsmParser &parser, } static void print(spirv::SubgroupBallotKHROp ballotOp, OpAsmPrinter &printer) { - printer << spirv::SubgroupBallotKHROp::getOperationName() << ' '; - printer.printOperand(ballotOp.predicate()); - printer << " : " << ballotOp.getType(); + printer << spirv::SubgroupBallotKHROp::getOperationName() << ' ' + << ballotOp.predicate() << " : " << ballotOp.getType(); } //===----------------------------------------------------------------------===// @@ -2812,20 +3013,15 @@ static ParseResult parseVariableOp(OpAsmParser &parser, OperationState &state) { } static void print(spirv::VariableOp varOp, OpAsmPrinter &printer) { - auto *op = varOp.getOperation(); SmallVector elidedAttrs{ spirv::attributeName()}; printer << spirv::VariableOp::getOperationName(); // Print optional initializer - if (op->getNumOperands() > 0) { - printer << " init("; - printer.printOperands(varOp.initializer()); - printer << ")"; - } - - printVariableDecorations(op, printer, elidedAttrs); + if (varOp.getNumOperands() != 0) + printer << " init(" << varOp.initializer() << ")"; + printVariableDecorations(varOp, printer, elidedAttrs); printer << " : " << varOp.getType(); } diff --git a/third_party/mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp b/third_party/mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp index ebafcb8675e..2cb75de084a 100644 --- a/third_party/mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp +++ b/third_party/mlir/lib/Dialect/SPIRV/Serialization/Serializer.cpp @@ -450,7 +450,7 @@ private: /// But we don't know the for %val0 and %val1 yet. One way is to visit /// all the blocks twice and use the first visit to assign an to each /// value. But it's paying the overheads just for OpPhi emission. Instead, - /// we still visit the blocks once for emssion. When we emit the OpPhi + /// we still visit the blocks once for emission. When we emit the OpPhi /// instructions, we use 0 as a placeholder for the s for %val0 and %val1. /// At the same time, we record their offsets in the emitted binary (which is /// placed inside `functions`) here. And then after emitting all blocks, we diff --git a/third_party/mlir/lib/Dialect/StandardOps/Ops.cpp b/third_party/mlir/lib/Dialect/StandardOps/Ops.cpp index ee90ceab064..3189e42d061 100644 --- a/third_party/mlir/lib/Dialect/StandardOps/Ops.cpp +++ b/third_party/mlir/lib/Dialect/StandardOps/Ops.cpp @@ -163,18 +163,21 @@ StandardOpsDialect::StandardOpsDialect(MLIRContext *context) addInterfaces(); } +/// Materialize a single constant operation from a given attribute value with +/// the desired resultant type. +Operation *StandardOpsDialect::materializeConstant(OpBuilder &builder, + Attribute value, Type type, + Location loc) { + return builder.create(loc, type, value); +} + void mlir::printDimAndSymbolList(Operation::operand_iterator begin, Operation::operand_iterator end, unsigned numDims, OpAsmPrinter &p) { - p << '('; - p.printOperands(begin, begin + numDims); - p << ')'; - - if (begin + numDims != end) { - p << '['; - p.printOperands(begin + numDims, end); - p << ']'; - } + Operation::operand_range operands(begin, end); + p << '(' << operands.take_front(numDims) << ')'; + if (operands.size() != numDims) + p << '[' << operands.drop_front(numDims) << ']'; } // Parses dimension and symbol list, and sets 'numDims' to the number of @@ -209,32 +212,20 @@ static detail::op_matcher m_ConstantIndex() { // Common canonicalization pattern support logic //===----------------------------------------------------------------------===// -namespace { /// This is a common class used for patterns of the form /// "someop(memrefcast) -> someop". It folds the source of any memref_cast /// into the root operation directly. -struct MemRefCastFolder : public RewritePattern { - /// The rootOpName is the name of the root operation to match against. - MemRefCastFolder(StringRef rootOpName, MLIRContext *context) - : RewritePattern(rootOpName, 1, context) {} - - PatternMatchResult match(Operation *op) const override { - for (auto *operand : op->getOperands()) - if (matchPattern(operand, m_Op())) - return matchSuccess(); - - return matchFailure(); +static LogicalResult foldMemRefCast(Operation *op) { + bool folded = false; + for (OpOperand &operand : op->getOpOperands()) { + auto cast = dyn_cast_or_null(operand.get()->getDefiningOp()); + if (cast && !cast.getOperand()->getType().isa()) { + operand.set(cast.getOperand()); + folded = true; + } } - - void rewrite(Operation *op, PatternRewriter &rewriter) const override { - for (unsigned i = 0, e = op->getNumOperands(); i != e; ++i) - if (auto *memref = op->getOperand(i)->getDefiningOp()) - if (auto cast = dyn_cast(memref)) - op->setOperand(i, cast.getOperand()); - rewriter.updatedRootInPlace(op); - } -}; -} // end anonymous namespace. + return success(folded); +} //===----------------------------------------------------------------------===// // AddFOp @@ -485,12 +476,9 @@ static ParseResult parseCallOp(OpAsmParser &parser, OperationState &result) { } static void print(OpAsmPrinter &p, CallOp op) { - p << "call " << op.getAttr("callee") << '('; - p.printOperands(op.getOperands()); - p << ')'; + p << "call " << op.getAttr("callee") << '(' << op.getOperands() << ')'; p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/{"callee"}); - p << " : "; - p.printType(op.getCalleeType()); + p << " : " << op.getCalleeType(); } static LogicalResult verify(CallOp op) { @@ -572,11 +560,7 @@ static ParseResult parseCallIndirectOp(OpAsmParser &parser, } static void print(OpAsmPrinter &p, CallIndirectOp op) { - p << "call_indirect "; - p.printOperand(op.getCallee()); - p << '('; - p.printOperands(op.getArgOperands()); - p << ')'; + p << "call_indirect " << op.getCallee() << '(' << op.getArgOperands() << ')'; p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/{"callee"}); p << " : " << op.getCallee()->getType(); } @@ -690,12 +674,7 @@ static void print(OpAsmPrinter &p, CmpIOp op) { auto predicateValue = op.getAttrOfType(CmpIOp::getPredicateAttrName()).getInt(); p << '"' << stringifyCmpIPredicate(static_cast(predicateValue)) - << '"'; - - p << ", "; - p.printOperand(op.lhs()); - p << ", "; - p.printOperand(op.rhs()); + << '"' << ", " << op.lhs() << ", " << op.rhs(); p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/{CmpIOp::getPredicateAttrName()}); p << " : " << op.lhs()->getType(); @@ -851,15 +830,8 @@ static void print(OpAsmPrinter &p, CmpFOp op) { assert(predicateValue >= static_cast(CmpFPredicate::FirstValidValue) && predicateValue < static_cast(CmpFPredicate::NumPredicates) && "unknown predicate index"); - Builder b(op.getContext()); - auto predicateStringAttr = - b.getStringAttr(getCmpFPredicateNames()[predicateValue]); - p.printAttribute(predicateStringAttr); - - p << ", "; - p.printOperand(op.lhs()); - p << ", "; - p.printOperand(op.rhs()); + p << '"' << getCmpFPredicateNames()[predicateValue] << '"' << ", " << op.lhs() + << ", " << op.rhs(); p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/{CmpFOp::getPredicateAttrName()}); p << " : " << op.lhs()->getType(); @@ -1002,9 +974,7 @@ static ParseResult parseCondBranchOp(OpAsmParser &parser, } static void print(OpAsmPrinter &p, CondBranchOp op) { - p << "cond_br "; - p.printOperand(op.getCondition()); - p << ", "; + p << "cond_br " << op.getCondition() << ", "; p.printSuccessorAndUseList(op.getOperation(), CondBranchOp::trueIndex); p << ", "; p.printSuccessorAndUseList(op.getOperation(), CondBranchOp::falseIndex); @@ -1025,7 +995,7 @@ static void print(OpAsmPrinter &p, ConstantOp &op) { if (op.getAttrs().size() > 1) p << ' '; - p.printAttribute(op.getValue()); + p << op.getValue(); // If the value is a symbol reference, print a trailing type. if (op.getValue().isa()) @@ -1250,11 +1220,15 @@ static LogicalResult verify(DeallocOp op) { void DeallocOp::getCanonicalizationPatterns(OwningRewritePatternList &results, MLIRContext *context) { - /// dealloc(memrefcast) -> dealloc - results.insert(getOperationName(), context); results.insert(context); } +LogicalResult DeallocOp::fold(ArrayRef cstOperands, + SmallVectorImpl &results) { + /// dealloc(memrefcast) -> dealloc + return foldMemRefCast(*this); +} + //===----------------------------------------------------------------------===// // DimOp //===----------------------------------------------------------------------===// @@ -1322,7 +1296,6 @@ OpFoldResult DimOp::fold(ArrayRef operands) { return {}; // The size at getIndex() is now a dynamic size of a memref. - auto memref = memrefOrTensor()->getDefiningOp(); if (auto alloc = dyn_cast_or_null(memref)) return *(alloc.getDynamicSizes().begin() + @@ -1339,13 +1312,11 @@ OpFoldResult DimOp::fold(ArrayRef operands) { return *(sizes.begin() + getIndex()); } - return {}; -} - -void DimOp::getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context) { /// dim(memrefcast) -> dim - results.insert(getOperationName(), context); + if (succeeded(foldMemRefCast(*this))) + return getResult(); + + return {}; } //===----------------------------------------------------------------------===// @@ -1407,18 +1378,12 @@ void DmaStartOp::build(Builder *builder, OperationState &result, } void DmaStartOp::print(OpAsmPrinter &p) { - p << "dma_start " << *getSrcMemRef() << '['; - p.printOperands(getSrcIndices()); - p << "], " << *getDstMemRef() << '['; - p.printOperands(getDstIndices()); - p << "], " << *getNumElements(); - p << ", " << *getTagMemRef() << '['; - p.printOperands(getTagIndices()); - p << ']'; - if (isStrided()) { - p << ", " << *getStride(); - p << ", " << *getNumElementsPerStride(); - } + p << "dma_start " << *getSrcMemRef() << '[' << getSrcIndices() << "], " + << *getDstMemRef() << '[' << getDstIndices() << "], " << *getNumElements() + << ", " << *getTagMemRef() << '[' << getTagIndices() << ']'; + if (isStrided()) + p << ", " << *getStride() << ", " << *getNumElementsPerStride(); + p.printOptionalAttrDict(getAttrs()); p << " : " << getSrcMemRef()->getType(); p << ", " << getDstMemRef()->getType(); @@ -1531,10 +1496,10 @@ LogicalResult DmaStartOp::verify() { return success(); } -void DmaStartOp::getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context) { +LogicalResult DmaStartOp::fold(ArrayRef cstOperands, + SmallVectorImpl &results) { /// dma_start(memrefcast) -> dma_start - results.insert(getOperationName(), context); + return foldMemRefCast(*this); } // --------------------------------------------------------------------------- @@ -1550,12 +1515,8 @@ void DmaWaitOp::build(Builder *builder, OperationState &result, } void DmaWaitOp::print(OpAsmPrinter &p) { - p << "dma_wait "; - p.printOperand(getTagMemRef()); - p << '['; - p.printOperands(getTagIndices()); - p << "], "; - p.printOperand(getNumElements()); + p << "dma_wait " << getTagMemRef() << '[' << getTagIndices() << "], " + << getNumElements(); p.printOptionalAttrDict(getAttrs()); p << " : " << getTagMemRef()->getType(); } @@ -1593,10 +1554,10 @@ ParseResult DmaWaitOp::parse(OpAsmParser &parser, OperationState &result) { return success(); } -void DmaWaitOp::getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context) { +LogicalResult DmaWaitOp::fold(ArrayRef cstOperands, + SmallVectorImpl &results) { /// dma_wait(memrefcast) -> dma_wait - results.insert(getOperationName(), context); + return foldMemRefCast(*this); } //===----------------------------------------------------------------------===// @@ -1604,8 +1565,7 @@ void DmaWaitOp::getCanonicalizationPatterns(OwningRewritePatternList &results, //===----------------------------------------------------------------------===// static void print(OpAsmPrinter &p, ExtractElementOp op) { - p << "extract_element " << *op.getAggregate() << '['; - p.printOperands(op.getIndices()); + p << "extract_element " << *op.getAggregate() << '[' << op.getIndices(); p << ']'; p.printOptionalAttrDict(op.getAttrs()); p << " : " << op.getAggregate()->getType(); @@ -1686,9 +1646,7 @@ bool IndexCastOp::areCastCompatible(Type a, Type b) { //===----------------------------------------------------------------------===// static void print(OpAsmPrinter &p, LoadOp op) { - p << "load " << *op.getMemRef() << '['; - p.printOperands(op.getIndices()); - p << ']'; + p << "load " << *op.getMemRef() << '[' << op.getIndices() << ']'; p.printOptionalAttrDict(op.getAttrs()); p << " : " << op.getMemRefType(); } @@ -1719,10 +1677,11 @@ static LogicalResult verify(LoadOp op) { return success(); } -void LoadOp::getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context) { +OpFoldResult LoadOp::fold(ArrayRef cstOperands) { /// load(memrefcast) -> load - results.insert(getOperationName(), context); + if (succeeded(foldMemRefCast(*this))) + return getResult(); + return OpFoldResult(); } //===----------------------------------------------------------------------===// @@ -1922,12 +1881,8 @@ static ParseResult parseReturnOp(OpAsmParser &parser, OperationState &result) { static void print(OpAsmPrinter &p, ReturnOp op) { p << "return"; - if (op.getNumOperands() != 0) { - p << ' '; - p.printOperands(op.getOperands()); - p << " : "; - interleaveComma(op.getOperandTypes(), p); - } + if (op.getNumOperands() != 0) + p << ' ' << op.getOperands() << " : " << op.getOperandTypes(); } static LogicalResult verify(ReturnOp op) { @@ -1984,9 +1939,7 @@ static ParseResult parseSelectOp(OpAsmParser &parser, OperationState &result) { } static void print(OpAsmPrinter &p, SelectOp op) { - p << "select "; - p.printOperands(op.getOperands()); - p << " : " << op.getTrueValue()->getType(); + p << "select " << op.getOperands() << " : " << op.getTrueValue()->getType(); p.printOptionalAttrDict(op.getAttrs()); } @@ -2093,9 +2046,7 @@ OpFoldResult SplatOp::fold(ArrayRef operands) { static void print(OpAsmPrinter &p, StoreOp op) { p << "store " << *op.getValueToStore(); - p << ", " << *op.getMemRef() << '['; - p.printOperands(op.getIndices()); - p << ']'; + p << ", " << *op.getMemRef() << '[' << op.getIndices() << ']'; p.printOptionalAttrDict(op.getAttrs()); p << " : " << op.getMemRefType(); } @@ -2131,10 +2082,10 @@ static LogicalResult verify(StoreOp op) { return success(); } -void StoreOp::getCanonicalizationPatterns(OwningRewritePatternList &results, - MLIRContext *context) { +LogicalResult StoreOp::fold(ArrayRef cstOperands, + SmallVectorImpl &results) { /// store(memrefcast) -> store - results.insert(getOperationName(), context); + return foldMemRefCast(*this); } //===----------------------------------------------------------------------===// @@ -2339,9 +2290,7 @@ static void print(OpAsmPrinter &p, ViewOp op) { auto *dynamicOffset = op.getDynamicOffset(); if (dynamicOffset != nullptr) p.printOperand(dynamicOffset); - p << "]["; - p.printOperands(op.getDynamicSizes()); - p << ']'; + p << "][" << op.getDynamicSizes() << ']'; p.printOptionalAttrDict(op.getAttrs()); p << " : " << op.getOperand(0)->getType() << " to " << op.getType(); } @@ -2609,13 +2558,8 @@ static ParseResult parseSubViewOp(OpAsmParser &parser, OperationState &result) { } static void print(OpAsmPrinter &p, SubViewOp op) { - p << op.getOperationName() << ' ' << *op.getOperand(0) << '['; - p.printOperands(op.offsets()); - p << "]["; - p.printOperands(op.sizes()); - p << "]["; - p.printOperands(op.strides()); - p << ']'; + p << op.getOperationName() << ' ' << *op.getOperand(0) << '[' << op.offsets() + << "][" << op.sizes() << "][" << op.strides() << ']'; SmallVector elidedAttrs = { SubViewOp::getOperandSegmentSizeAttr()}; @@ -2819,7 +2763,7 @@ public: return matchFailure(); } SmallVector staticShape(subViewOp.getNumSizes()); - for (auto size : enumerate(subViewOp.sizes())) { + for (auto size : llvm::enumerate(subViewOp.sizes())) { auto defOp = size.value()->getDefiningOp(); assert(defOp); staticShape[size.index()] = cast(defOp).getValue(); @@ -2865,7 +2809,7 @@ public: } SmallVector staticStrides(subViewOp.getNumStrides()); - for (auto stride : enumerate(subViewOp.strides())) { + for (auto stride : llvm::enumerate(subViewOp.strides())) { auto defOp = stride.value()->getDefiningOp(); assert(defOp); assert(baseStrides[stride.index()] > 0); @@ -2916,7 +2860,7 @@ public: } auto staticOffset = baseOffset; - for (auto offset : enumerate(subViewOp.offsets())) { + for (auto offset : llvm::enumerate(subViewOp.offsets())) { auto defOp = offset.value()->getDefiningOp(); assert(defOp); assert(baseStrides[offset.index()] > 0); diff --git a/third_party/mlir/lib/Dialect/VectorOps/CMakeLists.txt b/third_party/mlir/lib/Dialect/VectorOps/CMakeLists.txt index 754e62de14e..08d58404b71 100644 --- a/third_party/mlir/lib/Dialect/VectorOps/CMakeLists.txt +++ b/third_party/mlir/lib/Dialect/VectorOps/CMakeLists.txt @@ -1,7 +1,7 @@ add_llvm_library(MLIRVectorOps DialectRegistration.cpp VectorOps.cpp - VectorToVector.cpp + VectorTransforms.cpp ADDITIONAL_HEADER_DIRS ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/VectorOps diff --git a/third_party/mlir/lib/Dialect/VectorOps/VectorOps.cpp b/third_party/mlir/lib/Dialect/VectorOps/VectorOps.cpp index c1e88aa0076..48fc0d4dc30 100644 --- a/third_party/mlir/lib/Dialect/VectorOps/VectorOps.cpp +++ b/third_party/mlir/lib/Dialect/VectorOps/VectorOps.cpp @@ -22,6 +22,7 @@ #include "mlir/Dialect/VectorOps/VectorOps.h" #include "mlir/Dialect/StandardOps/Ops.h" +#include "mlir/Dialect/Utils/StructuredOpsUtils.h" #include "mlir/IR/AffineExpr.h" #include "mlir/IR/AffineMap.h" #include "mlir/IR/Builders.h" @@ -30,6 +31,8 @@ #include "mlir/IR/TypeUtilities.h" #include "mlir/Support/Functional.h" #include "mlir/Support/LLVM.h" +#include "mlir/Support/MathExtras.h" +#include "mlir/Support/STLExtras.h" #include "llvm/ADT/StringSet.h" using namespace mlir; @@ -39,7 +42,7 @@ using namespace mlir::vector; // VectorOpsDialect //===----------------------------------------------------------------------===// -mlir::vector::VectorOpsDialect::VectorOpsDialect(MLIRContext *context) +VectorOpsDialect::VectorOpsDialect(MLIRContext *context) : Dialect(getDialectNamespace(), context) { addOperations< #define GET_OP_LIST @@ -47,10 +50,28 @@ mlir::vector::VectorOpsDialect::VectorOpsDialect(MLIRContext *context) >(); } +/// Materialize a single constant operation from a given attribute value with +/// the desired resultant type. +Operation *VectorOpsDialect::materializeConstant(OpBuilder &builder, + Attribute value, Type type, + Location loc) { + return builder.create(loc, type, value); +} + //===----------------------------------------------------------------------===// // ContractionOp //===----------------------------------------------------------------------===// +void vector::ContractionOp::build(Builder *builder, OperationState &result, + Value *lhs, Value *rhs, Value *acc, + ArrayAttr indexingMaps, + ArrayAttr iteratorTypes) { + result.addOperands({lhs, rhs, acc}); + result.addTypes(acc->getType()); + result.addAttribute(getIndexingMapsAttrName(), indexingMaps); + result.addAttribute(getIteratorTypesAttrName(), iteratorTypes); +} + static ParseResult parseContractionOp(OpAsmParser &parser, OperationState &result) { OpAsmParser::OperandType lhsInfo; @@ -99,17 +120,16 @@ static void print(OpAsmPrinter &p, ContractionOp op) { llvm::StringSet<> traitAttrsSet; traitAttrsSet.insert(attrNames.begin(), attrNames.end()); SmallVector attrs; - for (auto attr : op.getAttrs()) { + for (auto attr : op.getAttrs()) if (traitAttrsSet.count(attr.first.strref()) > 0) attrs.push_back(attr); - } + auto dictAttr = DictionaryAttr::get(attrs, op.getContext()); p << op.getOperationName() << " " << dictAttr << " " << *op.lhs() << ", "; p << *op.rhs() << ", " << *op.acc(); - if (llvm::size(op.masks()) == 2) { - p << ", " << **op.masks().begin(); - p << ", " << **(op.masks().begin() + 1); - } + if (op.masks().size() == 2) + p << ", " << op.masks(); + p.printOptionalAttrDict(op.getAttrs(), attrNames); p << " : " << op.lhs()->getType() << ", " << op.rhs()->getType() << " into " << op.getResultType(); @@ -235,8 +255,11 @@ static LogicalResult verify(ContractionOp op) { return success(); } -SmallVector ContractionOp::getTraitAttrNames() { - return SmallVector{"indexing_maps", "iterator_types"}; +ArrayRef ContractionOp::getTraitAttrNames() { + static constexpr StringLiteral names[2] = {getIndexingMapsAttrName(), + getIteratorTypesAttrName()}; + ArrayRef res{names}; + return ArrayRef{res.begin(), res.end()}; } static int64_t getResultIndex(AffineMap map, AffineExpr targetExpr) { @@ -323,6 +346,42 @@ SmallVector ContractionOp::getIndexingMaps() { return res; } +//===----------------------------------------------------------------------===// +// ExtractElementOp +//===----------------------------------------------------------------------===// + +static void print(OpAsmPrinter &p, vector::ExtractElementOp op) { + p << op.getOperationName() << " " << *op.vector() << "[" << *op.position() + << " : " << op.position()->getType() << "]"; + p.printOptionalAttrDict(op.getAttrs()); + p << " : " << op.vector()->getType(); +} + +static ParseResult parseExtractElementOp(OpAsmParser &parser, + OperationState &result) { + OpAsmParser::OperandType vector, position; + Type positionType; + VectorType vectorType; + if (parser.parseOperand(vector) || parser.parseLSquare() || + parser.parseOperand(position) || parser.parseColonType(positionType) || + parser.parseRSquare() || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(vectorType)) + return failure(); + Type resultType = vectorType.getElementType(); + return failure( + parser.resolveOperand(vector, vectorType, result.operands) || + parser.resolveOperand(position, positionType, result.operands) || + parser.addTypeToList(resultType, result.types)); +} + +static LogicalResult verify(vector::ExtractElementOp op) { + VectorType vectorType = op.getVectorType(); + if (vectorType.getRank() != 1) + return op.emitOpError("expected 1-D vector"); + return success(); +} + //===----------------------------------------------------------------------===// // ExtractOp //===----------------------------------------------------------------------===// @@ -389,7 +448,7 @@ static LogicalResult verify(vector::ExtractOp op) { for (auto en : llvm::enumerate(positionAttr)) { auto attr = en.value().dyn_cast(); if (!attr || attr.getInt() < 0 || - attr.getInt() > op.getVectorType().getDimSize(en.index())) + attr.getInt() >= op.getVectorType().getDimSize(en.index())) return op.emitOpError("expected position attribute #") << (en.index() + 1) << " to be a non-negative integer smaller than the corresponding " @@ -398,14 +457,145 @@ static LogicalResult verify(vector::ExtractOp op) { return success(); } +//===----------------------------------------------------------------------===// +// ExtractSlicesOp +//===----------------------------------------------------------------------===// + +void ExtractSlicesOp::build(Builder *builder, OperationState &result, + TupleType tupleType, Value *vector, + ArrayRef sizes, + ArrayRef strides) { + result.addOperands(vector); + auto sizesAttr = builder->getI64ArrayAttr(sizes); + auto stridesAttr = builder->getI64ArrayAttr(strides); + result.addTypes(tupleType); + result.addAttribute(getSizesAttrName(), sizesAttr); + result.addAttribute(getStridesAttrName(), stridesAttr); +} + +static ParseResult parseExtractSlicesOp(OpAsmParser &parser, + OperationState &result) { + OpAsmParser::OperandType operandInfo; + ArrayAttr sizesAttr; + StringRef sizesAttrName = ExtractSlicesOp::getSizesAttrName(); + ArrayAttr stridesAttr; + StringRef stridesAttrName = ExtractSlicesOp::getStridesAttrName(); + VectorType vectorType; + TupleType resultTupleType; + return failure( + parser.parseOperand(operandInfo) || parser.parseComma() || + parser.parseAttribute(sizesAttr, sizesAttrName, result.attributes) || + parser.parseComma() || + parser.parseAttribute(stridesAttr, stridesAttrName, result.attributes) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(vectorType) || + parser.parseKeywordType("into", resultTupleType) || + parser.resolveOperand(operandInfo, vectorType, result.operands) || + parser.addTypeToList(resultTupleType, result.types)); +} + +static void print(OpAsmPrinter &p, ExtractSlicesOp op) { + p << op.getOperationName() << ' ' << *op.vector() << ", "; + p << op.sizes() << ", " << op.strides(); + p.printOptionalAttrDict( + op.getAttrs(), + /*elidedAttrs=*/{ExtractSlicesOp::getSizesAttrName(), + ExtractSlicesOp::getStridesAttrName()}); + p << " : " << op.vector()->getType(); + p << " into " << op.getResultTupleType(); +} + +static LogicalResult +isValidExtractOrInsertSlicesType(Operation *op, VectorType vectorType, + TupleType tupleType, ArrayRef sizes, + ArrayRef strides) { + // Check for non-unit strides. + // TODO(b/144845578) Support non-1 strides. + if (llvm::any_of(strides, [](int64_t s) { return s != 1; })) + return op->emitError("requires unit strides"); + // Check that 'vectorType' rank matches rank of tuple element vectors. + unsigned rank = vectorType.getRank(); + auto is_vector_type_of_rank = [&](Type t) { + return t.isa() && t.cast().getRank() == rank; + }; + if (!llvm::all_of(tupleType.getTypes(), is_vector_type_of_rank)) + return op->emitError("requires vector tuple elements of rank ") << rank; + // Check that 'sizes' and 'strides' are of size == 'rank'. + if (sizes.size() != rank || strides.size() != rank) + return op->emitError("requires sizes and strides of rank ") << rank; + + // Compute the number of slices in each dimension. + // TODO(andydavis) Move this into a slice generation helper function. + auto shape = vectorType.getShape(); + SmallVector dimSliceCounts(rank); + for (unsigned i = 0; i < rank; ++i) + dimSliceCounts[i] = ceilDiv(shape[i], sizes[i]); + // Compute the strides between slices in each dimension. + SmallVector sliceStrides(rank); + sliceStrides[rank - 1] = 1; + for (int i = rank - 2; i >= 0; --i) + sliceStrides[i] = sliceStrides[i + 1] * dimSliceCounts[i + 1]; + + // Generate each slice shape based on 'sizes', 'strides' and 'vectorType', + // and varify that the same matches the corresponding tuple element 'i'. + for (int64_t i = 0, e = tupleType.size(); i < e; ++i) { + // De-linearize w.r.t. 'sliceStrides'. + SmallVector vectorOffsets(rank); + int64_t linearIndex = i; + for (unsigned j = 0; j < rank; ++j) { + vectorOffsets.push_back(linearIndex / sliceStrides[i]); + linearIndex %= sliceStrides[i]; + } + // Convert from unrolled vector-space offsets to element-space offsets. + auto offsets = mlir::functional::zipMap( + [](int64_t v1, int64_t v2) { return v1 * v2; }, vectorOffsets, sizes); + // Initialize 'sliceSizes' to target 'sizes' + SmallVector sliceSizes(sizes.begin(), sizes.end()); + for (unsigned j = 0; j < rank; ++j) { + // Based on 'offsets' and 'shape' clip some dim sizes for partial tiles. + sliceSizes[j] = std::min(sliceSizes[j], shape[j] - offsets[j]); + } + // Create slice VectorType type. + auto sliceVectorType = + VectorType::get(sliceSizes, vectorType.getElementType()); + // Verify that 'sliceVectorType' matches tupleType.getTypes(i) + if (sliceVectorType != tupleType.getType(i)) + return op->emitError("invalid tuple element type ") << sliceVectorType; + } + return success(); +} + +static LogicalResult verify(ExtractSlicesOp op) { + SmallVector sizes; + op.getSizes(sizes); + SmallVector strides; + op.getStrides(strides); + return isValidExtractOrInsertSlicesType( + op.getOperation(), op.getSourceVectorType(), op.getResultTupleType(), + sizes, strides); +} + +static void populateFromInt64AttrArray(ArrayAttr arrayAttr, + SmallVectorImpl &results) { + for (auto attr : arrayAttr) + results.push_back(attr.cast().getInt()); +} + +void ExtractSlicesOp::getSizes(SmallVectorImpl &results) { + populateFromInt64AttrArray(sizes(), results); +} + +void ExtractSlicesOp::getStrides(SmallVectorImpl &results) { + populateFromInt64AttrArray(strides(), results); +} + //===----------------------------------------------------------------------===// // BroadcastOp //===----------------------------------------------------------------------===// static void print(OpAsmPrinter &p, BroadcastOp op) { - p << op.getOperationName() << " " << *op.source(); - p << " : " << op.getSourceType(); - p << " to " << op.getVectorType(); + p << op.getOperationName() << " " << *op.source() << " : " + << op.getSourceType() << " to " << op.getVectorType(); } static LogicalResult verify(BroadcastOp op) { @@ -444,6 +634,130 @@ static ParseResult parseBroadcastOp(OpAsmParser &parser, parser.addTypeToList(vectorType, result.types)); } +//===----------------------------------------------------------------------===// +// ShuffleOp +//===----------------------------------------------------------------------===// + +void ShuffleOp::build(Builder *builder, OperationState &result, Value *v1, + Value *v2, ArrayRef mask) { + result.addOperands({v1, v2}); + auto maskAttr = builder->getI32ArrayAttr(mask); + result.addTypes(v1->getType()); + result.addAttribute(getMaskAttrName(), maskAttr); +} + +static void print(OpAsmPrinter &p, ShuffleOp op) { + p << op.getOperationName() << " " << *op.v1() << ", " << *op.v2() << " " + << op.mask(); + p.printOptionalAttrDict(op.getAttrs(), {ShuffleOp::getMaskAttrName()}); + p << " : " << op.v1()->getType() << ", " << op.v2()->getType(); +} + +static LogicalResult verify(ShuffleOp op) { + VectorType resultType = op.getVectorType(); + VectorType v1Type = op.getV1VectorType(); + VectorType v2Type = op.getV2VectorType(); + // Verify ranks. + int64_t resRank = resultType.getRank(); + int64_t v1Rank = v1Type.getRank(); + int64_t v2Rank = v2Type.getRank(); + if (resRank != v1Rank || v1Rank != v2Rank) + return op.emitOpError("rank mismatch"); + // Verify all but leading dimension sizes. + for (int64_t r = 1; r < v1Rank; ++r) { + int64_t resDim = resultType.getDimSize(r); + int64_t v1Dim = v1Type.getDimSize(r); + int64_t v2Dim = v2Type.getDimSize(r); + if (resDim != v1Dim || v1Dim != v2Dim) + return op.emitOpError("dimension mismatch"); + } + // Verify mask length. + auto maskAttr = op.mask().getValue(); + int64_t maskLength = maskAttr.size(); + if (maskLength != resultType.getDimSize(0)) + return op.emitOpError("mask length mismatch"); + // Verify all indices. + int64_t indexSize = v1Type.getDimSize(0) + v2Type.getDimSize(0); + for (auto en : llvm::enumerate(maskAttr)) { + auto attr = en.value().dyn_cast(); + if (!attr || attr.getInt() < 0 || attr.getInt() >= indexSize) + return op.emitOpError("mask index #") + << (en.index() + 1) << " out of range"; + } + return success(); +} + +static ParseResult parseShuffleOp(OpAsmParser &parser, OperationState &result) { + OpAsmParser::OperandType v1, v2; + Attribute attr; + VectorType v1Type, v2Type; + if (parser.parseOperand(v1) || parser.parseComma() || + parser.parseOperand(v2) || + parser.parseAttribute(attr, ShuffleOp::getMaskAttrName(), + result.attributes) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(v1Type) || parser.parseComma() || + parser.parseType(v2Type) || + parser.resolveOperand(v1, v1Type, result.operands) || + parser.resolveOperand(v2, v2Type, result.operands)) + return failure(); + // Construct resulting type: leading dimension matches mask length, + // all trailing dimensions match the operands. + auto maskAttr = attr.dyn_cast(); + if (!maskAttr) + return parser.emitError(parser.getNameLoc(), "missing mask attribute"); + int64_t maskLength = maskAttr.size(); + if (maskLength <= 0) + return parser.emitError(parser.getNameLoc(), "invalid mask length"); + int64_t v1Rank = v1Type.getRank(); + SmallVector shape; + shape.reserve(v1Rank); + shape.push_back(maskLength); + for (int64_t r = 1; r < v1Rank; ++r) + shape.push_back(v1Type.getDimSize(r)); + VectorType resType = VectorType::get(shape, v1Type.getElementType()); + parser.addTypeToList(resType, result.types); + return success(); +} + +//===----------------------------------------------------------------------===// +// InsertElementOp +//===----------------------------------------------------------------------===// + +static void print(OpAsmPrinter &p, InsertElementOp op) { + p << op.getOperationName() << " " << *op.source() << ", " << *op.dest() << "[" + << *op.position() << " : " << op.position()->getType() << "]"; + p.printOptionalAttrDict(op.getAttrs()); + p << " : " << op.dest()->getType(); +} + +static ParseResult parseInsertElementOp(OpAsmParser &parser, + OperationState &result) { + OpAsmParser::OperandType source, dest, position; + Type positionType; + VectorType destType; + if (parser.parseOperand(source) || parser.parseComma() || + parser.parseOperand(dest) || parser.parseLSquare() || + parser.parseOperand(position) || parser.parseColonType(positionType) || + parser.parseRSquare() || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(destType)) + return failure(); + Type sourceType = destType.getElementType(); + return failure( + parser.resolveOperand(source, sourceType, result.operands) || + parser.resolveOperand(dest, destType, result.operands) || + parser.resolveOperand(position, positionType, result.operands) || + parser.addTypeToList(destType, result.types)); +} + +static LogicalResult verify(InsertElementOp op) { + auto dstVectorType = op.getDestVectorType(); + if (dstVectorType.getRank() != 1) + return op.emitOpError("expected 1-D vector"); + return success(); +} + //===----------------------------------------------------------------------===// // InsertOp //===----------------------------------------------------------------------===// @@ -460,8 +774,7 @@ static void print(OpAsmPrinter &p, InsertOp op) { p << op.getOperationName() << " " << *op.source() << ", " << *op.dest() << op.position(); p.printOptionalAttrDict(op.getAttrs(), {InsertOp::getPositionAttrName()}); - p << " : " << op.getSourceType(); - p << " into " << op.getDestVectorType(); + p << " : " << op.getSourceType() << " into " << op.getDestVectorType(); } static ParseResult parseInsertOp(OpAsmParser &parser, OperationState &result) { @@ -503,7 +816,7 @@ static LogicalResult verify(InsertOp op) { for (auto en : llvm::enumerate(positionAttr)) { auto attr = en.value().dyn_cast(); if (!attr || attr.getInt() < 0 || - attr.getInt() > destVectorType.getDimSize(en.index())) + attr.getInt() >= destVectorType.getDimSize(en.index())) return op.emitOpError("expected position attribute #") << (en.index() + 1) << " to be a non-negative integer smaller than the corresponding " @@ -512,6 +825,60 @@ static LogicalResult verify(InsertOp op) { return success(); } +//===----------------------------------------------------------------------===// +// InsertSlicesOp +//===----------------------------------------------------------------------===// + +static ParseResult parseInsertSlicesOp(OpAsmParser &parser, + OperationState &result) { + OpAsmParser::OperandType operandInfo; + ArrayAttr sizesAttr; + StringRef sizesAttrName = InsertSlicesOp::getSizesAttrName(); + ArrayAttr stridesAttr; + StringRef stridesAttrName = InsertSlicesOp::getStridesAttrName(); + TupleType tupleType; + VectorType resultVectorType; + return failure( + parser.parseOperand(operandInfo) || parser.parseComma() || + parser.parseAttribute(sizesAttr, sizesAttrName, result.attributes) || + parser.parseComma() || + parser.parseAttribute(stridesAttr, stridesAttrName, result.attributes) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(tupleType) || + parser.parseKeywordType("into", resultVectorType) || + parser.resolveOperand(operandInfo, tupleType, result.operands) || + parser.addTypeToList(resultVectorType, result.types)); +} + +static void print(OpAsmPrinter &p, InsertSlicesOp op) { + p << op.getOperationName() << ' ' << *op.vectors() << ", "; + p << op.sizes() << ", " << op.strides(); + p.printOptionalAttrDict( + op.getAttrs(), + /*elidedAttrs=*/{InsertSlicesOp::getSizesAttrName(), + InsertSlicesOp::getStridesAttrName()}); + p << " : " << op.vectors()->getType(); + p << " into " << op.getResultVectorType(); +} + +static LogicalResult verify(InsertSlicesOp op) { + SmallVector sizes; + op.getSizes(sizes); + SmallVector strides; + op.getStrides(strides); + return isValidExtractOrInsertSlicesType( + op.getOperation(), op.getResultVectorType(), op.getSourceTupleType(), + sizes, strides); +} + +void InsertSlicesOp::getSizes(SmallVectorImpl &results) { + populateFromInt64AttrArray(sizes(), results); +} + +void InsertSlicesOp::getStrides(SmallVectorImpl &results) { + populateFromInt64AttrArray(strides(), results); +} + //===----------------------------------------------------------------------===// // InsertStridedSliceOp //===----------------------------------------------------------------------===// @@ -683,8 +1050,8 @@ static LogicalResult verify(InsertStridedSliceOp op) { static void print(OpAsmPrinter &p, OuterProductOp op) { p << op.getOperationName() << " " << *op.lhs() << ", " << *op.rhs(); - if (llvm::size(op.acc()) > 0) - p << ", " << **op.acc().begin(); + if (!op.acc().empty()) + p << ", " << op.acc(); p << " : " << op.lhs()->getType() << ", " << op.rhs()->getType(); } @@ -829,14 +1196,12 @@ static LogicalResult verify(StridedSliceOp op) { return success(); } -namespace { - -static void populateFromInt64AttrArray(ArrayAttr arrayAttr, - SmallVectorImpl &results) { - for (auto attr : arrayAttr) - results.push_back(attr.cast().getInt()); +void StridedSliceOp::getOffsets(SmallVectorImpl &results) { + populateFromInt64AttrArray(offsets(), results); } +namespace { + // Pattern to rewrite a StridedSliceOp(ConstantMaskOp) -> ConstantMaskOp. class StridedSliceConstantMaskFolder final : public OpRewritePattern { @@ -930,16 +1295,10 @@ static LogicalResult verifyPermutationMap(AffineMap permutationMap, } static void print(OpAsmPrinter &p, TransferReadOp op) { - p << op.getOperationName() << " "; - p.printOperand(op.memref()); - p << "["; - p.printOperands(op.indices()); - p << "], "; - p.printOperand(op.padding()); - p << " "; + p << op.getOperationName() << " " << op.memref() << "[" << op.indices() + << "], " << op.padding() << " "; p.printOptionalAttrDict(op.getAttrs()); - p << " : " << op.getMemRefType(); - p << ", " << op.getVectorType(); + p << " : " << op.getMemRefType() << ", " << op.getVectorType(); } ParseResult parseTransferReadOp(OpAsmParser &parser, OperationState &result) { @@ -1002,15 +1361,10 @@ static LogicalResult verify(TransferReadOp op) { // TransferWriteOp //===----------------------------------------------------------------------===// static void print(OpAsmPrinter &p, TransferWriteOp op) { - p << op.getOperationName() << " " << *op.vector() << ", " << *op.memref(); - p << "["; - p.printOperands(op.indices()); - p << "]"; + p << op.getOperationName() << " " << *op.vector() << ", " << *op.memref() + << "[" << op.indices() << "]"; p.printOptionalAttrDict(op.getAttrs()); - p << " : "; - p.printType(op.getVectorType()); - p << ", "; - p.printType(op.getMemRefType()); + p << " : " << op.getVectorType() << ", " << op.getMemRefType(); } ParseResult parseTransferWriteOp(OpAsmParser &parser, OperationState &result) { @@ -1076,19 +1430,86 @@ void TypeCastOp::build(Builder *builder, OperationState &result, inferVectorTypeCastResultType(source->getType().cast())); } -static void print(OpAsmPrinter &p, TypeCastOp &op) { +static void print(OpAsmPrinter &p, TypeCastOp op) { auto type = op.getOperand()->getType().cast(); p << op.getOperationName() << ' ' << *op.memref() << " : " << type << " to " << inferVectorTypeCastResultType(type); } -static LogicalResult verify(TypeCastOp &op) { +static LogicalResult verify(TypeCastOp op) { auto resultType = inferVectorTypeCastResultType(op.getMemRefType()); if (op.getResultMemRefType() != resultType) return op.emitOpError("expects result type to be: ") << resultType; return success(); } +//===----------------------------------------------------------------------===// +// TupleOp +//===----------------------------------------------------------------------===// + +static ParseResult parseTupleOp(OpAsmParser &parser, OperationState &result) { + SmallVector operandInfos; + SmallVector types; + auto loc = parser.getCurrentLocation(); + auto *ctx = parser.getBuilder().getContext(); + return failure( + parser.parseOperandList(operandInfos) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonTypeList(types) || + parser.resolveOperands(operandInfos, types, loc, result.operands) || + parser.addTypeToList(TupleType::get(types, ctx), result.types)); +} + +static void print(OpAsmPrinter &p, TupleOp op) { + p << op.getOperationName() << ' '; + p.printOperands(op.getOperands()); + p.printOptionalAttrDict(op.getAttrs()); + p << " : "; + interleaveComma(op.getOperation()->getOperandTypes(), p); +} + +static LogicalResult verify(TupleOp op) { return success(); } + +//===----------------------------------------------------------------------===// +// TupleGetOp +//===----------------------------------------------------------------------===// + +static ParseResult parseTupleGetOp(OpAsmParser &parser, + OperationState &result) { + OpAsmParser::OperandType operandInfo; + IntegerAttr indexAttr; + StringRef indexAttrName = TupleGetOp::getIndexAttrName(); + Type indexType = parser.getBuilder().getIndexType(); + TupleType tupleType; + VectorType resultVectorType; + if (parser.parseOperand(operandInfo) || parser.parseComma() || + parser.parseAttribute(indexAttr, indexType, indexAttrName, + result.attributes) || + parser.parseOptionalAttrDict(result.attributes) || + parser.parseColonType(tupleType) || + parser.resolveOperand(operandInfo, tupleType, result.operands)) + return failure(); + if (indexAttr.getInt() < 0 || + indexAttr.getInt() >= static_cast(tupleType.size())) + return failure(); + parser.addTypeToList(tupleType.getType(indexAttr.getInt()), result.types); + return success(); +} + +static void print(OpAsmPrinter &p, TupleGetOp op) { + p << op.getOperationName() << ' ' << *op.getOperand() << ", " << op.index(); + p.printOptionalAttrDict(op.getAttrs(), + /*elidedAttrs=*/{TupleGetOp::getIndexAttrName()}); + p << " : " << op.getOperand()->getType(); +} + +static LogicalResult verify(TupleGetOp op) { + auto tupleType = op.getOperand()->getType().cast(); + if (op.getIndex() < 0 || op.getIndex() >= tupleType.size()) + return op.emitOpError("tuple get index out of range"); + return success(); +} + //===----------------------------------------------------------------------===// // ConstantMaskOp //===----------------------------------------------------------------------===// @@ -1104,9 +1525,9 @@ ParseResult parseConstantMaskOp(OpAsmParser &parser, OperationState &result) { parser.addTypeToList(resultType, result.types)); } -static void print(OpAsmPrinter &p, ConstantMaskOp &op) { - p << op.getOperationName() << ' ' << op.mask_dim_sizes(); - p << " : " << op.getResult()->getType(); +static void print(OpAsmPrinter &p, ConstantMaskOp op) { + p << op.getOperationName() << ' ' << op.mask_dim_sizes() << " : " + << op.getResult()->getType(); } static LogicalResult verify(ConstantMaskOp &op) { @@ -1152,13 +1573,11 @@ ParseResult parseCreateMaskOp(OpAsmParser &parser, OperationState &result) { parser.addTypeToList(resultType, result.types)); } -static void print(OpAsmPrinter &p, CreateMaskOp &op) { - p << op.getOperationName() << ' '; - p.printOperands(op.operands()); - p << " : " << op.getResult()->getType(); +static void print(OpAsmPrinter &p, CreateMaskOp op) { + p << op.getOperationName() << ' ' << op.operands() << " : " << op.getType(); } -static LogicalResult verify(CreateMaskOp &op) { +static LogicalResult verify(CreateMaskOp op) { // Verify that an operand was specified for each result vector each dimension. if (op.getNumOperands() != op.getResult()->getType().cast().getRank()) diff --git a/third_party/mlir/lib/Dialect/VectorOps/VectorToVector.cpp b/third_party/mlir/lib/Dialect/VectorOps/VectorToVector.cpp deleted file mode 100644 index 82d19f5efc5..00000000000 --- a/third_party/mlir/lib/Dialect/VectorOps/VectorToVector.cpp +++ /dev/null @@ -1,690 +0,0 @@ -//===- VectorToLoops.cpp - Conversion within the Vector dialect -----------===// -// -// Copyright 2019 The MLIR Authors. -// -// 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. -// ============================================================================= -// -// This file implements target-independent rewrites as 1->N patterns. -// -//===----------------------------------------------------------------------===// - -#include - -#include "mlir/Dialect/VectorOps/Utils.h" -#include "mlir/Dialect/VectorOps/VectorOps.h" -#include "mlir/Dialect/VectorOps/VectorTransforms.h" -#include "mlir/EDSC/Builders.h" -#include "mlir/EDSC/Helpers.h" -#include "mlir/IR/AffineExpr.h" -#include "mlir/IR/AffineMap.h" -#include "mlir/IR/Attributes.h" -#include "mlir/IR/Builders.h" -#include "mlir/IR/Function.h" -#include "mlir/IR/Location.h" -#include "mlir/IR/Matchers.h" -#include "mlir/IR/Module.h" -#include "mlir/IR/OperationSupport.h" -#include "mlir/IR/PatternMatch.h" -#include "mlir/IR/Types.h" -#include "mlir/Support/Functional.h" -#include "mlir/Support/STLExtras.h" - -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Debug.h" -#include "llvm/Support/raw_ostream.h" - -#define DEBUG_TYPE "vector-to-vector" - -using namespace mlir; -using llvm::dbgs; -using mlir::functional::zipMap; - -/// Given a shape with sizes greater than 0 along all dimensions, -/// returns the distance, in number of elements, between a slice in a dimension -/// and the next slice in the same dimension. -/// e.g. shape[3, 4, 5] -> linearization_basis[20, 5, 1] -static SmallVector computeStrides(ArrayRef shape) { - if (shape.empty()) - return {}; - SmallVector tmp; - tmp.reserve(shape.size()); - int64_t running = 1; - for (auto size : llvm::reverse(shape)) { - assert(size > 0 && "size must be nonnegative"); - tmp.push_back(running); - running *= size; - } - return SmallVector(tmp.rbegin(), tmp.rend()); -} - -static int64_t computeMaxLinearIndex(ArrayRef basis) { - if (basis.empty()) - return 0; - int64_t res = 1; - for (auto b : basis) - res *= b; - return res; -} - -/// Computes and returns the linearized index of 'offsets' w.r.t. 'basis'. -static int64_t linearize(ArrayRef offsets, ArrayRef basis) { - assert(offsets.size() == basis.size()); - int64_t linearIndex = 0; - for (unsigned idx = 0, e = basis.size(); idx < e; ++idx) - linearIndex += offsets[idx] * basis[idx]; - return linearIndex; -} - -/// Given a shape with sizes greater than 0 along all dimensions, returns the -/// delinearized components of linearIndex along shape. -static SmallVector delinearize(int64_t linearIndex, - ArrayRef basis) { - SmallVector res; - res.reserve(basis.size()); - for (unsigned idx = 0, e = basis.size(); idx < e; ++idx) { - assert(basis[idx] > 0); - res.push_back(linearIndex / basis[idx]); - linearIndex %= basis[idx]; - } - // Sanity check. - assert(linearIndex == 0 && "linear index remainder must be 0"); - return res; -} - -static constexpr auto kFakeForkOp = "__fake_fork__"; -static constexpr auto kFakeJoinOp = "__fake_join__"; -static constexpr auto kUnrollAttrName = "__unroll__"; -static constexpr auto kBaseCoordAttrName = "__base_coord__"; - -// Reads the IntegerArray attribute named `kUnrollAttrName` from `op` and -// returns its representation as a vector of integers. -static SmallVector extractUnrollFactors(Operation *op) { - SmallVector res; - auto unrollAttr = op->getAttr(kUnrollAttrName); - if (!unrollAttr) - return res; - auto unrollArrayAttr = unrollAttr.cast(); - res.reserve(unrollArrayAttr.size()); - for (auto attr : unrollArrayAttr) { - auto unroll = attr.cast().getValue().getSExtValue(); - assert(unroll > 0); - res.push_back(unroll); - } - return res; -} - -// Creates a custom `kFakeForkOp` used in progressive lowering to other vector -// operations. -static Operation *createFakeForkOp(PatternRewriter &builder, Location loc, - Value *operand, ArrayRef resultTypes, - ArrayRef unrollFactors = {}) { - OperationState *forkOp = - new OperationState(loc, kFakeForkOp, operand, resultTypes, {}); - if (!unrollFactors.empty()) - forkOp->addAttribute(kUnrollAttrName, - builder.getI64ArrayAttr(unrollFactors)); - return builder.createOperation(*forkOp); -} - -// Creates a custom `kFakeJoinOp` used in progressive lowering to other vector -// operations. -static Operation *createFakeJoinOp(PatternRewriter &builder, Location loc, - ArrayRef operands, Type resultType, - ArrayRef unrollFactors = {}, - ArrayRef baseCoords = {}) { - OperationState *joinOp = - new OperationState(loc, kFakeJoinOp, operands, resultType, {}); - if (!unrollFactors.empty()) - joinOp->addAttribute(kUnrollAttrName, - builder.getI64ArrayAttr(unrollFactors)); - if (!baseCoords.empty()) - joinOp->addAttribute(kBaseCoordAttrName, - builder.getI64ArrayAttr(baseCoords)); - return builder.createOperation(*joinOp); -} - -// Clones `op` into a new operations that takes `operands` and returns -// `resultTypes`. -static Operation *cloneOpWithOperandsAndTypes(PatternRewriter &builder, - Location loc, Operation *op, - ArrayRef operands, - ArrayRef resultTypes) { - OperationState res(loc, op->getName().getStringRef(), operands, resultTypes, - op->getAttrs()); - return builder.createOperation(res); -} - -// Helper function for Tablegen. -static bool hasShape(Value *v, ArrayRef shape) { - auto t = v->getType().dyn_cast(); - if (!t) - return false; - return std::equal(t.getShape().begin(), t.getShape().end(), shape.begin()); -} - -static Value *makeSplatZero(Location loc, PatternRewriter &rewriter, - VectorType vt) { - auto t = vt.getElementType(); - Value *f = nullptr; - if (t.isBF16() || t.isF16()) - f = rewriter.create(loc, t, rewriter.getF64FloatAttr(0.0f)); - else if (t.isF32()) - f = rewriter.create(loc, t, rewriter.getF32FloatAttr(0.0f)); - else if (t.isF64()) - f = rewriter.create(loc, t, rewriter.getF64FloatAttr(0.0f)); - if (f) - return rewriter.create(loc, vt, f); - llvm_unreachable("Unsupported type in `makeSplatZero`"); -} - -// Populates 'resultElements[indexMap[i]]' with elements from 'inputElements[i]' -// for each index 'i' in inputElements with a valid mapping in 'indexMap'. -static void getMappedElements(const DenseMap &indexMap, - ArrayRef inputElements, - SmallVectorImpl &resultElements) { - assert(indexMap.size() == resultElements.size()); - assert(inputElements.size() >= resultElements.size()); - for (unsigned i = 0, e = inputElements.size(); i < e; ++i) { - auto it = indexMap.find(i); - if (it != indexMap.end()) - resultElements[it->second] = inputElements[i]; - } -} - -// UnrolledOperandState aggregates per-operand state required for op unrolling. -struct UnrolledOperandState { - Value *operand; - SmallVector unrolledShape; - SmallVector unrollFactors; - SmallVector basis; - int64_t numInstances; -}; - -// Populates 'state' with unrolled shape, unroll factors, basis and -// num unrolled instances for 'operand'. -static void getUnrolledOperandState(Value *operand, - const DenseMap &indexMap, - ArrayRef targetShape, - UnrolledOperandState &state) { - auto vectorType = operand->getType().cast(); - state.operand = operand; - // Compute unrolled shape of 'operand'. - state.unrolledShape.resize(vectorType.getRank()); - getMappedElements(indexMap, targetShape, state.unrolledShape); - // Compute unroll factors for unrolled shape. - auto maybeUnrollFactors = - shapeRatio(vectorType.getShape(), state.unrolledShape); - assert(maybeUnrollFactors.hasValue()); - state.unrollFactors = *maybeUnrollFactors; - // Compute 'basis' and 'numInstances' based on 'state.unrollFactors'. - state.basis = computeStrides(state.unrollFactors); - state.numInstances = computeMaxLinearIndex(state.unrollFactors); -} - -// Computes and returns the linear index of the unrolled vector at -// 'vectorOffsets' within the vector operand represented by 'state'. -static int64_t -getUnrolledOperandLinearIndex(UnrolledOperandState &state, - ArrayRef vectorOffsets, - DenseMap &indexMap) { - // Compute operand offsets. - SmallVector sliceOffsets(state.unrolledShape.size()); - getMappedElements(indexMap, vectorOffsets, sliceOffsets); - // Compute and return linear index of 'sliceOffsets' w.r.t 'state.basis'. - return linearize(sliceOffsets, state.basis); -} - -// Returns an unrolled vector at 'vectorOffsets' within the vector operand -// represented by 'state'. The value is created if not present in 'cache'. -static Value *getOrCreateUnrolledOperandSlice( - Location loc, UnrolledOperandState &state, ArrayRef vectorOffsets, - ArrayRef offsets, DenseMap &indexMap, - SmallVectorImpl &cache, PatternRewriter &builder) { - // Compute operand offsets. - SmallVector sliceOffsets(state.unrolledShape.size()); - getMappedElements(indexMap, offsets, sliceOffsets); - // TODO(b/144845578) Support non-1 strides. - SmallVector sliceStrides(state.unrolledShape.size(), 1); - // Compute linear index of 'sliceOffsets' w.r.t 'state.basis'. - int64_t sliceLinearIndex = - getUnrolledOperandLinearIndex(state, vectorOffsets, indexMap); - assert(sliceLinearIndex < static_cast(cache.size())); - auto *operandSlice = cache[sliceLinearIndex]; - if (operandSlice == nullptr) { - // Initialize 'cache' with slice from 'state.operand'. - operandSlice = builder.create( - loc, state.operand, sliceOffsets, state.unrolledShape, sliceStrides); - // Store value back to 'cache'. - cache[sliceLinearIndex] = operandSlice; - } - return operandSlice; -} - -// -// unrollSingleResultStructuredOp -// -// Returns a value representing the result of structured operation 'op' -// with iteration bounds 'iterationBounds' unrolled to 'targetShape'. -// An iteration space index map argument 'iterationIndexMapList' must be -// specified, with a map for each structured op input and a single map for the -// single result. The map at index 'indexMapListResultIndex' in the list must -// be the single result map. -// -// Example: -// -// // Before unrolling -// -// operand0 operand1 operand2 -// \ | / -// -------------------- opA -------------------- -// -// // After unrolling by 2 -// -// operand0 operand1 operand2 -// / \ / \ / \ -// slice00 slice01 slice10 slice11 slice20 slice21 -// \ | | | / | -// -------------------- opA0 -------------------- | -// | | | | -// \ | | / -// -------------------- opA1 ------------------- -// | | -// \ / -// insertslice -// | - -// TODO(andydavis) Generalize this to support structured ops beyond -// vector ContractionOp, and merge it with 'unrollSingleResultOpMatchingType' -static Value *unrollSingleResultStructuredOp( - Operation *op, ArrayRef iterationBounds, - std::vector> &iterationIndexMapList, - unsigned indexMapListResultIndex, ArrayRef targetShape, - PatternRewriter &builder) { - auto shapedType = op->getResult(0)->getType().dyn_cast_or_null(); - if (!shapedType || !shapedType.hasStaticShape()) - assert(false && "Expected a statically shaped result type"); - - // Compute unroll factors for 'iterationBounds' based on 'targetShape' - auto maybeUnrollFactors = shapeRatio(iterationBounds, targetShape); - if (!maybeUnrollFactors.hasValue()) - assert(false && "Failed to compute unroll factors for target shape"); - auto unrollFactors = *maybeUnrollFactors; - - // Compute unrolled operation state for each mapped operand. - unsigned numMaps = iterationIndexMapList.size(); - SmallVector unrolledOperandState(numMaps); - assert(op->getNumOperands() >= numMaps); - for (unsigned i = 0; i < numMaps; ++i) { - getUnrolledOperandState(op->getOperand(i), iterationIndexMapList[i], - targetShape, unrolledOperandState[i]); - } - // Compute number of total unrolled instances. - auto numUnrolledInstances = computeMaxLinearIndex(unrollFactors); - auto basis = computeStrides(unrollFactors); - - auto &resultOperandState = unrolledOperandState[indexMapListResultIndex]; - auto unrolledResultType = VectorType::get(resultOperandState.unrolledShape, - shapedType.getElementType()); - - // Initialize caches for intermediate vector results. - std::vector> caches(numMaps); - for (unsigned i = 0; i < numMaps; ++i) { - caches[i].resize(unrolledOperandState[i].numInstances); - } - - // Unroll 'numUnrolledInstances' of 'op', storing results in 'caches'. - for (unsigned i = 0; i < numUnrolledInstances; ++i) { - // De-linearize w.r.t. 'basis'. - auto vectorOffsets = delinearize(i, basis); - // Convert from unrolled vector-space offsets to element-space offsets. - auto offsets = zipMap([](int64_t v1, int64_t v2) { return v1 * v2; }, - vectorOffsets, targetShape); - // Get cached slice (or create slice) for each operand at 'offsets'. - SmallVector operands; - operands.reserve(numMaps); - for (unsigned i = 0; i < numMaps; ++i) { - operands.push_back(getOrCreateUnrolledOperandSlice( - op->getLoc(), unrolledOperandState[i], vectorOffsets, offsets, - iterationIndexMapList[i], caches[i], builder)); - } - // Create op on sliced vector arguments. - auto resultVector = - cloneOpWithOperandsAndTypes(builder, op->getLoc(), op, operands, - unrolledResultType) - ->getResult(0); - - // Compute linear result index. - int64_t resultIndex = getUnrolledOperandLinearIndex( - resultOperandState, vectorOffsets, - iterationIndexMapList[indexMapListResultIndex]); - // Update result cache at 'resultIndex'. - caches[indexMapListResultIndex][resultIndex] = resultVector; - } - - // Make zero splat into which we will insert results from - // 'cache[indexMapListResultIndex]' - auto resultVectorType = op->getResult(0)->getType().cast(); - auto *res = makeSplatZero(op->getLoc(), builder, resultVectorType); - SmallVector strides(resultOperandState.unrollFactors.size(), 1); - // Insert vector accumulators into output. - for (unsigned i = 0; i < resultOperandState.numInstances; ++i) { - auto vectorOffsets = delinearize(i, resultOperandState.basis); - // Convert from unrolled vector-space offsets to element-space offsets. - auto offsets = zipMap([](int64_t v1, int64_t v2) { return v1 * v2; }, - vectorOffsets, resultOperandState.unrolledShape); - res = builder.create( - op->getLoc(), caches[indexMapListResultIndex][i], res, offsets, - strides); - } - - return res; -} - -// Entry point for unrolling declarative pattern rewrites. -// `op` is unrolled to the `targetShape` as follows, for each of its operands: -// 1. the unrolled type `unrolledVectorType` and number of unrolled instances -// `numUnrolledInstances` are computed from the `targetShape`. For now it is -// assumed the unrolling factors divide the vector sizes. -// 2. a fakeFork cast op is inserted that takes the operand and returns -// `numUnrolledInstances` results of type `unrolledVectorType`. -// 3. the original op is cloned `numUnrolledInstances` times, once for each -// result of the fakeFork cast op. -// 4. a fakeJoin cast op takes all these results and merges them into a single -// aggregate vector result whose size matches the original non-unrolled op -// operand types. -// -// Example: -// -// opA(operand0, operand1) // numUnrolledInstances = 3 -// -// operand0 operand1 -// | | -// fork fork -// <----------gather all fork ops ---------> -// /|\ /|\ -// f00 f01 f02 f10 f11 f12 -// <---------- clone op 3 times ---------> -// opA0(f00, f10), opA1(f01, f11), opA2(f02, f12) -// \ | / -// <-------------------- join -------------------------> -// -// Other local patterns then kick in iteratively (including DCE) and compose -// until all the fakeFork and fakeJoin ops are removed. -// -// This will be extended in the future to support more advanced use cases than -// simple pointwise ops. -Value * mlir::vector::unrollSingleResultOpMatchingType(PatternRewriter &builder, - Operation *op, - ArrayRef targetShape) { - if (auto contractionOp = dyn_cast(op)) { - // Get contraction op iteration bounds. - SmallVector iterationBounds; - contractionOp.getIterationBounds(iterationBounds); - assert(iterationBounds.size() == targetShape.size()); - // Get map from iteration space index to lhs/rhs/result shape index. - std::vector> iterationIndexMapList; - contractionOp.getIterationIndexMap(iterationIndexMapList); - if (llvm::size(contractionOp.masks()) == 2) { - // Add maps for lhs/rhs vector mask arguments (same lhs/rhs vector shape) - iterationIndexMapList.push_back(iterationIndexMapList[0]); - iterationIndexMapList.push_back(iterationIndexMapList[1]); - } - // Unroll 'op' 'iterationBounds' to 'targetShape'. - // TODO(andydavis) Use linalg style 'args_in'/'args_out' to partition - // 'iterationIndexMapList' instead of 'indexMapListResultIndex'. - return unrollSingleResultStructuredOp( - op, iterationBounds, iterationIndexMapList, - /*indexMapListResultIndex=*/2, targetShape, builder); - } - // TODO(andydavis) Create trivial iteration bounds and index map for - // elementwise operations and call 'unrollSingleResultStructuredOp'. Remove - // fakefork/join if possible. - - LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE - "]: unrollSingleResultOpMatchingType on func:\n"); - LLVM_DEBUG(op->getParentOfType().print(dbgs())); - if (!op->getNumResults()) - assert(false && "Use precondition till RewriterGen can act on nullptr"); - - auto shapedType = op->getResult(0)->getType().dyn_cast_or_null(); - if (!shapedType || !shapedType.hasStaticShape()) - assert(false && "Use precondition till RewriterGen can act on nullptr"); - - auto shape = shapedType.getShape(); - auto maybeUnrollFactors = shapeRatio(shape, targetShape); - if (!maybeUnrollFactors.hasValue()) - assert(false && "Use precondition till RewriterGen can act on nullptr"); - auto unrollFactors = *maybeUnrollFactors; - - auto loc = op->getLoc(); - auto numUnrolledInstances = computeMaxLinearIndex(unrollFactors); - auto unrolledVectorType = - VectorType::get(targetShape, shapedType.getElementType()); - SmallVector forkedType(numUnrolledInstances, unrolledVectorType); - SmallVector forkeds; - forkeds.reserve(numUnrolledInstances); - // Create a new forkOp for each operand. - for (auto *operand : op->getOperands()) - forkeds.push_back( - createFakeForkOp(builder, loc, operand, forkedType, unrollFactors)); - - SmallVector newOps; - newOps.reserve(numUnrolledInstances); - for (int64_t idx = 0; idx < numUnrolledInstances; ++idx) { - SmallVector operands; - operands.reserve(forkeds.size()); - for (auto *fork : forkeds) { - operands.push_back(fork->getResult(idx)); - } - newOps.push_back(cloneOpWithOperandsAndTypes(builder, loc, op, operands, - unrolledVectorType)); - } - - SmallVector newOpResults; - newOpResults.reserve(newOps.size()); - for (auto *newOp : newOps) - newOpResults.push_back(newOp->getResult(0)); - - return createFakeJoinOp(builder, loc, newOpResults, shapedType, unrollFactors, - {0}) - ->getResult(0); -} - -// Patterns with this benefit just forwards arguments to clean up fake fork and -// fake joins. It is a nicer and more direct cleanup when we can use it so it -// kicks in with higher precedence. -static constexpr int64_t kMatchingFakeForkFakeJoinBenefit = 1; - -namespace mlir { -namespace vector { -namespace { -#include "mlir/Dialect/VectorOps/VectorTransformPatterns.h.inc" -} // end namespace -} // end namespace vector -} // end namespace mlir - -// Match a fakeFork fed by a fakeJoin and just forward its operands. -// This is akin to calling `replaceAllUsesOf` but made to play nice with all the -// other RewritePattern. -struct ConvertMatchingFakeForkFakeJoinOp : public RewritePattern { - ConvertMatchingFakeForkFakeJoinOp(MLIRContext *context) - // low-benefit to kick-in late - : RewritePattern(kFakeForkOp, kMatchingFakeForkFakeJoinBenefit, context) { - } - - PatternMatchResult matchAndRewrite(Operation *op, - PatternRewriter &rewriter) const override { - if (op->getNumOperands() != 1) - return matchFailure(); - - auto *definingOp = op->getOperand(0)->getDefiningOp(); - if (!definingOp || definingOp->getName().getStringRef() != kFakeJoinOp) - return matchFailure(); - - if (definingOp->getNumOperands() != op->getNumResults()) - return matchFailure(); - - for (auto it : llvm::zip(definingOp->getOperands(), op->getResults())) { - if (std::get<0>(it)->getType() != std::get<1>(it)->getType()) - return matchFailure(); - } - - LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE - "]: ConvertMatchingFakeForkFakeJoinOp on op: " - << *op << " in func:\n"); - LLVM_DEBUG(op->getParentOfType().print(dbgs())); - rewriter.replaceOp(op, definingOp->getOperands()); - return matchSuccess(); - } -}; - -// Rewrites a fakeFork, whose (unique) operand is a blockArgument, into multiple -// vector.strided_slice ops. -struct ConvertFakeForkFromBlockArgsOrTransferReadOp : public RewritePattern { - ConvertFakeForkFromBlockArgsOrTransferReadOp(MLIRContext *context) - // low-benefit to kick-in late - : RewritePattern(kFakeForkOp, 0, context) {} - - PatternMatchResult matchAndRewrite(Operation *op, - PatternRewriter &rewriter) const override { - if (op->getNumOperands() != 1) - return matchFailure(); - - if (op->use_empty()) { - rewriter.eraseOp(op); - return matchSuccess(); - } - - auto *arg = op->getOperand(0); - if (!isa(arg) && - !isa(arg->getDefiningOp())) - return matchFailure(); - - LLVM_DEBUG(dbgs() << "\n[" DEBUG_TYPE - "]: ConvertFakeForkFromBlockArgsOp on op: " - << *op << " in func:\n"); - LLVM_DEBUG(op->getParentOfType().print(dbgs())); - - // Look at the unroll factors remaining on this op and act on the first one. - auto unrollFactorsStorage = extractUnrollFactors(op); - ArrayRef unrollFactors{unrollFactorsStorage}; - if (unrollFactors.empty()) { - // No more unrollFactors, just sanity check + forward the unique operand. - assert(op->getNumResults() == 1); - assert(arg->getType() == op->getResult(0)->getType()); - rewriter.replaceOp(op, arg); - return matchSuccess(); - } - - // Strides are always 1 for now. - // TODO(b/144845578) support non-1 strides. - auto forkedVectorType = arg->getType().cast(); - SmallVector strides(unrollFactors.size(), 1); - auto nUnrolled = computeMaxLinearIndex(unrollFactors); - SmallVector extractedVectors; - extractedVectors.reserve(op->getNumResults()); - auto linearizationBasis = computeStrides(unrollFactors); - for (unsigned idx = 0; idx < nUnrolled; ++idx) { - auto offsets = delinearize(idx, linearizationBasis); - offsets = zipMap([](int64_t v1, int64_t v2) { return v1 * v2; }, offsets, - unrollFactors); - auto leadingSize = - forkedVectorType.getShape().take_front(unrollFactors.size()); - auto sizes = zipMap([](int64_t v1, int64_t v2) { return v1 / v2; }, - leadingSize, unrollFactors); - extractedVectors.push_back( - rewriter - .create(op->getLoc(), arg, offsets, sizes, - strides) - .getResult()); - } - rewriter.replaceOp(op, extractedVectors); - return matchSuccess(); - } -}; - -// Rewrites a fakeJoin, whose (unique) operand is a blockArgument, into multiple -// vector.strided_slice ops. -struct ConvertFakeJoinOp : public RewritePattern { - ConvertFakeJoinOp(MLIRContext *context) - // low-benefit to kick-in late - : RewritePattern(kFakeJoinOp, 0, context) {} - - PatternMatchResult matchAndRewrite(Operation *op, - PatternRewriter &rewriter) const override { - if (op->getNumResults() != 1) - return matchFailure(); - - if (op->use_empty()) { - rewriter.eraseOp(op); - return matchSuccess(); - } - - auto resultVectorType = op->getResult(0)->getType().cast(); - auto loc = op->getLoc(); - auto *res = makeSplatZero(loc, rewriter, resultVectorType); - - auto unrollFactorsStorage = extractUnrollFactors(op); - ArrayRef unrollFactors{unrollFactorsStorage}; - auto linearizationBasis = computeStrides(unrollFactors); - auto nUnrolled = computeMaxLinearIndex(unrollFactors); - SmallVector strides(unrollFactors.size(), 1); - for (unsigned idx = 0; idx < nUnrolled; ++idx) { - auto offsets = delinearize(idx, linearizationBasis); - offsets = zipMap([](int64_t v1, int64_t v2) { return v1 * v2; }, offsets, - unrollFactors); - res = rewriter.create( - loc, op->getOperand(idx), res, offsets, strides); - } - - rewriter.replaceOp(op, res); - return matchSuccess(); - } -}; - -// Simple DCE for fakeForkOps/fakeJoinOps, we do not want them to escape a -// transformation (otherwise the transformation is considered incorrect). -struct FakeForkTrait { - static constexpr char const *name = kFakeForkOp; -}; -struct FakeJoinTrait { - static constexpr char const *name = kFakeJoinOp; -}; - -template struct DCEPattern : public RewritePattern { - DCEPattern(MLIRContext *context) - // low-benefit to kick-in late - : RewritePattern(OpNameTrait::name, 0, context) {} - - PatternMatchResult matchAndRewrite(Operation *op, - PatternRewriter &rewriter) const override { - assert(op->getName().getStringRef() == kFakeForkOp || - op->getName().getStringRef() == kFakeJoinOp); - if (!op->use_empty()) - return matchFailure(); - rewriter.eraseOp(op); - return matchSuccess(); - } -}; - -void mlir::populateVectorToVectorConversionPatterns( - MLIRContext *context, OwningRewritePatternList &patterns, - ArrayRef coarseVectorShape, ArrayRef fineVectorShape) { - vector::populateWithGenerated(context, &patterns); - vector::populateVectorToVectorCanonicalizationPatterns(patterns, context); - patterns - .insert, DCEPattern>(context); -} diff --git a/third_party/mlir/lib/Dialect/VectorOps/VectorTransforms.cpp b/third_party/mlir/lib/Dialect/VectorOps/VectorTransforms.cpp new file mode 100644 index 00000000000..6825709334b --- /dev/null +++ b/third_party/mlir/lib/Dialect/VectorOps/VectorTransforms.cpp @@ -0,0 +1,516 @@ +//===- VectorToLoops.cpp - Conversion within the Vector dialect -----------===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// This file implements target-independent rewrites as 1->N patterns. +// +//===----------------------------------------------------------------------===// + +#include + +#include "mlir/Dialect/VectorOps/Utils.h" +#include "mlir/Dialect/VectorOps/VectorOps.h" +#include "mlir/Dialect/VectorOps/VectorTransforms.h" +#include "mlir/EDSC/Builders.h" +#include "mlir/EDSC/Helpers.h" +#include "mlir/IR/AffineExpr.h" +#include "mlir/IR/AffineMap.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/Function.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/Matchers.h" +#include "mlir/IR/Module.h" +#include "mlir/IR/OperationSupport.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/IR/Types.h" +#include "mlir/Support/Functional.h" +#include "mlir/Support/STLExtras.h" + +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" + +#define DEBUG_TYPE "vector-to-vector" + +using namespace mlir; +using llvm::dbgs; +using mlir::functional::zipMap; + +/// Given a shape with sizes greater than 0 along all dimensions, +/// returns the distance, in number of elements, between a slice in a dimension +/// and the next slice in the same dimension. +/// e.g. shape[3, 4, 5] -> linearization_basis[20, 5, 1] +static SmallVector computeStrides(ArrayRef shape) { + if (shape.empty()) + return {}; + SmallVector tmp; + tmp.reserve(shape.size()); + int64_t running = 1; + for (auto size : llvm::reverse(shape)) { + assert(size > 0 && "size must be nonnegative"); + tmp.push_back(running); + running *= size; + } + return SmallVector(tmp.rbegin(), tmp.rend()); +} + +static int64_t computeMaxLinearIndex(ArrayRef basis) { + if (basis.empty()) + return 0; + int64_t res = 1; + for (auto b : basis) + res *= b; + return res; +} + +/// Computes and returns the linearized index of 'offsets' w.r.t. 'basis'. +static int64_t linearize(ArrayRef offsets, ArrayRef basis) { + assert(offsets.size() == basis.size()); + int64_t linearIndex = 0; + for (unsigned idx = 0, e = basis.size(); idx < e; ++idx) + linearIndex += offsets[idx] * basis[idx]; + return linearIndex; +} + +/// Given a shape with sizes greater than 0 along all dimensions, returns the +/// delinearized components of linearIndex along shape. +static SmallVector delinearize(int64_t linearIndex, + ArrayRef basis) { + SmallVector res; + res.reserve(basis.size()); + for (unsigned idx = 0, e = basis.size(); idx < e; ++idx) { + assert(basis[idx] > 0); + res.push_back(linearIndex / basis[idx]); + linearIndex %= basis[idx]; + } + // Sanity check. + assert(linearIndex == 0 && "linear index remainder must be 0"); + return res; +} + +// Clones `op` into a new operations that takes `operands` and returns +// `resultTypes`. +static Operation *cloneOpWithOperandsAndTypes(PatternRewriter &builder, + Location loc, Operation *op, + ArrayRef operands, + ArrayRef resultTypes) { + OperationState res(loc, op->getName().getStringRef(), operands, resultTypes, + op->getAttrs()); + return builder.createOperation(res); +} + +static Value *makeSplatZero(Location loc, PatternRewriter &rewriter, + VectorType vt) { + auto t = vt.getElementType(); + Value *f = nullptr; + if (t.isBF16() || t.isF16()) + f = rewriter.create(loc, t, rewriter.getF64FloatAttr(0.0f)); + else if (t.isF32()) + f = rewriter.create(loc, t, rewriter.getF32FloatAttr(0.0f)); + else if (t.isF64()) + f = rewriter.create(loc, t, rewriter.getF64FloatAttr(0.0f)); + if (f) + return rewriter.create(loc, vt, f); + llvm_unreachable("Unsupported type in `makeSplatZero`"); +} + +// Populates 'resultElements[indexMap[i]]' with elements from 'inputElements[i]' +// for each index 'i' in inputElements with a valid mapping in 'indexMap'. +static void getMappedElements(const DenseMap &indexMap, + ArrayRef inputElements, + SmallVectorImpl &resultElements) { + assert(indexMap.size() == resultElements.size()); + assert(inputElements.size() >= resultElements.size()); + for (unsigned i = 0, e = inputElements.size(); i < e; ++i) { + auto it = indexMap.find(i); + if (it != indexMap.end()) + resultElements[it->second] = inputElements[i]; + } +} + +// UnrolledVectorState aggregates per-operand/result vector state required for +// unrolling. +struct UnrolledVectorState { + SmallVector unrolledShape; + SmallVector unrollFactors; + SmallVector basis; + int64_t numInstances; +}; + +// Populates 'state' with unrolled shape, unroll factors, basis and +// num unrolled instances for 'vectorType'. +static void initUnrolledVectorState(VectorType vectorType, + const DenseMap &indexMap, + ArrayRef targetShape, + UnrolledVectorState &state) { + // Compute unrolled shape of 'vectorType'. + state.unrolledShape.resize(vectorType.getRank()); + getMappedElements(indexMap, targetShape, state.unrolledShape); + // Compute unroll factors for unrolled shape. + auto maybeUnrollFactors = + shapeRatio(vectorType.getShape(), state.unrolledShape); + assert(maybeUnrollFactors.hasValue()); + state.unrollFactors = *maybeUnrollFactors; + // Compute 'basis' and 'numInstances' based on 'state.unrollFactors'. + state.basis = computeStrides(state.unrollFactors); + state.numInstances = computeMaxLinearIndex(state.unrollFactors); +} + +// Computes and returns the linear index of the unrolled vector at +// 'vectorOffsets' within the vector represented by 'state'. +static int64_t +getUnrolledVectorLinearIndex(UnrolledVectorState &state, + ArrayRef vectorOffsets, + DenseMap &indexMap) { + // Compute vector offsets. + SmallVector sliceOffsets(state.unrolledShape.size()); + getMappedElements(indexMap, vectorOffsets, sliceOffsets); + // Compute and return linear index of 'sliceOffsets' w.r.t 'state.basis'. + return linearize(sliceOffsets, state.basis); +} + +// Returns an unrolled vector at 'vectorOffsets' within the vector +// represented by 'state'. The vector is created from a slice of 'initValue' +// if not present in 'cache'. +static Value *getOrCreateUnrolledVectorSlice( + Location loc, UnrolledVectorState &state, ArrayRef vectorOffsets, + ArrayRef offsets, DenseMap &indexMap, + Value *initValue, SmallVectorImpl &cache, + PatternRewriter &builder) { + // Compute slice offsets. + SmallVector sliceOffsets(state.unrolledShape.size()); + getMappedElements(indexMap, offsets, sliceOffsets); + // TODO(b/144845578) Support non-1 strides. + SmallVector sliceStrides(state.unrolledShape.size(), 1); + // Compute linear index of 'sliceOffsets' w.r.t 'state.basis'. + int64_t sliceLinearIndex = + getUnrolledVectorLinearIndex(state, vectorOffsets, indexMap); + assert(sliceLinearIndex < static_cast(cache.size())); + auto *valueSlice = cache[sliceLinearIndex]; + if (valueSlice == nullptr) { + assert(initValue != nullptr); + // Initialize 'cache' with slice from 'state.value'. + valueSlice = builder.create( + loc, initValue, sliceOffsets, state.unrolledShape, sliceStrides); + // Store value back to 'cache'. + cache[sliceLinearIndex] = valueSlice; + } + return valueSlice; +} + +// VectorState aggregates per-operand/result vector state required for +// creating slices of vector operands, and clones of the operation being +// unrolled. +struct VectorState { + // The type of this vector. + VectorType type; + // Map from iteration space index to vector dimension index. + DenseMap indexMap; + // Index of this value in operation's operand list (-1 if not an operand). + int64_t operandIndex = -1; + // Accumulator iterator flag. + bool isAcc = false; +}; + +// +// unrollSingleResultStructuredOp +// +// Returns a value representing the result of structured operation 'op' +// with iteration bounds 'iterationBounds' unrolled to 'targetShape'. +// A list of VectorState objects must be specified in 'vectors', where +// each VectorState in the list represents a vector operand or vector result +// (if the operation does not have an accumulator operand). +// The VectorState at index 'resultIndex' in the list must be the state +// associated with the operations single result (i.e. either its accumulator +// operand or vector result value). +// +// Example: +// +// // Before unrolling +// +// operand0 operand1 operand2 +// \ | / +// -------------------- opA -------------------- +// +// // After unrolling by 2 +// +// operand0 operand1 operand2 +// / \ / \ / \ +// slice00 slice01 slice10 slice11 slice20 slice21 +// \ | | | / | +// -------------------- opA0 -------------------- | +// | | | | +// \ | | / +// -------------------- opA1 ------------------- +// | | +// \ / +// insertslice +// | + +// TODO(andydavis) Add the following canonicalization/simplifcation patterns: +// *) Add pattern which matches InsertStridedSlice -> StridedSlice and forwards +// InsertStridedSlice operand to StridedSlice. +// *) Add pattern which matches SourceOp -> StridedSlice -> UserOp which checks +// if there are duplicate identical StridedSlice ops from SourceOp, and +// rewrites itself to use the first duplicate. This transformation should +// cause users of identifical StridedSlice ops to reuse the same StridedSlice +// operation, and leave the duplicate StridedSlice ops with no users +// (removable with DCE). + +// TODO(andydavis) Generalize this to support structured ops beyond +// vector ContractionOp, and merge it with 'unrollSingleResultOpMatchingType' +static Value *unrollSingleResultStructuredOp(Operation *op, + ArrayRef iterationBounds, + std::vector &vectors, + unsigned resultIndex, + ArrayRef targetShape, + PatternRewriter &builder) { + auto shapedType = op->getResult(0)->getType().dyn_cast_or_null(); + if (!shapedType || !shapedType.hasStaticShape()) + assert(false && "Expected a statically shaped result type"); + + // Compute unroll factors for 'iterationBounds' based on 'targetShape' + auto maybeUnrollFactors = shapeRatio(iterationBounds, targetShape); + if (!maybeUnrollFactors.hasValue()) + assert(false && "Failed to compute unroll factors for target shape"); + auto unrollFactors = *maybeUnrollFactors; + + // Compute unrolled vector state for each vector in 'vectors'. + unsigned numVectors = vectors.size(); + SmallVector unrolledVectorState(numVectors); + for (unsigned i = 0; i < numVectors; ++i) { + initUnrolledVectorState(vectors[i].type, vectors[i].indexMap, targetShape, + unrolledVectorState[i]); + } + // Compute number of total unrolled instances. + auto numUnrolledInstances = computeMaxLinearIndex(unrollFactors); + auto basis = computeStrides(unrollFactors); + + auto &resultValueState = unrolledVectorState[resultIndex]; + auto unrolledResultType = VectorType::get(resultValueState.unrolledShape, + shapedType.getElementType()); + + // Initialize caches for intermediate vector results. + std::vector> caches(numVectors); + for (unsigned i = 0; i < numVectors; ++i) + caches[i].resize(unrolledVectorState[i].numInstances); + + // Unroll 'numUnrolledInstances' of 'op', storing results in 'caches'. + for (unsigned i = 0; i < numUnrolledInstances; ++i) { + // De-linearize w.r.t. 'basis'. + auto vectorOffsets = delinearize(i, basis); + // Convert from unrolled vector-space offsets to element-space offsets. + auto offsets = zipMap([](int64_t v1, int64_t v2) { return v1 * v2; }, + vectorOffsets, targetShape); + // Get cached slice (or create slice) for each operand at 'offsets'. + SmallVector operands; + operands.resize(op->getNumOperands()); + for (unsigned i = 0; i < numVectors; ++i) { + int64_t operandIndex = vectors[i].operandIndex; + if (operandIndex < 0) + continue; // Output + auto *operand = op->getOperand(operandIndex); + operands[operandIndex] = getOrCreateUnrolledVectorSlice( + op->getLoc(), unrolledVectorState[i], vectorOffsets, offsets, + vectors[i].indexMap, operand, caches[i], builder); + } + // Create op on sliced vector arguments. + auto resultVector = + cloneOpWithOperandsAndTypes(builder, op->getLoc(), op, operands, + unrolledResultType) + ->getResult(0); + + // Compute linear result index. + int64_t linearIndex = getUnrolledVectorLinearIndex( + resultValueState, vectorOffsets, vectors[resultIndex].indexMap); + // Update result cache at 'linearIndex'. + caches[resultIndex][linearIndex] = resultVector; + } + + // Make zero splat into which we will insert results from + // 'cache[resultIndex]' + auto resultVectorType = op->getResult(0)->getType().cast(); + auto *res = makeSplatZero(op->getLoc(), builder, resultVectorType); + SmallVector strides(resultValueState.unrollFactors.size(), 1); + // Insert vector accumulators into output. + for (unsigned i = 0; i < resultValueState.numInstances; ++i) { + auto vectorOffsets = delinearize(i, resultValueState.basis); + // Convert from unrolled vector-space offsets to element-space offsets. + auto offsets = zipMap([](int64_t v1, int64_t v2) { return v1 * v2; }, + vectorOffsets, resultValueState.unrolledShape); + res = builder.create( + op->getLoc(), caches[resultIndex][i], res, offsets, strides); + } + return res; +} + +static void getVectorContractionOpUnrollState( + vector::ContractionOp contractionOp, ArrayRef targetShape, + SmallVectorImpl &iterationBounds, + std::vector &vectors, unsigned &resultIndex) { + // Get contraction op iteration bounds. + contractionOp.getIterationBounds(iterationBounds); + assert(iterationBounds.size() == targetShape.size()); + // Get map from iteration space index to lhs/rhs/result shape index. + std::vector> iterationIndexMapList; + contractionOp.getIterationIndexMap(iterationIndexMapList); + unsigned numIterators = iterationIndexMapList.size(); + vectors.resize(numIterators); + unsigned accOperandIndex = vector::ContractionOp::getAccOperandIndex(); + for (unsigned i = 0; i < numIterators; ++i) { + vectors[i].type = contractionOp.getOperand(i)->getType().cast(); + vectors[i].indexMap = iterationIndexMapList[i]; + vectors[i].operandIndex = i; + vectors[i].isAcc = i == accOperandIndex ? true : false; + } + + if (llvm::size(contractionOp.masks()) == 2) { + // Add vectors for lhs/rhs vector mask arguments. Masks have the + // same vector shape lhs/rhs args, so copy their index maps. + vectors.push_back( + {vectors[0].type, vectors[0].indexMap, accOperandIndex + 1, false}); + vectors.push_back( + {vectors[1].type, vectors[1].indexMap, accOperandIndex + 2, false}); + } + // Unroll 'op' 'iterationBounds' to 'targetShape'. + // TODO(andydavis) Use linalg style 'args_in'/'args_out' to partition + // 'vectors' instead of 'resultIndex'. + resultIndex = accOperandIndex; +} + +static void +getVectorElementwiseOpUnrollState(Operation *op, ArrayRef targetShape, + SmallVectorImpl &iterationBounds, + std::vector &vectors, + unsigned &resultIndex) { + // Verify that operation and operands all have the same vector shape. + auto resultType = op->getResult(0)->getType().dyn_cast_or_null(); + assert(resultType && "Expected op with vector result type"); + auto resultShape = resultType.getShape(); + // Verify that all operands have the same vector type as result. + assert(llvm::all_of(op->getOperandTypes(), + [=](Type type) { return type == resultType; })); + // Populate 'iterationBounds' with 'resultShape' for elementwise operations. + iterationBounds.assign(resultShape.begin(), resultShape.end()); + + // Create trivial elementwise identity index map based on 'resultShape'. + DenseMap indexMap; + indexMap.reserve(resultShape.size()); + for (unsigned i = 0; i < resultShape.size(); ++i) + indexMap[i] = i; + + // Create VectorState each operand and single result. + unsigned numVectors = op->getNumOperands() + op->getNumResults(); + vectors.resize(numVectors); + for (unsigned i = 0; i < op->getNumOperands(); ++i) + vectors[i] = {resultType, indexMap, i, false}; + vectors[numVectors - 1] = {resultType, indexMap, -1, false}; + resultIndex = numVectors - 1; +} + +// Entry point for unrolling declarative pattern rewrites. +Value *mlir::vector::unrollSingleResultOpMatchingType( + PatternRewriter &builder, Operation *op, ArrayRef targetShape) { + assert(op->getNumResults() == 1 && "Expected single result operation"); + + // Populate 'iterationBounds', 'vectors' and 'resultIndex' to unroll 'op'. + SmallVector iterationBounds; + std::vector vectors; + unsigned resultIndex; + + if (auto contractionOp = dyn_cast(op)) { + // Popultate state for vector ContractionOp. + getVectorContractionOpUnrollState(contractionOp, targetShape, + iterationBounds, vectors, resultIndex); + } else { + // Populate state for vector elementwise op. + getVectorElementwiseOpUnrollState(op, targetShape, iterationBounds, vectors, + resultIndex); + } + + // Unroll 'op' with 'iterationBounds' to 'targetShape'. + return unrollSingleResultStructuredOp(op, iterationBounds, vectors, + resultIndex, targetShape, builder); +} + +// Splits vector TransferReadOp into smaller TransferReadOps for each user. +struct SplitTransferReadOp : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + PatternMatchResult matchAndRewrite(vector::TransferReadOp xferReadOp, + PatternRewriter &rewriter) const override { + // TODO(andydavis, ntv) Support spliting TransferReadOp with non-identity + // permutation maps. Repurpose code from MaterializeVectors transformation. + if (!xferReadOp.permutation_map().isIdentity()) + return matchFailure(); + // Gather 'xferReadOp' users. + SmallVector sliceUsers; + sliceUsers.reserve(std::distance(xferReadOp.getResult()->use_begin(), + xferReadOp.getResult()->use_end())); + + for (auto *user : xferReadOp.getResult()->getUsers()) { + auto sliceOp = dyn_cast(user); + // Return if any user is not a vector::StridedSliceOp. + if (!sliceOp) + return matchFailure(); + sliceUsers.push_back(sliceOp); + } + // Make zero splat into which we will insert split xferReadOp results. + Location loc = xferReadOp.getLoc(); + auto *res = makeSplatZero(loc, rewriter, xferReadOp.getVectorType()); + + // Update each user in 'sliceUser' to use 'res'. + unsigned numSliceIndices = llvm::size(xferReadOp.indices()); + for (auto sliceUser : sliceUsers) { + // Gather static offsets from 'sliceUser'. + SmallVector sliceOffsets; + sliceUser.getOffsets(sliceOffsets); + assert(sliceOffsets.size() == numSliceIndices); + auto *ctx = rewriter.getContext(); + // Compute 'sliceIndices' by adding 'sliceOffsets[i]' to 'indices[i]'. + SmallVector sliceIndices(numSliceIndices); + for (auto it : llvm::enumerate(xferReadOp.indices())) { + auto expr = getAffineDimExpr(0, ctx) + + getAffineConstantExpr(sliceOffsets[it.index()], ctx); + auto map = AffineMap::get(/*dimCount=*/1, /*symbolCount=*/0, expr); + SmallVector mapOperands = {it.value()}; + sliceIndices[it.index()] = + rewriter.create(loc, map, mapOperands); + } + // Create split TransferReadOp for 'sliceUser'. + auto sliceVectorType = + sliceUser.getResult()->getType().cast(); + auto splitXferReadOp = rewriter.create( + loc, sliceVectorType, xferReadOp.memref(), sliceIndices, + xferReadOp.permutation_map(), xferReadOp.padding()); + // Create InsertStridedSlice into splat at same offsets as slice. + res = rewriter.create( + loc, xferReadOp.getVectorType(), splitXferReadOp, res, + sliceUser.offsets(), sliceUser.strides()); + } + + // Replace 'xferReadOp' with result 'res'. + rewriter.replaceOp(xferReadOp, res); + return matchSuccess(); + } +}; + +// TODO(andydavis) Add this as DRR pattern. +void mlir::vector::populateVectorToVectorTransformationPatterns( + OwningRewritePatternList &patterns, MLIRContext *context) { + patterns.insert(context); +} diff --git a/third_party/mlir/lib/EDSC/CMakeLists.txt b/third_party/mlir/lib/EDSC/CMakeLists.txt index eff3a42ce04..bb7e864bce9 100644 --- a/third_party/mlir/lib/EDSC/CMakeLists.txt +++ b/third_party/mlir/lib/EDSC/CMakeLists.txt @@ -13,7 +13,6 @@ add_llvm_library(MLIREDSC ADDITIONAL_HEADER_DIRS ${MLIR_MAIN_INCLUDE_DIR}/mlir/EDSC ) -add_dependencies(MLIREDSC MLIRReferenceImplementationTestGen) target_link_libraries(MLIREDSC PUBLIC MLIRAffineOps @@ -30,7 +29,6 @@ add_llvm_library(MLIREDSCInterface ${MLIR_MAIN_INCLUDE_DIR}/mlir/EDSC ) add_dependencies(MLIREDSCInterface MLIRIR) -add_dependencies(MLIREDSC MLIRReferenceImplementationTestGen) target_link_libraries(MLIREDSC PUBLIC MLIRIR diff --git a/third_party/mlir/lib/IR/AffineExpr.cpp b/third_party/mlir/lib/IR/AffineExpr.cpp index 95ebc0a1cbe..19599a8a62e 100644 --- a/third_party/mlir/lib/IR/AffineExpr.cpp +++ b/third_party/mlir/lib/IR/AffineExpr.cpp @@ -160,7 +160,7 @@ bool AffineExpr::isPureAffine() const { } // Returns the greatest known integral divisor of this affine expression. -uint64_t AffineExpr::getLargestKnownDivisor() const { +int64_t AffineExpr::getLargestKnownDivisor() const { AffineBinaryOpExpr binExpr(nullptr); switch (getKind()) { case AffineExprKind::SymbolId: @@ -444,6 +444,7 @@ static AffineExpr simplifyFloorDiv(AffineExpr lhs, AffineExpr rhs) { auto lhsConst = lhs.dyn_cast(); auto rhsConst = rhs.dyn_cast(); + // mlir floordiv by zero or negative numbers is undefined and preserved as is. if (!rhsConst || rhsConst.getValue() < 1) return nullptr; @@ -453,18 +454,32 @@ static AffineExpr simplifyFloorDiv(AffineExpr lhs, AffineExpr rhs) { // Fold floordiv of a multiply with a constant that is a multiple of the // divisor. Eg: (i * 128) floordiv 64 = i * 2. - if (rhsConst.getValue() == 1) + if (rhsConst == 1) return lhs; + // Simplify (expr * const) floordiv divConst when expr is known to be a + // multiple of divConst. auto lBin = lhs.dyn_cast(); if (lBin && lBin.getKind() == AffineExprKind::Mul) { if (auto lrhs = lBin.getRHS().dyn_cast()) { - // rhsConst is known to be positive if a constant. + // rhsConst is known to be a positive constant. if (lrhs.getValue() % rhsConst.getValue() == 0) return lBin.getLHS() * (lrhs.getValue() / rhsConst.getValue()); } } + // Simplify (expr1 + expr2) floordiv divConst when either expr1 or expr2 is + // known to be a multiple of divConst. + if (lBin && lBin.getKind() == AffineExprKind::Add) { + int64_t llhsDiv = lBin.getLHS().getLargestKnownDivisor(); + int64_t lrhsDiv = lBin.getRHS().getLargestKnownDivisor(); + // rhsConst is known to be a positive constant. + if (llhsDiv % rhsConst.getValue() == 0 || + lrhsDiv % rhsConst.getValue() == 0) + return lBin.getLHS().floorDiv(rhsConst.getValue()) + + lBin.getRHS().floorDiv(rhsConst.getValue()); + } + return nullptr; } @@ -497,10 +512,12 @@ static AffineExpr simplifyCeilDiv(AffineExpr lhs, AffineExpr rhs) { if (rhsConst.getValue() == 1) return lhs; + // Simplify (expr * const) ceildiv divConst when const is known to be a + // multiple of divConst. auto lBin = lhs.dyn_cast(); if (lBin && lBin.getKind() == AffineExprKind::Mul) { if (auto lrhs = lBin.getRHS().dyn_cast()) { - // rhsConst is known to be positive if a constant. + // rhsConst is known to be a positive constant. if (lrhs.getValue() % rhsConst.getValue() == 0) return lBin.getLHS() * (lrhs.getValue() / rhsConst.getValue()); } @@ -526,6 +543,7 @@ static AffineExpr simplifyMod(AffineExpr lhs, AffineExpr rhs) { auto lhsConst = lhs.dyn_cast(); auto rhsConst = rhs.dyn_cast(); + // mod w.r.t zero or negative numbers is undefined and preserved as is. if (!rhsConst || rhsConst.getValue() < 1) return nullptr; @@ -539,11 +557,20 @@ static AffineExpr simplifyMod(AffineExpr lhs, AffineExpr rhs) { if (lhs.getLargestKnownDivisor() % rhsConst.getValue() == 0) return getAffineConstantExpr(0, lhs.getContext()); + // Simplify (expr1 + expr2) mod divConst when either expr1 or expr2 is + // known to be a multiple of divConst. + auto lBin = lhs.dyn_cast(); + if (lBin && lBin.getKind() == AffineExprKind::Add) { + int64_t llhsDiv = lBin.getLHS().getLargestKnownDivisor(); + int64_t lrhsDiv = lBin.getRHS().getLargestKnownDivisor(); + // rhsConst is known to be a positive constant. + if (llhsDiv % rhsConst.getValue() == 0) + return lBin.getRHS() % rhsConst.getValue(); + if (lrhsDiv % rhsConst.getValue() == 0) + return lBin.getLHS() % rhsConst.getValue(); + } + return nullptr; - // TODO(bondhugula): In general, this can be simplified more by using the GCD - // test, or in general using quantifier elimination (add two new variables q - // and r, and eliminate all variables from the linear system other than r. All - // of this can be done through mlir/Analysis/'s FlatAffineConstraints. } AffineExpr AffineExpr::operator%(uint64_t v) const { diff --git a/third_party/mlir/lib/IR/Attributes.cpp b/third_party/mlir/lib/IR/Attributes.cpp index f2f3d41f980..b546643837b 100644 --- a/third_party/mlir/lib/IR/Attributes.cpp +++ b/third_party/mlir/lib/IR/Attributes.cpp @@ -527,7 +527,7 @@ DenseElementsAttr::AttributeElementIterator::AttributeElementIterator( /// Accesses the Attribute value at this iterator position. Attribute DenseElementsAttr::AttributeElementIterator::operator*() const { - auto owner = getFromOpaquePointer(object).cast(); + auto owner = getFromOpaquePointer(base).cast(); Type eltTy = owner.getType().getElementType(); if (auto intEltTy = eltTy.dyn_cast()) { if (intEltTy.getWidth() == 1) diff --git a/third_party/mlir/lib/IR/Block.cpp b/third_party/mlir/lib/IR/Block.cpp index ad68a36f1ee..63e85802b73 100644 --- a/third_party/mlir/lib/IR/Block.cpp +++ b/third_party/mlir/lib/IR/Block.cpp @@ -57,7 +57,15 @@ bool Block::isEntryBlock() { return this == &getParent()->front(); } void Block::insertBefore(Block *block) { assert(!getParent() && "already inserted into a block!"); assert(block->getParent() && "cannot insert before a block without a parent"); - block->getParent()->getBlocks().insert(Region::iterator(block), this); + block->getParent()->getBlocks().insert(block->getIterator(), this); +} + +/// Unlink this block from its current region and insert it right before the +/// specific block. +void Block::moveBefore(Block *block) { + assert(block->getParent() && "cannot insert before a block without a parent"); + block->getParent()->getBlocks().splice( + block->getIterator(), getParent()->getBlocks(), getIterator()); } /// Unlink this Block from its parent Region and delete it. @@ -257,3 +265,13 @@ Block *PredecessorIterator::unwrap(BlockOperand &value) { unsigned PredecessorIterator::getSuccessorIndex() const { return I->getOperandNumber(); } + +//===----------------------------------------------------------------------===// +// Successors +//===----------------------------------------------------------------------===// + +SuccessorRange::SuccessorRange(Block *block) : SuccessorRange(nullptr, 0) { + if (Operation *term = block->getTerminator()) + if ((count = term->getNumSuccessors())) + base = term->getBlockOperands().data(); +} diff --git a/third_party/mlir/lib/IR/Builders.cpp b/third_party/mlir/lib/IR/Builders.cpp index 4d6cd3550ca..691b2ad99c4 100644 --- a/third_party/mlir/lib/IR/Builders.cpp +++ b/third_party/mlir/lib/IR/Builders.cpp @@ -18,12 +18,13 @@ #include "mlir/IR/Builders.h" #include "mlir/IR/AffineExpr.h" #include "mlir/IR/AffineMap.h" -#include "mlir/IR/Attributes.h" +#include "mlir/IR/Dialect.h" #include "mlir/IR/IntegerSet.h" -#include "mlir/IR/Location.h" +#include "mlir/IR/Matchers.h" #include "mlir/IR/Module.h" #include "mlir/IR/StandardTypes.h" #include "mlir/Support/Functional.h" +#include "llvm/Support/raw_ostream.h" using namespace mlir; Builder::Builder(ModuleOp module) : context(module.getContext()) {} @@ -306,6 +307,13 @@ AffineMap Builder::getShiftedAffineMap(AffineMap map, int64_t shift) { OpBuilder::~OpBuilder() {} +/// Insert the given operation at the current insertion point and return it. +Operation *OpBuilder::insert(Operation *op) { + if (block) + block->getOperations().insert(insertPoint, op); + return op; +} + /// Add new block and set the insertion point to the end of it. The block is /// inserted at the provided insertion point of 'parent'. Block *OpBuilder::createBlock(Region *parent, Region::iterator insertPt) { @@ -328,40 +336,72 @@ Block *OpBuilder::createBlock(Block *insertBefore) { /// Create an operation given the fields represented as an OperationState. Operation *OpBuilder::createOperation(const OperationState &state) { - assert(block && "createOperation() called without setting builder's block"); - auto *op = Operation::create(state); - insert(op); - return op; + return insert(Operation::create(state)); } /// Attempts to fold the given operation and places new results within -/// 'results'. -void OpBuilder::tryFold(Operation *op, SmallVectorImpl &results) { +/// 'results'. Returns success if the operation was folded, failure otherwise. +/// Note: This function does not erase the operation on a successful fold. +LogicalResult OpBuilder::tryFold(Operation *op, + SmallVectorImpl &results) { results.reserve(op->getNumResults()); - SmallVector foldResults; - - // Returns if the given fold result corresponds to a valid existing value. - auto isValidValue = [](OpFoldResult result) { - return result.dyn_cast(); + auto cleanupFailure = [&] { + results.assign(op->result_begin(), op->result_end()); + return failure(); }; - // Check if the fold failed, or did not result in only existing values. + // If this operation is already a constant, there is nothing to do. + Attribute unused; + if (matchPattern(op, m_Constant(&unused))) + return cleanupFailure(); + + // Check to see if any operands to the operation is constant and whether + // the operation knows how to constant fold itself. SmallVector constOperands(op->getNumOperands()); - if (failed(op->fold(constOperands, foldResults)) || foldResults.empty() || - !llvm::all_of(foldResults, isValidValue)) { - // Simply return the existing operation results. - results.assign(op->result_begin(), op->result_end()); - return; + for (unsigned i = 0, e = op->getNumOperands(); i != e; ++i) + matchPattern(op->getOperand(i), m_Constant(&constOperands[i])); + + // Try to fold the operation. + SmallVector foldResults; + if (failed(op->fold(constOperands, foldResults)) || foldResults.empty()) + return cleanupFailure(); + + // A temporary builder used for creating constants during folding. + OpBuilder cstBuilder(context); + SmallVector generatedConstants; + + // Populate the results with the folded results. + Dialect *dialect = op->getDialect(); + for (auto &it : llvm::enumerate(foldResults)) { + // Normal values get pushed back directly. + if (auto *value = it.value().dyn_cast()) { + results.push_back(value); + continue; + } + + // Otherwise, try to materialize a constant operation. + if (!dialect) + return cleanupFailure(); + + // Ask the dialect to materialize a constant operation for this value. + Attribute attr = it.value().get(); + auto *constOp = dialect->materializeConstant( + cstBuilder, attr, op->getResult(it.index())->getType(), op->getLoc()); + if (!constOp) { + // Erase any generated constants. + for (Operation *cst : generatedConstants) + cst->erase(); + return cleanupFailure(); + } + assert(matchPattern(constOp, m_Constant(&attr))); + + generatedConstants.push_back(constOp); + results.push_back(constOp->getResult(0)); } - // Populate the results with the folded results and remove the original op. - llvm::transform(foldResults, std::back_inserter(results), - [](OpFoldResult result) { return result.get(); }); - op->erase(); -} + // If we were successful, insert any generated constants. + for (Operation *cst : generatedConstants) + insert(cst); -/// Insert the given operation at the current insertion point. -void OpBuilder::insert(Operation *op) { - if (block) - block->getOperations().insert(insertPoint, op); + return success(); } diff --git a/third_party/mlir/lib/IR/Operation.cpp b/third_party/mlir/lib/IR/Operation.cpp index 26f20d324f0..fd747a98a40 100644 --- a/third_party/mlir/lib/IR/Operation.cpp +++ b/third_party/mlir/lib/IR/Operation.cpp @@ -136,8 +136,7 @@ Operation *Operation::create(Location location, OperationName name, ArrayRef resultTypes, ArrayRef operands, NamedAttributeList attributes, - ArrayRef successors, - ArrayRef> regions, + ArrayRef successors, RegionRange regions, bool resizableOperandList) { unsigned numRegions = regions.size(); Operation *op = create(location, name, resultTypes, operands, attributes, @@ -598,9 +597,8 @@ void Operation::setSuccessor(Block *block, unsigned index) { } auto Operation::getNonSuccessorOperands() -> operand_range { - return {operand_iterator(this, 0), - operand_iterator(this, hasSuccessors() ? getSuccessorOperandIndex(0) - : getNumOperands())}; + return getOperands().take_front(hasSuccessors() ? getSuccessorOperandIndex(0) + : getNumOperands()); } /// Get the index of the first operand of the successor at the provided @@ -636,9 +634,7 @@ Operation::decomposeSuccessorOperandIndex(unsigned operandIndex) { auto Operation::getSuccessorOperands(unsigned index) -> operand_range { unsigned succOperandIndex = getSuccessorOperandIndex(index); - return {operand_iterator(this, succOperandIndex), - operand_iterator(this, - succOperandIndex + getNumSuccessorOperands(index))}; + return getOperands().slice(succOperandIndex, getNumSuccessorOperands(index)); } /// Attempt to fold this operation using the Op's registered foldHook. @@ -746,67 +742,6 @@ Operation *Operation::clone() { return clone(mapper); } -//===----------------------------------------------------------------------===// -// ValueRange -//===----------------------------------------------------------------------===// - -ValueRange::ValueRange(ArrayRef values) - : owner(values.data()), count(values.size()) {} -ValueRange::ValueRange(llvm::iterator_range values) - : count(llvm::size(values)) { - if (count != 0) { - auto begin = values.begin(); - owner = &begin.getObject()->getOpOperand(begin.getIndex()); - } -} -ValueRange::ValueRange(llvm::iterator_range values) - : count(llvm::size(values)) { - if (count != 0) { - auto begin = values.begin(); - owner = &begin.getObject()->getOpResult(begin.getIndex()); - } -} - -/// Drop the first N elements, and keep M elements. -ValueRange ValueRange::slice(unsigned n, unsigned m) const { - assert(n + m <= size() && "Invalid specifier"); - OwnerT newOwner; - if (OpOperand *operand = owner.dyn_cast()) - newOwner = operand + n; - else if (OpResult *result = owner.dyn_cast()) - newOwner = result + n; - else - newOwner = owner.get() + n; - return ValueRange(newOwner, m); -} - -/// Drop the first n elements. -ValueRange ValueRange::drop_front(unsigned n) const { - assert(size() >= n && "Dropping more elements than exist"); - return slice(n, size() - n); -} - -/// Drop the last n elements. -ValueRange ValueRange::drop_back(unsigned n) const { - assert(size() >= n && "Dropping more elements than exist"); - return ValueRange(owner, size() - n); -} - -ValueRange::Iterator::Iterator(OwnerT owner, unsigned curIndex) - : indexed_accessor_iterator( - owner, curIndex) {} - -Value *ValueRange::Iterator::operator*() const { - // Operands access the held value via 'get'. - if (OpOperand *operand = object.dyn_cast()) - return operand[index].get(); - // An OpResult is a value, so we can return it directly. - if (OpResult *result = object.dyn_cast()) - return &result[index]; - // Otherwise, this is a raw value array so just index directly. - return object.get()[index]; -} - //===----------------------------------------------------------------------===// // OpState trait class. //===----------------------------------------------------------------------===// @@ -999,7 +934,7 @@ OpTrait::impl::verifySameOperandsAndResultElementType(Operation *op) { auto elementType = getElementTypeOrSelf(op->getResult(0)); // Verify result element type matches first result's element type. - for (auto result : drop_begin(op->getResults(), 1)) { + for (auto result : llvm::drop_begin(op->getResults(), 1)) { if (getElementTypeOrSelf(result) != elementType) return op->emitOpError( "requires the same element type for all operands and results"); @@ -1230,7 +1165,7 @@ Value *impl::foldCastOp(Operation *op) { } //===----------------------------------------------------------------------===// -// CastOp implementation +// Misc. utils //===----------------------------------------------------------------------===// /// Insert an operation, generated by `buildTerminatorOp`, at the end of the @@ -1250,6 +1185,10 @@ void impl::ensureRegionTerminator( block.push_back(buildTerminatorOp()); } +//===----------------------------------------------------------------------===// +// UseIterator +//===----------------------------------------------------------------------===// + UseIterator::UseIterator(Operation *op, bool end) : op(op), res(end ? op->result_end() : op->result_begin()) { // Only initialize current use if there are results/can be uses. diff --git a/third_party/mlir/lib/IR/OperationSupport.cpp b/third_party/mlir/lib/IR/OperationSupport.cpp index 2c9a9cce86b..256a261acd8 100644 --- a/third_party/mlir/lib/IR/OperationSupport.cpp +++ b/third_party/mlir/lib/IR/OperationSupport.cpp @@ -36,7 +36,7 @@ OperationState::OperationState(Location location, OperationName name) : location(location), name(name) {} OperationState::OperationState(Location location, StringRef name, - ArrayRef operands, ArrayRef types, + ValueRange operands, ArrayRef types, ArrayRef attributes, ArrayRef successors, MutableArrayRef> regions, @@ -144,3 +144,50 @@ void detail::OperandStorage::grow(ResizableStorage &resizeUtil, operand.~OpOperand(); resizeUtil.setDynamicStorage(newStorage); } + +//===----------------------------------------------------------------------===// +// Operation Value-Iterators +//===----------------------------------------------------------------------===// + +//===----------------------------------------------------------------------===// +// OperandRange + +OperandRange::OperandRange(Operation *op) + : OperandRange(op->getOpOperands().data(), op->getNumOperands()) {} + +//===----------------------------------------------------------------------===// +// ResultRange + +ResultRange::ResultRange(Operation *op) + : ResultRange(op->getOpResults().data(), op->getNumResults()) {} + +//===----------------------------------------------------------------------===// +// ValueRange + +ValueRange::ValueRange(ArrayRef values) + : ValueRange(values.data(), values.size()) {} +ValueRange::ValueRange(OperandRange values) + : ValueRange(values.begin().getBase(), values.size()) {} +ValueRange::ValueRange(ResultRange values) + : ValueRange(values.begin().getBase(), values.size()) {} + +/// See `detail::indexed_accessor_range_base` for details. +ValueRange::OwnerT ValueRange::offset_base(const OwnerT &owner, + ptrdiff_t index) { + if (OpOperand *operand = owner.dyn_cast()) + return operand + index; + if (OpResult *result = owner.dyn_cast()) + return result + index; + return owner.get() + index; +} +/// See `detail::indexed_accessor_range_base` for details. +Value *ValueRange::dereference_iterator(const OwnerT &owner, ptrdiff_t index) { + // Operands access the held value via 'get'. + if (OpOperand *operand = owner.dyn_cast()) + return operand[index].get(); + // An OpResult is a value, so we can return it directly. + if (OpResult *result = owner.dyn_cast()) + return &result[index]; + // Otherwise, this is a raw value array so just index directly. + return owner.get()[index]; +} diff --git a/third_party/mlir/lib/IR/Region.cpp b/third_party/mlir/lib/IR/Region.cpp index a91d36b1e48..c588e567bc3 100644 --- a/third_party/mlir/lib/IR/Region.cpp +++ b/third_party/mlir/lib/IR/Region.cpp @@ -213,3 +213,27 @@ void llvm::ilist_traits<::mlir::Block>::transferNodesFromList( for (; first != last; ++first) first->parentValidOpOrderPair.setPointer(curParent); } + +//===----------------------------------------------------------------------===// +// RegionRange +//===----------------------------------------------------------------------===// + +RegionRange::RegionRange(MutableArrayRef regions) + : RegionRange(regions.data(), regions.size()) {} +RegionRange::RegionRange(ArrayRef> regions) + : RegionRange(regions.data(), regions.size()) {} + +/// See `detail::indexed_accessor_range_base` for details. +RegionRange::OwnerT RegionRange::offset_base(const OwnerT &owner, + ptrdiff_t index) { + if (auto *operand = owner.dyn_cast *>()) + return operand + index; + return &owner.get()[index]; +} +/// See `detail::indexed_accessor_range_base` for details. +Region *RegionRange::dereference_iterator(const OwnerT &owner, + ptrdiff_t index) { + if (auto *operand = owner.dyn_cast *>()) + return operand[index].get(); + return &owner.get()[index]; +} diff --git a/third_party/mlir/lib/IR/StandardTypes.cpp b/third_party/mlir/lib/IR/StandardTypes.cpp index 7054f6d5ca8..8a47c5b0b41 100644 --- a/third_party/mlir/lib/IR/StandardTypes.cpp +++ b/third_party/mlir/lib/IR/StandardTypes.cpp @@ -197,6 +197,10 @@ bool ShapedType::hasStaticShape() const { return hasRank() && llvm::none_of(getShape(), isDynamic); } +bool ShapedType::hasStaticShape(ArrayRef shape) const { + return hasStaticShape() && getShape() == shape; +} + //===----------------------------------------------------------------------===// // VectorType //===----------------------------------------------------------------------===// diff --git a/third_party/mlir/lib/IR/TypeUtilities.cpp b/third_party/mlir/lib/IR/TypeUtilities.cpp index a963a8dd459..54b1bf6329b 100644 --- a/third_party/mlir/lib/IR/TypeUtilities.cpp +++ b/third_party/mlir/lib/IR/TypeUtilities.cpp @@ -61,6 +61,23 @@ bool mlir::isOpaqueTypeWithName(Type type, StringRef dialect, return false; } +/// Returns success if the given two shapes are compatible. That is, they have +/// the same size and each pair of the elements are equal or one of them is +/// dynamic. +LogicalResult mlir::verifyCompatibleShape(ArrayRef shape1, + ArrayRef shape2) { + if (shape1.size() != shape2.size()) + return failure(); + for (const auto &dims : llvm::zip(shape1, shape2)) { + int64_t dim1 = std::get<0>(dims); + int64_t dim2 = std::get<1>(dims); + if (!ShapedType::isDynamic(dim1) && !ShapedType::isDynamic(dim2) && + dim1 != dim2) + return failure(); + } + return success(); +} + /// Returns success if the given two types have compatible shape. That is, /// they are both scalars (not shaped), or they are both shaped types and at /// least one is unranked or they have compatible dimensions. Dimensions are @@ -79,28 +96,22 @@ LogicalResult mlir::verifyCompatibleShape(Type type1, Type type2) { if (!sType1.hasRank() || !sType2.hasRank()) return success(); - if (sType1.getRank() != sType2.getRank()) - return failure(); - - for (const auto &dims : llvm::zip(sType1.getShape(), sType2.getShape())) { - int64_t dim1 = std::get<0>(dims); - int64_t dim2 = std::get<1>(dims); - if (!ShapedType::isDynamic(dim1) && !ShapedType::isDynamic(dim2) && - dim1 != dim2) - return failure(); - } - return success(); + return verifyCompatibleShape(sType1.getShape(), sType2.getShape()); } -OperandElementTypeIterator::OperandElementTypeIterator(OperandIterator it) - : llvm::mapped_iterator(it, &unwrap) {} +OperandElementTypeIterator::OperandElementTypeIterator( + Operation::operand_iterator it) + : llvm::mapped_iterator( + it, &unwrap) {} Type OperandElementTypeIterator::unwrap(Value *value) { return value->getType().cast().getElementType(); } -ResultElementTypeIterator::ResultElementTypeIterator(ResultIterator it) - : llvm::mapped_iterator(it, &unwrap) {} +ResultElementTypeIterator::ResultElementTypeIterator( + Operation::result_iterator it) + : llvm::mapped_iterator( + it, &unwrap) {} Type ResultElementTypeIterator::unwrap(Value *value) { return value->getType().cast().getElementType(); diff --git a/third_party/mlir/lib/Pass/Pass.cpp b/third_party/mlir/lib/Pass/Pass.cpp index fc1ad5bb939..cb5194acf21 100644 --- a/third_party/mlir/lib/Pass/Pass.cpp +++ b/third_party/mlir/lib/Pass/Pass.cpp @@ -279,7 +279,7 @@ MLIRContext *OpPassManager::getContext() const { /// Return the operation name that this pass manager operates on. const OperationName &OpPassManager::getOpName() const { return impl->name; } -/// Prints out the passes of the pass mangager as the textual representation +/// Prints out the passes of the pass manager as the textual representation /// of pipelines. void OpPassManager::printAsTextualPipeline(raw_ostream &os) { // Filter out passes that are not part of the public pipeline. diff --git a/third_party/mlir/lib/Pass/PassStatistics.cpp b/third_party/mlir/lib/Pass/PassStatistics.cpp index 461cf882bd2..3c46b0bf255 100644 --- a/third_party/mlir/lib/Pass/PassStatistics.cpp +++ b/third_party/mlir/lib/Pass/PassStatistics.cpp @@ -178,7 +178,7 @@ Pass::Statistic::Statistic(Pass *owner, const char *name, // Always set the 'initialized' bit to true so that this statistic isn't // placed in the static registry. // TODO: This is sort of hack as `llvm::Statistic`s can't be setup to avoid - // automatic registartion with the global registry. We should either add + // automatic registration with the global registry. We should either add // support for this in LLVM, or just write our own statistics classes. Initialized = true; #endif diff --git a/third_party/mlir/lib/Target/LLVMIR/ConvertToNVVMIR.cpp b/third_party/mlir/lib/Target/LLVMIR/ConvertToNVVMIR.cpp index 606e91b955f..728dc864ae5 100644 --- a/third_party/mlir/lib/Target/LLVMIR/ConvertToNVVMIR.cpp +++ b/third_party/mlir/lib/Target/LLVMIR/ConvertToNVVMIR.cpp @@ -58,7 +58,7 @@ static llvm::Intrinsic::ID getShflBflyIntrinsicId(llvm::Type *resultType, class ModuleTranslation : public LLVM::ModuleTranslation { public: - explicit ModuleTranslation(ModuleOp module) + explicit ModuleTranslation(Operation *module) : LLVM::ModuleTranslation(module) {} ~ModuleTranslation() override {} @@ -73,7 +73,7 @@ protected: }; } // namespace -std::unique_ptr mlir::translateModuleToNVVMIR(ModuleOp m) { +std::unique_ptr mlir::translateModuleToNVVMIR(Operation *m) { ModuleTranslation translation(m); auto llvmModule = LLVM::ModuleTranslation::translateModule(m); @@ -82,7 +82,8 @@ std::unique_ptr mlir::translateModuleToNVVMIR(ModuleOp m) { // Insert the nvvm.annotations kernel so that the NVVM backend recognizes the // function as a kernel. - for (auto func : m.getOps()) { + for (auto func : + ModuleTranslation::getModuleBody(m).getOps()) { if (!gpu::GPUDialect::isKernel(func)) continue; diff --git a/third_party/mlir/lib/Target/LLVMIR/ConvertToROCDLIR.cpp b/third_party/mlir/lib/Target/LLVMIR/ConvertToROCDLIR.cpp index dcd4d6c221f..7b7c3681371 100644 --- a/third_party/mlir/lib/Target/LLVMIR/ConvertToROCDLIR.cpp +++ b/third_party/mlir/lib/Target/LLVMIR/ConvertToROCDLIR.cpp @@ -69,7 +69,7 @@ static llvm::Value *createDeviceFunctionCall(llvm::IRBuilder<> &builder, class ModuleTranslation : public LLVM::ModuleTranslation { public: - explicit ModuleTranslation(ModuleOp module) + explicit ModuleTranslation(Operation *module) : LLVM::ModuleTranslation(module) {} ~ModuleTranslation() override {} @@ -84,7 +84,7 @@ protected: }; } // namespace -std::unique_ptr mlir::translateModuleToROCDLIR(ModuleOp m) { +std::unique_ptr mlir::translateModuleToROCDLIR(Operation *m) { ModuleTranslation translation(m); // lower MLIR (with RODL Dialect) to LLVM IR (with ROCDL intrinsics) @@ -94,7 +94,8 @@ std::unique_ptr mlir::translateModuleToROCDLIR(ModuleOp m) { // foreach GPU kernel // 1. Insert AMDGPU_KERNEL calling convention. // 2. Insert amdgpu-flat-workgroup-size(1, 1024) attribute. - for (auto func : m.getOps()) { + for (auto func : + ModuleTranslation::getModuleBody(m).getOps()) { if (!func.getAttrOfType(gpu::GPUDialect::getKernelFuncAttrName())) continue; diff --git a/third_party/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp b/third_party/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp index f985fed3991..f5f9ccabd76 100644 --- a/third_party/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp +++ b/third_party/mlir/lib/Target/LLVMIR/ModuleTranslation.cpp @@ -311,7 +311,7 @@ llvm::GlobalVariable::LinkageTypes convertLinkageType(LLVM::Linkage linkage) { // Create named global variables that correspond to llvm.mlir.global // definitions. void ModuleTranslation::convertGlobals() { - for (auto op : mlirModule.getOps()) { + for (auto op : getModuleBody(mlirModule).getOps()) { llvm::Type *type = op.getType().getUnderlyingType(); llvm::Constant *cst = llvm::UndefValue::get(type); if (op.getValueOrNull()) { @@ -470,10 +470,10 @@ LogicalResult ModuleTranslation::convertOneFunction(LLVMFuncOp func) { return success(); } -LogicalResult ModuleTranslation::checkSupportedModuleOps(ModuleOp m) { - for (Operation &o : m.getBody()->getOperations()) +LogicalResult ModuleTranslation::checkSupportedModuleOps(Operation *m) { + for (Operation &o : getModuleBody(m).getOperations()) if (!isa(&o) && !isa(&o) && - !isa(&o)) + !o.isKnownTerminator()) return o.emitOpError("unsupported module-level operation"); return success(); } @@ -481,7 +481,7 @@ LogicalResult ModuleTranslation::checkSupportedModuleOps(ModuleOp m) { LogicalResult ModuleTranslation::convertFunctions() { // Declare all functions first because there may be function calls that form a // call graph with cycles. - for (auto function : mlirModule.getOps()) { + for (auto function : getModuleBody(mlirModule).getOps()) { llvm::FunctionCallee llvmFuncCst = llvmModule->getOrInsertFunction( function.getName(), llvm::cast(function.getType().getUnderlyingType())); @@ -491,7 +491,7 @@ LogicalResult ModuleTranslation::convertFunctions() { } // Convert functions. - for (auto function : mlirModule.getOps()) { + for (auto function : getModuleBody(mlirModule).getOps()) { // Ignore external functions. if (function.isExternal()) continue; @@ -503,8 +503,9 @@ LogicalResult ModuleTranslation::convertFunctions() { return success(); } -std::unique_ptr ModuleTranslation::prepareLLVMModule(ModuleOp m) { - auto *dialect = m.getContext()->getRegisteredDialect(); +std::unique_ptr +ModuleTranslation::prepareLLVMModule(Operation *m) { + auto *dialect = m->getContext()->getRegisteredDialect(); assert(dialect && "LLVM dialect must be registered"); auto llvmModule = llvm::CloneModule(dialect->getLLVMModule()); diff --git a/third_party/mlir/lib/Transforms/DialectConversion.cpp b/third_party/mlir/lib/Transforms/DialectConversion.cpp index 6d34db90912..4b4575a5e50 100644 --- a/third_party/mlir/lib/Transforms/DialectConversion.cpp +++ b/third_party/mlir/lib/Transforms/DialectConversion.cpp @@ -25,7 +25,6 @@ #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/Support/Debug.h" -#include "llvm/Support/raw_ostream.h" using namespace mlir; using namespace mlir::detail; @@ -165,9 +164,10 @@ struct ArgConverter { // Rewrite Application //===--------------------------------------------------------------------===// - /// Erase any rewrites registered for the current block that is about to be - /// removed. This merely drops the rewrites without undoing them. - void notifyBlockRemoved(Block *block); + /// Erase any rewrites registered for the blocks within the given operation + /// which is about to be removed. This merely drops the rewrites without + /// undoing them. + void notifyOpRemoved(Operation *op); /// Cleanup and undo any generated conversions for the arguments of block. /// This method replaces the new block with the original, reverting the IR to @@ -195,9 +195,16 @@ struct ArgConverter { Block *block, TypeConverter::SignatureConversion &signatureConversion, ConversionValueMapping &mapping); + /// Insert a new conversion into the cache. + void insertConversion(Block *newBlock, ConvertedBlockInfo &&info); + /// A collection of blocks that have had their arguments converted. llvm::MapVector conversionInfo; + /// A mapping from valid regions, to those containing the original blocks of a + /// conversion. + DenseMap> regionMapping; + /// An instance of the unknown location that is used when materializing /// conversions. Location loc; @@ -213,18 +220,26 @@ struct ArgConverter { //===----------------------------------------------------------------------===// // Rewrite Application -void ArgConverter::notifyBlockRemoved(Block *block) { - auto it = conversionInfo.find(block); - if (it == conversionInfo.end()) - return; +void ArgConverter::notifyOpRemoved(Operation *op) { + for (Region ®ion : op->getRegions()) { + for (Block &block : region) { + // Drop any rewrites from within. + for (Operation &nestedOp : block) + if (nestedOp.getNumRegions()) + notifyOpRemoved(&nestedOp); - // Drop all uses of the original arguments and delete the original block. - Block *origBlock = it->second.origBlock; - for (BlockArgument *arg : origBlock->getArguments()) - arg->dropAllUses(); - delete origBlock; + // Check if this block was converted. + auto it = conversionInfo.find(&block); + if (it == conversionInfo.end()) + return; - conversionInfo.erase(it); + // Drop all uses of the original arguments and delete the original block. + Block *origBlock = it->second.origBlock; + for (BlockArgument *arg : origBlock->getArguments()) + arg->dropAllUses(); + conversionInfo.erase(it); + } + } } void ArgConverter::discardRewrites(Block *block) { @@ -240,7 +255,7 @@ void ArgConverter::discardRewrites(Block *block) { // Move the operations back the original block and the delete the new block. origBlock->getOperations().splice(origBlock->end(), block->getOperations()); - origBlock->insertBefore(block); + origBlock->moveBefore(block); block->erase(); conversionInfo.erase(it); @@ -302,9 +317,6 @@ void ArgConverter::applyRewrites(ConversionValueMapping &mapping) { if (castValue->use_empty()) castValue->getDefiningOp()->erase(); } - - // Drop the original block now the rewrites were applied. - delete origBlock; } } @@ -378,11 +390,24 @@ Block *ArgConverter::applySignatureConversion( } // Remove the original block from the region and return the new one. - newBlock->getParent()->getBlocks().remove(block); - conversionInfo.insert({newBlock, std::move(info)}); + insertConversion(newBlock, std::move(info)); return newBlock; } +void ArgConverter::insertConversion(Block *newBlock, + ConvertedBlockInfo &&info) { + // Get a region to insert the old block. + Region *region = newBlock->getParent(); + std::unique_ptr &mappedRegion = regionMapping[region]; + if (!mappedRegion) + mappedRegion = std::make_unique(region->getParentOp()); + + // Move the original block to the mapped region and emplace the conversion. + mappedRegion->getBlocks().splice(mappedRegion->end(), region->getBlocks(), + info.origBlock->getIterator()); + conversionInfo.insert({newBlock, std::move(info)}); +} + //===----------------------------------------------------------------------===// // ConversionPatternRewriterImpl //===----------------------------------------------------------------------===// @@ -643,11 +668,8 @@ void ConversionPatternRewriterImpl::applyRewrites() { // If this operation defines any regions, drop any pending argument // rewrites. - if (argConverter.typeConverter && repl.op->getNumRegions()) { - for (auto ®ion : repl.op->getRegions()) - for (auto &block : region) - argConverter.notifyBlockRemoved(&block); - } + if (argConverter.typeConverter && repl.op->getNumRegions()) + argConverter.notifyOpRemoved(repl.op); } // In a second pass, erase all of the replaced operations in reverse. This @@ -802,13 +824,6 @@ void ConversionPatternRewriter::replaceUsesOfBlockArgument(BlockArgument *from, impl->mapping.map(impl->mapping.lookupOrDefault(from), to); } -/// Clone the given operation without cloning its regions. -Operation *ConversionPatternRewriter::cloneWithoutRegions(Operation *op) { - Operation *newOp = OpBuilder::cloneWithoutRegions(*op); - impl->createdOps.push_back(newOp); - return newOp; -} - /// Return the converted value that replaces 'key'. Return 'key' if there is /// no such a converted value. Value *ConversionPatternRewriter::getRemappedValue(Value *key) { @@ -854,12 +869,11 @@ void ConversionPatternRewriter::cloneRegionBefore( } /// PatternRewriter hook for creating a new operation. -Operation * -ConversionPatternRewriter::createOperation(const OperationState &state) { - LLVM_DEBUG(llvm::dbgs() << "** Creating operation : " << state.name << "\n"); - auto *result = OpBuilder::createOperation(state); - impl->createdOps.push_back(result); - return result; +Operation *ConversionPatternRewriter::insert(Operation *op) { + LLVM_DEBUG(llvm::dbgs() << "** Inserting operation : " << op->getName() + << "\n"); + impl->createdOps.push_back(op); + return OpBuilder::insert(op); } /// PatternRewriter hook for updating the root operation in-place. @@ -946,6 +960,10 @@ public: ConversionTarget &getTarget() { return target; } private: + /// Attempt to legalize the given operation by folding it. + LogicalResult legalizeWithFold(Operation *op, + ConversionPatternRewriter &rewriter); + /// Attempt to legalize the given operation by applying the provided pattern. /// Returns success if the operation was legalized, failure otherwise. LogicalResult legalizePattern(Operation *op, RewritePattern *pattern, @@ -1011,6 +1029,14 @@ OperationLegalizer::legalize(Operation *op, return success(); } + // If the operation isn't legal, try to fold it in-place. + // TODO(riverriddle) Should we always try to do this, even if the op is + // already legal? + if (succeeded(legalizeWithFold(op, rewriter))) { + LLVM_DEBUG(llvm::dbgs() << "-- Success : Operation was folded\n"); + return success(); + } + // Otherwise, we need to apply a legalization pattern to this operation. auto it = legalizerPatterns.find(op->getName()); if (it == legalizerPatterns.end()) { @@ -1027,6 +1053,36 @@ OperationLegalizer::legalize(Operation *op, return failure(); } +LogicalResult +OperationLegalizer::legalizeWithFold(Operation *op, + ConversionPatternRewriter &rewriter) { + auto &rewriterImpl = rewriter.getImpl(); + RewriterState curState = rewriterImpl.getCurrentState(); + + // Try to fold the operation. + SmallVector replacementValues; + rewriter.setInsertionPoint(op); + if (failed(rewriter.tryFold(op, replacementValues))) + return failure(); + + // Insert a replacement for 'op' with the folded replacement values. + rewriter.replaceOp(op, replacementValues); + + // Recursively legalize any new constant operations. + for (unsigned i = curState.numCreatedOperations, + e = rewriterImpl.createdOps.size(); + i != e; ++i) { + Operation *cstOp = rewriterImpl.createdOps[i]; + if (failed(legalize(cstOp, rewriter))) { + LLVM_DEBUG(llvm::dbgs() << "-- FAIL: Generated folding constant '" + << cstOp->getName() << "' was illegal.\n"); + rewriterImpl.resetState(curState); + return failure(); + } + } + return success(); +} + LogicalResult OperationLegalizer::legalizePattern(Operation *op, RewritePattern *pattern, ConversionPatternRewriter &rewriter) { diff --git a/third_party/mlir/lib/Transforms/LoopTiling.cpp b/third_party/mlir/lib/Transforms/LoopTiling.cpp index 4ee7197f2df..10654783aa9 100644 --- a/third_party/mlir/lib/Transforms/LoopTiling.cpp +++ b/third_party/mlir/lib/Transforms/LoopTiling.cpp @@ -362,7 +362,7 @@ void LoopTiling::getTileSizes(ArrayRef band, // one possible approach. Or compute a polynomial in tile sizes and solve for // it. - // For an n-d tilable band, compute n^th root of the excess. + // For an n-d tileable band, compute n^th root of the excess. unsigned tSize = static_cast(floorl(std::pow(excessFactor, 1.0 / band.size()))); // We'll keep a running product to determine the last tile size better. diff --git a/third_party/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp b/third_party/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp index aa4563c96e4..e2ca3f8fc5e 100644 --- a/third_party/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp +++ b/third_party/mlir/lib/Transforms/Utils/GreedyPatternRewriteDriver.cpp @@ -86,12 +86,11 @@ public: // These are hooks implemented for PatternRewriter. protected: - // Implement the hook for creating operations, and make sure that newly - // created ops are added to the worklist for processing. - Operation *createOperation(const OperationState &state) override { - auto *result = OpBuilder::createOperation(state); - addToWorklist(result); - return result; + // Implement the hook for inserting operations, and make sure that newly + // inserted ops are added to the worklist for processing. + Operation *insert(Operation *op) override { + addToWorklist(op); + return OpBuilder::insert(op); } // If an operation is about to be removed, make sure it is not in our diff --git a/third_party/mlir/lib/Transforms/Utils/LoopUtils.cpp b/third_party/mlir/lib/Transforms/Utils/LoopUtils.cpp index f718d9a5637..50248b01359 100644 --- a/third_party/mlir/lib/Transforms/Utils/LoopUtils.cpp +++ b/third_party/mlir/lib/Transforms/Utils/LoopUtils.cpp @@ -933,7 +933,7 @@ static LogicalResult tryIsolateBands(const TileLoops &tileLoops) { TileLoops mlir::extractFixedOuterLoops(loop::ForOp rootForOp, ArrayRef sizes) { - // Collect prefectly nested loops. If more size values provided than nested + // Collect perfectly nested loops. If more size values provided than nested // loops available, truncate `sizes`. SmallVector forOps; forOps.reserve(sizes.size()); @@ -1235,11 +1235,10 @@ static AffineForOp generatePointWiseCopy(Location loc, Value *memref, memIndicesStart); // Construct the subscript for the slow memref being copied. - SmallVector operands = {memBase, forOp.getInductionVar()}; auto memIndex = b.create( loc, AffineMap::get(2, 0, b.getAffineDimExpr(0) + b.getAffineDimExpr(1)), - operands); + ValueRange({memBase, forOp.getInductionVar()})); memIndices.push_back(memIndex); } diff --git a/third_party/mlir/lib/Transforms/Utils/Utils.cpp b/third_party/mlir/lib/Transforms/Utils/Utils.cpp index 35a5273a28f..190b6c3155e 100644 --- a/third_party/mlir/lib/Transforms/Utils/Utils.cpp +++ b/third_party/mlir/lib/Transforms/Utils/Utils.cpp @@ -70,7 +70,7 @@ LogicalResult mlir::replaceAllMemRefUsesWith(Value *oldMemRef, Value *newMemRef, (void)oldMemRefRank; // unused in opt mode if (indexRemap) { assert(indexRemap.getNumSymbols() == symbolOperands.size() && - "symbolic operand count mistmatch"); + "symbolic operand count mismatch"); assert(indexRemap.getNumInputs() == extraOperands.size() + oldMemRefRank + symbolOperands.size()); assert(indexRemap.getNumResults() + extraIndices.size() == newMemRefRank); diff --git a/third_party/mlir/test/BUILD b/third_party/mlir/test/BUILD index 369ab93fa58..b4f2461afd6 100644 --- a/third_party/mlir/test/BUILD +++ b/third_party/mlir/test/BUILD @@ -33,17 +33,17 @@ gentbl( ) gentbl( - name = "TestLinalgTilePermuteTransformPatternsIncGen", + name = "TestVectorTransformPatternsIncGen", tbl_outs = [ ( "-gen-rewriters", - "lib/DeclarativeTransforms/TestLinalgTilePermutePatterns.h.inc", + "lib/DeclarativeTransforms/TestVectorTransformPatterns.h.inc", ), ], tblgen = "@local_config_mlir//:mlir-tblgen", - td_file = "lib/DeclarativeTransforms/TestLinalgTilePermutePatterns.td", + td_file = "lib/DeclarativeTransforms/TestVectorTransformPatterns.td", td_srcs = [ - "@local_config_mlir//:LinalgTransformPatternsTdFiles", + "@local_config_mlir//:VectorTransformPatternsTdFiles", ], ) @@ -147,22 +147,22 @@ cc_library( "lib/Transforms/TestCallGraph.cpp", "lib/Transforms/TestConstantFold.cpp", "lib/Transforms/TestInlining.cpp", - "lib/Transforms/TestLinalgTilePermuteTransforms.cpp", "lib/Transforms/TestLinalgTransforms.cpp", + "lib/Transforms/TestLiveness.cpp", "lib/Transforms/TestLoopFusion.cpp", "lib/Transforms/TestLoopMapping.cpp", "lib/Transforms/TestLoopParametricTiling.cpp", "lib/Transforms/TestMemRefStrideCalculation.cpp", "lib/Transforms/TestOpaqueLoc.cpp", "lib/Transforms/TestVectorToLoopsConversion.cpp", - "lib/Transforms/TestVectorToVectorConversion.cpp", + "lib/Transforms/TestVectorTransforms.cpp", "lib/Transforms/TestVectorizationUtils.cpp", ], includes = ["lib/TestDialect"], deps = [ ":TestDialect", - ":TestLinalgTilePermuteTransformPatternsIncGen", ":TestLinalgTransformPatternsIncGen", + ":TestVectorTransformPatternsIncGen", "@llvm//:support", "@local_config_mlir//:AffineOps", "@local_config_mlir//:Analysis", diff --git a/third_party/mlir/test/lib/DeclarativeTransforms/CMakeLists.txt b/third_party/mlir/test/lib/DeclarativeTransforms/CMakeLists.txt index 1ee62d82129..9672edb4c49 100644 --- a/third_party/mlir/test/lib/DeclarativeTransforms/CMakeLists.txt +++ b/third_party/mlir/test/lib/DeclarativeTransforms/CMakeLists.txt @@ -2,6 +2,6 @@ set(LLVM_TARGET_DEFINITIONS TestLinalgTransformPatterns.td) mlir_tablegen(TestLinalgTransformPatterns.h.inc -gen-rewriters) add_public_tablegen_target(MLIRTestLinalgTransformPatternsIncGen) -set(LLVM_TARGET_DEFINITIONS TestLinalgTilePermutePatterns.td) -mlir_tablegen(TestLinalgTilePermutePatterns.h.inc -gen-rewriters) -add_public_tablegen_target(MLIRTestLinalgTilePermutePatternsIncGen) +set(LLVM_TARGET_DEFINITIONS TestVectorTransformPatterns.td) +mlir_tablegen(TestVectorTransformPatterns.h.inc -gen-rewriters) +add_public_tablegen_target(MLIRTestVectorTransformPatternsIncGen) diff --git a/third_party/mlir/test/lib/DeclarativeTransforms/TestLinalgTilePermutePatterns.td b/third_party/mlir/test/lib/DeclarativeTransforms/TestLinalgTilePermutePatterns.td deleted file mode 100644 index 6d7bfffdf71..00000000000 --- a/third_party/mlir/test/lib/DeclarativeTransforms/TestLinalgTilePermutePatterns.td +++ /dev/null @@ -1,57 +0,0 @@ -//===- TestLinalgTilePermutePatterns.td - Test patterns --*- tablegen ----*-===// -// -// Copyright 2019 The MLIR Authors. -// -// 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. -// ============================================================================= -// -// This is the pattern definition file for declarative Linalg transformations -// tests. -// -//===----------------------------------------------------------------------===// - -#ifndef TEST_LINALG_TILEPERMUTE_PATTERNS -#define TEST_LINALG_TILEPERMUTE_PATTERNS - -include "mlir/Dialect/Linalg/Transforms/LinalgTransformPatterns.td" - -//===----------------------------------------------------------------------===// -// Linalg tiling and permutation patterns. -//===----------------------------------------------------------------------===// -def : Pat<(MatmulOp:$op $A, $B, $C), - (TileLinalgOp<[2000, 3000, 4000], "L2", [1,2,0]> $op), - [(Constraint]>> $op)]>; -def : Pat<(MatmulOp:$op $A, $B, $C), - (TileLinalgOp<[200, 300, 400], "L1", [1,0,2]> $op), - [(Constraint> $op)]>; -def : Pat<(MatmulOp:$op $A, $B, $C), - (TileLinalgOp<[20, 30, 40], "REG"> $op), - [(Constraint> $op)]>; - - -def : Pattern<(MatvecOp:$op $A, $b, $c), - [(TileLinalgOp<[5, 6], "L1", [1,0]> $op)], - [(Constraint $op)]>; - -def : Pattern<(DotOp:$op $a, $b, $c), - [(TileLinalgOp<[8000], "L1"> $op)], - [(Constraint, - HasLinalgTransformMarker<"L3">, - HasLinalgTransformMarker<"L2">]>> $op)]>; -def : Pattern<(DotOp:$op $a, $b, $c), - [(TileLinalgOp<[8], "REG"> $op)], - [(Constraint> $op)]>; - -#endif // TEST_LINALG_TILEPERMUTE_PATTERNS diff --git a/third_party/mlir/test/lib/DeclarativeTransforms/TestLinalgTransformPatterns.td b/third_party/mlir/test/lib/DeclarativeTransforms/TestLinalgTransformPatterns.td index 97e0cb21704..d2313927398 100644 --- a/third_party/mlir/test/lib/DeclarativeTransforms/TestLinalgTransformPatterns.td +++ b/third_party/mlir/test/lib/DeclarativeTransforms/TestLinalgTransformPatterns.td @@ -73,6 +73,31 @@ def : Pattern<(DotOp:$op $a, $b, $c), [(TileLinalgOp<[8], "REG"> $op)], [(Constraint> $op)]>; +//===----------------------------------------------------------------------===// +// Linalg tiling and permutation patterns. +//===----------------------------------------------------------------------===// +def : Pat<(MatmulOp:$op $A, $B, $C), + (TileLinalgOp<[2000, 3000, 4000], "L2__with_perm__", [1,2,0]> $op), + [(Constraint> $op)]>; +def : Pat<(MatmulOp:$op $A, $B, $C), + (TileLinalgOp<[200, 300, 400], "L1__with_perm__", [1,0,2]> $op), + [(Constraint> $op)]>; +def : Pat<(MatmulOp:$op $A, $B, $C), + (TileLinalgOp<[20, 30, 40], "REG__with_perm__"> $op), + [(Constraint> $op)]>; + + +def : Pattern<(MatvecOp:$op $A, $b, $c), + [(TileLinalgOp<[5, 6], "L1__with_perm__", [1,0]> $op)], + [(Constraint> $op)]>; + +def : Pattern<(DotOp:$op $a, $b, $c), + [(TileLinalgOp<[8000], "L1__with_perm__"> $op)], + [(Constraint> $op)]>; +def : Pattern<(DotOp:$op $a, $b, $c), + [(TileLinalgOp<[8], "REG__with_perm__"> $op)], + [(Constraint> $op)]>; + //===----------------------------------------------------------------------===// // Linalg to loops patterns. //===----------------------------------------------------------------------===// @@ -80,4 +105,31 @@ def : Pattern<(DotOp:$op $a, $b, $c), [(LinalgOpToLoops<"DotOp"> $op)], [(Constraint> $op)]>; +//===----------------------------------------------------------------------===// +// Linalg to vector contraction patterns. +//===----------------------------------------------------------------------===// +def : Pattern<(GenericOp:$op $_1, $_2, $_3, $_4, $_5, $_6, $_7, $_8), + [(LinalgOpToVectorContraction<"GenericOp"> $op)], + [(Constraint> $op)]>; + +//===----------------------------------------------------------------------===// +// Linalg generic permutation patterns. +//===----------------------------------------------------------------------===// +def : Pat<(GenericOp:$op $_1, $_2, $_3, $_4, $_5, $_6, $_7, $_8), + (PermuteGenericLinalgOp<[1,2,0],"PERMUTED"> $op), + [(Constraint]>> $op)]>; + +def : Pat<(IndexedGenericOp:$op $_1, $_2, $_3, $_4, $_5, $_6, $_7, $_8), + (PermuteGenericLinalgOp<[1,2,0],"PERMUTED"> $op), + [(Constraint]>> $op)]>; + +//===----------------------------------------------------------------------===// +// Linalg subview operands promotion. +//===----------------------------------------------------------------------===// +def : Pat<(MatmulOp:$op $A, $B, $C), + (LinalgOpPromoteSubviews<"MatmulOp"> $op), + [(Constraint> $op), + (Constraint> $op)]>; #endif // TEST_LINALG_TRANSFORMS_PATTERNS diff --git a/third_party/mlir/test/lib/DeclarativeTransforms/TestVectorTransformPatterns.td b/third_party/mlir/test/lib/DeclarativeTransforms/TestVectorTransformPatterns.td new file mode 100644 index 00000000000..228a8a018d6 --- /dev/null +++ b/third_party/mlir/test/lib/DeclarativeTransforms/TestVectorTransformPatterns.td @@ -0,0 +1,43 @@ +//===- TestVectorTransformPatterns.td - Test patterns ---*- tablegen ----*-===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// This is the pattern definition file for declarative Vector transformations +// tests. +// +//===----------------------------------------------------------------------===// + +#ifndef TEST_VECTOR_TRANSFORMS_PATTERNS +#define TEST_VECTOR_TRANSFORMS_PATTERNS + +include "mlir/Dialect/StandardOps/Ops.td" +include "mlir/Dialect/VectorOps/VectorOps.td" +include "mlir/Dialect/VectorOps/VectorTransformPatterns.td" + +def : Pat<(AddFOp:$op_results $a, $b), + (UnrollVectorOp<[2, 2]> $op_results, $a, $b), + [(Constraint> $a)]>; + +def : Pat<(AddFOp:$op_results $a, $b), + (UnrollVectorOp<[2, 2]> $op_results, $a, $b), + [(Constraint> $a)]>; + +// TODO(andydavis) Add Constraints on lhs/rhs shapes. +def : Pat<(Vector_ContractionOp:$op_results $a, $b, $c, $masks, $attr0, $attr1), + (UnrollVectorOp<[2, 2, 2]> $op_results, $a, $b, $c), + [(Constraint> $c)]>; + +#endif // TEST_VECTOR_TRANSFORMS_PATTERNS diff --git a/third_party/mlir/test/lib/IR/CMakeLists.txt b/third_party/mlir/test/lib/IR/CMakeLists.txt index 4ac6a911606..5cb2769a1f0 100644 --- a/third_party/mlir/test/lib/IR/CMakeLists.txt +++ b/third_party/mlir/test/lib/IR/CMakeLists.txt @@ -7,6 +7,9 @@ add_llvm_library(MLIRTestIR ) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../TestDialect) include_directories(${CMAKE_CURRENT_BINARY_DIR}/../TestDialect) +add_dependencies(MLIRTestIR + MLIRTestDialect +) target_link_libraries(MLIRTestIR MLIRPass ) diff --git a/third_party/mlir/test/lib/IR/TestMatchers.cpp b/third_party/mlir/test/lib/IR/TestMatchers.cpp index c0b92a8c433..5985a88ffa6 100644 --- a/third_party/mlir/test/lib/IR/TestMatchers.cpp +++ b/third_party/mlir/test/lib/IR/TestMatchers.cpp @@ -24,8 +24,8 @@ using namespace mlir; namespace { /// This is a test pass for verifying matchers. -struct TestMatchers : public ModulePass { - void runOnModule() override; +struct TestMatchers : public FunctionPass { + void runOnFunction() override; }; } // end anonymous namespace @@ -33,27 +33,20 @@ struct TestMatchers : public ModulePass { template unsigned countMatches(FuncOp f, Matcher &matcher) { unsigned count = 0; f.walk([&count, &matcher](Operation *op) { - if (matcher.match(op)) { - // llvm::outs() << "matched " << *op << "\n"; + if (matcher.match(op)) ++count; - } }); return count; } +using mlir::matchers::m_Any; +using mlir::matchers::m_Val; static void test1(FuncOp f) { - using mlir::matchers::m_any; - using mlir::matchers::m_val; - assert(f.getNumArguments() == 3 && "matcher test funcs must have 3 args"); - auto a = m_val(f.getArgument(0)); - auto b = m_val(f.getArgument(1)); - auto c = m_val(f.getArgument(2)); - (void)a; - (void)b; - (void)c; - llvm::outs() << f.getName(); + auto a = m_Val(f.getArgument(0)); + auto b = m_Val(f.getArgument(1)); + auto c = m_Val(f.getArgument(2)); auto p0 = m_Op(); // using 0-arity matcher llvm::outs() << "Pattern add(*) matched " << countMatches(f, p0) @@ -63,23 +56,23 @@ static void test1(FuncOp f) { llvm::outs() << "Pattern mul(*) matched " << countMatches(f, p1) << " times\n"; - auto p2 = m_Op(m_Op(), m_any()); + auto p2 = m_Op(m_Op(), m_Any()); llvm::outs() << "Pattern add(add(*), *) matched " << countMatches(f, p2) << " times\n"; - auto p3 = m_Op(m_any(), m_Op()); + auto p3 = m_Op(m_Any(), m_Op()); llvm::outs() << "Pattern add(*, add(*)) matched " << countMatches(f, p3) << " times\n"; - auto p4 = m_Op(m_Op(), m_any()); + auto p4 = m_Op(m_Op(), m_Any()); llvm::outs() << "Pattern mul(add(*), *) matched " << countMatches(f, p4) << " times\n"; - auto p5 = m_Op(m_any(), m_Op()); + auto p5 = m_Op(m_Any(), m_Op()); llvm::outs() << "Pattern mul(*, add(*)) matched " << countMatches(f, p5) << " times\n"; - auto p6 = m_Op(m_Op(), m_any()); + auto p6 = m_Op(m_Op(), m_Any()); llvm::outs() << "Pattern mul(mul(*), *) matched " << countMatches(f, p6) << " times\n"; @@ -95,7 +88,7 @@ static void test1(FuncOp f) { // clang-format off auto mul_of_muladd = m_Op(m_Op(), m_Op()); - auto mul_of_anyadd = m_Op(m_any(), m_Op()); + auto mul_of_anyadd = m_Op(m_Any(), m_Op()); auto p9 = m_Op(m_Op( mul_of_muladd, m_Op()), m_Op(mul_of_anyadd, mul_of_anyadd)); @@ -128,7 +121,7 @@ static void test1(FuncOp f) { llvm::outs() << "Pattern mul(a, add(b, c)) matched " << countMatches(f, p15) << " times\n"; - auto mul_of_aany = m_Op(a, m_any()); + auto mul_of_aany = m_Op(a, m_Any()); auto p16 = m_Op(mul_of_aany, m_Op(a, c)); llvm::outs() << "Pattern mul(mul(a, *), add(a, c)) matched " << countMatches(f, p16) << " times\n"; @@ -138,12 +131,25 @@ static void test1(FuncOp f) { << countMatches(f, p17) << " times\n"; } -void TestMatchers::runOnModule() { - auto m = getModule(); - for (auto f : m.getOps()) { - if (f.getName() == "test1") - test1(f); - } +void test2(FuncOp f) { + auto a = m_Val(f.getArgument(0)); + FloatAttr floatAttr; + auto p = m_Op(a, m_Op(a, m_Constant(&floatAttr))); + // Last operation that is not the terminator. + Operation *lastOp = f.getBody().front().back().getPrevNode(); + if (p.match(lastOp)) + llvm::outs() + << "Pattern add(add(a, constant), a) matched and bound constant to: " + << floatAttr.getValueAsDouble() << "\n"; +} + +void TestMatchers::runOnFunction() { + auto f = getFunction(); + llvm::outs() << f.getName() << "\n"; + if (f.getName() == "test1") + test1(f); + if (f.getName() == "test2") + test2(f); } static PassRegistration pass("test-matchers", diff --git a/third_party/mlir/test/lib/TestDialect/TestDialect.cpp b/third_party/mlir/test/lib/TestDialect/TestDialect.cpp index f470e6ab674..059cfb3dce7 100644 --- a/third_party/mlir/test/lib/TestDialect/TestDialect.cpp +++ b/third_party/mlir/test/lib/TestDialect/TestDialect.cpp @@ -291,7 +291,7 @@ LogicalResult TestOpWithVariadicResultsAndFolder::fold( LogicalResult mlir::OpWithInferTypeInterfaceOp::inferReturnTypes( llvm::Optional location, ValueRange operands, - ArrayRef attributes, ArrayRef regions, + ArrayRef attributes, RegionRange regions, SmallVectorImpl &inferedReturnTypes) { if (operands[0]->getType() != operands[1]->getType()) { return emitOptionalError(location, "operand type mismatch ", diff --git a/third_party/mlir/test/lib/TestDialect/TestOps.td b/third_party/mlir/test/lib/TestDialect/TestOps.td index d998eb37e74..cf15d0e49f3 100644 --- a/third_party/mlir/test/lib/TestDialect/TestOps.td +++ b/third_party/mlir/test/lib/TestDialect/TestOps.td @@ -916,10 +916,10 @@ def MixedVResultOp3 : TEST_Op<"mixed_variadic_out3", "Builder *builder, OperationState &state, IntegerAttr count", [{ auto i32Type = builder->getIntegerType(32); - state.addTypes(i32Type); // $ouput1 + state.addTypes(i32Type); // $output1 SmallVector types(count.getInt(), i32Type); - state.addTypes(types); // $ouput2 - state.addTypes(types); // $ouput3 + state.addTypes(types); // $output2 + state.addTypes(types); // $output3 state.addAttribute("count", count); }]> ]; diff --git a/third_party/mlir/test/lib/TestDialect/TestPatterns.cpp b/third_party/mlir/test/lib/TestDialect/TestPatterns.cpp index 9d85c7d93f8..94eb792cc66 100644 --- a/third_party/mlir/test/lib/TestDialect/TestPatterns.cpp +++ b/third_party/mlir/test/lib/TestDialect/TestPatterns.cpp @@ -375,7 +375,8 @@ struct TestLegalizePatternDriver ConversionTarget target(getContext()); target.addLegalOp(); target.addLegalOp(); - target.addIllegalOp(); + target + .addIllegalOp(); target.addDynamicallyLegalOp([](TestReturnOp op) { // Don't allow F32 operands. return llvm::none_of(op.getOperandTypes(), diff --git a/third_party/mlir/test/lib/Transforms/CMakeLists.txt b/third_party/mlir/test/lib/Transforms/CMakeLists.txt index 8a7933451b8..b6338e1d167 100644 --- a/third_party/mlir/test/lib/Transforms/CMakeLists.txt +++ b/third_party/mlir/test/lib/Transforms/CMakeLists.txt @@ -4,13 +4,13 @@ add_llvm_library(MLIRTestTransforms TestLoopFusion.cpp TestInlining.cpp TestLinalgTransforms.cpp - TestLinalgTilePermuteTransforms.cpp + TestLiveness.cpp TestLoopMapping.cpp TestLoopParametricTiling.cpp TestOpaqueLoc.cpp TestMemRefStrideCalculation.cpp TestVectorToLoopsConversion.cpp - TestVectorToVectorConversion.cpp + TestVectorTransforms.cpp TestVectorizationUtils.cpp ADDITIONAL_HEADER_DIRS @@ -22,7 +22,7 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../DeclarativeTransforms) include_directories(${CMAKE_CURRENT_BINARY_DIR}/../DeclarativeTransforms) add_dependencies(MLIRTestTransforms MLIRStandardOpsIncGen) add_dependencies(MLIRTestTransforms MLIRTestLinalgTransformPatternsIncGen) -add_dependencies(MLIRTestTransforms MLIRTestLinalgTilePermutePatternsIncGen) +add_dependencies(MLIRTestTransforms MLIRTestVectorTransformPatternsIncGen) target_link_libraries(MLIRTestTransforms MLIRAffineOps MLIRAnalysis diff --git a/third_party/mlir/test/lib/Transforms/TestLinalgTilePermuteTransforms.cpp b/third_party/mlir/test/lib/Transforms/TestLinalgTilePermuteTransforms.cpp deleted file mode 100644 index ec7fa4e71b4..00000000000 --- a/third_party/mlir/test/lib/Transforms/TestLinalgTilePermuteTransforms.cpp +++ /dev/null @@ -1,64 +0,0 @@ -//===- TestLinalgTilePermuteTransforms.cpp - Test Linalg tile + permute ---===// -// -// Copyright 2019 The MLIR Authors. -// -// 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. -// ============================================================================= -// -// This file implements logic for testing Linalg transformations. -// -//===----------------------------------------------------------------------===// - -#include "mlir/Dialect/Linalg/IR/LinalgOps.h" -#include "mlir/Dialect/Linalg/Transforms/LinalgTransforms.h" -#include "mlir/Dialect/Linalg/Utils/Utils.h" -#include "mlir/IR/PatternMatch.h" -#include "mlir/Pass/Pass.h" - -using namespace mlir; -using namespace mlir::linalg; - -namespace mlir { -namespace linalg { -namespace { -#include "TestLinalgTilePermutePatterns.h.inc" -} // end namespace -} // end namespace linalg -} // end namespace mlir - -namespace { -struct TestLinalgTilePermuteTransforms - : public FunctionPass { - void runOnFunction() override; -}; -} // end anonymous namespace - -/// Apply transformations specified as patterns. -void TestLinalgTilePermuteTransforms::runOnFunction() { - OwningRewritePatternList patterns; - auto funcOp = getFunction(); - - // Add the generated patterns to the list. - linalg::populateWithGenerated(&getContext(), &patterns); - applyPatternsGreedily(funcOp, patterns); - - // Drop the marker. - funcOp.walk([](LinalgOp op) { - op.removeAttr(LinalgTransforms::kLinalgTransformMarker); - }); -} - -static PassRegistration - pass("test-linalg-tile-and-permute-patterns", - "Test Linalg transformation with permutation patterns by applying " - "them greedily."); diff --git a/third_party/mlir/test/lib/Transforms/TestLiveness.cpp b/third_party/mlir/test/lib/Transforms/TestLiveness.cpp new file mode 100644 index 00000000000..d97060247f4 --- /dev/null +++ b/third_party/mlir/test/lib/Transforms/TestLiveness.cpp @@ -0,0 +1,42 @@ +//===- TestLiveness.cpp - Test liveness construction and information +//-------===// +// +// Copyright 2019 The MLIR Authors. +// +// 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. +// ============================================================================= +// +// This file contains test passes for constructing and resolving liveness +// information. +// +//===----------------------------------------------------------------------===// + +#include "mlir/Analysis/Liveness.h" +#include "mlir/Pass/Pass.h" + +using namespace mlir; + +namespace { + +struct TestLivenessPass : public FunctionPass { + void runOnFunction() override { + llvm::errs() << "Testing : " << getFunction().getName() << "\n"; + getAnalysis().print(llvm::errs()); + } +}; + +} // end anonymous namespace + +static PassRegistration + pass("test-print-liveness", + "Print the contents of a constructed liveness information."); diff --git a/third_party/mlir/test/lib/Transforms/TestVectorToVectorConversion.cpp b/third_party/mlir/test/lib/Transforms/TestVectorTransforms.cpp similarity index 78% rename from third_party/mlir/test/lib/Transforms/TestVectorToVectorConversion.cpp rename to third_party/mlir/test/lib/Transforms/TestVectorTransforms.cpp index 9f9b8a554fe..1d513065330 100644 --- a/third_party/mlir/test/lib/Transforms/TestVectorToVectorConversion.cpp +++ b/third_party/mlir/test/lib/Transforms/TestVectorTransforms.cpp @@ -1,5 +1,4 @@ -//===- TestVectorToVectorConversion.cpp - Test VectorTransfers lowering -//-------===// +//===- TestVectorToVectorConversion.cpp - Test VectorTransfers lowering ---===// // // Copyright 2019 The MLIR Authors. // @@ -18,25 +17,29 @@ #include +#include "mlir/Dialect/StandardOps/Ops.h" +#include "mlir/Dialect/VectorOps/VectorOps.h" #include "mlir/Dialect/VectorOps/VectorTransforms.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Pass/Pass.h" -#include "mlir/Transforms/Passes.h" using namespace mlir; +using namespace mlir::vector; namespace { +#include "TestVectorTransformPatterns.h.inc" struct TestVectorToVectorConversion : public FunctionPass { void runOnFunction() override { OwningRewritePatternList patterns; auto *context = &getContext(); - populateVectorToVectorConversionPatterns(context, patterns); + populateWithGenerated(context, &patterns); + populateVectorToVectorCanonicalizationPatterns(patterns, context); + populateVectorToVectorTransformationPatterns(patterns, context); applyPatternsGreedily(getFunction(), patterns); } }; - } // end anonymous namespace static PassRegistration diff --git a/third_party/mlir/tools/mlir-tblgen/DocGenUtilities.h b/third_party/mlir/tools/mlir-tblgen/DocGenUtilities.h index 3aa9e9fa3a2..b7617742727 100644 --- a/third_party/mlir/tools/mlir-tblgen/DocGenUtilities.h +++ b/third_party/mlir/tools/mlir-tblgen/DocGenUtilities.h @@ -15,7 +15,7 @@ // limitations under the License. // ============================================================================= // -// This file defines common utilities for generating documents from tablgen +// This file defines common utilities for generating documents from tablegen // structures. // //===----------------------------------------------------------------------===// diff --git a/third_party/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/third_party/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp index 06293900acc..dd56458ccb3 100644 --- a/third_party/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp +++ b/third_party/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp @@ -464,8 +464,7 @@ void OpClass::writeDeclTo(raw_ostream &os) const { os << extraClassDeclaration << "\n"; if (hasPrivateMethod) { - os << '\n'; - os << "private:\n"; + os << "\nprivate:\n"; for (const auto &method : methods) { if (method.isPrivate()) { method.writeDeclTo(os); @@ -515,21 +514,9 @@ private: // Generates builder methods for the operation. void genBuilder(); - // Generates the build() method that takes each result-type/operand/attribute - // as a stand-alone parameter. Attributes will take wrapped mlir::Attribute - // values. The generated build() method also requires specifying result types - // for all results. - void genSeparateParamWrappedAttrBuilder(); - - // Generates the build() method that takes each result-type/operand/attribute - // as a stand-alone parameter. Attributes will take raw values without - // mlir::Attribute wrapper. The generated build() method also requires - // specifying result types for all results. - void genSeparateParamUnwrappedAttrBuilder(); - - // Generates the build() method that takes a single parameter for all the - // result types and a separate parameter for each operand/attribute. - void genCollectiveTypeParamBuilder(); + // Generates the build() method that takes each operand/attribute + // as a stand-alone parameter. + void genSeparateArgParamBuilder(); // Generates the build() method that takes each operand/attribute as a // stand-alone parameter. The generated build() method uses first operand's @@ -668,29 +655,19 @@ void OpEmitter::emitDef(raw_ostream &os) { opClass.writeDefTo(os); } void OpEmitter::genAttrGetters() { FmtContext fctx; fctx.withBuilder("mlir::Builder(this->getContext())"); - for (auto &namedAttr : op.getAttributes()) { - const auto &name = namedAttr.name; - const auto &attr = namedAttr.attr; + // Emit the derived attribute body. + auto emitDerivedAttr = [&](StringRef name, Attribute attr) { auto &method = opClass.newMethod(attr.getReturnType(), name); auto &body = method.body(); + body << " " << attr.getDerivedCodeBody() << "\n"; + }; - // Emit the derived attribute body. - if (attr.isDerivedAttr()) { - body << " " << attr.getDerivedCodeBody() << "\n"; - continue; - } - - // Emit normal emitter. - - // Return the queried attribute with the correct return type. - auto attrVal = - (attr.hasDefaultValue() || attr.isOptional()) - ? formatv("this->getAttr(\"{0}\").dyn_cast_or_null<{1}>()", name, - attr.getStorageType()) - : formatv("this->getAttr(\"{0}\").cast<{1}>()", name, - attr.getStorageType()); - body << " auto attr = " << attrVal << ";\n"; + // Emit with return type specified. + auto emitAttrWithReturnType = [&](StringRef name, Attribute attr) { + auto &method = opClass.newMethod(attr.getReturnType(), name); + auto &body = method.body(); + body << " auto attr = " << name << "Attr();\n"; if (attr.hasDefaultValue()) { // Returns the default value if not set. // TODO: this is inefficient, we are recreating the attribute for every @@ -705,6 +682,32 @@ void OpEmitter::genAttrGetters() { body << " return " << tgfmt(attr.getConvertFromStorageCall(), &fctx.withSelf("attr")) << ";\n"; + }; + + // Generate raw named accessor type. This is a wrapper class that allows + // referring to the attributes via accessors instead of having to use + // the string interface for better compile time verification. + auto emitAttrWithStorageType = [&](StringRef name, Attribute attr) { + auto &method = + opClass.newMethod(attr.getStorageType(), (name + "Attr").str()); + auto &body = method.body(); + body << " return this->getAttr(\"" << name << "\")."; + if (attr.isOptional() || attr.hasDefaultValue()) + body << "dyn_cast_or_null<"; + else + body << "cast<"; + body << attr.getStorageType() << ">();"; + }; + + for (auto &namedAttr : op.getAttributes()) { + const auto &name = namedAttr.name; + const auto &attr = namedAttr.attr; + if (attr.isDerivedAttr()) { + emitDerivedAttr(name, attr); + } else { + emitAttrWithStorageType(name, attr); + emitAttrWithReturnType(name, attr); + } } } @@ -882,26 +885,11 @@ void OpEmitter::genNamedRegionGetters() { } } -void OpEmitter::genSeparateParamWrappedAttrBuilder() { - std::string paramList; - llvm::SmallVector resultNames; - buildParamList(paramList, resultNames, TypeParamKind::Separate); - - auto &m = opClass.newMethod("void", "build", paramList, OpMethod::MP_Static); - genCodeForAddingArgAndRegionForBuilder(m.body()); - - // Push all result types to the operation state - for (int i = 0, e = op.getNumResults(); i < e; ++i) { - m.body() << " " << builderOpState << ".addTypes(" << resultNames[i] - << ");\n"; - } -} - -void OpEmitter::genSeparateParamUnwrappedAttrBuilder() { +static bool canGenerateUnwrappedBuilder(Operator &op) { // If this op does not have native attributes at all, return directly to avoid // redefining builders. if (op.getNumNativeAttributes() == 0) - return; + return false; bool canGenerate = false; // We are generating builders that take raw values for attributes. We need to @@ -915,47 +903,75 @@ void OpEmitter::genSeparateParamUnwrappedAttrBuilder() { break; } } - if (!canGenerate) - return; - - std::string paramList; - llvm::SmallVector resultNames; - buildParamList(paramList, resultNames, TypeParamKind::Separate, - AttrParamKind::UnwrappedValue); - - auto &m = opClass.newMethod("void", "build", paramList, OpMethod::MP_Static); - genCodeForAddingArgAndRegionForBuilder(m.body(), /*isRawValueAttr=*/true); - - // Push all result types to the operation state. - for (int i = 0, e = op.getNumResults(); i < e; ++i) { - m.body() << " " << builderOpState << ".addTypes(" << resultNames[i] - << ");\n"; - } + return canGenerate; } -void OpEmitter::genCollectiveTypeParamBuilder() { - auto numResults = op.getNumResults(); +void OpEmitter::genSeparateArgParamBuilder() { + SmallVector attrBuilderType; + attrBuilderType.push_back(AttrParamKind::WrappedAttr); + if (canGenerateUnwrappedBuilder(op)) + attrBuilderType.push_back(AttrParamKind::UnwrappedValue); - // If this op has no results, then just skip generating this builder. - // Otherwise we are generating the same signature as the separate-parameter - // builder. - if (numResults == 0) - return; + // Emit with separate builders with or without unwrapped attributes and/or + // inferring result type. + auto emit = [&](AttrParamKind attrType, TypeParamKind paramKind, + bool inferType) { + std::string paramList; + llvm::SmallVector resultNames; + buildParamList(paramList, resultNames, paramKind, attrType); - // Similarly for ops with one single variadic result, which will also have one - // `ArrayRef` parameter for the result type. - if (numResults == 1 && op.getResult(0).isVariadic()) - return; + auto &m = + opClass.newMethod("void", "build", paramList, OpMethod::MP_Static); + auto &body = m.body(); + genCodeForAddingArgAndRegionForBuilder( + body, /*isRawValueAttr=*/attrType == AttrParamKind::UnwrappedValue); - std::string paramList; - llvm::SmallVector resultNames; - buildParamList(paramList, resultNames, TypeParamKind::Collective); + // Push all result types to the operation state - auto &m = opClass.newMethod("void", "build", paramList, OpMethod::MP_Static); - genCodeForAddingArgAndRegionForBuilder(m.body()); + if (inferType) { + // Generate builder that infers type too. + // TODO(jpienaar): Subsume this with general checking if type can be + // infered automatically. + // TODO(jpienaar): Expand to handle regions. + body << formatv(R"( + SmallVector inferedReturnTypes; + if (succeeded({0}::inferReturnTypes({1}.location, {1}.operands, + {1}.attributes, /*regions=*/{{}, inferedReturnTypes))) + {1}.addTypes(inferedReturnTypes); + else + llvm::report_fatal_error("Failed to infer result type(s).");)", + opClass.getClassName(), builderOpState); + return; + } - // Push all result types to the operation state - m.body() << formatv(" {0}.addTypes(resultTypes);\n", builderOpState); + switch (paramKind) { + case TypeParamKind::None: + return; + case TypeParamKind::Separate: + for (int i = 0, e = op.getNumResults(); i < e; ++i) { + body << " " << builderOpState << ".addTypes(" << resultNames[i] + << ");\n"; + } + return; + case TypeParamKind::Collective: + body << " " << builderOpState << ".addTypes(resultTypes);\n"; + return; + }; + llvm_unreachable("unhandled TypeParamKind"); + }; + + bool canInferType = + op.getTrait("InferTypeOpInterface::Trait") && op.getNumRegions() == 0; + for (auto attrType : attrBuilderType) { + emit(attrType, TypeParamKind::Separate, /*inferType=*/false); + if (canInferType) + emit(attrType, TypeParamKind::None, /*inferType=*/true); + // Emit separate arg build with collective type, unless there is only one + // variadic result, in which case the above would have already generated + // the same build method. + if (!(op.getNumResults() == 1 && op.getResult(0).isVariadic())) + emit(attrType, TypeParamKind::Collective, /*inferType=*/false); + } } void OpEmitter::genUseOperandAsResultTypeCollectiveParamBuilder() { @@ -1006,8 +1022,7 @@ void OpEmitter::genInferedTypeCollectiveParamBuilder() { /*regions=*/{{}, inferedReturnTypes))) build(builder, tblgen_state, inferedReturnTypes, operands, attributes); else - llvm::report_fatal_error("Failed to infer result type(s)."); - )", + llvm::report_fatal_error("Failed to infer result type(s).");)", opClass.getClassName(), builderOpState); } @@ -1096,18 +1111,13 @@ void OpEmitter::genBuilder() { // Generate default builders that requires all result type, operands, and // attributes as parameters. - // We generate three builders here: - // 1. one having a stand-alone parameter for each result type / operand / - // attribute, and - genSeparateParamWrappedAttrBuilder(); - genSeparateParamUnwrappedAttrBuilder(); - // 2. one having a stand-alone parameter for each operand / attribute and - // an aggregated parameter for all result types, and - genCollectiveTypeParamBuilder(); - // 3. one having an aggregated parameter for all result types / operands / + // We generate three classes of builders here: + // 1. one having a stand-alone parameter for each operand / attribute, and + genSeparateArgParamBuilder(); + // 2. one having an aggregated parameter for all result types / operands / // attributes, and genCollectiveParamBuilder(); - // 4. one having a stand-alone parameter for each operand and attribute, + // 3. one having a stand-alone parameter for each operand and attribute, // use the first operand or attribute's type as all result types // to facilitate different call patterns. if (op.getNumVariadicResults() == 0) { @@ -1118,11 +1128,6 @@ void OpEmitter::genBuilder() { if (op.getTrait("OpTrait::FirstAttrDerivedResultType")) genUseAttrAsResultTypeBuilder(); } - // TODO(jpienaar): Subsume this with general checking if type can be infered - // automatically. - // TODO(jpienaar): Expand to handle regions. - if (op.getTrait("InferTypeOpInterface::Trait") && op.getNumRegions() == 0) - genInferedTypeCollectiveParamBuilder(); } void OpEmitter::genCollectiveParamBuilder() { @@ -1141,13 +1146,6 @@ void OpEmitter::genCollectiveParamBuilder() { auto &m = opClass.newMethod("void", "build", params, OpMethod::MP_Static); auto &body = m.body(); - // Result types - if (numVariadicResults == 0 || numNonVariadicResults != 0) - body << " assert(resultTypes.size()" - << (numVariadicResults != 0 ? " >= " : " == ") << numNonVariadicResults - << "u && \"mismatched number of return types\");\n"; - body << " " << builderOpState << ".addTypes(resultTypes);\n"; - // Operands if (numVariadicOperands == 0 || numNonVariadicOperands != 0) body << " assert(operands.size()" @@ -1164,6 +1162,20 @@ void OpEmitter::genCollectiveParamBuilder() { for (int i = 0; i < numRegions; ++i) m.body() << " (void)" << builderOpState << ".addRegion();\n"; } + + // Result types + if (numVariadicResults == 0 || numNonVariadicResults != 0) + body << " assert(resultTypes.size()" + << (numVariadicResults != 0 ? " >= " : " == ") << numNonVariadicResults + << "u && \"mismatched number of return types\");\n"; + body << " " << builderOpState << ".addTypes(resultTypes);\n"; + + // Generate builder that infers type too. + // TODO(jpienaar): Subsume this with general checking if type can be infered + // automatically. + // TODO(jpienaar): Expand to handle regions. + if (op.getTrait("InferTypeOpInterface::Trait") && op.getNumRegions() == 0) + genInferedTypeCollectiveParamBuilder(); } void OpEmitter::buildParamList(std::string ¶mList, diff --git a/third_party/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp b/third_party/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp index 4da412c2f43..4c22d6236e0 100644 --- a/third_party/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp +++ b/third_party/mlir/tools/mlir-tblgen/OpInterfacesGen.cpp @@ -51,6 +51,19 @@ static void emitMethodNameAndArgs(const OpInterfaceMethod &method, os << ')'; } +// Get an array of all OpInterface definitions but exclude those subclassing +// "DeclareOpInterfaceMethods". +static std::vector +getAllOpInterfaceDefinitions(const RecordKeeper &recordKeeper) { + std::vector defs = + recordKeeper.getAllDerivedDefinitions("OpInterface"); + + llvm::erase_if(defs, [](const Record *def) { + return def->isSubClassOf("DeclareOpInterfaceMethods"); + }); + return defs; +} + //===----------------------------------------------------------------------===// // GEN: Interface definitions //===----------------------------------------------------------------------===// @@ -78,8 +91,7 @@ static bool emitInterfaceDefs(const RecordKeeper &recordKeeper, raw_ostream &os) { llvm::emitSourceFileHeader("Operation Interface Definitions", os); - auto defs = recordKeeper.getAllDerivedDefinitions("OpInterface"); - for (const auto *def : defs) { + for (const auto *def : getAllOpInterfaceDefinitions(recordKeeper)) { OpInterface interface(def); emitInterfaceDef(interface, os); } @@ -169,8 +181,7 @@ static bool emitInterfaceDecls(const RecordKeeper &recordKeeper, raw_ostream &os) { llvm::emitSourceFileHeader("Operation Interface Declarations", os); - auto defs = recordKeeper.getAllDerivedDefinitions("OpInterface"); - for (const auto *def : defs) { + for (const auto *def : getAllOpInterfaceDefinitions(recordKeeper)) { OpInterface interface(def); emitInterfaceDecl(interface, os); } @@ -230,8 +241,7 @@ static bool emitInterfaceDocs(const RecordKeeper &recordKeeper, os << "\n"; os << "# Operation Interface definition\n"; - auto defs = recordKeeper.getAllDerivedDefinitions("OpInterface"); - for (const auto *def : defs) + for (const auto *def : getAllOpInterfaceDefinitions(recordKeeper)) emitInterfaceDoc(*def, os); return false; } diff --git a/third_party/mlir/tools/mlir-tblgen/RewriterGen.cpp b/third_party/mlir/tools/mlir-tblgen/RewriterGen.cpp index f229a349d27..b2376e8739c 100644 --- a/third_party/mlir/tools/mlir-tblgen/RewriterGen.cpp +++ b/third_party/mlir/tools/mlir-tblgen/RewriterGen.cpp @@ -939,8 +939,7 @@ void PatternEmitter::createAggregateLocalVarsForOpArgs( "SmallVector tblgen_attrs; (void)tblgen_attrs;\n"); for (int argIndex = 0, e = resultOp.getNumArgs(); argIndex < e; ++argIndex) { - if (const auto *attr = - resultOp.getArg(argIndex).dyn_cast()) { + if (resultOp.getArg(argIndex).is()) { const char *addAttrCmd = "if ({1}) {{" " tblgen_attrs.emplace_back(rewriter." "getIdentifier(\"{0}\"), {1}); }\n"; diff --git a/third_party/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp b/third_party/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp index 422183ed948..f1712efb319 100644 --- a/third_party/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp +++ b/third_party/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp @@ -85,7 +85,13 @@ static void emitAttributeSerialization(const Attribute &attr, StringRef attrName, raw_ostream &os) { os << tabs << formatv("auto attr = {0}.getAttr(\"{1}\");\n", opVar, attrName); os << tabs << "if (attr) {\n"; - if (attr.getAttrDefName() == "I32ArrayAttr") { + if (attr.getAttrDefName() == "SPV_ScopeAttr" || + attr.getAttrDefName() == "SPV_MemorySemanticsAttr") { + os << tabs + << formatv(" {0}.push_back(prepareConstantInt({1}.getLoc(), " + "attr.cast()));\n", + operandList, opVar); + } else if (attr.getAttrDefName() == "I32ArrayAttr") { // Serialize all the elements of the array os << tabs << " for (auto attrElem : attr.cast()) {\n"; os << tabs @@ -284,7 +290,13 @@ static void emitAttributeDeserialization(const Attribute &attr, StringRef attrList, StringRef attrName, StringRef words, StringRef wordIndex, raw_ostream &os) { - if (attr.getAttrDefName() == "I32ArrayAttr") { + if (attr.getAttrDefName() == "SPV_ScopeAttr" || + attr.getAttrDefName() == "SPV_MemorySemanticsAttr") { + os << tabs + << formatv("{0}.push_back(opBuilder.getNamedAttr(\"{1}\", " + "getConstantInt({2}[{3}++])));\n", + attrList, attrName, words, wordIndex); + } else if (attr.getAttrDefName() == "I32ArrayAttr") { os << tabs << "SmallVector attrListElems;\n"; os << tabs << formatv("while ({0} < {1}.size()) {{\n", wordIndex, words); os << tabs diff --git a/third_party/mlir/utils/spirv/define_inst.sh b/third_party/mlir/utils/spirv/define_inst.sh index 3508c4f9b4f..f11078a8e76 100755 --- a/third_party/mlir/utils/spirv/define_inst.sh +++ b/third_party/mlir/utils/spirv/define_inst.sh @@ -35,13 +35,13 @@ file_name=$1 inst_category=$2 case $inst_category in - Op | ArithmeticOp | LogicalOp | CastOp | ControlFlowOp | StructureOp) + Op | ArithmeticOp | LogicalOp | CastOp | ControlFlowOp | StructureOp | AtomicUpdateOp | AtomicUpdateWithValueOp) ;; *) echo "Usage : " $0 " ()*" echo " is the file name of MLIR SPIR-V op definitions spec" echo " must be one of " \ - "(Op|ArithmeticOp|LogicalOp|CastOp|ControlFlowOp|StructureOp)" + "(Op|ArithmeticOp|LogicalOp|CastOp|ControlFlowOp|StructureOp|AtomicUpdateOp)" exit 1; ;; esac diff --git a/third_party/mlir/utils/spirv/gen_spirv_dialect.py b/third_party/mlir/utils/spirv/gen_spirv_dialect.py index d1530f77d5a..be7116c211f 100755 --- a/third_party/mlir/utils/spirv/gen_spirv_dialect.py +++ b/third_party/mlir/utils/spirv/gen_spirv_dialect.py @@ -353,7 +353,7 @@ def map_spec_operand_to_ods_argument(operand): # and 'IdScope' given that they should be generated from OpConstant. assert quantifier == '', ('unexpected to have optional/variadic memory ' 'semantics or scope ') - arg_type = 'I32' + arg_type = 'SPV_' + kind[2:] + 'Attr' elif kind == 'LiteralInteger': if quantifier == '': arg_type = 'I32Attr' @@ -472,7 +472,7 @@ def get_op_definition(instruction, doc, existing_info): description = existing_info.get('description', None) if description is None: - assembly = '\n ``` {.ebnf}\n'\ + assembly = '\n ```\n'\ ' [TODO]\n'\ ' ```\n\n'\ ' For example:\n\n'\ @@ -651,8 +651,9 @@ def update_td_op_definitions(path, instructions, docs, filter_list, instruction = next( inst for inst in instructions if inst['opname'] == opname) op_defs.append( - get_op_definition(instruction, docs[opname], - op_info_dict.get(opname, {}))) + get_op_definition( + instruction, docs[opname], + op_info_dict.get(opname, {'inst_category': inst_category}))) except StopIteration: # This is an op added by us; use the existing ODS definition. op_defs.append(name_op_map[opname]) diff --git a/third_party/psimd/BUILD b/third_party/psimd/BUILD new file mode 100644 index 00000000000..82bab3ffd96 --- /dev/null +++ b/third_party/psimd/BUILD @@ -0,0 +1 @@ +# This empty BUILD file is required to make Bazel treat this directory as a package. diff --git a/third_party/psimd/BUILD.bazel b/third_party/psimd/BUILD.bazel new file mode 100644 index 00000000000..fe101815c5f --- /dev/null +++ b/third_party/psimd/BUILD.bazel @@ -0,0 +1,15 @@ +# Description: +# Portable 128-bit SIMD intrinsics + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +cc_library( + name = "psimd", + hdrs = glob(["include/psimd.h"]), + includes = ["include"], + strip_include_prefix = "include", +) diff --git a/third_party/psimd/workspace.bzl b/third_party/psimd/workspace.bzl new file mode 100644 index 00000000000..49e2be0b684 --- /dev/null +++ b/third_party/psimd/workspace.bzl @@ -0,0 +1,15 @@ +"""Loads the psimd library, used by XNNPACK.""" + +load("//third_party:repo.bzl", "third_party_http_archive") + +def repo(): + third_party_http_archive( + name = "psimd", + strip_prefix = "psimd-8fd2884b88848180904a40c452a362d1ee429ad5", + sha256 = "9d4f05bc5a93a0ab8bcef12027ebe54cfddd0050d4862442449c8de11b4e8c17", + urls = [ + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/Maratyszcza/psimd/archive/8fd2884b88848180904a40c452a362d1ee429ad5.tar.gz", + "https://github.com/Maratyszcza/psimd/archive/8fd2884b88848180904a40c452a362d1ee429ad5.tar.gz", + ], + build_file = "//third_party/psimd:BUILD.bazel", + ) diff --git a/third_party/pthreadpool/BUILD b/third_party/pthreadpool/BUILD new file mode 100644 index 00000000000..82bab3ffd96 --- /dev/null +++ b/third_party/pthreadpool/BUILD @@ -0,0 +1 @@ +# This empty BUILD file is required to make Bazel treat this directory as a package. diff --git a/third_party/pthreadpool/BUILD.bazel b/third_party/pthreadpool/BUILD.bazel new file mode 100644 index 00000000000..1267e4f3736 --- /dev/null +++ b/third_party/pthreadpool/BUILD.bazel @@ -0,0 +1,32 @@ +# Description: +# Portable pthread-based thread pool for C and C++ + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +exports_files(["LICENSE"]) + +cc_library( + name = "pthreadpool", + srcs = [ + "src/threadpool-pthreads.c", + "src/threadpool-utils.h", + ], + hdrs = [ + "include/pthreadpool.h", + ], + copts = [ + "-O2", + ], + defines = [ + "PTHREADPOOL_NO_DEPRECATED_API", + ], + includes = [ + "include", + ], + strip_include_prefix = "include", + deps = [ + "@FXdiv", + ], +) diff --git a/third_party/pthreadpool/workspace.bzl b/third_party/pthreadpool/workspace.bzl new file mode 100644 index 00000000000..b21c9ca12f8 --- /dev/null +++ b/third_party/pthreadpool/workspace.bzl @@ -0,0 +1,15 @@ +"""Loads the pthreadpool library, used by XNNPACK.""" + +load("//third_party:repo.bzl", "third_party_http_archive") + +def repo(): + third_party_http_archive( + name = "pthreadpool", + strip_prefix = "pthreadpool-7ad026703b3109907ad124025918da15cfd3f100", + sha256 = "96eb4256fc438b7b8cab40541d383efaf546fae7bad380c24ea601c326c5f685", + urls = [ + "https://storage.googleapis.com/mirror.tensorflow.org/github.com/Maratyszcza/pthreadpool/archive/7ad026703b3109907ad124025918da15cfd3f100.tar.gz", + "https://github.com/Maratyszcza/pthreadpool/archive/7ad026703b3109907ad124025918da15cfd3f100.tar.gz", + ], + build_file = "//third_party/pthreadpool:BUILD.bazel", + ) diff --git a/third_party/systemlibs/syslibs_configure.bzl b/third_party/systemlibs/syslibs_configure.bzl index 8619cddfdd9..14949dafab0 100644 --- a/third_party/systemlibs/syslibs_configure.bzl +++ b/third_party/systemlibs/syslibs_configure.bzl @@ -29,7 +29,6 @@ VALID_LIBS = [ "icu", "jpeg", "jsoncpp_git", - "keras_applications_archive", "lmdb", "nasm", "nsync",