blob: 1276d8e267a17147e9676c135552e5ddec390a2b [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*!
* \file rpc_session.h
* \brief Base RPC session interface.
*/
#ifndef TVM_RUNTIME_RPC_RPC_SESSION_H_
#define TVM_RUNTIME_RPC_RPC_SESSION_H_
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/function.h>
#include <tvm/runtime/device_api.h>
#include <functional>
#include <memory>
#include <string>
#include "minrpc/rpc_reference.h"
namespace tvm {
namespace runtime {
/*!
* \brief Static FFI type index for `runtime::RPCObjectRef`.
*
* Allocated within the [kTVMFFIDynObjectBegin - 16, kTVMFFIDynObjectBegin)
* custom-static slot range. The sibling constant `kRuntimeDiscoDRef` lives in
* `tvm/runtime/disco/session.h` and uses `... - 14`; values must remain
* disjoint across this small reserved block.
*/
constexpr int32_t kRuntimeRPCObjectRef = TVMFFITypeIndex::kTVMFFIDynObjectBegin - 13;
/*!
* \brief The interface of all remote RPC sessions.
*
* It contains all the necessary interface to implement
* remote call and resource management.
*
* The interface is designed to allow easy proxy-chaining
* by forward requests to another RPCSession.
*/
class RPCSession {
public:
/*! \brief ffi::Function Handle in the remote. */
using PackedFuncHandle = void*;
/*! \brief Module handle in the remote. */
using ModuleHandle = void*;
/*! \brief Tensor handle in the remote. */
using TensorHandle = void*;
/*!
* \brief Callback to send an encoded return values via encode_args.
*
* \param encode_args The arguments that we can encode the return values into.
*
* Encoding convention (as list of arguments):
* - str/float/int/byte: [tcode: int, value: TVMValue] value follows ffi::Function convention.
* - ffi::Function/Module: [tcode: int, handle: void*]
* - Tensor: [tcode: int, meta: DLTensor*, nd_handle: void*]
* DLTensor* contains the meta-data as well as handle into the remote data.
* nd_handle can be used for deletion.
*/
using FEncodeReturn = std::function<void(ffi::PackedArgs encoded_args)>;
/*!
* \brief Callback to send an encoded return values via encode_args.
*
* \param status The return status, can be RPCCode::kReturn or RPCCode::kException.
* \param encode_args The arguments that we can encode the return values into.
*/
using FAsyncCallback = std::function<void(RPCCode status, ffi::PackedArgs encoded_args)>;
/*! \brief Destructor.*/
virtual ~RPCSession() {}
/*!
* \brief Get function in the session.
* \param name The name of the function.
* \return The function handle.
*/
virtual PackedFuncHandle GetFunction(const std::string& name) = 0;
/*!
* \brief Call into a remote Packed function.
*
* Calling convention:
*
* - type_code is follows the ffi::Function convention.
* - int/float/string/bytes follows the ffi::Function convention, all data are local.
* - ffi::Function/Module and future remote objects: pass remote handle instead.
* - Tensor/DLTensor: pass a DLTensor pointer, the data field of DLTensor
* points to a remote data handle returned by the Device API.
* The meta-data of the DLTensor sits on local.
*
* The caller populates the arguments and manages these arguments.
*
* The callee can change the content of arg_values and arg_type_codes
* if they want to do inplace modify and forward.
*
* The callee need to store the return value into ret_value.
* - ffi::Function/Module are stored as void*
* - Tensor is stored as local Tensor, whose data field is a remote handle.
* Notably the Tensor's deleter won't delete remote handle.
* It is up to the user of the RPCSession to such wrapping.
* - In short, remote handles are "moved" as return values
* and the callee needs to explicitly manage them by calling
* the deleter functions when they are no longer needed.
*
* \param func The function handle.
* \param args The input packed arguments.
* \param fencode_return The function to set the return value,
* if not called, return value is null.
*/
virtual void CallFunc(PackedFuncHandle func, ffi::PackedArgs args,
const FEncodeReturn& fencode_return) = 0;
/*!
* \brief Copy bytes into remote array content.
* \param local_from_bytes The source host data.
* \param remote_to The target array.
* \param nbytes The size of the memory in bytes.
*/
virtual void CopyToRemote(void* local_from_bytes, DLTensor* remote_to, uint64_t nbytes) = 0;
/*!
* \brief Copy bytes from remote array content.
* \param remote_from The source host data.
* \param local_to_bytes The target array.
* \param nbytes The size of the memory in bytes.
*/
virtual void CopyFromRemote(DLTensor* remote_from, void* local_to_bytes, uint64_t nbytes) = 0;
/*!
* \brief Free a remote function.
* \param handle The remote object handle.
* \param type_code The type code of the underlying type.
*/
virtual void FreeHandle(void* handle) = 0;
/*!
* \brief Get device API that represents the remote
* actions that can be taken on the remote.
*
* The caller can then call into the Alloc/Free functions
* to allocate free spaces and taking the pointer as the handle.
*
* The device API is guaranteed to be alive during the
* lifetime of the Session.
*
* \param dev The remote device.
* \param allow_missing Whether can we return nullptr if it is not available.
*
* \return The device API.
*/
virtual DeviceAPI* GetDeviceAPI(Device dev, bool allow_missing = false) = 0;
/*!
* \brief Whether the session is a local session and we can directly
* the data handle returned by the session and treat it as pointer
* to the local memory.
*
* This information is useful for RPC server to directly copy into the
* local memory without creating a temporary buffer.
*
* \return Whether it is a local session.
*/
virtual bool IsLocalSession() const = 0;
// Asynchrous variant of API
// These APIs are used by the RPC server to allow sessions that
// have special implementations for the async functions.
//
// In the async APIs, an exception is returned by the passing
// async_error=true, encode_args=[error_msg].
/*!
* \brief Whether the session is async.
*
* If the session is not async, its Aync implementations
* simply calls into the their synchronize counterparts,
* and the callback is guaranteed to be called before the async function finishes.
*
* \return the async state.
*
* \note We can only use async session in an Event driven RPC server.
*/
virtual bool IsAsync() const;
/*!
* \brief Asynchrously call func.
* \param func The function handle.
* \param args The packed arguments.
*
* \param callback The callback to pass the return value or exception.
*/
virtual void AsyncCallFunc(PackedFuncHandle func, ffi::PackedArgs args, FAsyncCallback callback);
/*!
* \brief Asynchrous version of CopyToRemote.
*
* \param local_from_bytes The source host data.
* \param remote_to The target array.
* \param nbytes The size of the memory in bytes.
* \param on_complete The callback to signal copy complete.
* \note All the allocated memory in local_from, and remote_to
* must stay alive until on_compelete is called.
*/
virtual void AsyncCopyToRemote(void* local_from_bytes, DLTensor* remote_to, uint64_t nbytes,
FAsyncCallback on_complete);
/*!
* \brief Asynchrous version of CopyFromRemote.
*
* \param remote_from The source host data.
* \param local_to_bytes The target array.
* \param nbytes The size of the memory in bytes.
* \param on_complete The callback to signal copy complete.
* \note All the allocated memory in remote_from, and local_to
* must stay alive until on_compelete is called.
*/
virtual void AsyncCopyFromRemote(DLTensor* remote_from, void* local_to_bytes, uint64_t nbytes,
FAsyncCallback on_complete);
/*!
* \brief Asynchrously wait for all events in dev, stream compeletes.
* \param dev The device.
* \param stream The stream to wait on.
* \param on_complete The callback to signal copy complete.
*/
virtual void AsyncStreamWait(Device dev, TVMStreamHandle stream, FAsyncCallback on_compelte);
/*!
* \return The session table index of the session.
*/
int table_index() const { return table_index_; }
/*!
* \brief Try get session from the global session table by table index.
* \param table_index The table index of the session.
* \return The shared_ptr to the session, can be nullptr.
*/
static std::shared_ptr<RPCSession> Get(int table_index);
/*!
* \brief Shutdown RPC connection.
*/
virtual void Shutdown() {}
protected:
/*!
* \brief Send an exception to the callback.
* \param msg The exception message.
*/
void SendException(FAsyncCallback callback, const char* msg);
private:
/*! \brief index of this session in RPC session table */
int table_index_{0};
/*! \brief Insert the current session to the session table.*/
static void InsertToSessionTable(std::shared_ptr<RPCSession> sess);
// friend declaration
friend ffi::Module CreateRPCSessionModule(std::shared_ptr<RPCSession> sess);
};
/*!
* \brief Remote space handle cell used by the RPC runtime API.
*
* When we allocate space using a rpc device, the data pointer
* points to an allocated RemoteSpace.
*/
struct RemoteSpace {
/*! \brief The remote data handle. */
void* data;
/*! \brief Reference to the underlying RPC session. */
std::shared_ptr<RPCSession> sess;
/*!
* \brief The remote Tensor object handle, if this RemoteSpace wraps a returned Tensor.
*
* Returned RPC Tensors carry both the backing data pointer and a Tensor object handle. The
* object handle must be released with FreeHandle so the remote side can correctly decrement the
* Tensor refcount and free the backing storage when it is no longer shared.
*/
void* object_handle{nullptr};
};
/*!
* \brief ffi::Object wrapper that represents a reference to a remote object
*/
class RPCObjectRefObj : public ffi::Object {
public:
/*!
* \brief constructor
* \param object_handle handle that points to the remote object
*
* \param sess The remote session, when session is nullptr
* it indicate the object is a temp object during rpc transmission
* and we don't have to free it
*/
RPCObjectRefObj(void* object_handle, std::shared_ptr<RPCSession> sess)
: object_handle_(object_handle), sess_(sess) {}
~RPCObjectRefObj() {
if (object_handle_ != nullptr && sess_ != nullptr) {
try {
sess_->FreeHandle(object_handle_);
} catch (const ffi::Error& e) {
// fault tolerance to remote close
}
object_handle_ = nullptr;
}
}
const std::shared_ptr<RPCSession>& sess() const { return sess_; }
void* object_handle() const { return object_handle_; }
static constexpr const uint32_t _type_index = kRuntimeRPCObjectRef;
static const constexpr bool _type_final = true;
TVM_FFI_DECLARE_OBJECT_INFO_STATIC("runtime.RPCObjectRef", RPCObjectRefObj, ffi::Object);
private:
// The object handle
void* object_handle_{nullptr};
// The local channel
std::shared_ptr<RPCSession> sess_;
};
/*!
* \brief Managed reference to RPCObjectRefObj.
* \sa RPCObjectRefObj
* \note No public constructor is provided as it is not supposed to be directly created by users.
*/
class RPCObjectRef : public ffi::ObjectRef {
public:
explicit RPCObjectRef(ffi::ObjectPtr<RPCObjectRefObj> data) : ffi::ObjectRef(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(RPCObjectRef, ffi::ObjectRef, RPCObjectRefObj);
};
/*!
* \brief Create a Global RPC module that refers to the session.
* \param sess The RPC session of the global module.
* \return The created module.
*/
ffi::Module CreateRPCSessionModule(std::shared_ptr<RPCSession> sess);
/*!
* \brief Get the session module from a RPC session Module.
* \param mod The input module(must be an RPCModule).
* \return The internal RPCSession.
*/
std::shared_ptr<RPCSession> RPCModuleGetSession(ffi::Module mod);
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_RPC_RPC_SESSION_H_