Lec5
def __add__(self, other):
if isinstance(other, Tensor):
return needle.ops.EWiseAdd()(self, other)
else:
return needle.ops.AddScalar(other)(self)
在Python中,
other
通常是一个表示与当前对象进行操作的另一个对象或值。它是一个通用的名称,可以代表任何类型的对象,具体取决于上下文和操作的类型。在特殊方法(例如
__add__
、__sub__
、__mul__
等)中,other
通常表示与当前对象进行操作的另一个对象。这可以是相同类型的对象,也可以是不同类型的对象。__add__
是在Python中用于加法操作的重载符号。当我们对两个对象使用加号运算符时(例如a + b
),Python会尝试调用对象的__add__
方法来执行加法操作。在该方法的实现中,首先通过
isinstance(other, Tensor)
判断参数other
是否是Tensor
类的实例。如果是,表示进行的是张量间的加法操作。如果
other
是Tensor
类的实例,代码调用了needle.ops.EWiseAdd()
创建一个EWiseAdd
操作的实例,并将self
和other
作为参数传递给它。EWiseAdd
是一个用于执行张量逐元素加法的操作。如果
other
不是Tensor
类的实例,表示进行的是张量与标量之间的加法操作。代码调用了needle.ops.AddScalar(other)
创建一个AddScalar
操作的实例,并将other
作为参数传递给它。AddScalar
是一个用于将标量加到张量的每个元素上的操作。根据
Op
类的定义,它是一个操作符的抽象基类,定义了一些必须实现的方法。其中包括__call__
、compute
和gradient
方法。Op
类的__call__
方法在基类中引发了NotImplementedError
,意味着该方法需要在子类中进行实现。Op
类的compute
方法用于计算操作符的前向传播,接受参数args
作为输入。在基类中同样引发了NotImplementedError
,需要在子类中进行实现。Op
类的gradient
方法用于计算给定输出梯度的每个输入值的偏导数。它接受out_grad
(对输出值的梯度)和node
(前向评估的值节点)作为参数,并返回输入节点的偏导数。同样,基类中引发了NotImplementedError
,需要在子类中进行实现。EWiseAdd
类是Op
类的子类,因此继承了Op
类的这些方法。在EWiseAdd
类中,它实现了compute
和gradient
方法,定义了逐元素相加的计算和梯度计算。而__call__
方法并未在EWiseAdd
类中实现,因此会调用父类Op
中的__call__
方法。所以,尽管
EWiseAdd
类没有具体实现__call__
方法,但它通过继承自父类Op
来继承了__call__
方法。调用needle.ops.EWiseAdd()(self, other)
时,会调用父类Op
的__call__
方法,并在其中调用Tensor.make_from_op
方法。而EWiseAdd
类具体实现了compute
和gradient
方法,用于执行逐元素相加的计算和梯度计算。那么
def __call__(self, args)
里的表示什么?在函数定义中,
*args
使用了*
运算符,它被称为"可变位置参数"或"星号参数"。它的作用是允许函数接受任意数量的位置参数,并将这些参数作为一个元组传递给函数体内部。needle.ops.EWiseAdd()(self, other)是在调用needle库里面ops.py的方法EwiseAdd()方法嘛?但是在ops.py里面是这样的,为什么不调用add方法?
您是正确的,
needle.ops.EWiseAdd()(self, other)
是调用了needle
库中ops.py
中的EWiseAdd
类的实例,并通过实例调用了__call__
方法。EWiseAdd
类是一个继承自TensorOp
的类,它实现了逐元素相加的操作。在EWiseAdd
类中,有一个compute
方法用于执行实际的计算操作,以及一个gradient
方法用于计算梯度。在
ops.py
中,还定义了一个名为add
的函数,它是EWiseAdd
类的便捷方式,用于执行逐元素相加的操作。该函数在内部创建了EWiseAdd
类的实例,并通过实例调用了__call__
方法来执行计算。因此,您可以选择调用
add
函数来执行逐元素相加的操作,如needle.ops.add(a, b)
。这样可以更加简洁地进行计算,而不需要直接实例化EWiseAdd
类。使用
needle.ops.EWiseAdd()(self, other)
的方式可能是因为在该上下文中,需要对EWiseAdd
类进行更多的自定义或者使用EWiseAdd
类的其他方法,所以直接实例化了EWiseAdd
类并调用了__call__
方法。
def make_from_op(op: Op, inputs: List["Value"]):
tensor = Tensor.__new__(Tensor)
tensor._init(op, inputs)
if not LAZY_MODE:
tensor.realize_cached_data()
return tensor
在这段代码中,Tensor.__new__(Tensor)
实际上是在动态分配一块内存来创建一个新的 Tensor
对象。
__new__()
是一个用于创建实例的特殊方法。它是在一个类实例化时被调用的,用于创建这个类的实例对象。与 __init__()
方法不同,__new__()
在实例创建之前被调用,因此它负责创建实例并返回它。
在这里,Tensor.__new__(Tensor)
创建了一个新的 Tensor
实例对象,分配了一块内存用于存储该对象的属性和方法。然后,通过调用 _init(op, inputs)
方法进行初始化,将 op
和 inputs
参数传递给 Tensor
对象进行初始化设置。
LAZY_MODE
所以,
*[x.realize_cached_data() for x in self.inputs]
将会将列表推导式生成的列表解包成独立的位置参数,并传递给函数的*args
参数。这样,compute
方法中的args
参数将成为一个元组,其中包含了所有输入节点的实现缓存数据
def realize_cached_data(self):
"""Run compute to realize the cached data"""
# avoid recomputation
if self.cached_data is not None:
return self.cached_data
# note: data implicitly calls realized cached data
self.cached_data = self.op.compute(
*[x.realize_cached_data() for x in self.inputs]
)
self.cached_data
return self.cached_data
class Op:
"""Operator definition."""
def __call__(self, *args):
raise NotImplementedError()
def compute(self, *args: Tuple[NDArray]):
"""Calculate forward pass of operator.
**NDArray is numpy array **
Eager Mode && Lazy Mode
it is costly
使用 detach
class Exp(TensorOp): def compute(self, a): return array_api.exp(a) def gradient(self, out_grad, node): ### BEGIN YOUR SOLUTION raise NotImplementedError() ### END YOUR SOLUTION
使用
numpy
的库
out_grad =, lhs=,rhs=,
def gradient(
self, out_grad: "Value", node: "Value"
) -> Union["Value", Tuple["Value"]]:
"""Compute partial adjoint for each input value for a given output adjoint.
Parameters
----------
out_grad: Value
The adjoint wrt to the output value.
node: Value
The value node of forward evaluation.
Returns
-------
input_grads: Value or Tuple[Value]
A list containing partial gradient adjoints to be propagated to
each of the input node.
"""
raise NotImplementedError()
def gradient_as_tuple(self, out_grad: "Value", node: "Value") -> Tuple["Value"]:
""" Convenience method to always return a tuple from gradient call"""
output = self.gradient(out_grad, node)
if isinstance(output, tuple):
return output
elif isinstance(output, list):
return tuple(output)
else:
return (output,)