You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Feature Request: plug-in support for new devices #4359
We need to be able to support using new devices in TensorFlow without requiring editing the TensorFlow binary source code.
This requires a few changes. Among many others:
The ability to dynamically register an implementation / factories for alternative devices
New or better APIs for allowing OpKernel's to use arbitrary device resources (e.g., right now we assume either the use of StreamExecutor or EigenDevice implementations, but that's obviously not general).
By dynamically, I assume you are referring to at compile time, but self-contained code changes. If you mean at runtime via dynamic module loading, I don't know off hand how to do this, but I think we can figure it out.
How do you wish to handle the kernel code? Should I plan to have new hardware supported by developers modifying the tensorflow/core/kernels/* and registering the new hardware? Or do you want this also separated. Although this currently is handled with macros and registration systems in the C++, but I ask from a build perspective. We could make a new folder that contains all the code for extra devices and contains all the build scripts over there. It this is done, I can write template files that have all the basic structural code (many projects do this for configuration scripts) that a user could basically rename and be ready to go.
Yes, I do mean self-contained code changes: a user should be able to use an existing binary install of TensorFlow that doesn't contain the custom device code, but then call a function to "load a device" and then another function to "load the kernels" for that device.
The latter is already possible for existing devices via the tf.load_op_library() mechanism, so theoretically something similar could be done for a new tf.load_device().
I'll answer your other question on SO, but I don't think the answer there will be instructive for new devices. Every device has its own execution model, and so the way the device code is written needs to take into account the execution model. On CPU, the ThreadPoolDevice is just a device that uses Eigen's multi-threaded CPU support to implement operations. On GPU, we have to manage streams and execution order carefully because GPU devices are asynchronous, not synchronous.
If you told us more about the execution model of your hardware, we might be able to suggest something more relevant -- it's likely that copying the CPU or the GPU way of doing things is not the right solution.
I need to talk with my supervisor before giving too many details. Here is the public info, but as a high level understanding, the chip is basically a cluster computer reduced to fit on a single chip. The many DSPs need to work together to do anything useful.
I didn't know about the operation interface! Its pretty awesome and I definitely think that is what I want to build.
It would seem that at a minimum, a developer would need to write an Allocator, Device, DeviceFactory, and DeviceContext. This would give a non-functional device because there are no kernels registered to it. As I was developing, I noticed that some kernels seemed to be core functions like ConstOp, AssignOp, NoOp, etc. that are needed for other things to work. It would seem that a user wouldn't want to code these explicitly as they are kind of obvious and redundant. Do you think these can/should be automatically built into the framework so that every device at least has these working out of the box?
Yes, those four pieces are the minimal requirements just to get the basics working, and then you'd have to register kernels for every op that was supported.
You're right that some ops are probably trivially implementable as long as some of the basics above are implemented. We'd have to think about how to 'auto register kernels' for all devices. However, for things like NoOp, it shouldn't be too hard: it would just be an include and a registration like in
How do I add headers to be installed into the TensorFlow include path? And how about for the .proto files? I need to "compile" them to headers and have them installed
I need several framework and common_runtime headers to compile device code. I tried to look through the Bazel files, but couldn't find anything obvious.
EDIT:
I tried a workaround of just setting my include path to the root of the Git repo, but found that things like #include "unsupported/Eigen/CXX11/Tensor" are being included. These would seem to be unnecessary for a "general" device. What should I do about this? I figure my options are: just include them (what I will do for the mean time), or write stripped down headers that contain only the minimal necessary to construct a device. IMO the second option is nicer for long term, but certainly more annoying to do.
EDIT 2:
Also, the Eigen files don't appear to have header guards and cause recurrent includes
FYI. I got a fake device working with contexts, factories, kernels and all, so I will begin to try to make it self contained. I posted various questions to StackOverflow and things. Those are more educational. The library problem above is more important for the purposes of this issue.
I got all the code down to one file with no changes to the main code base except the below. I list my solution ideas, but they involve modification of the main code so I wanted to make sure its okay.
To get rid of this one, I was thinking of using the existing registration, but that means exposing this function. I could just make the underlying std::unordered_map a private/protected member of the DeviceFactory class and then make DeviceNameUtils a friend. Or just expose the function handle in the header.
diff --git a/tensorflow/python/framework/device.py b/tensorflow/python/framework/device.py
index 8f5125d..505c2a9 100644
--- a/tensorflow/python/framework/device.py
+++ b/tensorflow/python/framework/device.py
@@ -155,7 +155,9 @@ class DeviceSpec(object):
elif ly == 2 and y[0] == "task":
self.task = y[1]
elif ((ly == 1 or ly == 2) and
- ((y[0].upper() == "GPU") or (y[0].upper() == "CPU"))):
+ ((y[0].upper() == "GPU") or
+ (y[0].upper() == "CPU") or
+ (y[0].upper() == "KPU"))):
if self.device_type is not None:
raise ValueError("Cannot specify multiple device types: %s" % spec)
self.device_type = y[0].upper()
If there is Python access to the same interface as above, then I can just use that. I am a fan of less configuration.
I think if you name your device "/device:KPU:0" instead of just "/KPU:0", it should work without any additional edits. "/cpu:0" and "/gpu:0" were shortcuts before we realized it was helpful to have the "/device" qualifier like we do for the other parts of a full device name. Can you give that a try and let me know?
Once I figure out how to get those includes and compile it outside of TF, I will submit a pull request within documentation a la "Adding an Op"
After that. I would like to discuss how to best support my particular hardware. My company probably wants this to be private as I will need to tell you details, so would you be willing to do it over email and not as a GitHub Issue?
Manually adding symlinks to for all those above headers tothe TensorFlow source, I still get this error,
g++ -std=c++11 -shared fpu_device.cc -o fpu.so -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include
In file included from /usr/local/lib/python2.7/dist-packages/tensorflow/include/tensorflow/core/common_runtime/local_device.h:19:0,
from fpu_device.cc:10:
/usr/local/lib/python2.7/dist-packages/tensorflow/include/tensorflow/core/common_runtime/device.h: In member function ‘std::string tensorflow::Device::DebugString() const’:
/usr/local/lib/python2.7/dist-packages/tensorflow/include/tensorflow/core/common_runtime/device.h:139:74: error: ‘ProtoDebugString’ was not declared in this scope
string DebugString() const { return ProtoDebugString(device_attributes_); }
^
fpu_device.cc: In member function ‘virtual tensorflow::Status tensorflow::FPUDevice::MakeTensorFromProto(const tensorflow::TensorProto&, tensorflow::AllocatorAttributes, tensorflow::Tensor*)’:
fpu_device.cc:84:41: error: ‘ProtoDebugString’ was not declared in this scope
ProtoDebugString(tensor_proto));
ProtoDebugString() is generated by tensorflow/tools/proto_text, which is a bit like protoc but for a minimal footprint class instead of a full protobuf one.
@CUinNYC indeed, that doc is a great attempt to show it's done, and that's the basic idea of how the scaffolding works, but the implementation details for every new hardware device means it takes care to figure out exactly how to implement the device code.
We're working with a few external folks such as those in this thread to provide some early guidance as to how it's done, and at some point we'll have some nice examples to point at (beyond say, our CPU and GPU implementations).
@RichDubielzig: the dataflow model of TF execution means that any operations that can be run at a given time will be run by the executor (assuming there are enough inter-op-parallelism threads to run them). So yes, it is possible to have multiple "matmul" nodes running at once if both can be run at the same time.
On CPU, we use Eigen for a lot of the computation, and each op has a shared threadpool on which to execute -- so those threads will typically always be busy if there's lots of op work to do (though I admit, it probably won't be super efficient to context switch all the time, but I digress).
On GPU, we use GPU streams to enqueue asyncrhronous ops: execution of an OpKernel just queues the work to be done, and we use streams to enforce execution order at the actual device.
@vrv I really appreciate the initial documentation. Thanks to that, I successfully created a "fake CPU" (really easy indeed). It required minor changes because of the TensorFlow code evolution (r1.0). So having an external-module approach would be more portable. Let me know if you need a help or feedback comments on a preliminary documentation.
In particular, I am interested in how to allocate memory for accelerators. My goal is to reduce as much as possible memory copies.
If anybody has comments or examples I will really appreciate that.
Just following up on my question: I am seeing results with this approach:
Define my operation as an AsyncOpKernel. Kernel pseudocode:
ComputeAsync( context input, DoneCallback done):
Get input tensors A & B from context
Also get a Knureon command queue handle Q from the DeviceContext object.
Figure out the dimensions of the tensors
Assemble a call f(A,B,dims) to my Knureon matrix multipllier algorithm
Queue the tuple (f(), Q, done) in the launch queue.
Return immediately
In launch queue, in a separate thread:
(as part of init routine, we have created our own dedicated ThreadPool, which includes a thread to run this wait/spawn loop)
Wait for resources R to run f()
schedule a task to run f() on R resources using Q and calling done().
In the task scheduled above:
deploy f() to R and kick it off.
wait on Q to finish
release R back to the Knureon environment
call the AsyncOpKernel done() callback
exit
Posted another question on StackOverflow regarding my confusion with DeviceContext and when it needs to be used. I am revisiting the issue because it turns out we have a smaller limit than I thought on open queues with the system and I'm wondering if I should
redesign with the idea of mapping one DeviceContext per queue
keep with the current design of one DeviceContext per OpKernelContext and manage queues in a separate pool as they are needed.
Another issue we have run into in attempting to debug XLA: It doesn't seem like we are able to exercise any backend compiler when running in a debugger. The issue is here:
Automatically closing due to lack of recent activity. Please update the issue when new information becomes available, and we will reopen the issue. Thanks!
Activity
aodhan-domhnaill commentedon Sep 14, 2016
Few questions.
About Feature Request
By dynamically, I assume you are referring to at compile time, but self-contained code changes. If you mean at runtime via dynamic module loading, I don't know off hand how to do this, but I think we can figure it out.
How do you wish to handle the kernel code? Should I plan to have new hardware supported by developers modifying the
tensorflow/core/kernels/*and registering the new hardware? Or do you want this also separated. Although this currently is handled with macros and registration systems in the C++, but I ask from a build perspective. We could make a new folder that contains all the code for extra devices and contains all the build scripts over there. It this is done, I can write template files that have all the basic structural code (many projects do this for configuration scripts) that a user could basically rename and be ready to go.About TensorFlow
TensorFlow Device Contexts, Streams, and Context Switching
vrv commentedon Sep 14, 2016
Yes, I do mean self-contained code changes: a user should be able to use an existing binary install of TensorFlow that doesn't contain the custom device code, but then call a function to "load a device" and then another function to "load the kernels" for that device.
The latter is already possible for existing devices via the tf.load_op_library() mechanism, so theoretically something similar could be done for a new tf.load_device().
I'll answer your other question on SO, but I don't think the answer there will be instructive for new devices. Every device has its own execution model, and so the way the device code is written needs to take into account the execution model. On CPU, the ThreadPoolDevice is just a device that uses Eigen's multi-threaded CPU support to implement operations. On GPU, we have to manage streams and execution order carefully because GPU devices are asynchronous, not synchronous.
If you told us more about the execution model of your hardware, we might be able to suggest something more relevant -- it's likely that copying the CPU or the GPU way of doing things is not the right solution.
aodhan-domhnaill commentedon Sep 14, 2016
I need to talk with my supervisor before giving too many details. Here is the public info, but as a high level understanding, the chip is basically a cluster computer reduced to fit on a single chip. The many DSPs need to work together to do anything useful.
I didn't know about the operation interface! Its pretty awesome and I definitely think that is what I want to build.
It would seem that at a minimum, a developer would need to write an Allocator, Device, DeviceFactory, and DeviceContext. This would give a non-functional device because there are no kernels registered to it. As I was developing, I noticed that some kernels seemed to be core functions like ConstOp, AssignOp, NoOp, etc. that are needed for other things to work. It would seem that a user wouldn't want to code these explicitly as they are kind of obvious and redundant. Do you think these can/should be automatically built into the framework so that every device at least has these working out of the box?
vrv commentedon Sep 14, 2016
Yes, those four pieces are the minimal requirements just to get the basics working, and then you'd have to register kernels for every op that was supported.
You're right that some ops are probably trivially implementable as long as some of the basics above are implemented. We'd have to think about how to 'auto register kernels' for all devices. However, for things like NoOp, it shouldn't be too hard: it would just be an include and a registration like in
tensorflow/tensorflow/core/kernels/no_op.cc
Line 21 in 5df4c71
aodhan-domhnaill commentedon Sep 14, 2016
How do I add headers to be installed into the TensorFlow include path? And how about for the .proto files? I need to "compile" them to headers and have them installed
I need several framework and common_runtime headers to compile device code. I tried to look through the Bazel files, but couldn't find anything obvious.
EDIT:
I tried a workaround of just setting my include path to the root of the Git repo, but found that things like
#include "unsupported/Eigen/CXX11/Tensor"are being included. These would seem to be unnecessary for a "general" device. What should I do about this? I figure my options are: just include them (what I will do for the mean time), or write stripped down headers that contain only the minimal necessary to construct a device. IMO the second option is nicer for long term, but certainly more annoying to do.EDIT 2:
Also, the Eigen files don't appear to have header guards and cause recurrent includes
aodhan-domhnaill commentedon Sep 15, 2016
FYI. I got a fake device working with contexts, factories, kernels and all, so I will begin to try to make it self contained. I posted various questions to StackOverflow and things. Those are more educational. The library problem above is more important for the purposes of this issue.
Thanks a bunch!
vrv commentedon Sep 15, 2016
At the moment, unsupported/Eigen/CXX11/Tensor is required by the framework -- it's how you can map our tensor.h Tensor into typed and shaped tensors.
Adding @keveman about how to add TF headers to include path. I thought https://github.com/tensorflow/tensorflow/blob/master/tensorflow/g3doc/get_started/os_setup.md#setting-up-tensorflow-for-development might be relevant, but I'm not sure.
aodhan-domhnaill commentedon Sep 16, 2016
I got all the code down to one file with no changes to the main code base except the below. I list my solution ideas, but they involve modification of the main code so I wanted to make sure its okay.
To get rid of this one, I was thinking of using the existing registration, but that means exposing this function. I could just make the underlying
std::unordered_mapa private/protected member of the DeviceFactory class and then make DeviceNameUtils a friend. Or just expose the function handle in the header.If there is Python access to the same interface as above, then I can just use that. I am a fan of less configuration.
vrv commentedon Sep 16, 2016
I think if you name your device "/device:KPU:0" instead of just "/KPU:0", it should work without any additional edits. "/cpu:0" and "/gpu:0" were shortcuts before we realized it was helpful to have the "/device" qualifier like we do for the other parts of a full device name. Can you give that a try and let me know?
aodhan-domhnaill commentedon Sep 16, 2016
Yup that worked!
Once I figure out how to get those includes and compile it outside of TF, I will submit a pull request within documentation a la "Adding an Op"
After that. I would like to discuss how to best support my particular hardware. My company probably wants this to be private as I will need to tell you details, so would you be willing to do it over email and not as a GitHub Issue?
vrv commentedon Sep 16, 2016
Adding @petewarden
aodhan-domhnaill commentedon Sep 19, 2016
@keveman
I have been manually adding headers to my include path and I need,
Manually adding symlinks to for all those above headers tothe TensorFlow source, I still get this error,
petewarden commentedon Sep 19, 2016
ProtoDebugString() is generated by tensorflow/tools/proto_text, which is a bit like protoc but for a minimal footprint class instead of a full protobuf one.
aodhan-domhnaill commentedon Sep 19, 2016
Thanks. Do you know how I can get it to be recognized when I am compiling it into an external module?
I am currently running
58 remaining items
vrv commentedon Feb 24, 2017
@CUinNYC indeed, that doc is a great attempt to show it's done, and that's the basic idea of how the scaffolding works, but the implementation details for every new hardware device means it takes care to figure out exactly how to implement the device code.
We're working with a few external folks such as those in this thread to provide some early guidance as to how it's done, and at some point we'll have some nice examples to point at (beyond say, our CPU and GPU implementations).
@RichDubielzig: the dataflow model of TF execution means that any operations that can be run at a given time will be run by the executor (assuming there are enough inter-op-parallelism threads to run them). So yes, it is possible to have multiple "matmul" nodes running at once if both can be run at the same time.
On CPU, we use Eigen for a lot of the computation, and each op has a shared threadpool on which to execute -- so those threads will typically always be busy if there's lots of op work to do (though I admit, it probably won't be super efficient to context switch all the time, but I digress).
On GPU, we use GPU streams to enqueue asyncrhronous ops: execution of an OpKernel just queues the work to be done, and we use streams to enforce execution order at the actual device.
@hawkinsp might have more to say here.
GiuseppeDiGuglielmo commentedon Feb 24, 2017
@vrv I really appreciate the initial documentation. Thanks to that, I successfully created a "fake CPU" (really easy indeed). It required minor changes because of the TensorFlow code evolution (r1.0). So having an external-module approach would be more portable. Let me know if you need a help or feedback comments on a preliminary documentation.
In particular, I am interested in how to allocate memory for accelerators. My goal is to reduce as much as possible memory copies.
If anybody has comments or examples I will really appreciate that.
RichDubielzig commentedon Feb 28, 2017
Just following up on my question: I am seeing results with this approach:
RichDubielzig commentedon Mar 9, 2017
Asked a new question on StackOverflow about the ConstTensor, which doesn't map to the DMAHelper::base() function:
http://stackoverflow.com/questions/42707600/tensorflow-how-to-get-pointer-to-data-contents-of-consttensor
RichDubielzig commentedon Mar 18, 2017
Posted another question on StackOverflow regarding my confusion with DeviceContext and when it needs to be used. I am revisiting the issue because it turns out we have a smaller limit than I thought on open queues with the system and I'm wondering if I should
http://stackoverflow.com/questions/42869340/tensorflow-why-does-a-gpu-device-only-have-one-device-context-and-does-it-rea
RichDubielzig commentedon Apr 13, 2017
Another issue we have run into in attempting to debug XLA: It doesn't seem like we are able to exercise any backend compiler when running in a debugger. The issue is here:
#9194
vrv commentedon Apr 13, 2017
@RichDubielzig I've been told that XLA-related development discussion has been moved to https://groups.google.com/forum/#!forum/xla-dev -- you might get more help / feedback there now.
RichDubielzig commentedon May 30, 2017
Asked a new question related to this on stackoverflow. Will also post to the xla-dev forum
https://stackoverflow.com/questions/44271775/can-tensorflow-xla-be-supported-by-plug-ins
tensorflowbutler commentedon Dec 20, 2017
It has been 14 days with no activity and this issue has an assignee.Please update the label and/or status accordingly.
tensorflowbutler commentedon Jan 4, 2018
It has been 14 days with no activity and this issue has an assignee.Please update the label and/or status accordingly.
shivaniag commentedon Jan 8, 2018
Automatically closing due to lack of recent activity. Please update the issue when new information becomes available, and we will reopen the issue. Thanks!