From 3cdeca6effcb656487b42c0b700669a547dd7e14 Mon Sep 17 00:00:00 2001 From: jiangzho Date: Wed, 24 Jun 2020 23:36:34 +0800 Subject: [PATCH 01/28] Adding Pluggable Device For TensorFlow RFC --- ...0200624-pluggable-device-for-tensorflow.md | 251 ++++++++++++++++++ .../design_overview.png | Bin 0 -> 14288 bytes .../gpu_example.png | Bin 0 -> 22112 bytes 3 files changed, 251 insertions(+) create mode 100644 rfcs/20200624-pluggable-device-for-tensorflow.md create mode 100644 rfcs/20200624-pluggable-device-for-tensorflow/design_overview.png create mode 100644 rfcs/20200624-pluggable-device-for-tensorflow/gpu_example.png diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md new file mode 100644 index 000000000..89af0dccb --- /dev/null +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -0,0 +1,251 @@ +# **Pluggable device for TensorFlow** + +| Status | Proposed | +:-------------- |:---------------------------------------------------- | +| **RFC #** | [NNN](https://github.com/tensorflow/community/pull/NNN) (update when you have community PR #)| +| **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | +| **Sponsor** | Anna Revinskaya (annarev@google.com) | +| **Updated** | 2020-06-24 | + +## **Objective** + +Implement a pluggable device mechanism which allows to run existing TensorFlow programs on a new device without user changing most of the code. Users only need to install a plugin in a specified directory, and the mechanism is able to discover and plug in the capabilities offered by the plugin. + +This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77), which aims to extend the TensorFlow design to plugin capabilities like adding a new device support. The modular device interface is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). + +## **Motivation** + +When extending TensorFlow to support a new device, one needs to modify TensorFlow code and maintain a special TensorFlow build for the new device. Modular TensorFlow RFC design a plugin architecture for serveral TensorFlow components(`Networking`, `Filesystems`, `Kernel`, `Graph` and `Accelerator backends`). This RFC describes the Accelerator backends module in the Tensorflow proper side, by introducing pluggable device to the TensorFlow device classes. + +The pluggable device discovery and initialization is transparent to end users. As long as the device plugin libraries follow the design described in this RFC, it can be plugged to TensorFlow proper and enable TensorFlow to run existing TensorFlow programs on a new device. + +## **User Benefit** + +This RFC allows TensorFlow to transparently run TensorFlow programs on new devices, as long as users set up the system properly installing the device plugin. + +## **Design Proposal** + +### Design Overview + +This RFC extends the TensorFlow device class hierarchy to add a standardized pluggable device named `PluggableDevice` which is built on top of [StreamExecutor](https://github.com/tensorflow/tensorflow/blob/e5023a1738cce7efcdf9d87863b85c80ab2f8c9e/tensorflow/stream_executor/stream_executor_pimpl.h#L73), and all new third-party devices who want to integrate with current TensorFlow stack only need to implement StreamExecutor C API(shown in Diagram 1). + +
+ +
+ +* `PluggableDevice` is defined in TensorFlow proper which inherits from [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h).It is built on top of StreamExecutor C++ interface to manage `PluggableDevice`’s key abstractions like StreamExecutor, stream, memory and event. + +* `PluggableDeviceExecutor` implements [StreamExecutor](https://github.com/tensorflow/tensorflow/blob/e5023a1738cce7efcdf9d87863b85c80ab2f8c9e/tensorflow/stream_executor/stream_executor_pimpl.h#L73) and is built on top of StreamExecutor C API (addressed in [RFC](https://github.com/tensorflow/community/pull/257)). + +* `PluggableDevice Implementation` is inside the TensorFlow plugin, which provides those C functions implementation defined in the StreamExecutor C API. + +The pluggable device mechanism contains device discovery and creation process which creates a `PluggableDevice` object and `PluggableDeviceExecutor` object for each pluggable device. + +With the RFC, existing TensorFlow GPU programs can run on a plugged device without the user changing the code. The Diagram 2 describes the workflow of TensorFlow with device plugin, it shows how a simple GPU program runs on the pluggable device. +
+ +
+ +### Device Discovery + +Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. + +During the plugin library initialization, it calls the `SE_ReigsterPlatform()` API to register the stream executor platform (`SE_Platform` struct) to TensorFlow proper. The `SE_ReigsterPlatform()` API is a callback API, part of StreamExecutor C API, which passes necessary information to TensorFlow proper to instantiate a stream executor platform ([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and register to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82). +The stream executor platform must be registered with the name "PluggableDevice". +See below code which is an example of registering a PluggableDevice platform with StreamExecutor C API: +```cpp +void RegisterPluggableDevicePlatform() { + static plugin_id_value = 123; + SE_PlatformId id; + id.id = &plugin_id_value; + int visible_device_count = get_plugin_device_count; + SE_Platform* custom_platform = SE_NewPlatform( + id, visible_device_count, + create_device, create_stream_executor, + delete_device, delete_stream_executor); + TF_Status* status = TF_NewStatus(); + std::string name = "PluggableDevice"; + SE_RegisterPlatform( + name.c_str(), name.size(), + custom_platform, + status); +} + +``` +Use static initialization to register the new platform: +```cpp +static bool IsPluggableDevicePlatformRegistered = []() { + RegisterPluggablePlatform(); + return true; +}(); + +``` + +### Device Creation + +`PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, PluggableDeviceFactory is registered as "GPU" name and given higher priority than the default GPU device. +  `REGISTER_LOCAL_DEVICE_FACTORY("GPU",PluggableDeviceFactory, 220); // plugged GPU` +  `REGISTER_LOCAL_DEVICE_FACTORY("GPU", GPUDeviceFactory, 210);//default GPU` +For those vendor who don't want to use "GPU" name, it's optional to register a new device name. For example: +  `REGISTER_LOCAL_DEVICE_FACTORY("Third-party device",PluggableDeviceFactory, 230); // plugged third party device` + +When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find its `se::platform` through its platform name: "PluggableDevice”, then stream executor platform (`se::platform`) further creates a StreamExecutor object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the StreamExecutor objects. + +The section below shows some pseudo code to introduce some extension inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). + +1. `PluggableDeviceFactory` creates and initializes a set of `PluggableDevice` instances when the session is created. +```cpp + PluggableDeviceFactory::CreateDevices(SessionOptions& options, const string& name_prefix, std::vector>* devices) { + for (int i = 0; i < options.device_count(); i++) { + PluggableDevice pluggable_device = CreatePluggableDevice(options,i); //set allocator + pluggable_device->Init(options); + devices.push_back(std::move(pluggable_device)); + } + } +``` + +2. `PluggableDevice` object binds a StreamExecutor and creates a set of Streams during the initialization.Streams include one compute stream and several memory copy streams. +```cpp + void PluggableDevice::Init(SessionOption& options) { + se::Platform* platform= se::MultiPlatformManager::PlatformWithName("PluggableDevice"); + stream_executor_ = platform->ExecutorForDevice(pluggable_dev_id_); + compute_stream_ = new se::Stream(stream_executor_); + compute_stream_->Init(); + host_to_device_stream_ = new se::Stream(stream_executor_); + host_to_device_stream_->Init(); + ... + } // create StreamExecutor +``` +3. `PluggableDevicePlatform` is responsible for the StreamExecutor creation. It creates an `SE_StreamExecutor` and `SE_Device` object through create_stream_executor and create_device which are registered in the `SE_Platform`. Then `PluggableDeviceExecutor` is constructed with `SE_StreamExecutor` and `SE_Device` object. +```cpp + StreamExecutor* PluggableDevicePlaform::ExeutorForDevice(int device_id) { + auto config = get_plugin_config(device_id); + SE_Options* se_option = get_se_option(device_id); + SE_StreamExecutor* se= platform_->create_stream_executor(); + SE_Device* sd = platform_->create_device(se_options) + auto executor = absl::make_unique(this, absl::make_unique(config, se, sd)); + return std::move(executor); + } +``` +**TensorFlow Proper** + +TensorFlow proper needs to be extended to support a new class `PluggableDevice` to represent a set of new third-party devices and a new stream executor platform (`PluggableDevicePlatform`) to create the device and related resources with the information registered from plugin. + +Two sets of classes need to be defined in TensorFlow proper. +* Set 1: `PluggableDevice` related classes + * class `PluggableDevice`: a class represents a set of new third-party devices, it has a new device type named "PluggableDevice"/DEVICE_PLUGGABLE. + * class `PluggableDeviceFactory`: a device factory to create the PluggableDevice + * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm. + * class `PluggableDeviceAllocator`: an allocator that wraps a PluggableDevice allocator. + * class `PluggableDeviceHostAllocator`: allocator for pinned CPU RAM that is made known to PluggableDevice for the purpose of efficient DMA with PluggableDevice. + * class `PluggableDeviceEventMgr`: an object to keep track of pending Events in the StreamExecutor streams. + * class `PluggableDeviceContext`: a wrapper of pluggable device specific context that can be passed to OpKernels. +* Set 2: `PluggableDevicePlatform` related classes + * class `PluggableDevicePlatform`: PluggableDevice-specific platform, its platform name is "PluggableDevice", it contains a C struct: SE_Platform* platform_ which is its internal implementation and as the C interface registered by device plugin. + * class `PluggableDeviceExecutor`: PluggableDevice-platform implementation of the platform-agnostic StreamExecutorInterface, it contains C structs: SE_StreamExecutor* executor_ and SE_Device* device_ whose member can be accessed in both TensorFlow proper and device plugins. + * class `PluggableDeviceStream`: wraps a StreamHandle in order to satisfy the platform-independent StreamInterface. It returns SE_Stream which is treated as an opaque type to TensorFlow, whose structure is created by the device plugin. + * class `PluggableDeviceTimer`: wraps an opaque handle: SE_Timer to satisfy the platform-independent TimerInterface. + * class `PluggableDeviceEvent`: wraps an opaque handle: SE_Event to satisfy the platform-independent EventInterface. + +**Tensorflow Plugin** + +Plugin authors need to provide those C functions implementation defined in StreamExecutor C API . +* `SE_StreamExecutor` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. Plugin creates the SE_StreamExecutor and registers its C API implementations to the SE_StreamExecutor. +```cpp + SE_StreamExecutor* create_stream_executor() { + SE_StreamExecutor* se_nfs = new SE_StreamExecutor(); + se->memcpy_from_host = my_device_memory_from_host_function; + se->allocate = my_allocate_function; + … + }//Init device +``` +* `SE_Device` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. Plugin creates the SE_Device and fills its device opaque handle and device name to the SE_Device. +```cpp + SE_Device* create_device(SE_Options* options, TF_Status* status) { + SE_Device* se = new SE_Device(); + se->device_handle = get_my_device_handle(); + ... + return se; + } +``` +* `SE_Stream` is defined in plugin and treated as an opaque struct in TensorFlow proper. +```cpp + void create_stream(SE_Device* executor, SE_Stream* stream, TF_Status*) { + *stream = new SE_Stream_st(); + (*stream)->stream_handle = create_my_stream_handle(executor); + .. + } +``` + +### PluggableDevice kernel registration + +This RFC shows an example of registering kernels for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). + +TensorFlow proper defines a new device_type named DEVICE_PLUGGABLE for PluggableDevice.This device_type is used for the kernel registration and dispatch. Plugin needs to register its kernel implementation with DEVICE_PLUGGABLE type. +```cpp +void InitPlugin() { + TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", DEVICE_PLUGGABLE, + &Conv_Create, &Conv_Compute, &Conv_Delete); + TF_Status* status = TF_NewStatus(); + TF_RegisterKernelBuilder(/*kernel_name*/"Convolution", builder, status); + if (TF_GetCode(status) != TF_OK) { /* handle errors */ } + TF_DeleteStatus(status); +} +``` +### Using stream inside PluggableDevice kernel + +The following code shows a convolution kernel implementation using the stream handle. The streams are created during the pluggable device creation. The placer decides which device to use for each OP in the graph. Then the streams associated with the device are used to construct the OpKernelContext for the op computation during the graph execution. +```cpp +void Conv_Compute(TF_OpKernelContext*) { + TF_GetInput(context, input_index, &input, &status); + TF_GetInput(context, filter_index, &filter, &status); + auto output = TF_AllocateOutput(context, output_index, TF_Float32, dims, num_dims, len, status); + SE_Stream se_stream = TF_GetStream(TF_OpKernelContext); + auto native_stream = static_cast(se_stream->stream_handle); + my_conv_impl(input, filter, output, native_stream); +} +``` +Kernel and op registration and implementation API [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md) needs to be extended to retrieve streams/device context from the TF_OpKernelContext, besides inputs and outputs. + +### **Alternatives Considered** + +* Without this RFC, end users need to change the python code to import the third-party device plugin. + +* Without this RFC, the third-party device vendor may implement the LocalDevice interface, which is not a C API interface and may interact with potential C++ ABI incompatibility issues. + +### **Performance Implications** + +* We don’t expect performance impact due to this RFC. The functions described by this RFC are realized at the initialization stage. + +### **Dependencies** + +* This RFC doesn’t add new dependencies to external libraries. + +* It depends on three modular TensorFlow related RFC + + * Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77) + + * StreamExecutor C interface [RFC](https://github.com/tensorflow/community/pull/257) + + * Kernel and op registration and implementation API [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md) + +### **Engineering Impact** + +* The impact to binary size / startup time / build time / test times are minimum. + +* The TensorFlow team will maintain this code. + +### **Platforms and Environments** + +* The pluggable device mechanism is based on `LoadLibrary()` so should work on all the platforms supported by `LoadLibrary`. The other enhancement to tensorflow proper is platform independent. + +### **Best Practices** + +* This works with Modular TensorFlow which will be the only way to integrate new third-party devices to the current TensorFlow stack. + +### **Compatibility** + +The RFC promotes the current TensorFlow ecosystem as it supports plugging new devices to TensorFlow. + +We don't expect this proposal to impact with other parts of the TensorFlow ecosystem. It doesn't support TFLite. It should not impede distribution strategies and would not interact with tf.fuction and SaveModel. + diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/design_overview.png b/rfcs/20200624-pluggable-device-for-tensorflow/design_overview.png new file mode 100644 index 0000000000000000000000000000000000000000..4711e98d84f41984959585e965c66f037e01c94f GIT binary patch literal 14288 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

SHe9a{F$YfTH8Q%SwO zy>w+Pa{$^KBuBSSv|f?}>b4;14AiNWX0KzU%BCDU$pXPmC9Nl)!lvxpln?>AY&;WkD~&1~$>+oF~M zR_@141P$n^-rSLPVm`pUUZzp01DR%op`nM+{Tq?rPf@;q|GrvP#WHZ(F~z(l?(A=4 zwR~D+W!He#Vszr$dA_wsdgPCI2`SRM5FcS9pa|kvE+kV<-GJBQ%_|?;HPG1E*`-6u z5@*II+P4}IA>|t0IYjgxTB+QgR1s3znh9=5N{-$vpvc%^+`I4dRcx6^wfe>!P@MLm zMS|f~&6~EYV{o=l#px(D-93wvjWHIBhK)%Ak1>71HAyB-g>!85BwZ^WMme}VtW+u=hM{;J&y*hxt+O&|r8px%X5(TU7%5bsESv8%7 z^y1>;2btGu^a9&8n%0362t|cQ}cN zSqsX?6_;}6@2z^&!9{&|+qR1Gr2$W_gaA7o>`ZtF=BxEgUb}H0nU?=zfwJ7nu)6YO z{VT5<%4X@Km^{Zl!4@@FgioH_*$ii~Gg;a5Ch8Vz>u)Doi;KZyOFlXEvJJyo%rU3E zvDNmH`Qe>WE{@U0?ui8(kLZ|dq;J7hkhH7}I4m-ls5g zHRG~95}9`o?)}DuEv;TuX>5+3F$vJaSH@d)0;SSW%W0Ry?lcs6szMPwh4LtsNc2ty z<34smDqNNx4OJ+ zWp>nIAHU~u?j;z5w>*h!S>dr2QQz4{!$sAwR;$`Anz<|5wQtuth$cv>4fL2U+o+?k zYkL~qJa#p38v}5Hljih{*Sp{IBTKZy?p!l4m#9nux7z1ngo;gc%L9dZ80^nQkHuTb zo3Ts7OS$5;am=>+(-1RtQHA)=Iu(IgiSA`fJgHxF%9d=gcUR2B$=fP(fn`?IB)*y# zXX2#hc0|=>3PQYuc(WV<8~R;Xmafz}q~WM@u9A4=+Ji|VcbOA|sq?av%x!!v(oWQo z!#r(V)531=OIJ~8L`%YOe=>`IOT!sjgS%`md(b!}7>e=MTD?qb$RytCS z(aBh#C94e_C26W%+l;-418NAG!Fm9t2}+T;^LL}i-+uRR8WWg0^uhoBJ`_(5wK50% z_T56Pfui~cb19A97%>3r{O`~8gc-#XGNFaT$Q;d&#w3TVE=w+9pqYX5{)h^q>-PjN zmH?_sH$cc51v?6K30}b?f3}MxDtK*@SSl5SJ_}3XzMO9t^$NrixpKx`H@1nJowY=& zS8yy;-8Zl8ee3kNnbXirm*VEL?U6&AHJ$+e>dzmy8bScd%;vh^RD?RmpoHb;mZ5{4 zs;(RPhO(S{YYN{>Bf*J#E_($^QN(3U-x$bfibq@nAAoK@H3R=16;^f|3EOJc%*d}r z57&)v+(CBV&1=yXCkS>bdlO17p8SGtZ1z5UBF@tu$O^AZMs8m+*XexeMG6afy$)%mR=;!_PkKcbVRu zz0*=a<6i3}`QBTiV-kTtON+D6gG`g1pMIxWfR5My>tl&H;P#d~1)UA1K0Xxc>lojw JyzBhO{{aTLmLC8B literal 0 HcmV?d00001 diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/gpu_example.png b/rfcs/20200624-pluggable-device-for-tensorflow/gpu_example.png new file mode 100644 index 0000000000000000000000000000000000000000..8b6d188b53d0fd6bd46f8831c3e12997081846cf GIT binary patch literal 22112 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 Date: Wed, 24 Jun 2020 23:51:31 +0800 Subject: [PATCH 02/28] Update RFC PR Number --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 89af0dccb..eda7c21ae 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -2,7 +2,7 @@ | Status | Proposed | :-------------- |:---------------------------------------------------- | -| **RFC #** | [NNN](https://github.com/tensorflow/community/pull/NNN) (update when you have community PR #)| +| **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | | **Updated** | 2020-06-24 | From 72c45896ef9da8dcb448ccd99cfa0f155b02f0c1 Mon Sep 17 00:00:00 2001 From: jzhoulon Date: Thu, 2 Jul 2020 14:46:47 +0800 Subject: [PATCH 03/28] Update 20200624-pluggable-device-for-tensorflow.md --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index eda7c21ae..3888afcb8 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -9,7 +9,7 @@ ## **Objective** -Implement a pluggable device mechanism which allows to run existing TensorFlow programs on a new device without user changing most of the code. Users only need to install a plugin in a specified directory, and the mechanism is able to discover and plug in the capabilities offered by the plugin. +Implement a pluggable device mechanism which allows to run existing TensorFlow programs on a new device without user changing the code. Users only need to install a plugin in a specified directory, and the mechanism is able to discover and plug in the capabilities offered by the plugin. This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77), which aims to extend the TensorFlow design to plugin capabilities like adding a new device support. The modular device interface is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). From 8fec775c9119fcacd476401c67ddb75eebfe6a26 Mon Sep 17 00:00:00 2001 From: jiangzho Date: Wed, 8 Jul 2020 17:35:22 +0800 Subject: [PATCH 04/28] update StreamExecutor C API --- ...0200624-pluggable-device-for-tensorflow.md | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 3888afcb8..0ac348a88 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -51,34 +51,36 @@ With the RFC, existing TensorFlow GPU programs can run on a plugged device witho Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. During the plugin library initialization, it calls the `SE_ReigsterPlatform()` API to register the stream executor platform (`SE_Platform` struct) to TensorFlow proper. The `SE_ReigsterPlatform()` API is a callback API, part of StreamExecutor C API, which passes necessary information to TensorFlow proper to instantiate a stream executor platform ([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and register to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82). -The stream executor platform must be registered with the name "PluggableDevice". See below code which is an example of registering a PluggableDevice platform with StreamExecutor C API: ```cpp void RegisterPluggableDevicePlatform() { static plugin_id_value = 123; SE_PlatformId id; id.id = &plugin_id_value; - int visible_device_count = get_plugin_device_count; - SE_Platform* custom_platform = SE_NewPlatform( - id, visible_device_count, - create_device, create_stream_executor, - delete_device, delete_stream_executor); + int visible_device_count = get_plugin_device_count(); + + SE_PlatformParams platform_params {SE_PLATFORM_PARAMS_SIZE, id, + create_device, destory_device, + create_stream_executor, destory_stream_executor}; + SE_Platform* custom_platform = SE_NewPlatform(platform_params); + + string platform_name = "MyCustomPlatform"; + string device_name = "MyCustomDevice"; + + SE_PlatformRegistartionParams platform_register_params {SE_PLATFORM_REGISTRATION_PARAMS_SIZE, + platform_name.c_str(), platform_name.size(), + device_name.c_str(), device_name.size(), + custom_platform}; TF_Status* status = TF_NewStatus(); - std::string name = "PluggableDevice"; - SE_RegisterPlatform( - name.c_str(), name.size(), - custom_platform, - status); + SE_RegisterPlatform(platform_register_params, status); } ``` -Use static initialization to register the new platform: +Use dynamic initialization to register the new platform: ```cpp -static bool IsPluggableDevicePlatformRegistered = []() { - RegisterPluggablePlatform(); - return true; -}(); - +void TF_InitPlugin() { + RegisterPluggableDevicePlatform(); +} ``` ### Device Creation @@ -152,8 +154,8 @@ Two sets of classes need to be defined in TensorFlow proper. Plugin authors need to provide those C functions implementation defined in StreamExecutor C API . * `SE_StreamExecutor` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. Plugin creates the SE_StreamExecutor and registers its C API implementations to the SE_StreamExecutor. ```cpp - SE_StreamExecutor* create_stream_executor() { - SE_StreamExecutor* se_nfs = new SE_StreamExecutor(); + SE_StreamExecutor* create_stream_executor(TF_Status* status) { + SE_StreamExecutor* se_nfs = new SE_StreamExecutor{ SE_STREAMEXECUTOR_STRUCT_SIZE }; se->memcpy_from_host = my_device_memory_from_host_function; se->allocate = my_allocate_function; … @@ -162,7 +164,7 @@ Plugin authors need to provide those C functions implementation defined in Strea * `SE_Device` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. Plugin creates the SE_Device and fills its device opaque handle and device name to the SE_Device. ```cpp SE_Device* create_device(SE_Options* options, TF_Status* status) { - SE_Device* se = new SE_Device(); + SE_Device* se = new SE_Device( SE_DEVICE_STRUCT_SIZE ); se->device_handle = get_my_device_handle(); ... return se; From a60fb80c36e5fffa544c7b978de6b8422dd72ba2 Mon Sep 17 00:00:00 2001 From: jiangzho Date: Sat, 11 Jul 2020 21:25:13 +0800 Subject: [PATCH 05/28] replace SE_RegisterPlatform->SE_InitializePlugin according to StreamExecutor C API RFC --- ...0200624-pluggable-device-for-tensorflow.md | 80 ++++++++++--------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 0ac348a88..4fbb12da1 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -15,7 +15,7 @@ This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow ## **Motivation** -When extending TensorFlow to support a new device, one needs to modify TensorFlow code and maintain a special TensorFlow build for the new device. Modular TensorFlow RFC design a plugin architecture for serveral TensorFlow components(`Networking`, `Filesystems`, `Kernel`, `Graph` and `Accelerator backends`). This RFC describes the Accelerator backends module in the Tensorflow proper side, by introducing pluggable device to the TensorFlow device classes. +When extending TensorFlow to support a new device, one needs to modify TensorFlow code and maintain a special TensorFlow build for the new device. Modular TensorFlow RFC design a plugin architecture for serveral TensorFlow components(`Networking`, `Filesystems`, `Kernel`, `Graph` and `Accelerator backends`). This RFC describes the Accelerator backends module in the TensorFlow proper side, by introducing pluggable device to the TensorFlow device classes. The pluggable device discovery and initialization is transparent to end users. As long as the device plugin libraries follow the design described in this RFC, it can be plugged to TensorFlow proper and enable TensorFlow to run existing TensorFlow programs on a new device. @@ -50,48 +50,54 @@ With the RFC, existing TensorFlow GPU programs can run on a plugged device witho Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, it calls the `SE_ReigsterPlatform()` API to register the stream executor platform (`SE_Platform` struct) to TensorFlow proper. The `SE_ReigsterPlatform()` API is a callback API, part of StreamExecutor C API, which passes necessary information to TensorFlow proper to instantiate a stream executor platform ([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and register to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82). -See below code which is an example of registering a PluggableDevice platform with StreamExecutor C API: +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and register to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper also gets the device type through `SE_InitializePlugin` and register the `PluggableDeviceFactory`with this type. The device type will be the device strings to be used to access pluggable device with tf.device() in python layer. +Plugin authors needs to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp -void RegisterPluggableDevicePlatform() { - static plugin_id_value = 123; - SE_PlatformId id; +void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { + static const int32_t plugin_id_value = 123; + SE_PlatformId id{ SE_PLATFORMID_STRUCT_SIZE }; id.id = &plugin_id_value; - int visible_device_count = get_plugin_device_count(); + int32_t visible_device_count = get_plugin_device_count(); - SE_PlatformParams platform_params {SE_PLATFORM_PARAMS_SIZE, id, - create_device, destory_device, - create_stream_executor, destory_stream_executor}; - SE_Platform* custom_platform = SE_NewPlatform(platform_params); - - string platform_name = "MyCustomPlatform"; - string device_name = "MyCustomDevice"; - - SE_PlatformRegistartionParams platform_register_params {SE_PLATFORM_REGISTRATION_PARAMS_SIZE, - platform_name.c_str(), platform_name.size(), - device_name.c_str(), device_name.size(), - custom_platform}; - TF_Status* status = TF_NewStatus(); - SE_RegisterPlatform(platform_register_params, status); -} - -``` -Use dynamic initialization to register the new platform: -```cpp -void TF_InitPlugin() { - RegisterPluggableDevicePlatform(); + std::string name = "MyDevicePlatform"; + std::string type = "GPU"; + + params.params.id = id; + params.params.visible_device_count = visible_device_count; + params.params.create_device = create_device; + params.params.destroy_device = destroy_device; + params.params.create_stream_executor = create_stream_executor; + params.params.destroy_stream_executor = destroy_stream_executor; + params.params.name = name.c_str(); + params.params.name_len = name.size(); + params.params.type = type.c_str(); + params.params.type_len = type.size(); } ``` - ### Device Creation -`PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, PluggableDeviceFactory is registered as "GPU" name and given higher priority than the default GPU device. -  `REGISTER_LOCAL_DEVICE_FACTORY("GPU",PluggableDeviceFactory, 220); // plugged GPU` -  `REGISTER_LOCAL_DEVICE_FACTORY("GPU", GPUDeviceFactory, 210);//default GPU` -For those vendor who don't want to use "GPU" name, it's optional to register a new device name. For example: -  `REGISTER_LOCAL_DEVICE_FACTORY("Third-party device",PluggableDeviceFactory, 230); // plugged third party device` - -When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find its `se::platform` through its platform name: "PluggableDevice”, then stream executor platform (`se::platform`) further creates a StreamExecutor object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the StreamExecutor objects. +`PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, plugin authors can register "GPU" string as the device type through `SE_InitializePlugin` and then TensorFlow proper will register the `PluggableDevice` as "GPU" name with higher priority than the default GPU device. +Plugin: +``` + SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { + ... + std::string type = "GPU" + params.params.type = type.c_str() + ... + } +``` +Proper: +``` + std::string platform_name_str(params.params.name, params.params.name_len); + std::string type_str(params.params.type, params.params.type_len); + DeviceFactory::Register(type_str, new PluggableDeviceFactory(platform_name_str), priority); +``` +For those vendors who don't want to use "GPU" name, it's optional to register a new device name. +Limitation: when multiple devices registered, their device names should be different, or it will get conflict and the registration will fail. This can be enhanced in the future. A possible solutoin: python layer provides API to let user specify an alternative device name they prefer if there is a conflict, such as: +``` + tf.load_plugin("CustomDeviceName", path_to_plugin_lib) +``` +When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find its `se::platform` through its platform name registered from plugin: "MyDevicePlatform”, then stream executor platform (`se::platform`) further creates or find a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. The section below shows some pseudo code to introduce some extension inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). @@ -149,7 +155,7 @@ Two sets of classes need to be defined in TensorFlow proper. * class `PluggableDeviceTimer`: wraps an opaque handle: SE_Timer to satisfy the platform-independent TimerInterface. * class `PluggableDeviceEvent`: wraps an opaque handle: SE_Event to satisfy the platform-independent EventInterface. -**Tensorflow Plugin** +**TensorFlow Plugin** Plugin authors need to provide those C functions implementation defined in StreamExecutor C API . * `SE_StreamExecutor` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. Plugin creates the SE_StreamExecutor and registers its C API implementations to the SE_StreamExecutor. From 3317b4b783033d160ff8e11ca66e7e86a601d6fd Mon Sep 17 00:00:00 2001 From: jiangzho Date: Sat, 11 Jul 2020 21:44:03 +0800 Subject: [PATCH 06/28] add user example --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 4fbb12da1..0b5dd8c03 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -96,6 +96,8 @@ For those vendors who don't want to use "GPU" name, it's optional to register a Limitation: when multiple devices registered, their device names should be different, or it will get conflict and the registration will fail. This can be enhanced in the future. A possible solutoin: python layer provides API to let user specify an alternative device name they prefer if there is a conflict, such as: ``` tf.load_plugin("CustomDeviceName", path_to_plugin_lib) + with tf.device("/CustomDeviceName:0"): + ... ``` When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find its `se::platform` through its platform name registered from plugin: "MyDevicePlatform”, then stream executor platform (`se::platform`) further creates or find a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. From a4b9120859eac80a84afff0463f077d2dcafd4ed Mon Sep 17 00:00:00 2001 From: jiangzho Date: Tue, 14 Jul 2020 09:14:47 +0800 Subject: [PATCH 07/28] update time --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 0b5dd8c03..3b12c0682 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -5,7 +5,7 @@ | **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | -| **Updated** | 2020-06-24 | +| **Updated** | 2020-07-14 | ## **Objective** From ec2ff5153cce96462033c476993034cc5eaeb0a2 Mon Sep 17 00:00:00 2001 From: jiangzho Date: Tue, 14 Jul 2020 21:17:19 +0800 Subject: [PATCH 08/28] device_type attribute of PluggableDevice --- ...0200624-pluggable-device-for-tensorflow.md | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 3b12c0682..71fff422f 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -79,7 +79,7 @@ void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* statu `PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, plugin authors can register "GPU" string as the device type through `SE_InitializePlugin` and then TensorFlow proper will register the `PluggableDevice` as "GPU" name with higher priority than the default GPU device. Plugin: ``` - SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { +void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { ... std::string type = "GPU" params.params.type = type.c_str() @@ -108,7 +108,7 @@ The section below shows some pseudo code to introduce some extension inside the PluggableDeviceFactory::CreateDevices(SessionOptions& options, const string& name_prefix, std::vector>* devices) { for (int i = 0; i < options.device_count(); i++) { PluggableDevice pluggable_device = CreatePluggableDevice(options,i); //set allocator - pluggable_device->Init(options); + pluggable_device->Init(options, pluggable_device_platform_name_); devices.push_back(std::move(pluggable_device)); } } @@ -116,8 +116,8 @@ The section below shows some pseudo code to introduce some extension inside the 2. `PluggableDevice` object binds a StreamExecutor and creates a set of Streams during the initialization.Streams include one compute stream and several memory copy streams. ```cpp - void PluggableDevice::Init(SessionOption& options) { - se::Platform* platform= se::MultiPlatformManager::PlatformWithName("PluggableDevice"); + void PluggableDevice::Init(SessionOption& options, const string& platform_name) { + se::Platform* platform= se::MultiPlatformManager::PlatformWithName(platform_name); stream_executor_ = platform->ExecutorForDevice(pluggable_dev_id_); compute_stream_ = new se::Stream(stream_executor_); compute_stream_->Init(); @@ -143,7 +143,7 @@ TensorFlow proper needs to be extended to support a new class `PluggableDevice` Two sets of classes need to be defined in TensorFlow proper. * Set 1: `PluggableDevice` related classes - * class `PluggableDevice`: a class represents a set of new third-party devices, it has a new device type named "PluggableDevice"/DEVICE_PLUGGABLE. + * class `PluggableDevice`: a class represents a set of new third-party devices, its device_type attribute (counter part of DEVICE_GPU, DEVICE_CPU) should be seperated from front-end visible device type name("GPU") to avoid kernel registration conflict with exsiting GPU(CUDA) kernels. it can be an alternative string registered from plugin, or "PLUGGABLE_" + device type(front-end visible device type registered through `SE_InitializePlugin`) as the device_type attribute, depending on StreamExecutor C API and kernel registration C API design. For the second option(adding "PLUGGABLE_" prefix), Kernel registration C API needs to add the "PLUGGABLE_" prefix to the registered device type so this device_type attribute can be transparently to the plugin authors. For example, plugin authors provide a "GPU" name through `SE_InitializePlugin` and register the kernels to the "GPU" name through `TF_NewKernelBuilder` in plugin side, and TensorFlow Proper takes the "GPU" as the name for Device registration and makes the "PLUGGABLE_GPU" as the device_type attribute (counter part of DEVICE_GPU, DEVICE_CPU) for PluggableDevice. * class `PluggableDeviceFactory`: a device factory to create the PluggableDevice * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm. * class `PluggableDeviceAllocator`: an allocator that wraps a PluggableDevice allocator. @@ -191,10 +191,25 @@ Plugin authors need to provide those C functions implementation defined in Strea This RFC shows an example of registering kernels for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). -TensorFlow proper defines a new device_type named DEVICE_PLUGGABLE for PluggableDevice.This device_type is used for the kernel registration and dispatch. Plugin needs to register its kernel implementation with DEVICE_PLUGGABLE type. +To avoid kernel registration conflict with existing GPU(CUDA) kernels, the backend device_type for kernel registration should be seperated from the front-end visible device type ("GPU"). Two Options: + option 1) The backend device_type can be an alternative string provided by plugin, and plugin authors use the string for kernel registration. + option 2) Another option is that plugin authors only need to provide one device type, and Tensorflow proper takes it as the string name for Device registration and makes "PLUGGABLE_" + device type as the device_type attribute in PluggableDevice for kernel registration. + +**Option 1:** +Plugin side: +plugin author provides an alternative string(such as "CUDA") to TensorFlow proper, which seperates from the front-end device type("GPU") and uses this string for kernel registration. ```cpp +void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { + ... + std::string type = "GPU" // front-end visible device type + params.params.type = type.c_str(); + std::string backend_device_type = "CUDA"; + params.params.type = backend_device_type.c_str(); + ... +} + void InitPlugin() { - TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", DEVICE_PLUGGABLE, + TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "CUDA", // seperate from front-end visible device type &Conv_Create, &Conv_Compute, &Conv_Delete); TF_Status* status = TF_NewStatus(); TF_RegisterKernelBuilder(/*kernel_name*/"Convolution", builder, status); @@ -202,6 +217,40 @@ void InitPlugin() { TF_DeleteStatus(status); } ``` +**Option 2:** +Plugin side: +plugin author provides the device type("GPU") for Device registration, and also uses it for kernel registration in plugin side. +``` +void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { + ... + std::string type = "GPU" // front-end visible device type + params.params.type = type.c_str(); + ... +} + +void InitPlugin() { + TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "GPU", // same type as front-end visible device type + &Conv_Create, &Conv_Compute, &Conv_Delete); + TF_Status* status = TF_NewStatus(); + TF_RegisterKernelBuilder(/*kernel_name*/"Convolution", builder, status); + if (TF_GetCode(status) != TF_OK) { /* handle errors */ } + TF_DeleteStatus(status); +} +``` +TensorFlow Proper side: +TensorFlow Proper uses this device type for Device registration and makes "PLUGGABLE_" + device_type("GPU") as the device_type attribute for kernel registration, this device_type attribute is transparently to the plugin authors. +``` +TF_KernelBuilder* TF_NewKernelBuilder( + const char* op_name, const char* device_name, + void* (*create_func)(TF_OpKernelConstruction*), + void (*compute_func)(void*, TF_OpKernelContext*), + void (*delete_func)(void*)) { + ... + result->cc_builder->Device(strcat("PLUGGABLE_", device_name)); // "PLUGGABLE_GPU" + ... +} +``` + ### Using stream inside PluggableDevice kernel The following code shows a convolution kernel implementation using the stream handle. The streams are created during the pluggable device creation. The placer decides which device to use for each OP in the graph. Then the streams associated with the device are used to construct the OpKernelContext for the op computation during the graph execution. From f42a102b8b751ef4a22d559c3109a1a14fad31a9 Mon Sep 17 00:00:00 2001 From: jiangzho Date: Wed, 22 Jul 2020 21:56:59 +0800 Subject: [PATCH 09/28] update PluggableBFCAllocator description --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 71fff422f..7d0a2d769 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -145,7 +145,7 @@ Two sets of classes need to be defined in TensorFlow proper. * Set 1: `PluggableDevice` related classes * class `PluggableDevice`: a class represents a set of new third-party devices, its device_type attribute (counter part of DEVICE_GPU, DEVICE_CPU) should be seperated from front-end visible device type name("GPU") to avoid kernel registration conflict with exsiting GPU(CUDA) kernels. it can be an alternative string registered from plugin, or "PLUGGABLE_" + device type(front-end visible device type registered through `SE_InitializePlugin`) as the device_type attribute, depending on StreamExecutor C API and kernel registration C API design. For the second option(adding "PLUGGABLE_" prefix), Kernel registration C API needs to add the "PLUGGABLE_" prefix to the registered device type so this device_type attribute can be transparently to the plugin authors. For example, plugin authors provide a "GPU" name through `SE_InitializePlugin` and register the kernels to the "GPU" name through `TF_NewKernelBuilder` in plugin side, and TensorFlow Proper takes the "GPU" as the name for Device registration and makes the "PLUGGABLE_GPU" as the device_type attribute (counter part of DEVICE_GPU, DEVICE_CPU) for PluggableDevice. * class `PluggableDeviceFactory`: a device factory to create the PluggableDevice - * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm. + * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm.It extends the BFC algorithm, counter part of GPUBFCAllocator. * class `PluggableDeviceAllocator`: an allocator that wraps a PluggableDevice allocator. * class `PluggableDeviceHostAllocator`: allocator for pinned CPU RAM that is made known to PluggableDevice for the purpose of efficient DMA with PluggableDevice. * class `PluggableDeviceEventMgr`: an object to keep track of pending Events in the StreamExecutor streams. From 2bf7974c9e1ab0f8ad766c139229c414f2b6aa2a Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Fri, 31 Jul 2020 05:01:05 -0400 Subject: [PATCH 10/28] adding supported/unsupported scenero for pluggable device and add subdevice type for low-level specialization of GPU device --- ...0200624-pluggable-device-for-tensorflow.md | 120 +++++++++--------- .../scenario1.png | Bin 0 -> 13752 bytes .../scenario2.png | Bin 0 -> 14671 bytes .../scenario3.png | Bin 0 -> 18939 bytes .../scenario4.png | Bin 0 -> 19668 bytes 5 files changed, 63 insertions(+), 57 deletions(-) create mode 100644 rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png create mode 100644 rfcs/20200624-pluggable-device-for-tensorflow/scenario2.png create mode 100644 rfcs/20200624-pluggable-device-for-tensorflow/scenario3.png create mode 100644 rfcs/20200624-pluggable-device-for-tensorflow/scenario4.png diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 7d0a2d769..802c5f531 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -1,11 +1,11 @@ -# **Pluggable device for TensorFlow** + **Pluggable device for TensorFlow** | Status | Proposed | :-------------- |:---------------------------------------------------- | | **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | -| **Updated** | 2020-07-14 | +| **Updated** | 2020-07-31 | ## **Objective** @@ -46,12 +46,41 @@ With the RFC, existing TensorFlow GPU programs can run on a plugged device witho +### Supported user scenarios of PluggableDevice + +This topic describes the user scenarios that are supported/unsupported in PluggableDevice. +* **Supported scenario**: Single PluggableDevice registered as "GPU" device type + In the case of installing one plugin that registers its PluggableDevice as "GPU" device type, the default GPUDevice will be invalid when the plugin is loaded. When user specifies the "GPU" device for ops under `with tf.device("gpu:0")`, PluggableDevice registered will be selected to run those ops. +

+ +* **Supported scenario**: Single PluggableDevice registered as a new device type. + In the case of installing one plugin that registers its PluggableDevice as a new device type, e.g., "XPU" device, user can speficies the "XPU" device for ops under `with tf.device("xpu:0")`, PluggableDevice registered will be selected to run those ops. +
+ +
+ +* **Supported scenario**: Multiple PluggableDevices registered as different device types. + In the case of installing multiple plugins that registers PluggableDevice as different device types, e.g., one is registered as "GPU" device and another is registered as "XPU" device, these PluggableDevices can be registered successfully and user can specify the device type to run ops on different hardware. +
+ +
+ +* **Non-Supported scenario**: Multiple PluggableDevices registered as the same device type. + In the case of installing multiple plugins that registers PluggableDevice as the same device type, e.g., more than one plugin registers its PluggableDevice as "GPU" device, these plugins's initialization will fail due to conflict. User needs to select which platform they want to use(either unloads the conflicting plugin or reconfigures the plugin with python API). +
+ +
+ + + ### Device Discovery Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and register to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper also gets the device type through `SE_InitializePlugin` and register the `PluggableDeviceFactory`with this type. The device type will be the device strings to be used to access pluggable device with tf.device() in python layer. -Plugin authors needs to implement `SE_InitializePlugin` and provide the necessary informations: +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type through `SE_InitializePlugin` and registers the `PluggableDeviceFactory`with the device type. The device type will be the device string to be used to access PluggableDevice with tf.device() in python layer. The subdevice type is for low-level specialization of GPU device. If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type. +Plugin authors needs to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { static const int32_t plugin_id_value = 123; @@ -61,6 +90,7 @@ void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* statu std::string name = "MyDevicePlatform"; std::string type = "GPU"; + std::string sub_type = "MY_GPU" params.params.id = id; params.params.visible_device_count = visible_device_count; @@ -72,11 +102,25 @@ void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* statu params.params.name_len = name.size(); params.params.type = type.c_str(); params.params.type_len = type.size(); + params.params.sub_type = sub_type.c_str(); + params.params.sub_type_len = sub_type.size(); +} +``` +`ListPhysicalDevice` will encode the subdevice type to the device name +```cpp +Status PluggableDeviceFactory::ListPhysicalDevices(std::vector* devices) { + se::Platform* platform = se::MultiPlatformManager::PlatformWithName(platform_name_); + for(int i = 0; i < platform->VisibleDeviceCount(); i++) { + const string device_name = strcat("/physical_device:", device_type_, "/", sub_device_type_, ":", i); + devices->push_back(device_name); + } + return Status::OK(); } ``` + ### Device Creation -`PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, plugin authors can register "GPU" string as the device type through `SE_InitializePlugin` and then TensorFlow proper will register the `PluggableDevice` as "GPU" name with higher priority than the default GPU device. +`PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, plugin authors can register "GPU" string as the device type through `SE_InitializePlugin` and then TensorFlow proper will register the `PluggableDevice` as "GPU" type with higher priority than the default GPU device. Plugin: ``` void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { @@ -92,14 +136,15 @@ Proper: std::string type_str(params.params.type, params.params.type_len); DeviceFactory::Register(type_str, new PluggableDeviceFactory(platform_name_str), priority); ``` -For those vendors who don't want to use "GPU" name, it's optional to register a new device name. -Limitation: when multiple devices registered, their device names should be different, or it will get conflict and the registration will fail. This can be enhanced in the future. A possible solutoin: python layer provides API to let user specify an alternative device name they prefer if there is a conflict, such as: +For those vendors who don't want to use "GPU" type, it's optional to register a new device type. +**Limitation**: when multiple plugins are installed, their device types should be different, or it will get conflict and the device registration will fail. TensorFlow proper can provide a python API to let user select a plugin by specifing a higher priority. ``` - tf.load_plugin("CustomDeviceName", path_to_plugin_lib) - with tf.device("/CustomDeviceName:0"): + tf.load_plugin_with_highest_priority(path_to_plugin_lib) + with tf.device("/GPU:0"): ... ``` -When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find its `se::platform` through its platform name registered from plugin: "MyDevicePlatform”, then stream executor platform (`se::platform`) further creates or find a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. + +When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name registered from plugin: "MyDevicePlatform”, then stream executor platform (`se::platform`) further creates or find a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. The section below shows some pseudo code to introduce some extension inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). @@ -114,7 +159,7 @@ The section below shows some pseudo code to introduce some extension inside the } ``` -2. `PluggableDevice` object binds a StreamExecutor and creates a set of Streams during the initialization.Streams include one compute stream and several memory copy streams. +2. `PluggableDevice` object binds a StreamExecutor and creates a set of Streams during the initialization. Streams include one compute stream and several memory copy streams. ```cpp void PluggableDevice::Init(SessionOption& options, const string& platform_name) { se::Platform* platform= se::MultiPlatformManager::PlatformWithName(platform_name); @@ -143,7 +188,7 @@ TensorFlow proper needs to be extended to support a new class `PluggableDevice` Two sets of classes need to be defined in TensorFlow proper. * Set 1: `PluggableDevice` related classes - * class `PluggableDevice`: a class represents a set of new third-party devices, its device_type attribute (counter part of DEVICE_GPU, DEVICE_CPU) should be seperated from front-end visible device type name("GPU") to avoid kernel registration conflict with exsiting GPU(CUDA) kernels. it can be an alternative string registered from plugin, or "PLUGGABLE_" + device type(front-end visible device type registered through `SE_InitializePlugin`) as the device_type attribute, depending on StreamExecutor C API and kernel registration C API design. For the second option(adding "PLUGGABLE_" prefix), Kernel registration C API needs to add the "PLUGGABLE_" prefix to the registered device type so this device_type attribute can be transparently to the plugin authors. For example, plugin authors provide a "GPU" name through `SE_InitializePlugin` and register the kernels to the "GPU" name through `TF_NewKernelBuilder` in plugin side, and TensorFlow Proper takes the "GPU" as the name for Device registration and makes the "PLUGGABLE_GPU" as the device_type attribute (counter part of DEVICE_GPU, DEVICE_CPU) for PluggableDevice. + * class `PluggableDevice`: a class represents a set of new third-party devices, its device_type attribute describes what kind of device this is. it can be "GPU" or other device type string. it also has an attribute: subdevice_type, subdevice_type is for low-level specialization of GPU device. It will be part of kernel dispatch key to avoid conflict issue with exiting GPU(CUDA) kernels. The subdevice_type is also used to check whether there is some CUDA specific logic code in grappler and common runtime when the device type is "GPU". * class `PluggableDeviceFactory`: a device factory to create the PluggableDevice * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm.It extends the BFC algorithm, counter part of GPUBFCAllocator. * class `PluggableDeviceAllocator`: an allocator that wraps a PluggableDevice allocator. @@ -189,67 +234,28 @@ Plugin authors need to provide those C functions implementation defined in Strea ### PluggableDevice kernel registration -This RFC shows an example of registering kernels for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). +This RFC shows an example of kernel registration for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). -To avoid kernel registration conflict with existing GPU(CUDA) kernels, the backend device_type for kernel registration should be seperated from the front-end visible device type ("GPU"). Two Options: - option 1) The backend device_type can be an alternative string provided by plugin, and plugin authors use the string for kernel registration. - option 2) Another option is that plugin authors only need to provide one device type, and Tensorflow proper takes it as the string name for Device registration and makes "PLUGGABLE_" + device type as the device_type attribute in PluggableDevice for kernel registration. - -**Option 1:** -Plugin side: -plugin author provides an alternative string(such as "CUDA") to TensorFlow proper, which seperates from the front-end device type("GPU") and uses this string for kernel registration. +To avoid kernel registration conflict with existing GPU(CUDA) kernels, plugin author needs to provide a device type(such as "GPU") as well as a subdevice type(such as "INTEL_GPU") to TensorFlow proper for kernel registration and dispatch. The device type indicates the device the kernel runs on, the subdevice type is for low-level specialization of the GPU device. ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { ... std::string type = "GPU" // front-end visible device type params.params.type = type.c_str(); - std::string backend_device_type = "CUDA"; + std::string sub_device_type = "INTEL_GPU"; // low-level specialization device type params.params.type = backend_device_type.c_str(); ... } -void InitPlugin() { - TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "CUDA", // seperate from front-end visible device type - &Conv_Create, &Conv_Compute, &Conv_Delete); - TF_Status* status = TF_NewStatus(); - TF_RegisterKernelBuilder(/*kernel_name*/"Convolution", builder, status); - if (TF_GetCode(status) != TF_OK) { /* handle errors */ } - TF_DeleteStatus(status); -} -``` -**Option 2:** -Plugin side: -plugin author provides the device type("GPU") for Device registration, and also uses it for kernel registration in plugin side. -``` -void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { - ... - std::string type = "GPU" // front-end visible device type - params.params.type = type.c_str(); - ... -} - -void InitPlugin() { - TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "GPU", // same type as front-end visible device type - &Conv_Create, &Conv_Compute, &Conv_Delete); +void InitKernelPlugin() { + TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "GPU", "INTEL_GPU", //"GPU" is device type + "INTEL_GPU", &Conv_Create, &Conv_Compute, &Conv_Delete); // "INTEL_GPU" is sub device type TF_Status* status = TF_NewStatus(); TF_RegisterKernelBuilder(/*kernel_name*/"Convolution", builder, status); if (TF_GetCode(status) != TF_OK) { /* handle errors */ } TF_DeleteStatus(status); } ``` -TensorFlow Proper side: -TensorFlow Proper uses this device type for Device registration and makes "PLUGGABLE_" + device_type("GPU") as the device_type attribute for kernel registration, this device_type attribute is transparently to the plugin authors. -``` -TF_KernelBuilder* TF_NewKernelBuilder( - const char* op_name, const char* device_name, - void* (*create_func)(TF_OpKernelConstruction*), - void (*compute_func)(void*, TF_OpKernelContext*), - void (*delete_func)(void*)) { - ... - result->cc_builder->Device(strcat("PLUGGABLE_", device_name)); // "PLUGGABLE_GPU" - ... -} -``` ### Using stream inside PluggableDevice kernel diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png new file mode 100644 index 0000000000000000000000000000000000000000..20a15de3ae1ddbceed1e13597147ac52e8ed8462 GIT binary patch literal 13752 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 literal 0 HcmV?d00001 diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario2.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario2.png new file mode 100644 index 0000000000000000000000000000000000000000..a8a9ad9bb62adeda29a4ac3612440c3989bdd1c8 GIT binary patch literal 14671 zcmch;byQT}+c&HTIFvHf&@nW~&`NiAqcDJgl+@554MUe8B`vKIf(S#0bcfP4bjN@& z2*@*j@9*z^fA8PVTF+W%&D!V8*?V8t-k*K-iP6zgCc>x2zjyB*k*bP<-o1NR5KQ|E z02lMi(YV=j@17`ys)DS6@BCq)rb<9(#**22A*gjHBF-FPuObLY9w&Z)3l&m5zhMP!|K-PYjdr}OV!2CnwF+>wE6>I zl!>mfJ{|sW5B~1)JT5hXQHsR+uG{ZPp^ze{{KCijIdgrT9&&K>INvm9W6UM7NZG z&Mh~`0*Qsk8YvRc_K|#Tf;-@;5O{Q{KHFO?;+Q;$U;FurKLKr+57tE|gQ?QniZBaF zX{SyItA5o2Sryf2r!bGccCnjgxi|&-4JkwDXJdSjy4s$MsboFhvKpd=73;paTzNV3 z=YDbTuH)Ws9KYIJWQC@ud3>P2rxM4y2<(Hf@^nHfLm4gdw)t+5y~r;vF!kueRB2r4 zFQI4YL?wdk{c_=31-Z6_yb;z~@hXmmAB1+W4FyTkL}m9B5gaqv6_i>PA?y9Z-En-J zxl7W*Exs2KS)rtGd~f=NE!}~`PKQ)%rEz!Z?l_YpIXUGNIP=(B6RNP6^p!D;FeTAk61%>*wm;4be92f9q!1XC%JMu4ggI3fo4a!)vgL z_Ven`gy>l@x2G*(Jl@a;_V;u%C{T!Jn*$_}H`0EHUi=JLg6OQn5?up({0Rwfw?yHC zSk}umic|L_1jQH&*%{l#M_6NLoOqP=rG6C+?F|<1G36w`o{3Fc#EqRS#j{aATit0z zky9XwyCXD@1uq*{gE=Y8Q8mr-oqVbd4!FpY>ymqM{Ym1I&=tK%tqLhy(xhl_nW-l? z8x$+XtetT!rNL00*fl|Y>fYo|M{5k$aOO^DvV)3qm`9e|7f1V(CWniECsh>E(bC;gOttGpZ&@n5tc#bir-ZzLzjRnb2+kJc(m043 z=_WFPL_clS-94%D3YB0jgUzLHo5}crAul!&1>(fKp~1UJrvlFJDY`0I!keq0Hhayn zwovsw{`L7*Px7B{@4kG#`F0SRNiTCRy~0J4O52QVeH=i@4W)p&8W~hrUGr6muA1yL~muHBR+*ZT0Gy!EU=cKc;H1R+zBR1@|oNsjDo`l`KtDS5j~A)X(6=q0Xh zixNUZHuL#8)izZ!%Yh@^5xy3LO$?mk`IAryJ_P9pN8+f;RX&yMpa@KH9XI2adpe)y zNgl&yMs-@qnd&BCbvy&eh-EPg!ak*^41G)vEcs&Atccc=EQ6h{Yu1Vs8zbFW#+b|M zKifYHu=0C3a=O~+@Eq;Jwu$$v@k2Zy&9&};O3G>AOt$ZZ4y1J;8UYoln%fI?MB!(= z&bVfa)_77>n0}98%A|3mz59=bMPAQ;0p(&$5W2TsV@ID2T65dbpsqq8yBH~zZ^Mw4o$MH)dbPa)_7g}ld3-#ki&H_7l7 zPnMHe)?-rFKTG0gxc9m}1pbM)l#$vt{?HBT5;kAum&Z>^2^dUggQg>Mc*!BADe+|| z#iU?XZ{iiv=wblYY{vGwhn7Z={;c#Kb|+nQVUAqb{Ikk+gWqn|aU#kOSwXV*hQ8#Q zUuvL6bomnZ53*nczZD;pppqCWZIx4!tiM^U{5|ITNdhx7J$*to2ss2A4L)})A5nQ#WH}i4TIAIz zZ1Bl}fGv7wQs_;c?u;{dlqj`mCFVT=w0S%nrfcc-;C>18Yy4T6d)osaPn3MC4zw6YvPt?(Ql$^dK%&l`R7CLFBc8*lbyF7e@IT-W}9P5#8 z2TalL9zxgW6`!Yj)QbpJKY*^|sXpAh!rMUsK)IfKof|&;*e@xjbDpzCUxqv?U4a!| zPaFO=LKx^+N`I(5Fhr1bGW=%MjsF@@B1yo|9$|6RD51fNb0)P8&WYHj%?_r;Q{E{pvhkDP)lkZ`q6UNv?kVkVRHbn`K9=*@7 zkbxUT8lVpry^~WYoC@{Ofyf4E!Xx{(KpbUSi^iMG*OP#ywBR3F!S71{F@n|vnT23p zEkEyE%Gy=l5o5~|W1r3ugU{|3qbW3z>%h@^t-lYu%j76SZnw4S?3;>wnf_W&Yl4gc zFKDDZ+Esu&Oc7(hq8vU|hx=uPFaq2kooWKXJa@wKbvOCmzs<6C9g(qYnAvjV0|enJ zLl$Y6UF2hU+#I6<#tgkdyCx4*81jm=ziK}`_pxV70bqy_jY6xytE1%VHd*BB;5Ohc z1ZebDk>u~x+r$j9UnpjS2Z?#^s&oI~jwt-+aRRY}X!?Q5kTk_;fX06w#<$OC1m)3LlS{!d7zW>ZL??BxxReF|RIGy?G5&`H7=~O+(1bf` z8f4a`POJBv>jvO3LFeqKo8+ zrx^DlW8`T*87b#A`!K(QUusSJqvi-t$3F~$enR>wT0ivF=UlIe*_YaM7ae;RU71r3 zehmFewfncGNLD$GX_@^V@D8R#Kq8IT{iQi9(wQ@)#sMF z_Rpz$oWHWqxH~)RUf*k&1rL3I;!7zUAq)Upq*E9TI4<`Y4*6;e1G9>n>Yz!+(4qUB* z+Z*d$0}SGIk~WjeEcJFfZ69)u*%m}a4(t(7vt{!J7+Xy7P4sFCmIW^eO@;@TZSucD zQEDLzW0AwNf9{gQNdwny8M`W%5RHrS-;1jW+DTpGRvgOOUGpZ?Pn69RhT1>8pZm=e zJ@x}#{z{?Ae3EbXSkLYD(@ueNX1ub3GN-&}O9{-O7*KBenCRV`!9RCt;EVLlU&&6{ zY;Wu{Z@7|Mk1rh74$sFCs*agxiMmeyv$$K4+5O=9sr>+2p@whoUJy=!$odO1N{i5BDz(V_A3Lv$m z)&GUDYr;5P z-HKw%O%2Og=xsQpa8sH<`o(|d_vsA)$)XtMcNy@Rd9YRC|Das;aKp2AS@2S32y1A{rpf@?mTSHI=4W&cu*Iqquk&HI0Z6^0llY2nJ5 zUME^XU&XeMr)aTdTLk}L=bX7Z;1?2f9W>fxg`S`vDp+hz?v{1 zzu?yAEuI|{3t+9a1`mnyalj8_G!Ns8y>83>1JuO4%e$7t2;~yOktODpjjp|^;hUEu z?yg?Xxl<7&&L?L_WlyYYbLM*L!%hIotr_#hgr)ZSypc0Lifs|xNyJuYy1K{~*>o$5b4RXQ)8wsOWC z7+b(E@a2cr@oKLXC|MYHN-l*h*?c)h2P}s=Y`(+ksD;muV_0@{e zJWh^Cw5gMLKm=?~@G4imwtvl3vvcVomdWONsrCe}S;Uahqq{#2U7LWXc#A&q&Pk|x z-9;XjuogM%R|QsJp5Q)JfJoZ^NcZ?4&p5kg}v(_uFU3SonOIyr^iD8#}XN}N_a7W#8Hpfo0;;}Nce1M($*^7D&k03lF zt3kzIO@|1`@BC+P=w&`@s=9Y?1jib!-a9%Qoee9{4m3vAlAj)bHwI*|GBXaYW^%Iw z>T{DGQ^`&Uk>BDJ5*u+0izd6ps_2~Tt2<@K05ZaWqD6?KdQwV3BB z`t;Zw9``$uZn)_HJJj5Noc1dzG73x$Ix8)75l!ZIOw8}AABl#2R2LXGx`oonHe)Pb zy6>D>Gchd5t5{WM09j#j2O*Q7oRn?d)motFS!rka%!0dIqLgCTRr;sVk90R^Iii%?Gwi=|6{Y1N`%oBN8o{vfo`y`<%0+Gsk@F9inD$FKlvaFLn zqZ>=x#Q-fo#E)6Xo8^iSHR@D-&Io;iXBGyPL!Yy)*(>*o{?eC`yu^KFGvrHX$>0y#G0y0@j8lu$to-5eDm@9@(Ch75t3*s_!@_t~i~Ar;Qy{*OOG66`2x|$yFFyT3JXPmYe)h6ATMWLiQKD2#GO5<=jkvde2K= zyg!+Iazn8o@em(~JR+6wVfC6hM1xtsuVc|V|63nlU@Z0-DW5g%VyXP@O)-BpjOy*S z1+e^NCk~J?>-7*y)*HO(!86AGG5g(t%O`H1G4Dx9a(=7mABj80R%|=^WRP4ldGS&0 z=}o_9eYK}$1Rh#LXvATmB>!5;t^)k`=hzVEhTy)dBQ>}Ob>!y&{;6V69CFy!-uC!X402k6%QgF~SA`&O@8PV> zZF8nu{O`=|C?>S6Mfc-}9Xt<8CyLBO<}3A;=^ylh423ir_chiik7oi)Z*DoXc_KVw zfk)Y~Ck7VqqTfxkI-<;#j*VGe)jw5=bTYVOmg1Cbm1^4aG3NoJcro&|g?|1UP2oeP zNvd8uXj6YwB6qU_+=d<)r@Tv#Wc^>gwmL#wh*dK%mp;PJgynXS*!XU=vkE&fwijcq zV@A0V*y(l(PgzbNX|k*vDpz?oQ3jw6$0)$M`?45aNQjX|6S&xaoADWpghAQ9Jy%G< z)np&8>>%fp&p;(~RA;c}?#+fMyz09#5%O&RP!sc+Fh?>TkBb_ zUOV)qhW=FxhArtdT~`l|4eDzHhBJZ(ImH+Z$lELuVB@t&`!R0IWy(V-n)}th8Y;D0 zlUc}gvA3^)buxHGA3@vSSHVnCMYU!0UZ900D9X&Xn?U5{EibHqO{G2cHR;Bm{ZwsWD95voT+Xpq=YG`Aa=L7&Hdo#ri}sQEZD!M0W4#Wd`*b@g5~0=J8ftXMpYwXsU{sCl674wP z@Mybw9Ze^%@T?To-jCcdNw>ZU-c!-ZptfHX&FvKBWqE&98*(kuSu>LY*P5cBbEFP6 z5V~3{t24l_DoJI7PJ7^kf}Q``KZY-F@38Ooo29G~o0LH>Iz*$ia(3!2m3z0-EgN7# zncf!7O1+wG4_8B79N`Wx9x)bH6?+UM5uM0C$Z>mJC2iN;3p`S6wCMsQ)b)@~-G~br z(03#~bg|Gt>@=yS<#a`~$c0@^B0~3O)GqOPeeYi@K_i-@Gj5GiUVyhX&SfLwARQw1 zwAYk8VS>BRRJRcY#@bcIWLAVapil*~+(LojlIoh>I%CER*r7;)CGS;gxd;HpTV~0?4UQ8Mm`DnKFp#n;#T)GtTsu=>&}P+Kz3III`Qdv?1I${?0f53 zoS^FT1YYu-pLI49JEXKj)PV>;`#8pNbpr@7Ow}$L@@Cx+@BiwUxs%vh|NBG+zah0* zDDkMncmasCs{V#IQ3iI%!Ynvy3O*!eej;WiQsNi#<596ONC>A1{k10k}sl}c4+o(a_1 zlY@r$qAIC@!iRA6>4#)Q3NaFx#Y1b&y0%z>H4kib{att0c2j*Fji>x80SBk`v5@ zVbCMQzbPd{lbhdGAv4Kn7u_PEZ!|1xK)1H+auS^U*Ghwwhv|4GBz;e=f#SA-t>elbSi6xz2;5TNEkJj3B9=J`{Bsib7c9UFNkG0n?23aPWdve_2@oLr&8 zJ+;J)g^!2B=ALoosh!Lief@h&g4VD-e+;Ka0mR@y@g~ zvoHA^uaX6Pi@;+UOc-D$TILVEvR%NvqQ997%X+-H|{;M_Ax zGT0wB+L_Vvxwq&aYpfeft_RhQ*%B(-H$+CyE0j!N9+5IuT_scwc#X zLm4#+7Uvi+vDY7!GIVO7)qhGvFr@o#6|x`fg+9qz&%$5cOvuqq6$seDzu~7xwkU3h zKOWT`{dHD{u?w)gdW0&h77hCXs}v4a_=r-;0U0jbKV4}cJ8aM{$>CM8_4AMUvV5JK zkC^VI7kH{CQ$z%Io+~c)4Fz&sQ4G9R5eVO>AJCN^m>L5$na( zx|N!rCr^+I*)xRs4P;6dxxj1BY2@a8_&BfMfFq}R) z1As_|$7*CrkI^i?>spf;b>lwm_oYQU{H{fdNW)JK{b0i-HA$P9oF}1A#UN0dPmV-$ z(MRRTHmZ|ZfUknxaW4Km z^l-}MxiLyP+7m(+I)Zt&$sp$+8C3Wd>{(OeNz*(4panyn^kQ`|fq#{|xxlDD$AHjJ zYQsh$t>Q#}r$L*Dp+?7Kjqg{_G@t9KZb>P_`$^)K^TigcSf;KmRPFkWxvyp4Cg;MB zbj+W+*bXgjC$pF-nLnqcd1ze7bHM?1%9Fa|u^tNAg`R!PW)zKentH_y$=C|IwESKP zsT9P|$XBi1Kj}${u|3};hImUd9oil~HT@MyN_3IC;7wc9W+7v3yMP{+446a>g?cJA z7{Z>Y+mRXim7B8ESfVa28@8}J<4Hl;#Oy&Y!ZrQo^zK_7-KysKZ0>y3%U|Iy{F2la zf&DI0>_=l2gQYR;2nj?vSwP4=b&6i_HL5WzeD_|ZqerVObLw=7$4Fr%g!dH41rtYe zj}aXAAr55h(`40b^pDz9eiYzsH_Ws?bTcsPsdNBG!9IfXl@y-uU^!u>m?cQGXDC4j zx9BENi>hs%6PmkF%^Ca*N$c%&phXsLbKU49ioG)+nC1tun_suT;KM`=RJE9CC!8fx zrESM&+2gD|`G2Ocm`UYXOd>oUOWB?U4tIXd%l}58Rx~5*u@nl}76vbt5JkJ=``FA? z-&>xI-b1+C7j;nM2{2jz zpk0{vGdaZjZ5e*;eUV|O?qrlfy|UJ;+@grubtemt%5=a1w_e$Z)M31&^bLC%WXwq2~SeZ+4 zL+LeoL?UBrO>w5>{@}1I%$r9|m4`n;#0Ecl6tu%7S-In_x!)cVur2e7Jq7-*%p?69 z70_-bCEmZL-j#}^6NhcB#HO?K0?7-8vY*0@mLf+1T8?#Fr zx>K*;yr>>Ed zky?p6LNvaXZ)_kpq_B9VHS}vMHa}8N@d^{hOJO;m*F;FX_l!fOfxB#U@-F>MWmBSL zJY|k~1_RkuXI?ZuL<-!~3xaDhFiqpLWHKn%3Zs92L>5Y&nJ-&kKMK%~7!n=TZO`ey z`s2cs?-54X@jDhfSXLf(k|B(R2xhDK}PC3vbcyN;Yk!?TYO!ZLUq=Q*uvLKIt*sTh78J zUYq94mJEYlhk)ln4~{++3!&d^R)wwSyzrCaGwPduwWMz-rR}3J0OR@VSeNn?*s5jS zyn6cCF4gPFTFcO+=$8?KjQn`DNS^F-P89wM^6~ndlqy?rMHv7TCiSZ*K{o^4!ISHB zB={j&cl!A=bwE*~66!rjN&gd7J;s{t=MIRHj<*|Htr{RZ`h*h3#0V*||3HT#_k^O@DuJdR1t9NP7?B7;|Xv_I^t(slWp-SgKQkC`AuRKknNl*0Bl2S2b1< z1#xwejyrHa*6;jc596hI2?5KS%q19(+e^QBMBeogzTbJHAPe|$+H95M^Y{^6{l47< z65*w4>;5AxAE!P4#2=c{19bju3)qYaWGaX=dpax{`jUR>Ykqv)njE}GOJ0i8!lP96 zFGJ*CaD&CM8fJHT9$n;qSDv9g-cdWZ1P|%<8H$CY0jQ7bHxJQgaEYW+$f_7AXNw+A z@D)|HE0f0Fq$p>;nZO6uTv7)kT?%iizut#WH>nB0Mm7CiHPypKS=JRh6JcT6neGe4 zsXcocm7U46r-K4kv~7?39DEzt2E?RaboIz*D18Ca#{fXm%JSjIc!9A`eP?i!y|WEU zJp7Vg_E|@V-e3QW6aAhXkm+_Z{C545>Va|oqdUh8H(;S%*NN%OEK}EhF#x%rKZI-nzh}Vogn275{7_y9bK=iQY zph-y8i8GZm`lDC`0VW29evY*Iu)l7=e4R@Bw8Vo5yx(C?{0c9_O_f6WEBDu`(BXhj znMOa@OzhGK*Xp`9p<;ITN-@6s1nx06G(a9HPy+nWwr5;mEH$r+{3L`V5n-xOCj-}6 zn%s#_jmMm-E#XxNO-u?*(Dam&LLyYcll~roV@xtjE98k65E6$9xHtbX2C+d7q3-?p z$3@XBrRK>~ayKo6_s$)}i%E+c>dg{!gr;Epdw`bB5wpCmThMM+HmIEOFE5t0+KD0< z5b9_yX52)tf+QFJav`@CM_yGTl)6O+itL@?VUWgf=^Ko!#nWBwLlom>P@4#?6O#x= zGNJ$(x~jc}6*<)eTH<8jcwby;n75p)wtr_ zC56x^x#MTNV3uQZb6MsMz{-d7`{)C81&P|SqlJpO5ThK4OBve)VNp~eA#QF66DtZLUqdxSvHQz z*sTUesIP_!sVO?^W}^BnzP(1m_LJkVVjpH6b?=#P ziA9Zap3!IWe-UlL2>rz1d+T7CL4;ugo!GU>Y@GZEYexzFLIATM1u(yd?gW*EKq*`d zbYzeyCBV8>*Yo$HfA14cB`4k{5jaN%?T9hl|J&Ug5Ok|+F^#Q7v&sBlBJ97Kf2G;K zGEVB3B8(`UkDtF)b1f`&gPe^Gg^$TV|G&RZBlvz`a+cJtWozu5`W$^H1YEGgP3q$vkQa|6^A7h`jW3;xQUzk~HHC!oo@|wLCM3#)* zNuKB)6Jj#4+0o%e-c7H|ZoS2v%NC3QkquRp)#k9}0P&@|sYjzLgMQ0U@uh|-OL4l@ zJDaa}duim3r1eMS+J~MA`%#`VTA_o2?d0q`JdaDk+MVOBKU2BlGZb2yqi2TAY z4z~KuNQ}F5;l>>1{kFZeeW?Fd(|Q5tz&{m?DJq2aTmO^{$ArYXR{IU*(xUmq1jZa` zFWMC5k`@Ig1jb5J&+r$bG^rj9i+{1MvWZY9PTKD^8i8~v>LiMuIXxY@7H~LNbPsc3 z7#ot>rd~F^%S(Uh!!zR)dn+%+E%K~IU2*n$7OlY3lRiCa&5rbwbV25LwV6JajdRK#r08l)v^~j#Ctp{ zMX`h4IA9JyqCf4kc*ot@XwjUq;`3-{>&ZDop@h4Knxb0n^)HW!-U|zYIk*vj?)STK zOsZ8X9l1^~TF_kzVxyLNYs?YDAKX8JlO&0~=#i62cJ&8v^`Fq8{Lf>$eY;Ls)-)U* zOR1X!F<*jm8mtbM%s$)NkJz4!WgI3_znnUfum z?aP)Cy+)?ptH>_bKFaB!7Rh4J6nu@|az z^RSNWR8vmP8v<(5BsYg8d#qn^?RI&6cmM8-CFeza;fTrMOSa zo$$sKB4z;>cDw)T}o$~GGVyV4H@(2;#p)+_U9eSu#;?d#reb$Ha7~VQs1+38CnzL`P9ieTG@o~cn za(2rGL%DntanN0fr*{Oih-4t$6YBgYG`bQgsQ5!lfz2$eBi6&Y#Wm`897(Y=>HbN0 zVd&EGp|cunYb7$L5`!n2Z}*bbMuW$y;Q!~24aeV#ZpIIQo`nDl5QJHYrl2bh;F{& zg4SvLk($BOvjlD|y)E8n@eU&Gd#&C+h6m!t?rt@$ad$3j*;h_h36=KI$a{=mRVooo zPVtNb69_|SWJiZ-#vNU$M&?qmO4aXi^!QM-Vfo*)Ebl78y|#aE%9&7L*uuLlatYZq z`IGA;TC;a-)2Cp)#b{nO+?vzB|ImvR`xFO8$y5-=VnJ~gt&Wq{Tg+nV*TccpRDthF z_3F70Fj1n5)j*2?f;!98OCAe@`coM!q_Ql!*0AMX71M7b>&#Xuac z>Y9t;vHUGG{#91f(L=fZRYM|o6ETLSRZoy>&YaP&Q%|cZn#%Ko|KOmtI~T_!s}7kV z4OIt3)y3&8yY|Ax-_8(cKTLv|XtovQk zDJ#%~)48bdW9J2Ea<1g7UEVuy#TTbOArh~@%zX2F)UhnEdD5lG_{I2T6x-;GY+D*<$+vEQIA4xvH>HR*AQ>VR(9}>GrP!m|z03Wa7 z9a!}6Q@cIM-2WIII7kdRn-q1??CJi-V%8~URL%>Ay0B0+f6H^L6heZx<_I(GiZE4) zRe`qH^F^ey81d{Oq~l;7LTE(CB&_2H4T)BZ>}b%8kbYe#;9$|PQ_!z&U`u2GeK2|T zGh)lSohvJhSftObq`%dEM}(qx2B0=yHW4^mZLxfyX16mJWSIKvmlj=jGf%Fy@6GMV z7J?z)hfrK8K7m=AZrHvZpGu+88@Bi^&dRA^Efc;vVfSVKzzweUF(%f0CDd?g&6OId z69yBJZ`?9dP|u&Zznt(fx*Qsno4Dzhfg~Q~K?L*-OFDgs_h%c_^V~F1si_Wjc{3tu zogoWK80Qz8Qrejdz>PPnJSKY?&b;V@)BdxMZOI?GvO;8C#yJC+m6hT?$^{xVZGr-?~+Ornn`{9* zLK|Mz0_Og0%DQ6PrRYR}g(Ccvjdd2{`EzH(nF4uopeWD2LRHhSnc zyB&c3vea#lSE%vrrH#+p7*YaQ&x=?YNFicA1CFRfBJ>Z{o!W6{lyF=jOWc&|2L11q zF9$U@7GmXwJ@9XVcybDgFEj7f#}`cxM0cGK@|aX}CaHpS1br#NU%o8vH_?w#nVhhf z%2lWc$j;-KoUz7eD+a}uhgRx5ge`yc9F3$#jSnR?e}1@oqsAOC zFCOl=hwazb@6!R@$6PFBfX|}M2P!Q?pQ7ENom$>J`Q~}DbcI7&veNPXkJdJO)5I}h zr%&g}m|XO@1pD95vd|>6$*-P{fw}f8`=vU)&8FB4Cap^``)18;u<%Vc>>hwLzp^l_ zVIsVh7MNiTAWxWHR3~9=dJ0T>p~|L1u=$N(d$;1l(#shbS3!V!@;ZeX%Pak5@2*Ch z*^VR{y_#tm>WHQ4Q(8PGxZf>AL34F3DBZ?{Fl8J98R1PpNbsCuEW+Xkor_ zB4eCB+PD&X@`k}!r7Wdlo0$xPS=?@)2n(hL2lx;vcFf%y)`BqnZ*ZfaC5$|A1@42v44!-BM7ndTLfolx5of9J z^5SMZP;zne-n`kAw{Sw{Et8eyOd?en<8Gw*ZqBE`MMWdB{V$DQUo@NHkc589@88bpAuLp%NiIqfeBMXgd*9jUeVbRx zVrFf)IKx(NMuRN;tYRF`F?=@XCathix6sBtz&`0GW$KDt7Jls=a$S!4#*Qh`Z4*$^ zY2;ZA7AN+k411}W$4gO0Zl51Fs3Brmm$?4hEY*hUnyo37C&%>JOaNappt7KW>FG4H zZe&yAG-4s|r?WupwRzfTzwURuxA;NY>p@<$I#f2?>z6H_$EAANBZ~F=-JTCpf(zS$ zslfze4P7(2f9vOii14FBXqCVOyzhlvWyBu-YQyGE32-3)!jx;l|Kf=nik0FYVX;DM zDdV#9o6>z@+k!(TmL$KT3S_`oEC(9q`J)~88dmZWBf)qR?;dBFF4Fo5soG;cl)R^^ MsHIRP_cHAN0FM3hjQ{`u literal 0 HcmV?d00001 diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario3.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario3.png new file mode 100644 index 0000000000000000000000000000000000000000..a15bd946b95793ab962aed5e3e501859d745ad90 GIT binary patch literal 18939 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Ⓢ7+MGSo7(vSZt=MnXC1nwp+##eVp7}4eS>t%l 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 a`KB)*@DuUd4V-CZ$E?h3ua=wMeexe Date: Mon, 3 Aug 2020 05:34:04 -0400 Subject: [PATCH 11/28] fix subdevice description --- ...0200624-pluggable-device-for-tensorflow.md | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 802c5f531..d9c840a6f 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -79,8 +79,7 @@ This topic describes the user scenarios that are supported/unsupported in Plugga Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type through `SE_InitializePlugin` and registers the `PluggableDeviceFactory`with the device type. The device type will be the device string to be used to access PluggableDevice with tf.device() in python layer. The subdevice type is for low-level specialization of GPU device. If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type. -Plugin authors needs to implement `SE_InitializePlugin` and provide the necessary informations: +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and registers the `PluggableDeviceFactory`with the device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type string is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type of GPU to identify. Plugin authors needs to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { static const int32_t plugin_id_value = 123; @@ -88,9 +87,8 @@ void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* statu id.id = &plugin_id_value; int32_t visible_device_count = get_plugin_device_count(); - std::string name = "MyDevicePlatform"; - std::string type = "GPU"; - std::string sub_type = "MY_GPU" + std::string name = "My_GPU"; //StreamExecutor platform name && subdevice type + std::string type = "GPU"; // device type params.params.id = id; params.params.visible_device_count = visible_device_count; @@ -102,14 +100,12 @@ void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* statu params.params.name_len = name.size(); params.params.type = type.c_str(); params.params.type_len = type.size(); - params.params.sub_type = sub_type.c_str(); - params.params.sub_type_len = sub_type.size(); } ``` -`ListPhysicalDevice` will encode the subdevice type to the device name +`ListPhysicalDevice` encodes the subdevice type string to the device type string. ```cpp Status PluggableDeviceFactory::ListPhysicalDevices(std::vector* devices) { - se::Platform* platform = se::MultiPlatformManager::PlatformWithName(platform_name_); + se::Platform* platform = se::MultiPlatformManager::PlatformWithName(sub_device_type_); for(int i = 0; i < platform->VisibleDeviceCount(); i++) { const string device_name = strcat("/physical_device:", device_type_, "/", sub_device_type_, ":", i); devices->push_back(device_name); @@ -120,13 +116,13 @@ Status PluggableDeviceFactory::ListPhysicalDevices(std::vector* devices) ### Device Creation -`PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, plugin authors can register "GPU" string as the device type through `SE_InitializePlugin` and then TensorFlow proper will register the `PluggableDevice` as "GPU" type with higher priority than the default GPU device. +`PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, plugin authors can register the "GPU" device type through `SE_InitializePlugin` and then TensorFlow proper will register the `PluggableDeviceFactory` for "GPU" type with higher priority than the default GPU device. Plugin: ``` void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { ... - std::string type = "GPU" - params.params.type = type.c_str() + std::string type = "GPU"; + params.params.type = type.c_str(); ... } ``` @@ -144,7 +140,7 @@ For those vendors who don't want to use "GPU" type, it's optional to register a ... ``` -When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name registered from plugin: "MyDevicePlatform”, then stream executor platform (`se::platform`) further creates or find a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. +When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name / subdevice type registered from plugin: "My_GPU”, then stream executor platform (`se::platform`) further creates or find a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. The section below shows some pseudo code to introduce some extension inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). @@ -236,19 +232,19 @@ Plugin authors need to provide those C functions implementation defined in Strea This RFC shows an example of kernel registration for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). -To avoid kernel registration conflict with existing GPU(CUDA) kernels, plugin author needs to provide a device type(such as "GPU") as well as a subdevice type(such as "INTEL_GPU") to TensorFlow proper for kernel registration and dispatch. The device type indicates the device the kernel runs on, the subdevice type is for low-level specialization of the GPU device. +To avoid kernel registration conflict with existing GPU(CUDA) kernels, plugin author needs to provide a device type(such as "GPU") as well as a subdevice type(such as "INTEL_GPU") to TensorFlow proper for kernel registration and dispatch. The device type indicates the device the kernel runs on, the subdevice type is for low-level specialization of the device. ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { ... std::string type = "GPU" // front-end visible device type params.params.type = type.c_str(); - std::string sub_device_type = "INTEL_GPU"; // low-level specialization device type - params.params.type = backend_device_type.c_str(); + std::string name = "INTEL_GPU"; // low-level specialization device type + params.params.type = name.c_str(); ... } void InitKernelPlugin() { - TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "GPU", "INTEL_GPU", //"GPU" is device type + TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "GPU", //"GPU" is device type "INTEL_GPU", &Conv_Create, &Conv_Compute, &Conv_Delete); // "INTEL_GPU" is sub device type TF_Status* status = TF_NewStatus(); TF_RegisterKernelBuilder(/*kernel_name*/"Convolution", builder, status); From 25ee309a29612966b706e1bca42c0b89a7043f37 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Mon, 3 Aug 2020 05:36:10 -0400 Subject: [PATCH 12/28] fix title --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index d9c840a6f..c4a992983 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -1,4 +1,4 @@ - **Pluggable device for TensorFlow** +#Pluggable device for TensorFlow | Status | Proposed | :-------------- |:---------------------------------------------------- | From f81cd14668b10003efa72708fc217da6ce56f72b Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Mon, 3 Aug 2020 05:39:21 -0400 Subject: [PATCH 13/28] fix title format --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index c4a992983..e32c0ae98 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -1,4 +1,4 @@ -#Pluggable device for TensorFlow +# Pluggable device for TensorFlow | Status | Proposed | :-------------- |:---------------------------------------------------- | From 28209ff2edfb5162e24f12ae2561a389a8f802d9 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Tue, 4 Aug 2020 05:14:54 -0400 Subject: [PATCH 14/28] fix format --- ...0200624-pluggable-device-for-tensorflow.md | 27 +++++++++--------- .../scenario1.png | Bin 13752 -> 14441 bytes .../scenario2.png | Bin 14671 -> 14807 bytes .../scenario3.png | Bin 18939 -> 20494 bytes .../scenario4.png | Bin 19668 -> 19838 bytes 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index e32c0ae98..b6b84f808 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -11,7 +11,7 @@ Implement a pluggable device mechanism which allows to run existing TensorFlow programs on a new device without user changing the code. Users only need to install a plugin in a specified directory, and the mechanism is able to discover and plug in the capabilities offered by the plugin. -This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77), which aims to extend the TensorFlow design to plugin capabilities like adding a new device support. The modular device interface is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). +This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77), which aims to extend the TensorFlow design to plug in capabilities like adding a new device support. The modular device interface is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). ## **Motivation** @@ -33,22 +33,22 @@ This RFC extends the TensorFlow device class hierarchy to add a standardized plu -* `PluggableDevice` is defined in TensorFlow proper which inherits from [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h).It is built on top of StreamExecutor C++ interface to manage `PluggableDevice`’s key abstractions like StreamExecutor, stream, memory and event. +* `PluggableDevice` is defined in TensorFlow proper which inherits from [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h). It is built on top of StreamExecutor C++ interface and manages `PluggableDevice`’s key abstractions like StreamExecutor, stream, memory and event. -* `PluggableDeviceExecutor` implements [StreamExecutor](https://github.com/tensorflow/tensorflow/blob/e5023a1738cce7efcdf9d87863b85c80ab2f8c9e/tensorflow/stream_executor/stream_executor_pimpl.h#L73) and is built on top of StreamExecutor C API (addressed in [RFC](https://github.com/tensorflow/community/pull/257)). +* `PluggableDeviceExecutor` is built on top of StreamExecutor C API (addressed in [RFC](https://github.com/tensorflow/community/pull/257)), implements [StreamExecutor](https://github.com/tensorflow/tensorflow/blob/e5023a1738cce7efcdf9d87863b85c80ab2f8c9e/tensorflow/stream_executor/stream_executor_pimpl.h#L73) . -* `PluggableDevice Implementation` is inside the TensorFlow plugin, which provides those C functions implementation defined in the StreamExecutor C API. +* `PluggableDeviceExecutor Implementation` is inside the TensorFlow plugin, which provides those C functions implementation defined in the StreamExecutor C API. The pluggable device mechanism contains device discovery and creation process which creates a `PluggableDevice` object and `PluggableDeviceExecutor` object for each pluggable device. -With the RFC, existing TensorFlow GPU programs can run on a plugged device without the user changing the code. The Diagram 2 describes the workflow of TensorFlow with device plugin, it shows how a simple GPU program runs on the pluggable device. +With the RFC, existing TensorFlow GPU programs can run on a plugged device without user changing the code. The Diagram 2 describes the workflow of TensorFlow with device plugin, it shows how a simple GPU program runs on the pluggable device.
### Supported user scenarios of PluggableDevice -This topic describes the user scenarios that are supported/unsupported in PluggableDevice. +This section describes the user scenarios that are supported/unsupported for PluggableDevice. * **Supported scenario**: Single PluggableDevice registered as "GPU" device type In the case of installing one plugin that registers its PluggableDevice as "GPU" device type, the default GPUDevice will be invalid when the plugin is loaded. When user specifies the "GPU" device for ops under `with tf.device("gpu:0")`, PluggableDevice registered will be selected to run those ops.
@@ -56,19 +56,19 @@ This topic describes the user scenarios that are supported/unsupported in Plugga
* **Supported scenario**: Single PluggableDevice registered as a new device type. - In the case of installing one plugin that registers its PluggableDevice as a new device type, e.g., "XPU" device, user can speficies the "XPU" device for ops under `with tf.device("xpu:0")`, PluggableDevice registered will be selected to run those ops. + In the case of installing one plugin that registers its PluggableDevice as a new device type, e.g., "XPU" device type, user can speficies the "XPU" device for ops under `with tf.device("xpu:0")`, PluggableDevice registered will be selected to run those ops.
* **Supported scenario**: Multiple PluggableDevices registered as different device types. - In the case of installing multiple plugins that registers PluggableDevice as different device types, e.g., one is registered as "GPU" device and another is registered as "XPU" device, these PluggableDevices can be registered successfully and user can specify the device type to run ops on different hardware. + In the case of installing multiple plugins that register PluggableDevices as different device types, e.g., one is registered as "GPU" device type and another is registered as "XPU" device type, these PluggableDevices can be registered successfully and user can specify the registered device type to run ops on different hardware.
* **Non-Supported scenario**: Multiple PluggableDevices registered as the same device type. - In the case of installing multiple plugins that registers PluggableDevice as the same device type, e.g., more than one plugin registers its PluggableDevice as "GPU" device, these plugins's initialization will fail due to conflict. User needs to select which platform they want to use(either unloads the conflicting plugin or reconfigures the plugin with python API). + In the case of installing multiple plugins that registers PluggableDevice as the same device type, e.g., more than one plugin registers its PluggableDevice as "GPU" device type, these plugins's initialization will fail due to registration conflict. User needs to select which platform they want to use(either unloads the conflicting plugin or reconfigures the plugin with python API).
@@ -79,7 +79,8 @@ This topic describes the user scenarios that are supported/unsupported in Plugga Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and registers the `PluggableDeviceFactory`with the device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type string is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type of GPU to identify. Plugin authors needs to implement `SE_InitializePlugin` and provide the necessary informations: +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type string is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type of GPU for identification. +Plugin authors need to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { static const int32_t plugin_id_value = 123; @@ -133,16 +134,16 @@ Proper: DeviceFactory::Register(type_str, new PluggableDeviceFactory(platform_name_str), priority); ``` For those vendors who don't want to use "GPU" type, it's optional to register a new device type. -**Limitation**: when multiple plugins are installed, their device types should be different, or it will get conflict and the device registration will fail. TensorFlow proper can provide a python API to let user select a plugin by specifing a higher priority. +**Limitation**: when multiple plugins are installed, their registered device types should be different, or it will get conflict and the device registration will fail. TensorFlow proper can provide a python API to let user select a plugin by specifing a higher priority. ``` tf.load_plugin_with_highest_priority(path_to_plugin_lib) with tf.device("/GPU:0"): ... ``` -When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name / subdevice type registered from plugin: "My_GPU”, then stream executor platform (`se::platform`) further creates or find a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. +When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name / subdevice type registered from plugin, then stream executor platform (`se::platform`) further creates or finds a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. -The section below shows some pseudo code to introduce some extension inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). +The section below shows some pseudo code to introduce some extensions inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). 1. `PluggableDeviceFactory` creates and initializes a set of `PluggableDevice` instances when the session is created. ```cpp diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png index 20a15de3ae1ddbceed1e13597147ac52e8ed8462..566310bda7e0774ea0cb6ed84e2b0e06de6b2b33 100644 GIT binary patch literal 14441 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 literal 13752 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 diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario2.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario2.png index a8a9ad9bb62adeda29a4ac3612440c3989bdd1c8..29e491f1be2fb46390fbe3caa213f415881bdb67 100644 GIT binary patch literal 14807 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 literal 14671 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 MsHIRP_cHAN0FM3hjQ{`u diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario3.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario3.png index a15bd946b95793ab962aed5e3e501859d745ad90..f741ab5170e9e847a48ce3cd5db69fdccda562f7 100644 GIT binary patch literal 20494 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Ⓢ7+MGSo7(vSZt=MnXC1nwp+##eVp7}4eS>t%l zA?=*8AkI^H@fKDh0v6_Hz>Kp)R|2L2?`4 zg9mX?0<1H;Ec-j1AkARE-ukYYrJv|PB(q0KJSCJM?(id+C?rO@dvqa{S>|9jOnc3& zpR!n0d^3nU@pKyfxI=YqJAP#mf^m-yc%<1zG3Hl&eRr%M&THjrgUL$=QJXA$a3J=f zrueTw(Qr%g#H(J~j$|8BP1oK0byfa=KN6*1J}E!yJv!)V6=GyCeJ0X#^cs3Hx-KGK z&(S-IQuX)xJ4CVy*x)U4JPjfsZx^=`c)O_Q*dKp>bGPK1^TEzBJ!*l%W=B}{N)G-T z5nW*)ny(=YMn%Vqxdv80w@L`NFS-1$;v*mZY;;TOIZhYw+GOg47sFCln|~s@{8i>% z*oi_K7+M^>xpf(l1)HI1To!sk$C&#F#qB@&F+jLi!ma2eu%1`C)_6jUl7W~J(qhqc zJiL!CtGXkgS#V>I1~St2LQnd?D%C*|TFa%f0l(dl!Vdx#j+-(J@pxk1MRCK6dILsGytSB zEJR)(i2ox{Bvv7VBA9r~2)3?Nb3s}i9|*JpHjPTVPrZ#PruSb&gUP=vnQj4WWs*+s zHl=|a`+iZvYYoLTr$%?w;RhY2@&O6SAV>8JNr1Kv<^e&VGpD-?|I2D#tM8Nq|CN(V z*d2yldUgNFbOq7_K?dw{!@=o$5Vase;g9&BWeBF&qc0u+AsLoc$8~S8`h6Q>{k_xq z{iKW9O5jQVEa@I{j}C6Ub(Zex)B}u`b^vhu4!an9ZslLl5Io`Hhj6aKnAC8rcHr{P zREBf9K9)!SM?AgN9)zk#Fb+6qdm^{q-~s%08?*PrEG<@|VZI^Lgj7@U#jP<}k+-_D zKJ29Abqh~93n+$te=eXk8p5$Y3i3>BGuNIEI8Get}R&GEv0gi)w6u~-|AvL7RZpA=P>>znV<5@j@oN)T;KQT=LJ2= zw)6XT)A@sBk@*9%#_!Ng>w8pw#Q1($D!C)g^O7_YHf0SCZw{w;+AF|J??c z{$Z3e!9=Pu`>t5?{$GD>{lf$O{B|#wT*8CX^F#heP!MDHfn)lR?hRc3ES9N^${T?j zB;gtX4rQHFu-`bl{g;b`3dbatWD~C=>Y3)uE*j5bV?2c~i}laeE_v}EEDeKRZ~XKl z5E78JthsUhKC|Q;-pMZ4#Ib;Eca2aGrw@@)$A8w|^OhWX^>r~rMSO3wvoBFg+|?r? zKa7QB)qz#~veCAf%>sBeY+Al@=eY$1v%s9FY^o}Lj zBGJ@v*Kl(^i(f9!Z|c4W7<`y>zI+&|7us`gAh+o|y^{1FJ`yzqjC|etk)ODdtaqX+ zJLCKbUTj5-3S~DZIqS!?_Jq@+ik$B>jDjE;+Gz-rAqu`!@HJf z5w1SCpxTS=k8c#)8R%BOpO6tM1aJ`M`X(haTxyZyfcwL?am|wj=6H=kh;bf{(3`<~ zIsqKs6d$et^th^}H8(AR_dXXBOjp)4I>PluspmWwKhKPywY{@w~iX@`zv z=%B`2I$0sEZNh37JZtOcDn~wz+Hn*DubW2K6t=Fg5{nWvT7zsPtI@H1Du7``Rd=ttdOTP$n7(#z| z)}NQH@t@C`2`t%!aujo*>&v2BLek%3SP&Fjo>#k766&OguLayDOY%VEa1XOXcrfev zBx6Z<@FMArNJSD0B7dg-h_-3-vU-F|pY1OC{y&O~rbrmAf?rB9ze@9ZKf|2LJPyS( ze=)44L1fG4N2#X|C;3qa2Ki4?ZA6yX)*CDh(jxD*Do~jt;Ol>u9KQ8$p2uXC%7UI9 z8%Ej%D>dCiKCvTGZjAR}o0-We(1&ip!W%&(S2CJqH)K0G8J?$$2}7KDnL$dD^qZMe zr5;YJ)4ocQ;}RW~WuBOT9=oPg9~U0W!Z9-HpK^BL!Ly{D9C8sXe^w}_nA}j@zv%h5Ih#KL=DRh+Gx}CL<&HOBG1mTu30q65I+-0V;hjAF7eu@2P3p4~SQnmX zxhRIPxc73Sq&FIvDxPI4>(I0Aa?-DxY#7?OtNeH6i1_jBKKw?H8Yxw1ZnXR5tX$Cu zAYMMV=ChJnxL6kH|FJgTyi=?MCqiV&<`(lu703*q@o>;ug%*Z2DZi9&(>Hg+i!?wp zO!wyIwOOa>4#6b=&(7QU8O{(TESli#x37Wdu+N8=M4@M|4HLfRS_8Y8M^u2yKFgCi zgzC2yJSp((L|_%RH81PO&X3nzZ_Uc31VHHst%gTsSLdX({!R6vJb@Zd*FSn{dYJ9P zZ}=nYnFG@0*yJQ3e`6Rfp_Ok|d5Uh8$%tPH32^TL6G|J<6ZQ86Yr~)pc+RN<^HZsF z?mbGSG%tSYfhV}(7ZT56Z<^ip?YX!@`SKf|ezC^wPVj9GS*p+7RTB3?OWw0IcZ8e; zomp2Mwo2sSI%)iT7;GddB+fi4?N<%p#vTulbGd9ucS6r@<2TSjr}%U88F`uP-Yc9y z(W%@P8*xQ9rT8l2cl>0lS3ri$P69z!@+3{5G8C`XN12D_8XW|`uj3iq!aw;1vvWwC zIq6?b^}xQ!IOECfWa|7w8s81K$Ls6z1<@*arDnr*wMTe2xHDS(Mp4VXwtx4AjG88S%gC&fIKX32rvJm(@wC7ur^Tce%ECC&x;6oz99P36qZnz)G{Br=}>3Yh9@g? zGFvhENNh4yXsrv%i|10VdFUUJdj%AjE9|;z%`oRiGK*{qV-V^h;zl`WUxu0C%ZsEd zmw37K9=n^LRfqI0YSs`A36hSLPTS5YK>m}kUdq#|tEo+;>zk@WT#}~PS-bvOS=FKT zzfi#W%?fkvL6G|Njr>4l-SxL_xsv&pi(KgZH$h5e3+Qg9n|l4Tt=d1G6mwTKl-IUA zm*B~nugNEZl5eFX_Iy|hT@>!xH0r&vJYHRTm~a*#LnCs0W`0N?^v*n1Km=RUvep@>leVN^Jzr4pjaN50vV8U5beM7jB%YrcHF~3Q?G`u;lCuga6@5(80)@i)5 zRES|Akt8Kl&{@0}dIrMKl%`$X&N=DiL@ixv6t9N|^hMlqr=Q87 z1lv{#I_4|O0<9I_Baql{WMzY78CzPe3;Ev7h`ko}KkqaGEGcF)nbN;7yX7BxjO(5$ zi%0eSwB^}Xb4Mg(z?dmvTo(m%P^%VX1kd)DM5e9a*`24Mm}@4r`bdr;Ci^+_%kQ?+ zzm7D;n#4QPpzBVCUHYNKw>wh$?k8YDM z_&vU4L|b0(X}M6QV6pOp4dzbc@hVLmrgP?3-)-M*`fj%Ta&bIa&i$00pkMeCwL>dQ zpkQ_Qw9r@;CABF7O?lM`CemjmqlE@-k|E_O2?M%HP~r744j-8&735ZVy_>a1bkANR zzYmNxCuXlO0pTM}xWzNg+rvnkamlNX)JD(2M1fqxH7X1VKrCOi`7?~6f=RpkW5>+; z0|QQU3YIU&-Z*dM;Dmar2S3`KoVM?8%z$k$vDFB(-HjcR<++)azd(%i{F?uQjQQ_R zQ)YTT8{}RuODgMK^J$Bdj$3}v;n*00`&o!?muZPFDFUdW%^lQ`i!A8i&jPlecE*MC zV&|V#QbubV(4Xi@;u!N7J+VFIeT3O$jN#^*0Oh1SeVa%uwF~VD3-uXpGbSXnN3xI+o2d1XF0;Ph}HmtB(15oeGz9I`O7_MddCNdK@ z?1!M0IZq#Rs)O9?{GxHY0n+M%t1D4md5|;Kh36bgc1U^6yi{M#Z%Yv!%_4T`30RJ` zX3du7_BD|YTg(aZUY8xJfq+b$LX9H`8impFLn{NE*=VEa#8BUkh^%yca?R6j3C{~* zX@@K6ADfDi|FuMxGSlW3)<3X9T72)1E`@RRmp{wWhHR{63AemmJfqAyt^3P^^z_s$ z0m1{LwpH3M=5_SS13j?!M0 zf{UqHiS{t=o@?KC5`^=+e|l|80v%Nqn2(c_&K`TFGq*Q;GcpfrJtAYNq2e5-x}<8C z%y>?cfONB6Y3EPJ>sXY{l2Lg zg<~m)AHC4F$hSm+xsVnjt0qR3pzwm7g?o=!$8^s`kjs62^J#54W0OxIq`yZZbI2pf`Cj<;Be3@B-j(HnlHVV5}?xbAn_fHspwy4;oA zb2tMgNd?fLbT}a6!^8i`b*~ksB3JoL?b=JlOeVr(n|na=KK9dy3!zlg4|Ab#kmsA@ ziI`*QX|wJ>3`qIZiJbl|FlH6roJV{Gdwbw#13O7R`a)Zl=P$y5`>g{ z`BqawI9s8mV8NHdDh*kGGc7?sED(=0e=5d+G?fup?=TR6UbX9&S=lFL{W7wQq4;Pu z6H&Rc((!I@Gh|M&b#XQ|yw^`p70Qvcd21sVvls&M<{|BYS_eCEk0yWl zj3^X8Wtw(G=Xn(MZARs?lx32joaoo5KM ztzpt>{cF#V+NEDI534^0lKzS7$3?O2J*xcc(vM&cKSUM`PxR#ap#gDQX&V%?KWf zXrMUpX~EymzAlah3uNBD)hjXP@6dDazIMH*$ZqC+yDaXah7-=|=o8xG8vYHUHG&A1 zmju#wMvhWVD+lQJrh_+fAQ&dk2ZpprG(QHF)34Ny{;QpMj2a^ z6OEY^%c1RXYdg7b68b~39Q^cS9pM)$%8OI%Gsi6Y@h?i+sae+pRiGt1CKUu_xqKKK ztJ(7>RCtdqjztZSkv<$oDtYTP#_e_4O<^c z&N85gUN8IiOa9`+LU`BI@BEBJZ`IR(1=4r5%#+8;xyQ?(0Q96L2`y-&`ak3DC;(G@ zLJRNGj_7?36Dpfc8AtY!wLm>Tob_Qx+KLt!t&S+P)qlff9= zggX^HK|#TUYwZNIok$@|^bva*M_c8RR6C87L6QA#o%4FTI!wdwM!u>2D6T2+FTu1o zkIP!ZUzX0T1e~6oxe%ok^NMh!(^s#V5+~?NM+>IBqqtb-Dtw7m{I2DV`@)h8OOEs9 z_HG49h+Mxbp(2B2&FUH-LO~6@&($8|$z`5$5-$(1#{{*T#F|f20@rzYOH9O6u7U1O zCvzkWk?LDE2oC|KSn_RUKxp-hP_Ugg7n=D;Q|FYTmD5e>9^i15d8<}da;1|3 zoVpFjkvW5cx_uGNnEjV^6qAeZ1^mk_DW{Y!>NyRj4Q7~i1c^9qbdtuYPyA&b5CPF3 zhFevU#VKZSb&!3#xbIl79 zgdW~R*cAu%HoJr#R%f1~Px@Tr0*GkIjhQ0yS|m!utPb{WTV_SNZD&Tn_|_G$21{w{ z@4H@psMMwp)K9R9KTX);h|=a5>B$&FguK_O++-tybKYbG3^FMX;D+l_6)Jh!IZpYJ zng>}!rCD}=tsIBNXG>&Cv?)Xu(oYG#wK4TO%$660j9bjibat&1$Y z)?|%zVpf?K(WAgUU}ogf7*ha&iQYsGn*!m_0xH;Ny&oPFFB6ir6RqKFQd9|4f*87e z?-w%P%GU2$id+gDlTGEkY=1v8SOj$ftvu9tJ1>s+V&GVNff3fh54xcg%^mjB*8&60 zb_~VF>(F`HeTk_2lAPXEW)Hf93~;TTKNAKBuOXKtMaM(>mU#jDAG!8e4xScNF|oXp zYKZXf5#QMTeX3BcKZuLmyy`(P^()dum}?6yCY@608^kznPqb0?9k#RH`YC`BNkI** zi#^%1+v4HPH2cnrL@3<#%NkM#{EIKlmPye{kiHW}N0&ihq;%8O%*^R$w_Y^Mxc%Itgvl>#;4}Ts*tNw;9;a5uyZaN~ZM-#w z&iGQoKNTw!AT0czV*WrlhYEL}T+fJ8Broa=8gB5e5a(zPM$|Y?^eC!0T*&8zZQ#$HA0FU6 z5kCq3Y~*{)^ zAs*yAXC~&v@vEt(WV?mz-w6Qo#2fpdfU#z!63w*%A9`U*3%F+bV6F-zeLTao%ps z5giKW*DDhtVp4K-wQ4CiKqv^Q;xIcg$^-xMF`W;KYhxec-sR7xIT`b);~vjRsh4h2 zgOSVXvt>$^F~GXMP@?hPF# zilI-_icH=nOw~Dpqoc82m*&C|m-=R3x1peOBQd1Pl##SL&NKQ=q`OR}{}dAyJ#i|r z?Nyaw*wL$R(-7M#rMCm6vv-Z8hX(?{{j}z1&6~sW-ir+*CU`R=2>{yM%%A$Wja&^|juNflw&M2u- zj_0h?#^&fc)dmJ^vF@2UWlw=}b3n@bel5g4adV3K7izxRzFZOFyd$+>0hr=D{9Wj= z=%wb)84e;84RT&|kryWwsPh4?)7zncXe+7DMiM_u%4>q8N0#>EG8@n;b@2_uz$Ifs zPmso+1M7gtD7_erW^z`jQnA~-I*uIpiYev9O#D7sgOEt`)DUfRq-MnF7Oc?=Qml*! zp|j%|x7Lc!S-S{X&&F0j?->4EW?A*!TU`?>aZ#muqHESZl`w_A)P4WgZEM|gTwKu) z5wcsrVJA0LqWDi;lAStJPEDgCa-8InPV)jAEATO(JZ#&#sXduVJ-O=smNsA&8}_T` z+bGAQB+WnLjPQx3rH;`iibAtX+PlfssP!)>v9X})erdeVgaeQ(iS$^UKJWM>JGOz_ zAm1tN;t`O0RkmKjxl=}y_VMdqKama${n}1CT4*>UuDeFTJVH9%j4RnH#$y&&GYV#Z zG}Y0T!&k6glB6K+&zjwyrdPEm`_G|McY?8RTutp#@)W~e%kNXVV}zVwpMEyA%i}X2 zh|6*fT#*dDRiuAa0n~yu90W|^AGrAW<6T4>tcR((sz;Zz6i9$i^ZDIml-u^gH## zM}dDB6}r1?1<0%q8P}$Ct{-Eb8Qolrh#Dsj%aCzx3oIyg9BUmJu*1G^Y~-Ff3hITo z9S0w_eZFGyFFX4sQ@6xMBquTWhGO-I_^wxUOB*^g|HSIvj50$K1FJ+;Q`A`sWUEitV$tk84M7+g#ar_75-o>+t}QNbgucX)=5 z5VVV=s{j}mzt_xtSArxfAJ5-sUEAL_kelfCba|Q%i3GkxD zvQ8J{R3C=UR~5$cf5hi6Z@Y3Ip&tC5@F-S_95BXPl;e>Cp7BOEd#ku%fpwTD2ZZFp zdTCqZ0UWIa;C{!Cw!>C*76diENPp}k>4^-lf64dKMr2w$_)VSn_o~wAKY~F&7h@HM z9cI%`$p`$&aaF{%sU8pDq+_%}Kegx{zyZGtx){aF+xyF4Z5| zty?l3F%?RSUJFb#!C}98Gbo;4avobw;3K`+d(hv^kM~XXT)D172tq9-8H0>GDE+t z65y>xc~gr(!E4S7rE%PA5h8msf6H-`{Pgrlpi{Bf@NV&gAO|BNJ-l{n5-XWlLKdb_cM+Nw4lU3=VxP>@sm<@zDF|1r@WSZ#7L z=(sSBh<+5`CX|4) z34a@ z4U6g|<78tM>Vld}_liqd(?_#KR(AubckCsI$ogNtEa131<=SpZtqFcGmQqDJ7uulB zRi`)|*=DF9nRL4HQyLG+Nr(bLn!}bK>UOjt4|OmvhCgtn-Ak#;Nd}GR_Vtple~CZ` z#-CE*7I#hbkI2Z`XS%UZh({+Ai(Z3?>7cjWJHhJw=T8?iyJTu=+GHga^Lj@o(TCT( zt67S;Zz*S|-Lf@%Y6el~vESr)`&U$6O#ETP1ljg=Js8VYzBpY}`LK`O*#2ZHl1t-l zQ)6u9%Epg%WDWr1L$E*}MNO?=W3rz|qCf#E(97cWXWC5qt-&k1?P*2P*=HR>y}g^P zw#F=T-1+gE9YVlK%E1i3zk{?1Dj^`@3CtenmiUZgma;*MJg0QC8by7A;NqIBiJWQI(wo za<-4e>G8z-2|uCfxNV&yNl7p#)pRFfp??{V>6w>7S@O&GspK5Jqf`81MY#7{y14hS z`(!^BeB;39qQR@;J~3Rs_i0}E!^yzXyK?daMS ztqz0yGF7sfAaRa(hSb)RK6Ll2qdBaqf_Fk!+XfSopF%xqz?dDU0Zzx8BDY=|0|}`w zmA@-{Zbsrdj8i`Gp^7)2C4{gx{ADyC#$qnQTMT+d$)Dq9Wm8|DI5ZHPfR@B4Abk#E z*$06xom(&Wv9y@BF@??qP(VWWj!BcM`eu-UJ09Z1@jJIPB}Rp=hmP(I4c0j(vBhg> zC)F+B(PJcw6G`js!Um#4bC zy0)B(j7Sh~)*>-~B^f6R%|$fH%CB9wS}+lfdc;fZoI4K9i@!BW(!-F;C!mP#$-D2O z-_7Bp5U;pJ*7!fvw4aJ%WhH4x!^?LWD%I=sOP6+vd95*y$A~!hO*h{wI=Gj5DfPVi zSpA}>WL*R3)*h@fFvt@%?c_uNfh-a&rO^>O~bx{MCuyhO43FeRN#2l2Y z(WCFu)p+UbQf}dxG*D`0TBh4V8vIVxmK;_E?IL1lWUcJrn z2asQtqR*Mj+mr7kvwti1Zodi?{?KNuSzvzP~qN;#LT zh3Q9;N1C+*03Z2q=WCD2zBn?Vgn!sM>)R#!t+cHD81?xvx>Mk|wGzTvETi>(MN0h( z4i*QRK5^2+xI-$H_j9NI>?QrC_8mx|@@nE>4Zfm0Z}Sv1e0DANv3+WBFSQLRKex(z zJd)6_q*->i%*Vh?EAwe**8y4r)_NfM;&dl8C5pR!6bjKbyE`CJ=GdMG23fTpwo`3dvBZh+Uf$F3{u9Y^j|& z6*T$gkOtZpxBeRMo)KxVPY)KV7;9_d^j9soqRF z;r235YRNUlB)i*pL7jJj_EL8mmJwa`Y&}PxA4&F)0rTjE$;0#Wgx>a14Lt`_hu1uIkHi+ zXfU+X%YOFlqf;Eiun{hhUCt-_-2-@x8Jr5~7C|B8NY_ICBli~`97>ee*>w}nutP?7 zyEOvdY=4luc1?q^&BX+fe77NmY{M}l*m%uiWBB(RMUqXA$#|{C@^4IEW)M_vcDt@f zl3h{iiBBFD{tAH4V;zi{?bp0VPLlOQkY?3(va;jj+)|P@?$6TqM=uf%iq)|ivp&6j z#fC(KSU;73If*$|#&G7|&B?yikez$83au|^IucJQ;nr33FG;6(S;(2FA&y5=@BYy9s&nF}9 z7SA2}ew&4XOjZ|^qOeRb0891#@h?quiBye+1e9}v*@^&1BjJO(o$WR;e_K}Q6+=<` z3kl-Du8pqE^I3_*y`e4NLD8DOv`+FqDRoGBFRULaSM&@H&S$HE!Yak1u;WrQE)U>h z1=_0~$Y{nGqx`mNY-?7b4mYL=#m>X!$L*~q7A+mM7t5>Cr_Nejtx+l#rv?aqz1Z<6 z(EL$*rYH4k-87bT<{&tn*EaX1@phMwCahDtMXt6_TPiC(U61IGB|miH^|OwHoee+F zBjXb@NOA1TMOd*^Ya?#MASnLYrvVqph3=d@NT0vKR{z$}BBgb@dSk&$RzX*X(#sLW#?}oB)q6rleK)|J2 zmg4neD?yHw+8JUqKX;3ZMTapSk6T=9*j2Wb(Bj7J;pe;(!dv9 z3jBU%5@0PGIz}>=9L|{7-Q;K)E!+uG!=|48W$Mwd`kLe@q|UCW8iH6mCqutJ>dceA zc&MrFPUo252kZD1)(ZXV{l|&bg$3<#S(4&YR2&HZp-S9O9c2A#XQqMU%H5A3caAQG;&lmT(pIn? z5pc-1KWOMTj7O`PKJn(n*C!Vqz_x%6mnQxplJT;M*Ghf^59ny6gyU0i&sr1LN0^p+ zN|6?1VO^`FaSCLDCYp&{xSI&K-;s&lVw^oQ2nkB|`8g5W_Be6c95Xz*T1^W>=M;=C zWt)rk%Ay| z{T!3p1?j$HEh><@#Wy$Tocp>=2y72aspZ$mI!BspOI^xmb{+s@i!Jn?oSJ z>Y;Ek9ybE5f&is`?nAqj&{fTQfs~9tM2J$b=(lsIi}&j44>6@P+taf-y4D=6Ur=2& zBw#X+G1=9O#$NdmJ6Ep$cW~tcGL>o<$@W7|6KnZ@=dcko-E-6OV@reBip6yxeGnG% z1-<@?sddxL7far4_A3*LdI1U@*L|@=dw8A^R#aE~U|*r6_z>^UC%ZK}2%K$@BjvYf z_xff*G0q|!5Rf2tlXb65;;%_WWsQUp#ZtkO4|o-UX8Gz}9HNC>Ch0__6~&KrRDVh?1}2}pUS~z+?UwVTh^z;OyucSP z1|4xxUr;y+r-lQ85;WuUiwLGu8I*vy81`hopiKswPTM4dGD~gB#T(F3*oH2=D`?Qt z{Hn&ch7%P{Bew2ET-N^3)(=Z~b4}#(%p>{vUXs8I?D72k{6Suawhl8W$caU__@# z^;~@ModZ%;Bk5~1hnmGS`d}=P8^5+$nz1F|=8K}T+VU)9366*3+r}IiTizqYHz-!WvAl`~!WZ;?ox|!oGx| zuYcamZQis+1dBi=nTF5eyuuQ;h=DTsHTkg)4K@FW@Yact5Ldv;ysI!z1xc?vVqH+L znuC6x4ZoN(4E(5cVx$up6NK-II_bR(pgC??Xveo>wU6=Pfw!Jjj^5zj>t0j(?vrao z()}sb`IDey*O=uF^ta08md;0uJgfe9kJ1OP9vsAKZ0W}C=!@g~(74Ccz(mB9s*r?_ z*2Nh-d`nGGJ;p;7Aw%@1UZbJD6w0gSGeie(hOk%#7hP0;AGfQ3_oXZv7?|Dj1Wl_Ujx} zquw>?h;f;36aTXyjZL(xhG|J-s}`r{(PF~+K`P%kWij#Cu^TvYacW7VAvy`E`WYq0 zo7wbQiLqBF^yv)puYNF2RM!(_!J=e_S9dcO@$dMy1Pzre!G&<^`S10AOI5ip7ahW> zK#fIit68q4+DAE(LiYvE$mTo6H1_DGoY^uZ>+Hu`g?3DpbGRJNs$<}>z2Nm;$XkMiRR^n;?jx1}o9E5zK}1 zeDN7m&F}W`7})d8R#Dy3pueX43KLnJs^(#!-)rrr>#$+acMt?PvEr^lZ`WO}rOYr* zFV1*67h4oS{EpyJ#65jutKeoLOemhq_=di{g8Vgv+tn)oze8K9O zJ~9ZarGgHf2fPn(Sd78?vXB2n1*jS=1Hs5$G2yy=8y z90h5Y`+$9A#?K}zs3THmA~t-mb~V^1MRoReDj}L)uU8!@=C~>?>{TpSE}Dw(rLnI0 zEtzr&rd~?gp~(yr+fMEja7c1#E(q>`Z;EEIUSCxO7;Mgr54#7F|7+a+l0k1jG$u z$&MUp1>bfaL3X;C zkXWn|bR%H4VTx@2E1?gP68>zesdO~0Vodf&<$K>U@Ok~k=P-9NP@tD%$KOH+In~mI zr)3STy!%ptUzXPu7!vvEY5o1#A<>9Kw>EDHMQy_Y?<@2R;$hrxnfjvSoVItW0wT%E=%l`y`d6DIU05 zQ!h4p?7yOoQ>NY|#Be8?VIn*-DkNZ^URloVC{JPBE#3JMAzl$0YOPkgc)HAdr?&f4mG}PpzSJrz^155<- zeM`7ehi0aWv>HVI5g`Bdnf0Na(}rQvCmXuF<&59UH%j6HSF{ler$qMJUt;z8PZPnM zAF1)V!Cl$y7{uh*TxUoX?JFCDKy|6xYh&zfc7^3^QnL#`oJc8{~~Ld#;;ncTG?p-oq_r| z%rQ7#!?`z6+YPxazq+UMHnDnmNuSuNpswTXy5;1=kIIUkNCp(&eTq`k>c_#_iCbx` zH>+xM4v4N6ht@Ys|4Q7uvZ|YZ<3Pv*TS~Z_x{51bArERUcH@nGokNF`Z zOkW^thHok1jRrvY20*Fd!%cqSu@j><6bR?NlkyCp(CN$5hQ?`Px}%77dc19+r&grA@6Kj5GAz!S_U2 zZB}Bxs4kiu)%>mLYEAaOuUgh)i_VN!(}d?UZg^-nF$526{RHnIGd`o7y7$+#hMl$x zk-9~F@-@(GI276&4k0zxn~WvvSaUVQP>77O7czDGX)Z!H*Op}r*V=-yPa&5-!F3{o*Js;G_+*oFIJD}1SH|9*XvQ*+G|XhAY`R~ zdP#AS@^td-i}0*IV^b0v(`K@A7L#0xD;Uk{390kHcVHSdLJ8t{Uc~?(c4({z*or#$ zJaeZ7BFMFMqter_%r+Xx0U^6%`<{L;v@{X(CIA}U2?nXC-!RxTzJ(2tUVK?;mc|=j zWa*sAq4w*e4H8A8eXXWC!`)Vnb=XR5P|V|7WB{}@D`X9S+n5M5P_+G{!RkDrW9`o& z;vfudNm{|L1@L*DACb_OY(1>l35PBt#XrxFb#(#5UxeC)Xk5v!EKrms%= zvS&l9+g`e)@%HWhXzca&(0W)k>sM&$-rCvqF1?rlf*;GmM}j~lBiC*A8U&LB8f&@h z-+F&Fcgh{MFl00{`H6OC;G^qpV*;B6YnLQ?g_9)1T)G=#FO6)y0G3*54=GjhP|Xi# zgWhqulTeA+noam@REG-34y@4CUhk4}QCE55@6UDN>2*z-i-r9C6d)#hFWP?l9C+r6 z%4d3bYk8byf60_)e!hdf9fYRrvA|yxS&&KXPA*R8a@SP7=D2rPb8{#r0Q&VB8SzSc zp-&R#R7McVsSa#UUNo7^#WXAznpa~CR*>#jRY4Yh&B@y0bm^>dnRa;Y7CA8C}QF4H}WyNVz8<6cPXLwS&j*ZHtL=Y2gNX+m0f;B$x z2tJWhyPzm4p4NL6-L&p|ao*potk8@oSG~}!av2gT8{`{qP?V=Th|E)QhhX0>e4^jN zx5BIfe7wBwkfc)=Vn1CW?Cahx54$>btAYtM(symjwTyDoxs-n7Y}Q}W0ipY$yF!&(IzGjmf=KdjjW+hK4Q&`S;) zEy2#nwkQ~YZA-BCO+_wC^Ips#tp(m#_vQfQUH83|9Vy-$>e5eEQu|dti#NC`~IWq~G!GrIBzddHORjT!Y?VMc zO?-G-?fE%n$S)k<5d6Tq{aIP_Dh5lF6mq!(X>0b&|6l4BPaQ{C1uKdKKLKWJ{bphW zS)pf4HY=x;q^4z#h?}x+oG;A#n8)Vx_oICz#&sU&kHGUlzFg!B6p5D%4dwQ~JX(v0 z-sE_(%Aol_|2t=SV-}Z!PMH`R30MPsq~`sjyg5DF zkgSR?^FKhHYRy;>9KS&R;vVG{Z^K2!WN%ORkdf=dL?`XZISS4Fj#HjxplPKn_?-o*bjyJvaqg|iZa zL-J|+1xkQudvc2dIr^VL1B zO+5<-j;wL?Bo9RLI>94Fsex3n==*VXrc@Hs)#C)k1NWPPw^+V4h%=t(mep6#KW>#` z3Vg)6LH_BT69Lz(_n=Do&i@cZt@f@CSs0mql7gs7ayw*YDe0iDU{qkI`ApqRd&fWC8e`{% zPx!Aa8lf7mRKg=QDC(ClcxJHV5pH)A>|VtEJF}*Hs$*14!EjDgiLU@xv4Yv!9^O1# zV2iqEA`uaXizIz75sTJ7k>j)>6HkHS9Q6iy9D7Bdu7rGH7()KySW%81M3aCyOh{`C z|KLMCSy#wY)(y->+&D&+Yo5hL91*63R+&98@APS*_oKu7HykbMOF3N~U0%y6jYN?w&M|9IUJA;3D*m&Y$!pPpr?A^IyV$65-o&+h%$Nv@_6EO zf11j4I~in&PcR5Awvom1W#V|9-xp8Cb*x74Qfe0$2g4f(Zat}>+z^tk=h?wOOk}&% zcQif}yYPChQon&&bB^ofOgF0`;hf(lLsd+>e4OmYBlD?T&wU3(^>XjmGE;v25XPV< z$cKN)jGJ&TvFe^a0?m+f_JIRxRYN~hc4B;9;o7D^ziT<{xX<#oO8k@2TsPFu$QuO@ a`KB)*@DuUd4V-CZ$E?h3ua=wMeexe Date: Thu, 6 Aug 2020 05:37:52 -0400 Subject: [PATCH 15/28] update with new StreamExecutor C API(SE_->SP_) --- ...0200624-pluggable-device-for-tensorflow.md | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index b6b84f808..5be81a805 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -11,7 +11,7 @@ Implement a pluggable device mechanism which allows to run existing TensorFlow programs on a new device without user changing the code. Users only need to install a plugin in a specified directory, and the mechanism is able to discover and plug in the capabilities offered by the plugin. -This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77), which aims to extend the TensorFlow design to plug in capabilities like adding a new device support. The modular device interface is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). +This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77), which aims at extending the TensorFlow design to plug in capabilities like adding a new device support. The modular device interface is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). ## **Motivation** @@ -56,7 +56,7 @@ This section describes the user scenarios that are supported/unsupported for Plu * **Supported scenario**: Single PluggableDevice registered as a new device type. - In the case of installing one plugin that registers its PluggableDevice as a new device type, e.g., "XPU" device type, user can speficies the "XPU" device for ops under `with tf.device("xpu:0")`, PluggableDevice registered will be selected to run those ops. + In the case of installing one plugin that registers its PluggableDevice as a new device type, e.g., "XPU" device type, user can speficify the "XPU" device for ops under `with tf.device("xpu:0")`, PluggableDevice registered will be selected to run those ops.
@@ -68,7 +68,7 @@ This section describes the user scenarios that are supported/unsupported for Plu * **Non-Supported scenario**: Multiple PluggableDevices registered as the same device type. - In the case of installing multiple plugins that registers PluggableDevice as the same device type, e.g., more than one plugin registers its PluggableDevice as "GPU" device type, these plugins's initialization will fail due to registration conflict. User needs to select which platform they want to use(either unloads the conflicting plugin or reconfigures the plugin with python API). + In the case of installing multiple plugins that registers PluggableDevice as the same device type, e.g., more than one plugin registers its PluggableDevice as "GPU" device type, these plugins's initialization will fail due to registration conflict. User needs to manually select which platform they want to use(either unloads the conflicting plugin or reconfigures the plugin with python API).
@@ -79,7 +79,7 @@ This section describes the user scenarios that are supported/unsupported for Plu Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the Plugin to instantiate a StreamExecutor Platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type string is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type of GPU for identification. +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the plugin to instantiate a StreamExecutor platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers the platform to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and then registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type for identification. Plugin authors need to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { @@ -134,14 +134,15 @@ Proper: DeviceFactory::Register(type_str, new PluggableDeviceFactory(platform_name_str), priority); ``` For those vendors who don't want to use "GPU" type, it's optional to register a new device type. -**Limitation**: when multiple plugins are installed, their registered device types should be different, or it will get conflict and the device registration will fail. TensorFlow proper can provide a python API to let user select a plugin by specifing a higher priority. +**Limitation**: when multiple plugins are installed, their registered device types should be different, or it will get conflict and the device registration will fail. TensorFlow proper can provide a python API to let user select a plugin by specifing a higher priority. +For example: ``` tf.load_plugin_with_highest_priority(path_to_plugin_lib) with tf.device("/GPU:0"): ... ``` -When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name / subdevice type registered from plugin, then stream executor platform (`se::platform`) further creates or finds a `StreamExecutor` object containing a `PluggableDeviceExecutor`, and multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. +When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name / subdevice type registered from plugin, then stream executor platform (`se::platform`) further creates or finds a `StreamExecutor` object containing a `PluggableDeviceExecutor`, as well as multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. The section below shows some pseudo code to introduce some extensions inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). @@ -168,14 +169,17 @@ The section below shows some pseudo code to introduce some extensions inside the ... } // create StreamExecutor ``` -3. `PluggableDevicePlatform` is responsible for the StreamExecutor creation. It creates an `SE_StreamExecutor` and `SE_Device` object through create_stream_executor and create_device which are registered in the `SE_Platform`. Then `PluggableDeviceExecutor` is constructed with `SE_StreamExecutor` and `SE_Device` object. +3. `PluggableDevicePlatform` is responsible for the StreamExecutor creation. It creates an `SP_StreamExecutor` and `SP_Device` object through create_stream_executor and create_device which are registered in the `SE_Platform`. Then `PluggableDeviceExecutor` is constructed with `SP_StreamExecutor` and `SP_Device` object. ```cpp StreamExecutor* PluggableDevicePlaform::ExeutorForDevice(int device_id) { auto config = get_plugin_config(device_id); SE_Options* se_option = get_se_option(device_id); - SE_StreamExecutor* se= platform_->create_stream_executor(); - SE_Device* sd = platform_->create_device(se_options) - auto executor = absl::make_unique(this, absl::make_unique(config, se, sd)); + TF_Status* status = TF_NewStatus(); + SP_StreamExecutor* se = new SP_StreamExecutor{ SP_STREAMEXECUTOR_STRUCT_SIZE }; + platform_->create_stream_executor(se, status); // create SP_StreamExecutor + SP_Device* se_device = new SP_Device{ SP_DEVICE_STRUCT_SIZE }; + platform_->create_device(se_device, se_options, status);//create SP_Device + auto executor = absl::make_unique(this, absl::make_unique(config, se, se_device)); return std::move(executor); } ``` @@ -194,44 +198,41 @@ Two sets of classes need to be defined in TensorFlow proper. * class `PluggableDeviceContext`: a wrapper of pluggable device specific context that can be passed to OpKernels. * Set 2: `PluggableDevicePlatform` related classes * class `PluggableDevicePlatform`: PluggableDevice-specific platform, its platform name is "PluggableDevice", it contains a C struct: SE_Platform* platform_ which is its internal implementation and as the C interface registered by device plugin. - * class `PluggableDeviceExecutor`: PluggableDevice-platform implementation of the platform-agnostic StreamExecutorInterface, it contains C structs: SE_StreamExecutor* executor_ and SE_Device* device_ whose member can be accessed in both TensorFlow proper and device plugins. - * class `PluggableDeviceStream`: wraps a StreamHandle in order to satisfy the platform-independent StreamInterface. It returns SE_Stream which is treated as an opaque type to TensorFlow, whose structure is created by the device plugin. + * class `PluggableDeviceExecutor`: PluggableDevice-platform implementation of the platform-agnostic StreamExecutorInterface, it contains C structs: SP_StreamExecutor* executor_ and SP_Device* device_ whose member can be accessed in both TensorFlow proper and device plugins. + * class `PluggableDeviceStream`: wraps a StreamHandle in order to satisfy the platform-independent StreamInterface. It returns SP_Stream which is treated as an opaque type to TensorFlow, whose structure is created by the device plugin. * class `PluggableDeviceTimer`: wraps an opaque handle: SE_Timer to satisfy the platform-independent TimerInterface. * class `PluggableDeviceEvent`: wraps an opaque handle: SE_Event to satisfy the platform-independent EventInterface. **TensorFlow Plugin** Plugin authors need to provide those C functions implementation defined in StreamExecutor C API . -* `SE_StreamExecutor` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. Plugin creates the SE_StreamExecutor and registers its C API implementations to the SE_StreamExecutor. +* `SP_StreamExecutor` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. TensorFlow proper creates a SP_StreamExecutor object and pass it to the plugin, then plugin fills it with its C API implementations. ```cpp - SE_StreamExecutor* create_stream_executor(TF_Status* status) { - SE_StreamExecutor* se_nfs = new SE_StreamExecutor{ SE_STREAMEXECUTOR_STRUCT_SIZE }; + void create_stream_executor(SP_StreamExecutor* se, TF_Status* status) { se->memcpy_from_host = my_device_memory_from_host_function; se->allocate = my_allocate_function; … }//Init device ``` -* `SE_Device` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. Plugin creates the SE_Device and fills its device opaque handle and device name to the SE_Device. +* `SP_Device` is defined as struct in the C API, both sides(TensorFlow proper and plugins) can access its members. TensorFlow proper creates a SP_Device with device ordinal and plugin fills the corresponding device opaque handle and device name to the SP_Device. ```cpp - SE_Device* create_device(SE_Options* options, TF_Status* status) { - SE_Device* se = new SE_Device( SE_DEVICE_STRUCT_SIZE ); - se->device_handle = get_my_device_handle(); + create_device(SP_Device* device, SE_Options* options, TF_Status* status) { + device->device_handle = get_my_device_handle(SE_Options); ... return se; - } + } ``` -* `SE_Stream` is defined in plugin and treated as an opaque struct in TensorFlow proper. +* `SP_Stream` is defined in plugin and treated as an opaque struct in TensorFlow proper. ```cpp - void create_stream(SE_Device* executor, SE_Stream* stream, TF_Status*) { - *stream = new SE_Stream_st(); - (*stream)->stream_handle = create_my_stream_handle(executor); + void create_stream(SP_Device* device, SP_Stream* stream, TF_Status*) { + (stream)->stream_handle = create_my_stream_handle(device); .. } ``` ### PluggableDevice kernel registration -This RFC shows an example of kernel registration for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). +This section shows an example of kernel registration for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). To avoid kernel registration conflict with existing GPU(CUDA) kernels, plugin author needs to provide a device type(such as "GPU") as well as a subdevice type(such as "INTEL_GPU") to TensorFlow proper for kernel registration and dispatch. The device type indicates the device the kernel runs on, the subdevice type is for low-level specialization of the device. ```cpp @@ -256,13 +257,13 @@ void InitKernelPlugin() { ### Using stream inside PluggableDevice kernel -The following code shows a convolution kernel implementation using the stream handle. The streams are created during the pluggable device creation. The placer decides which device to use for each OP in the graph. Then the streams associated with the device are used to construct the OpKernelContext for the op computation during the graph execution. +The following code shows a convolution kernel implementation using the stream handle. The streams are created during the pluggable device creation. The placer decides which device to use for each OP in the graph. Then the streams associated with the device are used to construct the OpKernelContext for the op computations during the graph execution. ```cpp void Conv_Compute(TF_OpKernelContext*) { - TF_GetInput(context, input_index, &input, &status); + (context, input_index, &input, &status); TF_GetInput(context, filter_index, &filter, &status); auto output = TF_AllocateOutput(context, output_index, TF_Float32, dims, num_dims, len, status); - SE_Stream se_stream = TF_GetStream(TF_OpKernelContext); + SP_Stream* se_stream = TF_GetStream(TF_OpKernelContext); auto native_stream = static_cast(se_stream->stream_handle); my_conv_impl(input, filter, output, native_stream); } From 81481f8fcc09f8c9b3444ee3e92a34dd4ed73bdc Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Mon, 10 Aug 2020 05:28:47 -0400 Subject: [PATCH 16/28] fix typo --- ...0200624-pluggable-device-for-tensorflow.md | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 5be81a805..99a1c5d1f 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -9,7 +9,7 @@ ## **Objective** -Implement a pluggable device mechanism which allows to run existing TensorFlow programs on a new device without user changing the code. Users only need to install a plugin in a specified directory, and the mechanism is able to discover and plug in the capabilities offered by the plugin. +Implement a pluggable device mechanism which allows to run existing TensorFlow programs on a new device without user changing the code. Users only need to install the plugin in a specified directory, and the mechanism is able to discover and plug in the capabilities offered by the plugin. This RFC is based on the Modular TensorFlow [RFC](https://github.com/tensorflow/community/pull/77), which aims at extending the TensorFlow design to plug in capabilities like adding a new device support. The modular device interface is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). @@ -39,7 +39,7 @@ This RFC extends the TensorFlow device class hierarchy to add a standardized plu * `PluggableDeviceExecutor Implementation` is inside the TensorFlow plugin, which provides those C functions implementation defined in the StreamExecutor C API. -The pluggable device mechanism contains device discovery and creation process which creates a `PluggableDevice` object and `PluggableDeviceExecutor` object for each pluggable device. +The pluggable device mechanism contains device discovery and creation process. Device discovery process loads the plugin and registers the device type as well as [DeviceFactory](https://github.com/tensorflow/tensorflow/blob/24e203fa08feee48c766b15eaa3afcc912324437/tensorflow/core/common_runtime/device_factory.h#L30). Device creation process creates a `PluggableDevice` object and finds or creates a `PluggableDeviceExecutor` object for each pluggable device. With the RFC, existing TensorFlow GPU programs can run on a plugged device without user changing the code. The Diagram 2 describes the workflow of TensorFlow with device plugin, it shows how a simple GPU program runs on the pluggable device.
@@ -77,21 +77,18 @@ This section describes the user scenarios that are supported/unsupported for Plu ### Device Discovery -Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. +Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to the default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the plugin to instantiate a StreamExecutor platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers the platform to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and then registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type for identification. +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the plugin to instantiate a StreamExecutor platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers the platform to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and then registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices` or `tf.config.experimental.get_device_details`) to get the subdevice type for identification. Plugin authors need to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { - static const int32_t plugin_id_value = 123; - SE_PlatformId id{ SE_PLATFORMID_STRUCT_SIZE }; - id.id = &plugin_id_value; int32_t visible_device_count = get_plugin_device_count(); std::string name = "My_GPU"; //StreamExecutor platform name && subdevice type std::string type = "GPU"; // device type - params.params.id = id; + params.params.id = plugin_id_value; params.params.visible_device_count = visible_device_count; params.params.create_device = create_device; params.params.destroy_device = destroy_device; @@ -103,7 +100,7 @@ void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* statu params.params.type_len = type.size(); } ``` -`ListPhysicalDevice` encodes the subdevice type string to the device type string. +`ListPhysicalDevice` encodes the subdevice type string to the device type string. ```cpp Status PluggableDeviceFactory::ListPhysicalDevices(std::vector* devices) { se::Platform* platform = se::MultiPlatformManager::PlatformWithName(sub_device_type_); @@ -114,6 +111,12 @@ Status PluggableDeviceFactory::ListPhysicalDevices(std::vector* devices) return Status::OK(); } ``` +`GetDeviceDetails` encodes the subdevice type string and it can be queried by `tf.config.experimental.get_device_details`. +Status PluggableDeviceFactory::GetDeviceDetails(int device_index, std::unordered_map* details) { + ... + (*details)["subdevice_type"] = sub_device_type_; + ... +} ### Device Creation @@ -141,8 +144,7 @@ For example: with tf.device("/GPU:0"): ... ``` - -When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugin device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name / subdevice type registered from plugin, then stream executor platform (`se::platform`) further creates or finds a `StreamExecutor` object containing a `PluggableDeviceExecutor`, as well as multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. +When a session is created, `PluggableDeviceFactory` creates a `PluggableDevice` object for the plugged device. During the initialization of the `PluggableDevice`, a global object `se::MultiPlatformManager` will find the `se::platform` through its platform name / subdevice type which is registered from plugin, then stream executor platform (`se::platform`) further creates or finds a `StreamExecutor` object containing a `PluggableDeviceExecutor`, as well as multiple stream objects(a computation stream and several memory copy streams) supporting the `StreamExecutor` objects. The section below shows some pseudo code to introduce some extensions inside the TensorFlow proper for the pluggable device creation. The implementation is based on StreamExecutor C API [RFC](https://github.com/tensorflow/community/pull/257). @@ -169,7 +171,7 @@ The section below shows some pseudo code to introduce some extensions inside the ... } // create StreamExecutor ``` -3. `PluggableDevicePlatform` is responsible for the StreamExecutor creation. It creates an `SP_StreamExecutor` and `SP_Device` object through create_stream_executor and create_device which are registered in the `SE_Platform`. Then `PluggableDeviceExecutor` is constructed with `SP_StreamExecutor` and `SP_Device` object. +3. `PluggableDevicePlatform` is responsible for the StreamExecutor creation. It creates an `SP_StreamExecutor` and `SP_Device` object through create_stream_executor and create_device which are registered in the `SP_Platform`. Then `PluggableDeviceExecutor` is constructed with `SP_StreamExecutor` and `SP_Device` object. ```cpp StreamExecutor* PluggableDevicePlaform::ExeutorForDevice(int device_id) { auto config = get_plugin_config(device_id); @@ -191,17 +193,17 @@ Two sets of classes need to be defined in TensorFlow proper. * Set 1: `PluggableDevice` related classes * class `PluggableDevice`: a class represents a set of new third-party devices, its device_type attribute describes what kind of device this is. it can be "GPU" or other device type string. it also has an attribute: subdevice_type, subdevice_type is for low-level specialization of GPU device. It will be part of kernel dispatch key to avoid conflict issue with exiting GPU(CUDA) kernels. The subdevice_type is also used to check whether there is some CUDA specific logic code in grappler and common runtime when the device type is "GPU". * class `PluggableDeviceFactory`: a device factory to create the PluggableDevice - * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm.It extends the BFC algorithm, counter part of GPUBFCAllocator. + * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm. It extends the BFC algorithm, counter part of GPUBFCAllocator. * class `PluggableDeviceAllocator`: an allocator that wraps a PluggableDevice allocator. * class `PluggableDeviceHostAllocator`: allocator for pinned CPU RAM that is made known to PluggableDevice for the purpose of efficient DMA with PluggableDevice. * class `PluggableDeviceEventMgr`: an object to keep track of pending Events in the StreamExecutor streams. * class `PluggableDeviceContext`: a wrapper of pluggable device specific context that can be passed to OpKernels. * Set 2: `PluggableDevicePlatform` related classes - * class `PluggableDevicePlatform`: PluggableDevice-specific platform, its platform name is "PluggableDevice", it contains a C struct: SE_Platform* platform_ which is its internal implementation and as the C interface registered by device plugin. - * class `PluggableDeviceExecutor`: PluggableDevice-platform implementation of the platform-agnostic StreamExecutorInterface, it contains C structs: SP_StreamExecutor* executor_ and SP_Device* device_ whose member can be accessed in both TensorFlow proper and device plugins. - * class `PluggableDeviceStream`: wraps a StreamHandle in order to satisfy the platform-independent StreamInterface. It returns SP_Stream which is treated as an opaque type to TensorFlow, whose structure is created by the device plugin. - * class `PluggableDeviceTimer`: wraps an opaque handle: SE_Timer to satisfy the platform-independent TimerInterface. - * class `PluggableDeviceEvent`: wraps an opaque handle: SE_Event to satisfy the platform-independent EventInterface. + * class `PluggableDevicePlatform`: PluggableDevice-specific platform, its platform name is registered from plugin through `SE_InitializePlugin`, the platform object contains a C struct: SP_Platform* platform_, which is its internal implementation and as the C interface registered by device plugin. + * class `PluggableDeviceExecutor`: The PluggableDevice implementation of the StreamExecutorInterface functionality, it contains two C structs: SP_StreamExecutor* executor_ and SP_Device* device_ , which as the StreamExecutor C interface registered by device plugin. + * class `PluggableDeviceStream`: wraps a StreamHandle in order to satisfy the platform-independent StreamInterface. It returns SP_Stream which is treated as an opaque type to TensorFlow, whose structure is defined by the device plugin. + * class `PluggableDeviceTimer`: wraps an opaque handle: SP_Timer to satisfy the platform-independent TimerInterface. + * class `PluggableDeviceEvent`: wraps an opaque handle: SP_Event to satisfy the platform-independent EventInterface. **TensorFlow Plugin** @@ -232,8 +234,7 @@ Plugin authors need to provide those C functions implementation defined in Strea ### PluggableDevice kernel registration -This section shows an example of kernel registration for PluggableDevice. Kernel and op registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). - +This section shows an example of kernel registration for PluggableDevice. Kernel registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). To avoid kernel registration conflict with existing GPU(CUDA) kernels, plugin author needs to provide a device type(such as "GPU") as well as a subdevice type(such as "INTEL_GPU") to TensorFlow proper for kernel registration and dispatch. The device type indicates the device the kernel runs on, the subdevice type is for low-level specialization of the device. ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { From 6c0fc36fde5fbefe3989c28951d8b134d36feaa4 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Mon, 10 Aug 2020 22:36:37 -0400 Subject: [PATCH 17/28] update date --- rfcs/20200624-pluggable-device-for-tensorflow.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 99a1c5d1f..7b8fba484 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -5,7 +5,7 @@ | **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | -| **Updated** | 2020-07-31 | +| **Updated** | 2020-08-11 | ## **Objective** @@ -112,12 +112,13 @@ Status PluggableDeviceFactory::ListPhysicalDevices(std::vector* devices) } ``` `GetDeviceDetails` encodes the subdevice type string and it can be queried by `tf.config.experimental.get_device_details`. +``` Status PluggableDeviceFactory::GetDeviceDetails(int device_index, std::unordered_map* details) { ... (*details)["subdevice_type"] = sub_device_type_; ... } - +``` ### Device Creation `PluggableDeviceFactory` is introduced to create the `PluggableDevice`, following the [LocalDevice](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/common_runtime/local_device.h) design pattern. To support existing GPU programs running on a new device without user changing the code, plugin authors can register the "GPU" device type through `SE_InitializePlugin` and then TensorFlow proper will register the `PluggableDeviceFactory` for "GPU" type with higher priority than the default GPU device. From 8257245572d5496b98bf5d4f3ac3ac5a755c4924 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Wed, 12 Aug 2020 05:57:14 -0400 Subject: [PATCH 18/28] add front-end mirroring mechanism --- ...0200624-pluggable-device-for-tensorflow.md | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 7b8fba484..9145b1028 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -73,13 +73,45 @@ This section describes the user scenarios that are supported/unsupported for Plu
- +### Front-end Mirroring mechanism +This section describes the front-end mirroring mechanism for python users, pointing at previous user scenarios. +* **device type** + Device type is user visible and controllable. User can specify the device type for the ops. e.g, specify device type as "gpu", "xpu". + ``` + >> with tf.device("/gpu:0"): + ... + >> with tf.device("/xpu:0"): + ... + ``` + user need to manually select a platform when multiple plugins register the same device type, or the plugins initilaization will fail due to device type conflict. e.g, when multiple plugins register the "GPU" device type ,user need to set a higher priority for the plugin. + ``` + >> tf.load_plugin_with_highest_priority(path_to_plugin_lib) + >> with tf.device("/gpu:0") + ... + ``` +* **subdevice type** + Subdevice type is user visible but not controllable. User can query the subdevice type of the device if he wants to know whether the GPU device is NVIDIA_GPU or INTEL_GPU, through [tf.config.experimental.list_physical_devices()](https://www.tensorflow.org/api_docs/python/tf/config/list_physical_devices). + + ``` + >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) + >> print(gpu_device) + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `INTEL_GPU`] + ``` +* **real device name** + Real device name is user visible but not controllable. User can query the real device name(e.g. "Titan V") for the specified device instance through [tf.config.experimental.get_device_details()](https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details). + ``` + >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) + >> if gpu_device: + details = tf.config.experimental.get_device_details(gpu_device[0]) + print(details.get(`device_name`)) + "TITAN_V, XXX" + ``` ### Device Discovery Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to the default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the plugin to instantiate a StreamExecutor platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers the platform to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and then registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices` or `tf.config.experimental.get_device_details`) to get the subdevice type for identification. +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the plugin to instantiate a StreamExecutor platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers the platform to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and then registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type for identification. user can also use `tf.config.get_device_details` to get the real device name(e.g. "TITAN V")for the specified device. Plugin authors need to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { @@ -111,11 +143,12 @@ Status PluggableDeviceFactory::ListPhysicalDevices(std::vector* devices) return Status::OK(); } ``` -`GetDeviceDetails` encodes the subdevice type string and it can be queried by `tf.config.experimental.get_device_details`. +`GetDeviceDetails` retrieves the physical device name of the hardware from plugin. ``` Status PluggableDeviceFactory::GetDeviceDetails(int device_index, std::unordered_map* details) { - ... - (*details)["subdevice_type"] = sub_device_type_; + se::Platform* platfom = se::MultiPlatformManager::PlatformWithName(sub_device_type_); + auto desc = platform->DescriptionForDevice(device_index).ConsumeValueOrDie(); + (*details)["device_name"] = desc->name(); // Titan V: XXX ... } ``` From 43be4ab6d490a4760bbf2544d37963f9f97fa1a7 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Wed, 12 Aug 2020 05:58:03 -0400 Subject: [PATCH 19/28] update date --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 9145b1028..6bd66177d 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -5,7 +5,7 @@ | **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | -| **Updated** | 2020-08-11 | +| **Updated** | 2020-08-12 | ## **Objective** From 27d5b275165c5cf91ba286160d32b9df03729728 Mon Sep 17 00:00:00 2001 From: jiangzho Date: Wed, 12 Aug 2020 21:20:48 +0800 Subject: [PATCH 20/28] update scenario1 desc --- .../scenario1.png | Bin 14441 -> 15974 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png index 566310bda7e0774ea0cb6ed84e2b0e06de6b2b33..2f1af3af9f2b2ff9e8e450402ac3efbbec486d5a 100644 GIT binary patch literal 15974 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 From 18258c01bdb8120c08053793581b893d47087c7a Mon Sep 17 00:00:00 2001 From: jiangzho Date: Wed, 12 Aug 2020 21:31:42 +0800 Subject: [PATCH 21/28] update scenario 3 desc --- ...0200624-pluggable-device-for-tensorflow.md | 4 ++-- .../scenario3.png | Bin 20494 -> 20766 bytes 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 6bd66177d..5e62507bf 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -5,7 +5,7 @@ | **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | -| **Updated** | 2020-08-12 | +| **Updated** | 2020-08-11 | ## **Objective** @@ -62,7 +62,7 @@ This section describes the user scenarios that are supported/unsupported for Plu * **Supported scenario**: Multiple PluggableDevices registered as different device types. - In the case of installing multiple plugins that register PluggableDevices as different device types, e.g., one is registered as "GPU" device type and another is registered as "XPU" device type, these PluggableDevices can be registered successfully and user can specify the registered device type to run ops on different hardware. + In the case of installing multiple plugins that register PluggableDevices as different device types, e.g., one is registered as "GPU" device type and another is registered as "XPU" device type, these PluggableDevices can be registered successfully and user can specify the registered device type to run ops on different hardware. Same as scenario1, when one PluggableDevice is registered as "GPU" device type, the default GPUDevice will be overrided.
diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario3.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario3.png index f741ab5170e9e847a48ce3cd5db69fdccda562f7..a8427938014c77cb4ea53af11e816d10fddfdca3 100644 GIT binary patch delta 17863 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 w#c!dd{Q%#Gp*vwHP>oqe2qA!xwqC@Hh|02t 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 oFmxvj1*$R22qAFaQ7m07*qoM6N<$f)me}U;qFB From eb83a479d91eb55e0b2e1d0ca25bba0a41e1d3bf Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Wed, 12 Aug 2020 22:58:25 -0400 Subject: [PATCH 22/28] update front-end mirroring mechanisim --- ...0200624-pluggable-device-for-tensorflow.md | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 6bd66177d..417c5ab86 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -5,7 +5,7 @@ | **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | -| **Updated** | 2020-08-12 | +| **Updated** | 2020-08-11 | ## **Objective** @@ -62,7 +62,7 @@ This section describes the user scenarios that are supported/unsupported for Plu * **Supported scenario**: Multiple PluggableDevices registered as different device types. - In the case of installing multiple plugins that register PluggableDevices as different device types, e.g., one is registered as "GPU" device type and another is registered as "XPU" device type, these PluggableDevices can be registered successfully and user can specify the registered device type to run ops on different hardware. + In the case of installing multiple plugins that register PluggableDevices as different device types, e.g., one is registered as "GPU" device type and another is registered as "XPU" device type, these PluggableDevices can be registered successfully and user can specify the registered device type to run ops on different hardware. Same with scenario1, when one PluggableDevice is registered as "GPU" device type, the default GPUDevice will be overrided.
@@ -75,30 +75,38 @@ This section describes the user scenarios that are supported/unsupported for Plu ### Front-end Mirroring mechanism This section describes the front-end mirroring mechanism for python users, pointing at previous user scenarios. -* **device type** - Device type is user visible and controllable. User can specify the device type for the ops. e.g, specify device type as "gpu", "xpu". +* **device type && subdevice type** + Device type is user visible and controllable. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". Subdevice type is user visible and user specify which subdevice to use for the device type(mirroring), e.g.("NVIDIA_GPU", "INTEL_GPU", "AMD_GPU"). ``` >> with tf.device("/gpu:0"): ... >> with tf.device("/xpu:0"): ... ``` - user need to manually select a platform when multiple plugins register the same device type, or the plugins initilaization will fail due to device type conflict. e.g, when multiple plugins register the "GPU" device type ,user need to set a higher priority for the plugin. +* **Front-end mirroring** + In the case of two GPUs in the same system, e.g. NVIDIA GPU + INTEL GPU and installing the Intel GPU plugin. + * **Option 1** + Only plugged gpu device is visible, PluggableDevice overrides GPUDevice. If user want to use CUDA device, he need to uninstall the plugin + ``` + >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) + >> print(gpu_device) + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `INTEL_GPU`] + >> with tf.device("/gpu:0"): + .. // place ops on PluggableDevice(Intel GPU) + ``` + * **Option 2** + Both plugged gpu device and default gpu device are visible, but only one gpu can work at the same time, plugged gpu device is default enabled, if user want to use CUDA device, he need to call mirroring API(set_sub_device_mapping()) to switch to CUDA device. ``` - >> tf.load_plugin_with_highest_priority(path_to_plugin_lib) - >> with tf.device("/gpu:0") - ... - ``` -* **subdevice type** - Subdevice type is user visible but not controllable. User can query the subdevice type of the device if he wants to know whether the GPU device is NVIDIA_GPU or INTEL_GPU, through [tf.config.experimental.list_physical_devices()](https://www.tensorflow.org/api_docs/python/tf/config/list_physical_devices). - - ``` - >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) - >> print(gpu_device) - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `INTEL_GPU`] + >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) + >> print(gpu_device) + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `INTEL_GPU`, enabled] + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `NVIDIA_GPU`, not-enabled] + >> tf.config.set_subdevice_mapping("NVIDIA_GPU") + >> with tf.device("/gpu:0"): + .. // place ops on GPUDevice(NVIDIA GPU) ``` -* **real device name** - Real device name is user visible but not controllable. User can query the real device name(e.g. "Titan V") for the specified device instance through [tf.config.experimental.get_device_details()](https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details). +* **physical device name** + physical device name is user visible. User can query the physical device name(e.g. "Titan V") for the specified device instance through [tf.config.experimental.get_device_details()](https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details). ``` >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) >> if gpu_device: From c4542bf7d63028f195f954061994b5ece0a228f7 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Wed, 12 Aug 2020 22:58:51 -0400 Subject: [PATCH 23/28] update date --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 417c5ab86..1a38e3922 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -5,7 +5,7 @@ | **RFC #** | [262](https://github.com/tensorflow/community/pull/262)| | **Author(s)** | Zhoulong Jiang (zhoulong.jiang@intel.com), Yiqiang Li (yiqiang.li@intel.com), Eric Lin (eric.lin@intel.com), Jianhui Li (jian.hui.li@intel.com) | | **Sponsor** | Anna Revinskaya (annarev@google.com) | -| **Updated** | 2020-08-11 | +| **Updated** | 2020-08-13 | ## **Objective** From 5030d99853e1c105feb8d26862cce60a1d746492 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Wed, 12 Aug 2020 22:59:46 -0400 Subject: [PATCH 24/28] fix desc --- rfcs/20200624-pluggable-device-for-tensorflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 1a38e3922..ccf17ba4a 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -76,7 +76,7 @@ This section describes the user scenarios that are supported/unsupported for Plu ### Front-end Mirroring mechanism This section describes the front-end mirroring mechanism for python users, pointing at previous user scenarios. * **device type && subdevice type** - Device type is user visible and controllable. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". Subdevice type is user visible and user specify which subdevice to use for the device type(mirroring), e.g.("NVIDIA_GPU", "INTEL_GPU", "AMD_GPU"). + Device type is user visible. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". Subdevice type is user visible and user can specify which subdevice to use for the device type(mirroring), e.g.("NVIDIA_GPU", "INTEL_GPU", "AMD_GPU"). ``` >> with tf.device("/gpu:0"): ... From e9ed21064e0ef80d47c8b47becb3edad89c90b5f Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Thu, 13 Aug 2020 00:09:25 -0400 Subject: [PATCH 25/28] update front-end mirroring description --- rfcs/20200624-pluggable-device-for-tensorflow.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index e16b7933e..0364b0d29 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -75,7 +75,7 @@ This section describes the user scenarios that are supported/unsupported for Plu ### Front-end Mirroring mechanism This section describes the front-end mirroring mechanism for python users, pointing at previous user scenarios. -* **device type && subdevice type** +* **Device type && Subdevice type** Device type is user visible. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". Subdevice type is user visible and user can specify which subdevice to use for the device type(mirroring), e.g.("NVIDIA_GPU", "INTEL_GPU", "AMD_GPU"). ``` >> with tf.device("/gpu:0"): @@ -86,16 +86,16 @@ This section describes the front-end mirroring mechanism for python users, point * **Front-end mirroring** In the case of two GPUs in the same system, e.g. NVIDIA GPU + INTEL GPU and installing the Intel GPU plugin. * **Option 1** - Only plugged gpu device is visible, PluggableDevice overrides GPUDevice. If user want to use CUDA device, he need to uninstall the plugin + Only plugged gpu device is visible, PluggableDevice(INTEL GPU) overrides the default GPUDevice(NVIDIA GPU). If user want to use NVIDIA GPU, he needs to manually uninstall the plugin. ``` >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) >> print(gpu_device) [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `INTEL_GPU`] >> with tf.device("/gpu:0"): - .. // place ops on PluggableDevice(Intel GPU) + >> .. // place ops on PluggableDevice(Intel GPU) ``` * **Option 2** - Both plugged gpu device and default gpu device are visible, but only one gpu can work at the same time, plugged gpu device is default enabled, if user want to use CUDA device, he need to call mirroring API(set_sub_device_mapping()) to switch to CUDA device. + Both plugged gpu device and default gpu device are visible, but only one gpu can work at the same time, plugged gpu device is default enabled, if user want to use NVIDIA GPU, he need to call mirroring API(set_sub_device_mapping()) to switch to NVIDIA gpu device. ``` >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) >> print(gpu_device) @@ -103,15 +103,15 @@ This section describes the front-end mirroring mechanism for python users, point [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `NVIDIA_GPU`, not-enabled] >> tf.config.set_subdevice_mapping("NVIDIA_GPU") >> with tf.device("/gpu:0"): - .. // place ops on GPUDevice(NVIDIA GPU) + >> .. // place ops on GPUDevice(NVIDIA GPU) ``` -* **physical device name** +* **Physical device name** physical device name is user visible. User can query the physical device name(e.g. "Titan V") for the specified device instance through [tf.config.experimental.get_device_details()](https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details). ``` >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) >> if gpu_device: - details = tf.config.experimental.get_device_details(gpu_device[0]) - print(details.get(`device_name`)) + >> details = tf.config.experimental.get_device_details(gpu_device[0]) + >> print(details.get(`device_name`)) "TITAN_V, XXX" ``` From 0b3feb369060e36606e65f0baf7d80b015d97d47 Mon Sep 17 00:00:00 2001 From: Zhoulong Date: Thu, 13 Aug 2020 03:00:56 -0400 Subject: [PATCH 26/28] front-end mirroring -> device mapping --- ...0200624-pluggable-device-for-tensorflow.md | 51 ++++++++++-------- .../scenario1.png | Bin 15974 -> 14532 bytes .../scenario3.png | Bin 20766 -> 20671 bytes .../scenario4.png | Bin 19838 -> 19703 bytes 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 0364b0d29..d49997458 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -50,7 +50,7 @@ With the RFC, existing TensorFlow GPU programs can run on a plugged device witho This section describes the user scenarios that are supported/unsupported for PluggableDevice. * **Supported scenario**: Single PluggableDevice registered as "GPU" device type - In the case of installing one plugin that registers its PluggableDevice as "GPU" device type, the default GPUDevice will be invalid when the plugin is loaded. When user specifies the "GPU" device for ops under `with tf.device("gpu:0")`, PluggableDevice registered will be selected to run those ops. + In the case of installing one plugin that registers its PluggableDevice as "GPU" device type, the default GPUDevice will be overrided by PluggableDevice when the plugin is loaded. When user specifies the "GPU" device for ops under `with tf.device("gpu:0")`, PluggableDevice registered will be selected to run those ops.
@@ -68,43 +68,49 @@ This section describes the user scenarios that are supported/unsupported for Plu * **Non-Supported scenario**: Multiple PluggableDevices registered as the same device type. - In the case of installing multiple plugins that registers PluggableDevice as the same device type, e.g., more than one plugin registers its PluggableDevice as "GPU" device type, these plugins's initialization will fail due to registration conflict. User needs to manually select which platform they want to use(either unloads the conflicting plugin or reconfigures the plugin with python API). + In the case of installing multiple plugins that registers PluggableDevice as the same device type, e.g., more than one plugin registers its PluggableDevice as "GPU" device type, these plugins's initialization will fail due to registration conflict. Users need to manually select which platform they want to use(either unloads the conflicting plugin or reconfigures the plugin with python API).
-### Front-end Mirroring mechanism -This section describes the front-end mirroring mechanism for python users, pointing at previous user scenarios. +### Device mapping mechanism +This section describes the device mapping mechanism for python users, pointing at previous user scenarios. * **Device type && Subdevice type** - Device type is user visible. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". Subdevice type is user visible and user can specify which subdevice to use for the device type(mirroring), e.g.("NVIDIA_GPU", "INTEL_GPU", "AMD_GPU"). + Device type is user visible. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". Subdevice type is user visible and user can specify which subdevice to use for the device type(device mapping), e.g.("NVIDIA_GPU", "INTEL_GPU", "AMD_GPU"). ``` >> with tf.device("/gpu:0"): ... >> with tf.device("/xpu:0"): ... ``` -* **Front-end mirroring** - In the case of two GPUs in the same system, e.g. NVIDIA GPU + INTEL GPU and installing the Intel GPU plugin. - * **Option 1** - Only plugged gpu device is visible, PluggableDevice(INTEL GPU) overrides the default GPUDevice(NVIDIA GPU). If user want to use NVIDIA GPU, he needs to manually uninstall the plugin. +* **Device mapping** + In the case of two GPUs in the same system, e.g. NVIDIA GPU + X GPU and installing the X GPU plugin. + * **Option 1**: override CUDA device, only plugged gpu device visible + Only plugged gpu device is visible, PluggableDevice(X GPU) overrides the default GPUDevice(CUDA GPU). If users want to use CUDA GPU, they need to manually uninstall the plugin. ``` >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) >> print(gpu_device) - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `INTEL_GPU`] + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `X_GPU`] >> with tf.device("/gpu:0"): - >> .. // place ops on PluggableDevice(Intel GPU) + >> .. // place ops on PluggableDevice(X GPU) + ``` + * **Option 2**: both visible, but plugged gpu is default, user can set the device mapping + Both plugged gpu device and default gpu device are visible, but only one gpu can work at the same time, plugged gpu device is default enabled(with higher priority), if users want to use NVIDIA GPU, they need to call device mapping API(set_sub_device_mapping()) to switch to CUDA device. ``` - * **Option 2** - Both plugged gpu device and default gpu device are visible, but only one gpu can work at the same time, plugged gpu device is default enabled, if user want to use NVIDIA GPU, he need to call mirroring API(set_sub_device_mapping()) to switch to NVIDIA gpu device. - ``` >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) >> print(gpu_device) - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `INTEL_GPU`, enabled] - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `NVIDIA_GPU`, not-enabled] + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `X_GPU`, enabled] + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `NVIDIA_GPU`, disabled] + >> tf.config.set_subdevice_mapping("NVIDIA_GPU") + >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) + >> print(gpu_device) + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `X_GPU`, disabled] + [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `NVIDIA_GPU`, enabled] + >> with tf.device("/gpu:0"): >> .. // place ops on GPUDevice(NVIDIA GPU) - ``` + ``` * **Physical device name** physical device name is user visible. User can query the physical device name(e.g. "Titan V") for the specified device instance through [tf.config.experimental.get_device_details()](https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details). ``` @@ -114,18 +120,17 @@ This section describes the front-end mirroring mechanism for python users, point >> print(details.get(`device_name`)) "TITAN_V, XXX" ``` - ### Device Discovery Upon initialization of TensorFlow, it uses platform independent `LoadLibrary()` to load the dynamic library. The plugin library should be installed to the default plugin directory "…python_dir.../site-packages/tensorflow-plugins". The modular tensorflow [RFC](https://github.com/tensorflow/community/pull/77) describes the process of loading plugins. -During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the plugin to instantiate a StreamExecutor platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers the platform to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and then registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the user cares whether he is running on Intel/NVIDIA GPU, he can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type for identification. user can also use `tf.config.get_device_details` to get the real device name(e.g. "TITAN V")for the specified device. +During the plugin library initialization, TensorFlow proper calls the `SE_InitializePlugin` API (part of StreamExecutor C API) to retrieve nescessary informations from the plugin to instantiate a StreamExecutor platform([se::platform](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/platform.h#L93) class) and registers the platform to a global object [se::MultiPlatformManager](https://github.com/tensorflow/tensorflow/blob/cb32cf0f0160d1f582787119d0480de3ba8b9b53/tensorflow/stream_executor/multi_platform_manager.h#L82), TensorFlow proper gets a device type and a subdevice type from plugin through `SE_InitializePlugin` and then registers the `PluggableDeviceFactory`with the registered device type. The device type string will be used to access PluggableDevice with tf.device() in python layer. The subdevice type is used for low-level specialization of GPU device(kernel, StreamExecutor, common runtime, grapper, placer..). If the users care whether they are running on NVIDIA GPU or X GPU(Intel GPU, Amd GPU..), they can call python API (such as `tf.config.list_physical_devices`) to get the subdevice type for identification. user can also use `tf.config.get_device_details` to get the real device name(e.g. "TITAN V")for the specified device. Plugin authors need to implement `SE_InitializePlugin` and provide the necessary informations: ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { int32_t visible_device_count = get_plugin_device_count(); - std::string name = "My_GPU"; //StreamExecutor platform name && subdevice type + std::string name = "X_GPU"; //StreamExecutor platform name && subdevice type std::string type = "GPU"; // device type params.params.id = plugin_id_value; @@ -277,20 +282,20 @@ Plugin authors need to provide those C functions implementation defined in Strea ### PluggableDevice kernel registration This section shows an example of kernel registration for PluggableDevice. Kernel registration and implementation API is addressed in a separate [RFC](https://github.com/tensorflow/community/blob/master/rfcs/20190814-kernel-and-op-registration.md). -To avoid kernel registration conflict with existing GPU(CUDA) kernels, plugin author needs to provide a device type(such as "GPU") as well as a subdevice type(such as "INTEL_GPU") to TensorFlow proper for kernel registration and dispatch. The device type indicates the device the kernel runs on, the subdevice type is for low-level specialization of the device. +To avoid kernel registration conflict with existing GPU(CUDA) kernels, plugin author needs to provide a device type(such as "GPU") as well as a subdevice type(such as "X_GPU") to TensorFlow proper for kernel registration and dispatch. The device type indicates the device the kernel runs on, the subdevice type is for low-level specialization of the device. ```cpp void SE_InitializePlugin(SE_PlatformRegistrationParams* params, TF_Status* status) { ... std::string type = "GPU" // front-end visible device type params.params.type = type.c_str(); - std::string name = "INTEL_GPU"; // low-level specialization device type + std::string name = "X_GPU"; // low-level specialization device type params.params.type = name.c_str(); ... } void InitKernelPlugin() { TF_KernelBuilder* builder = TF_NewKernelBuilder(/*op_name*/"Convolution", "GPU", //"GPU" is device type - "INTEL_GPU", &Conv_Create, &Conv_Compute, &Conv_Delete); // "INTEL_GPU" is sub device type + "X_GPU", &Conv_Create, &Conv_Compute, &Conv_Delete); // "X_GPU" is sub device type TF_Status* status = TF_NewStatus(); TF_RegisterKernelBuilder(/*kernel_name*/"Convolution", builder, status); if (TF_GetCode(status) != TF_OK) { /* handle errors */ } diff --git a/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png b/rfcs/20200624-pluggable-device-for-tensorflow/scenario1.png index 2f1af3af9f2b2ff9e8e450402ac3efbbec486d5a..a8030e52d1bea44d06a9d3b772bc6f2a84236af3 100644 GIT binary patch literal 14532 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 h?|lk7Zy~r#(PF{f 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�k-r23 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 Date: Thu, 13 Aug 2020 23:32:49 -0400 Subject: [PATCH 27/28] modify front-end usage for pluggable device according to the review meeting --- ...0200624-pluggable-device-for-tensorflow.md | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index d49997458..6973f7222 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -73,20 +73,22 @@ This section describes the user scenarios that are supported/unsupported for Plu -### Device mapping mechanism -This section describes the device mapping mechanism for python users, pointing at previous user scenarios. -* **Device type && Subdevice type** - Device type is user visible. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". Subdevice type is user visible and user can specify which subdevice to use for the device type(device mapping), e.g.("NVIDIA_GPU", "INTEL_GPU", "AMD_GPU"). +### Front-end device string +This section describes the device string for python users, pointing at previous user scenarios. +* **Device type** + Device type is user visible. User can specify the device type for the ops. e.g, "gpu", "xpu", "cpu". ``` >> with tf.device("/gpu:0"): ... >> with tf.device("/xpu:0"): ... ``` -* **Device mapping** - In the case of two GPUs in the same system, e.g. NVIDIA GPU + X GPU and installing the X GPU plugin. - * **Option 1**: override CUDA device, only plugged gpu device visible - Only plugged gpu device is visible, PluggableDevice(X GPU) overrides the default GPUDevice(CUDA GPU). If users want to use CUDA GPU, they need to manually uninstall the plugin. +* **Subdevice type** + Subdevice type is user visible, aims at differentiating gpu devices between different hardware vendors. Users can query the subdevice type if they want to know whether the GPU device is "NVIDIA_GPU", "INTEL_GPU" or "AMD_GPU", by invoking [list_physical_devices](https://www.tensorflow.org/api_docs/python/tf/config/list_physical_devices). + + This RFC proposes that plugged gpu device will override the default gpu device. When users invoke `list_physical_devices("GPU")`, they can only see the plugged gpu device. If users want to use default GPU device(CUDA), they need to manually uninstall the plugin. + + For example, in the case of two GPUs in the same system, e.g. NVIDIA GPU + X GPU and installing the X GPU plugin, only X GPU is visible and usable. If users want to use Nvidia GPU, they need to uninstall the X GPU plugin. ``` >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) >> print(gpu_device) @@ -94,23 +96,6 @@ This section describes the device mapping mechanism for python users, pointing a >> with tf.device("/gpu:0"): >> .. // place ops on PluggableDevice(X GPU) ``` - * **Option 2**: both visible, but plugged gpu is default, user can set the device mapping - Both plugged gpu device and default gpu device are visible, but only one gpu can work at the same time, plugged gpu device is default enabled(with higher priority), if users want to use NVIDIA GPU, they need to call device mapping API(set_sub_device_mapping()) to switch to CUDA device. - ``` - >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) - >> print(gpu_device) - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `X_GPU`, enabled] - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `NVIDIA_GPU`, disabled] - - >> tf.config.set_subdevice_mapping("NVIDIA_GPU") - >> gpu_device = tf.config.experimental.list_physical_devices(`GPU`) - >> print(gpu_device) - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `X_GPU`, disabled] - [PhysicalDevice(name = `physical_device:GPU:0`), device_type = `GPU`, subdevice_type = `NVIDIA_GPU`, enabled] - - >> with tf.device("/gpu:0"): - >> .. // place ops on GPUDevice(NVIDIA GPU) - ``` * **Physical device name** physical device name is user visible. User can query the physical device name(e.g. "Titan V") for the specified device instance through [tf.config.experimental.get_device_details()](https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details). ``` From 492ca0bb740f0f54afd1755a811b81dcadcff983 Mon Sep 17 00:00:00 2001 From: "Jiang, Zhoulong" Date: Thu, 3 Sep 2020 01:36:14 -0400 Subject: [PATCH 28/28] update MemAllocator --- rfcs/20200624-pluggable-device-for-tensorflow.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/20200624-pluggable-device-for-tensorflow.md b/rfcs/20200624-pluggable-device-for-tensorflow.md index 6973f7222..8b798b4ba 100644 --- a/rfcs/20200624-pluggable-device-for-tensorflow.md +++ b/rfcs/20200624-pluggable-device-for-tensorflow.md @@ -226,7 +226,8 @@ Two sets of classes need to be defined in TensorFlow proper. * class `PluggableDevice`: a class represents a set of new third-party devices, its device_type attribute describes what kind of device this is. it can be "GPU" or other device type string. it also has an attribute: subdevice_type, subdevice_type is for low-level specialization of GPU device. It will be part of kernel dispatch key to avoid conflict issue with exiting GPU(CUDA) kernels. The subdevice_type is also used to check whether there is some CUDA specific logic code in grappler and common runtime when the device type is "GPU". * class `PluggableDeviceFactory`: a device factory to create the PluggableDevice * class `PluggableDeviceBFCAllocator`: a PluggableDevice memory allocator that implements a ‘best fit with coalescing’ algorithm. It extends the BFC algorithm, counter part of GPUBFCAllocator. - * class `PluggableDeviceAllocator`: an allocator that wraps a PluggableDevice allocator. + * class `PluggableDeviceMemAllocator`: Suballocator for GPU memory. + * class `PluggableDeviceProcessState`: manages per-process state when PluggableDevices are present * class `PluggableDeviceHostAllocator`: allocator for pinned CPU RAM that is made known to PluggableDevice for the purpose of efficient DMA with PluggableDevice. * class `PluggableDeviceEventMgr`: an object to keep track of pending Events in the StreamExecutor streams. * class `PluggableDeviceContext`: a wrapper of pluggable device specific context that can be passed to OpKernels.