🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

  • 使用 OpenAI API 開發(或準備開發)應用程式的人
  • 曾被問過「如果是日文,大概會有多少 token?」而不知如何回答的人
  • 需要向上司或客戶提交 API 成本估算的人
  • 對大型語言模型(LLM)詞元化(tokenization)機制有興趣的人

この記事で得られること

  • 為何用「大概估算」來計算日文 token 數是絕望地困難
  • BPE(Byte Pair Encoding)的運作原理,以及日文在結構上為何處於不利地位
  • 不同編碼(cl100k_base / o200k_base)下日文的 token 效率實際情況
  • 使用 tiktoken 做精確 token 計數的方法,以及實務上的成本估算做法

この記事で扱わないこと

  • OpenAI API 本身的使用教學
  • 微調(fine-tuning)或 Embedding 的 token 計算
  • Anthropic / Google 等其他廠商 API 的 Tokenizer 細節

1. 事由起點:一句「大概就好」

有一天,有人這樣拜託我:

「請幫我粗估使用 OpenAI API 的聊天機器人每月大概要花多少錢。」

「大概」。我懂。英文有種經驗法則像是「1 個英文單字 ≒ 1.3 token」。那日文呢?

「1 個字元差不多 1 token?」
…不對。

「那每個文節 1 token?」
…也不對。

重要的是:同樣數量的日文字元,根據內容不同,token 數會有超過 2 倍的變動。所謂的「大概估算」這個概念從一開始就不太成立。

越查越深,這篇就是我掉進那個絕望深淵後的紀錄。


2. 前提知識:什麼是 token — 用「兌換所」的比喻理解

為了幫助理解詞元化(tokenization),在這裡我用「兌換所(外幣兌換)」的比喻來說明。

大型語言模型(LLM)無法直接理解人類語言。模型內部會把所有文字換成稱為 token(詞元)的「通貨」來處理。而 OpenAI API 的計費,就是根據這些兌換後的 token 數量來計算。

關鍵問題在於,這個「兌換匯率」會因語言而大不相同。英文通常享有「優惠匯率」,會被轉成較少數量的 token;而日文則常被視為「較貴的匯率」,會消耗更多 token。

OpenAI API 以 token 為單位計費。相同語意的句子,用日文書寫時可能比英文消耗 3–4 倍的 token。在做成本估算時若忽略語言差異,預算很可能會大幅偏差。


3. BPE(Byte Pair Encoding)— 兌換匯率就是這樣決定的

那麼這個「兌換匯率」是如何決定的呢?OpenAI 的 tokenizer 採用了 BPE(Byte Pair Encoding)演算法。

3.1 BPE 的運作 —「記住常用的組合」

用兌換所的比喻來說,BPE 就像是「針對經常出現的紙鈔組合,準備專屬的通貨」的機制。

舉例來說,在英文文本中 th 這樣的位元組序列會大量出現。BPE 會把它合併成一個 token。再如 the 也非常常見,所以整個 " the"(包含空格)可能被收錄為一個 token。

英文在 token 化時,會相對有效率地把「單字」或「單字的一部分」合併成 token,這就是所謂的「優惠匯率」來源。

3.2 結構上日文為何不利

而日文的情況則是絕望的關鍵所在。

理由 1:UTF-8 編碼下位元組數較多
BPE 並不是直接處理字元,而是基於 UTF-8 的位元組序列做合併。

  • ASCII(英數)1 byte,例如 a0x61
  • 平假名 / 片假名 3 bytes,例如 0xE3 0x81 0x82
  • 漢字 3 bytes,例如 0xE7 0x8C 0xAB
  • Emoji 4 bytes,例如 🔥0xF0 0x9F 0x94 0xA5

英文的 cat 是 3 bytes,日文的 也是 3 bytes。但 cat 在 BPE 的訓練資料中大量出現,能被合併成 1 個 token; 的出現頻率相對較低,常會被拆成多個位元組的 token(2–3 token)。

所以即使是相同的位元組數,因為訓練資料中出現頻率不同,token 數會完全不同。日文在訓練資料中的出現頻率遠低於英文,導致位元組合併較少、token 效率差。

理由 2:Unicode CJK 統一漢字過多
Unicode(截至 15.1)包含超過 97,000 個 CJK 統一漢字。cl100k_base 的詞彙表大約有 100,000 個 token,如果要把所有 CJK 漢字都納入,詞彙表幾乎會被耗盡。結果是很多漢字會以位元組形式被拆開成多個 token。

理由 3:日文沒有以空格分詞的概念
英文句子單字間有空格,使得 BPE 的前處理能容易形成像 " the" 這樣的單字塊。日文如「私は猫が好きです」是連續的字串,BPE 的基於正規表達式的前處理,在日文上無法像英文那樣有效地切分與合併。


4. 實驗:用日英相同語意的句子比較

接下來實際使用 tiktoken 做驗證。把日文與英文拿到兌換所去換錢,親自感受匯率差異。

4.1 環境建置

pip install tiktoken

4.2 驗證程式碼(含中文化的輸出說明)

import tiktoken

def compare_tokens(text_ja: str, text_en: str, encoding_name: str = "o200k_base"):
    """比較日文與英文文字的 token 數量"""
    enc = tiktoken.get_encoding(encoding_name)

    tokens_ja = enc.encode(text_ja)
    tokens_en = enc.encode(text_en)

    print(f"=== {encoding_name} ===")
    print(f"日文: 「{text_ja}」")
    print(f"  字元數: {len(text_ja)}, token 數: {len(tokens_ja)}, 字元/Token 比: {len(text_ja)/len(tokens_ja):.2f}")
    print(f"英文: \"{text_en}\"")
    print(f"  字元數: {len(text_en)}, token 數: {len(tokens_en)}, 字元/Token 比: {len(text_en)/len(tokens_en):.2f}")
    print(f"  Token 比(日/英): {len(tokens_ja)/len(tokens_en):.2f} 倍")
    print()
    return tokens_ja, tokens_en

# 測試案例
pairs = [
    ("こんにちは", "Hello"),
    ("私は猫が好きです", "I like cats"),
    ("本日は晴天なり", "It is sunny today"),
    ("機械学習モデルの性能を最適化する方法について解説します",
     "This article explains how to optimize machine learning model performance"),
    ("お誕生日おめでとう", "Happy Birthday"),
]

for ja, en in pairs:
    compare_tokens(ja, en)

4.3 執行結果 — 絕望的表格

(原文中的表格意圖示例,下方用文字重現)
日文 / 英文 範例 | JP 文字數 | JP token | EN 文字數 | EN token | JP/EN 比
こんにちは / Hello | 5 | 3 | 5 | 1 | 3.0 倍
私は猫が好きです / I like cats | 8 | 6 | 11 | 3 | 2.0 倍
本日は晴天なり / It is sunny today | 7 | 5 | 11 | 4 | 1.25 倍
機械学習モデルの... / This article... | 26 | 15 | 57 | 11 | 1.36 倍
お誕生日おめでとう / Happy Birthday | 9 | 8 | 11 | 2 | 4.0 倍

「お誕生日おめでとう」是 8 token,而 "Happy Birthday" 是 2 token。4 倍。

為了傳達相同的祝福,日文竟然要付出英文 4 倍的 API 費用。現在似乎是個「用感情收費」的時代。


5. 更進一步的絕望:觀察 token 切分內部

這時你可能會問:「那日文到底是一個字會變幾個 token?」來看細節。

import tiktoken

enc = tiktoken.get_encoding("o200k_base")
text = "お誕生日おめでとう"
tokens = enc.encode(text)

print(f"文本: {text}")
print(f"token 數: {len(tokens)}")
print(f"token ID: {tokens}")
print()

for token_id in tokens:
    token_bytes = enc.decode_single_token_bytes(token_id)
    try:
        decoded = token_bytes.decode("utf-8")
    except UnicodeDecodeError:
        decoded = repr(token_bytes)
    print(f"  ID:{token_id:>6} → {repr(token_bytes):>20} → {decoded}")

在 o200k_base(GPT-4o)下的結果可能像:

文本: お誕生日おめでとう
token 數: 8
  ID:  8930 → b'\xe3\x81\x8a'       → お
  ID:  9697 → b'\xe8\xaa'           → (不完整)
  ID:   243 → b'\x95'               → (不完整)
  ID:128225 → b'\xe7\x94\x9f\xe6\x97\xa5' → 生日
  ID:  8930 → b'\xe3\x81\x8a'       → お
  ID: 17693 → b'\xe3\x82\x81'       → め
  ID: 43834 → b'\xe3\x81\xa7\xe3\x81\xa8' → でと
  ID: 12735 → b'\xe3\x81\x86'       → う

(;゚д゚) 呆住了。

「誕」字被撕成了兩個 token。b'\xe8\xaa' 與 b'\x95' 分別是 UTF-8 的位元組被斷在中間(誕 的 UTF-8 是 E8 AA 95)。而「生日」兩個字卻被合成為 1 個 token。

也就是說:

  • 字元:お — 1 token,因為平假名常見、有專屬 token
  • 字元:誕 — 被拆成 2 個 token,因為是較冷門的漢字,位元組被切斷
  • 字元:生日 — 兩個字合為 1 個 token(在中文/中文語料中常見)
  • 字元:お、め、でと、う — 各自依序有不同合併情況

總結:即便在同一句日文裡,每個字的 token 效率差異極大。平假名通常效率較好(1 字 ≒ 1 token),漢字則會根據出現頻率在 1–3 token 間波動。這種不規則性是導致「大概估算」會失準的根本原因。


6. 編碼差異 — 兌換所也有種類

不同模型使用的編碼(也就是「兌換所」)不同。

編碼 詞彙表大小 對應模型
r50k_base 約 50,000 GPT-3 時代模型
cl100k_base 約 100,000 GPT-4、GPT-3.5-turbo
o200k_base 約 200,000 GPT-4o、GPT-4o-mini

詞彙表越大,代表能把更多的位元組組合收錄為已知 token,也就是兌換所的貨品越齊全。

不同編碼下的日文 token 效率示例:

import tiktoken

text = "お誕生日おめでとう"
for enc_name in ["r50k_base", "cl100k_base", "o200k_base"]:
    enc = tiktoken.get_encoding(enc_name)
    tokens = enc.encode(text)
    print(f"{enc_name:>15}: {len(tokens)} tokens")

結果可能是:
r50k_base: 14
cl100k_base: 9
o200k_base: 8

同一句話在不同編碼下會接近翻倍的差異。因此在做 API 成本估算時,必須記得「使用哪個模型」會改變 token 數。由於成本 = token 單價 × token 數,模型選擇對成本有雙重影響(單價與 token 效率都不同)。


7. 「大概估算」的極限與實務可行的估算方法

到目前為止可以確定:用「字數 × 固定係數」去估算日文 token 是從原理上就不精確的。但在商務情境中常常還是需要一個估算數字。以下提供三個層級的現實做法。

7.1 等級 1:超粗略(用於簡報 / 初期提案)

精度低,但能抓到數量級即可。

  • 文本類型與建議係數(字數 → token):
    • 以平假名為主的文章:× 0.7〜1.0
    • 漢字較多的文章:× 1.0〜1.5
    • 混合(一般日文):× 0.8〜1.2
    • 英文:× 0.25〜0.35

一般日文可用「字數 ≒ token 數」作為最粗略的近似。以上係數基於 o200k_base(GPT-4o 系列)。若使用 cl100k_base(GPT-4),通常 token 數會再高出 1.1〜1.3 倍。

7.2 等級 2:抽樣測量(用於正式估算書 / 預算申請)

準備與實際使用情境接近的樣本文本,使用 tiktoken 做計數。

範例估算程式(已中文化):

import tiktoken

def estimate_monthly_cost(
    sample_texts: list[str],
    avg_requests_per_day: int,
    model: str = "gpt-4o",
    input_price_per_1m: float = 2.50,   # $/1M token(輸入)
    output_price_per_1m: float = 10.00, # $/1M token(輸出)
    avg_output_ratio: float = 1.5,      # 輸出/輸入 token 比例
):
    """從樣本文本估算 API 月額成本"""
    enc = tiktoken.encoding_for_model(model)

    token_counts = [len(enc.encode(text)) for text in sample_texts]
    avg_input_tokens = sum(token_counts) / len(token_counts)
    avg_output_tokens = avg_input_tokens * avg_output_ratio

    monthly_requests = avg_requests_per_day * 30
    total_input_tokens = avg_input_tokens * monthly_requests
    total_output_tokens = avg_output_tokens * monthly_requests

    input_cost = (total_input_tokens / 1_000_000) * input_price_per_1m
    output_cost = (total_output_tokens / 1_000_000) * output_price_per_1m

    print("=== 月額成本估算 ===")
    print(f"模型: {model}")
    print(f"樣本平均輸入 token: {avg_input_tokens:.0f}")
    print(f"估計平均輸出 token: {avg_output_tokens:.0f}")
    print(f"每月請求數: {monthly_requests:,}")
    print(f"每月輸入 token: {total_input_tokens:,.0f}")
    print(f"每月輸出 token: {total_output_tokens:,.0f}")
    print(f"輸入成本: ${input_cost:.2f}")
    print(f"輸出成本: ${output_cost:.2f}")
    print(f"合計: ${input_cost + output_cost:.2f} / 月")

    return input_cost + output_cost

# 使用範例:客服 Bot 的估算
sample_queries = [
    "商品の返品方法を教えてください。注文番号は12345です。",
    "先月購入したノートPCのバッテリーが膨張しています。交換対応は可能でしょうか?",
    "配送状況を確認したいのですが、追跡番号がわかりません。名前と住所で検索できますか?",
    "クレジットカードの請求額が注文金額と異なるのですが、内訳を確認できますか?",
    "解約手続きをお願いします。今月末で契約終了にしてください。",
]

estimate_monthly_cost(sample_queries, avg_requests_per_day=500)

7.3 等級 3:正式營運日誌分析(用於運營優化)

上線後應累積 API 日誌,從中分析實際的 token 消耗並修正估算。OpenAI 的 API 回應會包含 usage 欄位,例如:

# API 回應中的 usage 範例
{
    "usage": {
        "prompt_tokens": 156,
        "completion_tokens": 342,
        "total_tokens": 498
    }
}

把這些 usage 數據存下來並統計平均,會得到最準確的估算方式。


8. 常見錯誤與解法

症狀 原因 對策
安裝 tiktoken 時出錯 需要 Rust toolchain 通常 pip install tiktoken 可解決;若仍需 Rust,可安裝 rustup
使用 encoding_for_model("gpt-4o") 時發生 KeyError tiktoken 版本過舊 pip install --upgrade tiktoken 更新至最新版
把日文 token decode 後看到亂碼 token 在 UTF-8 字元中間被分割 用 decode_single_token_bytes() 取得各 token 的位元組後,先連接再 decode
估算與實際 API 計費差距很大 忽略 system prompt 或會話歷史的 token 計算時要把送到 API 的所有訊息(system、user、assistant)全部加總
同一文字在不同呼叫中 token 數會變 通常不會變;若變動可能是有不可見字元混入 在計數前對文字做正規化(例如 NFKC)
最常見的估算錯誤 只計算使用者輸入 token,忘記 system prompt 和歷史 實際計費是 system prompt + 全部會話歷史 + 本次輸入 + 輸出

9. 診斷腳本 — 一鍵檢查你的文本 token 效率

下面的腳本可以直接複製貼上,檢測任意文本的 token 效率。

import tiktoken
import sys

def diagnose_tokens(text: str):
    """診斷文本的 token 效率"""
    encodings = {
        "o200k_base (GPT-4o)": "o200k_base",
        "cl100k_base (GPT-4)": "cl100k_base",
    }

    print(f"輸入文本: {text[:50]}{'...' if len(text) > 50 else ''}")
    print(f"字元數: {len(text)}")
    print(f"UTF-8 位元組數: {len(text.encode('utf-8'))}")
    print()

    for label, enc_name in encodings.items():
        enc = tiktoken.get_encoding(enc_name)
        tokens = enc.encode(text)

        ratio = len(text) / len(tokens) if tokens else 0
        byte_ratio = len(text.encode('utf-8')) / len(tokens) if tokens else 0

        print(f"--- {label} ---")
        print(f"  token 數: {len(tokens)}")
        print(f"  字元/Token 比: {ratio:.2f} (越接近 1.0 越有效)")
        print(f"  位元組/Token 比: {byte_ratio:.2f}")

        # token 效率評估
        if ratio >= 1.0:
            print("  評價: ✅ 良好(1 字元 ≦ 1 token)")
        elif ratio >= 0.5:
            print("  評價: ⚠️ 普通(接近日文平均效率)")
        else:
            print("  評價: ❌ 非效率(可能漢字或特殊字元較多)")
        print()

# 使用範例
if __name__ == "__main__":
    test_texts = [
        "こんにちは、今日はいい天気ですね。",
        "機械学習における勾配降下法の最適化手法について概説する。",
        "Hello, it's a nice day today.",
        "RTX 5090でCUDA環境を構築してローカルLLMを動かす手順を解説します。",
    ]

    for text in test_texts:
        diagnose_tokens(text)
        print("=" * 60)

10. 2026 年版:模型別成本速查表

API 成本計算公式很簡單,但變數很多,先整理一下:

成本 = (輸入 token 數 / 1,000,000) × P_in + (輸出 token 數 / 1,000,000) × P_out

(下表為 2026 年 3 月的主要模型定價範例)

模型 編碼 輸入 ($/1M token) 輸出 ($/1M token)
GPT-4o o200k_base $2.50 $10.00
GPT-4o-mini o200k_base $0.15 $0.60
GPT-4 cl100k_base $30.00 $60.00

GPT-4o 不僅 token 單價大幅下降(約 GPT-4 的 1/12),o200k_base 的編碼也改善了日文的 token 效率。對於日文使用者來說,「單價便宜 × token 效率提升」是雙重利好。

10.1 日文 1,000 字的大致成本估算(近似值)

模型 輸入 1,000 字 輸出 1,000 字
GPT-4o 約 $0.0025 約 $0.010
GPT-4o-mini 約 $0.00015 約 $0.0006
GPT-4 約 $0.039 約 $0.078

註:此處用「日文 1,000 字 ≒ 1,000 token」做概算(以 o200k_base 為基準;cl100k_base 可能高出 1.1–1.3 倍)。


11. 學習路線圖

本文觸及了日文詞元化的「黑盒」問題。建議的後續學習步驟如下。

  • tiktoken 官方倉庫(openai/tiktoken) — 約 30 分鐘
  • OpenAI Cookbook:如何計算 token(How to count tokens) — 約 1 小時
  • Tiktokenizer(可在瀏覽器試用) — 約 15 分鐘
  • BPE 原始論文:Sennrich et al., 2016 "Neural Machine Translation of Rare Words with Subword Units" — 約 2 小時
  • Karpathy 的 Tokenizer 解說(Let’s Build the GPT Tokenizer)— 約 3 小時

總結

從一句「幫我大概算一下」出發,調查結果遠比預期複雜得多。

主要結論:

  1. 日文的 token 數無法從字數精準推估。平假名、片假名、漢字、英數混合比例,甚至每個漢字的出現頻率都會造成大幅變動。
  2. 日文通常比英文消耗約 2–4 倍的 token。這源自於 BPE 訓練資料中英文出現頻率的壓倒性優勢,是一個結構性問題。
  3. 編碼會影響 token 數。o200k_base 相較 cl100k_base 對日文效率有所改善。
  4. 想要精確估算,必須使用 tiktoken 實測。「字數 × 係數」頂多只能抓數量級。

作為日文使用者,看到這種 token 效率差距多少會有些無奈:英文 "Happy Birthday" 只要 2 token,而「お誕生日おめでとう」竟然是 8 token,同樣是祝福,卻被多收 4 倍的費用。

不過 GPT-4o 世代的編碼改進確實在改善這種不平衡。o200k_base 的詞彙表擴大是對非英文語系的一大步。短期內最務實的做法仍然是:用 tiktoken 實測你的樣本,並以實測數據作為估算依據。


參考文獻



原文出處:https://qiita.com/GeneLab_999/items/04ff280edc3178b60104


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝15   💬9   ❤️1
424
🥈
我愛JS
📝1   💬8  
51
🥉
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付