forked from pytorch/pytorch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpython_ir.cpp
483 lines (457 loc) · 16.1 KB
/
python_ir.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
#include <torch/csrc/python_headers.h>
#include <torch/csrc/jit/ir.h>
#include <torch/csrc/jit/pybind.h>
#include <torch/csrc/jit/python_tracer.h>
#include <torch/csrc/utils/pybind.h>
#include <torch/csrc/jit/export.h>
#include <torch/csrc/jit/passes/shape_analysis.h>
#include <torch/csrc/jit/passes/python_print.h>
#include <torch/csrc/jit/argument_spec.h>
#include <torch/csrc/utils/auto_gil.h>
#include <torch/csrc/utils/python_strings.h>
#include <iostream>
#include <sstream>
namespace torch { namespace jit {
using c10::Type;
std::string getPythonName(const PyObject* obj_) {
AutoGIL gil;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
PyObject* obj = const_cast<PyObject*>(obj_);
auto v = py::getattr(obj, "__name__", py::str("<python_value>"));
// if this was a autograd.Function recover the name of the class
return py::str(v);
}
std::ostream& printPyObject(std::ostream & out, const THPObjectPtr& obj) {
AutoGIL gil;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
auto pyobj = py::handle(const_cast<PyObject*>(obj.get()));
if (py::isinstance<py::tuple>(pyobj)) {
// This special-case for printing tuples handles a problem where
// str((2L, 3L)) outputs "(2L, 3L)" in Python 2 but "(2, 3)"
// in Python 3. In order to suppress the L-suffix, we must
// manually print the string ourselves, calling str() on the
// sub-elements.
//
// This is a fairly fragile fix (What if you have nested tuples
// in tuples? What if you have dictionaries?) but it seems to hit
// the cases that are triggered in practice in onnx-pytorch. Revisit
// this code if this is not the case.
//
// By the way, one non-solution for this problem is to monkeypatch
// tuple.__str__; this doesn't work because Python doesn't allow
// monkeypatching methods of built-in types.
auto pytuple = pyobj.cast<py::tuple>();
out << "(";
size_t i = 0;
for (const auto& o : pytuple) {
if (i > 0) {
out << ", ";
}
THPObjectPtr str(py::str(o).release().ptr());
out << THPUtils_unpackString(str.get());
i++;
}
if (i == 1) {
out << ",";
}
out << ")";
return out;
} else {
return out << THPUtils_unpackString(py::str(pyobj).ptr());
}
}
// execute a Python function, used for Ops we can't optimize but that we want to optimize around
struct ConcretePythonOp : public PythonOp {
ConcretePythonOp(Graph * graph)
: PythonOp(graph) {}
std::string name() const override {
AutoGIL gil;
if(auto autograd = autogradFunction()) {
return getPythonName(autograd->get());
} else {
return getPythonName(pyobj.get());
}
}
void cloneFrom(Node * other_) override {
Node::cloneFrom(other_);
auto other = other_->cast<PythonOp>();
this->cconv = other->cconv;
Py_INCREF(other->pyobj.get());
this->pyobj = THPObjectPtr(other->pyobj.get());
for(auto & sa : other->scalar_args) {
Py_INCREF(sa.get());
this->scalar_args.emplace_back(sa.get());
}
}
Node * allocNewInstance(Graph * g) override {
return new ConcretePythonOp(g);
}
// recover the autograd.Function instance, if this PythonOp's function
// was originally SomeFunction.apply
// used in ONNX for discovering symbolics
c10::optional<THPObjectPtr> autogradFunction() const override {
AutoGIL gil;
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
py::handle obj = const_cast<PyObject*>(pyobj.get());
auto r = py::getattr(obj, "__self__", py::none());
if(r.is_none())
return c10::nullopt;
auto apply = py::getattr(r, "apply", py::none());
if(apply.is_none())
return c10::nullopt;
auto c = PyObject_RichCompareBool(apply.ptr(), obj.ptr(), Py_NE);
if(PyErr_Occurred())
throw py::error_already_set();
if(c)
return c10::nullopt;
return THPObjectPtr(r.release().ptr());
}
void writeScalars(std::ostream& out) const override {
out << "(";
int i = 0;
for (auto& scalar : scalar_args) {
if (i++ > 0)
out << ", ";
printPyObject(out, scalar);
}
out << ")";
}
};
PythonOp* pythonAllocPythonOp(Graph* g) {
return new ConcretePythonOp(g);
}
void initPythonIRBindings(PyObject * module_) {
setAllocPythonOp(pythonAllocPythonOp);
auto m = py::handle(module_).cast<py::module>();
#define GS(name) \
def(#name,&Graph :: name)
py::class_<Graph,std::shared_ptr<Graph>>(m,"Graph")
.def(py::init<>())
.def("__repr__",[](Graph & g) {
std::stringstream ss;
ss << g;
return ss.str();
})
.def("propagate_shapes", [](std::shared_ptr<Graph> g, std::vector<at::Tensor> inputs, bool with_grad) {
setInputTypes(*g, ArgumentSpec(with_grad, fmap<IValue>(inputs), inputs.size()));
PropagateInputShapes(g);
})
.def("_export_onnx", [](const std::shared_ptr<Graph> g, const std::vector<at::Tensor>& initializers,
int64_t onnx_opset_version, bool defer_weight_export,
::torch::onnx::OperatorExportTypes operator_export_type) {
std::string graph;
RawDataExportMap export_map;
std::tie(graph, export_map) = export_onnx(
g, initializers, onnx_opset_version, defer_weight_export, operator_export_type);
std::unordered_map<std::string, py::bytes> python_serialized_export_map;
for (auto& kv : export_map) {
auto t = kv.second;
size_t copy_bytes = t.type().elementSizeInBytes() * t.numel();
// TODO: this is an unecessary copy. In theory we can directly return
// the map from identifier to Tensor, but we need some API in Python
// to get raw `bytes` containing the raw tensor data.
python_serialized_export_map[kv.first] = py::bytes(static_cast<const char*>(t.data_ptr()), copy_bytes);
}
return std::make_tuple(py::bytes(graph), python_serialized_export_map);
}, py::arg("initializers"),
py::arg("onnx_opset_version")=0,
py::arg("defer_weight_export")=false,
py::arg("operator_export_type")=::torch::onnx::OperatorExportTypes::ONNX)
.def("_pretty_print_onnx", [](const std::shared_ptr<Graph> g,
const std::vector<at::Tensor>& initializers,
int64_t onnx_opset_version, bool defer_weight_export,
::torch::onnx::OperatorExportTypes operator_export_type,
bool google_printer) {
return pretty_print_onnx(
g, initializers, onnx_opset_version, defer_weight_export, operator_export_type,
google_printer);
}, py::arg("initializers"),
py::arg("onnx_opset_version")=0,
py::arg("defer_weight_export")=false,
py::arg("operator_export_type")=::torch::onnx::OperatorExportTypes::ONNX,
py::arg("google_printer")=false)
.def("inputs",[](Graph &g) {
return py::make_iterator(g.inputs().begin(), g.inputs().end());
})
.def("outputs",[](Graph &g) {
return py::make_iterator(g.outputs().begin(), g.outputs().end());
})
// TODO: Iterator invalidation might make this hazardous
.def("nodes",[](Graph &g) {
return py::make_iterator(g.nodes().begin(), g.nodes().end());
})
.def("addInput",[](Graph &g) { return g.addInput(); })
.def("copy",[](Graph &g) {
return g.copy();
})
.GS(eraseInput)
.GS(registerOutput)
.def("create",[](Graph & g, const char * str) {
return g.create(Symbol::fromQualString(str));
})
.def("create",[](Graph & g, const char * str, size_t noutputs) {
return g.create(Symbol::fromQualString(str), noutputs);
})
.def("create",[](Graph & g, const char * str, const std::vector<Value*> & inputs) {
return g.create(Symbol::fromQualString(str),inputs);
})
.def("create",[](Graph & g, const char * str, const std::vector<Value*> & inputs, size_t noutputs) {
return g.create(Symbol::fromQualString(str),inputs, noutputs);
})
.def("param_node", [](Graph &g) {
return g.block()->param_node();
})
.def("return_node", [](Graph &g) {
return g.block()->return_node();
})
.def("pretty_print", [](Graph &g) {
std::ostringstream oss;
g.prettyPrint(oss);
return oss.str();
})
.GS(createFusionGroup)
.def("createClone",[](Graph & g, Node * n, py::object fn) {
return g.createClone(n, [&](Value * e) {
return fn(e).cast<Value*>();
});
})
.GS(appendNode)
.GS(prependNode)
.GS(lint)
.GS(insertNode)
;
#undef GS
#define VS(name) \
def(#name,&Value :: name)
py::class_<Value,std::unique_ptr<Value, py::nodelete>>(m,"Value")
.def("__repr__",[](Value & n) {
std::stringstream ss;
ss << n.uniqueName() << " defined in (" << *n.node() << ")";
return ss.str();
})
.VS(type)
.VS(setType)
.VS(inferTypeFrom)
// skip owningGraph because it returns a raw pointer to a otherwise
// std::shared_ptr stored graph object, and would cause a double free
.VS(unique)
.VS(uniqueName)
.VS(setUniqueName)
.VS(offset)
.VS(uses)
.VS(replaceAllUsesWith)
.def("node",[](Value &v) { return v.node(); })
.def("setTypeAs", [](Value * node, Value * other) {
node->setType(other->type());
return node;
})
.VS(copyMetadata)
.VS(isTensor)
;
#undef VS
py::class_<Block, std::unique_ptr<Block, py::nodelete>>(m, "Block")
.def("nodes",[](Block &b) {
return py::make_iterator(b.nodes().begin(), b.nodes().end());
});
#define NS(name) \
def(#name,&Node :: name)
py::class_<Node,std::unique_ptr<Node, py::nodelete>>(m,"Node")
.def("__repr__",[](Node & n) {
std::stringstream ss;
ss << n;
return ss.str();
})
.def("getSourceLocation", [](Node & n) -> py::object {
std::stringstream ss;
if (auto sl = n.getSourceLocation()) {
sl->highlight(ss);
return py::str(ss.str());
} else {
return py::none();
}
})
.def("hasMultipleOutputs",[](Node&n) {
return n.outputs().size() > 1;
})
.def("outputsSize",[](Node &n) {
return n.outputs().size();
})
.NS(kind)
.def("inputs",[](Node &n) {
return py::make_iterator(n.inputs().begin(), n.inputs().end());
})
.def("outputs",[](Node &n) {
return py::make_iterator(n.outputs().begin(), n.outputs().end());
})
.def("output", [](Node &n) {
return n.output();
})
.NS(addInput)
.NS(replaceInput)
.NS(replaceInputWith)
.NS(replaceAllUsesWith)
.NS(insertBefore)
.NS(insertAfter)
.NS(moveAfter)
.NS(moveBefore)
.NS(removeInput)
.NS(removeAllInputs)
.NS(destroy)
.NS(hasUses)
.NS(eraseOutput)
.NS(addOutput)
.NS(scopeName)
.NS(isNondeterministic)
.def("blocks", [](Node& n) {
return py::make_iterator(n.blocks().begin(), n.blocks().end());
})
.NS(addBlock)
#define AS(name) def(#name,&Attributes<Node> :: name)
// methods from Attributes
.AS(copyAttributes)
.AS(hasAttributes)
#undef AS
#define AS(name) def(#name,&Attributes<Node> :: name ## S)
// The default method names take Symbol, but the string conversion for
// Symbol you to qualify with attr::. This is not very user friendly
// for attributes, so expose the string variants instead.
.AS(hasAttribute)
.AS(kindOf)
.AS(removeAttribute)
.AS(attributeNames)
#undef AS
#define CREATE_ACCESSOR(Kind,method) \
def(#method "_",[](Node & n, const char * name, Kind##Attr::ValueType v) { \
return n . method ## _(Symbol::attr(name), std::move(v)); \
}) \
.def(#method, [](Node & n, const char * name) { \
return n.method(Symbol::attr(name)); \
})
.CREATE_ACCESSOR(Float,f)
.CREATE_ACCESSOR(Floats,fs)
.CREATE_ACCESSOR(String,s)
.CREATE_ACCESSOR(Strings,ss)
.CREATE_ACCESSOR(Int,i)
.CREATE_ACCESSOR(Ints,is)
.CREATE_ACCESSOR(Graph,g)
.CREATE_ACCESSOR(Graphs,gs)
#undef CREATE_ACCESSOR
// Tensor (t_) -- manually written to unwrap the variable into a tensor.
.def("t_",[](Node & n, const char * name, torch::autograd::Variable v) {
return n.t_(Symbol::attr(name), std::move(v.data()));
})
.def("t", [](Node & n, const char * name) {
return torch::autograd::make_variable(n.t(Symbol::attr(name)), /*requires_grad=*/false);
})
// Tensors (ts_) -- manually written to unwrap variables into tensors.
.def("ts_",[](Node & n, const char * name, std::vector<torch::autograd::Variable> vs) {
std::vector<at::Tensor> tensors;
tensors.reserve(vs.size());
for (auto& variable : vs) {
tensors.push_back(std::move(variable.data()));
}
return n.ts_(Symbol::attr(name), std::move(tensors));
})
.def("ts", [](Node & n, const char * name) {
auto tensors = n.ts(Symbol::attr(name));
std::vector<torch::autograd::Variable> variables;
variables.reserve(tensors.size());
for (auto& tensor : tensors) {
variables.push_back(torch::autograd::make_variable(
std::move(tensor), /*requires_grad=*/false));
}
return variables;
})
.def("z_",[](Node & n, const char * name, at::Tensor v) {
return n.t_(Symbol::attr(name), autograd::Variable(v.view({})).data());
})
.def("z",[](Node & n, const char * name) {
return n.t(Symbol::attr(name));
})
.def("zs_",[](Node & n, const char * name, TensorsAttr::ValueType v) {
for (auto& i : v) {
i = autograd::Variable(i.view({})).data();
}
return n.ts_(Symbol::attr(name), std::move(v));
})
.def("zs",[](Node & n, const char * name) {
return n.ts(Symbol::attr(name));
})
.def("pyobj",[](Node & n) {
return py::handle(n.expect<PythonOp>()->pyobj.get()).cast<py::object>();
})
.def("cconv",[](Node & n) {
return n.expect<PythonOp>()->cconv;
})
.def("pyname",[](Node & n) {
return n.expect<PythonOp>()->name();
})
.def("scalar_args",[](Node & n) {
auto op = n.expect<PythonOp>();
auto scalars = py::list();
auto append = scalars.attr("append");
for(auto & arg : op->scalar_args) {
append(py::handle(arg.get()));
}
return scalars;
})
;
using ::c10::Type;
py::class_<Type,std::shared_ptr<Type>>(m,"Type")
.def("__repr__",[](Type & t) {
return t.python_str();
})
.def("str",[](Type & t) {
std::ostringstream s;
s << t;
return s.str();
})
.def("kind",[](const Type& t) {
return typeKindToString(t.kind());
})
.def("sizes",[](Type& t) {
return t.expect<CompleteTensorType>()->sizes();
})
.def("strides",[](Type& t) {
return t.expect<CompleteTensorType>()->strides();
})
.def("contiguous",[](Type& t) {
return std::static_pointer_cast<Type>(t.expect<CompleteTensorType>()->contiguous());
})
.def("scalarType",[](Type& t) {
return toString(t.expect<TensorType>()->scalarType());
})
.def("__eq__", [](std::shared_ptr<Type>& self, std::shared_ptr<Type>& other) {
return *self == *other;
})
.def("isSubtypeOf", [](std::shared_ptr<Type>& self, std::shared_ptr<Type> other) {
return self->isSubtypeOf(other);
});
py::class_<NumberType, Type, std::shared_ptr<NumberType>>(m, "NumberType")
.def_static("get", &NumberType::get);
py::class_<IntType, Type, std::shared_ptr<IntType>>(m, "IntType")
.def_static("get", &IntType::get);
py::class_<FloatType, Type, std::shared_ptr<FloatType>>(m, "FloatType")
.def_static("get", &FloatType::get);
py::class_<DynamicType, Type, std::shared_ptr<DynamicType>>(m, "DynamicType")
.def_static("get", &DynamicType::get);
py::class_<BoolType, Type, std::shared_ptr<BoolType>>(m, "BoolType")
.def_static("get", &BoolType::get);
py::class_<TupleType, Type, std::shared_ptr<TupleType>>(m, "TupleType")
.def(py::init([](std::vector<TypePtr> a){ return TupleType::create(a); }))
.def("elements", [](TupleType &self){
std::vector<TypePtr> types;
for (const auto& type : self.elements()) {
types.push_back(type);
}
return types;
});
py::class_<ListType, Type, std::shared_ptr<ListType>>(m, "ListType")
.def_static("ofInts", &ListType::ofInts)
.def_static("ofTensors", &ListType::ofTensors)
.def("getElementType", &ListType::getElementType);
py::class_<Use>(m,"Use")
.def_readonly("user",&Use::user)
.def_readonly("offset",&Use::offset);
}
}}