深度解析:HyNet 與 SOSNet 半精度描述符的 CPU/GPU 穩(wěn)定性改造)
計(jì)算機(jī)視覺(jué)人工智能深度學(xué)習(xí)圖像處理【免費(fèi)下載鏈接】kornia Geometric Computer Vision Library for Spatial AI項(xiàng)目地址https://gitcode.com/gh_mirrors/ko/kornia點(diǎn)擊查看免費(fèi)下載本文基于 Kornia 倉(cāng)庫(kù) changelog.d/migration-085.fixed.md 中的修復(fù)記錄深入解析HyNet與SOSNet兩個(gè)局部圖像描述符網(wǎng)絡(luò)在半精度f(wàn)loat16/bfloat16下無(wú)法在 CPU 運(yùn)行、且退化 patch 輸出全 NaN 的兩大缺陷一條缺陷源自avg_pool3d在 CPU 上沒(méi)有半精度 kernel另一條源自1e-10的eps在float16中不可表示而沖刷為 0。讀完本文你將理解該修復(fù)的設(shè)計(jì)決策、實(shí)現(xiàn)細(xì)節(jié)sosnet.py、hynet.py、對(duì)應(yīng)的測(cè)試保障以及該變更對(duì)半精度描述符數(shù)值行為的影響。修復(fù)背景一條遷移記錄里的兩個(gè)缺陷在 Kornia 的 changelog 驅(qū)動(dòng)開發(fā)流程中changelog.d/目錄下的每條migration-*.fixed.md記錄了一個(gè)已合入的缺陷修復(fù)。本次記錄對(duì)應(yīng) issue #4224PR #4225明確說(shuō)明HyNet和SOSNet現(xiàn)在可以在半精度下運(yùn)行——無(wú)論是 CPU 還是 GPU——并且對(duì)退化 patch 不再返回 NaN。修復(fù)前兩個(gè)缺陷坐在同一行代碼上two defects sat on the same line即位于兩個(gè)網(wǎng)絡(luò)forward末尾的LocalResponseNorm歸一化步驟CPU 半精度 kernel 缺失兩個(gè)網(wǎng)絡(luò)的最終LocalResponseNorm收到的是一個(gè) 4 維(B, C, 1, 1)張量而torch.nn.functional.local_response_norm對(duì) 4 維輸入會(huì)路由到avg_pool3d該 kernel 在 CPU 上沒(méi)有float16/bfloat16實(shí)現(xiàn)因此無(wú)論輸入是什么CPU 上都會(huì)直接拋出NotImplementedError: avg_pool3d_out_frame not implemented for HalfBFloat16拼寫同樣不存在該實(shí)現(xiàn)float16下eps沖刷為 0 導(dǎo)致 0/0在float16且 kernel 存在的設(shè)備如 MPS、CUDA上只要網(wǎng)絡(luò)把某個(gè) patch 映射到恰好為零的激活描述符就會(huì)全部變成 NaN。缺陷一local_response_norm的avg_pool3d路由與 CPU kernel 缺失LocalResponseNorm在描述符網(wǎng)絡(luò)中的角色先看兩個(gè)網(wǎng)絡(luò)的歸一化步驟在源碼中的位置SOSNet.forward特征提取層輸出descr后self.desc_norm nn.Sequential(nn.LocalResponseNorm(256, alpha256.0, beta0.5, k0.0))即對(duì)每個(gè)描述符向量執(zhí)行 L2 歸一化。HyNet.forwardself.desc_norm nn.LocalResponseNorm(2 * self.dim_desc, 2.0 * self.dim_desc, 0.5, 0.0)同樣在末尾做歸一化。這兩個(gè)desc_norm的輸入形狀都是(B, C, 1, 1)——特征圖已經(jīng)過(guò)kernel_size8的全局卷積SOSNet 的最后一個(gè)Conv2d(128, 128, kernel_size8)、HyNet 的Conv2d(128, dim_desc, kernel_size8)壓縮為單像素。而 PyTorch 的F.local_response_norm對(duì) 4 維輸入會(huì)通過(guò)avg_pool3d計(jì)算鄰域平方和這正是缺陷所在avg_pool3d的 CPU kernel 對(duì)半精度數(shù)據(jù)類型沒(méi)有注冊(cè)實(shí)現(xiàn)于是 CPU 上所有半精度調(diào)用都會(huì)撞上NotImplementedError。這也是為什么修復(fù)記錄強(qiáng)調(diào)該歸一化步驟與kornia.feature.siftdesc中給eps守衛(wèi)的保護(hù)不同siftdesc的守衛(wèi)只針對(duì)float16做提升原因見(jiàn)缺陷二而此處由于 CPU kernel 缺口同時(shí)覆蓋float16與bfloat16兩種 dtype必須做一個(gè)更寬的提升。缺陷二1e-10的eps在float16中不可表示常數(shù) patch 如何一路走到恰好為零eps的作用是保證歸一化的除法在描述符向量恰好為零時(shí)依然有定義SOSNet.forward(input, eps: float 1e-10)sosnet.py 中eps在descr eps中加入desc_norm內(nèi)部用該值做除法分母的守衛(wèi)。HyNet.forward(x)hynet.py 中eps_l2_norm: float 1e-10同樣在desc_norm的除法中起守衛(wèi)作用。關(guān)鍵事實(shí)是這兩個(gè)網(wǎng)絡(luò)的任意常數(shù) patch 都會(huì)精確走到零激活SOSNet 中每個(gè)Conv2d都是biasFalse每個(gè)BatchNorm2d都是affineFalse見(jiàn) sosnet.py 的nn.Sequential結(jié)構(gòu)因此一個(gè)常數(shù) patch 經(jīng)過(guò)零偏置卷積與無(wú)仿射參數(shù)的 BatchNorm 后到達(dá)歸一化層的值就是精確的 0。HyNet 只有在is_biasFalse配置下才會(huì)走到零hynet.py因?yàn)?HyNet 使用FilterResponseNorm2dTLU閾值線性單元組合TLU 的偏置開啟時(shí)歸一化前的張量永遠(yuǎn)不會(huì)精確落在 0 上。float16與bfloat16的本質(zhì)差異修復(fù)記錄給出了精確的數(shù)值解釋float16的指數(shù)范圍有限1e-10不可表示會(huì)被沖刷flush為0.0于是除法變成0/0產(chǎn)生 NaN。這是 MPS/CUDA 等 kernel 存在的設(shè)備上出現(xiàn)全 NaN 的根因。bfloat16保留了float32的指數(shù)范圍只是尾數(shù)精度更低因此1e-10在bfloat16中依然可表示守衛(wèi)在這里是存活的bfloat16只受 CPU kernel 缺失問(wèn)題影響。這也解釋了為何siftdesc的_rootsiftsiftdesc.py只對(duì)float16做提升SIFT 的守衛(wèi)面臨的是同樣的eps不可表示問(wèn)題但不存在 CPUavg_pool3d的 dtype 覆蓋差異——bfloat16那里沒(méi)有1e-10沖刷問(wèn)題所以只需要保護(hù)float16。修復(fù)方案歸一化步驟提升到float32再回寫修復(fù)的核心決策是無(wú)論輸入是float16還是bfloat16都只把最后那一步歸一化提升到float32計(jì)算再轉(zhuǎn)回原 dtype。兩個(gè)網(wǎng)絡(luò)的具體實(shí)現(xiàn)# SOSNet.forward 的歸一化分支sosnet.py if descr.dtype in (torch.float16, torch.bfloat16): descr self.desc_norm(descr.float() eps).to(descr.dtype) else: descr self.desc_norm(descr eps)# HyNet.forward 的歸一化分支hynet.py if x.dtype in (torch.float16, torch.bfloat16): x self.desc_norm(x.float() self.eps_l2_norm).to(x.dtype) else: x self.desc_norm(x self.eps_l2_norm)設(shè)計(jì)要點(diǎn)提升范圍最小化只提升歸一化這一步而不是整個(gè)網(wǎng)絡(luò)前向。網(wǎng)絡(luò)的卷積、歸一化主體仍以半精度執(zhí)行從而保留半精度的性能收益。覆蓋兩種 half dtypefloat16解決eps沖刷問(wèn)題bfloat16解決 CPU kernel 缺失問(wèn)題一次dtype in (torch.float16, torch.bfloat16)的分支同時(shí)覆蓋兩者——比siftdesc的float16單獨(dú)提升更寬原因正如前文所述CPU kernel 缺口對(duì)兩種 dtype 都成立。float32/float64完全不動(dòng)這兩種精度走原始表達(dá)式在 CPU、CUDA、MPS 上逐位bitwise不變即常規(guī)精度用戶不會(huì)觀察到任何行為變化。注意HyNet的提升分支中x.float() self.eps_l2_norm的加法發(fā)生在float32域內(nèi)確保1e-10以float32語(yǔ)義參與運(yùn)算而非先被截?cái)?。?shù)值行為變化半精度輸出會(huì)變但變得更準(zhǔn)修復(fù)并非零成本半精度描述符的輸出會(huì)改變因此該變更是一次有意的數(shù)值語(yǔ)義調(diào)整而不是純粹的 bug 修復(fù)。changelog 給出了可驗(yàn)證的量化結(jié)論輸出變化幅度為0.25–2.25 epseps即 dtype 的單位舍入誤差量級(jí)。變化最大的方向是朝向float64參考值移動(dòng)而不是僅僅遠(yuǎn)離 NaNSOSNet在float16下對(duì)float64同權(quán)重模型的最大絕對(duì)誤差從2.15e-03降到2.28e-04即誤差下降約一個(gè)數(shù)量級(jí)。未改進(jìn)的配置也保持在原值一個(gè)eps之內(nèi)即不會(huì)引入明顯退化。由此帶來(lái)的兼容性提示半精度描述符在本版本前后不再逐位可比。如果你的應(yīng)用依賴跨版本的半精度描述符做緩存、數(shù)據(jù)庫(kù)索引或位級(jí)比對(duì)升級(jí)后需要重新生成。測(cè)試保障退化 patch 與編譯路徑的雙重守護(hù)該修復(fù)在測(cè)試層有多重驗(yàn)證test_sosnet.py、test_hynet.py1. 退化 patch 有限性測(cè)試直接回歸守護(hù)# test_sosnet.py: test_degenerate_patch_gives_finite_descriptors pytest.mark.parametrize(patch_value, [0.0, 0.5]) def test_degenerate_patch_gives_finite_descriptors(self, device, dtype, patch_value): patches torch.full((2, 1, 32, 32), patch_value, devicedevice, dtypedtype) sosnet SOSNet().to(device, dtype).eval() descriptors sosnet(patches) assert torch.isfinite(descriptors).all(), ...對(duì)應(yīng)地test_hynet.py 的test_degenerate_patch_gives_finite_descriptors額外參數(shù)化is_bias為True/False覆蓋 HyNet 走到精確零is_biasFalse與不走零is_biasTrue兩種路徑。兩個(gè)測(cè)試都使用torch.full構(gòu)造常數(shù) patch并在全部設(shè)備 × dtype 組合下斷言輸出無(wú) NaN/Inf。2. 編譯路徑守護(hù)兩個(gè)測(cè)試類都包含test_dynamo、test_dynamo_fullgraphtorch.compile(..., fullgraphTrue)與test_jit。測(cè)試注釋明確指出歸一化的 dtype 分支讀取的是靜態(tài)元數(shù)據(jù)torch._dynamo在 trace 時(shí)即可解析該分支因此編譯后的結(jié)果必須與 eager 一致——包括被提升的半精度路徑若分支被錯(cuò)誤丟棄會(huì)重新返回 NaN。同時(shí)docs/source/_data/export_support.json中記錄了feature.HyNet與feature.SOSNet為可捕獲capturable模型本次修復(fù)不能引入 graph break。使用與驗(yàn)證如何在自己環(huán)境中復(fù)現(xiàn)復(fù)現(xiàn)修復(fù)前后差異的最小腳本基于倉(cāng)庫(kù)中公開的 APIkornia/feature/__init__.py中SOSNet、HyNet、FilterResponseNorm2d均已導(dǎo)出import torch from kornia.feature import SOSNet, HyNet for dtype in (torch.float16, torch.bfloat16, torch.float32, torch.float64): patches torch.full((2, 1, 32, 32), 0.5, dtypedtype) sosnet SOSNet().to(dtype).eval() descs sosnet(patches) print(fSOSNet {dtype}: finite{torch.isfinite(descs).all().item()} shape{tuple(descs.shape)}) hynet HyNet(is_biasFalse).to(dtype).eval() descs hynet(patches) print(fHyNet {dtype}: finite{torch.isfinite(descs).all().item()} shape{tuple(descs.shape)})在修復(fù)前float16的SOSNet/HyNet輸出會(huì)全部為 NaNCPU 上的兩種 half dtype 都會(huì)直接拋NotImplementedError修復(fù)后所有組合都應(yīng)輸出finiteTrue的(B, 128)描述符。更嚴(yán)謹(jǐn)?shù)膶?duì)照可參考倉(cāng)庫(kù)測(cè)試在float64下用同一套隨機(jī)初始化的模型權(quán)重跑一次作為參考test_gradcheck中即使用torch.float64輸入再比較半精度輸出的最大絕對(duì)誤差。總結(jié)本次修復(fù)#4224 / #4225揭示了深度學(xué)習(xí)中一個(gè)典型的隱性精度陷阱組合框架 kernel 覆蓋不完整CPUavg_pool3d無(wú) half 實(shí)現(xiàn) 浮點(diǎn)守衛(wèi)值不可表示1e-10在float16中沖刷為 0而兩個(gè)問(wèn)題恰好落在同一行歸一化代碼上。修復(fù)以僅提升最后一步歸一化到float32的最小侵入方案同時(shí)解決兩者并通過(guò)常數(shù) patch 有限性測(cè)試、dynamo/JIT 編譯一致性測(cè)試建立長(zhǎng)期守護(hù)。對(duì)于使用半精度推理加速的視覺(jué)應(yīng)用這意味著HyNet/SOSNet描述符管線現(xiàn)在可以安全地在 CPU 與 GPU 上以float16/bfloat16運(yùn)行代價(jià)是半精度輸出相對(duì)舊版本不再逐位可比——遷移時(shí)建議按本文的量化說(shuō)明重新評(píng)估數(shù)值一致性需求。贊分享計(jì)算機(jī)視覺(jué)人工智能深度學(xué)習(xí)圖像處理【免費(fèi)下載鏈接】kornia Geometric Computer Vision Library for Spatial AI項(xiàng)目地址https://gitcode.com/gh_mirrors/ko/kornia點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Kornia 半精度修復(fù)HyNet 與 SOSNet 在 float16/bfloat16 下的 NaN 與 CPU 內(nèi)核缺口解析Kornia 半精度修復(fù)HyNet 與 SOSNet 在 float16/bfloat16 下的 NaN 與 CPU 內(nèi)核缺口解析 本篇文章聚焦 Kornia計(jì)算機(jī)視覺(jué)深度學(xué)習(xí)人工智能圖像處理kornia 特征描述子在 float16 下的 NaN 修復(fù)SIFTDescriptor、DenseSIFTDescriptor、HardNet 半精度數(shù)值穩(wěn)定性詳解kornia 特征描述子在 float16 下的 NaN 修復(fù)SIFTDescriptor、DenseSIFTDescriptor、HardNet 半精度數(shù)值計(jì)算機(jī)視覺(jué)人工智能深度學(xué)習(xí)圖像處理Kornia 半精度修復(fù)深度解析讓 guided_blur / GuidedBlur 在 float16 與 bfloat16 下穩(wěn)定運(yùn)行Kornia 半精度修復(fù)深度解析讓 guided_blur / GuidedBlur 在 float16 與 bfloat16 下穩(wěn)定運(yùn)行 導(dǎo)讀 本篇技術(shù)文章計(jì)算機(jī)視覺(jué)深度學(xué)習(xí)人工智能圖像處理創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考