詞元:模型眼裡的文字
用 DeepSeek 和 OpenAI 的分詞器實際切一段中文、英文、文言文和程式碼,看模型到底把文字切成了什麼,以及這為什麼決定了價格,也解釋了模型為什麼數不清字數。
- 約 35 分鐘
- 難度:入門
- 實測:2026-09-14 deepseek-flash,DeepSeek V4 分詞器,tiktoken o200k_base
程式碼和執行結果保留原樣(簡體中文),與實際執行時完全一致。
大模型按詞元(token)收費,上下文視窗按詞元計算,輸出上限也按詞元設定。上一課你已經見過 usage 裡的 prompt_tokens 和 completion_tokens。可詞元到底是什麼?一個漢字是一個詞元嗎?一個英文單詞呢?
這不只是算賬的問題。模型有一些奇怪的毛病,比如有時數不清一句話有幾個字,根源也在詞元上。這一課我們把文字實際切開來看。
模型讀不到文字,只讀得到數字
神經網路只能處理數字。所以文字進入模型之前,要先經過一個叫分詞器(tokenizer)的程式:它有一張固定的詞表,把文字切成詞表裡有的片段,每個片段對應一個編號。模型看到的是一串編號,輸出的也是編號,最後再由分詞器翻譯迴文字。
這些片段就是詞元。它可能是一個字、一個詞、半個英文單詞,也可能是一個標點或者幾個空格。DeepSeek 的詞表裡有 129,280 個詞元,OpenAI 的 GPT-4o 這一代用的 o200k_base 詞表有 200,019 個。
詞表是在訓練模型之前,根據大量文本統計出來的:經常一起出現的字元組合,就合併成一個詞元。第 09 模組第 1 課會從零實現這個演算法。現在只需要記住結論:常見的組合會被合成一個詞元,罕見的會被拆碎。
實際切一下
DeepSeek 公開了它的分詞器,可以下載到本地使用。下載地址,解壓後得到一個 deepseek_v4_tokenizer 目錄。再裝兩個包:
uv add tokenizers tiktoken
tokenizers 用來載入 DeepSeek 的分詞器檔案,tiktoken 是 OpenAI 的分詞器。下面的指令碼把同樣幾段文字交給兩個分詞器,列印切出來的每個片段:
import tiktoken
from tokenizers import Tokenizer
deepseek = Tokenizer.from_file("deepseek_v4_tokenizer/tokenizer.json")
openai_enc = tiktoken.get_encoding("o200k_base") # GPT-4o 等 OpenAI 模型用的分词器
samples = [
"人工智能正在改变软件开发的方式,越来越多的程序员开始用大模型写代码。",
"Artificial intelligence is changing how software is built, and more programmers now write code with large language models.",
"学而时习之,不亦说乎?有朋自远方来,不亦乐乎?",
"def add(a, b):\n return a + b\n",
"3.1415926535897932384626",
"😀",
]
def deepseek_pieces(text):
ids = deepseek.encode(text, add_special_tokens=False).ids
return [deepseek.decode([i]) for i in ids]
def openai_pieces(text):
pieces = []
for i in openai_enc.encode(text):
raw = openai_enc.decode_single_token_bytes(i)
# 一个汉字或表情可能被拆成几个字节,单独拿出来不是合法的 UTF-8
pieces.append(raw.decode("utf-8", errors="replace"))
return pieces
for text in samples:
ds, oa = deepseek_pieces(text), openai_pieces(text)
print(f"原文({len(text)} 个字符):{text!r}")
print(f" DeepSeek {len(ds):3d} 个词元:{ds}")
print(f" OpenAI {len(oa):3d} 个词元:{oa}")
print()
執行結果:
原文(34 个字符):'人工智能正在改变软件开发的方式,越来越多的程序员开始用大模型写代码。'
DeepSeek 15 个词元:['人工智能', '正在', '改变', '软件开发', '的方式', ',', '越来越多的', '程序员', '开始', '用', '大', '模型', '写', '代码', '。']
OpenAI 21 个词元:['人工', '智能', '正在', '改变', '软件', '开发', '的', '方式', ',', '越来越', '多', '的', '程序', '员', '开始', '用', '大', '模型', '写', '代码', '。']
原文(122 个字符):'Artificial intelligence is changing how software is built, and more programmers now write code with large language models.'
DeepSeek 20 个词元:['Artificial', ' intelligence', ' is', ' changing', ' how', ' software', ' is', ' built', ',', ' and', ' more', ' programmers', ' now', ' write', ' code', ' with', ' large', ' language', ' models', '.']
OpenAI 20 个词元:['Artificial', ' intelligence', ' is', ' changing', ' how', ' software', ' is', ' built', ',', ' and', ' more', ' programmers', ' now', ' write', ' code', ' with', ' large', ' language', ' models', '.']
原文(23 个字符):'学而时习之,不亦说乎?有朋自远方来,不亦乐乎?'
DeepSeek 22 个词元:['学', '而', '时', '习', '之', ',', '不', '亦', '说', '乎', '?', '有', '朋', '自', '远方', '来', ',', '不', '亦', '乐', '乎', '?']
OpenAI 21 个词元:['学', '而', '时', '习', '之', ',不', '亦', '说', '乎', '?', '有', '朋', '自', '远', '方', '来', ',不', '亦', '乐', '乎', '?']
原文(32 个字符):'def add(a, b):\n return a + b\n'
DeepSeek 12 个词元:['def', ' add', '(a', ',', ' b', '):\n', ' ', ' return', ' a', ' +', ' b', '\n']
OpenAI 12 个词元:['def', ' add', '(a', ',', ' b', '):\n', ' ', ' return', ' a', ' +', ' b', '\n']
原文(24 个字符):'3.1415926535897932384626'
DeepSeek 10 个词元:['3', '.', '141', '592', '653', '589', '793', '238', '462', '6']
OpenAI 10 个词元:['3', '.', '141', '592', '653', '589', '793', '238', '462', '6']
原文(1 个字符):'😀'
DeepSeek 2 个词元:['�', '�']
OpenAI 1 个词元:['😀']
這一屏輸出裡有好幾個值得細看的地方。
現代漢語,DeepSeek 切得更粗。"人工智慧"、"軟體開發"、"越來越多的"在 DeepSeek 的詞表裡都是一個詞元,同一句話它用 15 個詞元,OpenAI 用 21 個。詞表是按訓練語料統計的,DeepSeek 的語料裡中文更多,所以更多的中文片語被合併了。同樣的中文內容,詞元越少,花錢越少,一次能塞進上下文的內容也越多。
英文,兩家一模一樣。常見的英文單詞基本都是一個詞元,而且空格是跟著後面的單詞走的:' intelligence' 前面帶了一個空格。122 個字元切成 20 個詞元,差不多一個單詞一個。
文言文,幾乎一個字一個詞元。"學而時習之"在兩個詞表裡都是五個單字。現代漢語裡常見的是"學習"、"時間"這樣的組合,"時習"、"不亦"這種組合太少見,沒被合併。同樣是 23 個字,文言文用的詞元比現代漢語多一倍。
程式碼裡的空格也是錢。縮排的四個空格里,三個空格成了一個單獨的詞元。Python 程式碼縮排層級越深,空格佔的詞元越多。
數字被切成三位一段。3.1415926... 被切成 141、592、653 這樣的塊。模型看到的不是一個完整的數,而是幾個"三位數片段"。這是模型做多位數運算容易出錯的原因之一:豎式計算要逐位對齊,而它的輸入根本不是逐位的。
表情符號可能被拆成位元組。DeepSeek 把 😀 切成了兩個詞元,單獨解碼出來都是亂碼 �,因為每個詞元只是這個表情編碼的一部分位元組,拼在一起才是完整的字元。
算錢時怎麼估計詞元數
最準的辦法有兩個:用官方分詞器在本地數,或者直接看 API 返回的 usage。
不方便的時候,可以粗略估計。DeepSeek 官方文件給的換算是:1 個英文字元約 0.3 個詞元,1 箇中文字元約 0.6 個詞元。我用 API 實測了幾段文字,結果比官方的數字低:
| 文本 | 字元數 | 詞元數 | 每字元詞元數 |
|---|---|---|---|
| 現代漢語(上面那句) | 34 | 15 | 0.44 |
| 英文(上面那句) | 122 | 20 | 0.16 |
| 文言文(上面那句) | 23 | 22 | 0.96 |
| Python 程式碼 | 32 | 12 | 0.38 |
| 5 個表情 | 5 | 10 | 2.00 |
測法是這樣的:同一個問題設定 max_tokens=1,只看 prompt_tokens。先發一個只有字母 a 的訊息,記下它的輸入詞元數作為基準(格式標記的開銷),再發要測的文字,兩者相減再加一,就是這段文字本身的詞元數。
def count_tokens(text):
r = client.chat.completions.create(
model="deepseek-flash",
messages=[{"role": "user", "content": text}],
max_tokens=1,
extra_body={"thinking": {"type": "disabled"}},
)
return r.usage.prompt_tokens
base = count_tokens("a") # 格式标记加上字母 a 本身,我测到的是 5
print(count_tokens("学而时习之,不亦说乎?有朋自远方来,不亦乐乎?") - base + 1)
官方文件的數字偏保守,拿來做預算正好,不容易超支。但要記住,詞元數取決於內容:古文、生僻字、表情、少見的語言都會比日常中文"貴"。
為什麼模型數不清字數
有個出名的例子:問模型 "strawberry 裡有幾個字母 r",很多模型答 2,正確答案是 3。
看看分詞器怎麼處理這個詞:DeepSeek 把單獨的 strawberry 切成 st、raw、berry 三塊,而前面帶空格的 strawberry(在句子中間出現時就是這樣)則是一整個詞元。模型拿到的是一個編號(79430),它並不直接"看見"裡面的字母。它知道這個詞裡有幾個 r,只能靠訓練時見過類似的討論。
不過這個例子太出名了,現在的模型大多見過。我讓 deepseek-flash 回答了幾種問法,每種關掉思考和開著思考各問三次:
| 問題 | 關掉思考 | 開著思考 |
|---|---|---|
| 英文問 strawberry 裡有幾個 r | 3, 3, 3 | 3, 3, 3 |
| 中文問 strawberry 裡有幾個字母 r | 2, 3, 3 | 3, 3, 3 |
| 中文問 raspberry 裡有幾個字母 r | 3, 2, 3 | 3, 3, 3 |
| "秋天的葉子一片片落下"這句話有幾個字 | 10, 10, 11 | 10, 10, 10 |
那個老問題用英文問已經全對了,換成中文問、換一個詞、改成數漢字,關掉思考時就會偶爾出錯。開著思考全對,因為模型在思考過程裡會把單詞一個字母一個字母地寫出來再數,相當於自己把詞元拆開了。
實際開發中,凡是需要精確計數的事情,比如字數統計、字串長度、文本截斷,別交給模型,用程式碼做。len("秋天的叶子一片片落下") 永遠等於 10。
常見問題
不同模型的詞元數能直接比較嗎? 不能。同樣一段中文,DeepSeek 和 OpenAI 的詞元數差了三成。比較兩個模型的價格時,要用同一段真實的業務文本,分別算出詞元數再乘以各自的單價,不能只比單價。
我在程式碼裡用 tiktoken 數 DeepSeek 的詞元,可以嗎? 英文和程式碼基本一樣,中文會偏多。要準確就用 DeepSeek 自己的分詞器。
練習
- 把你的名字、你所在城市的名字、一句方言,交給
tokens.py裡的兩個分詞器,看分別切成了什麼樣。 - 用
count_tokens測一測:同一段內容,寫成中文和寫成英文,哪個詞元更多?換成你工作中真實的一段文字試試。 - 找一段 JSON 資料(比如一個 API 的返回結果),數一數它的詞元數。再把它壓縮成一行、去掉所有空格,重新數一次,詞元數少了多少?這在需要把大量資料塞給模型時很有用。
自測
1. 同樣一句現代漢語,DeepSeek 用的詞元比 OpenAI 的分詞器少。為什麼?這對你有什麼影響?
分詞器的詞表是從訓練語料裡統計出來的,DeepSeek 的語料中文比例更高,所以更多的中文片語被合併成了一個詞元。對你的影響是:同樣的中文內容,在 DeepSeek 上花的錢更少,一次能放進上下文的內容也更多。比較模型價格時,要用真實文本算出詞元數再比。
2. 模型為什麼容易數錯一個單詞裡有幾個某個字母?
模型看到的是詞元編號,不是字母。一個常見單詞往往就是一個詞元,模型沒法直接"看見"裡面的字母,只能憑訓練時的記憶回答。開啟思考後,模型會先把單詞逐字母寫出來再數,準確率就高多了。需要精確計數時,應該用程式碼而不是模型。
3. 為什麼文言文比同樣字數的現代漢語"貴"?
詞表合併的是訓練語料裡常見的字元組合。現代漢語的常用詞("人工智慧"、"程式設計師")經常出現,被合成了一個詞元;文言文裡的組合("時習"、"不亦")很少見,沒有被合併,只能一個字一個詞元。詞元多,費用就高。