Skip to content

Conversation

@bdhirsh
Copy link
Contributor

@bdhirsh bdhirsh commented Apr 23, 2021

A few tweaks to the codegen that make the next PR (generating out/inplace wrappers) easier.

  1. [main change] When generating ExternalBackendFunction objects, I added the backend's kernel to the dispatch entry of NativeFunction. The invariant now is that if ExternalBackendFunction implements the XLA backend, then it should expect its corresponding NativeFunction to have an XLA entry in the dispatch table. I do that with a new with_dispatch_key() method on NativeFunction. This makes it easier to re-use the code in register_dispatch_key.py, since the external logic now is mostly just "another dispatch key" (with some exceptions; see the next PR). I'm open to other opinions though.
  2. Doing that requires parsing the backend into a valid dispatch key, so I added more validations and tests. We also technically need to parse the backend's autograd key if they've provided any autograd kernels (if xla provides "backend: XLA" and they have an autograd section, I try to parse the "AutogradXLA" key), so I added tests for that too.
  3. I ended up removing ExternalBackendFunctionsGroup.from_function_group and putting the logic directly in gen_backend_stubs.py. Kind of annoying, but I mostly did it because storing the kernel name directly in ExternalBackendFunction (in the dispatch table) requires calling the dispatcher API, and we can't add a dependency on dispatcher.py directly in model.py. We don't have that problem with NativeFunction objects because the kernel name is provided directly in the yaml, whereas for external ops, we force them to follow the dispatcher convention for naming.

Stack from ghstack:

@facebook-github-bot
Copy link
Contributor

facebook-github-bot commented Apr 23, 2021

💊 CI failures summary and remediations

As of commit 245034d (more details on the Dr. CI page):


  • 1/1 failures introduced in this PR

1 failure not recognized by patterns:

Job Step Action
GitHub Actions quick-checks Ensure no unqualified type ignore 🔁 rerun

This comment was automatically generated by Dr. CI (expand for details).Follow this link to opt-out of these comments for your Pull Requests.

Please report bugs/suggestions to the (internal) Dr. CI Users group.

Click here to manually regenerate this comment.

@bdhirsh bdhirsh requested review from bhosmer and ezyang April 26, 2021 17:41
A few tweaks to the codegen that make the next PR (generating out/inplace wrappers) easier.

1. [**main change**] When generating `ExternalBackendFunction` objects, I added the backend's kernel to the `dispatch` entry of `NativeFunction`. The invariant now is that if `ExternalBackendFunction` implements the `XLA` backend, then it should expect its corresponding `NativeFunction` to have an `XLA` entry in the dispatch table. I do that with a new `with_dispatch_key()` method on `NativeFunction`. This makes it easier to re-use the code in `register_dispatch_key.py`, since the external logic now is mostly just "another dispatch key" (with some exceptions; see the next PR). I'm open to other opinions though.
2. Doing that requires parsing the backend into a valid dispatch key, so I added more validations and tests. We also technically need to parse the backend's autograd key if they've provided any autograd kernels (if xla provides "backend: XLA" and they have an autograd section, I try to parse the "AutogradXLA" key), so I added tests for that too.
3. I ended up removing `ExternalBackendFunctionsGroup.from_function_group` and putting the logic directly in `gen_backend_stubs.py`. Kind of annoying, but I mostly did it because storing the kernel name directly in `ExternalBackendFunction` (in the dispatch table) requires calling the dispatcher API, and we can't add a dependency on `dispatcher.py` directly in `model.py`. We don't have that problem with NativeFunction objects because the kernel name is provided directly in the yaml, whereas for external ops, we force them to follow the dispatcher convention for naming.




[ghstack-poisoned]
return str(self).lower()

def is_autograd_key(self) -> bool:
return 'Autograd' in str(self)
Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps, if str(self).startswith('Autograd') as a more restrictive option

try:
return DispatchKey.parse(value)
except AssertionError:
return None
Copy link
Contributor

Choose a reason for hiding this comment

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

Will be wondering why we need this. If we actually need to detect parse errors we'll need to roll out a separate exception hierarchy for them; catching assert errors is too dangerous

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair; I can rewrite this to avoid the try/catch. Alternatively, if we're fine with the original parsing error message then we don't need this

)

@staticmethod
def with_dispatch_entry(f: 'NativeFunction', dispatch_key: DispatchKey, kernel: str) -> 'NativeFunction':
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a little odd this isn't a method, given that it takes a NativeFunction as argument


backend = yaml_values.pop('backend', None)
assert backend is not None, 'You must provide a value for "backend"'
backend_key = DispatchKey.try_parse(backend)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't you just rely on the parser failure propagating here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I totally can- I added this to make the error message clearer. That way if e.g., a backend without a corresponding autograd key tried to add an autograd entry, they'd get a more actionable error message than a parsing error.

Do you think the parsing error is good enough? We probably want good documentation on the new codegen anyway, that makes it clear how to opt into autograd kernels.

Copy link
Contributor

Choose a reason for hiding this comment

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

Another way to make the error better is to "push an error context" before doing the parse. The preexisting example:

    with context(f'in native_functions.yaml line {f.loc}:\n  {f.func}'):

dispatch_key = DispatchKey.parse(f'Autograd{backend}') \
if m is not None and m.is_autograd else DispatchKey.parse(backend)
kernel = kernel_name(f.func)
return ExternalBackendFunction(NativeFunction.with_dispatch_entry(f, dispatch_key, kernel), dispatch_key, m)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking back to https://github.com/pytorch/pytorch/pull/55050/files#r616215579

The stated motivation for patching in the dispatch information is to make it easier to reuse existing code in the codegen. This is very reasonable. But to me this more and more bends the orientation of the system towards trying to reuse the existing data structures or directly adding in the information as you need, and then augmenting it with side data (so the initial model doesn't have to changed).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After talking to Ed, I'm working on a new patch that will change most of the contents of this PR:

Rather than augmenting NativeFunction directly to be external-backend-aware, I'm going to move data on NativeFunction that is backend-dependent into a different struct. Important stuff it'll contain includes (a) the kernel name (different per backend), and (b) whether the kernel is structured (technically the same for all in-tree backends, but can be different per external backend).

kernel = kernel_name(g.out.func)
dispatch_key = DispatchKey.parse(f'Autograd{backend}') \
if out_meta is not None and out_meta.is_autograd else DispatchKey.parse(backend)
out = ExternalBackendFunction(NativeFunction.with_dispatch_entry(g.out, dispatch_key, kernel), dispatch_key, out_meta)
Copy link
Contributor

Choose a reason for hiding this comment

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

The three blocks here feel juuuust long enough to want some deduping

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fair 😛

Copy link
Contributor

@ezyang ezyang left a comment

Choose a reason for hiding this comment

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

okey dokey

@github-actions
Copy link
Contributor

Looks like this PR hasn't been updated in a while so we're going to go ahead and mark this as Stale.
Feel free to remove the Stale label if you feel this was a mistake.
If you are unable to remove the Stale label please contact a maintainer in order to do so.
If you want the bot to never mark this PR stale again, add the no-stale label.
Stale pull requests will automatically be closed after 30 days of inactivity.

@github-actions github-actions bot added the Stale label Apr 13, 2022
@github-actions github-actions bot closed this May 13, 2022
@facebook-github-bot facebook-github-bot deleted the gh/bdhirsh/108/head branch June 12, 2022 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants