-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Wrapped TF C APIs as runtime functions, so that IRGen can generate calls to them #19555
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…lls to them. This patch "disolved" the call to the packaged experimental C API TF_RunConstOp() into a set of finer-grained C API calls. An alternative to the runtime function approach is to use Clang importer to call the C APIs. Clang importer allows the C APIs to become available as SIL functions with the same names, and thus can be found and called in IRGen via silModule.findFunction() (see https://github.com/apple/swift/blob/ad7def2c6bffd95d62e5e665c9faed0f8dac49f5/lib/IRGen/IRGenSIL.cpp#L1956-L1957). The concerns with this approach are: 1. There are no precedents in IRGen that calls C APIs via this clang importer route. Having IRGen depend on clang importer may not be desirable. 2. Some C APIs cannot be found via clang importer (e.g. TF_NewStatus() can be found, but TFE_NewOp() cannot). As a work-around, these C APIs must be called in the user module code first, before IRGen over the user module can locate them as SIL functions. This feels brittle. 3. When passing objects between compiler runtime entry points (swift functions) such as @_silgen_name("_swift_tfc_GetGlobalEagerContext") and the C APIs, IRGen have to issue bitcast to convert between a real struct type (e.g. TFE_Context*) and void* (i8* in LLVM type) as used in the entry points. In contrast, the wrapped runtime functions consistently use void* in their interfaces, so that IRGen need not do such bitcasts. One downside with the runtime function approach is that we need to wrap ~20 TF C APIs in the new files TensorFlow.{h,cpp}. This is however mostly a one-time eng cost.
@swift-ci please test tensorflow |
auto *TFNewStatusFn = IGM.getTF_NewStatusFn(); | ||
auto status = Builder.CreateCall(TFNewStatusFn, {}); | ||
|
||
if (opInfo.getOperationName() != "Const") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Special casing the Const op is just a temporary thing for your incremental development, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup! There was a TODO above.
auto opNameValAddr = llvm::ConstantExpr::getInBoundsGetElementPtr( | ||
global->getValueType(), global, indices); | ||
return opNameValAddr; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic all LGTM,
include/swift/Runtime/TensorFlow.h
Outdated
//===----------------------------------------------------------------------===// | ||
|
||
#include "swift/Runtime/Config.h" | ||
#include <stdint.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<cstdint>
please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
ARGS(Int32Ty), | ||
ATTRS(NoUnwind)) | ||
|
||
FUNCTION(TF_DeleteTensor, swift_tfc_TF_DeleteTensor, C_CC, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, why do you need these wrappers? Why can't you just declare TF_DeleteTensor here with the void*
flavored signature? It should all work out due to the behavior of getRuntimeFunction calling LLVM's getOrCreateFunction API that returns the function or does a bitcast if an incompatible signature is already present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Things like _swift_tfc_CheckOk that are not trivial wrappers definitely will require entries here, I'm just asking about the trivial wrappers like the TF_DeleteTensor case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try this and report back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I'm able to remove the decls and impls in TensorFlow.{h,cpp}. The entries in RuntimeFunctions.def remain, so that we can call them in IRGen (e.g. via IGM.getTF_DeleteTensorFn()
). Thanks!
lib/IRGen/IRGenSIL.cpp
Outdated
IGM.getAddrOfSILFunction(getContextSilFn, NotForDefinition); | ||
assert(getContextFn); | ||
llvm::Function *getContextFn = | ||
findFunction("_swift_tfc_GetGlobalEagerContext", silModule); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you plan to move this one to runtimefunctions as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can I change those swift functions to be callable as runtime functions (e.g. i need to access swift objects like _ExecutionContext.global.eagerContext
somehow)?
If we can do that, we may not need #19556, which would be nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just make sure they have a convention(c) calling convention and public linkage and you can call them as C functions!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple change:
Replace @_silgen_name
with @_cdecl
and you'll get C-convention functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. Converted some entry points to @_cdecl
, and added runtime function entries for them.
For the remaining ones, I'll convert them once we use the new AnyTensorHandle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thank you for going down this path. I agree it is a pain given the number of runtime functions we have to deal with, but I think it is a good thing to align with the existing design of IRGen rather than paving new roads.
1. Removed runtime func impls, when we can us the C API ones. 2. Changed some compiler RT entry points to using @_cdecl, so that they can be called in IRGen directly (instead of going through clang importer and silgen names).
@swift-ci please clean test tensorflow |
@@ -1183,3 +1178,8 @@ public func _CreateTensorHandleFromCTensorHandle( | |||
return TensorHandle<Float>(owning: ownedCHandle) | |||
} | |||
|
|||
@inlinable | |||
@_cdecl("_swift_tfc_CheckOk") | |||
public func _CheckOk(_ s: CTFStatus) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All our previous existing exported runtime functions have prefix “_TFC” in the Swift name. These should be updated for consistency. For example, this function should be “_TFCCheckOK”.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
@inlinable | ||
@_silgen_name("_swift_tfc_GetGlobalEagerContext") | ||
@_cdecl("_swift_tfc_GetGlobalEagerContext") | ||
public func _GetGlobalEagerContext() -> CTFEContext { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think these have to be public. @usableFromInline should work fine. Have you tried it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this works for the @_cdecl
functions. Changed.
return fn; | ||
} | ||
|
||
void checkOk(llvm::Value *status) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CheckOk is a really generic name, but it’s tied to TFC. After prefixes for runtime functions are added, this function should be renamed accordingly to prevent confusion with the rest of the IRGen code base.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the TFC prefix.
lib/IRGen/IRGenSIL.cpp
Outdated
// SWIFT_ENABLE_TENSORFLOW | ||
// Returns the LLVM function with `funcName`. It must exist. | ||
llvm::Function *findFunction(StringRef funcName, SILModule &silModule) { | ||
LLVM_DEBUG(llvm::dbgs() << "IRGen for " << funcName << "().\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This debug comment is confusing. It looks like it’s print the status that the compiler is running IRGen on a function, but it is not. It’s looking up a function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to "IRGen for calling ". I expect findFunction()
to go away once we have AnyTensorHandle, since all compiler runtime entry points can be defined as C functions at that time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great
@swift-ci please clean test tensorflow |
swift/stdlib/public/runtime/CMakeLists.txt, because CI run (e.g. https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-Linux/859/console) reported: ``` CMake Error at stdlib/public/runtime/CMakeLists.txt:30 (include_directories): include_directories given empty-string as include directory. ```
@swift-ci please clean test tensorflow |
// - Naming convention: | ||
// - TF_XXX functions correspond to TF C APIs | ||
// - TFE_XXX functions correspond to TF eager C APIs | ||
// - TFC_XXX functions are C functions defined in the compiler runtime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
stdlib/public/runtime/CMakeLists.txt
Outdated
@@ -27,6 +27,7 @@ list(APPEND swift_runtime_compile_flags | |||
"-D__SWIFT_CURRENT_DYLIB=swiftCore") | |||
|
|||
# SWIFT_ENABLE_TENSORFLOW | |||
find_package(TensorFlow REQUIRED) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: I moved code in the new files TensorFlow.{h,cpp} to the CTensorFlow
module (ctensorflow_init.{h,cpp}), so that the stdlib/public/runtime/ need not
depend on the TF library. As such I reverted the changes to the CMakefile here.
More context for the original plan:
find_package()
uses the swift/cmake/modules/FindTensorFlow.cmake script to define the TF_INCLUDE_DIR variable, which the next line uses, so that the new runtime source file swift/stdlib/public/runtime/TensorFlow.cpp can find header files like "tensorflow/c/c_api.h".
https://cmake.org/cmake/help/v3.0/command/find_package.html has more context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patch looks great, thanks!
@@ -70,7 +70,7 @@ func checkOk(_ s: CTFStatus?, file: StaticString = #file, line: UInt = #line) { | |||
typealias CTFSession = OpaquePointer | |||
|
|||
/// The `TF_Status *` type. | |||
typealias CTFStatus = OpaquePointer | |||
public typealias CTFStatus = OpaquePointer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since _TFCCheckOk
is @usableFromInline
now, this typealias can also be @usableFromInline
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
module (ctensorflow_init.{h,cpp}), so that the stdlib/public/runtime/ need not depend on the TF library. Also addresed more feedback.
@swift-ci please clean test tensorflow |
Linux tests passed: Mac test hit a network error (https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-macOS/90/console):
I'll retry it. |
@swift-ci Please test tensorflow macOS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
Mac test finished: https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-macOS/92/ Thanks all for the review! |
This patch "dissolved" the call to the packaged experimental C API TF_RunConstOp() into a set of finer-grained C API calls.
An alternative to the runtime function approach is to use Clang importer to call
the C APIs. Clang importer allows the C APIs to become available as SIL
functions with the same names, and thus can be found and called in IRGen via
silModule.findFunction() (see
https://github.com/apple/swift/blob/ad7def2c6bffd95d62e5e665c9faed0f8dac49f5/lib/IRGen/IRGenSIL.cpp#L1956-L1957). The concerns with this approach are:
There are no precedents in IRGen that calls C APIs via this clang importer
route. Having IRGen depend on clang importer may not be desirable.
Some C APIs cannot be found via clang importer (e.g. TF_NewStatus() can be
found, but TFE_NewOp() cannot). As a work-around, these C APIs must be called in
the user module code first, before IRGen over the user module can locate them as
SIL functions. This feels brittle.
When passing objects between compiler runtime entry points (swift functions)
such as @_silgen_name("_swift_tfc_GetGlobalEagerContext") and the C APIs, IRGen
have to issue bitcast to convert between a real struct type (e.g. TFE_Context*)
and void* (i8* in LLVM type) as used in the entry points.
In contrast, the wrapped runtime functions consistently use void* in their
interfaces, so that IRGen need not do such bitcasts.
One downside with the runtime function approach is that we need to wrap ~20 TF C
APIs in the new files TensorFlow.{h,cpp}. This is however mostly a one-time eng cost.