From 4ccf91469c0ef5d6bab7751d3e1aa20587838305 Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Thu, 3 Oct 2019 09:59:49 -0700 Subject: [PATCH 1/8] initial commit --- rfcs/20191017-tfx-standardized-inputs.md | 692 ++++++++++++++++++ .../impl_tfxio.png | Bin 0 -> 51239 bytes .../oss_lib_org.png | Bin 0 -> 49047 bytes .../overview.png | Bin 0 -> 39507 bytes 4 files changed, 692 insertions(+) create mode 100644 rfcs/20191017-tfx-standardized-inputs.md create mode 100644 rfcs/20191017-tfx-standardized-inputs/impl_tfxio.png create mode 100644 rfcs/20191017-tfx-standardized-inputs/oss_lib_org.png create mode 100644 rfcs/20191017-tfx-standardized-inputs/overview.png diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md new file mode 100644 index 000000000..a39b2ede1 --- /dev/null +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -0,0 +1,692 @@ + + +# Standardized TFX Inputs + +Status | Proposed +:------------ | :------------------------------------------------------------ +**RFC #** | [NNN](https://github.com/tensorflow/community/pull/NNN) +**Author(s)** | Zhuo Peng (zhuo@google.com), Kester Tong (kestert@google.com) +**Sponsor** | Konstantinos Katsiapis (katsiapis@google.com) +**Updated** | 2019-10-03 + +# Objective + +* To define a common in-memory data representation that: + * is powerful enough to encode the following logical training data format: + flat + ([`tf.Example`](https://github.com/tensorflow/tensorflow/blob/abfba15cd9734cec7ecd3d0661b146fc251c842d/tensorflow/core/example/example.proto#L88)), + sequence + ([`tf.SequenceExample`](https://github.com/tensorflow/tensorflow/blob/abfba15cd9734cec7ecd3d0661b146fc251c842d/tensorflow/core/example/example.proto#L298)) + or structured data (e.g. + [Protocol Buffers](https://developers.google.com/protocol-buffers) or + [Apache Avro](https://avro.apache.org/)). + * all TFX components can understand and can support their own unique use + cases with. +* To define an I/O abstraction layer that produces the above in-memory + representation from supported physical storage formats, while hiding TFX’s + choice of such storage formats from TFX users. +* To define a bridge from the above in-memory representation to TF feedables + (i.e. Tensors and certain + [CompositeTensors](https://github.com/tensorflow/tensorflow/blob/abfba15cd9734cec7ecd3d0661b146fc251c842d/tensorflow/python/framework/composite_tensor.py#L1)). + +# Motivation + +## Fragmented in-memory data representations across TFX + +A TFX component may use its input data (data generated by ExampleGen) in two +ways: + +* it may need to understand the data and conduct analysis. Usually this + happens in [Apache Beam](https://beam.apache.org/), and does not involve a + TF Model. For example: + * [TFDV](https://github.com/tensorflow/data-validation) and + [TFT](https://github.com/tensorflow/transform) compute some statistics + over the data. + * [TFMA](https://github.com/tensorflow/model-analysis) may slice the + dataset by certain columns in the data. +* it may feed the data to TensorFlow. Note that feeding TF alone may not + require understanding the data. For example, TFMA may feed a TF model with + serialized tf.Example, which may be the raw form of the data. + +Currently, each TFX component has its own in-memory data representation to cover +the two use cases by a different approach: + +| | TFDV | TFT | TFMA | BulkInference | +|:---| :--- | :--- | :--- | :------------ | +| In-memory data representation | Arrow RecordBatches | Dict[str, np.ndarray] | str (raw data records), Dict[str, np.ndarray] | str (raw data records) | +| Understand the data and conduct analysis | input data is encoded losslessly as RecordBatches. | the in-mem representation may be lossy. | Relies on the model’s input layer, and the format is Dict[str, np.ndarray]. | N/A | +| Feed TF | N/A | the in-mem representation is TF feedable. | Feed “raw data” to the model. | Feed “raw data” to the model | + +This has created many issues: + +* Users of individual components need to adapt their data (if input format not + already supported) to each component they want to use. +* Individual components rely on unenforceable assumptions on how to interpret + the input data consistently. +* The complexity of adding new logical data representations (for example, + tf.SequenceExample) scales with the number of components. + +## The need for supporting new physical storage formats in TFX + +Two factors drive this need: + +* TFX needs to offer users more choices of the storage format, e.g, + [Apache Parquet](https://parquet.apache.org/). +* TFX wants to be able to choose the optimal storage format based on user’s + workload, in a user-transparent manner. A unified I/O abstraction would make + it easier to support a new physical format in TFX, since one would not have + to understand every single TFX component in order to implement such support. + +## TFX interoperability with the rest of the world + +If we choose a commonly available and adopted exchange format as our in-memory +representation, our users will be able to use TFX components with much less +effort on data conversion. This aligns with TFX’s long term vision. + +# User Benefit + +## TFX End Users + +While this change is transparent to end users, it will facilitate the design and +implementation of many user-facing features, for example: + +* Columnar storage format in TFX. +* Structured training examples. + +## Individual TFX component users + +We use TFXIO to refer to the proposed I/O abstraction layer. All TFX components +will start using TFXIO to ingest the data and have a unified way of representing +the data. Individual TFX component users would be able to implement TFXIO for +their own data formats / storage formats that are not supported by TFX. By +design, any such implementation will be readily accessible by all TFX +components. + +## TFX developers + +Developers working on TFX infrastructure will not have to understand the +internals of each component any more in order to make changes to I/O and parsing +(for example, adding support for a new storage format for the training +examples). + +Developers working on TFX components would benefit from sharing common +operations against the unified in-memory representation, or even higher-level +computations. For instance, suppose that we implement a sketch-based algorithm +to compute approximate heavy hitters over this in-memory representation. We can +now share this implementation inside both TFDV and TFT for their top-K feature +value computation. + +# Design Proposal + +This design proposes **a common in-memory data representation**, **a way to +translate that into TF feedables** (np.ndarray or EagerTensors) and **a set of +APIs** each component can use to get both. + +![alt_text](20191017-tfx-standardized-inputs/overview.png) + +## Common in-memory data representation + +[Apache Arrow](https://arrow.apache.org/) will be used as the common in-memory +data representation. Beam-based TFX components will accept +PCollection[pyarrow.[RecordBatch](https://arrow.apache.org/docs/python/data.html#record-batches)]. + +Each logical data format will have its own encoding convention, +[discussed](#logical-data-encoding-in-arrow) in the detailed design. + +We chose Apache Arrow because: + +* It’s Expressive enough. + * Lossless encoding of (conformant) tf.Example, tf.SequenceExample + * Can encode structured data (proto) +* It’s a columnar format. It works well with common TFX workloads: + * Column (feature)-wise analysis + * Feed a batch of columns (features) to TensorFlow. +* It’s OSS friendly. + * Community support for more storage format I/O (e.g. Apache Parquet) + * Friendly to other OSS data formats, both in-memory and on disk (e.g. + Pandas) + * Friendly to numpy / TF: many Arrow array types share the same memory + layout with numpy ndarrays and certain type of TF (composite) Tensors. +* TF neutral. + * Leaves the possibility of supporting other ML libraries open. + +## Translation from Arrow to TF feedables + +The analogy to this is parsing tf.Examples into TF feedables -- extra +information is needed in this translation because a +[`Feature`](https://github.com/tensorflow/tensorflow/blob/abfba15cd9734cec7ecd3d0661b146fc251c842d/tensorflow/core/example/feature.proto#L76) +can be converted to a Tensor, a SparseTensor or a +[RaggedTensor](https://www.tensorflow.org/guide/ragged_tensor) depending on the +[feature specs](https://github.com/tensorflow/tensorflow/blob/635e23a774936b5fe6fa3ef3cb6e54b55d93f324/tensorflow/python/ops/parsing_ops.py#L46-L49). +Currently this extra information is implicitly contained in the pipeline schema +(an instance of the +[TFMD Schema](https://github.com/tensorflow/metadata/blob/master/tensorflow_metadata/proto/v0/schema.proto)) +proto. + +Similarly, an Arrow column can be translated to various TF feedables. +[An extension to the pipeline schema](#tensorrepresentation) is proposed to for +a user to express the intention for conversion. + +The conversion can be efficient (zero-copy) in certain cases. It is +[discussed](#efficient-arrow-tensor-conversion) in the detailed design. + +## Standardized Inputs APIs + +We propose a set of APIs that TFX components will call, and need to be +implemented for each of the supported combination of {physical, logical} format. + +```py +class TFXIO(object): + """Abstract basic class of all Standardized TFX inputs API implementations.""" + def __init__( + self, pipeline_env, + schema: Optional[tfmd.Schema]=None + ): + pass + + @abc.abstractmethod + def BeamSource(self, + projections: Optional[List[ColumnName]]=None + ) -> beam.PTransform: + """Returns a beam PTransform that produces PCollection[pa.RecordBatch]. + + May NOT raise an error if the TFMD schema was not provided at construction time. + + Args: + specified number of rows. Otherwise, beam will try to adjust the batch + size automatically. + projections: if not None, only the specified subset of columns will be + read. + """ + + @abc.abstractmethod + def TensorAdapter(self) -> TensorAdapter: + """Returns a TensorAdapter that converts pa.RecordBatch to TF inputs. + + May raise an error if the TFMD schema was not provided at construction time. + """ + + @abc.abstractmethod + def ArrowSchema(self) -> pyarrow.Schema: + """Returns the schema of the Arrow RecordBatch generated by BeamSource(). + + May raise an error if the TFMD schema was not provided at construction time. + """ + + @abc.abstractmethod + def TFDataset(self, ...) -> tf.data.Dataset: + """Returns a Dataset of TF inputs. + + May raise an error if the TFMD schema was not provided at construction time. + """ +``` + +Where `TensorAdapter` is: + +```py +class TensorAdapter(object): + + def __init__( + self, + tensor_representations: Dict[str, TensorRepresentation]): + """Initializer. + + Args: + tensor_representations: keys are the names of the output tensors; values + describe how an output tensor should be derived from a RecordBatch. See + this section for details. + """ + pass + + def TypeSpecs(self) -> Dict[str, tf.TypeSpec]: + """Returns tf.TypeSpec for each tensor to be produced by ToBatchTensors(). + + TypeSpecs can be used to construct placeholders or tf.function signatures. + """ + + def ToBatchTensors( + self, record_batch: pyarrow.RecordBatch, + projections: Optional[List[TensorName]]=None + ) -> Dict[str, TFFeedable]: # TFFeedable: np.ndarrays or tf.EagerTensor + # (or compositions of them, i.e. + # CompositeTensors). + """Converts a record batch to batched tensors. + + Each will conform to the corresponding TypeSpec. + + Args: + projections: if not None, only specified subset of tensors will be + converted. + """ +``` + +Note that we will provide a default implementation of `TensorAdapter`, but TFXIO +implementations can implement their own `TensorAdapter`. A custom +`TensorAdapter` would allow a `TFXIO` implmentation to rely on a TF graph to +do parsing -- the same graph can be used in both `BeamSource` and +`TensorAdapter`. + +# Detailed Design + +## Logical data encoding in Arrow + +On a high level, a batch of logical entities (“examples”) is encoded into a +[`pyarrow.RecordBatch`](https://arrow.apache.org/docs/python/generated/pyarrow.RecordBatch.html#pyarrow.RecordBatch). +Features or fields (from structured records) are encoded as columns in the +RecordBatch. + +Note that +[`pyarrow.Table`](https://arrow.apache.org/docs/python/data.html#tables) offers +an abstraction similar to RecordBatch with the key difference being that a +column in a Table might contain multiple chunks of contiguous memory regions +while a column in a RecordBatch contains only one chunk. RecrodBatch is chosen +because we want to enforce that TFXIO implementations produce batched data in +the most efficient way (one chunk per batch). Users of TFXIO may construct a +Table from one or more RecordBatches since easy conversion from one to the other +is supported by Apache Arrow. + +This design aims to support the logical structure of tf.Example, +tf.SequenceExample or structured data like Protocol Buffers. Thus only a subset +of Arrow array types are needed. All TFX components will guarantee to understand +those types, but no more. Below is a summary of supported encodings: + +| Logical representation | Arrow encoding | +| :--------------------- | :------------- | +| Feature with no value | `NullArray` | +| Univalent feature (one value per example) | `FixedSizeListArray` (list_size = 1) | +| Multivalent feature (multiple values per example) | `[FixedSize]ListArray` | +| Sequence feature (list of lists of values per example) | `[FixedSize]ListArray<[FixedSize]ListArray>` | +| Proto-like structured data | `ListArray}>>` | + +However the design is flexible to support more complicated logical structures, +for example, k-nested sequences (tf.SequenceExample is 2-nested). + +Next we show that these encodings cover the logical data formats we aim to +support: + +### tf.Example + +[Conformant](https://github.com/tensorflow/tensorflow/blob/abfba15cd9734cec7ecd3d0661b146fc251c842d/tensorflow/core/example/example.proto#L78) +tf.Examples are assumed. I/O + parsing should throw an error upon non-conformant +instances. + +A key requirement derived from the conformant-ness is for the encoding to be +able to distinguish the following two cases: + +* a feature is present, but it’s value list is empty + + ``` + { + features { + "my_feature": { + bytes_list { + } + } + } + ``` + +* a feature is not present + + ``` + { + features { + } + } + ``` + + or + + ``` + { + features { + "my_feature": {} # none of the oneof is set + } + } + ``` + +Each feature can be encoded as: + +``` +[FixedSize]ListArray +``` + +Then, the feature value in case a) is encoded as an empty sub-list, while the +feature value in case b) is encoded as null. + +If we know that all the lists in a `ListArray` are of equal length (from the +schema of the data, see below sections), `FixedSizeListArray` can be used to +obviate the `O(N)` space overhead for lengths of lists. + +### tf.SequenceExample + +[Conformant](https://github.com/tensorflow/tensorflow/blob/abfba15cd9734cec7ecd3d0661b146fc251c842d/tensorflow/core/example/example.proto#L184) +tf.SequenceExamples are assumed. I/O + parsing should throw an error upon +non-conformant instances. + +A context feature will be encoded similarly to a feature in tf.Example. A +sequence feature will be encoded as: + +``` +[FixedSize]ListArray<[FixedSize]ListArray> +``` + +To avoid name conflicts with context features, all the sequence features can be +grouped into one `StructArray`: + +``` +StructArray<{'sequence_feature1': ListArray>, ...}> +``` + +### Structured data (e.g. Protocol Buffers / Apache Avro) + +A batch of structured records can be encoded as follows: + +* Each direct leaf field of the structure can be encoded similarly to + tf.Example. (`ListArray` of primitive types). +* Each sub-message can be encoded as: + + ``` + ListArray>> + ``` + +## Arrow to TF Feedable conversion + +### TensorRepresentation + +One or more Arrow columns can potentially be converted to multiple types of TF +feedables. + +For example, a `ListArray` can be converted to: + +* a Tensor, if given a default value to pad +* a SparseTensor to represent a ragged array +* a RaggedTensor + +The choice depends on user’s intents, which currently is +[implicitly](https://github.com/tensorflow/transform/blob/11afcff467f779ba6163686395582e69603987d1/tensorflow_transform/tf_metadata/schema_utils.py#L172) +expressed in the pipeline schema. + +We propose to create a new [TFMD](https://github.com/tensorflow/metadata) +(TensorFlow MetaData) Proto, `TensorRepresentation` to carry those intents implicitly: + +```protobuf +message TensorRepresentation { + oneof { + DenseTensor { … } // column_name, dtype, shape, default_value + VarLenSparseTensor { … } // column_name, dtype + SparseTensor { } // dtype, value_column_name, indice_column_names + VarLenRaggedTensor { … } // dtype + RaggedTensor { } // dtype, value_column_name, row_partition_column_names, ... + StructuredTensor { } // column_names + } +} +``` + +This proto is used in two places: + +* It’s part of TFMD schema: + + ```protobuf + message TensorRepresentationGroup { + map tensor_representation = 2; + }; + + message Schema { + repeated Feature feature = 1; + // … + map tensor_representation_group = 42; + } + ``` + + Note : + + * `TensorRepresentationGroup` allows different instances of one TFX + component use different sets of `TensorRepresentation`s. + * `tensor_representation_group` is **optional**. If the user does not + specify any, a default representation will be derived from + schema.feature to keep backwards compatibility. + * this field is **not** a sub-message of Schema::Feature, because a TF + feedable may comprise multiple columns + + Being part of the schema makes it possible to serialize and materialize the + intents for other components to use, which allows TFT’s materialization + functionality to have its own TFXIO implementation that hides the + data/physical format from the user. + + When generating the initial schema from the statistics of the data, TFDV can + propose a default set of `TensorRepresentationGroup`. The user may revise + the proposal and TFDV can validate `TensorRepresentationGroup`s in a + continuous manner. + +* The default implementation of TensorAdapter takes an optional `Dict[str, + TensorRepresentation]` at construction time. If a TFXIO implementation + choose to use the default TensorAdapter, it needs to provide them (may come + directly from the Schema). + +### Efficient Arrow->Tensor conversion + +The key to efficient conversions is to avoid copying of data. The prerequisites +to do so are: + +* Same memory alignment +* Same memory layout + +Currently 64-byte alignment is the standard in both Tensorflow's `TensorBuffer` +and Apache Arrow's `Buffer`. Forthermore, it can be guaranteed by implementing +our own version of `arrow::MemoryPool` that is backed by a +`tensorflow::Allocator`. + +The memory layout will be the same if right types are chosen at both ends thus +zero-copy conversion can be done, for example: + +* `FixedLengthListArray` (or `ListArray` of equal-length lists) -> dense + Tensors. +* `ListArray>` -> + [RaggedTensors](https://github.com/tensorflow/tensorflow/blob/3c2dabf53dd085c21e38a28b467e52c566c0dfaf/tensorflow/python/ops/ragged/ragged_tensor.py#L1). +* `ListArray>` -> + [StructuredTensors](https://github.com/tensorflow/community/blob/master/rfcs/20190910-struct-tensor.md) + +In other cases, copies can be avoided for the values, but some computation is +needed: + +* `ListArray>` -> `tf.SparseTensor` + * Need to compute the sparse indices from `ListArray`'s list offsets. + +The rest cases require a copy: + +* `ListArray>`(of non-equal-length lists) -> dense Tensors + +With TensorRepresentation available in the Schema, a TFXIO implementation may +optimize its decoder to choose the most efficient Arrow type. + +#### Conversion of string features + +Arrow’s string arrays (`BinaryArray`) have a different memory layout than +TensorFlow’s string Tensors, even with +[`tensorflow::tstring`](https://github.com/tensorflow/community/blob/master/rfcs/20190411-string-unification.md). +There is always some overhead in conversion, but with `tensorflow::tstring` a +Tensor of `string_view`s is possible, thus the overhead will be a function of +the number of strings being converted, instead of the lengths of the strings. + +#### TF APIs for conversions + +In TF 1.x we will use np.ndarray as a bridge as Arrow has zero-copy conversion +to numpy’s ndarrays. (not for string arrays). + +Starting from TF 2.x, we will be able to create EagerTensors from Python +memoryview(s) so that strings can be covered. + +## TFMD Schema + +[The TFMD Schema](https://github.com/tensorflow/metadata/blob/master/tensorflow_metadata/proto/v0/schema.proto) +is a pipeline-level artifact and in the scope of this proposal, it may serve two +purposes: + +* To provide optional inputs to the parsing logic for optimizations. +* To carry user’s intents of converting data to TF feedables. + +The two purposes don’t have to be served in the following cases: + +* TFDV should not require a schema to work and it does not need TF feedables. +* Some TFXIO implementation may not need the schema for either purposes. + +Therefore the TFMD schema is optional, and a TFXIO implementation: + +* should guarantee that the `BeamSource()`can return a valid + `PCollection[RecordBatch]` without a schema. + * Other interfaces may raise an error when a schema was not provided. +* does not have to require a TFMD schema for all its interfaces to work. + +## (TensorFlow) Trainer integration + +For TFX to freely choose the storage format for training examples for a user, we +**cannot** expose file-based or record-based interface to that user in the TF +trainer, because: + +* the user might not know how to open those files. +* there might not be an efficient representation of a “record” (this is true + for columnar storage formats like Apache Parquet) but only an efficient + representation of a batch of records. + +Thus we propose that to most users, the TF Trainer only exposes a handle to a +`tf.data.Dataset` of parsed (composite) Tensors. + +Each `TFXIO` implementation will implement a `TFDataset()` interface to return +such a `tf.data.Dataset`. This dataset contains logically a set of batched +(composite) Tensors that are of the same type as the corresponding +`TensorAdapter()` would return for a `RecordBatch`. See +[this section](#recommended-way-of-implementing-a-tfxio) about how to minimize +the code needs to be written for a new `TFXIO` implementation. + +The `TFDataset()` interface will accept common knobs that a user may need to +tweak: + +* Batch size +* Random shuffle + +## Code organization and OSS + +### `tfx_bsl` package + +TFXIO will be used by all TFX components as well as the TFX framework, making it +be almost at the bottom of the dependency chain. Moreover, a lot of +implementations details will be in C++, with python wrapping around, and we want +to make sure our TFX components pip packages remain pure Python for easy +maintenance. Therefore we propose a new python package +[tfx_bsl](https://github.com/tensorflow/tfx-bsl) (TFX Shared Basic Libraries) to +contain the implementations of `TFXIO` and other libraries shared across TFX +components. + +![alt_text](20191017-tfx-standardized-inputs/oss_lib_org.png) + +## Recommended way of implementing a TFXIO + +To maximize code sharing, the following way of implementing a `TFXIO` is +suggested: + +![alt_text](20191017-tfx-standardized-inputs/impl_tfxio.png) + +One would only need to implement the IO+Parsing-to-arrow in C++ once, and reuse +it in the BeamSource() and a format-specific Dataset Op that produces a +DT_VARIANT tensor that points to the parsed Arrow RecordBatch. Then we provide +one C++ library that translates the Arrow RecordBatch to Tensors, which can also +be reused in a TF op (as the downstream of the Dataset, or in a Python wrapper). + +# Alternatives Considered + +We’ve considered an alternative where +[**StructuredTensor**](https://github.com/tensorflow/community/blob/master/rfcs/20190910-struct-tensor.md) +is the unified in-memory representation, and **tf.Data** is the unified I/O +abstraction. + +StructuredTensor is equally powerful as Arrow’s StructArray so it’s able to +represent all our logical representations. + +Because StructuredTensor is a CompositeTensor, we could imagine that I/O + +parsing of a logical data format in a physical storage format is handled by a +specific tf.Data Dataset that yields StructuredTensors. + +We also need to be able to construct a beam source from such a Dataset (although +there is a non-trivial gap). + +The advantages of this approach are: + +* Less effort to integrate: adding a new format == adding a new tf.Dataset. +* Unified story across TF and beam. +* No third-party dependencies. + * Note that we can still provide an Arrow -> StructuredTensor adapter to + achieve interoperability. + +What this alternative does not change / address: + +* We still need the TensorAdapter API to convert from StructuredTensor to + other TF feedables, unless we are willing to only offer StructuredTensors to + our end-users (which might be the case eventually, but not likely to happen + soon). So a good portion of this proposal will remain mostly unchanged. +* We still cannot expose file-based or record-based interface to end-users in + the TF trainer. As that is a direct result of I/O + parsing being abstracted + out. + +The disadvantages of this approach are: + +* This tightly couples TF with TFX components, in the following ways: + + * Components will need TF to read the data in. + * Components that analyze the data (e.g. TFDV) will operate against + StructuredTensors, and the easiest way to to conduct certain + computations (for example, slicing a StructuredTensor or computing the + mean) with StructuredTensors is through TF ops and their python + bindings. + + Such a coupling is not in our favor because: + + * Some TFX components functionally do not require TF to work. For example, + TFDV can analyze any data set. TFMA can analyze a model while treating + the model as a blackbox. In both cases, the ML library that trained the + model is irrelevant and TF should not be assumed. And coupling with TF + does not only introduce a heavy dependency, but also forces the user to + learn about TF if they need to implement a TFXIO for their data format. + + * Operations against StructuredTensors boils down to Python + TF ops. The + overhead of either is much higher than just calling an Arrow C++ API + that does the same operation. + + * The main extension point of TF is Ops which don't understand the nested + structures. Compared to using Arrow’s C++ APIs, implementing an Op that + deals with StructuredTensors will be much complicated. + +# Questions and Discussion Topics + +## OSS build / release issues + +### We don’t have a strong representation in the Arrow community + +This has led to some issues. For example, the PyPI/Wheel packaging for pyarrow +currently is unfunded and lacks volunteers and there is a risk of support being +dropped, but we do rely on the pyarrow wheel as TFX is released on PyPI. + +### ABI compatibility with libarrow + +The OSS library, tfx_bsl will depend on Arrow and TensorFlow’s DSOs (dynamic +shared objects). Because both libraries currently expose C++ APIs, there are +always risks of incompatible ABIs as TensorFlow and Arrow are likely to be built +using different toolchains, we cannot completely eliminate the risks. + +With [Modular TensorFlow](https://github.com/tensorflow/community/pull/77), +which replaced all the C++ APIs with C-APIs, we will be able to eliminate the +risk by using the same toolchain that builds Arrow. + +Furthermore, the Apache Arrow community is discussing about an +[ABI-stable C-struct](https://github.com/apache/arrow/pull/5442) that describes +Arrow Arrays. This will allow to build Apache Arrow from source and link +statically with our code, and only talk with pyarrow through that ABI-stable +interface. + +Since in Google we build everything from HEAD, using the same toolchain, there +are no risks. + +## Performance + +We would have to convert from Apache Arrow dataframes to TF feedables / Tensors. +Sometimes this conversion cannot happen efficiently (requires copying out the +data or other computation). diff --git a/rfcs/20191017-tfx-standardized-inputs/impl_tfxio.png b/rfcs/20191017-tfx-standardized-inputs/impl_tfxio.png new file mode 100644 index 0000000000000000000000000000000000000000..7309b56ccf643ce8cb98d9537e6ae0a06dac951a GIT binary patch literal 51239 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 literal 0 HcmV?d00001 diff --git a/rfcs/20191017-tfx-standardized-inputs/oss_lib_org.png b/rfcs/20191017-tfx-standardized-inputs/oss_lib_org.png new file mode 100644 index 0000000000000000000000000000000000000000..1e706c8da871e79941da5446428d82620eccd789 GIT binary patch literal 49047 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 Womqz+aTKgPb==G6?{p9JrT+jl)_$M> literal 0 HcmV?d00001 From b9be95c7672e9c00d8f93c161a672e2a893515aa Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Thu, 3 Oct 2019 10:09:18 -0700 Subject: [PATCH 2/8] updated the RFC number --- rfcs/20191017-tfx-standardized-inputs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md index a39b2ede1..c35abdb95 100644 --- a/rfcs/20191017-tfx-standardized-inputs.md +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -4,7 +4,7 @@ Status | Proposed :------------ | :------------------------------------------------------------ -**RFC #** | [NNN](https://github.com/tensorflow/community/pull/NNN) +**RFC #** | [162](https://github.com/tensorflow/community/pull/162) **Author(s)** | Zhuo Peng (zhuo@google.com), Kester Tong (kestert@google.com) **Sponsor** | Konstantinos Katsiapis (katsiapis@google.com) **Updated** | 2019-10-03 From 75ea8f386bd7e6d10e158b5ecc4da090ad2ca4a3 Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Thu, 3 Oct 2019 10:19:08 -0700 Subject: [PATCH 3/8] small fixes --- rfcs/20191017-tfx-standardized-inputs.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md index c35abdb95..ca5a2b9a9 100644 --- a/rfcs/20191017-tfx-standardized-inputs.md +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -186,15 +186,13 @@ class TFXIO(object): @abc.abstractmethod def BeamSource(self, - projections: Optional[List[ColumnName]]=None + projections: Optional[List[Text]]=None ) -> beam.PTransform: """Returns a beam PTransform that produces PCollection[pa.RecordBatch]. May NOT raise an error if the TFMD schema was not provided at construction time. Args: - specified number of rows. Otherwise, beam will try to adjust the batch - size automatically. projections: if not None, only the specified subset of columns will be read. """ @@ -208,10 +206,10 @@ class TFXIO(object): @abc.abstractmethod def ArrowSchema(self) -> pyarrow.Schema: - """Returns the schema of the Arrow RecordBatch generated by BeamSource(). + """Returns the schema of the Arrow RecordBatch generated by BeamSource(). - May raise an error if the TFMD schema was not provided at construction time. - """ + May raise an error if the TFMD schema was not provided at construction time. + """ @abc.abstractmethod def TFDataset(self, ...) -> tf.data.Dataset: @@ -233,8 +231,7 @@ class TensorAdapter(object): Args: tensor_representations: keys are the names of the output tensors; values - describe how an output tensor should be derived from a RecordBatch. See - this section for details. + describe how an output tensor should be derived from a RecordBatch. """ pass @@ -279,7 +276,7 @@ Note that [`pyarrow.Table`](https://arrow.apache.org/docs/python/data.html#tables) offers an abstraction similar to RecordBatch with the key difference being that a column in a Table might contain multiple chunks of contiguous memory regions -while a column in a RecordBatch contains only one chunk. RecrodBatch is chosen +while a column in a RecordBatch contains only one chunk. RecordBatch is chosen because we want to enforce that TFXIO implementations produce batched data in the most efficient way (one chunk per batch). Users of TFXIO may construct a Table from one or more RecordBatches since easy conversion from one to the other @@ -441,7 +438,7 @@ This proto is used in two places: Note : * `TensorRepresentationGroup` allows different instances of one TFX - component use different sets of `TensorRepresentation`s. + component to use different sets of `TensorRepresentation`s. * `tensor_representation_group` is **optional**. If the user does not specify any, a default representation will be derived from schema.feature to keep backwards compatibility. @@ -492,7 +489,7 @@ needed: * `ListArray>` -> `tf.SparseTensor` * Need to compute the sparse indices from `ListArray`'s list offsets. -The rest cases require a copy: +The remaining cases require a copy: * `ListArray>`(of non-equal-length lists) -> dense Tensors From d679dfdf4cc80d8c508656d612cca9f1268bf645 Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Tue, 8 Oct 2019 16:34:27 -0700 Subject: [PATCH 4/8] more fixes and addressed several comments --- rfcs/20191017-tfx-standardized-inputs.md | 27 ++++++++++++++++-------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md index ca5a2b9a9..471a4ede6 100644 --- a/rfcs/20191017-tfx-standardized-inputs.md +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -179,7 +179,7 @@ implemented for each of the supported combination of {physical, logical} format. class TFXIO(object): """Abstract basic class of all Standardized TFX inputs API implementations.""" def __init__( - self, pipeline_env, + self, schema: Optional[tfmd.Schema]=None ): pass @@ -226,17 +226,20 @@ class TensorAdapter(object): def __init__( self, + arrow_schema: pyarrow.Schema, tensor_representations: Dict[str, TensorRepresentation]): """Initializer. Args: + arrow_schema: the schema of the RecordBatches this adapter is to receive + in ToBatchTensors(). tensor_representations: keys are the names of the output tensors; values describe how an output tensor should be derived from a RecordBatch. """ - pass + def TypeSpecs(self) -> Dict[str, tf.TypeSpec]: - """Returns tf.TypeSpec for each tensor to be produced by ToBatchTensors(). + """Returns tf.TypeSpec for each tensor in `tensor_representation`. TypeSpecs can be used to construct placeholders or tf.function signatures. """ @@ -247,13 +250,14 @@ class TensorAdapter(object): ) -> Dict[str, TFFeedable]: # TFFeedable: np.ndarrays or tf.EagerTensor # (or compositions of them, i.e. # CompositeTensors). - """Converts a record batch to batched tensors. + """Converts a RecordBatch to batched TFFeedables per `tensor_representation` - Each will conform to the corresponding TypeSpec. + Each will conform to the corresponding TypeSpec / TensorRepresentation. Args: - projections: if not None, only specified subset of tensors will be - converted. + projections: a set of names of TFFeedables (mentioned in + `tensor_representation`). If not None, only TFFeedables of those names + will be converted. """ ``` @@ -263,6 +267,11 @@ implementations can implement their own `TensorAdapter`. A custom do parsing -- the same graph can be used in both `BeamSource` and `TensorAdapter`. +The default `TensorAdapter` can be constructed out of the Arrow schema (which +is required for any TFXIO implementation) and `TensorRepresentations`. The +latter is part of the TFMD schema. See [this section](#tensorrepresentation) +for details. + # Detailed Design ## Logical data encoding in Arrow @@ -659,8 +668,8 @@ The disadvantages of this approach are: ### We don’t have a strong representation in the Arrow community This has led to some issues. For example, the PyPI/Wheel packaging for pyarrow -currently is unfunded and lacks volunteers and there is a risk of support being -dropped, but we do rely on the pyarrow wheel as TFX is released on PyPI. +currently is unfunded and lacks volunteers, and the pyarrow wheel sometimes had +issues with TensorFlow ([example](https://github.com/apache/arrow/issues/4472)). ### ABI compatibility with libarrow From c47a0fe60838abf78e5f0be14abe645b129ec036 Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Thu, 10 Oct 2019 08:38:36 -0700 Subject: [PATCH 5/8] clarify that StrcuturedTensor cannot encode null values --- rfcs/20191017-tfx-standardized-inputs.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md index 471a4ede6..6931f8dd0 100644 --- a/rfcs/20191017-tfx-standardized-inputs.md +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -605,8 +605,10 @@ We’ve considered an alternative where is the unified in-memory representation, and **tf.Data** is the unified I/O abstraction. -StructuredTensor is equally powerful as Arrow’s StructArray so it’s able to -represent all our logical representations. +StructuredTensor is almost equally powerful as Arrow’s StructArray, except +that it cannot encode null values (i.e. a missing value cannot be distinguished +from a null value). The discussion below assumes we extend StructuredTensor so +that nulls can be encoded. Because StructuredTensor is a CompositeTensor, we could imagine that I/O + parsing of a logical data format in a physical storage format is handled by a From c4329df079904a7b385aa6a08698265f62815968 Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Mon, 28 Oct 2019 15:55:43 -0700 Subject: [PATCH 6/8] revised the RFC to add more background and better comparison against the alternatives --- rfcs/20191017-tfx-standardized-inputs.md | 246 +++++++++++------- .../double-translation.png | Bin 0 -> 27791 bytes 2 files changed, 145 insertions(+), 101 deletions(-) create mode 100644 rfcs/20191017-tfx-standardized-inputs/double-translation.png diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md index 6931f8dd0..cd54a2423 100644 --- a/rfcs/20191017-tfx-standardized-inputs.md +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -31,25 +31,19 @@ Status | Proposed # Motivation -## Fragmented in-memory data representations across TFX - -A TFX component may use its input data (data generated by ExampleGen) in two -ways: - -* it may need to understand the data and conduct analysis. Usually this - happens in [Apache Beam](https://beam.apache.org/), and does not involve a - TF Model. For example: - * [TFDV](https://github.com/tensorflow/data-validation) and - [TFT](https://github.com/tensorflow/transform) compute some statistics - over the data. - * [TFMA](https://github.com/tensorflow/model-analysis) may slice the - dataset by certain columns in the data. -* it may feed the data to TensorFlow. Note that feeding TF alone may not - require understanding the data. For example, TFMA may feed a TF model with - serialized tf.Example, which may be the raw form of the data. - -Currently, each TFX component has its own in-memory data representation to cover -the two use cases by a different approach: +## Interoperability of TFX libraries +TFX offers a portfolio of libraries, including +[TFT](https://github.com/tensorflow/transform), +[TFDV](https://github.com/tensorflow/data-validation) and +[TFMA](https://github.com/tensorflow/model-analysis). These libraries can be +used in a standalone manner (i.e. a user can run TFDV without/outside TFX) +while on the other hand, one TFX component (a program orchestrated by TFX) may +use multiple TFX libraries under the hood. For example, there could be a +TFX component that transforms the training data by invoking TFT and +collects pre-transform and post-transform statistics of the data by invoking +TFDV. + +Currently, each TFX library accepts different in-memory data representation: | | TFDV | TFT | TFMA | BulkInference | |:---| :--- | :--- | :--- | :------------ | @@ -57,31 +51,55 @@ the two use cases by a different approach: | Understand the data and conduct analysis | input data is encoded losslessly as RecordBatches. | the in-mem representation may be lossy. | Relies on the model’s input layer, and the format is Dict[str, np.ndarray]. | N/A | | Feed TF | N/A | the in-mem representation is TF feedable. | Feed “raw data” to the model. | Feed “raw data” to the model | -This has created many issues: - -* Users of individual components need to adapt their data (if input format not - already supported) to each component they want to use. -* Individual components rely on unenforceable assumptions on how to interpret - the input data consistently. -* The complexity of adding new logical data representations (for example, - tf.SequenceExample) scales with the number of components. +When a TFX component needs to invoke multiple TFX libraries, it may need to +decode the data into one of the libraries’ in-memory representations, +and translate that into the other library’s. For example, in the “Transform” +component mentioned above: + +![alt_text](20191017-tfx-standardized-inputs/double-translation.png) + +Note that TFDV is invoked twice, with different data. Thus the translation needs +to happen twice. + +This has created several issues: + +* The translation is computationally expensive. In a real world set-up, such + translation could take as many CPU cycles as the core TFT and TFDV logic + takes in total. +* More such translation logic may need to be implemented to support expanding + TFX use cases -- imagine that TFMA needs to invoke TFDV to compute + statistics over slices of data identified by model evaluation. +* The complexity of adding new logical data representations scales with the + number of components. For example, to add SequenceExample support in TFX, + one may need to come up with an in-memory representation for each of the + components, to keep the consistency within the component. +* TFX library users whose data format is not supported natively by TFX would + have to implement the decoding logic for each of the libraries + they want to use. For example, had TFX not supported the CSV format, a user + would have to implement + [one CSV decoder for TFT](https://github.com/tensorflow/transform/blob/master/tensorflow_transform/coders/csv_coder.py), + and [another CSV decoder for TFDV](https://github.com/tensorflow/data-validation/blob/master/tensorflow_data_validation/coders/csv_decoder.py) + +A common in-memory data representation would address the issues. ## The need for supporting new physical storage formats in TFX -Two factors drive this need: - -* TFX needs to offer users more choices of the storage format, e.g, - [Apache Parquet](https://parquet.apache.org/). -* TFX wants to be able to choose the optimal storage format based on user’s - workload, in a user-transparent manner. A unified I/O abstraction would make - it easier to support a new physical format in TFX, since one would not have - to understand every single TFX component in order to implement such support. +Currently TFX (mostly) assumes tf.Example on TFRecord. However because TFX is a +managed environment, it is desired that its choice of physical format of data is +an implementation detail and is opaque to the components and the users. TFX +would like to explore switching to a columnar physical format like Apache +Parquet and it can be imagined that there will be a migration at some point. +Such a migration must happen in either of the following two ways: -## TFX interoperability with the rest of the world +* Change every single TFX library and component that needs to read data to + add support for reading the new physical format (into each library’s own + in-memory representation) +* Rely on an indirection through tf.Example and give up some performance + because of the translation. -If we choose a commonly available and adopted exchange format as our in-memory -representation, our users will be able to use TFX components with much less -effort on data conversion. This aligns with TFX’s long term vision. +Beyond easier migrations (which could arguably be one-time efforts), a good I/O +abstraction would allow TFX to choose the optimal storage format based on user’s +workload, in a user-transparent manner. # User Benefit @@ -116,6 +134,47 @@ to compute approximate heavy hitters over this in-memory representation. We can now share this implementation inside both TFDV and TFT for their top-K feature value computation. +# Requirements + +The in-memory representation should: + +* Be columnar. + + A columnar in-memory representation works better than a row based on under + typical workload of TFX libraries: + + Compute statistics over a column. + + Feed a batch of rows to TensorFlow. + +* Be able to losslessly encode the logical data format. Specifically, it + should be able to distinguish a null (unpopulated) value from an empty list. + + TFDV produces distinct statistics for nulls and empty lists. + +* Provide for efficient integration with TF computation. Ideally, using the + in-memory representation with TensorFlow should not require a data copy. + + Feeding TF is a significant TFX workload, both in terms of CPU cycles and + number of examples processed. + +* Have efficient Python APIs to slice, filter and concatenate. Or more + generally, have efficient Python APIs for data analysis type of workload. + + For example, TFMA may group the examples by “geo_location” and “language” + and evaluate the model for each of the slices. This operation would require + efficient slicing a batch of examples, and concatenation of slices that + belong to the same slice key (because the slices could be very small, and + inefficient for feeding TF). TFDV has similar use cases where statistics of + a certain slice of data needs to be collected. + + This type of workload is also significant in TFX, both in terms of CPU + cycles and number of examples processed. + +* Be interoperable with the rest of the world. + + The OSS world should be able to use TFX components with little effort on + data conversion. This aligns with TFX’s long term vision. + + # Design Proposal This design proposes **a common in-memory data representation**, **a way to @@ -600,68 +659,53 @@ be reused in a TF op (as the downstream of the Dataset, or in a Python wrapper). # Alternatives Considered -We’ve considered an alternative where -[**StructuredTensor**](https://github.com/tensorflow/community/blob/master/rfcs/20190910-struct-tensor.md) -is the unified in-memory representation, and **tf.Data** is the unified I/O -abstraction. - -StructuredTensor is almost equally powerful as Arrow’s StructArray, except -that it cannot encode null values (i.e. a missing value cannot be distinguished -from a null value). The discussion below assumes we extend StructuredTensor so -that nulls can be encoded. - -Because StructuredTensor is a CompositeTensor, we could imagine that I/O + -parsing of a logical data format in a physical storage format is handled by a -specific tf.Data Dataset that yields StructuredTensors. - -We also need to be able to construct a beam source from such a Dataset (although -there is a non-trivial gap). - -The advantages of this approach are: - -* Less effort to integrate: adding a new format == adding a new tf.Dataset. -* Unified story across TF and beam. -* No third-party dependencies. - * Note that we can still provide an Arrow -> StructuredTensor adapter to - achieve interoperability. - -What this alternative does not change / address: - -* We still need the TensorAdapter API to convert from StructuredTensor to - other TF feedables, unless we are willing to only offer StructuredTensors to - our end-users (which might be the case eventually, but not likely to happen - soon). So a good portion of this proposal will remain mostly unchanged. -* We still cannot expose file-based or record-based interface to end-users in - the TF trainer. As that is a direct result of I/O + parsing being abstracted - out. - -The disadvantages of this approach are: - -* This tightly couples TF with TFX components, in the following ways: - - * Components will need TF to read the data in. - * Components that analyze the data (e.g. TFDV) will operate against - StructuredTensors, and the easiest way to to conduct certain - computations (for example, slicing a StructuredTensor or computing the - mean) with StructuredTensors is through TF ops and their python - bindings. - - Such a coupling is not in our favor because: - - * Some TFX components functionally do not require TF to work. For example, - TFDV can analyze any data set. TFMA can analyze a model while treating - the model as a blackbox. In both cases, the ML library that trained the - model is irrelevant and TF should not be assumed. And coupling with TF - does not only introduce a heavy dependency, but also forces the user to - learn about TF if they need to implement a TFXIO for their data format. - - * Operations against StructuredTensors boils down to Python + TF ops. The - overhead of either is much higher than just calling an Arrow C++ API - that does the same operation. - - * The main extension point of TF is Ops which don't understand the nested - structures. Compared to using Arrow’s C++ APIs, implementing an Op that - deals with StructuredTensors will be much complicated. +## StructuredTensor + +We’ve considered an alternative where [StructuredTensor](https://github.com/tensorflow/community/blob/master/rfcs/20190910-struct-tensor.md) +is the unified in-memory representation, but it does not meet all of the +requirements: + +| | Arrow RecordBatch | StructuredTensor | +| :-------------------------------- | :----------------- | :---------------- | +| Columnar | Yes | Yes | +| Lossless encoding (nullity) | Yes | No (see remark 1) | +| Efficient translatable to Tensors | Yes (see remark 2) | Yes | +| Efficient slicing, filtering and concatenation | Yes | No (see remark 3) | +| Interoperability with the rest of the world | Good through Apache Arrow | +Needs adaptation (see remark 4) | + +Remarks: + +1. We could revise the design of StructuredTensor to include the nullibility + support. +2. Only when the backing buffers are aligned correctly. Currently both TF + and Apache Arrow has 64-byte alignment. And this can be enforced by + implementing our own Arrow MemoryPool wrapping a TF allocator. +3. See the comparison in [this colab notebook](https://colab.sandbox.google.com/drive/1SCQs88J4Tc6HKk2AfpYvELaJDxa4h4IQ). + * It’s worth calling out that Arrow is meant to be a data analysis library + and better data analysis support (for example, support for a “group-by” + clause) will be added over time. +4. We may gain similar interoperability by creating an Arrow to + StructuredTensor adapter. + * Beyond the technical aspects, we believe by having all the TFX libraries + directly adopting a popular OSS in-memory format will send a positive + message that TFX is meant to work well with the rest of the world. + +## tf.Data + +We’ve also considered tf.Data as the unified I/O abstraction. tf.Data has +support for a good number of data formats through +[tensorflow-io](https://github.com/tensorflow/io/tree/master/tensorflow_io). + +It's faily straightforward to implement a Beam PSource that wraps a file-backed +tf.data DatasetSource, and have that PSource produce Arrow RecordBatches, +but due to lack of support for [dynamic work rebalancing](https://cloud.google.com/blog/products/gcp/no-shard-left-behind-dynamic-work-rebalancing-in-google-cloud-dataflow) in tf.data, such an implementation would +not match the performance of existing beam PSources. + +While we cannot rely solely on tf.data as the I/O abstraction, the proposed +TFXIO interface does not disallow such a tf.data based implementation. So we can +still gain support for many other formats through tensorflow-io while still +using existing beam PSources for formats that have native support. # Questions and Discussion Topics diff --git a/rfcs/20191017-tfx-standardized-inputs/double-translation.png b/rfcs/20191017-tfx-standardized-inputs/double-translation.png new file mode 100644 index 0000000000000000000000000000000000000000..a65d1d87f14f706dd3e480a035afa5cf0fddc6af GIT binary patch literal 27791 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 literal 0 HcmV?d00001 From 273c62a1943c6986afd47858d0b4abb7c488fc21 Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Mon, 28 Oct 2019 15:57:46 -0700 Subject: [PATCH 7/8] minor fix --- rfcs/20191017-tfx-standardized-inputs.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md index cd54a2423..0e848a4bf 100644 --- a/rfcs/20191017-tfx-standardized-inputs.md +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -671,8 +671,7 @@ requirements: | Lossless encoding (nullity) | Yes | No (see remark 1) | | Efficient translatable to Tensors | Yes (see remark 2) | Yes | | Efficient slicing, filtering and concatenation | Yes | No (see remark 3) | -| Interoperability with the rest of the world | Good through Apache Arrow | -Needs adaptation (see remark 4) | +| Interoperability with the rest of the world | Good through Apache Arrow | Needs adaptation (see remark 4) | Remarks: @@ -687,9 +686,9 @@ Remarks: clause) will be added over time. 4. We may gain similar interoperability by creating an Arrow to StructuredTensor adapter. - * Beyond the technical aspects, we believe by having all the TFX libraries - directly adopting a popular OSS in-memory format will send a positive - message that TFX is meant to work well with the rest of the world. + * Beyond the technical aspects, we believe by having all the TFX libraries + directly adopting a popular OSS in-memory format will send a positive + message that TFX is meant to work well with the rest of the world. ## tf.Data From 230783e5d9302b60420855e5ec6b9455ac9ef67b Mon Sep 17 00:00:00 2001 From: Zhuo Peng <1835738+brills@users.noreply.github.com> Date: Wed, 6 Nov 2019 15:07:31 -0800 Subject: [PATCH 8/8] replaced colab links with publicly accessible ones and clarify about TFX components does not always need to run TF. --- rfcs/20191017-tfx-standardized-inputs.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/rfcs/20191017-tfx-standardized-inputs.md b/rfcs/20191017-tfx-standardized-inputs.md index 0e848a4bf..596209540 100644 --- a/rfcs/20191017-tfx-standardized-inputs.md +++ b/rfcs/20191017-tfx-standardized-inputs.md @@ -32,7 +32,7 @@ Status | Proposed # Motivation ## Interoperability of TFX libraries -TFX offers a portfolio of libraries, including +TFX offers a portfolio of libraries, including [TFT](https://github.com/tensorflow/transform), [TFDV](https://github.com/tensorflow/data-validation) and [TFMA](https://github.com/tensorflow/model-analysis). These libraries can be @@ -169,6 +169,13 @@ The in-memory representation should: This type of workload is also significant in TFX, both in terms of CPU cycles and number of examples processed. + Note that TFX libraries don't always need to run TF graphs. For example, + TFDV, despite of its name, only analyzes the training data and (almost) does + not call any TF API. Another example, TFMA, will support "blackbox" + evaluation where the model being evaluated does not have to be a TF model. + Therefore a TF-neutral in-memory representation that works well with plain + Python code is desirable. + * Be interoperable with the rest of the world. The OSS world should be able to use TFX components with little effort on @@ -680,7 +687,10 @@ Remarks: 2. Only when the backing buffers are aligned correctly. Currently both TF and Apache Arrow has 64-byte alignment. And this can be enforced by implementing our own Arrow MemoryPool wrapping a TF allocator. -3. See the comparison in [this colab notebook](https://colab.sandbox.google.com/drive/1SCQs88J4Tc6HKk2AfpYvELaJDxa4h4IQ). + [This colab notebook](https://colab.research.google.com/drive/1bM8gso7c8x4UXx5htDM4N1KUSTuRvIFL) + shows that as long as the memory alignment is the same, feeding TF with an + Arrow Array has very little overhead. +3. See the comparison in [this colab notebook](https://colab.research.google.com/drive/1CvDjZCH3GQE8iojCmRHPuSqLTw8KgNf3). * It’s worth calling out that Arrow is meant to be a data analysis library and better data analysis support (for example, support for a “group-by” clause) will be added over time.