融合通路設(shè)計與調(diào)優(yōu))
去年下半年我接手了一個內(nèi)部系統(tǒng)的改造需求要把圖像、文本和一小段語音信號塞進同一個模型里做聯(lián)合判斷。數(shù)據(jù)量不小標注質(zhì)量也還行單模態(tài)的指標都挺漂亮可一旦拼到一起整體效果反而比只用文本還差一截。排查了兩周最后發(fā)現(xiàn)問題不在數(shù)據(jù)也不在模型大小而在“通路”上——模態(tài)信息從各自的編碼器出來之后走哪條路、在哪一層匯合、匯合之后還剩多少信息這條路徑的設(shè)計直接決定了模型能不能真正“看懂”多模態(tài)輸入。這篇要聊的Multimodal Pathway說的就是這條路徑本身從各模態(tài)編碼、投影對齊、交互融合到最終任務(wù)頭的整條信息通道設(shè)計。它不是某個具體模型的名字而是一類架構(gòu)決策的統(tǒng)稱。你在做圖像問答、跨模態(tài)檢索、視頻內(nèi)容理解、工業(yè)質(zhì)檢里的圖文核對甚至多傳感器融合都會撞上它。適合兩類人看一類是剛要把第二個模態(tài)接進現(xiàn)有模型的工程師另一類是發(fā)現(xiàn)“融合了但沒變好”的算法同學。下面我按自己踩坑的順序把設(shè)計思路、參數(shù)取舍、代碼骨架和排查經(jīng)驗完整攤開講。1. 先把“通路”這兩個字拆開看1.1 一條通路要經(jīng)過的四個關(guān)口很多人一上來就糾結(jié)“用什么融合模塊”其實通路是分段的每一段的失敗模式完全不同混在一起討論永遠吵不出結(jié)論。我習慣把它拆成四個關(guān)口每個關(guān)口有自己的輸入輸出和驗收標準。第一個關(guān)口是模態(tài)內(nèi)編碼。圖像走視覺編碼器文本走詞嵌入加編碼器語音走聲學前端。這一段的目標是把原始信號變成一串定長的向量序列。注意這里的“定長”不是指數(shù)值定長而是指 token 數(shù)量和維度要穩(wěn)定否則后面拼接時形狀對不上。視覺側(cè)通常是切 patch224×224 的圖按 14×14 切得到 16×16256 個 token文本側(cè)按分詞后長度算一般在幾十到幾百之間浮動。第二個關(guān)口是投影對齊。不同編碼器輸出的維度往往不一樣視覺可能是 1024 維文本可能是 768 維投影層負責把它們映射到同一個寬度。這一步看著簡單其實是整個通路里最容易偷懶也最容易出問題的地方后面單獨展開。第三個關(guān)口是交互融合。這是模態(tài)真正“見面”的地方??梢允呛唵纹唇雍笠黄疬^ Transformer也可以是用交叉注意力讓文本去查圖像還可以是到最后一層才做分數(shù)加權(quán)。位置選錯后面怎么調(diào)都補不回來。第四個關(guān)口是任務(wù)頭輸出。分類頭、生成頭、檢索用的對比頭都算。這一段的問題通常是“信息壓縮過頭”尤其是做生成任務(wù)時如果融合后的序列長度被壓得太狠解碼端會明顯感覺到“細節(jié)沒了”。把這四段分開看你會發(fā)現(xiàn)很多“融合效果不好”的案例其實是第一段就有問題——比如視覺 token 里混進了大量背景 patch投影層再好也救不回來。1.2 三種融合位置的取舍別憑感覺選融合位置的選擇是通路設(shè)計里第一個大決策。我做過一個對比表是我們內(nèi)部實測之后整理的參數(shù)量和顯存是按單卡 80G、混合精度估算的融合方式交互發(fā)生位置顯存增幅訓練難度適合的場景早期融合編碼后立即拼接低約 1.2 倍低模態(tài)對齊良好、序列都短中期融合中間若干層交叉注意力高約 2.5 到 3 倍中高需要細粒度對應(yīng)如圖文問答晚期融合各模態(tài)獨立出分數(shù)再合并極低低模態(tài)獨立性強、要求可解釋混合融合主路中期 旁路晚期中高高線上要降級兜底的生產(chǎn)系統(tǒng)早期融合最省事把視覺 token 和文本 token 拼成一條長序列直接喂給 Transformer。問題在于圖像 token 動輒兩三百個文本才幾十個拼完之后序列被視覺主導模型容易“看著圖忘了話”。我們最早那版就是早期融合圖文匹配任務(wù)還行一到需要精確讀文字的場合就崩因為文本 token 在注意力里被稀釋了。中期融合是我現(xiàn)在的主力方案。核心是保留兩條獨立的主干在中間某幾層插入交叉注意力讓文本 token 作為 query 去查視覺 token 的 key/value。好處是文本序列長度不變不會被稀釋代價是顯存漲得厲害交叉注意力的矩陣規(guī)模是文本長度乘以視覺長度雖然比全序列自注意力省但層數(shù)一多也頂不住。晚期融合最穩(wěn)各模態(tài)各自算分再加權(quán)線上降級特別方便——視覺服務(wù)掛了就只走文本。但它學不到細粒度對應(yīng)關(guān)系做“圖里第三行文字寫了什么”這種任務(wù)基本沒戲。我們現(xiàn)在線上跑的是混合結(jié)構(gòu)主路用中期融合保證效果旁路保留一個晚期融合的輕量分支主路超時或異常時切旁路。這個設(shè)計不是一開始就想好的是被線上事故逼出來的。2. 核心設(shè)計里最容易被低估的三件事2.1 投影層不是“降維那一下”它決定信息損失上限投影層最常見的寫法就是一個線性層把視覺特征從 1024 維映射到 4096 維。參數(shù)量算下來是 1024×40964096大概 420 萬占整體模型的比例很小所以很多人隨手就定了。但我實測下來這一層的設(shè)計對最終指標的影響遠超它的參數(shù)量占比。問題出在 token 數(shù)量上。視覺編碼器輸出 256 個 token如果全部保留并投到 4096 維光視覺部分就要占 256×4096 的激活空間再乘以 batch 和層數(shù)顯存很快就見底。于是有兩種常見處理一種是直接線性投影全保留另一種是先用查詢式重采樣把 256 個 token 壓到 32 或 64 個再投影。我兩種都試過結(jié)論是看任務(wù)性質(zhì)分。如果任務(wù)需要定位細節(jié)比如“圖中左上角的儀表讀數(shù)是多少”壓縮 token 會直接丟掉空間分辨率指標掉得很明顯如果任務(wù)是整體語義判斷比如“這張圖是不是戶外場景”壓縮到 64 個 token 幾乎無損還能省下一大截顯存。重采樣器的寫法不復雜核心是一組可學習的查詢向量去對視覺 token 做注意力import torch import torch.nn as nn class QueryResampler(nn.Module): 把變長的視覺 token 壓縮成固定數(shù)量的語義 token def __init__(self, vis_dim, out_dim, num_query64, num_heads8, depth2): super().__init__() self.query nn.Parameter(torch.randn(1, num_query, out_dim) * 0.02) self.kv_proj nn.Linear(vis_dim, out_dim) self.layers nn.ModuleList([ nn.ModuleDict({ q_norm: nn.LayerNorm(out_dim), kv_norm: nn.LayerNorm(out_dim), attn: nn.MultiheadAttention(out_dim, num_heads, batch_firstTrue), ffn: nn.Sequential( nn.Linear(out_dim, out_dim * 4), nn.GELU(), nn.Linear(out_dim * 4, out_dim), ), ffn_norm: nn.LayerNorm(out_dim), }) for _ in range(depth) ]) def forward(self, vis_tokens, vis_maskNone): # vis_tokens: [B, N_vis, vis_dim] kv self.kv_proj(vis_tokens) # [B, N_vis, out_dim] q self.query.expand(kv.size(0), -1, -1) # [B, num_query, out_dim] key_padding None if vis_mask is None else ~vis_mask for layer in self.layers: q2 layer[q_norm](q) kv2 layer[kv_norm](kv) attn_out, _ layer[attn]( q2, kv2, kv2, key_padding_maskkey_padding, need_weightsFalse, ) q q attn_out q q layer[ffn](layer[ffn_norm](q)) return q # [B, num_query, out_dim]這段代碼有兩個細節(jié)值得說。一是self.query的初始化標準差我用 0.02試過 0.5 那種大初始化訓練前期梯度爆炸過一次loss 直接變 NaN。二是key_padding_mask的處理視覺側(cè)如果有 padding 的 token比如不同分辨率圖片拼成一個 batch不做 mask 的話重采樣會去關(guān)注無意義的填充位訓出來的 token 語義是臟的。這個坑我在第二批數(shù)據(jù)上才踩到因為第一批圖片全是統(tǒng)一尺寸根本沒有 padding。2.2 路由與門控讓信息自己選擇走哪條道當模態(tài)數(shù)量超過兩個或者同一模態(tài)內(nèi)部有不同來源比如攝像頭圖像和雷達圖像簡單拼接就不夠用了。這時候可以做模態(tài)級路由給每個 token 算一個權(quán)重決定它更多地流向哪個專家分支。思路參考混合專家的做法但不要照搬到模態(tài)上。我最初的實現(xiàn)是給每個模態(tài)一個標量門控結(jié)果訓完之后門控全塌縮到某一個模態(tài)上等于白做。后來改成 token 級路由才正常class ModalRouter(nn.Module): token 級門控為每個 token 分配專家權(quán)重 def __init__(self, dim, num_experts4, top_k2, temperature1.0): super().__init__() self.gate nn.Linear(dim, num_experts, biasFalse) self.num_experts num_experts self.top_k top_k self.temperature temperature def forward(self, x, modality_ids): # x: [B, L, dim]; modality_ids: [B, L] 每個 token 屬于哪個模態(tài) logits self.gate(x) / self.temperature # [B, L, E] # 重要路由權(quán)重按模態(tài)做歸一化防止某個模態(tài) token 多就占優(yōu) mask torch.zeros_like(logits) for m in modality_ids.unique(): sel (modality_ids m) mask[sel] 1.0 logits logits.masked_fill(mask 0, float(-inf)) weights torch.softmax(logits, dim-1) topv, topi weights.topk(self.top_k, dim-1) topv topv / topv.sum(dim-1, keepdimTrue) return topv, topimodality_ids這個參數(shù)看著多余其實是我踩坑之后加的。第一版沒有它路由權(quán)重的歸一化是在整個 batch 的所有 token 上做的結(jié)果視覺 token 數(shù)量是文本的五六倍softmax 之后視覺專家被分到的權(quán)重天然就高路由形同虛設(shè)。加上模態(tài)掩碼之后每個模態(tài)內(nèi)部獨立競爭負載才均衡。另外必須配一個負載均衡損失否則專家利用率會嚴重傾斜。我用的形式是各專家被選中概率的平方和乘以專家數(shù)$$ L_{bal} E \cdot \sum_{i1}^{E} f_i^2 $$其中f_i是第 i 個專家在當前 batch 里被選中的頻率。這個損失權(quán)重我設(shè)在 0.01 量級太大了會影響主任務(wù)收斂太小了沒效果。調(diào)這個系數(shù)的那幾天日志里專家命中率的曲線一會兒接近均勻一會兒全打到一個專家上來回試了七八個值才穩(wěn)定下來。2.3 訓練階段怎么切分決定通路能不能學起來通路結(jié)構(gòu)搭好之后訓練策略比結(jié)構(gòu)本身更影響結(jié)果。我的做法是分三段每段的凍結(jié)范圍不同階段編碼器投影層融合層任務(wù)頭典型步數(shù)對齊預熱凍結(jié)訓練凍結(jié)訓練2k 到 5k通路放開凍結(jié)訓練訓練訓練10k 到 20k聯(lián)合微調(diào)部分解凍訓練訓練訓練5k 到 10k第一階段只訓投影層和任務(wù)頭目的是讓兩個模態(tài)的表示先落到一個大致兼容的空間里。這一步學習率可以給大一點我用 1e-3因為投影層是隨機初始化的太小的學習率根本挪不動。如果跳過這一步直接聯(lián)合訓練我遇到過投影層梯度被編碼器傳來的大梯度沖垮的情況loss 前兩千步劇烈震蕩。第二階段放開融合層學習率降到 1e-4編碼器繼續(xù)凍結(jié)。這一步是真正學“跨模態(tài)對應(yīng)關(guān)系”的階段耗時最長。有個細節(jié)融合層里的交叉注意力key/value 側(cè)的權(quán)重可以給更小的學習率因為它承接的是已經(jīng)訓好的視覺表示動太快會破壞原有語義。第三階段才解凍編碼器的高層學習率壓到 1e-5 到 2e-5。這一步收益不大但風險不小我的經(jīng)驗是只在數(shù)據(jù)量足夠十萬級以上的時候做小數(shù)據(jù)集上解凍編碼器幾乎必然過擬合驗證集指標第二 epoch 就開始往下走。3. 一條完整通路的具體實現(xiàn)與參數(shù)核算3.1 先把張量形狀和序列布局約定死寫代碼之前我強制自己做了一件事把所有張量的形狀、含義、mask 約定寫在一張表里貼在旁邊。這個習慣是被一次 debug 折磨出來的——某次訓練一切正常但推理時結(jié)果全亂查了一晚上發(fā)現(xiàn)是訓練時視覺 token 在前文本在后推理腳本里順序反了因為模型沒報錯形狀完全兼容。我們的約定是這樣的張量形狀含義vis_tokens[B, Nv, Dv]視覺編碼器輸出Nv 一般 256 或 576vis_mask[B, Nv]True 表示有效 tokentxt_ids[B, Lt]文本 token idLt 為 padding 后長度txt_tokens[B, Lt, Dt]文本編碼器輸出modality_ids[B, NvLt]拼接后每個位置屬于哪個模態(tài)0 視覺 1 文本fused[B, NvLt, D]融合后序列D 為統(tǒng)一寬度序列拼接順序我固定成“視覺在前、文本在后”并且在融合層之后加一個可學習的位置偏置讓模型知道兩段的分界在哪。這個偏置聽起來可有可無但實測去掉之后做需要精確定位的任務(wù)會掉一到兩個點因為純位置編碼區(qū)分不出“這里是模態(tài)邊界”這件事。3.2 融合塊的代碼骨架融合塊我寫成了一個獨立模塊方便按需插入到主干的不同層。核心是雙向的交叉注意力加上殘差class FusionBlock(nn.Module): def __init__(self, dim, num_heads16, dropout0.1, use_gateTrue): super().__init__() self.txt2vis nn.MultiheadAttention(dim, num_heads, dropoutdropout, batch_firstTrue) self.vis2txt nn.MultiheadAttention(dim, num_heads, dropoutdropout, batch_firstTrue) self.norm1 nn.LayerNorm(dim) self.norm2 nn.LayerNorm(dim) self.ffn nn.Sequential( nn.Linear(dim, dim * 4), nn.GELU(), nn.Dropout(dropout), nn.Linear(dim * 4, dim), ) self.norm3 nn.LayerNorm(dim) self.use_gate use_gate if use_gate: # 門控初值偏小讓訓練初期以恒等映射為主避免破壞預訓練表示 self.gate nn.Parameter(torch.full((dim,), -2.0)) def forward(self, vis, txt, vis_maskNone, txt_maskNone): kv_pad None if vis_mask is None else ~vis_mask q_pad None if txt_mask is None else ~txt_mask t2v, _ self.txt2vis(self.norm1(txt), vis, vis, key_padding_maskkv_pad, need_weightsFalse) v2t, _ self.vis2txt(self.norm2(vis), txt, txt, key_padding_maskq_pad, need_weightsFalse) if self.use_gate: g torch.sigmoid(self.gate) txt txt g * t2v vis vis g * v2t else: txt txt t2v vis vis v2t txt txt self.ffn(self.norm3(txt)) return vis, txt門控的初值我設(shè)成 -2.0sigmoid 之后大約 0.12意思是訓練剛開始時融合分支只貢獻一成多的信息主體還是各模態(tài)自己的表示。這個技巧是從殘差連接的思想里變通過來的融合層是隨機初始化的如果一開始就全量注入會把編碼器辛苦學到的表示沖散。實測帶門控的版本第一階段 loss 下降明顯更平滑。3.3 顯存和吞吐的核算別等 OOM 才想?yún)?shù)量的賬好算激活的賬不好算但必須算。以 batch8、視覺 token 256、文本 token 512、融合寬度 4096、主干 32 層為例我按混合精度粗算一遍單樣本單層的 token 激活大約是 768×4096×2 字節(jié)約 6.3MB。32 層乘 8 個樣本光這一項就是 1.6GB 左右還不算 FFN 里的 4 倍擴展實際要乘 4 到 5就是 6 到 8GB。真正吃顯存的是注意力矩陣。768 個 token 的自注意力矩陣規(guī)模 768×76816 個頭混合精度下是 768×768×16×2 字節(jié)約 18.9MB 每樣本每層。乘 32 層乘 8 個樣本差不多 4.8GB如果頭數(shù)翻倍或者序列變長這塊會迅速失控。我實際跑下來單卡 80G 上這個配置能跑 batch 6 左右。想上到 batch 16 就得開梯度檢查點代價是訓練速度慢四成左右。這個取舍我的做法是預訓練階段不開檢查點用大 batch 保證梯度穩(wěn)定微調(diào)階段開檢查點用更大 batch 換更少的步數(shù)因為微調(diào)階段本來就步數(shù)少。還有個容易被忽略的點交叉注意力的顯存要看兩邊長度的乘積。文本 512、視覺 256交叉注意力的矩陣是 256×512比 768×768 的自注意力小三倍多所以中期融合的顯存開銷其實主要來自主干自注意力不是融合本身。這個認知幫我在優(yōu)化時找對了方向——一開始我以為是融合層太重去砍融合層數(shù)結(jié)果指標掉了顯存沒省多少。3.4 訓練日志里必須盯的四個指標跑起來之后光看總 loss 是不夠的。我固定盯四個數(shù)第一個是各模態(tài) token 的梯度范數(shù)。如果文本側(cè)梯度范數(shù)是視覺側(cè)的十分之一說明通路嚴重偏向視覺后面調(diào)多久都沒用得回去查投影層的初始化尺度。第二個是專家或門控的命中分布。上面提過塌縮是最常見的問題我一開始每 200 步打一次直方圖現(xiàn)在設(shè)成自動告警某個專家占比超過 60% 就打印警告。第三個是驗證集上單模態(tài)和融合的差值。理想情況融合明顯更好如果融合反而不如最好的單模態(tài)說明通路上有信息被破壞了通常是融合層過早注入或者門控沒起作用。第四個是投影層輸出的數(shù)值范圍。這一項很少有人看但我吃過虧某次投影層輸出均值偏移越來越大訓到一半激活值到了幾千的量級后面接的 LayerNorm 直接被撐爆。加上輸出端的歸一化和權(quán)重衰減之后穩(wěn)住了。4. 實際跑下來遇到的坑和排查方法4.1 故障速查表這些問題我基本都親自遇到過按癥狀整理出來方便對照癥狀最可能的原因排查動作處理方式融合后指標不如單模態(tài)融合層注入過早破壞預訓練表示看融合層門控值分布門控初值調(diào)小或延后融合層插入位置loss 前期劇烈震蕩投影層隨機初始化 學習率過大打印投影層梯度范數(shù)先凍結(jié)主干單訓投影層學習率 1e-3 起訓練正常推理全亂序列拼接順序或 mask 約定不一致對齊訓練與推理的張量布局把約定寫成文檔并加斷言檢查門控全塌縮到一個分支缺少負載均衡或歸一化范圍錯誤打印路由權(quán)重直方圖加模態(tài)掩碼 均衡損失權(quán)重 0.01生成任務(wù)細節(jié)丟失視覺 token 壓縮過度對比壓縮前后定位類指標提高查詢數(shù)量或關(guān)鍵任務(wù)不壓縮顯存 OOM 但參數(shù)量不大注意力矩陣隨序列平方增長按公式手算矩陣顯存開梯度檢查點或降 token 數(shù)驗證指標第二 epoch 就下降解凍范圍過大數(shù)據(jù)量不夠看訓練/驗證 loss 分叉點回退到只訓融合層表格里第一行那個問題最隱蔽。我們有一版結(jié)構(gòu)融合層從第一層就開始插入結(jié)果模型在圖文匹配上還行一到需要語言理解的子任務(wù)就崩。后來把融合層挪到主干的后三分之一同樣的數(shù)據(jù)同樣的超參語言類子任務(wù)直接漲了四個點。原因不難理解淺層的表示還很“局部”視覺 token 和文本 token 在這個階段語義空間差得遠強行交互等于讓兩個說不同語言的人在還沒學會共同詞匯時就對話只會互相干擾。4.2 幾條我認為最值錢的避坑經(jīng)驗第一條永遠先跑一個“只拼不融”的基線。就是把兩個模態(tài)的表示直接拼接或者相加不做任何交叉注意力跑一遍看指標。這個基線看起來簡陋但它告訴你數(shù)據(jù)本身能撐到什么程度。我后來所有的融合改進都是跟這個基線比而不是跟單模態(tài)比。跟單模態(tài)比容易得出“融合有效”的錯覺其實可能只是多參數(shù)帶來的收益。第二條mask 一定要在最早的地方處理。我見過太多實現(xiàn)是在融合之后才處理 padding那時候無效 token 已經(jīng)參與了注意力計算污染已經(jīng)發(fā)生了。正確做法是編碼器輸出后立刻 mask重采樣和交叉注意力全程帶著 mask。這個改動沒有任何性能代價但對小 batch、變長輸入的場景影響巨大我們的短文本樣本指標因此漲了將近三個點。第三條別迷信“層數(shù)越多融合越充分”。我試過在 24 層主干里插 12 個融合塊結(jié)果訓練時間翻倍指標反而比插 4 個差。原因是每次融合都會引入一次信息重排插得太密各模態(tài)自己的表示還沒來得及在層內(nèi)充分演化就被反復混合最終學到的是一種“糊在一起”的表示區(qū)分度下降。我現(xiàn)在的經(jīng)驗值是融合塊占主干層數(shù)的 15% 到 25%插在中后段。第四條模態(tài)不平衡要在損失層面處理不要在數(shù)據(jù)層面硬湊。視覺 token 遠多于文本 token 是常態(tài)有人靠復制文本 token 來平衡這是錯的復制出來的 token 梯度會疊加等于給文本加了額外權(quán)重。正確做法是在注意力 mask 和路由歸一化里按模態(tài)處理讓每個模態(tài)內(nèi)部獨立競爭這樣數(shù)量差異不影響權(quán)重分配。第五條推理鏈路上留一條旁路。這個不是算法問題是工程問題。多模態(tài)通路意味著多個服務(wù)串聯(lián)任何一個環(huán)節(jié)超時都會拖垮整體。我們現(xiàn)在的主路走中期融合旁路保留一個只用文本的輕量分支主路響應(yīng)超過閾值就切旁路雖然精度降一些但可用性保住了。這個設(shè)計上線后P99 延遲從原來的接近兩秒降到了六百毫秒以內(nèi)。第六條把投影層的輸出范圍當成一個監(jiān)控指標。前面提過這個數(shù)不看會出事。我的做法是在訓練腳本里每 500 步統(tǒng)計一次投影層輸出的均值和 99 分位數(shù)均值偏移超過初始值的三倍就告警。這個小監(jiān)控幫我在一次結(jié)構(gòu)調(diào)整中提前發(fā)現(xiàn)了初始化尺度的問題省了至少兩天的排查時間。5. 關(guān)于通路設(shè)計的一點個人體會做這類結(jié)構(gòu)改造做久了我越來越覺得“多模態(tài)通路”這個說法其實挺準確的——它強調(diào)的是路徑不是模塊。很多人包括最早的我習慣去找一個“更好的融合模塊”仿佛換個注意力變體就能解決問題。但實際調(diào)試下來絕大部分收益來自路徑上的位置選擇和訓練階段的切分模塊本身用的是最標準的交叉注意力沒什么花哨的。還有一個感受是多模態(tài)的調(diào)試比單模態(tài)難難在問題不報錯。形狀對得上、loss 在下降、指標不算難看但就是沒達到預期你得靠一堆中間統(tǒng)計去定位到底是哪一段通路在漏信息。我現(xiàn)在養(yǎng)成了一個習慣每加一個新模態(tài)或者改一次通路結(jié)構(gòu)先把四個關(guān)口的中間輸出各存一份小樣本人眼看一遍。視覺 token 里是不是全是背景、投影后有沒有數(shù)值異常、融合后的表示和融合前差多少這些看一眼就有感覺比盲調(diào)超參高效得多。最后分享一個我最近在試的擴展方向把通路的“寬度”也做成可學習的?,F(xiàn)在的做法是每個模態(tài)固定走一條路徑我在想能不能讓模型自己決定某個樣本要不要走完整的融合鏈路簡單的樣本走淺通路復雜的走深通路。初步試驗里用路由權(quán)重做樣本難度估計把三成左右的簡單樣本分流到淺通路整體吞吐提升了四成精度只掉了零點幾。這條線還在跑等數(shù)據(jù)穩(wěn)定了再細聊。