|
| 1 | +# Configurable File Systems |
| 2 | + |
| 3 | +| Status | Proposed | |
| 4 | +| :------------ | :-------------------------------------------------------------------------------------------- | |
| 5 | +| **RFC #** | [NNN](https://github.com/tensorflow/community/pull/NNN) (update when you have community PR #) | |
| 6 | +| **Author(s) ** | Sami Kama ( [email protected]) | |
| 7 | +| **Sponsor ** | Mihai Maruseac ( [email protected]) | |
| 8 | +| **Updated** | 2020-08-04 | |
| 9 | + |
| 10 | +## Objective |
| 11 | + |
| 12 | +The aim of this RFC to extend filesystem API to enable users to pass configuration parameters to tune the behavior of implementation to their use cases. |
| 13 | + |
| 14 | +## Motivation |
| 15 | + |
| 16 | +There are many FileSystem implementations in Tensorflow that enable interaction with various storage solutions. Most of these implementations have internal parameters that are suitable for generic use case but not necessarily optimal for all cases. For example accessing remote filesystems through multiple threads can improve the throughput if there is a high bandwidth connection to the remote thus increasing number of connections might be beneficial. On the other hand if the connection is slow, a higher number of threads will just waste resources and may even reduce the throughput. Depending on the resources available during the execution, users should be able to alter some of the parameters of the Filesystems to improve the performance of their execution. This can be especially useful for the the cases where the execution is data i/o bound. |
| 17 | + |
| 18 | +## User Benefit |
| 19 | + |
| 20 | +With this proposal users will be able to fine tune some parameters that developers expose through configuration API and get an improved perfomance for file i/o. |
| 21 | + |
| 22 | +## Design Proposal |
| 23 | + |
| 24 | +This proposal introduces two new methods to plugin api structure `TF_FilesystemOps` as shown below. |
| 25 | + |
| 26 | +```cpp |
| 27 | +struct TF_FilesystemOps{ |
| 28 | + // other members are ignored for brevity |
| 29 | + void (*const get_filesystem_configuration)(char** serialized_config, int *serialized_length, TF_Status* status); |
| 30 | + void (*const set_filesystem_configuration)(const char* serialized_config, int serialized_length, TF_Status* status); |
| 31 | +} |
| 32 | +``` |
| 33 | +
|
| 34 | +where `serialized_config` is a pointer to the buffer containing serialized human readable form of the protobuf object described below and `serialized_length` is the length of the buffer. |
| 35 | +
|
| 36 | +For non-plugin based filesystems, FileSystem API can be extended similarly. |
| 37 | +
|
| 38 | +```cpp |
| 39 | +class FileSystem{ |
| 40 | + public: |
| 41 | + // existing methods are not shown. |
| 42 | + Status GetConfiguration(std::unique_ptr<FilesystemConfig>* config); |
| 43 | + Status SetConfiguration(std::unique_ptr<FilesystemConfig> new_configuration); |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +Since each filesystem will likely to have different set of tunable parameters, a `FilesystemConfig` object can be used to unify the API and allow discovery of existing tunable parameters at runtime. We propose a protobuf object with the following schema |
| 48 | + |
| 49 | +```proto |
| 50 | +message FilesystemAttr{ |
| 51 | + message ListValue { |
| 52 | + repeated bytes s = 2; // "list(string)" |
| 53 | + repeated int64 i = 3 [packed = true]; // "list(int)" |
| 54 | + repeated float f = 4 [packed = true]; // "list(float)" |
| 55 | + repeated bool b = 5 [packed = true]; // "list(bool)" |
| 56 | + } |
| 57 | + oneof value { |
| 58 | + bytes s = 2; // "string" |
| 59 | + int64 i = 3; // "int" |
| 60 | + float f = 4; // "float" |
| 61 | + bool b = 5; // "bool" |
| 62 | + ListValue list = 1; // any "list(...)" |
| 63 | + } |
| 64 | + optional string description = 2; |
| 65 | +} |
| 66 | +
|
| 67 | +message FilesystemConfig{ |
| 68 | + string owner = 1; |
| 69 | + string version = 2; |
| 70 | + map<string, FilesystemAttr> options = 3 ; |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +It is possible to choose `FilesystemConfig` to be another human readable key-value store format with a similar structure such as `json` or `yaml`, though this may limit the data types that can be used for configuration. |
| 75 | +Filesystems which doesn't have user configurable parameters can leave these methods unimplemented. In that case default implementations will return `nullptr` wherever applicable. `FilesystemConfig` object can be exposed to python layer for modifications at python level. |
| 76 | + |
| 77 | +Typical use pattern would be that user queries the Filesystem implementation for current configuration. Filesystem returns an object populated with all configurable parameters and their existing or default values which also serves as a schema. User creates a copy of the configuration, modified desired parameters in protobuf object and passes this back to Filesystem through `SetConfiguration()` call. Then Filesystem alter its operational parameters if modifications are within acceptable limits or return an error with apropriate message describing the issue. |
| 78 | + |
| 79 | +### Alternatives Considered |
| 80 | + |
| 81 | +Alternative to this proposal is to use a side-channel such as an environment variable to modify the internal parameters. However this is cumbersome, error prone and may not be possible to use at all under certain circumstances. |
| 82 | + |
| 83 | +### Performance Implications |
| 84 | + |
| 85 | +This proposal should help improve persistent storage i/o performance. |
| 86 | + |
| 87 | +### Dependencies |
| 88 | + |
| 89 | +This proposal do not introduce any new dependencies though, plugin based filesystems may have to link against protobuf (and hide its symbols) or respective library if an alternative form for `FilesystemConfig` is chosen. |
| 90 | + |
| 91 | +### Engineering Impact |
| 92 | + |
| 93 | +Engineering impact of this change is negligable. Amount of change needed is proportional to configurability that developers choose to expose to user. |
| 94 | + |
| 95 | +### Platforms and Environments |
| 96 | + |
| 97 | +This proposal is applicable to all Filesystems on all supported platforms. |
| 98 | + |
| 99 | +### Best Practices |
| 100 | + |
| 101 | +This proposal provides tuning handles to users for tuning the i/o performance. These can be documented in performance guides, in filesystem implementations or the `FilesystemAttr.description` field of the configuration object. |
| 102 | + |
| 103 | +### Tutorials and Examples |
| 104 | + |
| 105 | +An example use of the new API could be as follows. |
| 106 | + |
| 107 | +```cpp |
| 108 | +Status SetFilesystemThreads(int thread_count) { |
| 109 | + ModularFileSystem* fs = Env::Default()->GetFileSystemForFile( |
| 110 | + "remote://some_configurable_remote_filesystem"); |
| 111 | + std::unique_ptr<FilesystemConfig> config; |
| 112 | + auto s = fs->GetConfiguration(&config); |
| 113 | + if (!s.ok()) return s; |
| 114 | + if (!config) return Status::OK(); // No configuration support |
| 115 | + std::unique_ptr<FilesystemConfig> new_config = |
| 116 | + std::make_unique<FilesystemConfig>() new_config->CopyFrom(*config); |
| 117 | + if (config->options.contains("ThreadPoolSize")) { |
| 118 | + new_config->options.at("ThreadPoolSize").set_i(8); |
| 119 | + } |
| 120 | + fs->SetConfiguration(std::move(new_config)); |
| 121 | + return Status::OK(); |
| 122 | +} |
| 123 | +``` |
| 124 | +
|
| 125 | +### Compatibility |
| 126 | +
|
| 127 | +This proposal have no effect on compatibility of existing code. |
| 128 | +
|
| 129 | +### User Impact |
| 130 | +
|
| 131 | +This proposal will expose new methods to user to query and modify operational parameters of Filesystems. Users wishing to tune their Filesystem access will be able to do so. |
| 132 | +
|
| 133 | +## Questions and Discussion Topics |
| 134 | +
|
| 135 | +### Comments and Altrenatives Came Out During Posting Period |
| 136 | +
|
| 137 | +During the posting period, some concerns about the protobuf passing through C C++ boundaries has been raised and alternative approaches has been discussed. @mihaimaruseac suggested following structure for crossing plugin-framework boundary. |
| 138 | +
|
| 139 | +```cpp |
| 140 | +typedef struct TF_Filesystem_Option_Value { |
| 141 | + int type_tag; |
| 142 | + int num_values; |
| 143 | + union { |
| 144 | + int64 inv_val; |
| 145 | + double real_val; |
| 146 | + struct { |
| 147 | + char* buf; |
| 148 | + int buf_length; |
| 149 | + } buffer_val; |
| 150 | + } *values; // owned |
| 151 | +} TF_Filesystem_Option_Value; |
| 152 | +
|
| 153 | +typedef struct TF_Filesystem_Option { |
| 154 | + char* name; // null terminated, owned |
| 155 | + char* description; // null terminated, owned |
| 156 | + int per_file; // bool actually, but bool is not a C type |
| 157 | + TF_Filesystem_Option_Value *value; // owned |
| 158 | +} TF_Filesystem_Option; |
| 159 | +``` |
| 160 | + |
| 161 | +On framework side these options can be translated to user friendly C++ and Python data structures and helper functions for getting and setting options can be provided in filesystem header file for plugins to use. With this approach all the buffer allocation and dealloactions will be done through allocator functions provided by plugins. |
| 162 | + |
| 163 | +If this schema is prefered C layer methods become |
| 164 | + |
| 165 | +```cpp |
| 166 | + void (*const get_filesystem_configuration)(TF_Filesystem_Option** options, int *num_options, TF_Status* status); |
| 167 | + void (*const set_filesystem_configuration)(const TF_Filesystem_Option** options, int num_options, TF_Status* status); |
| 168 | +} |
| 169 | +``` |
| 170 | +
|
| 171 | +Alternatively API can be expanded with per-option getters and setters in which case methods similar to following would be added to filesystem API |
| 172 | +
|
| 173 | +```cpp |
| 174 | + void (*const get_filesystem_configuration_option)(const char* key, TF_Filesystem_Option *option, TF_Status* status); |
| 175 | + void (*const set_filesystem_configuration_option)(const TF_Filesystem_Option* option, TF_Status* status); |
| 176 | + void (*const get_filesystem_configuration_keys)(char** Keys, int *num_keys, TF_Status* status); |
| 177 | +
|
| 178 | +``` |
| 179 | + |
| 180 | +The first option has the advantage of smaller API surface for plugin developers to implement at the expense of bigger data size crossing the framework-plugin boundary. Adding per-option methods to the API can simplify data preparation for boudary crossing for filesystems that have very large configuration options. |
0 commit comments