<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          深度學習框架如何優(yōu)雅的做算子對齊任務?

          共 19915字,需瀏覽 40分鐘

           ·

          2022-01-23 23:11

          ↑ 點擊藍字?關(guān)注極市平臺

          作者 | BBuf?
          來源 | GiantPandaCV
          編輯 | 極市平臺

          極市導讀

          ?

          文介紹了OneFlow的算子AutoTest框架看一下OneFlow深度學習框架在算子開發(fā)過程中是如何優(yōu)雅的做算子對齊任務。?>>加入極市CV技術(shù)交流群,走在計算機視覺的最前沿

          本文介紹OneFlow的算子AutoTest框架看一下OneFlow深度學習框架在算子開發(fā)過程中是如何優(yōu)雅的做算子對齊任務的(由@大缺弦 開發(fā),后經(jīng)我和其它同事進行擴展和豐富功能形成今天的形態(tài))。這個AutoTest框架也可以很輕易移植到其它深度學習訓練框架使用。

          0. 前言

          之前回答過「如何為PyTorch做貢獻的知乎問題」,原貼見:https://www.zhihu.com/question/502301777/answer/2248950419 ?;卮鹛岬搅巳ツ暝贠neFlow開發(fā)一些算子時,基于算子AutoTest框架找到了一些PyTorch算子的bug,并給PyTorch做出了反饋或修復。但這個回答沒有介紹這個AutoTest框架長什么樣子,以及它背后的原理。因此,這篇文章就用來介紹OneFlow的算子AutoTest框架看一下OneFlow深度學習框架在算子開發(fā)過程中是如何優(yōu)雅的做算子對齊任務的(由@大缺弦 開發(fā),后經(jīng)我和其它同事進行擴展和豐富功能形成今天的形態(tài))。這個AutoTest框架也可以很輕易移植到其它深度學習訓練框架使用,代碼實現(xiàn)在 https://github.com/Oneflow-Inc/oneflow/blob/v0.6.0/python/oneflow/test_utils/automated_test_util/torch_flow_dual_object.py。

          1. 傳統(tǒng)的算子對齊方式

          不局限于OneFlow,任何組織或者個人編寫的深度學習訓練框架都需要驗證算子的實現(xiàn)正確性。那么,深度學習框架中驗證算子正確性的一般做法是什么呢?以百度的PaddlePaddle為例,在驗證算子正確性時一般是根據(jù)調(diào)用其它標準庫獲得的結(jié)果(比如卷積算子的驗證就調(diào)用cudnn的卷積,erf算子的驗證就調(diào)用了scipy的erf)或者直接使用numpy模擬的計算結(jié)果來進行驗證(比如full算子的驗證即為numpy模擬)。在PyTorch的測試中還有硬編碼一些測試樣例的方式,也即將固定輸入樣例的標準答案和算子計算的結(jié)果進行對比,以此判斷算子實現(xiàn)的正確性。

          這些方法都沒有什么問題,但在編寫測試時需要不少的人力并且在算子開發(fā)初期可能有一些corner case會容易想不到。以OneFlow為例,由于算子的行為是對齊PyTorch,如果要驗證轉(zhuǎn)置卷積Op在各種情況下的正確性,那么什么樣的測試代碼才可以全面驗證呢?一種做法是將每個參數(shù)都枚舉出來:

          import?torch
          import?numpy?as?np
          import?oneflow?as?flow

          for?N?in?range(1,?5):
          ????for?C_in?in?range(1,?10):
          ????????for?L_in?in?range(1,?10):
          ????????????for?H_in?in?range(1,?10):
          ????????????????for?C_out?in?range(1,?10):
          ????????????????????for?Ksize?in?range(1,?10):
          ????????????????????????for?Pad?in?range(1,?10):
          ????????????????????????????for?Dilation?in?range(1,?10):
          ????????????????????????????????for?Stride?in?range(1,?min(L_in,?H_in)):
          ????????????????????????????????????for?OutPad?in?range(1,?min(Dilation,?Stride)):
          ????????????????????????????????????????try:
          ????????????????????????????????????????????torch_input?=?torch.randn(N,?C_in,?L_in,?H_in)
          ????????????????????????????????????????????flow_input?=?flow.tensor(torch_input.numpy())
          ????????????????????????????????????????????torch_input.requires_grad?=?True
          ????????????????????????????????????????????flow_input.requires_grad?=?True
          ????????????????????????????????????????????torch_m?=?torch.nn.ConvTranspose2d(in_channels=C_in,?out_channels=C_out,?kernel_size=Ksize,?padding=Pad,?stride=Stride,
          ????????????????????????????????????????????????output_padding=(OutPad),?dilation=Dilation,?bias=False)
          ????????????????????????????????????????????flow_m?=?flow.nn.ConvTranspose2d(in_channels=C_in,?out_channels=C_out,?kernel_size=Ksize,?padding=Pad,?stride=Stride,
          ????????????????????????????????????????????????output_padding=(OutPad),?dilation=Dilation,?bias=False)
          ????????????????????????????????????????????flow_m.weight.data?=?flow.tensor(torch_m.weight.data.detach().numpy(),?requires_grad=True)
          ????????????????????????????????????????????torch_out?=?torch_m(torch_input)
          ????????????????????????????????????????????flow_out?=?flow_m(flow_input)
          ????????????????????????????????????????????torch_out?=?torch_out.sum()
          ????????????????????????????????????????????flow_out?=?flow_out.sum()
          ????????????????????????????????????????????assert(np.allclose(torch_out.detach().numpy(),?flow_out.detach().numpy(),?1e-06,?1e-06)),?"forward?not?equal"
          ????????????????????????????????????????????torch_out.backward()
          ????????????????????????????????????????????flow_out.backward()
          ????????????????????????????????????????????print(torch_input.grad.detach().numpy())
          ????????????????????????????????????????????print(flow_input.grad.detach()[:N,?:C_in,?:L_in,?:H_in].numpy())
          ????????????????????????????????????????????assert(np.allclose(torch_input.grad.detach().numpy(),?flow_input.grad.detach()[:N,?:C_in,?:L_in,?:H_in].numpy(),?1e-03,?1e-03)),?"backward?not?equal"
          ????????????????????????????????????????except?Exception?as?e:
          ????????????????????????????????????????????print('Input?Param?Error')

          但這種做法雖然驗證得比較全面但同樣有缺點。首先枚舉的上界如何確定?如果給了一個大的上界,那么這個算子的驗證時間會非常長,不利于在 CI 流程中使用。如果上界很小就可能忽略一些 corner case,導致測試仍然不會全面并增加算子出 bug 的風險。

          基于算子測試的這些問題,同事 @大缺弦 開發(fā)了一個算子 AutoTest 框架,用于解決 OneFlow 算子和 PyTorch 算子對齊的問題。后來我在此基礎上又為這個 AutoTest 框架豐富了其它的一些功能,感覺目前已經(jīng)比較好使,接下里做一個全面介紹。

          整個 AutoTest 框架只有2個Python文件,即 https://github.com/Oneflow-Inc/oneflow/blob/v0.6.0/python/oneflow/test_utils/automated_test_util/torch_flow_dual_object.py ?和 https://github.com/Oneflow-Inc/oneflow/blob/v0.6.0/python/oneflow/test_utils/automated_test_util/generators.py 。并且這個AutoTest框架可以輕易移植到其它任何深度學習框架去做算子對齊任務。

          2. 算子AutoTest框架用法

          在介紹原理之前,我們先看一下 AutoTest 框架的用法。以上面的反卷積算子為例,使用了 AutoTest 框架之后就可以用下面的代碼來完成算子對齊測試:

          @autotest()
          def?test_deconv2d_with_random_data(test_case):
          ????channels?=?random(1,?6)
          ????m?=?torch.nn.ConvTranspose2d(
          ????????in_channels=channels,
          ????????out_channels=random(1,?20),
          ????????kernel_size=random(1,?4),
          ????????stride=random()?|?nothing(),
          ????????padding=random(1,?3).to(int)?|?nothing(),
          ????????dilation=random(1,?5)?|?nothing(),
          ????????groups=random(1,?5)?|?nothing(),
          ????????padding_mode=constant("zeros")?|?nothing(),
          ????)
          ????m.train(random())
          ????device?=?random_device()
          ????m.to(device)
          ????x?=?random_pytorch_tensor(ndim=4,?dim1=channels).to(device)
          ????y?=?m(x)
          ????return?y

          熟悉PyTorch的小伙伴可以發(fā)現(xiàn)這個算子測試代碼和PyTorch的代碼風格基本一樣。的確,AutoTest框架相當于是一個high level的PyTorch,它的接口和PyTorch一樣,但對于給定的輸入會分別用OneFlow和PyTorch運行一遍,記錄運行過程中得到的每個tensor以及對應梯度tensor的值,再對這些OneFlow和PyTorch分別產(chǎn)生的tensor檢查一遍數(shù)值形狀是否完全相同,以完成自動測試工作,我們后面會細講。

          我們可以再看一個測試matmul算子的例子:

          ?@autotest()
          ?def?test_flow_matmul_with_random_data(test_case):
          ?????k?=?random(1,?6)
          ?????x?=?random_pytorch_tensor(ndim=2,?dim1=k)
          ?????y?=?random_pytorch_tensor(ndim=2,?dim0=k)
          ?????z?=?torch.matmul(x,?y)
          ??return?z

          我們基于random_pytorch_tensor方法構(gòu)造了兩個隨機tensor xy,它們的維度分別是[m, k][k, n],這些維度的值都是隨機生成的。

          執(zhí)行上述兩個測試例子,自動測試框架會自動幫我們隨機出各種合法參數(shù)組合成的Op,并基于數(shù)值和類型完全相同的輸入Tensor(PyTorch和OneFlow各有一份)分別運行PyTorch和OneFlow的代碼,并完成算子的自動測試。由于自動測試框架的用法對齊了PyTorch用法,我們在開發(fā)算子之后編寫測試樣例將非常簡單。不用再引入其它的標準庫或者使用Numpy去模擬一遍算子的前向反向計算過程等,解放了生產(chǎn)力。

          并且測試的時候只要次數(shù)足夠多,就可以很大概率的覆蓋到一些OneFlow算子和PyTorch算子無法對齊的樣例,這個時候如果能拿到對應的復現(xiàn)樣例就可以幫助我們確定OneFlow算子實現(xiàn)是否存在問題。

          3. 算子AutoTest框架實現(xiàn)思路

          了解了AutoTest框架的使用方法之后,這里來講解一下AutoTest框架的實現(xiàn)思路。從上面的用法可以大概可以猜到AutoTest框架在實現(xiàn)時會分成兩部分,一部分是如何產(chǎn)生隨機數(shù)據(jù),另外一部分是運AutoTest部分的程序并記錄和比較中間tensor以及對應的梯度tensor的形狀和數(shù)值。

          3.1 如何產(chǎn)生隨機數(shù)據(jù)?

          這里說的隨機數(shù)據(jù)不僅指的是隨機的輸入tensor,還包含Op的屬性參數(shù)比如上面反卷積Op測試例子中的kernel_size=random(1, 4)就實現(xiàn)了指定kernel_size將會在[1, 4)這個區(qū)間進行取值。

          這部分實現(xiàn)在 https://github.com/Oneflow-Inc/oneflow/blob/v0.6.0/python/oneflow/test_utils/automated_test_util/generators.py 這個文件里。首先我們看一下這個文件導出了哪些接口:

          __all__?=?[
          ????"random_tensor",
          ????"random_bool",
          ????"random_device",
          ????"random",
          ????"random_or_nothing",
          ????"oneof",
          ????"constant",
          ????"nothing"
          ]

          這些接口都是繼承了generator基類用來產(chǎn)生隨機數(shù)據(jù)結(jié)構(gòu)的類,這里的數(shù)據(jù)結(jié)構(gòu)既可以是內(nèi)置類型如int,也可以是自定義數(shù)據(jù)類型比如tensor。AutoTest框架所有的參數(shù)的隨機性都是基于這些方法來做到的,我們看一下generator基類的實現(xiàn):

          class?generator:
          ????def?__init__(self,?children):
          ????????self.children?=?children
          ????????self._value?=?None

          ????def?_init(self):
          ????????self._value?=?None
          ????????for?x?in?self.children:
          ????????????x._init()

          ????def?eval(self):
          ????????self._init()
          ????????return?self.value()

          ????def?_calc_value(self):
          ????????raise?NotImplementedError()

          ????def?value(self):
          ????????if?self._value?is?None:
          ????????????self._value?=?self._calc_value()
          ????????return?self._value

          ????def?size(self):
          ????????return?1

          ????def?__or__(self,?other):
          ????????other?=?pack(other)
          ????????return?oneof(
          ????????????self,?other,?possibility=self.size()?/?(self.size()?+?other.size())
          ????????)

          ????def?__ror__(self,?other):
          ????????return?self?|?other

          ????def?__add__(self,?other):
          ????????return?add(self,?other)

          ????def?__radd__(self,?other):
          ????????return?self?+?other

          ????def?__sub__(self,?other):
          ????????return?self?+?neg(other)

          ????def?__rsub__(self,?other):
          ????????return?neg(self?-?other)

          ????def?__mul__(self,?other):
          ????????return?mul(self,?other)

          ????def?__rmul__(self,?other):
          ????????return?self?*?other

          ????def?to(self,?annotation):
          ????????self._to(annotation)
          ????????for?x?in?self.children:
          ????????????x.to(annotation)
          ????????return?self

          ????def?_to(self,?annotation):
          ????????pass

          這個類不僅持有了_calc_value,value,eval等和取值有關(guān)的函數(shù),還持有size這個反應生成數(shù)據(jù)個數(shù)的函數(shù)。另外還持有了一系列的魔法函數(shù),讓不同的generator子類可以互相組合,提升了自動測試框架書寫的靈活性。最后還有一個to成員函數(shù),這個函數(shù)被繼承generator基類的類重寫,用來確定這個隨機數(shù)據(jù)結(jié)構(gòu)的數(shù)值類型。

          所有的generator派生類都繼承了generator基類,并重寫其中的__init____calc_value,size,_to等成員函數(shù)。比如nothing這個generator的派生類就是直接重寫_calc_value函數(shù),并在其中返回一個什么都不做的類的實體。

          class?Nothing:
          ????pass

          class?nothing(generator):
          ????def?__init__(self):
          ????????super().__init__([])

          ????def?_calc_value(self):
          ????????return?Nothing()

          再例如,random這個generator的派生類的定義如下:

          class?random(generator):
          ????def?__init__(self,?low=1,?high=6):
          ????????self.low?=?pack(low)
          ????????self.high?=?pack(high)
          ????????super().__init__([self.low,?self.high])
          ????????self.annotation?=?None

          ????def?_to(self,?annotation):
          ????????if?self.annotation?is?not?None:
          ????????????return
          ????????if?hasattr(annotation,?"__origin__"):
          ????????????#?PyTorch?_size_2_t?and?similar?types?are?defined?by?type?variables,
          ????????????#?leading?to?unexpected?__args__?and?__origin__
          ????????????#
          ????????????#?>>>?_size_2_t?=?Union[T,?Tuple[T,?T]][int]
          ????????????#?>>>?_size_2_t.__origin__
          ????????????#?typing.Union[~T,?typing.Tuple[~T,?~T]]
          ????????????#
          ????????????#?So?recreate?a?new?annotation?object?by?repr?and?eval
          ????????????#
          ????????????#?>>>?_size_2_t
          ????????????#?typing.Union[int,?typing.Tuple[int,?int]]
          ????????????#?>>>?_size_2_t_new?=?eval(repr(annotation))
          ????????????#?>>>?_size_2_t_new.__origin__
          ????????????#?typing.Union
          ????????????annotation?=?eval(repr(annotation))
          ????????self.annotation?=?annotation

          ????def?_generate(self,?annotation):
          ????????if?hasattr(annotation,?"__origin__"):
          ????????????if?annotation.__origin__?is?Union:
          ????????????????x?=?random_util.choice(annotation.__args__)
          ????????????????return?self._generate(x)
          ????????????if?annotation.__origin__?is?Tuple?or?annotation.__origin__?is?py_tuple:
          ????????????????return?[self._generate(x)?for?x?in?annotation.__args__]
          ????????????else:
          ????????????????raise?NotImplementedError(
          ????????????????????f"Not?implemented?annotation?{annotation}?in?random,?type(annotation.__origin__)?is?{type(annotation.__origin__)}"
          ????????????????)

          ????????low,?high?=?self.low.value(),?self.high.value()

          ????????if?annotation?==?int:
          ????????????val?=?int(rng.integers(low,?high))
          ????????elif?annotation?==?float:
          ????????????val?=?float(rng.random()?*?(high?-?low)?+?low)
          ????????elif?annotation?==?bool:
          ????????????val?=?random_util.choice([True,?False])
          ????????else:
          ????????????raise?NotImplementedError(
          ????????????????f"Not?implemented?annotation?{annotation}?in?random"
          ????????????)
          ????????return?val

          ????def?_calc_value(self):
          ????????return?self._generate(self.annotation)


          def?random_or_nothing(low,?high):
          ????return?oneof(random(low,?high),?nothing(),?possibility=2?/?3)

          這里需要注意的一點是,持有annotation屬性的generator派生類的可以通過to來更新annotation屬性(如random類),也可以忽略這個annotation直接在_calc_value構(gòu)造相應類型的隨機結(jié)果(如random_device類)。

          3.2 AutoTest核心實現(xiàn)

          AutoTest框架的核心實現(xiàn)在 https://github.com/Oneflow-Inc/oneflow/blob/v0.6.0/python/oneflow/test_utils/automated_test_util/torch_flow_dual_object.py 這個文件。這個文件最后2行代碼是:

          torch?=?GetDualObject("",?torch_original,?flow)
          __all__?=?["autotest",?"random_pytorch_tensor"]

          這行代碼torch = GetDualObject("", torch_original, flow) 里面的torch_original表示原始的PyTorch框架,而使用GetDualObject獲得的torch表示是對原始的PyTorch和OneFlow進行了一個封裝,變成了一個high level的PyTorch。因此,這里最關(guān)鍵的實現(xiàn)就是GetDualObject這個函數(shù),我們先不關(guān)注這個函數(shù)具體在做什么,而是它返回了什么。查看代碼可以發(fā)現(xiàn)這個函數(shù)返回了一個DualObject類對象,我們先研究一下這個類:

          class?DualObject:
          ????def?__init__(self,?name,?pytorch,?oneflow):
          ????????self.name?=?name
          ????????self.pytorch?=?pytorch
          ????????self.oneflow?=?oneflow
          ????????if?isinstance(pytorch,?torch_original.nn.Module):
          ????????????state_dict?=?pytorch.state_dict()
          ????????????state_dict?=?{k:?v.detach().cpu().numpy()?for?(k,?v)?in?state_dict.items()}
          ????????????oneflow.load_state_dict(state_dict,?strict=False)
          ????????????if?testing:
          ????????????????dual_modules_to_test.append(self)
          ????????if?isinstance(pytorch,?torch_original.Tensor):
          ????????????if?testing:
          ????????????????dual_objects_to_test.append(self)

          ????def?__repr__(self):
          ????????return?f"PyTorch?object:\n{self.pytorch}\n\nOneFlow?object:\n{self.oneflow}"

          ????def?__getattr__(self,?key):
          ????????pytorch_attr?=?getattr(self.pytorch,?key)
          ????????oneflow_attr?=?getattr(self.oneflow,?key)
          ????????new_name?=?f"{self.name}.{key}"
          ????????global?call_pytorch
          ????????call_pytorch?=?self.pytorch
          ????????return?GetDualObject(new_name,?pytorch_attr,?oneflow_attr)

          __init__中傳入了類對象名和 pytorch/oneflow 兩個對象,在導出 high level 的 PyTorch的時候傳入的是torch_originalflow,而在導出random_pytorch_tensor 接口時傳入的是pytorch_tensoroneflow_tensor。這里不妨先看一下random_pytorch_tensor這個函數(shù)的實現(xiàn):

          def?random_pytorch_tensor(
          ????ndim=None,
          ????dim0=1,
          ????dim1=None,
          ????dim2=None,
          ????dim3=None,
          ????dim4=None,
          ????low=0,
          ????high=1,
          ????dtype=float,
          ????requires_grad=True,
          )
          :

          ????if?isinstance(requires_grad,?generator):
          ????????requires_grad?=?requires_grad.value()
          ????pytorch_tensor?=?(
          ????????random_tensor(ndim,?dim0,?dim1,?dim2,?dim3,?dim4,?low,?high,?dtype)
          ????????.value()
          ????????.requires_grad_(requires_grad?and?dtype?!=?int)
          ????)
          ????flow_tensor?=?flow.tensor(
          ????????pytorch_tensor.detach().cpu().numpy(),
          ????????requires_grad=(requires_grad?and?dtype?!=?int),
          ????)
          ????return?GetDualObject("unused",?pytorch_tensor,?flow_tensor)

          可以看到它和導出high level PyTorch的實現(xiàn)一樣,也是通過調(diào)用GetDualObject來獲得了一個對象。再回到DualObject類的實現(xiàn),可以發(fā)現(xiàn)這里分別使用了dual_modules_to_testdual_objects_to_test這兩個list來分別記錄OneFlow和PyTorch的nn.Module和tensor對象。另外DualObject類還重寫了__getattr__這個魔法方法,這里以Flatten為例來看看這個魔法方法獲取了AutoTest程序中的那些屬性:

          def?__getattr__(self,?key):
          ????????pytorch_attr?=?getattr(self.pytorch,?key)
          ????????oneflow_attr?=?getattr(self.oneflow,?key)
          ????????print(key)
          ????????#?print(pytorch_attr)
          ????????#?print(oneflow_attr)
          ????????new_name?=?f"{self.name}.{key}"

          ????????return?GetDualObject(new_name,?pytorch_attr,?oneflow_attr)

          #?flatten的AutoTest程序
          @autotest(auto_backward=False)
          def?test_against_pytorch(test_case):
          ????m?=?torch.nn.Flatten(
          ????????start_dim=random(1,?6)?|?nothing(),?end_dim=random(1,?6)?|?nothing()
          ????)
          ????m.train(random())
          ????device?=?random_device()
          ????m.to(device)
          ????x?=?random_pytorch_tensor().to(device)
          ????y?=?m(x)
          ????return?y

          然后看一下__getattr__中key的打印結(jié)果:

          nn
          Flatten
          train
          to
          to

          可以看到被autotest()裝飾器修飾的測試程序中的PyTorch或者OneFlow的nn.Module或者其它函數(shù)都重寫了這個方法,它將這些nn.Module或者其它函數(shù)的參數(shù)和屬性都取出來并同樣使用GetDualObject返回一個新的DualObject對象,我們可以打印一下Flatten這個nn.Module對應的DualObject對象是什么:

          PyTorch?object:
          1,?end_dim=-1)>

          OneFlow?object:
          1,?end_dim=-1)>

          GetDualObject這個函數(shù)就是根據(jù)傳入的Pytorch以及OneFlow對象和它們的名字來生成一個DualObject對象。GetDualObject這個函數(shù)會為high level的PyTorch重寫傳入的原始PyTorch以及OneFlow對象的__call__魔法函數(shù),最后返回一個DualObject對象,這個過程還包含了跳過一些不需要關(guān)注的魔法函數(shù)以及檢查傳入對象的屬性是否合法和基于nn.Module和其它API默認參數(shù)的類型對generator繼承類產(chǎn)生的隨機數(shù)據(jù)綁定特定類型的工作(get_args函數(shù)中完成)。這里還有一句對于Tensor方法的特判,因為Tensor方法的調(diào)用方式(通過getattr)和其它Module和函數(shù)不同(通過__call__)。

          GetDualObject的實現(xiàn)思路大致就是這樣,代碼比較長這里就不貼了,感興趣可以在這里查看:https://github.com/Oneflow-Inc/oneflow/blob/v0.6.0/python/oneflow/test_utils/automated_test_util/torch_flow_dual_object.py#L195-L401 。

          最后,我們看一下autotest()裝飾器的實現(xiàn):

          def?autotest(
          ????n=20,
          ????auto_backward=True,
          ????rtol=0.0001,
          ????atol=1e-05,
          ????check_graph=True,
          ????check_allclose=True,
          )
          :

          ????verbose?=?os.getenv("ONEFLOW_TEST_VERBOSE")?is?not?None

          ????def?deco(f):
          [email protected](f)
          ????????def?new_f(test_case):
          ????????????nonlocal?n
          ????????????loop_limit?=?n?*?20
          ????????????loop?=?0
          ????????????while?n?>?0:
          ????????????????clear_note_fake_program()
          ????????????????if?loop?>?loop_limit:
          ????????????????????raise?ValueError("autotest?stuck?in?an?endless?loop!")
          ????????????????dual_modules_to_test.clear()
          ????????????????dual_objects_to_test.clear()
          ????????????????try:
          ????????????????????global?testing
          ????????????????????testing?=?True
          ????????????????????global?testing_graph
          ????????????????????if?check_graph:
          ????????????????????????testing_graph?=?True
          ????????????????????res?=?f(test_case)
          ????????????????????testing?=?False
          ????????????????????testing_graph?=?False
          ????????????????except?(PyTorchDoesNotSupportError,?BothDoNotSupportError)?as?e:
          ????????????????????if?verbose:
          ????????????????????????print(f"{f.__name__}")
          ????????????????????????print(e)
          ????????????????????loop?+=?1
          ????????????????????continue
          ????????????????if?res?is?not?None:
          ????????????????????if?not?isinstance(res,?collections.abc.Sequence):
          ????????????????????????res?=?[res]
          ????????????????????func_outputs?=?res
          ????????????????????for?x?in?res:
          ????????????????????????if?auto_backward:
          ????????????????????????????if?isinstance(x.pytorch,?torch_original.Tensor):
          ????????????????????????????????call_tensor_id.append(id(x.pytorch))
          ????????????????????????????????x.sum().backward()
          ????????????????????????dual_objects_to_test.append(x)
          ????????????????for?x?in?dual_modules_to_test:
          ????????????????????for?key?in?x.pytorch.state_dict().keys():
          ????????????????????????if?key?not?in?x.oneflow.state_dict().keys():
          ????????????????????????????warnings.warn(f"oneflow?module?don't?have?`{key}`")
          ????????????????????????????continue
          ????????????????????????vis_parameters[key]?=?x.pytorch.state_dict()[key]
          ????????????????????????dual_objects_to_test.append(
          ????????????????????????????GetDualObject(
          ????????????????????????????????"unused",
          ????????????????????????????????getattr(x.pytorch,?key),
          ????????????????????????????????getattr(x.oneflow,?key),
          ????????????????????????????)
          ????????????????????????)
          ????????????????????????call_tensor_id.append(id(getattr(x.pytorch,?key)))
          ????????????????????????dual_objects_to_test.append(
          ????????????????????????????GetDualObject(
          ????????????????????????????????"unused",
          ????????????????????????????????getattr(x.pytorch,?key).grad,
          ????????????????????????????????getattr(x.oneflow,?key).grad,
          ????????????????????????????)
          ????????????????????????)
          ????????????????????????call_tensor_id.append(id(getattr(x.pytorch,?key).grad))

          ????????????????for?x?in?dual_objects_to_test:
          ????????????????????if?(
          ????????????????????????isinstance(x.pytorch,?torch_original.Tensor)
          ????????????????????????and?id(x.pytorch)?not?in?call_tensor_id
          ????????????????????):
          ????????????????????????vis_tensor.append(x.pytorch)
          ????????????????#?check?eager
          ????????????????for?x?in?dual_objects_to_test:
          ????????????????????if?check_allclose:
          ????????????????????????test_case.assertTrue(check_equality(x,?rtol=rtol,?atol=atol),?x)
          ????????????????????if?verbose:
          ????????????????????????print(f"{f.__name__}?test?eager?passed.")
          ????????????????????
          ????????????????n?-=?1
          ????????????????loop?+=?1

          ????????return?new_f

          ????return?deco

          這個裝飾器的res = f(test_case)這行代碼會執(zhí)行這個裝飾器修飾的自動測試程序,會在給定輸入的情況下去分別運行PyTorch和OneFlow的程序獲得所有中間的輸出tensor,包括tensor的梯度,并將它們記錄到dual_modules_to_test這個列表。再遍歷這個列表里面的每個tensor,比較數(shù)值和shape是否完全一樣。比較函數(shù)實現(xiàn)在 https://github.com/Oneflow-Inc/oneflow/blob/v0.6.0/python/oneflow/test_utils/automated_test_util/torch_flow_dual_object.py#L565-L599 。原理就是拿到tensor的numpy數(shù)據(jù)進行比較。autotest() 裝飾器還有幾個參數(shù)可以調(diào)整,可以控制測試是否執(zhí)行反向,執(zhí)行次數(shù),以及最后結(jié)果對比的精度閾值。

          4. 自動生成出BUG的程序和數(shù)據(jù)

          上面介紹完了AutoTest框架的原理和使用方法,這里再展示一下基于AutoTest框架如何拿到可復現(xiàn)BUG的程序以及對應的輸入tensor和參數(shù)等。原理很簡單,就是把GetDualObject過程中使用的 api 記錄下來拼起來就構(gòu)成一個完整的程序,這里展示一下在 CI 中的效果。https://github.com/Oneflow-Inc/oneflow/runs/4760189461?check_suite_focus=true 這個例子展示了在某次CI過程中,OneFlow的 conv_transpose2d 算子和 PyTorch 的 conv_transpose2d 算子在某個 case 下沒有對齊,那么 CI 在報告這個錯誤時也輸出了對應的復現(xiàn)代碼和數(shù)據(jù),可以方便框架開發(fā)者進行定位和判斷:

          自動測試框架在算子和PyTorch沒對齊時會輸出復現(xiàn)程序和數(shù)據(jù)

          自動測試框架在算子和PyTorch沒對齊時會輸出復現(xiàn)程序和數(shù)據(jù)

          除此之外,這個 AutoTest 框架目前不僅負責 Eager 算子的測試,還被我們擴展到支持 nn.Graph 和 Eager Consistent 等多種情況,極大的方便了框架開發(fā)者。

          5. 總結(jié)

          這篇文章介紹了 OneFlow 的算子 AutoTest 框架,提供了一個深度學習優(yōu)雅的做算子對齊的方法,使得開發(fā)者和用戶可以像寫 PyTorch 那樣方便寫測試程序。AutoTest 框架的靈活性和易用性都比較強,歡迎大家學習或者使用。


          相關(guān)鏈接

          • https://github.com/Oneflow-Inc/oneflow
          • https://github.com/pytorch/pytorch


          如果覺得有用,就請分享到朋友圈吧!

          △點擊卡片關(guān)注極市平臺,獲取最新CV干貨

          公眾號后臺回復“transformer”獲取最新Transformer綜述論文下載~


          極市干貨
          課程/比賽:珠港澳人工智能算法大賽保姆級零基礎人工智能教程
          算法trick目標檢測比賽中的tricks集錦從39個kaggle競賽中總結(jié)出來的圖像分割的Tips和Tricks
          技術(shù)綜述:一文弄懂各種loss function工業(yè)圖像異常檢測最新研究總結(jié)(2019-2020)


          #?CV技術(shù)社群邀請函?#

          △長按添加極市小助手
          添加極市小助手微信(ID : cvmart4)

          備注:姓名-學校/公司-研究方向-城市(如:小極-北大-目標檢測-深圳)


          即可申請加入極市目標檢測/圖像分割/工業(yè)檢測/人臉/醫(yī)學影像/3D/SLAM/自動駕駛/超分辨率/姿態(tài)估計/ReID/GAN/圖像增強/OCR/視頻理解等技術(shù)交流群


          每月大咖直播分享、真實項目需求對接、求職內(nèi)推、算法競賽、干貨資訊匯總、與?10000+來自港科大、北大、清華、中科院、CMU、騰訊、百度等名校名企視覺開發(fā)者互動交流~


          覺得有用麻煩給個在看啦~??
          瀏覽 85
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄片在现免费观看 | 亚洲色图第八页 | 激情小说综合网 | 成年人视频大全 | 狠狠操狠狠爱五月婷婷 |