| /* |
| * 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.cc |
| * \brief RPC session for remote function call. |
| */ |
| #include "rpc_endpoint.h" |
| |
| #include <tvm/ffi/function.h> |
| #include <tvm/runtime/base.h> |
| #include <tvm/runtime/device_api.h> |
| #include <tvm/runtime/serializer.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <chrono> |
| #include <cmath> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "../../support/arena.h" |
| #include "../../support/ring_buffer.h" |
| #include "../../support/utils.h" |
| #include "rpc_local_session.h" |
| |
| namespace tvm { |
| namespace runtime { |
| |
| /*! |
| * Event-driven state-machine based handlers for RPCEndpoint. |
| * |
| * Key functions: |
| * |
| * - SendPackedSeq: send the arguments over to the peer |
| * - HandleNextEvent: handle the next request from the peer(RPCCode followed by per code protocol). |
| */ |
| class RPCEndpoint::EventHandler : public dmlc::Stream { |
| public: |
| EventHandler(support::RingBuffer* reader, support::RingBuffer* writer, std::string name, |
| std::string* remote_key, std::function<void()> flush_writer) |
| : reader_(reader), |
| writer_(writer), |
| name_(name), |
| remote_key_(remote_key), |
| flush_writer_(flush_writer) { |
| this->Clear(); |
| |
| if (*remote_key == "%toinit") { |
| state_ = kInitHeader; |
| remote_key_->resize(0); |
| pending_request_bytes_ = sizeof(int32_t); |
| } |
| } |
| |
| /*! |
| * \brief Bytes needed to fulfill current request |
| */ |
| size_t BytesNeeded() const { |
| if (reader_->bytes_available() < pending_request_bytes_) { |
| return pending_request_bytes_ - reader_->bytes_available(); |
| } else { |
| return 0; |
| } |
| } |
| |
| /*! |
| * \brief Request number of bytes from the reader. |
| * \param nbytes The number of bytes |
| */ |
| void RequestBytes(size_t nbytes) { |
| pending_request_bytes_ += nbytes; |
| reader_->Reserve(pending_request_bytes_); |
| } |
| |
| /*! \return Whether we are ready to handle next request. */ |
| bool Ready() const { return reader_->bytes_available() >= pending_request_bytes_; } |
| |
| /*! \return Whether we can perform a clean shutdown */ |
| bool CanCleanShutdown() const { return state_ == kRecvPacketNumBytes; } |
| |
| /*! \brief Finish the copy ack stage. */ |
| void FinishCopyAck() { this->SwitchToState(kRecvPacketNumBytes); } |
| |
| /*! |
| * \brief Enter the io loop until the next event. |
| * \param client_mode Whether we are in the client. |
| * \param async_server_mode Whether we are in the async server mode. |
| * \param setreturn The function to set the return value encoding. |
| * \return The function to set return values when there is a return event. |
| */ |
| RPCCode HandleNextEvent(bool client_mode, bool async_server_mode, |
| RPCSession::FEncodeReturn setreturn) { |
| std::swap(client_mode_, client_mode); |
| std::swap(async_server_mode_, async_server_mode); |
| |
| RPCCode status = RPCCode::kNone; |
| |
| while (status == RPCCode::kNone && state_ != kWaitForAsyncCallback && this->Ready()) { |
| switch (state_) { |
| case kInitHeader: |
| HandleInitHeader(); |
| break; |
| case kRecvPacketNumBytes: { |
| uint64_t packet_nbytes; |
| ICHECK(this->Read(&packet_nbytes)); |
| if (packet_nbytes != 0) { |
| this->SwitchToState(kProcessPacket); |
| this->RequestBytes(packet_nbytes); |
| } else { |
| this->SwitchToState(kRecvPacketNumBytes); |
| } |
| break; |
| } |
| case kProcessPacket: { |
| this->HandleProcessPacket(setreturn); |
| break; |
| } |
| case kWaitForAsyncCallback: { |
| break; |
| } |
| case kReturnReceived: { |
| this->SwitchToState(kRecvPacketNumBytes); |
| status = RPCCode::kReturn; |
| break; |
| } |
| case kCopyAckReceived: { |
| status = RPCCode::kCopyAck; |
| break; |
| } |
| case kShutdownReceived: { |
| status = RPCCode::kShutdown; |
| } |
| } |
| } |
| |
| std::swap(async_server_mode_, async_server_mode); |
| std::swap(client_mode_, client_mode); |
| return status; |
| } |
| |
| /*! \brief Clear all the states in the Handler.*/ |
| void Clear() { |
| state_ = kRecvPacketNumBytes; |
| pending_request_bytes_ = sizeof(uint64_t); |
| } |
| |
| /*! |
| * \brief Validate that the arguments can be sent through RPC. |
| * \param args The arguments. |
| */ |
| void ValidateArguments(ffi::PackedArgs args) { |
| for (int i = 0; i < args.size(); ++i) { |
| if (args[i] == nullptr) continue; |
| if (args[i].type_index() == ffi::TypeIndex::kTVMFFIModule) continue; |
| if (args[i].type_index() == ffi::TypeIndex::kTVMFFISmallStr || |
| args[i].type_index() == ffi::TypeIndex::kTVMFFISmallBytes) |
| continue; |
| if (args[i].type_index() == ffi::TypeIndex::kTVMFFIStr || |
| args[i].type_index() == ffi::TypeIndex::kTVMFFIBytes) |
| continue; |
| if (const Object* obj = args[i].as<Object>()) { |
| if (!obj->IsInstance<RPCObjectRefObj>()) { |
| LOG(FATAL) << "ValueError: Cannot pass argument " << i << ", type " << obj->GetTypeKey() |
| << " (type_index = " << obj->type_index() << ")"; |
| } |
| } else if (auto opt_device = args[i].as<DLDevice>()) { |
| DLDevice dev = opt_device.value(); |
| ICHECK(!IsRPCSessionDevice(dev)) << "InternalError: cannot pass RPC device in the channel"; |
| } |
| } |
| } |
| |
| void ThrowError(RPCServerStatus code, RPCCode info = RPCCode::kNone) { |
| LOG(FATAL) << "RPCServerError:" << RPCServerStatusToString(code); |
| } |
| |
| uint64_t PackedSeqGetNumBytes(const ffi::AnyView* packed_args, int num_args, bool client_mode) { |
| return RPCReference::PackedSeqGetNumBytes(reinterpret_cast<const TVMFFIAny*>(packed_args), |
| num_args, client_mode, this); |
| } |
| |
| void SendPackedSeq(const ffi::AnyView* packed_args, int num_args, bool client_mode) { |
| RPCReference::SendPackedSeq(reinterpret_cast<const TVMFFIAny*>(packed_args), num_args, |
| client_mode, this); |
| } |
| |
| // Endian aware IO handling |
| using Stream::Read; |
| using Stream::ReadArray; |
| using Stream::Write; |
| using Stream::WriteArray; |
| |
| void MessageStart(uint64_t packet_nbytes) { |
| // Unused here, implemented for microTVM framing layer. |
| } |
| |
| bool Read(RPCCode* code) { |
| int32_t cdata; |
| if (!this->Read(&cdata)) return false; |
| *code = static_cast<RPCCode>(cdata); |
| return true; |
| } |
| void Write(RPCCode code) { |
| int32_t cdata = static_cast<int>(code); |
| this->Write(cdata); |
| } |
| |
| void WriteFFIAny(const TVMFFIAny* in) { |
| // NOTE: for now all remote object are encoded as RPCObjectRef |
| // follow the same disco protocol in case we would like to upgrade later |
| // TODO(tqchen): consider merge with disco protocol |
| const AnyView* any_view_ptr = reinterpret_cast<const AnyView*>(in); |
| if (const auto* ref = any_view_ptr->as<RPCObjectRefObj>()) { |
| this->template Write<uint32_t>(runtime::TypeIndex::kRuntimeRPCObjectRef); |
| uint64_t handle = reinterpret_cast<uint64_t>(ref->object_handle()); |
| this->template Write<int64_t>(handle); |
| } else if (auto opt_str = any_view_ptr->as<ffi::String>()) { |
| this->template Write<uint32_t>(ffi::TypeIndex::kTVMFFIStr); |
| this->template Write<uint64_t>((*opt_str).size()); |
| this->template WriteArray<char>((*opt_str).data(), (*opt_str).size()); |
| } else if (auto opt_bytes = any_view_ptr->as<ffi::Bytes>()) { |
| this->template Write<uint32_t>(ffi::TypeIndex::kTVMFFIBytes); |
| this->template Write<uint64_t>((*opt_bytes).size()); |
| this->template WriteArray<char>((*opt_bytes).data(), (*opt_bytes).size()); |
| } else { |
| LOG(FATAL) << "ValueError: Object type is not supported in RPC calling convention: " |
| << any_view_ptr->GetTypeKey() << " (type_index = " << any_view_ptr->type_index() |
| << ")"; |
| } |
| } |
| uint64_t GetFFIAnyProtocolBytes(const TVMFFIAny* in) { |
| const AnyView* any_view_ptr = reinterpret_cast<const AnyView*>(in); |
| if (any_view_ptr->as<RPCObjectRefObj>()) { |
| return sizeof(uint32_t) + sizeof(int64_t); |
| } else if (auto opt_str = any_view_ptr->as<ffi::String>()) { |
| return sizeof(uint32_t) + sizeof(uint64_t) + (*opt_str).size(); |
| } else if (auto opt_bytes = any_view_ptr->as<ffi::Bytes>()) { |
| return sizeof(uint32_t) + sizeof(uint64_t) + (*opt_bytes).size(); |
| } else { |
| LOG(FATAL) << "ValueError: Object type is not supported in RPC calling convention: " |
| << any_view_ptr->GetTypeKey() << " (type_index = " << any_view_ptr->type_index() |
| << ")"; |
| TVM_FFI_UNREACHABLE(); |
| } |
| } |
| |
| void ReadFFIAny(TVMFFIAny* out) { |
| // NOTE: for now all remote object are encoded as RPCObjectRef |
| // follow the same disco protocol in case we would like to upgrade later |
| // |
| // Rationale note: Only handle remote object allows the same mechanism to work for minRPC |
| // which is needed for wasm and other env that goes through C API |
| uint32_t type_index; |
| this->template Read<uint32_t>(&type_index); |
| if (type_index == runtime::TypeIndex::kRuntimeRPCObjectRef) { |
| uint64_t handle; |
| this->template Read<uint64_t>(&handle); |
| // Always wrap things back in RPCObjectRef |
| // this is because we want to enable multi-hop RPC |
| // and next hop would also need to check the object index |
| RPCObjectRef rpc_obj( |
| ffi::make_object<RPCObjectRefObj>(reinterpret_cast<void*>(handle), nullptr)); |
| // Legacy ABI translation |
| // TODO(tqchen): remove this once we have upgraded to new ABI |
| *reinterpret_cast<AnyView*>(out) = rpc_obj; |
| any_arena_.emplace_back(rpc_obj); |
| } else if (type_index == ffi::TypeIndex::kTVMFFIStr) { |
| uint64_t size; |
| this->template Read<uint64_t>(&size); |
| std::string data(size, '\0'); |
| this->template ReadArray<char>(data.data(), size); |
| ffi::String ret(std::move(data)); |
| *reinterpret_cast<AnyView*>(out) = ret; |
| any_arena_.emplace_back(ret); |
| } else if (type_index == ffi::TypeIndex::kTVMFFIBytes) { |
| uint64_t size; |
| this->template Read<uint64_t>(&size); |
| std::string data(size, '\0'); |
| this->template ReadArray<char>(data.data(), size); |
| ffi::Bytes ret(std::move(data)); |
| *reinterpret_cast<AnyView*>(out) = ret; |
| any_arena_.emplace_back(ret); |
| } else { |
| LOG(FATAL) << "ValueError: Object type is not supported in Disco calling convention: " |
| << Object::TypeIndex2Key(type_index) << " (type_index = " << type_index << ")"; |
| } |
| } |
| |
| void MessageDone() { |
| // Unused here, implemented for microTVM framing layer. |
| } |
| |
| template <typename T> |
| T* ArenaAlloc(int count) { |
| static_assert(std::is_pod<T>::value, "need to be trival"); |
| return arena_.template allocate_<T>(count); |
| } |
| |
| /*! \brief Recycle all the memory used in the arena */ |
| void RecycleAll() { |
| this->any_arena_.clear(); |
| this->arena_.RecycleAll(); |
| } |
| |
| protected: |
| enum State { |
| kInitHeader, |
| kRecvPacketNumBytes, |
| kProcessPacket, |
| kWaitForAsyncCallback, |
| kReturnReceived, |
| kCopyAckReceived, |
| kShutdownReceived |
| }; |
| // Current state; |
| State state_; |
| // Initialize remote header |
| int init_header_step_{0}; |
| // Whether current handler is client or server mode. |
| bool client_mode_{false}; |
| // Whether current handler is in the async server mode. |
| bool async_server_mode_{false}; |
| // Internal arena |
| support::Arena arena_; |
| // internal arena for temp objects |
| std::vector<ffi::Any> any_arena_; |
| |
| // State switcher |
| void SwitchToState(State state) { |
| // invariant |
| if (state != kCopyAckReceived) { |
| ICHECK_EQ(pending_request_bytes_, 0U) << "state=" << state; |
| } |
| // need to actively flush the writer |
| // so the data get pushed out. |
| if (state_ == kWaitForAsyncCallback) { |
| flush_writer_(); |
| } |
| state_ = state; |
| ICHECK(state != kInitHeader) << "cannot switch to init header"; |
| if (state == kRecvPacketNumBytes) { |
| this->RequestBytes(sizeof(uint64_t)); |
| // recycle arena for the next session. |
| this->RecycleAll(); |
| } |
| } |
| |
| // handler for initial header read |
| void HandleInitHeader() { |
| if (init_header_step_ == 0) { |
| int32_t len; |
| this->Read(&len); |
| remote_key_->resize(len); |
| init_header_step_ = 1; |
| this->RequestBytes(len); |
| return; |
| } else { |
| ICHECK_EQ(init_header_step_, 1); |
| this->ReadArray(remote_key_->data(), remote_key_->length()); |
| this->SwitchToState(kRecvPacketNumBytes); |
| } |
| } |
| |
| // Handler for read code. |
| void HandleProcessPacket(RPCSession::FEncodeReturn setreturn) { |
| RPCCode code = RPCCode::kNone; |
| this->Read(&code); |
| if (code >= RPCCode::kSyscallCodeStart) { |
| this->HandleSyscall(code); |
| } else { |
| switch (code) { |
| case RPCCode::kInitServer: { |
| this->HandleInitServer(); |
| break; |
| } |
| case RPCCode::kCallFunc: { |
| this->HandleNormalCallFunc(); |
| break; |
| } |
| case RPCCode::kCopyFromRemote: { |
| this->HandleCopyFromRemote(); |
| break; |
| } |
| case RPCCode::kCopyToRemote: { |
| this->HandleCopyToRemote(); |
| break; |
| } |
| case RPCCode::kException: |
| case RPCCode::kReturn: { |
| this->HandleReturn(code, setreturn); |
| break; |
| } |
| case RPCCode::kCopyAck: { |
| this->SwitchToState(kCopyAckReceived); |
| break; |
| } |
| case RPCCode::kShutdown: { |
| this->SwitchToState(kShutdownReceived); |
| break; |
| } |
| default: |
| LOG(FATAL) << "Unknown event " << static_cast<int>(code); |
| } |
| } |
| } |
| |
| /*! |
| * \brief Receive incoming packed seq from the stream. |
| * \return The received argments. |
| * \note The ffi::PackedArgs is available until we switchstate. |
| */ |
| ffi::PackedArgs RecvPackedSeq() { |
| ffi::AnyView* packed_args; |
| int num_args; |
| RPCReference::RecvPackedSeq(reinterpret_cast<TVMFFIAny**>(&packed_args), &num_args, this); |
| return ffi::PackedArgs(packed_args, num_args); |
| } |
| |
| /*! |
| * \brief Return exception to the remote. |
| * \param err_msg The error message. |
| */ |
| void ReturnException(const char* err_msg) { RPCReference::ReturnException(err_msg, this); } |
| |
| /*! |
| * \brief Return nullptr to the remote. |
| * \param err_msg The error message. |
| */ |
| void ReturnVoid() { RPCReference::ReturnVoid(this); } |
| |
| /*! |
| * \brief Return a packed sequence to the remote. |
| * \param args The arguments. |
| */ |
| void ReturnPackedSeq(ffi::PackedArgs args) { |
| RPCReference::ReturnPackedSeq(reinterpret_cast<const TVMFFIAny*>(args.data()), args.size(), |
| this); |
| } |
| |
| /*! |
| * \brief Handle the case when return/exception value is received. |
| * \param code The RPC code. |
| * \param setreturn The function to encode return. |
| */ |
| void HandleReturn(RPCCode code, RPCSession::FEncodeReturn setreturn) { |
| ffi::PackedArgs args = RecvPackedSeq(); |
| if (code == RPCCode::kException) { |
| // switch to the state before sending exception. |
| this->SwitchToState(kRecvPacketNumBytes); |
| ffi::String msg = args[0].cast<ffi::String>(); |
| if (!support::StartsWith(msg, "RPCSessionTimeoutError: ")) { |
| msg = "RPCError: Error caught from RPC call:\n" + msg; |
| } |
| LOG(FATAL) << msg; |
| } |
| |
| ICHECK(setreturn != nullptr) << "fsetreturn not available"; |
| setreturn(args); |
| |
| this->SwitchToState(kReturnReceived); |
| } |
| |
| void HandleSyscall(RPCCode code); |
| |
| void HandleCopyFromRemote() { |
| DLTensor* arr = RPCReference::ReceiveDLTensor(this); |
| uint64_t data_bytes; |
| this->Read(&data_bytes); |
| size_t elem_bytes = (arr->dtype.bits * arr->dtype.lanes + 7) / 8; |
| auto* sess = GetServingSession(); |
| // Return Copy Ack with the given data |
| auto fcopyack = [this](char* dptr, size_t num_bytes) { |
| RPCCode code = RPCCode::kCopyAck; |
| uint64_t packet_nbytes = sizeof(code) + num_bytes; |
| |
| this->Write(packet_nbytes); |
| this->Write(code); |
| this->WriteArray(dptr, num_bytes); |
| this->SwitchToState(kRecvPacketNumBytes); |
| }; |
| |
| // When session is local, we can directly treat handle |
| // as the cpu pointer without allocating a temp space. |
| if (arr->device.device_type == kDLCPU && sess->IsLocalSession() && DMLC_IO_NO_ENDIAN_SWAP) { |
| char* data_ptr = reinterpret_cast<char*>(arr->data) + arr->byte_offset; |
| fcopyack(data_ptr, data_bytes); |
| } else { |
| char* temp_data = this->ArenaAlloc<char>(data_bytes); |
| auto on_copy_complete = [this, elem_bytes, data_bytes, temp_data, fcopyack]( |
| RPCCode status, ffi::PackedArgs args) { |
| if (status == RPCCode::kException) { |
| this->ReturnException(args[0].cast<const char*>()); |
| this->SwitchToState(kRecvPacketNumBytes); |
| } else { |
| // endian aware handling |
| if (!DMLC_IO_NO_ENDIAN_SWAP) { |
| dmlc::ByteSwap(temp_data, elem_bytes, data_bytes / elem_bytes); |
| } |
| fcopyack(temp_data, data_bytes); |
| } |
| }; |
| |
| this->SwitchToState(kWaitForAsyncCallback); |
| sess->AsyncCopyFromRemote(arr, static_cast<void*>(temp_data), data_bytes, on_copy_complete); |
| } |
| } |
| |
| void HandleCopyToRemote() { |
| DLTensor* arr = RPCReference::ReceiveDLTensor(this); |
| uint64_t data_bytes; |
| this->Read(&data_bytes); |
| size_t elem_bytes = (arr->dtype.bits * arr->dtype.lanes + 7) / 8; |
| auto* sess = GetServingSession(); |
| |
| // When session is local, we can directly treat handle |
| // as the cpu pointer without allocating a temp space. |
| if (arr->device.device_type == kDLCPU && sess->IsLocalSession()) { |
| char* dptr = reinterpret_cast<char*>(arr->data) + arr->byte_offset; |
| this->ReadArray(dptr, data_bytes); |
| |
| if (!DMLC_IO_NO_ENDIAN_SWAP) { |
| dmlc::ByteSwap(dptr, elem_bytes, data_bytes / elem_bytes); |
| } |
| this->ReturnVoid(); |
| this->SwitchToState(kRecvPacketNumBytes); |
| } else { |
| char* temp_data = this->ArenaAlloc<char>(data_bytes); |
| this->ReadArray(temp_data, data_bytes); |
| |
| if (!DMLC_IO_NO_ENDIAN_SWAP) { |
| dmlc::ByteSwap(temp_data, elem_bytes, data_bytes / elem_bytes); |
| } |
| |
| auto on_copy_complete = [this](RPCCode status, ffi::PackedArgs args) { |
| if (status == RPCCode::kException) { |
| this->ReturnException(args[0].cast<const char*>()); |
| this->SwitchToState(kRecvPacketNumBytes); |
| } else { |
| this->ReturnVoid(); |
| this->SwitchToState(kRecvPacketNumBytes); |
| } |
| }; |
| |
| this->SwitchToState(kWaitForAsyncCallback); |
| sess->AsyncCopyToRemote(static_cast<void*>(temp_data), arr, data_bytes, on_copy_complete); |
| } |
| } |
| |
| // Handle for packed call. |
| void HandleNormalCallFunc() { |
| uint64_t call_handle; |
| |
| this->Read(&call_handle); |
| ffi::PackedArgs args = RecvPackedSeq(); |
| |
| this->SwitchToState(kWaitForAsyncCallback); |
| GetServingSession()->AsyncCallFunc(reinterpret_cast<void*>(call_handle), args, |
| [this](RPCCode status, ffi::PackedArgs args) { |
| if (status == RPCCode::kException) { |
| this->ReturnException(args[0].cast<const char*>()); |
| } else { |
| ValidateArguments(args); |
| this->ReturnPackedSeq(args); |
| } |
| this->SwitchToState(kRecvPacketNumBytes); |
| }); |
| } |
| |
| void HandleInitServer() { |
| std::string client_protocol_ver; |
| |
| uint64_t len; |
| this->Read(&len); |
| client_protocol_ver.resize(len); |
| this->Read(dmlc::BeginPtr(client_protocol_ver), len); |
| |
| ffi::PackedArgs args = RecvPackedSeq(); |
| |
| try { |
| ICHECK(serving_session_ == nullptr) << "Server has already been initialized"; |
| |
| std::string server_protocol_ver = kRPCProtocolVer; |
| ICHECK_EQ(client_protocol_ver, server_protocol_ver) |
| << "Server[" << name_ << "]: Client protocol version mismatch with the server " |
| << " server protocol=" << server_protocol_ver |
| << ", client protocol=" << client_protocol_ver; |
| |
| std::string constructor_name; |
| ffi::PackedArgs constructor_args = ffi::PackedArgs(nullptr, 0); |
| |
| if (args.size() == 0) { |
| constructor_name = "rpc.LocalSession"; |
| serving_session_ = std::make_shared<LocalSession>(); |
| } else { |
| constructor_name = args[0].cast<std::string>(); |
| constructor_args = args.Slice(1); |
| } |
| |
| auto fconstructor = tvm::ffi::Function::GetGlobal(constructor_name); |
| ICHECK(fconstructor.has_value()) << " Cannot find session constructor " << constructor_name; |
| ffi::Any con_ret; |
| |
| try { |
| fconstructor->CallPacked(constructor_args, &con_ret); |
| } catch (const Error& e) { |
| LOG(FATAL) << "Server[" << name_ << "]:" |
| << " Error caught from session constructor " << constructor_name << ":\n" |
| << e.what(); |
| } |
| auto opt_con_ret = con_ret.as<ffi::Module>(); |
| // Legacy ABI translation |
| ICHECK(opt_con_ret.has_value()) |
| << "Server[" << name_ << "]:" |
| << " Constructor " << constructor_name << " need to return an RPCModule"; |
| ffi::Module mod = opt_con_ret.value(); |
| std::string tkey = mod->kind(); |
| ICHECK_EQ(tkey, "rpc") << "Constructor " << constructor_name << " to return an RPCModule"; |
| serving_session_ = RPCModuleGetSession(mod); |
| this->ReturnVoid(); |
| } catch (const std::exception& e) { |
| this->ReturnException(e.what()); |
| } |
| |
| this->SwitchToState(kRecvPacketNumBytes); |
| } |
| |
| void HandleSyscallStreamSync() { |
| ffi::PackedArgs args = RecvPackedSeq(); |
| try { |
| auto dev = args[0].cast<Device>(); |
| TVMStreamHandle handle = args[1].cast<void*>(); |
| |
| this->SwitchToState(kWaitForAsyncCallback); |
| GetServingSession()->AsyncStreamWait(dev, handle, |
| [this](RPCCode status, ffi::PackedArgs args) { |
| if (status == RPCCode::kException) { |
| this->ReturnException(args[0].cast<const char*>()); |
| } else { |
| this->ReturnVoid(); |
| } |
| this->SwitchToState(kRecvPacketNumBytes); |
| }); |
| } catch (const std::exception& e) { |
| this->ReturnException(e.what()); |
| this->SwitchToState(kRecvPacketNumBytes); |
| } |
| } |
| |
| // Handler for special syscalls that have a specific RPCCode. |
| template <typename F> |
| void SysCallHandler(F f) { |
| ffi::PackedArgs args = RecvPackedSeq(); |
| try { |
| ffi::Any rv; |
| f(GetServingSession(), args, &rv); |
| AnyView packed_args[1]; |
| packed_args[0] = rv; |
| this->ReturnPackedSeq(ffi::PackedArgs(packed_args, 1)); |
| } catch (const std::exception& e) { |
| this->ReturnException(e.what()); |
| } |
| this->SwitchToState(kRecvPacketNumBytes); |
| } |
| |
| private: |
| RPCSession* GetServingSession() const { |
| ICHECK(serving_session_ != nullptr) |
| << "Need to call InitRemoteSession first before any further actions"; |
| ICHECK(!serving_session_->IsAsync() || async_server_mode_) |
| << "Cannot host an async session in a non-Event driven server"; |
| |
| return serving_session_.get(); |
| } |
| // Utility functions |
| // Internal read function, update pending_request_bytes_ |
| size_t Read(void* data, size_t size) final { |
| ICHECK_LE(size, pending_request_bytes_); |
| reader_->Read(data, size); |
| pending_request_bytes_ -= size; |
| return size; |
| } |
| // write the data to the channel. |
| size_t Write(const void* data, size_t size) final { |
| writer_->Write(data, size); |
| return size; |
| } |
| |
| // Number of pending bytes requests |
| size_t pending_request_bytes_{0}; |
| // The ring buffer to read data from. |
| support::RingBuffer* reader_; |
| // The ringr buffer to write reply to. |
| support::RingBuffer* writer_; |
| // The session used to serve the RPC requests. |
| std::shared_ptr<RPCSession> serving_session_; |
| // Name of endpoint. |
| std::string name_; |
| // remote key |
| std::string* remote_key_; |
| // function to flush the writer. |
| std::function<void()> flush_writer_; |
| }; |
| |
| RPCCode RPCEndpoint::HandleUntilReturnEvent(bool client_mode, RPCSession::FEncodeReturn setreturn) { |
| RPCCode code = RPCCode::kCallFunc; |
| |
| CHECK(channel_) << "Expected connection to server " << name_ |
| << " to be active, but the connection was previously closed"; |
| while (code != RPCCode::kReturn && code != RPCCode::kShutdown && code != RPCCode::kCopyAck) { |
| while (writer_.bytes_available() != 0) { |
| writer_.ReadWithCallback( |
| [this](const void* data, size_t size) { return channel_->Send(data, size); }, |
| writer_.bytes_available()); |
| } |
| size_t bytes_needed = handler_->BytesNeeded(); |
| if (bytes_needed != 0) { |
| size_t n = reader_.WriteWithCallback( |
| [this](void* data, size_t size) { return channel_->Recv(data, size); }, bytes_needed); |
| if (n == 0) { |
| if (handler_->CanCleanShutdown()) { |
| return RPCCode::kShutdown; |
| } else { |
| LOG(FATAL) << "Channel closes before we get needed bytes"; |
| } |
| } |
| } |
| code = handler_->HandleNextEvent(client_mode, false, setreturn); |
| } |
| return code; |
| } |
| |
| void RPCEndpoint::Init() { |
| // callback to flush the writer. |
| auto flush_writer = [this]() { |
| while (writer_.bytes_available() != 0) { |
| size_t n = writer_.ReadWithCallback( |
| [this](const void* data, size_t size) { return channel_->Send(data, size); }, |
| writer_.bytes_available()); |
| if (n == 0) break; |
| } |
| }; |
| |
| // Event handler |
| handler_ = std::make_shared<EventHandler>(&reader_, &writer_, name_, &remote_key_, flush_writer); |
| |
| // Quick function to for syscall remote. |
| syscall_remote_ = ffi::Function([this](ffi::PackedArgs all_args, ffi::Any* rv) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| RPCCode code = static_cast<RPCCode>(all_args[0].cast<int>()); |
| ffi::PackedArgs args = all_args.Slice(1); |
| |
| // run transmission |
| uint64_t packet_nbytes = |
| sizeof(code) + handler_->PackedSeqGetNumBytes(args.data(), args.size(), true); |
| |
| // All packet begins with packet nbytes |
| handler_->Write(packet_nbytes); |
| handler_->Write(code); |
| handler_->SendPackedSeq(args.data(), args.size(), true); |
| |
| code = HandleUntilReturnEvent(true, [rv](ffi::PackedArgs args) { |
| ICHECK_EQ(args.size(), 1); |
| *rv = args[0]; |
| }); |
| ICHECK(code == RPCCode::kReturn) << "code=" << static_cast<int>(code); |
| }); |
| } |
| |
| /*! |
| * \brief Create a new RPCEndpoint instance. |
| * \param channel RPCChannel used to communicate. |
| * \param name Name of this session, used to identify log messages from this RPCEndpoint instance. |
| * \param remote_key The remote key reported during protocol initialization, or "%toinit" if the |
| * RPCEndpoint should handle this phase of the protocol for you. Some servers may prefer to access |
| * parts of the key to modify their behavior. |
| * \param fcleanup The cleanup Packed function. |
| */ |
| std::shared_ptr<RPCEndpoint> RPCEndpoint::Create(std::unique_ptr<RPCChannel> channel, |
| std::string name, std::string remote_key, |
| ffi::TypedFunction<void()> fcleanup) { |
| std::shared_ptr<RPCEndpoint> endpt = std::make_shared<RPCEndpoint>(); |
| endpt->channel_ = std::move(channel); |
| endpt->name_ = std::move(name); |
| endpt->remote_key_ = std::move(remote_key); |
| endpt->fcleanup_ = fcleanup; |
| endpt->Init(); |
| return endpt; |
| } |
| |
| RPCEndpoint::~RPCEndpoint() { this->Shutdown(); } |
| |
| void RPCEndpoint::Shutdown() { |
| if (channel_ != nullptr) { |
| RPCCode code = RPCCode::kShutdown; |
| uint64_t packet_nbytes = sizeof(code); |
| |
| handler_->Write(packet_nbytes); |
| handler_->Write(code); |
| |
| // flush all writing buffer to output channel. |
| try { |
| while (writer_.bytes_available() != 0) { |
| size_t n = writer_.ReadWithCallback( |
| [this](const void* data, size_t size) { return channel_->Send(data, size); }, |
| writer_.bytes_available()); |
| if (n == 0) break; |
| } |
| } catch (const Error& e) { |
| } |
| channel_.reset(nullptr); |
| } |
| } |
| |
| void RPCEndpoint::ServerLoop() { |
| if (const auto f = tvm::ffi::Function::GetGlobal("tvm.rpc.server.start")) { |
| (*f)(); |
| } |
| ffi::Any rv; |
| ICHECK(HandleUntilReturnEvent(false, [](ffi::PackedArgs) {}) == RPCCode::kShutdown); |
| if (const auto f = tvm::ffi::Function::GetGlobal("tvm.rpc.server.shutdown")) { |
| (*f)(); |
| } |
| channel_.reset(nullptr); |
| if (fcleanup_ != nullptr) fcleanup_(); |
| } |
| |
| int RPCEndpoint::ServerAsyncIOEventHandler(const std::string& in_bytes, int event_flag) { |
| RPCCode code = RPCCode::kNone; |
| if (in_bytes.length() != 0) { |
| reader_.Write(in_bytes.c_str(), in_bytes.length()); |
| code = handler_->HandleNextEvent(false, true, [](ffi::PackedArgs) {}); |
| } |
| if ((event_flag & 2) != 0 && writer_.bytes_available() != 0) { |
| writer_.ReadWithCallback( |
| [this](const void* data, size_t size) { return channel_->Send(data, size); }, |
| writer_.bytes_available()); |
| } |
| ICHECK(code != RPCCode::kReturn && code != RPCCode::kCopyAck); |
| // if the code is kShutdown, return 0 to indicate the server should exit |
| if (code == RPCCode::kShutdown) return 0; |
| // if the writer has bytes available, return 2 to indicate the server should send data |
| // usually by calling the handler again |
| if (writer_.bytes_available() != 0) return 2; |
| // otherwise, return 1 to indicate the server should and read |
| return 1; |
| } |
| |
| void RPCEndpoint::InitRemoteSession(ffi::PackedArgs args) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| RPCCode code = RPCCode::kInitServer; |
| std::string protocol_ver = kRPCProtocolVer; |
| uint64_t length = protocol_ver.length(); |
| |
| // run transmission |
| uint64_t packet_nbytes = sizeof(code) + sizeof(length) + length + |
| handler_->PackedSeqGetNumBytes(args.data(), args.size(), true); |
| |
| // All packet begins with packet nbytes |
| handler_->Write(packet_nbytes); |
| handler_->Write(code); |
| handler_->Write(length); |
| handler_->WriteArray(protocol_ver.data(), length); |
| handler_->SendPackedSeq(args.data(), args.size(), true); |
| |
| code = HandleUntilReturnEvent(true, [](ffi::PackedArgs args) {}); |
| ICHECK(code == RPCCode::kReturn) << "code=" << static_cast<int>(code); |
| } |
| |
| // Get remote function with name |
| void RPCEndpoint::CallFunc(RPCSession::PackedFuncHandle h, ffi::PackedArgs args, |
| RPCSession::FEncodeReturn encode_return) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| handler_->ValidateArguments(args); |
| RPCCode code = RPCCode::kCallFunc; |
| uint64_t handle = reinterpret_cast<uint64_t>(h); |
| |
| // run transmission |
| uint64_t packet_nbytes = sizeof(code) + sizeof(handle) + |
| handler_->PackedSeqGetNumBytes(args.data(), args.size(), true); |
| |
| handler_->Write(packet_nbytes); |
| handler_->Write(code); |
| handler_->Write(handle); |
| handler_->SendPackedSeq(args.data(), args.size(), true); |
| |
| code = HandleUntilReturnEvent(true, encode_return); |
| ICHECK(code == RPCCode::kReturn) << "code=" << RPCCodeToString(code); |
| } |
| |
| void RPCEndpoint::CopyToRemote(void* from_bytes, DLTensor* to, uint64_t nbytes) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| RPCCode code = RPCCode::kCopyToRemote; |
| |
| uint64_t tensor_total_size_bytes = static_cast<uint64_t>(GetDataSize(*to)); |
| ICHECK_LE(to->byte_offset + nbytes, tensor_total_size_bytes) |
| << "CopyToRemote: overflow in tensor size: (byte_offset=" << to->byte_offset |
| << ", nbytes=" << nbytes << ", tensor_total_size=" << tensor_total_size_bytes << ")"; |
| |
| uint64_t overhead = RemoteCopyCalculatePacketOverheadSize(to, code, nbytes); |
| uint64_t packet_nbytes = overhead + nbytes; |
| |
| handler_->Write(packet_nbytes); |
| handler_->Write(code); |
| RPCReference::SendDLTensor(handler_, to); |
| handler_->Write(nbytes); |
| handler_->WriteArray(reinterpret_cast<char*>(from_bytes), nbytes); |
| ICHECK(HandleUntilReturnEvent(true, [](ffi::PackedArgs) {}) == RPCCode::kReturn); |
| } |
| |
| void RPCEndpoint::CopyFromRemote(DLTensor* from, void* to_bytes, uint64_t nbytes) { |
| std::lock_guard<std::mutex> lock(mutex_); |
| RPCCode code = RPCCode::kCopyFromRemote; |
| |
| uint64_t tensor_total_size_bytes = static_cast<uint64_t>(GetDataSize(*from)); |
| ICHECK_LE(from->byte_offset + nbytes, tensor_total_size_bytes) |
| << "CopyFromRemote: overflow in tensor size: (byte_offset=" << from->byte_offset |
| << ", nbytes=" << nbytes << ", tensor_total_size=" << tensor_total_size_bytes << ")"; |
| |
| uint64_t overhead = RemoteCopyCalculatePacketOverheadSize(from, code, nbytes); |
| uint64_t packet_nbytes = overhead; |
| |
| handler_->Write(packet_nbytes); |
| handler_->Write(code); |
| RPCReference::SendDLTensor(handler_, from); |
| handler_->Write(nbytes); |
| ICHECK(HandleUntilReturnEvent(true, [](ffi::PackedArgs) {}) == RPCCode::kCopyAck); |
| |
| handler_->ReadArray(reinterpret_cast<char*>(to_bytes), nbytes); |
| handler_->FinishCopyAck(); |
| } |
| |
| // SysCallEventHandler functions |
| void RPCGetGlobalFunc(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto name = args[0].cast<std::string>(); |
| *rv = handler->GetFunction(name); |
| } |
| |
| void RPCFreeHandle(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| void* handle = args[0].cast<void*>(); |
| handler->FreeHandle(handle); |
| } |
| |
| void RPCDevSetDevice(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| handler->GetDeviceAPI(dev)->SetDevice(dev); |
| } |
| |
| void RPCDevGetAttr(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| DeviceAttrKind kind = static_cast<DeviceAttrKind>(args[1].cast<int>()); |
| if (kind == kExist) { |
| DeviceAPI* api = handler->GetDeviceAPI(dev, true); |
| if (api != nullptr) { |
| api->GetAttr(dev, kind, rv); |
| } else { |
| *rv = 0; |
| } |
| } else { |
| handler->GetDeviceAPI(dev)->GetAttr(dev, static_cast<DeviceAttrKind>(kind), rv); |
| } |
| } |
| |
| void RPCDevAllocData(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| uint64_t nbytes = args[1].cast<uint64_t>(); |
| uint64_t alignment = args[2].cast<uint64_t>(); |
| DLDataType type_hint = args[3].cast<DLDataType>(); |
| void* data = handler->GetDeviceAPI(dev)->AllocDataSpace(dev, nbytes, alignment, type_hint); |
| *rv = data; |
| } |
| |
| void RPCDevAllocDataWithScope(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto arr = args[0].cast<DLTensor*>(); |
| Device dev = arr->device; |
| int ndim = arr->ndim; |
| int64_t* shape = arr->shape; |
| DLDataType dtype = arr->dtype; |
| auto mem_scope = args[1].cast<ffi::Optional<ffi::String>>(); |
| void* data = handler->GetDeviceAPI(dev)->AllocDataSpace(dev, ndim, shape, dtype, mem_scope); |
| *rv = data; |
| } |
| |
| void RPCDevFreeData(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| void* ptr = args[1].cast<void*>(); |
| handler->GetDeviceAPI(dev)->FreeDataSpace(dev, ptr); |
| } |
| |
| void RPCCopyAmongRemote(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto from = args[0].cast<DLTensor*>(); |
| auto to = args[1].cast<DLTensor*>(); |
| TVMStreamHandle stream = args[2].cast<void*>(); |
| |
| Device dev = from->device; |
| if (dev.device_type == kDLCPU) { |
| dev = to->device; |
| } else { |
| ICHECK(to->device.device_type == kDLCPU || to->device.device_type == from->device.device_type) |
| << "Can not copy across different dev types directly"; |
| } |
| handler->GetDeviceAPI(dev)->CopyDataFromTo(from, to, stream); |
| } |
| |
| void RPCDevCreateStream(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| void* data = handler->GetDeviceAPI(dev)->CreateStream(dev); |
| *rv = data; |
| } |
| |
| void RPCDevFreeStream(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| TVMStreamHandle stream = args[1].cast<void*>(); |
| handler->GetDeviceAPI(dev)->FreeStream(dev, stream); |
| } |
| |
| void RPCDevSetStream(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| TVMStreamHandle stream = args[1].cast<void*>(); |
| handler->GetDeviceAPI(dev)->SetStream(dev, stream); |
| } |
| |
| void RPCDevGetCurrentStream(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) { |
| auto dev = args[0].cast<Device>(); |
| *rv = handler->GetDeviceAPI(dev)->GetCurrentStream(dev); |
| } |
| |
| void RPCEndpoint::EventHandler::HandleSyscall(RPCCode code) { |
| // Event handler sit at clean state at this point. |
| switch (code) { |
| // system functions |
| case RPCCode::kFreeHandle: |
| SysCallHandler(RPCFreeHandle); |
| break; |
| case RPCCode::kGetGlobalFunc: |
| SysCallHandler(RPCGetGlobalFunc); |
| break; |
| case RPCCode::kDevSetDevice: |
| SysCallHandler(RPCDevSetDevice); |
| break; |
| case RPCCode::kDevGetAttr: |
| SysCallHandler(RPCDevGetAttr); |
| break; |
| case RPCCode::kDevAllocData: |
| SysCallHandler(RPCDevAllocData); |
| break; |
| case RPCCode::kDevAllocDataWithScope: |
| SysCallHandler(RPCDevAllocDataWithScope); |
| break; |
| case RPCCode::kDevFreeData: |
| SysCallHandler(RPCDevFreeData); |
| break; |
| case RPCCode::kDevCreateStream: |
| SysCallHandler(RPCDevCreateStream); |
| break; |
| case RPCCode::kDevFreeStream: |
| SysCallHandler(RPCDevFreeStream); |
| break; |
| case RPCCode::kDevStreamSync: |
| this->HandleSyscallStreamSync(); |
| break; |
| case RPCCode::kDevSetStream: |
| SysCallHandler(RPCDevSetStream); |
| break; |
| case RPCCode::kDevGetCurrentStream: |
| SysCallHandler(RPCDevGetCurrentStream); |
| break; |
| case RPCCode::kCopyAmongRemote: |
| SysCallHandler(RPCCopyAmongRemote); |
| break; |
| default: |
| LOG(FATAL) << "Unknown event " << static_cast<int>(code); |
| } |
| |
| if (state_ != kWaitForAsyncCallback) { |
| ICHECK_EQ(state_, kRecvPacketNumBytes); |
| } |
| } |
| |
| /*! |
| * \brief RPC client session that proxies all calls to an endpoint. |
| */ |
| class RPCClientSession : public RPCSession, public DeviceAPI { |
| public: |
| /*! |
| * \brief param endpoint The client endpoint of the session. |
| */ |
| explicit RPCClientSession(std::shared_ptr<RPCEndpoint> endpoint) : endpoint_(endpoint) {} |
| |
| // function overrides |
| PackedFuncHandle GetFunction(const std::string& name) final { |
| return endpoint_->SysCallRemote(RPCCode::kGetGlobalFunc, name).cast<void*>(); |
| } |
| |
| void CallFunc(PackedFuncHandle func, ffi::PackedArgs args, |
| const FEncodeReturn& fencode_return) final { |
| endpoint_->CallFunc(func, args, fencode_return); |
| } |
| |
| void CopyToRemote(void* local_from_bytes, DLTensor* remote_to, uint64_t nbytes) final { |
| RPCCode code = RPCCode::kCopyToRemote; |
| uint64_t overhead = RemoteCopyCalculatePacketOverheadSize(remote_to, code, nbytes); |
| uint64_t rpc_max_size = GetRPCMaxTransferSize(); |
| ICHECK_GT(rpc_max_size, overhead) << "CopyToRemote: Invalid block size!"; |
| const uint64_t block_size = rpc_max_size - overhead; |
| uint64_t block_count = 0; |
| const uint64_t num_blocks = nbytes / block_size; |
| void* from_bytes; |
| |
| for (block_count = 0; block_count < num_blocks; block_count++) { |
| remote_to->byte_offset = block_count * block_size; |
| from_bytes = reinterpret_cast<void*>( |
| (reinterpret_cast<uint8_t*>(local_from_bytes) + block_count * block_size)); |
| endpoint_->CopyToRemote(from_bytes, remote_to, block_size); |
| } |
| |
| const uint64_t remainder_bytes = nbytes % block_size; |
| if (remainder_bytes != 0) { |
| remote_to->byte_offset = block_count * block_size; |
| from_bytes = reinterpret_cast<void*>( |
| (reinterpret_cast<uint8_t*>(local_from_bytes) + block_count * block_size)); |
| endpoint_->CopyToRemote(from_bytes, remote_to, remainder_bytes); |
| } |
| } |
| |
| void CopyFromRemote(DLTensor* remote_from, void* local_to_bytes, uint64_t nbytes) final { |
| RPCCode code = RPCCode::kCopyFromRemote; |
| uint64_t overhead = RemoteCopyCalculatePacketOverheadSize(remote_from, code, nbytes); |
| uint64_t rpc_max_size = GetRPCMaxTransferSize(); |
| ICHECK_GT(rpc_max_size, overhead) << "CopyFromRemote: Invalid block size!"; |
| const uint64_t block_size = rpc_max_size - overhead; |
| uint64_t block_count = 0; |
| const uint64_t num_blocks = nbytes / block_size; |
| void* to_bytes; |
| |
| for (block_count = 0; block_count < num_blocks; block_count++) { |
| remote_from->byte_offset = block_count * block_size; |
| to_bytes = reinterpret_cast<void*>( |
| (reinterpret_cast<uint8_t*>(local_to_bytes) + block_count * block_size)); |
| endpoint_->CopyFromRemote(remote_from, to_bytes, block_size); |
| } |
| |
| const uint64_t remainder_bytes = nbytes % block_size; |
| if (remainder_bytes != 0) { |
| remote_from->byte_offset = block_count * block_size; |
| to_bytes = reinterpret_cast<void*>( |
| (reinterpret_cast<uint8_t*>(local_to_bytes) + block_count * block_size)); |
| endpoint_->CopyFromRemote(remote_from, to_bytes, remainder_bytes); |
| } |
| } |
| |
| void FreeHandle(void* handle) final { endpoint_->SysCallRemote(RPCCode::kFreeHandle, handle); } |
| |
| void SetDevice(Device dev) final { endpoint_->SysCallRemote(RPCCode::kDevSetDevice, dev); } |
| |
| void GetAttr(Device dev, DeviceAttrKind kind, ffi::Any* rv) final { |
| if (dev.device_type == kDLCPU && kind == kExist) { |
| // cpu always exists. |
| *rv = 1; |
| } else { |
| *rv = endpoint_->SysCallRemote(RPCCode::kDevGetAttr, dev, static_cast<int>(kind)); |
| } |
| } |
| |
| void* AllocDataSpace(Device dev, size_t nbytes, size_t alignment, DLDataType type_hint) final { |
| return endpoint_->SysCallRemote(RPCCode::kDevAllocData, dev, nbytes, alignment, type_hint) |
| .cast<void*>(); |
| } |
| |
| void* AllocDataSpace(Device dev, int ndim, const int64_t* shape, DLDataType dtype, |
| ffi::Optional<ffi::String> mem_scope) final { |
| DLTensor temp; |
| temp.data = nullptr; |
| temp.device = dev; |
| temp.ndim = ndim; |
| temp.dtype = dtype; |
| temp.shape = const_cast<int64_t*>(shape); |
| temp.strides = nullptr; |
| temp.byte_offset = 0; |
| if (mem_scope.has_value()) { |
| return endpoint_ |
| ->SysCallRemote(RPCCode::kDevAllocDataWithScope, &temp, |
| static_cast<std::string>(mem_scope.value())) |
| .cast<void*>(); |
| } else { |
| return endpoint_->SysCallRemote(RPCCode::kDevAllocDataWithScope, &temp, nullptr) |
| .cast<void*>(); |
| } |
| } |
| |
| void FreeDataSpace(Device dev, void* ptr) final { |
| endpoint_->SysCallRemote(RPCCode::kDevFreeData, dev, ptr); |
| } |
| |
| void CopyDataFromTo(DLTensor* from, DLTensor* to, TVMStreamHandle stream) final { |
| endpoint_->SysCallRemote(RPCCode::kCopyAmongRemote, from, to, stream).cast<void*>(); |
| } |
| |
| TVMStreamHandle CreateStream(Device dev) final { |
| return endpoint_->SysCallRemote(RPCCode::kDevCreateStream, dev).cast<void*>(); |
| } |
| |
| void FreeStream(Device dev, TVMStreamHandle stream) final { |
| endpoint_->SysCallRemote(RPCCode::kDevFreeStream, dev, stream); |
| } |
| |
| void StreamSync(Device dev, TVMStreamHandle stream) final { |
| endpoint_->SysCallRemote(RPCCode::kDevStreamSync, dev, stream); |
| } |
| |
| void SetStream(Device dev, TVMStreamHandle stream) final { |
| endpoint_->SysCallRemote(RPCCode::kDevSetStream, dev, stream); |
| } |
| |
| TVMStreamHandle GetCurrentStream(Device dev) final { |
| return endpoint_->SysCallRemote(RPCCode::kDevGetCurrentStream, dev).cast<void*>(); |
| } |
| |
| DeviceAPI* GetDeviceAPI(Device dev, bool allow_missing) final { return this; } |
| |
| bool IsLocalSession() const final { return false; } |
| |
| void Shutdown() final { endpoint_->Shutdown(); } |
| |
| private: |
| uint64_t GetRPCMaxTransferSize() { |
| if (rpc_chunk_max_size_bytes_ > 0) { |
| return (uint64_t)rpc_chunk_max_size_bytes_; |
| } |
| |
| PackedFuncHandle rpc_func = GetFunction("tvm.rpc.server.GetCRTMaxPacketSize"); |
| if (rpc_func == nullptr) { |
| rpc_chunk_max_size_bytes_ = (int64_t)kRPCMaxTransferSizeBytesDefault; |
| } else { |
| CallFunc(rpc_func, ffi::PackedArgs(nullptr, 0), [this](ffi::PackedArgs args) { |
| // Use args[1] as return value, args[0] is tcode |
| // Look at RPCWrappedFunc in src/runtime/rpc/rpc_module.cc |
| rpc_chunk_max_size_bytes_ = args[1].cast<int64_t>(); |
| ICHECK_GT(rpc_chunk_max_size_bytes_, 0) |
| << "RPC max transfer size is <= 0! (remote value = " << rpc_chunk_max_size_bytes_ |
| << ")"; |
| }); |
| } |
| return (uint64_t)rpc_chunk_max_size_bytes_; |
| } |
| |
| std::shared_ptr<RPCEndpoint> endpoint_; |
| int64_t rpc_chunk_max_size_bytes_ = -1; |
| }; |
| |
| std::shared_ptr<RPCSession> CreateClientSession(std::shared_ptr<RPCEndpoint> endpoint) { |
| return std::make_shared<RPCClientSession>(endpoint); |
| } |
| |
| uint64_t RemoteCopyCalculatePacketOverheadSize(DLTensor* tensor, RPCCode code, uint64_t nbytes) { |
| uint64_t shape_bytes = tensor->ndim * sizeof(int64_t); |
| uint64_t to_data = reinterpret_cast<uint64_t>(static_cast<uint8_t*>(tensor->data)); |
| uint64_t overhead = sizeof(code) + sizeof(to_data) + sizeof(tensor->device) + |
| sizeof(tensor->ndim) + sizeof(tensor->dtype) + sizeof(tensor->byte_offset) + |
| shape_bytes + sizeof(nbytes); |
| return overhead; |
| } |
| |
| } // namespace runtime |
| } // namespace tvm |