pytorch/torch/csrc/jit/api/module.cpp

413 lines
14 KiB
C++
Raw Normal View History

#include <torch/csrc/jit/api/module.h>
#include <ATen/record_function.h>
#include <c10/util/Exception.h>
#include <torch/csrc/autograd/generated/variable_factories.h>
#include <torch/csrc/jit/frontend/error_report.h>
#include <torch/csrc/jit/frontend/ir_emitter.h>
#include <torch/csrc/jit/frontend/schema_matching.h>
#include <torch/csrc/jit/jit_log.h>
#include <torch/csrc/jit/passes/dead_code_elimination.h>
#include <torch/csrc/jit/passes/inliner.h>
#include <torch/csrc/jit/runtime/operator.h>
[jit] add a compiled script module (#5630) Add script::Module C++ class to represent script modules switch AST -> IR conversion to work on Modules/Methods rather than raw graphs function-only AST -> IR conversion is just a simplified case where there is only one module with a single method and no parameters. introduce SugaredValue in compiler.h to represent values in scope in a script function that are not first-class and that get desugared. This is used to represent the module's self parameter, as well as python function calls, and method calls on tensor provide a Python ScriptModule that provides a nice API on top of script::Module allowing for the definition of script modules with methods, parameters, and submodules Not in this PR but intended for the future: ScriptModule actually subclasses nn.Module, with most methods implemented Unification of tracedmodule and script module functionality into one container class. Detailed changelog: * Switch compiler over to using Module, but don't use them yet. * Remove intermediate attribute encoding in compiler * Create SugaredValue object to handle resolution of compiled module. * switch to_ir to modules, implement Select * hacky python wrappers * Private ScriptModule * Add `define` to script module * Attributes use TK_LIST_LITERAL this anticipates adding a real list literal expression to the language. * Add a metaclass to make sure script stubs are registered * Add a test * Doc createResolutionCallback * Docs and minor editing * Address PR comments * Document * Fix unicode issue
2018-03-12 13:52:40 +00:00
namespace torch {
namespace jit {
static ObjectPtr create_module_object(
c10::QualifiedName class_name,
std::shared_ptr<CompilationUnit> cu,
bool shouldMangle = false) {
// If the name is unqualified, prepend a `__torch__`, similar to what Python
// does with `__main__` for top-level code.
if (class_name.prefix().empty()) {
class_name = c10::QualifiedName("__torch__", class_name.name());
}
if (shouldMangle && cu->get_class(class_name) != nullptr) {
class_name = cu->mangle(class_name);
}
auto cls = ClassType::create(std::move(class_name), cu, /*is_module=*/true);
cu->register_type(cls);
return c10::ivalue::Object::create(
c10::StrongTypePtr(std::move(cu), std::move(cls)), 0);
}
Module::Module(c10::QualifiedName class_name)
: Object(create_module_object(
std::move(class_name),
std::make_shared<CompilationUnit>())) {}
module dedupe (#26666) Summary: Pull Request resolved: https://github.com/pytorch/pytorch/pull/26666 Changes: - Introduce a `ConcreteModuleType` concept. This acts both as the key into the type cache, and as the source of truth for `ModuleValue::attr` queries. It needs to do both jobs because that's how we ensure correctness (if the types are different, it's because `ModuleValue::attr` would return different things). - Now `recursive_script` will first construct a `ConcreteModuleType` and search for a pre-existing type before starting compilation. - All previous paths to creating a `ScriptModule` (including inheriting from `ScriptModule`) are now rewritten to go through `create_script_module`, so that we have only a single place where construction happens. Behavioral changes: - Big change to `torch.jit.ScriptModule` inheritance: all attributes are now recursively scripted if possible, matching recursive scripting semantics. This makes it hard to keep something from being scripted (for example, a Python submodule). Possibly we'll need an `ignore()` type thing for attributes. In particular, this adds `self.training` to *every* ScriptModule, since it's present on every `nn.Module`. - I believe this change to be transparent to existing users of the inheritance API, since if you had an attribute that is unscriptable that you never used, there is no error. In some cases, we will create new attributes (even if they are unused), which will increase serialized model size from before. Test Plan: Imported from OSS Differential Revision: D17551196 Pulled By: suo fbshipit-source-id: b476d1c9feb3ddfd63406d90989aaf9dfe890591
2019-10-12 16:49:56 +00:00
Module::Module(
std::shared_ptr<CompilationUnit> cu,
const c10::ClassTypePtr& type)
: Object(c10::ivalue::Object::create(
module dedupe (#26666) Summary: Pull Request resolved: https://github.com/pytorch/pytorch/pull/26666 Changes: - Introduce a `ConcreteModuleType` concept. This acts both as the key into the type cache, and as the source of truth for `ModuleValue::attr` queries. It needs to do both jobs because that's how we ensure correctness (if the types are different, it's because `ModuleValue::attr` would return different things). - Now `recursive_script` will first construct a `ConcreteModuleType` and search for a pre-existing type before starting compilation. - All previous paths to creating a `ScriptModule` (including inheriting from `ScriptModule`) are now rewritten to go through `create_script_module`, so that we have only a single place where construction happens. Behavioral changes: - Big change to `torch.jit.ScriptModule` inheritance: all attributes are now recursively scripted if possible, matching recursive scripting semantics. This makes it hard to keep something from being scripted (for example, a Python submodule). Possibly we'll need an `ignore()` type thing for attributes. In particular, this adds `self.training` to *every* ScriptModule, since it's present on every `nn.Module`. - I believe this change to be transparent to existing users of the inheritance API, since if you had an attribute that is unscriptable that you never used, there is no error. In some cases, we will create new attributes (even if they are unused), which will increase serialized model size from before. Test Plan: Imported from OSS Differential Revision: D17551196 Pulled By: suo fbshipit-source-id: b476d1c9feb3ddfd63406d90989aaf9dfe890591
2019-10-12 16:49:56 +00:00
c10::StrongTypePtr(std::move(cu), type),
type->numAttributes())) {}
Module::Module(
c10::QualifiedName class_name,
std::shared_ptr<CompilationUnit> cu,
bool shouldMangle)
: Object(create_module_object(
std::move(class_name),
std::move(cu),
shouldMangle)) {}
// first class mode runs models as first class objects,
// and does not force inlining everywhere. This is experimental
// as we bring up the system since it will degrade performance
// and may introduce bugs. test_jit.py provides context managers
// that enable it for specific tests.
thread_local bool inline_everything = false;
bool& getInlineEverythingMode() {
return inline_everything;
}
void Module::to(at::Device device, at::ScalarType dtype, bool non_blocking) {
to_impl(device, dtype, non_blocking);
}
void Module::to(at::ScalarType dtype, bool non_blocking) {
to_impl(/*device=*/c10::nullopt, dtype, non_blocking);
}
void Module::to(at::Device device, bool non_blocking) {
to_impl(device, /*dtype=*/c10::nullopt, non_blocking);
}
void module_state_to(
autograd::Variable variable,
const c10::optional<at::Device>& device,
const c10::optional<at::ScalarType>& dtype,
bool non_blocking) {
// Need to access the `at::Tensor` as a `Variable` here.
// Use the data's original device or dtype if not supplied here.
Remove Variable::Impl and DifferentiableViewImpl (#17072) Summary: As part of the Variable/Tensor merge work: https://github.com/pytorch/pytorch/issues/13638, we make the following changes in this PR: 1. Remove the `Variable::Impl` class and the `DifferentiableViewImpl` class 2. Change all `Variable.data()` call sites to either use `Variable` directly, or use `Variable.tensor_data()` 3. Remove `Variable.data()` API 3. Add `Variable.variable_data()` that matches `tensor.data` in Python API, which creates a new `Variable` that shares the same storage and tensor metadata with the original `Variable`, but with a completely new autograd history. After this PR, Variable doesn't wrap a Tensor internally anymore, and both Variable and Tensor use the same TensorImpl class as its `impl_`. The only difference is that Variable always has AutogradMeta in its TensorImpl, but Tensor doesn't. **Note that this PR is BC-breaking in the following use cases:** **Use Case 1:** Previously, `x.data = y` works even if `x` and `y` are of different TensorImpl type (e.g. `x` is a CPU dense tensor whose impl is of type TensorImpl, while `y` is a CPU sparse tensor whose impl is of type SparseTensorImpl). However, after this PR, `x.data = y` doesn't work anymore if `x` and `y` are of different TensorImpl type, because the underlying implementation `variable.set_data(tensor)` no longer works if `variable` and `tensor` have different TensorImpl type. **Use Case 2:** If a tensor `x`'s `grad` is sparse, accumulating dense gradients to `x` will change the tensor that `x.grad` is pointing to. This is better illustrated with the following example: ```python params = torch.tensor([1.5, 1.5]).requires_grad_() with torch.no_grad(): # Change gradient to a sparse tensor params.grad = torch.sparse_coo_tensor(torch.tensor([[1, 1]]).long(), torch.tensor([1., 1.])) grad_saved = params.grad params.backward(torch.tensor([1.5, 1.5])) assert id(grad_saved) == id(params.grad) # This will fail after this PR ``` The assertion in the last line will fail after this PR, because adding dense gradients to sparse gradients will change the `params.grad` tensor reference. Pull Request resolved: https://github.com/pytorch/pytorch/pull/17072 Differential Revision: D14075257 Pulled By: yf225 fbshipit-source-id: 0e681df641270dea586042dd26db59f2e76b5957
2019-05-24 04:03:29 +00:00
auto new_data = variable.to(
device.value_or(variable.device()),
dtype.value_or(variable.scalar_type()),
non_blocking);
variable.set_data(new_data);
}
void Module::to_impl(
const c10::optional<at::Device>& device,
const c10::optional<at::ScalarType>& dtype,
bool non_blocking) {
for (at::Tensor e : parameters()) {
module_state_to(e, device, dtype, non_blocking);
}
for (at::Tensor e : buffers()) {
module_state_to(e, device, dtype, non_blocking);
}
}
Method::Method(ModulePtr owner, Function* function)
: owner_(std::move(owner)), function_(function) {}
Module Method::owner() const {
return Module(owner_);
}
void Method::run(Stack& stack) {
stack.insert(stack.begin(), owner()._ivalue());
RECORD_TORCHSCRIPT_FUNCTION(name(), stack);
function_->run(stack);
}
IValue Method::operator()(std::vector<IValue> stack, const Kwargs& kwargs) {
stack.insert(stack.begin(), owner()._ivalue());
RECORD_TORCHSCRIPT_FUNCTION(name(), stack);
return (*function_)(std::move(stack), kwargs);
}
First class modules in the compiler, round 2 (#19167) Summary: This PR propagates where we use first-class modules objects into the compiler. This creates a transitionary state where: * compiler.cpp creates Graphs where `self` is a Module class and attributes/parameters/buffers/submodules are looked up with `prim::GetAttr` * GraphExecutor still runs "lowered graphs" where the self object has been removed by a compiler pass `lower_first_class_method`. * Tracing still creates "lowered graphs", and a pass "lift_lowered_method" creates a first-class method graph for things. * This PR separates out Method and Function. A script::Function is a pure Graph with no `self` bound. Similar to Python, a script::Method is just a bound `self` and its underlying `script::Function`. * This PR also separates CompilationUnit from Module. A CompilationUnit is just a list of named script::Functions. Class's have a CompilationUnit holding the class methods, and Modules also have a CompilationUnit holding their Methods. This avoids the weird circular case Module --has a-> Class -> has a -> Module ... Details: * In this transitionary state, we maintain two copies of a Graph, first-class module and lowered. Th first-class one has a self argument that is the module's class type. The lowered one is the lowered graph that uses the initial_ivalues inputs. * When defining lowered methods using `_defined_lowered` we immediately create the first-class equivalent. The reverse is done lazily, creating lowered_methods on demand from the class. * The two way conversions will be deleted in a future PR when the executor itself runs first-class objects. However this requires more changes to (1) the traces, (2) the python bindings, and (3) the onnx export pass and would make this PR way to large. Pull Request resolved: https://github.com/pytorch/pytorch/pull/19167 Differential Revision: D14891966 Pulled By: zdevito fbshipit-source-id: 0b5f03118aa65448a15c7a7818e64089ec93d7ea
2019-04-11 20:30:42 +00:00
void Module::clone_method(
const Module& orig,
const Function& method,
const std::unordered_map<TypePtr, TypePtr>& type_remap) {
// type remapping - when we copy method implementations from one module
// singleton to another, we need to update the types of the self arguments
// to match the new module.
// XXX - this only handles modules that occur as variables, not modules
// that appear in aggregate types. Currently this works fine because
// we restrict how modules can be used during the lowering step. Eventually,
// we will need to decide what it means for us to 'copy' a module.
// For instance, we can copy just the state (parameters, attributes),
// but share the code. Or we can copy the code. If we choose to copy the
// code, what should we do about aggregate types that contain a module?
auto type_remap_fn = [&](TypePtr in) {
auto it = type_remap.find(in);
if (it == type_remap.end())
return in;
return it->second;
};
auto graph = method.graph()->copy();
graph->remapTypes(type_remap_fn);
auto schema = method.getSchema().cloneWithRemappedTypes(type_remap_fn);
const auto this_method_name = getNameForMethod(method.name());
auto copied =
_ivalue()->compilation_unit()->create_function(this_method_name, graph);
type()->addMethod(copied);
copied->setSchema(std::move(schema));
First class modules in the compiler, round 2 (#19167) Summary: This PR propagates where we use first-class modules objects into the compiler. This creates a transitionary state where: * compiler.cpp creates Graphs where `self` is a Module class and attributes/parameters/buffers/submodules are looked up with `prim::GetAttr` * GraphExecutor still runs "lowered graphs" where the self object has been removed by a compiler pass `lower_first_class_method`. * Tracing still creates "lowered graphs", and a pass "lift_lowered_method" creates a first-class method graph for things. * This PR separates out Method and Function. A script::Function is a pure Graph with no `self` bound. Similar to Python, a script::Method is just a bound `self` and its underlying `script::Function`. * This PR also separates CompilationUnit from Module. A CompilationUnit is just a list of named script::Functions. Class's have a CompilationUnit holding the class methods, and Modules also have a CompilationUnit holding their Methods. This avoids the weird circular case Module --has a-> Class -> has a -> Module ... Details: * In this transitionary state, we maintain two copies of a Graph, first-class module and lowered. Th first-class one has a self argument that is the module's class type. The lowered one is the lowered graph that uses the initial_ivalues inputs. * When defining lowered methods using `_defined_lowered` we immediately create the first-class equivalent. The reverse is done lazily, creating lowered_methods on demand from the class. * The two way conversions will be deleted in a future PR when the executor itself runs first-class objects. However this requires more changes to (1) the traces, (2) the python bindings, and (3) the onnx export pass and would make this PR way to large. Pull Request resolved: https://github.com/pytorch/pytorch/pull/19167 Differential Revision: D14891966 Pulled By: zdevito fbshipit-source-id: 0b5f03118aa65448a15c7a7818e64089ec93d7ea
2019-04-11 20:30:42 +00:00
}
void Module::clone_method(const Module& orig, const std::string& name) {
std::unordered_map<TypePtr, TypePtr> type_remap;
std::vector<std::pair<Module, Module>> to_scan = {{orig, *this}};
while (!to_scan.empty()) {
auto entry = to_scan.back();
to_scan.pop_back();
type_remap[entry.first._ivalue()->type()] = entry.second._ivalue()->type();
for (const NameModule& s : entry.first.named_children()) {
to_scan.emplace_back(
s.value, Module(entry.second.attr(s.name).toObject()));
}
}
return clone_method(orig, orig.get_method(name).function(), type_remap);
First class modules in the compiler, round 2 (#19167) Summary: This PR propagates where we use first-class modules objects into the compiler. This creates a transitionary state where: * compiler.cpp creates Graphs where `self` is a Module class and attributes/parameters/buffers/submodules are looked up with `prim::GetAttr` * GraphExecutor still runs "lowered graphs" where the self object has been removed by a compiler pass `lower_first_class_method`. * Tracing still creates "lowered graphs", and a pass "lift_lowered_method" creates a first-class method graph for things. * This PR separates out Method and Function. A script::Function is a pure Graph with no `self` bound. Similar to Python, a script::Method is just a bound `self` and its underlying `script::Function`. * This PR also separates CompilationUnit from Module. A CompilationUnit is just a list of named script::Functions. Class's have a CompilationUnit holding the class methods, and Modules also have a CompilationUnit holding their Methods. This avoids the weird circular case Module --has a-> Class -> has a -> Module ... Details: * In this transitionary state, we maintain two copies of a Graph, first-class module and lowered. Th first-class one has a self argument that is the module's class type. The lowered one is the lowered graph that uses the initial_ivalues inputs. * When defining lowered methods using `_defined_lowered` we immediately create the first-class equivalent. The reverse is done lazily, creating lowered_methods on demand from the class. * The two way conversions will be deleted in a future PR when the executor itself runs first-class objects. However this requires more changes to (1) the traces, (2) the python bindings, and (3) the onnx export pass and would make this PR way to large. Pull Request resolved: https://github.com/pytorch/pytorch/pull/19167 Differential Revision: D14891966 Pulled By: zdevito fbshipit-source-id: 0b5f03118aa65448a15c7a7818e64089ec93d7ea
2019-04-11 20:30:42 +00:00
}
Module Module::copy() const {
return Module(_ivalue()->copy());
}
Module Module::deepcopy() const {
return Module(_ivalue()->deepcopy());
}
Module Module::clone(bool inplace) const {
std::unordered_map<TypePtr, TypePtr> type_remap;
IValue::HashAliasedIValueMap memo;
return clone_impl(type_remap, inplace, memo);
}
Module Module::clone_impl(
std::unordered_map<TypePtr, TypePtr>& type_remap,
bool inplace,
IValue::HashAliasedIValueMap memo) const {
// Create a new _ivalue in the same compilation unit.
// Since now we have shared ClassType, we need to preserve the shared
// ClassType during cloning, so we first need to check if the type
// is already cloned, if so, we'll create a new module with the cloned
// ClassType, if not, we'll create a new module and a new ClassType.
bool type_already_cloned = type_remap.find(type()) != type_remap.end();
Module r;
if (type_already_cloned) {
// if we cloned the class type before, we'll reuse it
Module new_module(
_ivalue()->compilation_unit(), type_remap[type()]->cast<ClassType>());
r = new_module;
} else {
Module new_module(*type()->name(), _ivalue()->compilation_unit(), true);
r = new_module;
type_remap[type()] = r.type();
}
// Copy slots. If a slot is a module - recursively clone it.
size_t N = type()->numAttributes();
for (size_t i = 0; i < N; ++i) {
IValue s = _ivalue()->getSlot(i);
std::string attr_name = type()->getAttributeName(i);
TypePtr attr_type = type()->getAttribute(i);
if (attr_type->is_module()) {
const Module& orig = Module(s.toObject());
Module cloned = orig.clone_impl(type_remap, inplace, memo);
type_remap[orig.type()] = cloned.type();
// NOTE: why do we need to manually setattr on object instead of using
// register_module here? because the attr can be a module interface
// type and hold a Module object still. register_module will not let us
// correctly set up the type for this attr, so we had to do this manually.
// In the case it's an interface type, the type will be shared by the new
// cloned instance in the same compilation unit bc it only contains a list
// of functionSchema
r.type()->addOrCheckAttribute(
attr_name, attr_type->cast<ClassType>() ? cloned.type() : attr_type);
r._ivalue()->setAttr(attr_name, cloned._ivalue());
} else {
// this adds new slot and creates a new attribute for the underlying type
// if the type is not already cloned, otherwise it will only add a new
// slot and typecheck
r.register_attribute(
type()->getAttributeName(i),
attr_type,
// we'll deepcopy the IValue in non inplace option
inplace ? s : s.deepcopy(memo),
type()->is_parameter(i),
type()->is_buffer(i));
}
}
// only clone the methods if the ClassType is not cloned before
if (!type_already_cloned) {
// clone constants
for (size_t i = 0; i < type()->numConstants(); ++i) {
r.type()->addConstant(type()->getConstantName(i), type()->getConstant(i));
}
// clone methods, remapping the types to the cloned ones.
for (auto& fn : type()->methods()) {
r.clone_method(*this, *fn, type_remap);
}
}
return r;
}
void Module::train(bool on) {
for (Module m : modules()) {
if (auto slot = m._ivalue()->type()->findAttributeSlot("training")) {
m._ivalue()->setSlot(*slot, on);
} else {
TORCH_INTERNAL_ASSERT("'training' attribute not found");
}
}
}
IValue Module::create_class(const c10::QualifiedName& name, Stack stack) const {
// Look up the class
const auto classType =
_ivalue()->compilation_unit()->get_class(c10::QualifiedName(name));
if (!classType) {
AT_ERROR(
"Could not find class with name: '",
name.qualifiedName(),
"' in module.");
}
// Create a bare object with correct number of slots
const size_t numAttrs = classType->numAttributes();
auto obj = c10::ivalue::Object::create(
c10::StrongTypePtr(_ivalue()->compilation_unit(), classType), numAttrs);
// Invoke the `__init__()` of the class with the arguments provided.
Stack stackWithSelf = {obj};
for (auto& arg : stack) {
stackWithSelf.push_back(std::move(arg));
}
// Note: following Python, `__init__()` modifies its first parameter in-place
// and returns nothing.
classType->getMethod("__init__").operator()(std::move(stackWithSelf));
return obj;
}
buffer_list Module::buffers(bool recurse) const {
return buffer_list(*this, recurse, /*return_module=*/false);
}
named_buffer_list Module::named_buffers(bool recurse) const {
return named_buffer_list(*this, recurse, /*return_module=*/false);
}
module_list Module::children() const {
return module_list(*this, /*recurse=*/false, /*return_module=*/false);
}
named_module_list Module::named_children() const {
return named_module_list(*this, /*recurse=*/false, /*return_module=*/false);
}
module_list Module::modules() const {
return module_list(*this, /*recurse=*/true, /*return_module=*/true);
}
named_module_list Module::named_modules() const {
return named_module_list(*this, /*recurse=*/true, /*return_module=*/true);
}
parameter_list Module::parameters(bool recurse) const {
return parameter_list(*this, recurse, /*return_module=*/false);
}
named_parameter_list Module::named_parameters(bool recurse) const {
return named_parameter_list(*this, recurse, /*return_module=*/false);
}
attribute_list Module::attributes(bool recurse) const {
return attribute_list(*this, recurse, /*return_module=*/false);
}
named_attribute_list Module::named_attributes(bool recurse) const {
return named_attribute_list(*this, recurse, /*return_module=*/false);
}
void Module::apply(const std::function<void(Module&)>& fn) {
for (Module s : modules()) {
fn(s);
}
}
std::string Module::dump_to_str(
bool print_method_bodies,
bool print_attr_values,
bool print_param_values,
int level = 0) const {
std::stringstream ss;
std::stringstream parameters_ss;
std::stringstream attributes_ss;
std::stringstream methods_ss;
std::stringstream submodules_ss;
for (const NameTensor& p : named_parameters(/*recurse=*/false)) {
parameters_ss << p.name << " = ";
if (print_param_values) {
parameters_ss << p.value << std::endl;
} else {
parameters_ss << "..." << std::endl;
}
}
for (const NameValue& p : named_attributes(/*recurse=*/false)) {
attributes_ss << p.name << " = ";
if (!p.value.isTensor() || print_attr_values) {
attributes_ss << p.value << std::endl;
} else {
attributes_ss << "..." << std::endl;
}
}
for (const Method& method : get_methods()) {
methods_ss << " method " << method.name() << " {" << std::endl;
if (print_method_bodies) {
methods_ss << torch::jit::jit_log_prefix(
" ", method.graph()->toString())
<< std::endl;
}
methods_ss << " }" << std::endl;
}
ss << "module " << type()->name()->qualifiedName() << " {" << std::endl;
ss << " parameters {" << std::endl;
ss << torch::jit::jit_log_prefix(" ", parameters_ss.str());
ss << " }" << std::endl;
ss << " attributes {" << std::endl;
ss << torch::jit::jit_log_prefix(" ", attributes_ss.str());
ss << " }" << std::endl;
ss << " methods {" << std::endl;
ss << torch::jit::jit_log_prefix(" ", methods_ss.str());
ss << " }" << std::endl;
ss << " submodules {" << std::endl;
for (const NameModule& s : named_children()) {
// We do level + 2, because one level of indentation comes from 'submodules'
// scope and the other one goes from a specific submodule we're printing.
ss << s.value.dump_to_str(
print_method_bodies, print_attr_values, print_param_values, level + 2);
}
ss << " }" << std::endl;
ss << "}" << std::endl;
std::string indent(2 * level, ' ');
return torch::jit::jit_log_prefix(indent, ss.str());
}
void Module::dump(
bool print_method_bodies = true,
bool print_attr_values = true,
bool print_param_values = true) const {
std::cout << dump_to_str(
print_method_bodies, print_attr_values, print_param_values)
<< std::endl;
}
} // namespace jit
} // namespace torch
namespace c10 {
torch::jit::Module IValue::toModule() const {
return torch::jit::Module(toObject());
}
bool IValue::isModule() const {
return isObject() && toObjectRef().type()->is_module();
}
} // namespace c10