python.py 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476
  1. from dataclasses import dataclass
  2. from typing import Dict, List, Optional, Sequence, Set, Tuple, Union
  3. from torchgen.api import cpp
  4. from torchgen.api.types import Binding, CppSignature, CppSignatureGroup
  5. from torchgen.gen import pythonify_default
  6. from torchgen.model import (
  7. Argument,
  8. BaseTy,
  9. BaseType,
  10. FunctionSchema,
  11. ListType,
  12. NativeFunction,
  13. OptionalType,
  14. Return,
  15. Type,
  16. Variant,
  17. )
  18. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  19. #
  20. # Data Models
  21. #
  22. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  23. #
  24. # [Notes] python binding codegen
  25. #
  26. # The Python binding codegen produces code that takes the input list of
  27. # PyObjects, finds the matching ATen C++ function using PythonArgParser,
  28. # converts the PyObjects into C++ types and calls the ATen C++ function:
  29. #
  30. # +--------+ parsing +------------------------+ binding +-----------------------+
  31. # | PyObjs | ---------> | PythonArgParser Output | ---------> | Cpp Function Dispatch |
  32. # +--------+ +------------------------+ +-----------------------+
  33. #
  34. # The following examples demonstrate the data models the Python binding
  35. # codegen needs to deal with and the tasks it needs to accomplish. It
  36. # helps understand the purpose of the new data types we introduced below.
  37. #
  38. # - Function Schema (source of truth)
  39. #
  40. # aten::empty.names(int[] size, *, Dimname[]? names,
  41. # ScalarType? dtype=None, Layout? layout=None,
  42. # Device? device=None, bool? pin_memory=None,
  43. # MemoryFormat? memory_format=None) -> Tensor
  44. #
  45. # - Python Signature
  46. #
  47. # It's used to generate input schema string for PythonArgParser.
  48. # Note: TensorOptions fields are reordered and the additional
  49. # 'requires_grad' field is added:
  50. #
  51. # empty(IntArrayRef size, *, DimnameList? names,
  52. # MemoryFormat? memory_format=None, ScalarType dtype=None,
  53. # Layout layout=torch.strided, Device device=None,
  54. # bool pin_memory=False, bool requires_grad=False)
  55. #
  56. # - C++ Signature
  57. #
  58. # It's used to generate C++ lambda formals & dispatch call.
  59. # Note: the scattered TensorOptions fields are packed into 'options'.
  60. #
  61. # auto dispatch_empty =
  62. # [](IntArrayRef size, c10::optional<DimnameList> names,
  63. # const TensorOptions & options,
  64. # c10::optional<MemoryFormat> memory_format) -> Tensor {
  65. # pybind11::gil_scoped_release no_gil;
  66. # return torch::empty(size, names, options, memory_format);
  67. # };
  68. #
  69. # - Binding between Python Arguments and C++ Arguments
  70. #
  71. # Given a set of Python Arguments in scope, we need produce the
  72. # binding expressions that translate the Python API into C++ API:
  73. #
  74. # Python Args Cpp Args Binding Exprs
  75. # -----------------------------------------------------------------
  76. # 0: size size '_r.intlist(0)'
  77. # 1: names names 'names' [special init]
  78. # 2: memory_format -------+
  79. # 3: dtype -----+-|--> options 'options' [special packing]
  80. # 4: layout / |
  81. # 5: device / +--> memory_format '_r.memoryformatOptional(2)'
  82. # 6: pin_memory /
  83. # 7: requires_grad -+
  84. #
  85. # So the full dispatch expression would look like:
  86. #
  87. # dispatch_empty(_r.intlist(0), names, options,
  88. # _r.memoryformatOptional(2))
  89. #
  90. # Where does 'names' come from? It involves special local init:
  91. #
  92. # auto __names = _r.toDimnameListOptional(1);
  93. # c10::optional<DimnameList> names =
  94. # __names ? c10::make_optional(DimnameList(__names.value()))
  95. # : c10::nullopt;
  96. #
  97. # Where does 'options' come from? It involves special local init
  98. # for TensorOptions. Note that Python side has the additional
  99. # 'requires_grad' field:
  100. #
  101. # const auto options = TensorOptions()
  102. # .dtype(_r.scalartype(3))
  103. # .device(_r.device(5))
  104. # .layout(_r.layoutOptional(4))
  105. # .requires_grad(_r.toBool(7))
  106. # .pinned_memory(_r.toBool(6));
  107. #
  108. # In some other cases one Python Argument can map to multiple C++
  109. # Arguments. For example:
  110. #
  111. # aten::max.names_dim(Tensor self, Dimname dim, bool keepdim=False)
  112. # -> (Tensor values, Tensor indices)
  113. #
  114. # Python Args Cpp Args Binding Exprs
  115. # ---------------------------------------------------------------------
  116. # +----> max 'out[0]'
  117. # /-----> max_values 'out[1]
  118. # 0: input / self '_r.tensor(0)'
  119. # 1: dim / dim '_r.dimname(1)'
  120. # 2: keepdim / keepdim '_r.toBool(2)'
  121. # 3: out -----+ [local init] out '_r.tensorlist_n<2>(3)'
  122. #
  123. # As demonstrated above, the binding can involve reordering,
  124. # packing, unpacking and special local inits.
  125. #
  126. #
  127. # Let's look at a concrete example:
  128. #
  129. # static PythonArgParser parser({
  130. # "abs(Tensor input, *, Tensor out=None)",
  131. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  132. # ^
  133. # +--- Python Schema, represented by PythonSignature and PythonArgument
  134. #
  135. # }, /*traceable=*/true);
  136. #
  137. # ParsedArgs<2> parsed_args;
  138. # auto _r = parser.parse(nullptr, args, kwargs, parsed_args);
  139. #
  140. # ...
  141. #
  142. # if (_r.isNone(1)) {
  143. # ~~~~~~~~~~~~ <--- Scattered PythonArgParser output (arg name = 'out')
  144. # represented by PythonArgParserOutputExpr
  145. #
  146. # // aten::abs(Tensor self) -> Tensor
  147. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  148. # ^
  149. # +--- NativeFunction schema, base version
  150. #
  151. # auto dispatch_abs = [](const Tensor & self) -> Tensor {
  152. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  153. # ^
  154. # +--- dispatch_lambda_args / dispatch_lambda_return_str
  155. # generated from NativeFunction / CppSignature
  156. # (deprecated PythonSignature is special)
  157. # arguments are represented by DispatchLambdaArgument
  158. #
  159. # pybind11::gil_scoped_release no_gil;
  160. # return self.abs();
  161. # ~~~~~~~~~~~ <--- cpp_dispatch_target / cpp_dispatch_exprs
  162. # generated from NativeFunction / CppSignature
  163. # };
  164. # return wrap(dispatch_abs(_r.tensor(0)));
  165. # ~~~~~~~~~~~~~
  166. # ^
  167. # +--- dispatch_lambda_exprs
  168. # binding PythonArgParserOutputExpr (python args)
  169. # and DispatchLambdaArgument (c++ args)
  170. #
  171. # } else {
  172. # // aten::abs.out(Tensor self, *, Tensor(a!) out) -> Tensor(a!)
  173. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  174. # ^
  175. # +--- NativeFunction schema, out-variant
  176. #
  177. # auto dispatch_abs_out = [](Tensor out, const Tensor & self) -> Tensor {
  178. # pybind11::gil_scoped_release no_gil;
  179. # return at::abs_out(out, self);
  180. # };
  181. # return wrap(dispatch_abs_out(_r.tensor(1), _r.tensor(0)));
  182. # }
  183. #
  184. #
  185. # [Notes] python interface codegen
  186. # The python dataclasses below are used used to generate both python binding code
  187. # and pyi type hint signatures.
  188. # In theory these two should look very similar, but there are number of differences
  189. # in how pyi signatures vs. python_arg_parser signatures are generated.
  190. # These differences have been encapsulated in signature_str() vs. signature_str_pyi()
  191. # to display the full signatures, and argument_str() vs argument_str_pyi() to display arguments.
  192. # For examples, only pyi signatures include return types.
  193. @dataclass(frozen=True)
  194. class PythonReturns:
  195. returns: Tuple[Return, ...]
  196. @dataclass(frozen=True)
  197. class PythonArgument:
  198. name: str
  199. type: Type
  200. default: Optional[str]
  201. # Used to generate the default init expr for some PythonArgParser outputs, e.g.:
  202. #
  203. # _r.layoutWithDefault(3, layout_from_backend(self.options().backend())))
  204. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  205. # ^
  206. # +--- default_init str
  207. default_init: Optional[str]
  208. # Compute argument formal for python argument parsing.
  209. # Needs to be consistent with torch/csrc/utils/python_arg_parser.h.
  210. def argument_str(self, *, method: bool = False, symint: bool = True) -> str:
  211. type_str = (
  212. argument_type_str(self.type, symint=symint)
  213. .replace("const ", "")
  214. .replace(" &", "")
  215. )
  216. name = self.name
  217. # s/self/input/ outside method bindings
  218. # [old codegen] TODO: remove this? doesn't rename in codegen, it's just
  219. # for the parse string
  220. if name == "self" and type_str in ["Tensor", "Number"] and not method:
  221. name = "input"
  222. # add default
  223. if self.default is not None:
  224. default = {
  225. "nullptr": "None",
  226. "c10::nullopt": "None",
  227. "{}": "None",
  228. }.get(self.default, self.default)
  229. return f"{type_str} {name}={default}"
  230. else:
  231. return f"{type_str} {name}"
  232. def argument_str_pyi(
  233. self, *, method: bool = False, deprecated: bool = False
  234. ) -> str:
  235. type_str = argument_type_str_pyi(self.type)
  236. name = self.name
  237. # s/self/input/ outside method bindings
  238. # [old codegen] TODO: remove this? doesn't rename in codegen, it's just
  239. # for the parse string
  240. if name == "self" and type_str == "Tensor" and not method and not deprecated:
  241. name = "input"
  242. if name == "from": # from is a Python keyword...
  243. name += "_"
  244. # pyi merges the _out and functional variants into the same signature, with an optional out arg
  245. if name == "out" and type_str == "Tensor" and not deprecated:
  246. type_str = "Optional[" + type_str + "]"
  247. # pyi deprecated signatures don't get defaults for their out arg
  248. treat_as_no_default = (
  249. deprecated
  250. and isinstance(self, PythonOutArgument)
  251. and self.default == "None"
  252. )
  253. # add default
  254. if self.default is not None and not treat_as_no_default:
  255. if (
  256. isinstance(self.type, ListType)
  257. and self.type.elem == BaseType(BaseTy.int)
  258. and self.default.startswith("{")
  259. and self.default.endswith("}")
  260. ):
  261. default = "(" + self.default[1:-1] + ")"
  262. else:
  263. default = {
  264. "nullptr": "None",
  265. "c10::nullopt": "None",
  266. "{}": "None",
  267. "MemoryFormat::Contiguous": "contiguous_format",
  268. "QScheme::PER_TENSOR_AFFINE": "per_tensor_affine",
  269. }.get(self.default, self.default)
  270. return f"{name}: {type_str}={default}"
  271. else:
  272. return f"{name}: {type_str}"
  273. @dataclass(frozen=True)
  274. class PythonOutArgument(PythonArgument):
  275. # In Python signature multiple output fields are packed into one 'out' argument.
  276. # When binding to C++, it's first binded to a local 'out' variable:
  277. # 'auto out = _r.tensorlist_n<2>(2);',
  278. # then binded to scattered C++ output arguments as 'out[0]', 'out[1]', and etc.
  279. # TODO: maybe don't need keep scattered out fields for python signature?
  280. outputs: Tuple[PythonArgument, ...]
  281. @staticmethod
  282. def from_outputs(
  283. outputs: Tuple[PythonArgument, ...]
  284. ) -> Optional["PythonOutArgument"]:
  285. if not outputs:
  286. return None
  287. size = len(outputs)
  288. if size == 1:
  289. return PythonOutArgument(
  290. name=outputs[0].name,
  291. type=outputs[0].type,
  292. default="None",
  293. default_init=None,
  294. outputs=outputs,
  295. )
  296. elif size > 1:
  297. if any(map(lambda a: not a.type.is_tensor_like(), outputs)):
  298. raise RuntimeError(f"Unsupported output type: {outputs}")
  299. return PythonOutArgument(
  300. name="out",
  301. # TODO: shouldn't this be OptionalType[ListType[...]], since it defaults to None?
  302. type=ListType(BaseType(BaseTy.Tensor), size),
  303. default="None",
  304. default_init=None,
  305. outputs=outputs,
  306. )
  307. raise AssertionError(r"Unexpected PythonOutArgument size")
  308. @dataclass(frozen=True)
  309. class PythonSignature:
  310. # Base operator name, without inplace/outplace suffix.
  311. name: str
  312. # Positional arguments.
  313. # TODO: create a dedicated SelfArgument type for 'self'?
  314. input_args: Tuple[PythonArgument, ...]
  315. # Keyword arguments excluding the 'out' argument and scattered kwargs belonging
  316. # to TensorOptions (dtype, layout, device, pin_memory, requires_grad, etc).
  317. input_kwargs: Tuple[PythonArgument, ...]
  318. output_args: Optional[PythonOutArgument]
  319. # Return types, which are only used by pyi
  320. returns: PythonReturns
  321. # These are scattered kwargs arguments belonging to TensorOptions.
  322. # When binding to C++, they are packed into a TensorOptions object 'options'.
  323. # It's possible that the C++ signature doesn't take TensorOptions object (e.g.
  324. # for out variant), in which case they will be used as scattered fields without
  325. # being packed into 'options'.
  326. # TODO: maybe create a PythonTensorOptionsArgument?
  327. tensor_options_args: Tuple[PythonArgument, ...]
  328. # method or function signature?
  329. method: bool
  330. @property
  331. def deprecated(self) -> bool:
  332. return False
  333. def arguments(
  334. self, *, skip_outputs: bool = False, skip_tensor_options: bool = False
  335. ) -> Tuple[Union[PythonArgument, PythonOutArgument], ...]:
  336. result: List[Union[PythonArgument, PythonOutArgument]] = []
  337. result.extend(self.input_args)
  338. result.extend(self.input_kwargs)
  339. if self.output_args is not None and not skip_outputs:
  340. result.append(self.output_args)
  341. if not skip_tensor_options:
  342. result.extend(self.tensor_options_args)
  343. return tuple(result)
  344. def arguments_count(self) -> int:
  345. return len(self.arguments())
  346. def output_idx(self) -> int:
  347. return len(self.input_args) + len(self.input_kwargs)
  348. # [old codegen] Compute the Python function signature for argument parsing,
  349. # as specified in torch/csrc/utils/python_arg_parser.h. WARNING:
  350. # this is NOT the same type signature as specified by PEP 484
  351. # as understood by mypy; our format was independently developed
  352. # and has some quirks to make it more suitable specifically
  353. # for error parsing.
  354. #
  355. # For a translation to mypy-valid type signatures, see
  356. # signature_str_pyi().
  357. def signature_str(self, *, skip_outputs: bool = False, symint: bool = True) -> str:
  358. args = self.arguments(skip_outputs=skip_outputs)
  359. schema_formals: List[str] = list(
  360. map(lambda a: a.argument_str(method=self.method, symint=symint), args)
  361. )
  362. positional_argc = len(self.input_args)
  363. if len(schema_formals) > positional_argc:
  364. schema_formals.insert(positional_argc, "*")
  365. return f'{self.name}({", ".join(schema_formals)})'
  366. def signature_str_pyi(self, *, skip_outputs: bool = False) -> str:
  367. args = self.arguments(skip_outputs=skip_outputs)
  368. schema_formals: List[str] = list(
  369. map(lambda a: a.argument_str_pyi(method=self.method), args)
  370. )
  371. positional_argc = len(self.input_args)
  372. if len(schema_formals) > positional_argc:
  373. schema_formals.insert(positional_argc, "*")
  374. # only pyi signatures include returns
  375. returns_str = returns_str_pyi(self)
  376. # pyi also includes self (with no typing/defaults) for methods
  377. if self.method:
  378. schema_formals.insert(0, "self")
  379. return f'def {self.name}({", ".join(schema_formals)}) -> {returns_str}: ...'
  380. def signature_str_pyi_vararg(self, *, skip_outputs: bool = False) -> Optional[str]:
  381. # only pyi uses vararg signatures
  382. args = self.arguments(skip_outputs=skip_outputs)
  383. schema_formals: List[str] = list(
  384. map(lambda a: a.argument_str_pyi(method=self.method), args)
  385. )
  386. # vararg only applies to pyi signatures. vararg variants are not generated for all signatures
  387. num_args = self.arguments_count()
  388. num_positionalargs = len(self.input_args)
  389. have_vararg_version = False
  390. if num_args > 0:
  391. vararg_type = args[0].type
  392. if (
  393. isinstance(vararg_type, ListType)
  394. and str(vararg_type.elem) in ["int", "SymInt"]
  395. and num_positionalargs == 1
  396. ):
  397. have_vararg_version = True
  398. if not have_vararg_version:
  399. return None
  400. # Below are the major changes in vararg vs. regular pyi signatures
  401. # vararg signatures also omit the asterix
  402. schema_formals[0] = "*" + args[0].name + ": _int"
  403. returns_str = returns_str_pyi(self)
  404. # pyi also includes self (with no typing/defaults) for methods
  405. if self.method:
  406. schema_formals.insert(0, "self")
  407. return f'def {self.name}({", ".join(schema_formals)}) -> {returns_str}: ...'
  408. # The deprecated python signature involves some special logic, so create a
  409. # dedicated data model to store these extra properties.
  410. @dataclass(frozen=True)
  411. class PythonSignatureDeprecated(PythonSignature):
  412. # Schema for the deprecated function
  413. deprecated_schema: FunctionSchema
  414. # The deprecated signature might miss some arguments that the corresponding
  415. # C++ signature expects. We need store the constant default values to pass in.
  416. # For example:
  417. # [deprecate signature]: addmm(Scalar beta, Tensor self, Tensor mat1, Tensor mat2)
  418. # [func schema]: aten::addmm(Tensor self, Tensor mat1, Tensor mat2, *, Scalar beta=1, Scalar alpha=1) -> Tensor
  419. # [func call]: self.addmm(mat1, mat2, beta, 1)
  420. # We store ['self', 'mat1', 'mat2', 'beta', '1'] in this case.
  421. deprecated_args_exprs: Tuple[str, ...]
  422. @property
  423. def deprecated(self) -> bool:
  424. return True
  425. def signature_str(self, *, skip_outputs: bool = False, symint: bool = True) -> str:
  426. return (
  427. PythonSignature.signature_str(
  428. self, skip_outputs=skip_outputs, symint=symint
  429. )
  430. + "|deprecated"
  431. )
  432. def signature_str_pyi(self, *, skip_outputs: bool = False) -> str:
  433. args = self.arguments(skip_outputs=skip_outputs)
  434. schema_formals: List[str] = list(
  435. map(lambda a: a.argument_str_pyi(method=self.method, deprecated=True), args)
  436. )
  437. positional_argc = len(self.input_args)
  438. if len(schema_formals) > positional_argc:
  439. schema_formals.insert(positional_argc, "*")
  440. returns_str = returns_str_pyi(self)
  441. return f'def {self.name}({", ".join(schema_formals)}) -> {returns_str}: ...'
  442. def signature_str_pyi_vararg(self, *, skip_outputs: bool = False) -> Optional[str]:
  443. # the codegen doesn't include vararg variants for deprecated signatures
  444. return None
  445. # This struct is used to hold the PythonSignature and its corresponding
  446. # NativeFunction BEFORE grouping base and out-variant functions.
  447. # Why not store NativeFunction in PythonSignature or construct PythonSignature
  448. # from NativeFunction? Because they are not 1-1 mapped.
  449. # One native function could have both deprecated and non-deprecated python
  450. # signatures - NativeFunction doesn't contain information to construct the
  451. # deprecated python signature.
  452. # One python signature is used to handle both the base and the out-variant
  453. # function - see 'PythonSignatureGroup'.
  454. @dataclass(frozen=True)
  455. class PythonSignatureNativeFunctionPair:
  456. signature: PythonSignature
  457. function: NativeFunction
  458. # We merge pairs of functions with signatures that are equivalent mod
  459. # output arguments, and use a single entry in the python_arg_parser sig
  460. # list for both (output arguments become optional).
  461. @dataclass(frozen=True)
  462. class PythonSignatureGroup:
  463. # The signature used for Python argument parsing. The outplace signature
  464. # is preferred if exists, because it can be used to parse inputs for both
  465. # the out-place variant and the base version (with output omitted).
  466. signature: PythonSignature
  467. # The regular ATen declaration (e.g. conv2d)
  468. base: NativeFunction
  469. # The out variant (e.g. conv2d_out)
  470. outplace: Optional[NativeFunction]
  471. @classmethod
  472. def from_pairs(
  473. cls,
  474. functional: PythonSignatureNativeFunctionPair,
  475. out: Optional[PythonSignatureNativeFunctionPair],
  476. ) -> "PythonSignatureGroup":
  477. if out is None:
  478. return PythonSignatureGroup(
  479. signature=functional.signature,
  480. base=functional.function,
  481. outplace=None,
  482. )
  483. # prefer the signature with optional out=... arguments because it's the
  484. # superset that can be used to parse input for both base and outplace.
  485. signature_kwargs = out.signature.__dict__.copy()
  486. # Out overloads in C++ don't have TensorOptions arguments,
  487. # so take these from the functional variant
  488. signature_kwargs[
  489. "tensor_options_args"
  490. ] = functional.signature.tensor_options_args
  491. return PythonSignatureGroup(
  492. signature=type(out.signature)(**signature_kwargs),
  493. base=functional.function,
  494. outplace=out.function,
  495. )
  496. # C++ function dispatch is wrapped in a lambda function. The lambda function
  497. # has almost the same signature as the C++ function, only with some small
  498. # variants - see details below.
  499. # This data model is used to represent arguments of the lambda function
  500. # signature.
  501. @dataclass(frozen=True)
  502. class DispatchLambdaArgument:
  503. name: str
  504. type_str: str
  505. is_out_arg: bool
  506. # To pass PyObjects arguments to C++ function (via the lambda wrapper),
  507. # we need first convert PyObjects into simple C++ objects. This work
  508. # is done by PythonArgParser.
  509. # This data model is used to represent the output of PythonArgParser.
  510. # It has 1-1 mapping with PythonArgument in PythonSignature.
  511. @dataclass(frozen=True)
  512. class PythonArgParserOutputExpr:
  513. # argument name
  514. name: str
  515. # RHS expression to reference PythonArgParser output.
  516. expr: str
  517. # In some special cases we need create different expr, e.g.:
  518. # '_r.isNone(1)' instead of '_r.tensor(1)'.
  519. index: int
  520. # The python argument it maps to.
  521. argument: PythonArgument
  522. @property
  523. def is_none_expr(self) -> str:
  524. return f"_r.isNone({self.index})"
  525. # To pass PythonArgParser output to the lambda wrapper, we need bind
  526. # PythonArgParserOutputExpr to DispatchLambdaArgument.
  527. # They are not always 1-1 mapped, e.g. scattered TensorOptions fields
  528. # need be packed into a TensorOptions object, which is the argument
  529. # that the lambda function wrapper takes.
  530. @dataclass(frozen=True)
  531. class DispatchLambdaArgumentExprs:
  532. # The exprs that provide the binding for lambda arguments, e.g.:
  533. #
  534. # 'self' -> '_r.tensor(0)'
  535. # 'min' -> 'out[0]' / 'min_indices' -> 'out[1]'
  536. # 'options' -> 'options'
  537. #
  538. # It has 1-1 mapping with DispatchLambdaArgument.
  539. exprs: Sequence[str]
  540. # Special local inits, which might introduce new variables that
  541. # the 'exprs' above reference, e.g.:
  542. #
  543. # 'auto out = _r.tensorlist_n<2>(2);'
  544. #
  545. inits: Sequence[str]
  546. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  547. #
  548. # Helper Functions
  549. #
  550. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  551. def _cpp_signature(f: NativeFunction, *, method: bool = False) -> CppSignature:
  552. return CppSignatureGroup.from_native_function(f, method=method).signature
  553. def has_tensor_options(f: NativeFunction) -> bool:
  554. return f.func.arguments.tensor_options is not None
  555. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  556. #
  557. # Python Signature
  558. #
  559. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  560. # 'simple_type' was introduced by the old codegen, which is slightly
  561. # different from the python schema type, e.g.: doesn't have '?' suffix
  562. # for optional Tensor/TensorList; doesn't have '[size]' suffix for list type.
  563. def argument_type_str(
  564. t: Type, *, simple_type: bool = False, symint: bool = True
  565. ) -> str:
  566. if isinstance(t, BaseType):
  567. if t.name == BaseTy.Tensor:
  568. return "Tensor"
  569. elif t.name == BaseTy.int:
  570. return "int64_t"
  571. elif t.name == BaseTy.float:
  572. return "double"
  573. elif t.name == BaseTy.str:
  574. return "c10::string_view"
  575. elif t.name in [
  576. BaseTy.bool,
  577. BaseTy.QScheme,
  578. BaseTy.Scalar,
  579. BaseTy.ScalarType,
  580. BaseTy.Generator,
  581. BaseTy.Storage,
  582. BaseTy.Layout,
  583. BaseTy.Device,
  584. BaseTy.MemoryFormat,
  585. BaseTy.Dimname,
  586. BaseTy.Stream,
  587. BaseTy.ConstQuantizerPtr,
  588. BaseTy.SymInt,
  589. ]:
  590. # These python schema type names line up with their function schema names
  591. return t.name.name
  592. elif isinstance(t, OptionalType):
  593. if str(t.elem) == "Tensor":
  594. # Is it desired to keep '?' for simple_type with new style dispatcher?
  595. return "Tensor?"
  596. elem = argument_type_str(t.elem, simple_type=simple_type, symint=symint)
  597. return f"{elem}?"
  598. elif isinstance(t, ListType):
  599. size = t.size if not simple_type else None
  600. if str(t.elem) == "bool":
  601. assert t.size is not None
  602. return f"::std::array<bool,{t.size}>"
  603. elif str(t.elem) == "int":
  604. return f"IntArrayRef[{size}]" if size is not None else "IntArrayRef"
  605. elif str(t.elem) == "SymInt":
  606. if symint:
  607. return (
  608. f"SymIntArrayRef[{size}]" if size is not None else "SymIntArrayRef"
  609. )
  610. else:
  611. return f"IntArrayRef[{size}]" if size is not None else "IntArrayRef"
  612. elif str(t.elem) == "Tensor":
  613. return f"TensorList[{size}]" if size is not None else "TensorList"
  614. elif str(t.elem) == "Scalar":
  615. return f"ScalarList[{size}]" if size is not None else "ScalarList"
  616. elif str(t.elem) == "Tensor?":
  617. if simple_type:
  618. return "c10::List<c10::optional<Tensor>>"
  619. else:
  620. return "const c10::List<c10::optional<Tensor>> &"
  621. elif str(t.elem) == "Dimname":
  622. return f"DimnameList[{size}]" if size is not None else "DimnameList"
  623. elem = argument_type_str(t.elem, simple_type=simple_type, symint=symint)
  624. return f"ArrayRef<{elem}>"
  625. raise RuntimeError(f"unrecognized type {repr(t)}")
  626. def argument_type_size(t: Type) -> Optional[int]:
  627. l = t.is_list_like()
  628. if l is not None and str(l.elem) != "bool":
  629. return l.size
  630. else:
  631. return None
  632. def argument(a: Argument) -> PythonArgument:
  633. return PythonArgument(
  634. name=a.name,
  635. type=a.type,
  636. # TODO: directly translate a.default to python default
  637. default=str(
  638. pythonify_default(cpp.default_expr(a.default, a.type, symint=False))
  639. )
  640. if a.default is not None
  641. else None,
  642. default_init=None,
  643. )
  644. # Generates a PythonSignature that can be used for either .pyi or PythonArgParser codegen
  645. def signature(
  646. f: NativeFunction, *, method: bool = False, pyi: bool = False
  647. ) -> PythonSignature:
  648. return signature_from_schema(
  649. f.func, category_override=f.category_override, method=method, pyi=pyi
  650. )
  651. def signature_from_schema(
  652. func: FunctionSchema,
  653. *,
  654. category_override: Optional[str],
  655. method: bool = False,
  656. pyi: bool = False,
  657. ) -> PythonSignature:
  658. args: List[Argument] = []
  659. args.extend(func.arguments.pre_self_positional)
  660. # Skip SelfArgument if this is method.
  661. if not method and func.arguments.self_arg is not None:
  662. args.append(func.arguments.self_arg.argument)
  663. args.extend(func.arguments.post_self_positional)
  664. args.extend(func.arguments.pre_tensor_options_kwarg_only)
  665. # Skip TensorOptionsArguments. Python side TensorOptions
  666. # arguments are created based on different rules - see below.
  667. args.extend(func.arguments.post_tensor_options_kwarg_only)
  668. args.extend(func.arguments.out)
  669. input_arg_set = {a.name for a in func.arguments.flat_positional}
  670. kwarg_only_set = {a.name for a in func.arguments.flat_kwarg_only}
  671. out_arg_set = {a.name for a in func.arguments.out}
  672. input_args = tuple(map(argument, filter(lambda a: a.name in input_arg_set, args)))
  673. input_kwargs = tuple(
  674. map(argument, filter(lambda a: a.name in kwarg_only_set, args))
  675. )
  676. outputs = tuple(map(argument, filter(lambda a: a.name in out_arg_set, args)))
  677. # Reintroduce the scattered fields of TensorOptions for Python.
  678. # Compared to the cpp counterpart, the python arguments have new property
  679. # (default_init) and a new argument 'requires_grad', which require some
  680. # special handlings.
  681. # [old codegen] TODO: because these aren't guaranteed to be 100% faithful
  682. # to the original versions in the yaml, this recreation is a potential
  683. # source of drift between eager and JIT. Pull this logic out to a shared place.
  684. has_tensor_input_arg = any(
  685. a.type.is_tensor_like() for a in func.arguments.flat_non_out
  686. )
  687. if any(a.name == "requires_grad" for a in func.schema_order_arguments()):
  688. raise ValueError(
  689. "argument named requires_grad is reserved, should not explicitly add it in the schema"
  690. )
  691. # [old codegen] this probably won't work if one of the returns is not a tensor,
  692. # but it will produce a compile-time error that is obvious.
  693. has_tensor_return = any(r.type.is_tensor_like() for r in func.returns)
  694. name: str = cpp.name(func)
  695. is_factory_function = category_override == "factory" or (
  696. has_tensor_return and not has_tensor_input_arg
  697. )
  698. is_like_or_new_function = (
  699. category_override in ("new", "like")
  700. or name.startswith("new_")
  701. or name.endswith("_like")
  702. )
  703. tensor_options_args: List[PythonArgument] = []
  704. if is_factory_function or is_like_or_new_function:
  705. def topt_default_init(name: str) -> Optional[str]:
  706. topt_args = func.arguments.tensor_options
  707. if topt_args is None:
  708. return None
  709. a = getattr(topt_args, name)
  710. if a.default is None or a.default == "None":
  711. return None
  712. return cpp.default_expr(a.default, a.type, symint=False)
  713. tensor_options_args.append(
  714. PythonArgument(
  715. name="dtype",
  716. type=OptionalType(BaseType(BaseTy.ScalarType)),
  717. default="None",
  718. default_init=(
  719. "self.scalar_type()"
  720. if is_like_or_new_function
  721. else topt_default_init("dtype")
  722. ),
  723. )
  724. )
  725. tensor_options_args.append(
  726. PythonArgument(
  727. name="layout",
  728. type=OptionalType(BaseType(BaseTy.Layout)),
  729. default="None",
  730. default_init=(
  731. "self.layout()"
  732. if is_like_or_new_function
  733. else topt_default_init("layout")
  734. ),
  735. )
  736. )
  737. tensor_options_args.append(
  738. PythonArgument(
  739. name="device",
  740. type=OptionalType(BaseType(BaseTy.Device)),
  741. default="None",
  742. default_init=(
  743. "self.device()"
  744. if is_like_or_new_function
  745. else (
  746. topt_default_init("device")
  747. or "torch::tensors::get_default_device()"
  748. )
  749. ),
  750. )
  751. )
  752. tensor_options_args.append(
  753. PythonArgument(
  754. name="pin_memory",
  755. type=OptionalType(BaseType(BaseTy.bool)),
  756. default="False",
  757. default_init=None,
  758. )
  759. )
  760. tensor_options_args.append(
  761. PythonArgument(
  762. name="requires_grad",
  763. type=OptionalType(BaseType(BaseTy.bool)),
  764. default="False",
  765. default_init=None,
  766. )
  767. )
  768. returns = PythonReturns(returns=func.returns)
  769. return PythonSignature(
  770. name=str(func.name.name),
  771. input_args=input_args,
  772. input_kwargs=input_kwargs,
  773. output_args=PythonOutArgument.from_outputs(outputs),
  774. tensor_options_args=tuple(tensor_options_args),
  775. returns=returns,
  776. method=method,
  777. )
  778. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  779. #
  780. # Python Interface
  781. #
  782. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  783. def namedtuple_fieldnames(returns: Tuple[Return, ...]) -> List[str]:
  784. if len(returns) <= 1 or all(map(lambda r: r.name is None, returns)):
  785. return []
  786. else:
  787. if any(map(lambda r: r.name is None, returns)):
  788. # When building on Windows, `PyStructSequence_UnnamedField` could not be
  789. # resolved by the linker for some reason, which cause error in building:
  790. #
  791. # python_nn_functions.cpp.obj : error LNK2001: unresolved external symbol
  792. # PyStructSequence_UnnamedField
  793. #
  794. # Thus, at this point in time, we do not support unnamed
  795. # fields in namedtuple; you must either name all fields,
  796. # or none of them.
  797. raise ValueError("Unnamed field is not supported by codegen")
  798. return list(map(lambda r: str(r.name), returns))
  799. def argument_type_str_pyi(t: Type) -> str:
  800. add_optional = False
  801. if isinstance(t, OptionalType):
  802. t = t.elem
  803. add_optional = True
  804. if isinstance(t, BaseType):
  805. if t.name == BaseTy.int:
  806. ret = "_int"
  807. if t.name == BaseTy.SymInt:
  808. ret = "Union[_int, SymInt]"
  809. elif t.name == BaseTy.float:
  810. ret = "_float"
  811. elif t.name == BaseTy.str:
  812. ret = "str"
  813. elif t.name == BaseTy.Scalar:
  814. ret = "Number"
  815. elif t.name == BaseTy.ScalarType:
  816. ret = "_dtype"
  817. elif t.name == BaseTy.bool:
  818. ret = "_bool"
  819. elif t.name == BaseTy.QScheme:
  820. ret = "_qscheme"
  821. elif t.name == BaseTy.Layout:
  822. ret = "_layout"
  823. elif t.name == BaseTy.Device:
  824. ret = "Union[_device, str, None]"
  825. elif t.name == BaseTy.MemoryFormat:
  826. ret = "memory_format"
  827. elif t.name == BaseTy.Dimname:
  828. ret = "Union[str, ellipsis, None]"
  829. elif t.name in [BaseTy.Tensor, BaseTy.Generator, BaseTy.Storage, BaseTy.Stream]:
  830. # These python schema type names line up with their function schema names
  831. ret = t.name.name
  832. elif isinstance(t, ListType):
  833. if str(t.elem) == "int":
  834. ret = "Union[_int, _size]" if t.size is not None else "_size"
  835. elif t.is_tensor_like():
  836. # TODO: this doesn't seem right...
  837. # Tensor?[] currently translates to Optional[Union[Tuple[Tensor, ...], List[Tensor]]]
  838. # It should probably translate to Union[Tuple[Optional[Tensor], ...], List[Optional[Tensor]]]
  839. if isinstance(t.elem, OptionalType):
  840. add_optional = True
  841. ret = (
  842. "Union[Tensor, Tuple[Tensor, ...], List[Tensor]]"
  843. if t.size is not None
  844. else "Union[Tuple[Tensor, ...], List[Tensor]]"
  845. )
  846. elif str(t.elem) == "float":
  847. ret = "Sequence[_float]"
  848. else:
  849. elem = argument_type_str_pyi(t.elem)
  850. ret = f"Sequence[{elem}]"
  851. else:
  852. raise RuntimeError(f"unrecognized type {repr(t)}")
  853. if add_optional:
  854. ret = "Optional[" + ret + "]"
  855. return ret
  856. def return_type_str_pyi(t: Type) -> str:
  857. # Where arguments are open to accepting Union, return types should return
  858. # concrete types
  859. if isinstance(t, OptionalType):
  860. inner = return_type_str_pyi(t.elem)
  861. return f"Optional[{inner}]"
  862. if isinstance(t, BaseType):
  863. if t.name == BaseTy.Device:
  864. return "_device"
  865. elif t.name == BaseTy.Dimname:
  866. ret = "Optional[str]"
  867. else:
  868. return argument_type_str_pyi(t)
  869. if isinstance(t, ListType):
  870. inner = return_type_str_pyi(t.elem)
  871. return f"List[{inner}]"
  872. return argument_type_str_pyi(t)
  873. def returns_named_tuple_pyi(signature: PythonSignature) -> Optional[Tuple[str, str]]:
  874. python_returns = [return_type_str_pyi(r.type) for r in signature.returns.returns]
  875. namedtuple_name = signature.name
  876. field_names = namedtuple_fieldnames(signature.returns.returns)
  877. if field_names:
  878. tuple_args = [
  879. f'("{name}", {typ})' for name, typ in zip(field_names, python_returns)
  880. ]
  881. namedtuple_def = f'NamedTuple("{namedtuple_name}", [{", ".join(tuple_args)}])'
  882. return namedtuple_name, namedtuple_def
  883. return None
  884. def returns_str_pyi(signature: PythonSignature) -> str:
  885. field_names = namedtuple_fieldnames(signature.returns.returns)
  886. if field_names:
  887. return f"torch.return_types.{signature.name}"
  888. python_returns = [return_type_str_pyi(r.type) for r in signature.returns.returns]
  889. if len(python_returns) > 1:
  890. return "Tuple[" + ", ".join(python_returns) + "]"
  891. if len(python_returns) == 1:
  892. return python_returns[0]
  893. return "None"
  894. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  895. #
  896. # C++ Function Dispatch
  897. #
  898. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  899. # This section provides APIs to generate the code that does C++ function
  900. # dispatch. The C++ function call is wrapped by a lambda function.
  901. # For example:
  902. #
  903. # // aten::selu_(Tensor(a!) self) -> Tensor(a!)
  904. # auto dispatch_selu_ = [](Tensor self) -> Tensor {
  905. # pybind11::gil_scoped_release no_gil;
  906. # return at::selu_(self);
  907. # };
  908. #
  909. # The lambda function's signature follows the C++ signature in common
  910. # cases, e.g.:
  911. #
  912. # // aten::add.Tensor(Tensor self, Tensor other, *, Scalar alpha=1) -> Tensor
  913. # [](const Tensor & self, const Tensor & other, Scalar alpha) -> Tensor
  914. #
  915. # For out variant the 'out' argument's type is changed from 'Tensor &'
  916. # to 'Tensor'. It's because when calling the lambda it passes in the
  917. # PythonArgParser output '_r.tensor(3)', which is stack allocated object
  918. # and needs to pass by value. Also see comments in 'dispatch_lambda_return_str()'.
  919. #
  920. # // aten::add.out(Tensor self, Tensor other, *, Scalar alpha=1, Tensor(a!) out) -> Tensor(a!)
  921. # [](Tensor out, const Tensor & self, const Tensor & other, Scalar alpha) -> Tensor
  922. #
  923. # For multi-output case it can keep using reference type because the
  924. # PythonArgParser output has been unpacked to local variables, e.g.:
  925. #
  926. # // aten::max.names_dim_max(Tensor self, Dimname dim, bool keepdim=False, *,
  927. # // Tensor(a!) max, Tensor(b!) max_values) -> (Tensor(a!) values, Tensor(b!) indices)
  928. # [](Tensor & max, Tensor & max_values, const Tensor & self, Dimname dim, bool keepdim) -> std::tuple<Tensor,Tensor>
  929. #
  930. # For deprecated python signature, it should follow deprecated python arg order.
  931. # TODO: This is to keep same byte-for-byte result as the old codegen - maybe unnecessary?
  932. def dispatch_lambda_args(
  933. ps: PythonSignature, f: NativeFunction, symint: bool = True
  934. ) -> Tuple[DispatchLambdaArgument, ...]:
  935. if isinstance(ps, PythonSignatureDeprecated):
  936. schema = ps.deprecated_schema
  937. else:
  938. schema = f.func
  939. # Start with cpp arguments - dispatch lambda signature always include 'self'
  940. cpp_args = cpp.arguments(
  941. arguments=schema.arguments,
  942. faithful=False,
  943. symint=symint,
  944. method=False,
  945. cpp_no_default_args=f.cpp_no_default_args,
  946. )
  947. out_args: Set[str] = {a.name for a in schema.arguments.out}
  948. # Convert from cpp argument to lambda argument
  949. def dispatch_lambda_arg(cpp_arg: Binding) -> DispatchLambdaArgument:
  950. type_str = cpp_arg.type
  951. is_out_arg = cpp_arg.name in out_args
  952. if ps.method and cpp_arg.name == "self":
  953. # For method's 'self', we can use 'const Tensor &' and simply ignore mutability!
  954. type_str = "const at::Tensor &"
  955. else:
  956. # For other cases we need prevent dangling refs to temps (unless it's
  957. # unpacked scattered output)
  958. # The reason is explained in the comments above and in 'dispatch_lambda_return_str()'.
  959. # TODO: avoid this special handling?
  960. ensure_temp_safe = len(out_args) <= 1 or not is_out_arg
  961. if ensure_temp_safe:
  962. type_str = {
  963. "at::Tensor &": "at::Tensor",
  964. }.get(type_str, type_str)
  965. return DispatchLambdaArgument(
  966. name=cpp_arg.name,
  967. type_str=type_str,
  968. is_out_arg=is_out_arg,
  969. )
  970. return tuple(map(dispatch_lambda_arg, cpp_args))
  971. # [old codegen] XXX: if you got here because of an assertion failure, it doesn't mean
  972. # it's enough to just extend the list here. Before you do this, make sure
  973. # to add an appropriate wrap() overload in torch/csrc/autograd/utils/wrap_outputs.h.
  974. SUPPORTED_RETURN_TYPES = {
  975. "at::Tensor",
  976. "::std::tuple<at::Tensor,at::Tensor>",
  977. "::std::tuple<at::Tensor,at::Tensor,at::Tensor>",
  978. "::std::tuple<at::Tensor,at::Tensor,at::Tensor,at::Tensor>",
  979. "::std::tuple<at::Tensor,at::Tensor,at::Tensor,at::Tensor,at::Tensor>",
  980. "::std::tuple<at::Tensor,at::Tensor,at::Tensor,at::Tensor,at::Tensor,at::Tensor>",
  981. "::std::tuple<at::Tensor,at::Tensor,at::Tensor,int64_t>",
  982. "::std::tuple<at::Tensor,at::Tensor,double,int64_t>",
  983. "::std::tuple<at::Tensor,at::Tensor,at::Tensor,at::Tensor,int64_t>",
  984. "::std::tuple<at::Tensor,at::Tensor,double,at::Tensor,int64_t>",
  985. "::std::tuple<double,int64_t>",
  986. "::std::tuple<at::Tensor,::std::vector<at::Tensor>>",
  987. "::std::vector<at::Tensor>",
  988. # Needed for flash attention forw/backward
  989. "::std::tuple<at::Tensor,at::Tensor,at::Tensor,at::Tensor,int64_t,int64_t,int64_t,int64_t,at::Tensor>",
  990. "at::Scalar",
  991. "bool",
  992. "int64_t",
  993. "void*",
  994. "void",
  995. "at::QScheme",
  996. "double",
  997. "at::IntArrayRef",
  998. "at::ScalarType",
  999. }
  1000. def dispatch_lambda_return_str(f: NativeFunction) -> str:
  1001. # [old codegen] Remove type annotation (e.g. 'Tensor' rather than 'Tensor &')
  1002. # because the dispatch lambdas take mutable arguments *by value*, not
  1003. # by reference. If you then return a reference to such an argument, you
  1004. # will now have a pointer to a dangling stack entry. Not good.
  1005. #
  1006. # You want:
  1007. #
  1008. # auto dispatch_selu_ = [](Tensor self) -> Tensor { ...; return at::selu_(self); };
  1009. # ^^^^^^
  1010. #
  1011. # *not*
  1012. #
  1013. # auto dispatch_selu_ = [](Tensor self) -> Tensor& { ...; return at::selu_(self); };
  1014. # ^^^^^^^
  1015. #
  1016. # (NB: We can't make dispatch_selu_ take Tensor&, because the enclosing
  1017. # codegen looks like dispatch_selu_(_r.tensor(0)), and you can't take a
  1018. # mutable reference to temporary. Maybe we could assign it to a
  1019. # variable itself.)
  1020. returns_without_annotation = tuple(
  1021. map(lambda r: Return(r.name, r.type, None), f.func.returns)
  1022. )
  1023. return_str = cpp.returns_type(returns_without_annotation, symint=True).cpp_type()
  1024. if return_str not in SUPPORTED_RETURN_TYPES:
  1025. raise RuntimeError(f"{f.func.name} returns unsupported type {return_str}")
  1026. return return_str
  1027. def cpp_dispatch_target(f: NativeFunction) -> str:
  1028. symint = f.func.has_symint()
  1029. name = cpp.name(f.func, symint_overload=symint)
  1030. if Variant.method in f.variants:
  1031. return f"self.{name}"
  1032. if Variant.function in f.variants:
  1033. if has_tensor_options(f) or f.func.name.name.base.endswith("_like"):
  1034. namespace = "torch"
  1035. else:
  1036. namespace = "at"
  1037. return f"{namespace}::{name}"
  1038. raise RuntimeError(f"could not dispatch, neither function nor method: {f.func}")
  1039. def cpp_dispatch_exprs(
  1040. f: NativeFunction,
  1041. *,
  1042. python_signature: Optional[PythonSignature] = None,
  1043. ) -> Tuple[str, ...]:
  1044. cpp_args: Sequence[Binding] = _cpp_signature(f, method=False).arguments()
  1045. exprs: Tuple[str, ...] = tuple()
  1046. if not isinstance(python_signature, PythonSignatureDeprecated):
  1047. # By default the exprs are consistent with the C++ signature.
  1048. exprs = tuple(map(lambda a: a.name, cpp_args))
  1049. else:
  1050. # For deprecated python signature we may need fill in some constants.
  1051. exprs = tuple(
  1052. filter(
  1053. lambda n: n != "out" or f.func.is_out_fn(),
  1054. python_signature.deprecated_args_exprs,
  1055. )
  1056. )
  1057. if Variant.method in f.variants:
  1058. exprs = tuple(filter("self".__ne__, exprs))
  1059. return exprs
  1060. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  1061. #
  1062. # Python / C++ Args Binding
  1063. #
  1064. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
  1065. # We explicitly enumerate the PythonArgParser unpacking methods for all
  1066. # supported types. This might be more verbose than necessary, partially
  1067. # because of the irregularity of unpacking method naming, partially
  1068. # because we want to mimic the old codegen behavior - to reject
  1069. # unexpected and/or unsupported cases which the old codegen rejects.
  1070. # For certain cases it is intentionally more restrictive than necessary,
  1071. # e.g.: it doesn't accepts doublelist with definite size.
  1072. def arg_parser_unpack_method(
  1073. t: Type, default: Optional[str], default_init: Optional[str], *, symint: bool = True
  1074. ) -> str:
  1075. has_default_init = default_init is not None
  1076. if has_default_init and str(t) not in (
  1077. "ScalarType?",
  1078. "ScalarType",
  1079. "Device",
  1080. "Device?",
  1081. "Layout",
  1082. "Layout?",
  1083. "bool",
  1084. "bool?",
  1085. ):
  1086. raise RuntimeError(f"type '{t}' does not supported unpacking with default")
  1087. if isinstance(t, BaseType):
  1088. if t.name in [
  1089. BaseTy.Tensor,
  1090. BaseTy.Stream,
  1091. BaseTy.Storage,
  1092. BaseTy.Scalar,
  1093. BaseTy.Dimname,
  1094. ]:
  1095. # These unpack methods line up with their schema names
  1096. return t.name.name.lower()
  1097. elif t.name == BaseTy.ScalarType:
  1098. return "scalartypeWithDefault" if has_default_init else "scalartype"
  1099. elif t.name == BaseTy.Device:
  1100. return "deviceWithDefault" if has_default_init else "device"
  1101. elif t.name == BaseTy.int:
  1102. return "toInt64"
  1103. elif t.name == BaseTy.SymInt:
  1104. if symint:
  1105. return "toSymInt"
  1106. else:
  1107. return "toInt64"
  1108. elif t.name == BaseTy.bool:
  1109. return "toBoolWithDefault" if has_default_init else "toBool"
  1110. elif t.name == BaseTy.float:
  1111. return "toDouble"
  1112. elif t.name == BaseTy.str:
  1113. return "stringView"
  1114. elif t.name == BaseTy.Layout:
  1115. return "layoutWithDefault" if has_default_init else "layout"
  1116. elif t.name == BaseTy.MemoryFormat:
  1117. return "memoryformat"
  1118. elif isinstance(t, OptionalType):
  1119. if str(t.elem) == "Tensor":
  1120. return "optionalTensor"
  1121. elif str(t.elem) == "Generator":
  1122. return "generator"
  1123. elif str(t.elem) == "Dimname[]":
  1124. return "toDimnameListOptional"
  1125. elif not has_default_init and default in (None, "None", "c10::nullopt"):
  1126. # If default is None: append 'Optional' to elem's unpacking method
  1127. return (
  1128. arg_parser_unpack_method(t.elem, None, None, symint=symint) + "Optional"
  1129. )
  1130. else:
  1131. # Otherwise, load as underlying type with default
  1132. return arg_parser_unpack_method(
  1133. t.elem, default, default_init, symint=symint
  1134. )
  1135. elif isinstance(t, ListType):
  1136. if str(t.elem) == "Tensor":
  1137. # accept and use definite size
  1138. if t.size is not None:
  1139. return f"tensorlist_n<{t.size}>"
  1140. else:
  1141. return "tensorlist"
  1142. elif str(t.elem) == "Tensor?":
  1143. return "list_of_optional_tensors"
  1144. elif str(t.elem) == "Dimname":
  1145. # accept definite size
  1146. return "dimnamelist"
  1147. elif str(t.elem) == "int":
  1148. # accept definite size
  1149. return "intlist"
  1150. elif str(t) == "float[]":
  1151. return "doublelist"
  1152. elif str(t.elem) == "SymInt":
  1153. # accept definite size
  1154. if symint:
  1155. return "symintlist"
  1156. else:
  1157. return "intlist"
  1158. elif str(t) == "Scalar[]":
  1159. return "scalarlist"
  1160. raise RuntimeError(f"type '{t}' is not supported by PythonArgParser")
  1161. # Return RHS expression for python argument using PythonArgParser output.
  1162. # e.g. for arg name 'foo', arg type 'bool', arg_index = 2, returns '_r.toBool(2)'
  1163. def arg_parser_output_expr(
  1164. arg_index: int, a: PythonArgument, *, symint: bool = True
  1165. ) -> PythonArgParserOutputExpr:
  1166. has_default = a.default_init is not None
  1167. unpack_method = arg_parser_unpack_method(
  1168. t=a.type, default=a.default, default_init=a.default_init, symint=symint
  1169. )
  1170. default = f", {a.default_init}" if has_default else ""
  1171. expr = f"_r.{unpack_method}({arg_index}{default})"
  1172. return PythonArgParserOutputExpr(
  1173. name=a.name,
  1174. expr=expr,
  1175. index=arg_index,
  1176. argument=a,
  1177. )
  1178. # Returns a map with key = arg_name and value = PythonArgParserOutputExpr.
  1179. def arg_parser_output_exprs(
  1180. ps: PythonSignature, f: NativeFunction, *, symint: bool = True
  1181. ) -> Dict[str, PythonArgParserOutputExpr]:
  1182. return {
  1183. e.name: e
  1184. for i, a in enumerate(ps.arguments())
  1185. for e in (arg_parser_output_expr(i, a, symint=symint),)
  1186. }
  1187. # argument name to type for scattered tensor options fields
  1188. TENSOR_OPTIONS_FIELDS = {
  1189. "dtype": "ScalarType?",
  1190. "device": "Device?",
  1191. "layout": "Layout?",
  1192. "pin_memory": "bool?",
  1193. "requires_grad": "bool?",
  1194. }
  1195. # bind arg parser outputs (python args) with dispatch lambda arguments (c++ args).
  1196. def dispatch_lambda_exprs(
  1197. ps: PythonSignature, f: NativeFunction, *, symint: bool = True
  1198. ) -> DispatchLambdaArgumentExprs:
  1199. # This method is to bind 'arg_parser_outputs' and 'lambda_args' by producing
  1200. # 'inits' and 'lambda_args_exprs' for each lambda argument using arg parser
  1201. # outputs.
  1202. arg_parser_outputs = arg_parser_output_exprs(ps, f, symint=symint)
  1203. lambda_args = dispatch_lambda_args(ps, f, symint=symint)
  1204. inits: List[str] = []
  1205. lambda_args_exprs: Dict[str, str] = {}
  1206. has_toptions = has_tensor_options(f)
  1207. # 1. special inits/unpacking to provide binding exprs for lambda arguments.
  1208. for a in ps.arguments(skip_tensor_options=True):
  1209. name = a.name
  1210. arg_parser_expr = arg_parser_outputs[a.name].expr
  1211. if has_toptions and name == "self":
  1212. # TODO: why this needs to be special case?
  1213. inits.extend(
  1214. [
  1215. f"auto self = {arg_parser_expr};",
  1216. ]
  1217. )
  1218. lambda_args_exprs[name] = name
  1219. elif (
  1220. isinstance(a, PythonOutArgument)
  1221. and len(a.outputs) > 1
  1222. and f.func.is_out_fn()
  1223. ):
  1224. inits.extend(
  1225. [
  1226. f"auto out = {arg_parser_expr};",
  1227. ]
  1228. )
  1229. for i, out_arg in enumerate(a.outputs):
  1230. lambda_args_exprs[out_arg.name] = f"out[{i}]"
  1231. elif str(a.type) == "Dimname[]?":
  1232. # [old codegen]
  1233. # TODO: make this part of something more general, or get rid of it.
  1234. # optional<ArrayRef<T>> are special. The PythonArgParser returns an
  1235. # optional<vector<T>>, which cannot be implicitly converted to
  1236. # optional<ArrayRef<T>>. One needs to unwrap the optional and rewrap.
  1237. inits.extend(
  1238. [
  1239. f"auto __{name} = {arg_parser_expr};",
  1240. f"c10::optional<DimnameList> {name} = __{name} ? c10::make_optional(DimnameList(__{name}.value())) : c10::nullopt;", # noqa: B950
  1241. ]
  1242. )
  1243. lambda_args_exprs[name] = name
  1244. else:
  1245. # default case - directly using PythonArgParser output expr
  1246. lambda_args_exprs[name] = arg_parser_expr
  1247. # method's self is passed directly to python binding, rather than parsed
  1248. if ps.method:
  1249. lambda_args_exprs["self"] = "self"
  1250. # 2. special packing/checking for TensorOptions.
  1251. tensor_options_args_names = list(map(lambda a: a.name, ps.tensor_options_args))
  1252. if has_toptions:
  1253. if f.func.is_out_fn():
  1254. raise RuntimeError(f"{f.func}: tensor options with output arg")
  1255. for a in ps.tensor_options_args:
  1256. if a.name not in TENSOR_OPTIONS_FIELDS:
  1257. raise RuntimeError(
  1258. f"{f.func}: unrecognized tensor options field '{a.name}' in python binding arguments"
  1259. )
  1260. if str(a.type) != TENSOR_OPTIONS_FIELDS.get(a.name):
  1261. raise RuntimeError(
  1262. f"{f.func}: unrecognized type '{str(a.type)}' for tensor options field '{a.name}'"
  1263. )
  1264. if not all(
  1265. map(lambda a: a in tensor_options_args_names, TENSOR_OPTIONS_FIELDS.keys())
  1266. ):
  1267. raise RuntimeError(
  1268. f"{f.func}: incomplete tensor options args: {tensor_options_args_names}"
  1269. )
  1270. inits.append(
  1271. f"""\
  1272. const auto options = TensorOptions()
  1273. .dtype({arg_parser_outputs['dtype'].expr})
  1274. .device({arg_parser_outputs['device'].expr})
  1275. .layout({arg_parser_outputs['layout'].expr})
  1276. .requires_grad({arg_parser_outputs['requires_grad'].expr})
  1277. .pinned_memory({arg_parser_outputs['pin_memory'].expr});
  1278. torch::utils::maybe_initialize_cuda(options);
  1279. """
  1280. )
  1281. lambda_args_exprs["options"] = "options"
  1282. # 3. special case - access scattered TensorOptions fields without packing
  1283. # TODO: maybe move to the generator side as it's not related to binding.
  1284. if not has_toptions and tensor_options_args_names:
  1285. if "dtype" in tensor_options_args_names:
  1286. # we're an output-arg variant, check these args against output tensor
  1287. if not f.func.is_out_fn():
  1288. raise RuntimeError(
  1289. f"{f.func}: dtype in tensor_options_args without output arg"
  1290. )
  1291. if not all(
  1292. map(lambda a: a in tensor_options_args_names, ("layout", "device"))
  1293. ):
  1294. raise RuntimeError(
  1295. f"{f.func}: incomplete tensor options for output check"
  1296. )
  1297. inits.append(
  1298. f"""\
  1299. check_out_type_matches({arg_parser_outputs['out'].expr}, {arg_parser_outputs['dtype'].expr},
  1300. {arg_parser_outputs['dtype'].is_none_expr}, {arg_parser_outputs['layout'].expr},
  1301. {arg_parser_outputs['device'].expr}, {arg_parser_outputs['device'].is_none_expr});
  1302. """
  1303. )
  1304. # we'll set requires_grad on outgoing tensor
  1305. if "requires_grad" not in tensor_options_args_names:
  1306. raise RuntimeError(
  1307. f'{f.func}: expected "requires_grad" in tensor_options_args absent, but found [{tensor_options_args_names}]'
  1308. )
  1309. return DispatchLambdaArgumentExprs(
  1310. exprs=tuple(map(lambda a: lambda_args_exprs[a.name], lambda_args)),
  1311. inits=inits,
  1312. )