Skip to content
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

[VirtualMachine] new method allowing to set one input tensor by its index or name #10293

Merged
merged 15 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/tvm/runtime/vm/executable.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ class Executable : public ModuleNode {
*/
void SetLib(const runtime::Module& lib);

/*!
* \brief Get VMFunction.
* \param func_name The function's name.
* \return VMFunction.
*/
const VMFunction& GetVMFunctionWithName(const std::string& func_name) const;

/*!
* \brief Get the arity of the VMFunction.
* \param func Function name.
Expand Down
56 changes: 55 additions & 1 deletion include/tvm/runtime/vm/vm.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ struct VMFunction {
params(std::move(params)),
instructions(std::move(instructions)),
register_file_size(register_file_size),
param_device_indexes(std::move(param_device_indexes)) {}
param_device_indexes(std::move(param_device_indexes)) {
ICHECK_EQ(params.size(), param_device_indexes.size())
<< "The number of provided parameters doesn't match the number of assigned devices";
vvchernov marked this conversation as resolved.
Show resolved Hide resolved
}

VMFunction() = default;

Expand Down Expand Up @@ -270,6 +273,15 @@ class VirtualMachine : public runtime::ModuleNode {
*/
void SetInput(std::string name, TVMArgs args, int offset);

/*!
* \brief Set one input tensor with index or name to a function.
* \param name The function name
* \param args args[1:] are two arguments (index or name, tensor) to the
vvchernov marked this conversation as resolved.
Show resolved Hide resolved
* function. If the tensor is not of the correct device for the function,
* they will be copied to the device.
*/
void SetOneInputTensor(std::string name, TVMArgs args);

/*!
* \brief Internal hook for profiling the start of an op.
*
Expand All @@ -286,6 +298,48 @@ class VirtualMachine : public runtime::ModuleNode {
*/
virtual void OpStopHook();

private:
/*!
* \brief Get index of input tensor from its name.
* \param func_name The function's name.
* \param input_name The input tensor name.
* \return The input tensor index.
*/
int64_t getInputIndexFromVMFunction(const std::string& func_name,
vvchernov marked this conversation as resolved.
Show resolved Hide resolved
const std::string& input_name) const;

/*!
* \brief Get index of input tensor from its name.
* \param params parameter names.
* \param input_name The input tensor name.
* \return The input tensor index.
*/
int64_t getInputIndexFromName(const std::vector<std::string>& params,
const std::string& input_name) const;

/*!
* \brief Check executable exists and get VM function from it.
* \param func_name The function's name.
* \return VM function.
*/
const VMFunction& checkAndGetVMFunction(const std::string& func_name) const;

/*!
* \brief Creats inputs_ field, if it exists check its size.
* \param func_name The function's name.
* \param size inputs_ field size.
* \return VM function.
*/
void createInputsOrCheckSize(const std::string& func_name, size_t size);

/*!
* \brief Set one input tensor with given index to set of input tensors if need copy to given
* device. \param tensors the input tensors set (destination) \param tensor some tensor (not
* neccessary DLTensor). \param index The input tensor index. \param dev device to copy if need.
*/
void SetInputTensorWithIndex(std::vector<ObjectRef>& tensors, // NOLINT(*)
const TVMArgValue& tensor, int index, Device dev);

protected:
/*! \brief The virtual machine's packed function table. */
std::vector<PackedFunc> packed_funcs_;
Expand Down
25 changes: 9 additions & 16 deletions src/runtime/vm/executable.cc
Original file line number Diff line number Diff line change
Expand Up @@ -109,27 +109,20 @@ PackedFunc Executable::GetFunction(const std::string& name, const ObjectPtr<Obje
}
}

int Executable::GetFunctionArity(std::string func_name) const {
const VMFunction& Executable::GetVMFunctionWithName(const std::string& func_name) const {
auto it = global_map.find(func_name);
if (it == global_map.end()) {
LOG(ERROR) << "Cannot find function " << func_name << " in executable";
return -1;
}
const auto& func = functions[it->second];
ICHECK(it != global_map.end()) << "Cannot find function " << func_name << " in executable";
return functions[it->second];
}

int Executable::GetFunctionArity(std::string func_name) const {
const auto& func = GetVMFunctionWithName(func_name);
return func.params.size();
}

std::string Executable::GetFunctionParameterName(std::string func_name, uint32_t index) const {
auto it = global_map.find(func_name);
if (it == global_map.end()) {
LOG(ERROR) << "Cannot find function " << func_name << " in executable";
return "";
}
const auto& func = functions[it->second];
if (index > func.params.size()) {
LOG(ERROR) << "Invalid parameter index";
return "";
}
const auto& func = GetVMFunctionWithName(func_name);
ICHECK_LT(index, func.params.size()) << "Invalid parameter index";
return func.params[index];
}

Expand Down
120 changes: 83 additions & 37 deletions src/runtime/vm/vm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -190,17 +190,7 @@ PackedFunc VirtualMachine::GetFunction(const std::string& name,
} else if (name == "get_input_index") {
return TypedPackedFunc<int64_t(std::string, std::string)>(
[this](std::string input_name, std::string func_name) {
auto gvit = exec_->global_map.find(func_name);
ICHECK(gvit != exec_->global_map.end()) << "Cannot find function " << func_name;
auto func_index = gvit->second;
const auto& vm_func = exec_->functions[func_index];
const auto& param_names = vm_func.params;
for (uint64_t i = 0; i < param_names.size(); i++) {
if (input_name == param_names[i]) {
return static_cast<int64_t>(i);
}
}
return static_cast<int64_t>(-1);
return getInputIndexFromVMFunction(func_name, input_name);
});
} else if (name == "init") {
return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) {
Expand All @@ -221,6 +211,9 @@ PackedFunc VirtualMachine::GetFunction(const std::string& name,
} else if (name == "set_input") {
return PackedFunc(
[sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { SetInput(args[0], args, 1); });
} else if (name == "set_one_input") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: suggest a unit test exercising this new method from vm.py (though I'm assuming you need to change that anyway, perhaps in a follow up PR?) Maybe you are just using the runtime module directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I use runtime module directly on native side. It is a reason why I try to use set_input methods instead of direct using of invoke. The latter requires to pack all input tensors to TvmArgs in certain sequence on native side. For me it is more controlable and clear to input one by one tensor with its tag (index or name). It is simultaneously the answer to your below question. Nevertheless I agree that my patch was not full and I've extended python API of VirtualMachine by set_one_input method. I observed that there is no even set_input unit test. Unit tests are in progress.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added pytests.

return PackedFunc(
[sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { SetOneInputTensor(args[0], args); });
} else if (name == "load_late_bound_consts") {
return PackedFunc([this](TVMArgs args, TVMRetValue* rv) {
CHECK_EQ(args.size(), 1);
Expand All @@ -234,39 +227,92 @@ PackedFunc VirtualMachine::GetFunction(const std::string& name,
}

void VirtualMachine::SetInput(std::string func_name, TVMArgs args, int offset) {
ICHECK(exec_) << "The executable is not created yet.";
auto gvit = exec_->global_map.find(func_name);
ICHECK(gvit != exec_->global_map.end()) << "Cannot find function " << func_name;
auto func_index = gvit->second;
const auto& vm_func = exec_->functions[func_index];
const auto& param_names = vm_func.params;
ICHECK_EQ(args.size() - offset, param_names.size())
const auto& vm_func = checkAndGetVMFunction(func_name);
size_t params_num = vm_func.params.size();
ICHECK_EQ(args.size() - offset, params_num)
<< "The number of provided parameters doesn't match the number of arguments";
ICHECK_EQ(param_names.size(), vm_func.param_device_indexes.size())
<< "The number of provided parameters doesn't match the number of assigned devices";
std::vector<ObjectRef> func_args(param_names.size());
std::vector<ObjectRef> func_args(params_num);
for (int i = offset; i < args.size(); ++i) {
Device dev = GetDevice(vm_func.param_device_indexes[i - offset]);

if (args[i].type_code() == kTVMDLTensorHandle) {
// Automatically convert input DLTensors to NDArray
DLTensor* tensor = args[i];
std::vector<int64_t> shape;
for (int64_t i = 0; i < tensor->ndim; i++) {
shape.push_back(tensor->shape[i]);
}
NDArray ary = NDArray::Empty(shape, tensor->dtype, dev);
ary.CopyFrom(tensor);
func_args[i - offset] = ary;
} else {
ObjectRef obj = CopyTo(args[i], dev);
func_args[i - offset] = obj;
}
int index = i - offset;
Device dev = GetDevice(vm_func.param_device_indexes[index]);
SetInputTensorWithIndex(func_args, args[i], index, dev);
}
inputs_.erase(func_name);
inputs_.emplace(func_name, func_args);
}

void VirtualMachine::SetOneInputTensor(std::string func_name, TVMArgs args) {
ICHECK_EQ(args.size(), 3) << "The expected number of arguments is 3 "
<< "(func_name, index or name, tensor)";
const auto& vm_func = checkAndGetVMFunction(func_name);
size_t params_num = vm_func.params.size();

int inp_index;
if (args[1].type_code() == kTVMArgInt) {
inp_index = args[1];
} else if (args[1].type_code() == kTVMStr) {
inp_index = static_cast<int>(getInputIndexFromName(vm_func.params, args[1]));
} else {
LOG(FATAL) << "The second argument type (" << args[1].type_code()
<< ") doesn't match integer or string";
}
ICHECK_LT(inp_index, params_num);

createInputsOrCheckSize(func_name, params_num);
Device dev = GetDevice(vm_func.param_device_indexes[inp_index]);
SetInputTensorWithIndex(inputs_[func_name], args[2], inp_index, dev);
}

int64_t VirtualMachine::getInputIndexFromVMFunction(const std::string& func_name,
const std::string& input_name) const {
const auto& vm_func = checkAndGetVMFunction(func_name);
return getInputIndexFromName(vm_func.params, input_name);
}

int64_t VirtualMachine::getInputIndexFromName(const std::vector<std::string>& params,
const std::string& input_name) const {
// TODO(vvchernov): excess integer type?
for (uint64_t i = 0; i < params.size(); i++) {
if (input_name == params[i]) {
return static_cast<int64_t>(i);
}
}
return static_cast<int64_t>(-1);
}

const VMFunction& VirtualMachine::checkAndGetVMFunction(const std::string& func_name) const {
ICHECK(exec_) << "The executable is not created yet.";
return exec_->GetVMFunctionWithName(func_name);
}

void VirtualMachine::createInputsOrCheckSize(const std::string& func_name, size_t size) {
if (inputs_.count(func_name)) {
ICHECK_EQ(inputs_[func_name].size(), size)
<< "The size of function" << func_name
<< " doesn't match the number of provided parameters";
} else {
std::vector<ObjectRef> func_args(size);
inputs_.emplace(func_name, func_args);
}
}

void VirtualMachine::SetInputTensorWithIndex(std::vector<ObjectRef>& tensors,
const TVMArgValue& inp_tensor, int index, Device dev) {
if (inp_tensor.type_code() == kTVMDLTensorHandle) {
// Automatically convert input DLTensors to NDArray
DLTensor* tensor = inp_tensor;
std::vector<int64_t> shape;
for (int64_t i = 0; i < tensor->ndim; i++) {
shape.push_back(tensor->shape[i]);
}
NDArray ary = NDArray::Empty(shape, tensor->dtype, dev);
ary.CopyFrom(tensor);
tensors[index] = ary;
} else {
tensors[index] = CopyTo(inp_tensor, dev);
}
}

inline Device VirtualMachine::GetDevice(Index device_index) const {
ICHECK_GE(devices_.size(), device_index) << "invalid device index: " << device_index;
return devices_[device_index];
Expand Down