關(guān)成本優(yōu)化實(shí)戰(zhàn):從模型路由到Token治理的省錢(qián)策略)
1. 為什么AI網(wǎng)關(guān)會(huì)成為成本中心1.1 直連模式下的隱性浪費(fèi)做AI工程化很多人踩過(guò)的第一個(gè)坑就是先不搞網(wǎng)關(guān)直接調(diào)接口。業(yè)務(wù)少的時(shí)候沒(méi)問(wèn)題但模型一旦鋪開(kāi)直連模式的賬根本算不過(guò)來(lái)。我見(jiàn)過(guò)一個(gè)團(tuán)隊(duì)內(nèi)部四個(gè)業(yè)務(wù)線分別接了大模型API每個(gè)業(yè)務(wù)線各自實(shí)現(xiàn)了一套鑒權(quán)、重試、日志邏輯。表面上每個(gè)模塊都正常實(shí)際上同一個(gè)prompt在不同業(yè)務(wù)線被重復(fù)請(qǐng)求了三次三份token賬單三份超時(shí)重試成本。這就是典型的隱性浪費(fèi)——不是某一次調(diào)用特別貴而是大量重復(fù)的調(diào)用在無(wú)人統(tǒng)一管理的情況下持續(xù)產(chǎn)生費(fèi)用。更麻煩的是直連模式下每次模型升級(jí)、換供應(yīng)商、調(diào)整配額都要改每個(gè)業(yè)務(wù)方的代碼。工程返工成本不直接體現(xiàn)在云賬單上但會(huì)占用人力最終同樣算進(jìn)項(xiàng)目總成本里。網(wǎng)關(guān)的價(jià)值就在這里把模型調(diào)用收口讓上游只關(guān)心業(yè)務(wù)請(qǐng)求讓下游的模型切換、降級(jí)、路由策略全部收斂到一層。從這個(gè)角度看Java網(wǎng)關(guān)不是多一層轉(zhuǎn)發(fā)開(kāi)銷(xiāo)而是成本治理的抓手。轉(zhuǎn)發(fā)的網(wǎng)絡(luò)開(kāi)銷(xiāo)通常在毫秒級(jí)但省下來(lái)的token和運(yùn)維成本往往是數(shù)量級(jí)的差距。前期投入一個(gè)網(wǎng)關(guān)后續(xù)每個(gè)業(yè)務(wù)方接入都能復(fù)用同一套成本策略這才是AI工程化里工程二字的含義。1.2 網(wǎng)關(guān)成本構(gòu)成的四張賬單在討論優(yōu)化之前先把成本拆清楚。我按自己的經(jīng)驗(yàn)把AI網(wǎng)關(guān)的支出分成四張賬單成本類(lèi)型主要來(lái)源常見(jiàn)占比可控性模型調(diào)用費(fèi)token消耗、API單價(jià)最高通常60%以上高路由/緩存/壓縮基礎(chǔ)設(shè)施費(fèi)網(wǎng)關(guān)實(shí)例、JVM內(nèi)存、帶寬約20%中實(shí)例數(shù)/JVM調(diào)優(yōu)工程返工費(fèi)聯(lián)調(diào)、排障、改接口約10%高統(tǒng)一抽象觀測(cè)與日志費(fèi)日志采集、指標(biāo)存儲(chǔ)約10%中采樣策略這個(gè)拆法不是絕對(duì)的但能幫你看清楚優(yōu)化的優(yōu)先級(jí)。模型調(diào)用費(fèi)永遠(yuǎn)是大頭所以后面所有策略里凡是能降低token消耗的手段都值得優(yōu)先做?;A(chǔ)設(shè)施費(fèi)要盯住實(shí)例數(shù)×單實(shí)例資源很多團(tuán)隊(duì)喜歡橫向擴(kuò)容結(jié)果機(jī)器開(kāi)了一堆CPU和內(nèi)存利用率卻不達(dá)標(biāo)。觀測(cè)日志費(fèi)容易被忽略網(wǎng)關(guān)流量一大全量請(qǐng)求日志的存儲(chǔ)成本非常驚人必須引入采樣。我自己的習(xí)慣是每個(gè)季度做一次成本歸因把賬單數(shù)據(jù)拉出來(lái)按模型、按業(yè)務(wù)方、按時(shí)間段三個(gè)維度透視。哪個(gè)業(yè)務(wù)方的prompt特別長(zhǎng)、哪個(gè)模型被無(wú)差別地用于所有請(qǐng)求、哪個(gè)時(shí)段的重試率異常高一眼就能看出來(lái)。沒(méi)有這一步后邊的優(yōu)化都是盲目的。2. 模型路由與降級(jí)策略把請(qǐng)求打到最合適的模型上2.1 分級(jí)模型池的設(shè)計(jì)成本優(yōu)化的第一刀永遠(yuǎn)砍在模型選擇上。同一個(gè)能力不同模型檔位的單價(jià)差距可以是幾倍到幾十倍。如果所有請(qǐng)求都走頂配模型那就是在給供應(yīng)商送錢(qián)。我在網(wǎng)關(guān)里做的是分級(jí)模型池把可用模型按能力和價(jià)格分成三檔——Premium、Standard、Lite。網(wǎng)關(guān)內(nèi)部維護(hù)一個(gè)路由配置類(lèi)似于這樣public class ModelRoute { private final String routeName; // 路由名例如 chat-common private final ModelTier primary; // 首選檔位 private final ListModelTier fallbacks; // 降級(jí)鏈 private final int maxInputTokens; // 該路由允許的最大輸入token private final int budgetCents; // 單次請(qǐng)求預(yù)算上限 }路由配置用YAML或配置中心下發(fā)業(yè)務(wù)方接入時(shí)只需要指定routeName具體打哪個(gè)模型由網(wǎng)關(guān)決定。比如chat-common默認(rèn)打到Standard檔當(dāng)Standard的延遲超閾值或配額不足時(shí)自動(dòng)降級(jí)到Lite檔。而需要深度推理的場(chǎng)景比如代碼審查、復(fù)雜數(shù)據(jù)分析才顯式指定Premium檔。這套設(shè)計(jì)的關(guān)鍵不是分檔本身而是讓業(yè)務(wù)方?jīng)]有繞過(guò)網(wǎng)關(guān)的理由。如果業(yè)務(wù)代碼里還能隨意指定model參數(shù)分檔就是擺設(shè)。所以網(wǎng)關(guān)側(cè)要做強(qiáng)制校驗(yàn)請(qǐng)求里的model字段要么為空走路由要么在允許名單內(nèi)否則直接拒絕。我在實(shí)際落地時(shí)還加了一個(gè)路由白名單審批流程新增模型檔位必須經(jīng)過(guò)成本評(píng)估避免某個(gè)貪大求全的開(kāi)發(fā)直接繞過(guò)路由寫(xiě)死頂配模型。2.2 動(dòng)態(tài)路由的四個(gè)決策因子靜態(tài)分檔只是第一步真正有價(jià)值的動(dòng)態(tài)路由要考慮四個(gè)因子意圖類(lèi)型通過(guò)請(qǐng)求的接口路徑或提示詞分類(lèi)判斷是簡(jiǎn)單問(wèn)答、文檔總結(jié)還是復(fù)雜推理上下文長(zhǎng)度輸入token越多單價(jià)越高長(zhǎng)上下文請(qǐng)求可以?xún)?yōu)先考慮能效比高的模型預(yù)算上限每個(gè)調(diào)用方有月度或日度預(yù)算網(wǎng)關(guān)在額度快用盡時(shí)自動(dòng)把流量切到低價(jià)檔延遲容忍度同步交互場(chǎng)景對(duì)延遲敏感優(yōu)先速度快的模型異步批處理場(chǎng)景可以接受較慢但便宜的模型。這四個(gè)因子在網(wǎng)關(guān)里合成一個(gè)路由決策結(jié)果。我推薦用一個(gè)輕量的打分函數(shù)而不是復(fù)雜的規(guī)則引擎否則規(guī)則多了以后自己都改不動(dòng)。每個(gè)因子按0到1打分乘以權(quán)重求和按得分排序選擇目標(biāo)模型檔位。權(quán)重可以放在配置中心里動(dòng)態(tài)調(diào)比如大促期間把預(yù)算上限的權(quán)重調(diào)高平時(shí)把意圖類(lèi)型的權(quán)重調(diào)高。這里要強(qiáng)調(diào)一點(diǎn)動(dòng)態(tài)路由一定要有可解釋性。每次路由決策都要記錄為什么選了這一檔否則一旦線上出問(wèn)題你根本不知道請(qǐng)求為什么被降級(jí)了。我在路由結(jié)果里會(huì)附帶決策原因編碼比如REASON_BUDGET_EXHAUSTED、REASON_MAX_INPUT_EXCEEDED排障時(shí)非常有價(jià)值。每個(gè)決策都會(huì)寫(xiě)進(jìn)結(jié)構(gòu)化日志配合鏈路追蹤可以完整還原一次請(qǐng)求的模型路徑。2.3 降級(jí)與重試的正確姿勢(shì)網(wǎng)關(guān)里的重試和降級(jí)是兩個(gè)經(jīng)常被混為一談的東西。重試是指同一個(gè)模型再試一次降級(jí)是換一個(gè)模型。兩者的成本含義完全不同重試可能讓費(fèi)用翻倍降級(jí)是讓單價(jià)降低。我的經(jīng)驗(yàn)是網(wǎng)絡(luò)錯(cuò)誤和限流錯(cuò)誤可以重試但需要退避而內(nèi)容錯(cuò)誤比如模型返回格式異常、拒絕回答不要重試直接走降級(jí)鏈。因?yàn)閮?nèi)容錯(cuò)誤重試大概率還是同樣結(jié)果白白多花一次token。這個(gè)判斷我最早也做反過(guò)線上有一陣重試率陡增排查了半天發(fā)現(xiàn)是某個(gè)模型對(duì)特定prompt穩(wěn)定返回空內(nèi)容網(wǎng)關(guān)還在傻乎乎地重試三遍費(fèi)用翻了幾倍。public CompletionResponse invokeWithFallback(GatewayRequest req) { ModelRoute route routeRegistry.resolve(req.getRouteName()); for (ModelTier tier : route.getFallbackChain()) { try { return modelClient.invoke(tier, req); } catch (RateLimitException e) { int retryAfter e.getRetryAfterSeconds(); if (tier.equals(route.getPrimary()) retryAfter 10) { sleepWithJitter(retryAfter); return modelClient.invoke(tier, req); } } catch (ModelReturnedErrorException e) { // 內(nèi)容錯(cuò)誤不重試直接換檔 continue; } } throw new GatewayFallbackExhaustedException(route.getRouteName()); }重試時(shí)要加抖動(dòng)jitter避免多個(gè)請(qǐng)求同時(shí)重試打爆限流。我習(xí)慣用指數(shù)退避隨機(jī)抖動(dòng)初始300ms最多重試3次。降級(jí)鏈的長(zhǎng)度一般控制在兩跳以?xún)?nèi)超過(guò)兩跳說(shuō)明路由配置有問(wèn)題應(yīng)該回到配置排查而不是繼續(xù)往下試。降級(jí)鏈的每一跳都要記錄耗時(shí)和結(jié)果否則你只能看到最終異??床坏街虚g哪一跳浪費(fèi)了最多時(shí)間。3. 緩存層設(shè)計(jì)把重復(fù)的錢(qián)徹底省下來(lái)3.1 精確緩存與語(yǔ)義緩存的邊界緩存是token成本優(yōu)化里見(jiàn)效最快的手段但也最容易做壞。我先說(shuō)結(jié)論網(wǎng)關(guān)里至少要做兩層緩存——精確緩存和語(yǔ)義緩存它們的適用場(chǎng)景完全不同。精確緩存就是完全相同的請(qǐng)求直接返回舊結(jié)果。適用場(chǎng)景是低風(fēng)險(xiǎn)、穩(wěn)定性要求高的調(diào)用比如數(shù)據(jù)查詢(xún)類(lèi)的自然語(yǔ)言轉(zhuǎn)SQL、固定的運(yùn)營(yíng)文案生成。Key是請(qǐng)求參數(shù)的規(guī)范化拼接比如歸一化之后的prompt、模型檔位、采樣參數(shù)的組合。命中時(shí)直接從Redis返回不調(diào)用模型成本為零。語(yǔ)義緩存是意思相同但表述不同的請(qǐng)求用歷史結(jié)果返回。比如某業(yè)務(wù)方上午問(wèn)上個(gè)月訂單量是多少下午問(wèn)上個(gè)月的訂單數(shù)有多少精確緩存永遠(yuǎn)命中不了但語(yǔ)義上完全一致。實(shí)現(xiàn)方式是給請(qǐng)求文本做embedding然后在向量庫(kù)里做相似度檢索超過(guò)閾值我常用0.92到0.95就復(fù)用緩存結(jié)果。這里要?jiǎng)澢暹吔缇_緩存可以在所有請(qǐng)求上開(kāi)啟語(yǔ)義緩存只適合確定性場(chǎng)景。凡是結(jié)果需要強(qiáng)時(shí)效性比如股價(jià)、天氣或者內(nèi)容需要多樣性的場(chǎng)景比如營(yíng)銷(xiāo)文案、頭腦風(fēng)暴語(yǔ)義緩存一律關(guān)掉否則用戶(hù)會(huì)看到一個(gè)老是被重復(fù)答案的機(jī)器人體驗(yàn)反而崩了。我見(jiàn)過(guò)一個(gè)團(tuán)隊(duì)把語(yǔ)義緩存用在了新聞?wù)涌谏辖Y(jié)果所有用戶(hù)拿到的都是同一篇?dú)v史摘要差點(diǎn)釀成事故。3.2 網(wǎng)關(guān)里的緩存寫(xiě)入與失效策略緩存寫(xiě)入放在哪一層很多人會(huì)搞錯(cuò)。不要在業(yè)務(wù)方寫(xiě)緩存也不要在模型響應(yīng)之后單獨(dú)做一層事后緩存而是要在網(wǎng)關(guān)轉(zhuǎn)發(fā)之前先查、轉(zhuǎn)發(fā)成功之后再寫(xiě)。我的實(shí)現(xiàn)是標(biāo)準(zhǔn)Cache Aside模式public CompletionResponse handle(GatewayRequest req) { String cacheKey cacheKeyBuilder.build(req); OptionalCompletionResponse cached cache.get(cacheKey); if (cached.isPresent()) { metricRecorder.recordCacheHit(req.getRouteName(), cacheKey); return cached.get(); } CompletionResponse resp invoker.invokeWithFallback(req); if (resp.isSuccess() !resp.isTruncated()) { cache.set(cacheKey, resp, route.getCacheTtlSeconds()); } return resp; }有兩點(diǎn)需要注意。第一只有完整的、沒(méi)有觸發(fā)截?cái)嗟捻憫?yīng)才應(yīng)該寫(xiě)入緩存如果響應(yīng)因?yàn)閙ax_tokens不夠被截?cái)嗔藢?xiě)入緩存會(huì)把不完整的內(nèi)容反復(fù)發(fā)給用戶(hù)。第二TTL不要拍腦袋定要根據(jù)業(yè)務(wù)場(chǎng)景設(shè)置。對(duì)于數(shù)據(jù)報(bào)表類(lèi)請(qǐng)求我通常設(shè)300秒對(duì)于靜態(tài)知識(shí)型問(wèn)答可以到24小時(shí)對(duì)于實(shí)時(shí)性高的場(chǎng)景就不寫(xiě)緩存。我還在緩存里存了寫(xiě)入時(shí)間和模型版本模型升級(jí)時(shí)可以把對(duì)應(yīng)版本的緩存批量失效掉避免新舊模型結(jié)果混著返回。3.3 緩存命中率上不去的原因排查如果你的精確緩存命中率低于預(yù)期先別急著懷疑緩存框架大概率是Key設(shè)計(jì)的問(wèn)題。最常見(jiàn)的坑有三個(gè)。第一個(gè)是提示詞里的無(wú)關(guān)變量。很多請(qǐng)求文本里帶了當(dāng)前時(shí)間、隨機(jī)數(shù)、用戶(hù)ID等字段導(dǎo)致Key永遠(yuǎn)不一樣。排查方法是把請(qǐng)求參數(shù)打散看哪些字段的高基數(shù)是異常的然后把它們從Key計(jì)算里剔除。第二個(gè)是采樣參數(shù)不一致。同一個(gè)prompttemperature一會(huì)兒是0.7一會(huì)兒是0.9Key就分叉了。我的做法是緩存Key只考慮temperature為0或低采樣場(chǎng)景高采樣場(chǎng)景默認(rèn)不參與精確緩存。第三個(gè)是字符串空白和全角半角差異。提示詞里多了個(gè)換行、全角括號(hào)變成半角Key就變了所以Key構(gòu)建前要做字符串歸一化去首尾空白、統(tǒng)一換行符、統(tǒng)一全半角。語(yǔ)義緩存命中率低的原因則一般是相似度閾值設(shè)得太高。閾值高精度高但召回少。我建議先從0.90開(kāi)始?jí)簻y(cè)觀察線上誤命中比例如果用戶(hù)反饋答非所問(wèn)的比例可以接受再逐步降到0.85。寧可少命中一點(diǎn)也不能讓不相關(guān)的內(nèi)容以高相似度復(fù)用到別的請(qǐng)求上。語(yǔ)義緩存的向量本身也要考慮維度大小256維和768維的存儲(chǔ)成本差很多在滿(mǎn)足效果的前提下選低維方案。4. Token治理請(qǐng)求入口的消費(fèi)控制4.1 系統(tǒng)提示詞的成本審計(jì)模型調(diào)用的賬單由輸入token和輸出token共同決定而輸入token里最被忽視的就是系統(tǒng)提示詞。很多團(tuán)隊(duì)的提示詞是從業(yè)務(wù)方那里拼出來(lái)的越疊越長(zhǎng)動(dòng)輒兩三千token而且每個(gè)請(qǐng)求都帶上費(fèi)用呈線性放大。我做了一次審計(jì)之后發(fā)現(xiàn)業(yè)務(wù)系統(tǒng)里一條普通的知識(shí)問(wèn)答請(qǐng)求系統(tǒng)提示詞占了整個(gè)輸入token的70%以上。把提示詞壓縮掉一半同等業(yè)務(wù)量下模型調(diào)用費(fèi)直接降了兩成。壓縮手段并不玄乎去掉形容詞、合并重復(fù)的指令、把冗長(zhǎng)的few-shot例子換成更精簡(jiǎn)的版本、公共約束抽到網(wǎng)關(guān)層統(tǒng)一維護(hù)而不是每個(gè)業(yè)務(wù)方各寫(xiě)一份。網(wǎng)關(guān)里還應(yīng)該做一道硬校驗(yàn)提示詞長(zhǎng)度上限。超過(guò)閾值的請(qǐng)求要么拒絕要么強(qiáng)制走摘要預(yù)處理鏈路先用便宜的小模型把長(zhǎng)文本壓縮成固定長(zhǎng)度的摘要再送給大模型。這個(gè)先小后大的模式在文檔分析、長(zhǎng)對(duì)話場(chǎng)景里非常好用整體成本能降30%到50%。我第一次給一個(gè)合同審核業(yè)務(wù)上線這個(gè)鏈路時(shí)對(duì)方一開(kāi)始擔(dān)心小模型摘要會(huì)丟信息后來(lái)我把摘要長(zhǎng)度調(diào)到剛好覆蓋關(guān)鍵條款后對(duì)接方就接受了。4.2 上下文窗口裁剪的兩種常用策略多輪對(duì)話是最容易燒token的場(chǎng)景。每輪都把完整歷史記錄傳進(jìn)去上下文越來(lái)越長(zhǎng)成本越來(lái)越高而且模型還可能被早期閑聊信息干擾。網(wǎng)關(guān)需要在把請(qǐng)求發(fā)給模型之前對(duì)上下文做一個(gè)瘦身。我實(shí)踐過(guò)三種裁剪策略排個(gè)優(yōu)先級(jí)滑動(dòng)窗口只保留最近N輪對(duì)話適合閑聊型和客服型場(chǎng)景N通常取10到20輪。實(shí)現(xiàn)簡(jiǎn)單是性?xún)r(jià)比最高的起點(diǎn)。摘要層每滿(mǎn)M輪就把之前的對(duì)話交給小模型生成一版摘要后續(xù)請(qǐng)求用摘要加最近幾輪原文適合需要長(zhǎng)程記憶的場(chǎng)景成本和控制力的平衡最好。語(yǔ)義篩選給每輪對(duì)話打embedding計(jì)算與當(dāng)前問(wèn)題的相關(guān)性只保留相關(guān)性高的輪次效果最好但計(jì)算成本最高適合對(duì)質(zhì)量要求極苛刻的場(chǎng)景。三種策略我在網(wǎng)關(guān)里做成了可配置的Pipeline每個(gè)路由可以聲明自己的上下文處理方式。對(duì)于混合場(chǎng)景我建議是組合式方案滑動(dòng)窗口兜底摘要層兜底早期信息最后做語(yǔ)義篩選做精修。不要一上來(lái)就上最復(fù)雜的那套先跑通前兩種觀察語(yǔ)義漂移情況再?zèng)Q定。我見(jiàn)過(guò)一個(gè)團(tuán)隊(duì)直接上語(yǔ)義篩選結(jié)果每輪對(duì)話都要做embedding向量服務(wù)的成本比省下的token還多。還要說(shuō)一個(gè)輸出token的控制。max_tokens直接限制輸出成本很多業(yè)務(wù)方不設(shè)置這個(gè)參數(shù)模型就會(huì)把整個(gè)輸出窗口答滿(mǎn)。網(wǎng)關(guān)層應(yīng)該給每個(gè)路由設(shè)置默認(rèn)的max_tokens上限并且把輸出截?cái)嗟男盘?hào)記錄到日志里如果大量請(qǐng)求觸頂說(shuō)明這個(gè)路由的max_tokens配置過(guò)小需要單獨(dú)調(diào)大而不是全局放寬。我習(xí)慣在響應(yīng)里額外回傳一個(gè)usage包含promptTokens、completionTokens和truncated標(biāo)志方便做成本核算和質(zhì)量監(jiān)控。5. JVM與基礎(chǔ)設(shè)施層面的省錢(qián)空間5.1 連接池與HTTP客戶(hù)端的調(diào)優(yōu)網(wǎng)關(guān)的轉(zhuǎn)發(fā)延遲和吞吐很大程度取決于HTTP客戶(hù)端配置。默認(rèn)情況下每創(chuàng)建一個(gè)連接都要經(jīng)歷TCP握手和TLS握手在AI請(qǐng)求這種高延遲交互里連接開(kāi)銷(xiāo)會(huì)直接影響網(wǎng)關(guān)實(shí)例的資源利用率。我用的是OkHttp兩個(gè)關(guān)鍵參數(shù)是連接池大小和keep-alive時(shí)間。連接池太小會(huì)導(dǎo)致頻繁建連太大又可能空占文件描述符。以4核8G的實(shí)例為例我建議連接池per-host控制在200到400keep-alive設(shè)置為300秒空閑連接超過(guò)60秒就回收。JVM自帶的HttpClient也可以做但配置項(xiàng)更底層普通團(tuán)隊(duì)沒(méi)必要在這個(gè)層面折騰除非你有非常明確的性能瓶頸證據(jù)。還有一個(gè)容易忽略的點(diǎn)HTTP客戶(hù)端超時(shí)時(shí)間和模型接口的超時(shí)時(shí)間要匹配。如果client端超時(shí)比模型平均響應(yīng)時(shí)間短網(wǎng)關(guān)會(huì)大量超時(shí)重試費(fèi)用翻倍如果設(shè)得太長(zhǎng)用戶(hù)感知的延遲會(huì)很難看。我一般把連接超時(shí)設(shè)為3秒讀取超時(shí)設(shè)為模型P99延遲的1.5倍并按模型檔位分別配置。比如Standard檔P99是4秒讀取超時(shí)就設(shè)6秒Premium檔P99是8秒讀取超時(shí)就設(shè)12秒。5.2 內(nèi)存占用、GC與網(wǎng)關(guān)實(shí)例數(shù)Java網(wǎng)關(guān)在AI場(chǎng)景下最大的基礎(chǔ)設(shè)施成本其實(shí)是內(nèi)存。JVM默認(rèn)的堆配置不針對(duì)網(wǎng)關(guān)這種短請(qǐng)求、多線程的負(fù)載做優(yōu)化很容易出現(xiàn)堆內(nèi)存使用率低但GC頻繁的現(xiàn)象。我的調(diào)優(yōu)建議堆內(nèi)存不要超過(guò)物理內(nèi)存的一半留足給堆外內(nèi)存、線程棧和Metaspace。4核8G的實(shí)例堆設(shè)3G到4G就夠不要貪大。GC選擇上如果JDK 17以上ZGC在高吞吐網(wǎng)關(guān)場(chǎng)景的表現(xiàn)不錯(cuò)但需要測(cè)試驗(yàn)證不想折騰就用G1把暫停時(shí)間目標(biāo)設(shè)為50ms到100ms。關(guān)鍵動(dòng)作是做一次壓測(cè)記錄GC暫停時(shí)間和吞吐率再根據(jù)結(jié)果調(diào)整。我見(jiàn)過(guò)一個(gè)團(tuán)隊(duì)把堆內(nèi)存從4G加到6GGC頻率反而更高了因?yàn)槎炎兇蠛蠡厥諘r(shí)間變長(zhǎng)暫停時(shí)間暴漲。實(shí)例數(shù)量不是越多越好。我見(jiàn)過(guò)一個(gè)團(tuán)隊(duì)為了滿(mǎn)足可用性把網(wǎng)關(guān)從2個(gè)實(shí)例擴(kuò)到8個(gè)結(jié)果QPS并沒(méi)有翻四倍因?yàn)槠款i在模型接口的限流而不是網(wǎng)關(guān)本身。網(wǎng)關(guān)的容量規(guī)劃必須以后端模型通道的并發(fā)上限為基準(zhǔn)而不是以網(wǎng)關(guān)能接多少Q(mào)PS為基準(zhǔn)。模型通道允許多少并發(fā)網(wǎng)關(guān)就維持多少并發(fā)實(shí)例多余的都是純浪費(fèi)。所以我在做容量規(guī)劃時(shí)會(huì)先跟模型供應(yīng)商確認(rèn)并發(fā)上限或者用壓測(cè)打探出通道的真實(shí)瓶頸再反推實(shí)例數(shù)。5.3 異步化與線程模型的收益網(wǎng)關(guān)的每次AI調(diào)用都是秒級(jí)響應(yīng)如果每個(gè)請(qǐng)求占用一個(gè)線程等模型返回線程數(shù)很快就會(huì)打滿(mǎn)然后被迫擴(kuò)容。Java生態(tài)里解決這個(gè)問(wèn)題的標(biāo)準(zhǔn)方案有兩種響應(yīng)式編程和虛擬線程。如果項(xiàng)目是Spring Boot 3.2以上我強(qiáng)烈建議直接試虛擬線程。配置非常簡(jiǎn)單把Tomcat的executor換成虛擬線程即可代碼幾乎不用改。虛擬線程在等待I/O時(shí)不占用平臺(tái)線程網(wǎng)關(guān)實(shí)例可以用很小的線程資源支撐幾百個(gè)并發(fā)AI調(diào)用實(shí)例數(shù)可以直接減半。我在一個(gè)老項(xiàng)目上測(cè)試過(guò)同樣是2個(gè)實(shí)例虛擬線程方案能扛住的并發(fā)A I請(qǐng)求數(shù)是平臺(tái)線程方案的4倍多代價(jià)只是極輕微的CPU占用上升。這里要提醒一句虛擬線程不是萬(wàn)能藥。如果代碼里有大量的CPU密集計(jì)算比如每次都做全量向量距離計(jì)算虛擬線程的優(yōu)勢(shì)會(huì)被抵消因?yàn)镃PU資源還是有限的。網(wǎng)關(guān)里的CPU密集任務(wù)應(yīng)該抽到專(zhuān)門(mén)的線程池或者放到獨(dú)立的計(jì)算服務(wù)里保持I/O密集型主鏈路的輕盈。像語(yǔ)義緩存里的向量相似度計(jì)算我就單獨(dú)放到一個(gè)檢索增強(qiáng)模塊里異步執(zhí)行不讓它阻塞主請(qǐng)求鏈路。6. 成本觀測(cè)與回歸驗(yàn)證6.1 成本指標(biāo)的埋點(diǎn)設(shè)計(jì)沒(méi)有觀測(cè)的優(yōu)化都是盲目的。我比較推崇在網(wǎng)關(guān)里建立一套單位請(qǐng)求成本Cost Per Request, CPR指標(biāo)把它當(dāng)成網(wǎng)關(guān)的北極星指標(biāo)。CPR的計(jì)算公式很簡(jiǎn)單CPR (輸入token數(shù) × 輸入單價(jià) 輸出token數(shù) × 輸出單價(jià)) / 請(qǐng)求總數(shù)但這個(gè)平均指標(biāo)掩蓋了方差所以要配合多維度的透視。我按以下維度做聚合維度作用路由名發(fā)現(xiàn)哪些路由最燒錢(qián)模型檔位驗(yàn)證降級(jí)是否真的省了錢(qián)業(yè)務(wù)方找出prompt體量異常的調(diào)用方時(shí)間段配合流量峰值判斷是否存在浪費(fèi)緩存命中率判斷緩存策略是否健康埋點(diǎn)可以直接打在網(wǎng)關(guān)的響應(yīng)攔截器里每次調(diào)用把model、inputTokens、outputTokens、costCents、routeName、cacheHit等字段寫(xiě)進(jìn)Prometheus的Counter或Histogram然后接Grafana出圖。日志系統(tǒng)里也同步打一條結(jié)構(gòu)化日志方便做追溯分析但日志量建議抽樣比如全量日志只保留1%到5%其余只保留聚合指標(biāo)避免觀測(cè)成本反過(guò)來(lái)侵蝕成本優(yōu)化的收益。6.2 一次真實(shí)的優(yōu)化復(fù)盤(pán)說(shuō)一個(gè)我自己做過(guò)的案例給各位一個(gè)直觀感受。我們的知識(shí)庫(kù)問(wèn)答網(wǎng)關(guān)做優(yōu)化前CPR約為0.35美元每千次請(qǐng)求模型調(diào)用費(fèi)占總支出的72%。第一步上線分級(jí)模型路由把簡(jiǎn)單事實(shí)性問(wèn)題從Premium檔切到Standard檔CPR降到0.27。第二步上精確緩存問(wèn)答場(chǎng)景的緩存命中率做到38%CPR降到0.19。第三步壓縮系統(tǒng)提示詞并加上下文滑動(dòng)窗口CPR降到0.15。最后把網(wǎng)關(guān)實(shí)例從6個(gè)減到4個(gè)并切換虛擬線程基礎(chǔ)設(shè)施成本直接減半。整個(gè)鏈路優(yōu)化下來(lái)月度總成本下降了大約55%。收益的關(guān)鍵在于每一步都先用數(shù)據(jù)確認(rèn)了優(yōu)化空間而不是上來(lái)就重構(gòu)。第一版路由上線前我花了三天把歷史請(qǐng)求按意圖復(fù)雜度和模型檔位做了離線回放確認(rèn)有40%的請(qǐng)求可以用低價(jià)檔覆蓋才敢動(dòng)代碼。后來(lái)上緩存之前我又跑了一周的緩存命中率模擬確認(rèn)至少30%的請(qǐng)求是重復(fù)的才把緩存方案確定下來(lái)。6.3 持續(xù)防退化機(jī)制成本優(yōu)化不是一次性的工作最大的敵人是回歸。業(yè)務(wù)方會(huì)逐漸往提示詞里塞需求、調(diào)用頻率會(huì)悄悄上升、模型供應(yīng)商會(huì)調(diào)整價(jià)格表這些都是成本退化的來(lái)源。我建議在網(wǎng)關(guān)里跑兩條防退化機(jī)制。第一條是成本護(hù)欄按路由設(shè)置每日預(yù)算告警達(dá)到閾值80%時(shí)通知負(fù)責(zé)人100%時(shí)自動(dòng)把流量切到更低檔位并發(fā)出高優(yōu)警報(bào)。第二條是周度成本報(bào)告每周拉一次CPR趨勢(shì)和上周對(duì)比凡是有超過(guò)10%增量的路由自動(dòng)觸發(fā)定位流程。這兩條機(jī)制不需要很重的開(kāi)發(fā)量但能確保成本優(yōu)化成果不被時(shí)間侵蝕。我現(xiàn)在還會(huì)在季度例行巡檢里做一次降級(jí)有效性驗(yàn)證故意把模擬請(qǐng)求打到某個(gè)高優(yōu)模型通道上觀察網(wǎng)關(guān)是否正確降級(jí)到低價(jià)檔以及降級(jí)后的響應(yīng)質(zhì)量是否達(dá)標(biāo)。驗(yàn)證通過(guò)后我才會(huì)在周報(bào)里確認(rèn)成本策略沒(méi)有退化。這個(gè)習(xí)慣幫我抓出過(guò)一次問(wèn)題——某個(gè)新版本上線時(shí)路由表配置漏了一條降級(jí)鏈直接斷掉高優(yōu)請(qǐng)求打到低價(jià)模型后質(zhì)量明顯下滑要不是驗(yàn)證機(jī)制跑得早線上用戶(hù)早就開(kāi)始罵了。最后再分享一個(gè)體會(huì)成本優(yōu)化的核心不是摳門(mén)而是讓每一分錢(qián)都產(chǎn)生對(duì)應(yīng)的業(yè)務(wù)價(jià)值。Java網(wǎng)關(guān)做成本優(yōu)化的每個(gè)環(huán)節(jié)——路由、緩存、Token治理、JVM調(diào)優(yōu)——都不難真正難的是持續(xù)盯住數(shù)據(jù)讓成本指標(biāo)像業(yè)務(wù)指標(biāo)一樣被認(rèn)真對(duì)待。