-
Notifications
You must be signed in to change notification settings - Fork 27.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Import structure & first three model refactors #31329
Import structure & first three model refactors #31329
Conversation
I'd be interested in having your review when you find the time @amyeroberts @ArthurZucker @Wauplin. |
src/transformers/__init__.py
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is mostly cleanup removing items that should not have been here in the first place
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nice that you added some tests as well!
🔥 mostly let's try to remove the all by using the _TFBertLayer notation instead, and having one dirty file with legacy imports
# "sentencepiece", "tf" | ||
# ) | ||
# ) | ||
elif "backends" in lines[previous_index + 1]: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in this specifc case, parsing the super small code would be simpler with AST, WDYT?
not parsing the entire module, but just a few lines might be fast enough!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or regex might be a bit simpler ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't done this yet. If we see this doesn't affect performance I'm happy to go with that in a future PR.
tests/utils/import_structures/import_structure_register_with_comments.py
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔥 🔥 🔥 🔥 🔥 Can't wait to see so much duplicated code cut!
Main comment is that the @register
pattern is quite repetitive, especially as we know e.g. all the pytorch layers with require torch
.
Some of the logic I think could be simplified with some regex ✨magic✨
Returns the content of the __all__ variable in the file content. | ||
Returns None if not defined, otherwise returns a list of strings. | ||
""" | ||
lines = file_content.split("\n") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like something we could extract directly with a regex
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed! I haven't done that yet but agree it could be done if it doesn't affect performance. Probably in a future PR.
tests/utils/import_structures/import_structure_register_with_duplicates.py
Outdated
Show resolved
Hide resolved
# This allows registering items with other decorators. We'll take a look | ||
# at the line that follows at the same indentation level. | ||
if line.startswith((" ", "\t", "@", ")")) and not line.startswith("@register"): | ||
continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should be able to use a regex to capture just the registered backends which would mean we don't need to try to handle other decorators
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed!
# Backends are defined on the same line as register | ||
if "backends" in previous_line: | ||
backends_string = previous_line.split("backends=")[1].split("(")[1].split(")")[0] | ||
backends = tuple(sorted([b.strip("'\",") for b in backends_string.split(", ")])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for sorting them here? Especially if they're just going to be passed to the frozenset on L1762
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @LysandreJik, thanks for the ping. I review it to the extent of my small knowledge of transformers
but for some parts feel free to ignore. I started my review before Amy and ArthurZucker reviews so some comments might be outdated. And great job overall! Let's make sure to optimize the import logic to have as little overhead as possible :)
class Placeholder(metaclass=DummyObject): | ||
_backends = missing_backends | ||
|
||
def __init__(self, *args, **kwargs): | ||
requires_backends(self, missing_backends) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the logic a bit complex here:
- we have a method
requires_backends
that takes an input an obj and a list of backends- the
obj
is forwarded only to retrieve the name of the class/of the object (L1473)
- the
- we have a
DummyObject
metaclass to define__getattributes__
=> fails when accessing any public property/method - we have a
Placeholder
object using the metaclass + defining a dummy__init__
method that also requires backends.Placeholder.__name__
andPlaceholder.__module__
are then set afterward.
I feel that this is a bit clunky. The ImportError logic is set twice (once in metaclass, one in class). Some class attribute are set in class definition (_backends
) while others are set afterwards (__name__
/__module__
). _backends
is set only so that the metaclass sees it, etc.
Something you can do to dynamically generate classes is to still use a metaclass but use is as a callable. It's quite low-level Python but what you are doing here is low-level anyway. Something like this should work:
class PlaceholderFactory(type):
# 1. check that `_backends` is provided
def __new__(cls, name: str, bases: Any, namespace: Any) -> Any:
if "_backends" not in namespace:
raise RuntimeError("`_backends` must be provided when generating a class with `PlaceholderFactory`.")
return super().__new__(cls, name, bases, namespace)
# 2. forbid to call `__init__`
def __call__(cls, *args, **kwargs) -> Any:
cls._requires_backends()
# 3. forbid any class attributes
def __getattribute__(cls, name: str) -> Any:
if "_" in name:
return super().__getattribute__(name)
cls._requires_backends()
# 4. alias for `requires_backends` but all the logic can also be moved inside it
def _requires_backends(cls):
requires_backends(cls.__name__, cls._backends)
Once you have this, you can dynamically create the placeholder class like this:
value = PlaceholderFactory(name, (), {"__module__": self.__spec__, "_backends": missing_backends})
IMO it makes it clearer which part is handling what.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a very valuable comment with which I entirely agree. I would be happy for us to get to that in a follow-up PR if that works with you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
of course!
def spread_import_structure(nested_import_structure): | ||
def propagate_tuple(unordered_import_structure): | ||
tuple_first_import_structure = {} | ||
for _key, _value in unordered_import_structure.items(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(nit) Instead of _key
and _value
I'd rather write more explicit names like module_name
/ module
(?). The future ourselves will thanks us in 2 years :D
d805428
to
f8527ee
Compare
f8527ee
to
908dceb
Compare
This issue has been automatically marked as stale because it has not had recent activity. If you think this still needs to be addressed please comment on this thread. Please note that issues that do not follow the contributing guidelines are likely to be ignored. |
Not stale |
5d76ab7
to
9b05311
Compare
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com>
3c57001
to
3967eaa
Compare
Thanks all for your reviews! This should now be ready to merge! Since the last review cycle, here are the changes that have been done:
|
There are some nice next steps following this:
|
As seen with @ArthurZucker and @amyeroberts, merging with the first three models. |
* Import structure & first three model refactors * Register -> Export. Export all in __all__. Sensible defaults according to filename. * Apply most comments from Amy and some comments from Lucain Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com> * Style * Add comment * Clearer .py management * Raise if not in backend mapping * More specific type * More efficient listdir * Misc fixes --------- Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com>
* Import structure & first three model refactors * Register -> Export. Export all in __all__. Sensible defaults according to filename. * Apply most comments from Amy and some comments from Lucain Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com> * Style * Add comment * Clearer .py management * Raise if not in backend mapping * More specific type * More efficient listdir * Misc fixes --------- Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com>
* Import structure & first three model refactors * Register -> Export. Export all in __all__. Sensible defaults according to filename. * Apply most comments from Amy and some comments from Lucain Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com> * Style * Add comment * Clearer .py management * Raise if not in backend mapping * More specific type * More efficient listdir * Misc fixes --------- Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com>
* Import structure & first three model refactors * Register -> Export. Export all in __all__. Sensible defaults according to filename. * Apply most comments from Amy and some comments from Lucain Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com> * Style * Add comment * Clearer .py management * Raise if not in backend mapping * More specific type * More efficient listdir * Misc fixes --------- Co-authored-by: amyeroberts <22614925+amyeroberts@users.noreply.github.com> Co-authored-by: Lucain Pouget <lucainp@gmail.com>
Simplifying the import structure
This is the first PR that aims to facilitate adding new models by reducing the overhead caused by the
__init__.py
contributions.It introduces three new concepts:
export
keyword, which accepts thebackends
tuple as input.define_import_structure
, which accepts a filepath as input.__all__
keyword for easy exportsThis approach adds a little overhead to what is currently done as it needs to open files and do some string checks across files. It is negligible compared to framework instantiations like
torch
but should still be highlighted; this approach is extremely specific totransformers
and to the fact that we want to reduce the barrier of contributions to a minimum.@export
The
export
keyword acts as an exporter. For now this is limited to thesrc/transformers/models
path only, but if accepted, it can be propagated to the entire repository.Using the
@export
decorator on a class or method exports it to be importable by third-parties. In doing so, it accepts a tuple ofbackends
. In case a backend isn't installed, the object can still be imported; but using any method or attribute on this object will raise an error.This method aims to deprecate and replace the use of
dummy_xxx
modules and objects.define_import_structure
This method relies on all the objects in a module being correctly marked with
@register
and with the correct backends. It replaces the current complex dict structure dedicated to Lazy loading, and continues supporting lazy loading.The
__all__
keywordThis keyword is used here principally for static type checkers that cannot understand the
@register
keyword. Correctly exporting objects through this enables correct type hinting/type checking.The
define_import_structure
keyword acts in conjunction with the__all__
method and the@export
decorator to correctly identify which imports depend on what backends so as to reflect that in the lazy import scheme.The rest of the model refactor is here: #31330