真實手冊 RAG 一日實戰:15 個坑全記錄

重點摘要
  • 把「合成的假手冊」換成真實可下載的工廠設備手冊做 RAG,一天內連環踩了 15+ 個坑,橫跨四端:環境/GPU、PDF 讀取、程式碼、檢索與生成。
  • 最陰的根因是 CJK 相容表意文字(U+F9E0):「易」被抽成非標準碼,肉眼一樣但 in 搜不到、嵌入向量爛、LLM 看不懂——一行 NFKC 正規化才解。
  • 故障排除手冊的價值是「症狀 → 該做什麼」。get_text() 把表格壓平就毀了;用 find_tables() 還原「症狀→處置」配對才救回可操作性。
  • 小模型(3B)會答非所問、反問、簡體;換 7B + 意圖導向檢索加權,才真的答出「電源指示不亮 → 換控制箱保險絲」。

這是一篇 真實手冊 RAG(檢索增強生成)的實戰除錯全記錄。目標很單純:讓工廠人員用自然語言問設備手冊,得到「有出處、可操作」的答案——正是 PMC 技術通報〈突破傳統技術手冊:LLM 如何成為技術操作知識的即時來源〉描繪的場景。但把 demo 從「程式合成的假手冊」換成「真的工廠手冊 PDF」之後,現實給了我一整天的耳光。以下按環境、PDF 讀取、程式、檢索與生成四端,鉅細靡遺記錄每一個坑、根因與修法。

🧭 本文導覽
Part 一 — 目標與踩坑總覽
Part 二 — 這支 Notebook 怎麼運作
Part 三 — 環境:Kaggle 三連 OOM
Part 四 — PDF 讀取:真手冊是髒資料
Part 五 — 表格當一等公民
Part 六 — 程式端的自挖坑
Part 七 — 檢索:找對書/章節/型態
Part 八 — 生成:3B vs 7B
Part 九 — 收束心法
Part 一 — 目標與踩坑總覽

為什麼要用「真實手冊」而不是合成資料?

一開始的 demo 用程式生成一份「30 台一模一樣的泵浦」的假手冊。它有個致命的假象:每台機台內容完全相同,問「泵浦壓力」會回一堆重複的引用,看起來像複製貼上。這其實暴露了合成資料的失真——但也讓人懷疑:真手冊會不會更好?答案是:真手冊不會有這種假重複,但它是另一種地獄——髒資料。真手冊的抽字有相容字、有垂直單字、有壓平的表格、有不一致的目錄。換句話說,合成資料考驗「架構」,真實資料考驗「前處理」。

我用三個平行的搜尋 agent,實際下載並驗證了 13 份公開的真手冊(抽水機、空壓機、CNC、變頻器、PLC、工業爐…),最後用一份 57 頁的南投「12 吋移動式抽水機操作維護手冊」當主測材——因為它是真廠商 O&M、有真正的「故障原因→矯正措施」表。

15 個坑,四端一覽

一句話根因修法
環境載入模型 CUDA OOM長 session 重跑,舊模型沒釋放Restart / empty_cache
環境bge-m3 OOM(單 op 4.69 GiB)max_seq_length 預設 8192壓到 512
環境7B 生成 OOM(單 op 6.87 GiB)T4 塞不下雙模型換 3B/嵌入移 CPU
PDF🎯 搜不到/嵌入爛/LLM看不懂CJK 相容字 U+F9E0NFKC 正規化
PDF表格抽成垂直單字亂碼get_text 逐字換行接字 + 去空白
PDF🎯 故障表壓平→答不出該做什麼表格結構丟失find_tables 還原症狀→處置
PDF目錄/TOC 不一致有的有有的沒有TOC用TOC否則標題偵測
程式⚠️ 絕對路徑害 Kaggle 跑掛OUT 寫成本機路徑改相對路徑
程式notebook 生舊格式只改獨立檔漏改 cell同步 + 測真入口
程式依據書名變章節名變數 shadowing迴圈變數改名
程式去空白靜默失效正則反斜線三層跳脫改 “”.join(split())
檢索引用張冠李戴盲切跨機台+子字串過濾結構化切片+metadata
檢索問A機台答B機台無型式路由book/型式 metadata 過濾
檢索問故障答保養混合context從多數答意圖導向加權
生成3B 答非所問/簡體/亂碼小模型天花板7B + opencc + 後處理
Part 二 — 這支 Notebook 怎麼運作

使用情境:誰來問、問什麼

先看這套系統要解決誰的問題。使用者是現場人員或維修工程師,他們手上有一堆設備手冊,但翻不動、也不知道答案在第幾頁。他們要的不是「相關文件」,而是「該做什麼」。

多本手冊 RAG 系統現場人員 / 維修工程師問「X 故障了,該怎麼處置?」問「這台多久要保養一次?」跨機台查詢(空壓機 vs 泵浦)要求標出處(哪本·哪章·第幾頁)

核心觀念:我們「利用」模型,不「改」模型

這是整套設計的靈魂,也是它跟「購物網關鍵字檢索」本質不同的地方。我們沒有重新訓練、也沒有微調任何模型——bge-m3 和 Qwen 都是現成的、凍結的。我們做的全部是「餵對東西」:把手冊清洗乾淨、切成對的片、用向量找出最相關的段,再把這些段當「臨時課本」交給模型現場閱讀作答。模型的本事沒變,變的是我們給它的上下文品質

面向購物網「關鍵字檢索」本文的「RAG:利用模型」
比對方式字面關鍵字比對(有這個詞就中)語意向量相似度(意思接近就中)
回傳什麼一串「相關商品/文件」連結一段「直接回答 + 出處」
懂不懂「意思」不懂,只認字懂語意(電源指示不亮 ≈ 沒電、跳電)
有沒有改模型無模型有現成模型但凍結不改,只給它上下文
答案哪來人自己從清單裡找模型從我們給的手冊段落現場讀出來
一句話
RAG = 把「凍結的通用模型」+「你的私有手冊」臨時接起來。難的不是模型,是把手冊變成模型讀得懂的乾淨上下文——這一整天的坑,幾乎都在這件事上。

五段管線:從 PDF 到「症狀→處置」

這支 Jupyter notebook 從頭到尾就是五段。下圖是資料流,接著逐段看它「在做什麼、怎麼切、怎麼變向量、怎麼跟模型交互」。

① 抽字NFKC+表格還原② 切片章節邊界+metadata③ 嵌入bge-m3 → 向量④ 檢索路由+意圖加權⑤ 生成7B 讀資料作答

① 抽字 — 把髒 PDF 洗乾淨、把表格救回來

每一頁先用 pymupdf 抽字,馬上做 NFKC 正規化(修相容字)、接垂直單字,並用 find_tables() 把故障/保養表還原成有語意的「症狀→處置 / 保養項目→週期」文字。

def _pages(doc):
    return {i+1: _norm_extract(_page_text(doc[i])) for i in range(doc.page_count)}

def _norm_extract(t):
    t = unicodedata.normalize("NFKC", t)   # 修 CJK 相容字(U+F9E0 易 → 標準 易)
    ...                                    # 接垂直單字 + 去中文字間空格

def _page_text(page):
    for tb in page.find_tables().tables:   # 把表格按行列還原
        # 故障表 → 「[啟動馬達無法啟動] 症狀:電源指示不亮 → 處置:換保險絲」
        ...

② 切片 — 一章一片,每片貼上「哪本·哪機·哪章·第幾頁」

關鍵不是「切多大」,而是「照結構切」:有目錄用目錄,沒目錄用標題偵測。每一片都帶 metadata,這是後面「找對書、找對章節」的根。

chunks.append(dict(
    book_id=book_id, machine=machine, book_title=title,
    section=sectitle,              # 例:「五、 簡易故障排除」
    page_range=pr,                 # 例:(9, 10)
    text=seg))                     # 已含「症狀:… → 處置:…」結構化文字

③ 嵌入 — 把每片「變成向量」,才能用語意找

「變向量(embedding)」是 RAG 能懂語意的關鍵:bge-m3 把每片文字轉成 1024 維向量,意思接近的片,向量就接近。注意向量文字前面帶了 metadata,讓「書名/機台/章節」也進入語意空間。建完索引後把嵌入模型移到 CPU 騰出顯存給生成模型。

embedder = SentenceTransformer("BAAI/bge-m3", device=DEVICE)
embedder.max_seq_length = 512
def view(c):   # 向量文字帶上 metadata
    return f"[{c['book_title']}|{c['machine']}|{c['section']}|第{c['page_range'][0]}頁] {c['text']}"
emb = embedder.encode([view(c) for c in all_chunks], normalize_embeddings=True)  # → (N, 1024) 向量矩陣
embedder.to("cpu")   # 建完索引移 CPU,騰顯存給生成

④ 檢索 — 語意相似 +「找對書」+「找對型態」

查詢也轉成向量,用內積找最相似的片;再疊三層加權:機台路由(問空壓機只查空壓機那本)、章節加權意圖加權(問故障就強推含「→ 處置:」的片)。這是純向量相似度不夠、需要 metadata 訊號的地方。

fault_intent = any(k in q for k in ["故障","排除","異音","震動","過熱","怎麼辦","處置"])
maint_intent = any(k in q for k in ["保養","維護","週期","多久","潤滑","更換"])
def _boost(c):
    b = 0.30*sum(1 for w in qkw if w in (c["section"] or ""))     # 章節命中
    if fault_intent and "→ 處置:" in c["text"]: b += 0.35          # 問故障 → 推症狀→處置片
    if maint_intent and ("→ 週期:" in c["text"]): b += 0.35        # 問保養 → 推保養週期片
    return b
pool = [(sim + _boost(c), c) for sim, c in pool]                   # 相似度 + 加權 重排

⑤ 與模型交互 — 把撈到的段當「臨時課本」交給模型

這一步就是「利用模型不改模型」的具體樣子:把 top-k 段組成 ctx,連同問題一起丟給 Qwen,要求它只准用這些資料作答、每點標依據編號。模型沒被訓練過這本手冊,它是現場讀我們遞上去的段落才答得出來。長度與繁體再用程式後處理收尾。

ctx = "\n".join(f"[{n+1}] ({_cite(c)}) {c['text']}" for n,c in enumerate(top))
ans = chat(SYS, f"【資料】\n{ctx}\n\n【問題】{q}")   # SYS:只准用【資料】、每點標(依據N)、禁反問
# 後處理:opencc 簡→繁、說明硬截 150 字、依據只標「書·章·頁」不倒原文
為什麼這樣就不算「改模型」
模型的權重從頭到尾沒動。我們只是每次問答時,臨時把「最相關的手冊段落」放進它的輸入(context window)裡讓它讀。換一本手冊、加一本手冊,完全不用重訓——這就是 RAG 相對於「微調模型」的最大好處:知識可熱插拔

模型可熱插拔:免費本地 vs 雲端 120B,一行切換

在 Kaggle 上跑小模型,純粹是「免費機器」的妥協。既然我們是「利用模型不改模型」,那換一顆更強的模型就完全不用動前面的抽字、切片、檢索——只換 chat() 背後打誰。所以生成端加了一個後端路由,兩者都留著、隨你切:預設本地 Qwen(免費、免金鑰即可跑),要更好品質就切到雲端 Cerebras gpt-oss-120b(不佔本地 GPU)。

LLM_BACKEND = "local"        # 兩者可選:"local" 免費本地 Qwen /  "cerebras" 雲端 120B

if LLM_BACKEND == "local":
    llm = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B-Instruct", ...)  # 免費、但受 T4 顯存限制
    def _gen(system, user, n): ...本地 generate...
else:
    def _gen(system, user, n):     # 雲端:OpenAI 相容端點,不載本地模型 → 零 OOM
        r = requests.post("https://api.cerebras.ai/v1/chat/completions",
            headers={"Authorization": f"Bearer {CEREBRAS_API_KEY}"},
            json={"model": "gpt-oss-120b", "temperature": 0,
                  "messages": [{"role":"system","content":system},{"role":"user","content":user}]})
        return r.json()["choices"][0]["message"]["content"]

def chat(system, user, n=320): return _clean(_gen(system, user, n))   # 前面全部不用改
本地 Qwen(免費)雲端 Cerebras gpt-oss-120b
參數量3B / 7B120B
答題品質會反問/簡體/受限連貫、直接、穩
本地 GPU要載模型、易 OOM完全不佔(打 API)
成本免費依 API 用量
適合無網路/純離線 demo要品質、要跑真手冊
這正是 RAG 的價值
檢索與生成解耦——知識在你的手冊裡(可熱插拔),推理在模型裡(可換更強的)。同一套 R-A-G 流程,今天用免費小模型跑通,明天接雲端 120B 只改一行,前面辛苦洗乾淨的手冊上下文原封不動。

R-A-G 到底怎麼運作?一張時序圖看懂

RAG 三個字母就是三個動作:R(Retrieval 檢索)找出最相關的手冊段、A(Augmented 增強)把這些段塞進提問、G(Generation 生成)讓模型讀著這些段作答。下面這張 UML 時序圖把「離線建索引」和「線上一次問答」拆開來看。

使用者/系統bge-m3 嵌入向量索引Qwen LLM〔離線·建索引一次〕手冊 → 抽字(NFKC+表格)→ 切片(章節+metadata)每片 → 1024 維向量,存進索引① 問題文字「電源指示不亮怎麼辦」② 問題也轉成向量③ 查詢向量④ 跟每一片向量算內積分數最高 = 命中(R 檢索)⑤ 回傳命中片的原文 + 出處(書·章·頁)⑥【資料=命中片】+【問題】一起送進去(A 增強)⑦ 讀著資料生成答案(G 生成,權重不變)⑧ 答案:電源指示不亮 → 換控制箱保險絲(依據1)

怎麼知道一句話「中」哪個 index?——不是關鍵字,是向量最近

這是最多人卡住的地方。答案是:問題不是拿去「比對關鍵字」,而是被轉成一個向量,再跟索引裡每一片的向量算「有多接近」(內積 / cosine 相似度),分數最高的幾片就是「命中的 index」。所以就算問句用的字跟手冊不一樣,只要意思接近,一樣中——這就是它比購物網關鍵字檢索聰明的地方。

# emb 是 (N, 1024) 的向量矩陣(N 片手冊,每片一個向量)
qe = embedder.encode([問題], normalize_embeddings=True)[0]   # 問題 → 1024 維向量
scores = emb @ qe                    # 一次算出「問題 vs 每一片」的相似度分數
top = np.argsort(-scores)[:k]        # 分數最高的 k 片 = 命中的 index
# → 再用這些 index 去 all_chunks 取出對應的原文與出處

舉例:問「電源指示不亮怎麼辦」,手冊裡那句「電源指示不亮 → 控制箱保險絲燒毀需更換」的向量會離問題向量最近,即使問句沒有「保險絲」三個字,它照樣被撈出來——因為兩者語意相近。撈出來的原文才是接下來要交給模型的「臨時課本」。

R · Retrieval
問題轉向量 → 跟索引算相似度 → 撈出最相近的手冊片(+出處)
A · Augmented
把撈到的片當【資料】,跟【問題】拼成 prompt 送進模型
G · Generation
模型「讀著這份資料」生成答案並標依據——權重全程沒動
為什麼每個前置動作都必要
切片:索引的單位要夠小,才能精準命中「就是這一段」、也才標得出第幾頁;整本書當一個向量,問什麼都「中整本」等於沒用。② 表格還原:故障表壓平後,「症狀」和「處置」的向量糊在一起,命中了也答不出配對;還原成「症狀→處置」一句,那句的向量才能被「怎麼處置」這種問句精準命中。③ NFKC / 清洗:髒字元會讓向量算歪,命中就跟著歪。前面每一步,都是為了讓「④ 算相似度」這一刻命中對的片。
Part 三 — 環境:Kaggle 三連 OOM

環境端:Kaggle T4 的三連 OOM

第一關就是顯卡記憶體。三種 OOM 各有不同根因,值得分清楚。

bge-m3 的 max_seq_length 是隱形炸彈

症狀:嵌入模型在 scaled_dot_product_attention 一次要 4.69 GiB。根因是 bge-m3 預設 max_seq_length=8192,而 attention 記憶體正比於序列長度的平方。我們的切片都很短,8192 純浪費。

embedder = SentenceTransformer("BAAI/bge-m3", device=DEVICE)
embedder.max_seq_length = 512        # 記憶體降約 256 倍
emb = embedder.encode(texts, batch_size=16, normalize_embeddings=True)
通則
載入任何 encoder 嵌入模型,先看它的 max_seq_length,對照你切片的實際長度往下壓,別用預設。

7B + 嵌入模型塞不下 T4

索引建好後,7B 生成時 attention 一次要 6.87 GiB,而 bge-m3(2.3G)+ Qwen 7B(5.5G)已把 16 GB 吃滿。解法是建完索引把 bge-m3 移到 CPU(查詢時只編碼幾個短字串,CPU 夠快),把 GPU 全留給生成;或直接用 3B。

emb = embedder.encode(...)            # 先在 GPU 建索引(快)
if DEVICE == "cuda":
    embedder.to("cpu"); gc.collect(); torch.cuda.empty_cache()   # 騰出 ~2.3 GB
Part 四 — PDF 讀取:真手冊是髒資料

PDF 讀取端:真手冊是「髒資料」

這一端是本日最大的坑,也是最有價值的發現。真手冊的 get_text() 不保證給你乾淨的標準 Unicode

🎯 最陰的根因:CJK 相容表意文字

現象:"簡易故障排除" in textFalse,但畫面明明看得到這六個字。印出 codepoint 才真相大白:

>>> [(ch, hex(ord(ch))) for ch in "簡易故障排除"[:2]]
[("簡", "0x7c21"), ("易", "0xf9e0")]     # 易 = U+F9E0 !!
# U+F9E0 是「CJK 相容表意文字」,不是標準的 易(U+6613)
# 肉眼一樣,電腦當成不同字 → 搜不到、路由失效、嵌入向量用到怪碼、LLM 看不懂

這一個字元差異,一路毒化了搜尋、機台路由、嵌入品質、甚至 LLM 的理解——而且它不報錯,最難查。修法是抽字後做一次 Unicode NFKC 正規化,把相容字轉回標準字:

import unicodedata
text = unicodedata.normalize("NFKC", text)   # U+F9E0 易 → U+6613 易
# 本地驗證:目標故障表從「0 片可搜」→「3 片可搜」
排查手法
substring in text 明明畫面看得到卻回 False,第一時間 print([(ch,hex(ord(ch))) for ch in seg]) 看 codepoint——這次就是這樣抓到 0xF9E0 的。

垂直單字亂碼

複雜表格被 get_text() 抽成「一字一行」:「啟\n動\n困\n難」。餵給 LLM 就崩潰跳針。修法是把「只有一個字的行」接回正常詞,並去掉中文字之間的多餘空格。

Part 五 — 表格當一等公民

把「表格」當一等公民:還原「症狀 → 處置」

這是本日的核心突破。故障排除手冊的價值就是「症狀 X → 做處置 Y」的配對。但 get_text() 把三欄故障表壓成一長串,LLM 看不出配對,只能給「請參閱相關資料」這種廢話。使用者一句話點醒:「你給我的處置答案,我根本不知道我該做什麼。

解法是用 pymupdf 的 find_tables() 把表格按行列抓回來,再依表型格式化成有語意的文字:

for t in page.find_tables().tables:
    rows = t.extract()
    # 症狀→處置表(表頭含「矯正措施/處置」):
    #   [啟動馬達無法啟動] 症狀:電源指示不亮 → 處置:控制箱保險絲燒毀需更換
    # 保養矩陣(項目×週期,打 *):
    #   保養項目:更換引擎機油 → 週期:50小時、150小時、600小時或1年
    # 症狀×原因矩陣(打勾):
    #   原因:燃油品質不良 → 可能造成:啟動困難、怠速冒白煙
修好後的答案
問「抽水機的簡易故障排除」→「1. 電源指示不亮 → 更換控制箱保險絲(依據1)。2. 機油壓力過低 → 檢查並補充潤滑機油(依據1)。」這才是使用者要的「該做什麼」。
通則
手冊 RAG 遇到表格要當一等公民(find_tables + 按語意格式化),別只靠 get_text 的閱讀順序——故障/對照/規格表的配對關係就是答案本身
Part 六 — 程式端的自挖坑

程式端:四個自己挖的坑

工具再好,自己寫的 bug 一樣能讓你白跑一輪。

發生什麼教訓
絕對路徑把本機測試用的 OUT="/home/tom/.../x.pdf" 同步進 notebook,Kaggle 上路徑不存在一寫就爆本機測過的程式搬進要在別處跑的 notebook,先刷掉絕對/暫存路徑
漏改雙拷貝只改了獨立產生器檔,漏改 notebook 內嵌的同一份 → 生舊格式資料同邏輯多處拷貝要同步;測要測「使用者實際會跑的入口」
變數 shadowing切片器迴圈變數 title 蓋掉函式的「書名」參數 title → 引用把章節名當書名多層 metadata 流動時,參數名與迴圈名務必不同
跳脫地獄re.sub(r"\s+") 經 heredoc→python→JSON 三層變 \\s+ 靜默失效程式化寫 regex 進 JSON 能避反斜線就避;去空白改用 "".join(s.split())
Part 七 — 檢索:找對書/章節/型態

檢索端:找對書、找對章節、找對「型態」

真手冊 + 多本之後,檢索精準度成為主戰場。三個層次的加權缺一不可。

  • 找對書(多本路由):問「空壓機異音」自動只查《空壓機手冊》,不查泵浦那本——靠每片帶的 machine/aka metadata。
  • 找對章節:問「簡易故障排除」要撈到「五、簡易故障排除」那節,而不是隔壁的引擎排障——靠切片時按標題邊界切(不是盲切固定字數),section 標籤才正確、嵌入才專注。
  • 找對型態(意圖加權):top-k 裡故障表只佔 1 段、其餘是保養,7B 就從多數答成保養。解法是偵測查詢意圖,對「內容型態符合意圖」的片加分——問故障就強推含「→ 處置:」的片,問保養就推含「→ 週期:」的片。
通則
RAG 檢索除了向量相似度,還要有「意圖 ↔ 內容型態」對齊訊號。結構化抽表時留下「→處置:/→週期:」這種型態標記,正好拿來做意圖加權——這比「章節名對不對」更準,因為它加權的是「這片是不是你要的那種答案」。
Part 八 — 生成:3B vs 7B

生成端:3B vs 7B,以及「LLM 不會算字數」

檢索餵對了,生成端還有兩件事。

小模型的天花板

維度Qwen 3BQwen 7B
答題連貫性捏假章節、反問自己條列通順、照資料
照資料 vs 幻覺較多自由發揮較貼資料
繁體中文常輸出簡體繁體較穩
代價T4 跑得動T4 會 OOM,需大 GPU

後處理能救的:簡→繁用 OpenCC("s2twp")(模型不甩繁中指令,程式轉最可靠)、清角色標記/亂碼。救不了的「答非所問」是推理天花板——只能換 7B。

LLM 不會算字數,長度要在程式端硬控

SYS 寫「150 字內」完全沒用。量測發現:生成的說明其實大致守住,真正撐爆總長的是我把「依據」整段原文倒出來(4 段 × 160 字)。兩個修法:說明超長就在句號硬截;依據只標「書·章·頁」位置,不倒原文(原文只留在餵給模型的 context)。

一句話
RAG 輸出分兩層:給模型的 context(要完整原文) vs 給人看的 citation(只要可定位的書·章·頁),兩者內容不同,別混用同一份丟出去。
Part 九 — 收束心法

三端通則:一天奮戰換來的心法

  1. 環境:多模型上小顯卡要精算 VRAM(嵌入模型 seq 壓短、建完移 CPU、生成模型挑小的);長 session 別回頭重跑載入格。
  2. PDF 讀取:真手冊 = 髒資料。抽字後標配三件:NFKC 正規化(修相容字)、清空白(接垂直單字)、find_tables 還原表格語意。別假設 get_text 給的是乾淨標準 Unicode。
  3. 程式:同邏輯多處拷貝要同步;本機測要測「使用者實際入口」;搬進可攜 notebook 前刷掉絕對路徑;substring in text 靜默 False 先懷疑正規化沒真的執行。
  4. 檢索與生成:相似度之外要有 metadata 路由 + 意圖加權;長度靠程式硬控不靠 prompt;引用給位置不給原文;小模型跑得動 ≠ 答得好。

最後的成果:一支可配置讀哪幾本的真手冊 RAG,問哪台機器自動找對書、撈對章節,故障問題給你「症狀 → 處置」的可操作答案,保養問題給你明確週期,每一句都標得出「在哪本、哪章、第幾頁」。這一天的耳光,值得。

生成端實測:同一 RAG,7B vs 35B vs 120B 換模型比一比

把 notebook 的 generate 做成可切換後端(地端 Qwen2.5-7B / 雲端 gpt-oss-120b / 雲端 Qwen3.6-35B-A3B),檢索結果完全相同、只換生成模型,問同樣兩題:「抽水機的簡易故障排除有哪些?」「泵浦的維護保養週期?」——直接看不同模型的生成能力差在哪。

模型 部署 生成結果 判定
Qwen2.5-7B 地端(Kaggle GPU) 【說明】只吐「修。」然後把問題當答案反問(「抽水泵無法啟動的原因可能是?」);另一題出現「器 * 冷卻系統壓力測試」亂碼 ❌ 崩,不能用
Qwen3.6-35B-A3B 雲端 API 條列清楚:進水管漏氣→未置 O 形環、每滿 50h 換潤滑油、每滿 100h 塗油脂、每滿 500h 查同心 ✅ 乾淨
gpt-oss-120b 雲端 API 條列清楚:進水管漏氣→未置 O 環、泵浦漏氣→螺絲未鎖;保養週期 50/100/500h ✅ 乾淨
結論一|軸一(生成能力)= 模型落差,實錘。
7B 在「說明+依據」這種要照抄手冊、又要守格式的任務上直接崩(吐反問、亂碼);35B 與 120b 都是 production 級。所以想斷網地端只靠 7B → 現場不能信。而 35B-A3B 是 MoE(只激活 ~3B),品質卻守到 120b 等級,是「斷網又要品質」的關鍵候選。
結論二|軸二(檢索撈對段)三顆都沒解——換多強的模型也救不了。
問「簡易故障排除」,四條依據裡卻有三條指向「操作注意事項」段;真正的「五、簡易故障排除 第 9 頁」只排到第 3、說明幾乎沒引用它。120b 只是把「撈錯的段」講得漂亮——漂亮的錯答比崩掉更危險
更糟:35B 冒出「電源指示不亮 → 保險絲燒毀」,但 system prompt 白紙黑字禁止「資料沒提保險絲就不准寫保險絲」。這是疑似幻覺 + 假掛依據,待查手冊第 8 頁定案。這條病跟模型無關,是下一步要修的重點。

硬體註記:Qwen3.6-35B-A3B 是 MoE,每次只激活 ~3B 算力,但記憶體要載入全部 35B——4-bit 仍需約 21GB,單張 T4(16GB)會 OOM,得 2×T4(32GB)或直接走雲端 API。MoE 省的是算力,不是記憶體——這也是這次差點又踩的坑。

留言

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *