Skip to content
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

Fast Inheritted Instantiation #4

Open
HamidrezaKmK opened this issue Dec 31, 2022 · 3 comments
Open

Fast Inheritted Instantiation #4

HamidrezaKmK opened this issue Dec 31, 2022 · 3 comments

Comments

@HamidrezaKmK
Copy link
Collaborator

Just like Composites, sometimes when something is inherited then all the way down in the inheritance tree everything should be included in the constructors.

class A:
    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y
class B:
    def __init__(self, a, b) -> None:
        self.a = a
        self.b = b

class C(A, B):
    def __init__(self, c, d, a, b, x, y) -> None:
        self.c = c
        self.d = d
        A.__init__(self, x, y)
        B.__init__(self, a, b)

class D(C):
    def __init__(self, e, f, c, d, a, b, x, y) -> None:
        super().__init__(c, d, a, b, x, y)
        self.e = e
        self.f = f

class E(D):
    def __init__(self, g, h, e, f, c, d, a, b, x, y) -> None:
        super().__init__(e, f, c, d, a, b, x, y)
        self.g = g
        self.h = h

In that case, we can use a notion of dyw.inherited_field as follows:

class A:
    def __init__(self, x, y) -> None:
        self.x = x
        self.y = y
class B:
    def __init__(self, a, b) -> None:
        self.a = a
        self.b = b

@dyw.dynamize
class C(A, B):
    x = dyw.inherited_field(default=1, parent=A)
    y = dyw.inherited_field(default=2, parent=A)
    
    a = dyw.inherited_field(default=10, parent=B)
    b = dyw.inherited_field(default=20, parent=B)

    c = dyw.field(default=100)
    d = dyw.field(default=200)

@dyw.dynamize
class D(C):
    e = dyw.field(default=11)
    f = dyw.field(default=22)

@dyw.dynamize
class E(D):
    g = dyw.field(default=111)
    h = dyw.field(default=222)

This should then be similarly instantiated as the previous one.

@vahidzee
Copy link
Owner

vahidzee commented Dec 31, 2022

Another way is to deduce the inherited fields at runtime and change the __init__ of super on the fly.
Meaning imagine you have the following code:

@dy.dynamize(extends=[torch.nn.Linear])
class MyLinear(torch.nn.Linear):
    def __init__(self, in_features, out_features):
        # do something with in_features and out_features
        # call super().__init__ to create the linear instance itself
        super().__init__(in_features, out_features) # specially changed method which passes on other arguments

There's no actual need to specify which fields of the base class(es) should be dynamized; instead, we can assume that when a class is mentioned as extends when dynamizing a wrapped class, we wish to manipulate certain parts of the instantiation logic of the parent class, and leave the rest as is.

This means that other parameters in the mentioned example, like device and dtype, are to stay the same as the original torch.nn.Linear implementation.
But what if we want to change those at runtime? We want to instantiate a MyLinear with some specific dtype.

I suggest that we wrap the base classes __init__ function, and we already modify the MyLinear when dynamizing. This way, in our wrapper of the parent __init__, we can pass along other parameters which are not mentioned in the MyLinear implementation.

In summary, we can extend the init of a class by mentioning which base classes it tries to extend.

@vahidzee
Copy link
Owner

When using multiple inheritances, as long as the init's of parents don't collide everything is nice and easy, but when they do, we can just agree on a syntax to handle them

Take the example of:

class BaseA:
    def __init__(self, x, y, z):
       pass

class BaseB:
    def __init__(self, y, z, k):
        pass

class BaseC:
    def __init__(self, z, k, i):
        pass

@dy.dynamize(extends=[BaseA, BaseB])
class MyClass(BaseA, BaseB, BaseC):
    def __init__(self, a, b, c):
        # codes here
        BaseA.__init__(self, x=...)
        BaseB.__init__(self, ...)
        BaseC.__init__(self, ...)

First of all, I believe extends is necessary because the user might not want us to dynamically infer the parameters passed on to one or some of the base classes.

Second, in this example, we can't just extend the original __init__ of MyClass so that it takes on y, z, k. We have to know whether the same y can be passed on to BaseA and BaseB or in the case BaseC was also mentioned in the extends list, z could be shared between all three base classes.

Oneway is to create the new init with new parameter names which don't collide with one another, like:

def __init__(self, ..., x, BaseA__y, BaseA__z, BaseB__y, BaseB__z, ...)

Another way is to allow sharing of arguments with the same names, meaning if someone called init with: MyClass(..., y=sth1, z=sth2, BaseB__z=sth3, ...) we share sth1 as the value for y in both BaseA and BaseB, use sth2 as the value for z in any of the base classes that needs it (and are mentioned in the extends list) except for BaseB for which we use z=sth3.

@vahidzee
Copy link
Owner

It's also a good idea to explicitly specify which arguments could be merged between the base classes mentioned in the extends list. For instance we can add a extends_merge_args and mention the name of the variables which could be used across different base classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants