Bỏ qua để vào nội dung chính
Đếm token cho Claude: vì sao tiktoken lệch 17% ngân sách

Đếm token cho Claude: vì sao tiktoken lệch 17% ngân sách

Bởi Mia Thompson
16 thg 9, 20267 phút đọc

tiktoken không biết từ vựng của Claude nên đếm thiếu trung vị 17,4% trên 4.200 request. Cách sửa bằng count_tokens và ba cái bẫy khi lên production.

Budget guard của bạn báo prompt nặng 171.000 token, cửa sổ ngữ cảnh còn dư, và API vẫn trả về 400 "prompt too long". Bạn mở log, đếm lại bằng tay, và không hiểu con số sai ở đâu.

Nguyên nhân thường nằm ở đúng một dòng: hàm đếm token dùng tiktoken cho một model không phải của OpenAI. Bài này chỉ ra lệch bao nhiêu, lệch ở loại nội dung nào, và cách thay bằng endpoint đếm chính xác mà vẫn giữ được cost guard.

tiktoken không biết từ vựng của Claude

Một lập trình viên đã log song song hai con số — ước lượng cục bộ và lượng token API thực sự tính tiền — trong suốt một tháng chạy pipeline. Theo bài viết, tiktoken là tokenizer của OpenAI: "It does not know Claude's vocabulary, so every number it gives you for a Claude prompt is a guess."

Lý do kỹ thuật đơn giản. cl100k_base là bảng merge BPE của OpenAI; model khác có bảng merge khác, nên cùng một chuỗi ký tự bị cắt thành số mảnh khác nhau.

Lệch bao nhiêu, và lệch nặng nhất ở đâu

Tác giả báo cáo số đo trên 4.200 request thật: ước lượng bằng tiktoken thấp hơn số token input API tính tiền, "a median 17.4% below". Phân bố theo bài viết là "Median 17.4% low, p95 34% low, best case 6% low. It was never once high."

Đây là số đo của riêng tác giả trên traffic của một pipeline cụ thể, không phải benchmark độc lập. Nhưng hướng lệch mới là điều đáng chú ý: ước lượng luôn thấp hơn thực tế, tức là guard luôn sai về phía nguy hiểm.

Độ lệch bám rất sát loại nội dung, theo bảng tác giả công bố:

Loại nội dungMức đếm thiếu (trung vị)
Văn bản tiếng Anh thuần9%
Diff Python và TypeScript24%
Kết quả tool dạng JSON38%
Stack trace và log dump29%

Không có hệ số quy đổi cố định để bù: hệ số tinh chỉnh trên văn bản thường sẽ vỡ ngay khi agent dán vào một diff 400 dòng — đúng lúc prompt đủ lớn để chạm giới hạn. Request làm hỏng run của tác giả có 94% khối lượng là code.

Thiệt hại tác giả ghi nhận trong 21 ngày: "19 runs died on context-limit 400s that my guard had waved through. Input spend ran 21% above what the estimator's forecast implied for the month." Hai lỗi này cộng dồn — mỗi lần 400 lại kéo theo một lần cắt bớt rồi gửi lại, nên prompt tệ nhất bị trả tiền hai lần.

Nửa số lỗi còn lại không liên quan tới tokenizer

Phần lớn code đếm token chỉ duyệt mảng messages. Nhưng request thật gồm tools, rồi system, rồi mới tới messages. Tác giả mô tả chính xác điểm mù này: "My nine tool schemas came to 1,318 tokens, on every single call" và system prompt kèm repo map thêm khoảng 900 token nữa.

Tổng cộng khoảng 2.218 token bị đếm bằng 0 trên mọi request. Với pipeline khoảng 200 call mỗi ngày, đó là sai số cố định, có hệ thống, không phải nhiễu.

Cách sửa: đếm bằng đúng body sắp gửi

Giải pháp là gọi endpoint đếm token chính thức với cùng body. Tác giả xác nhận: "It matched usage exactly on every call I checked. Cost: one extra round trip, median 240ms."

Đoạn code minh hoạ dưới đây theo mô tả trong bài — bạn cần chỉnh theo SDK và tên model đang dùng:

resp = client.messages.count_tokens(
    model="claude-haiku-4-5",   # counts are model-specific
    system=SYSTEM_PROMPT,
    tools=TOOLS,
    messages=messages,
)
print(resp.input_tokens)

Hai chi tiết dễ bỏ sót. Thứ nhất, kết quả phụ thuộc model — truyền đúng model bạn sắp gọi, không phải ID nằm sẵn trong hằng số. Thứ hai, nếu bạn dựng body ở một chỗ và đếm ở chỗ khác, sớm muộn bạn sẽ đếm một body không phải cái được gửi đi.

Đối chiếu với thực tế: phải cộng đủ ba trường

Khi bật prompt caching, so sánh ước lượng với usage.input_tokens sẽ cho kết quả vô lý. Theo bài viết, với caching bật thì "input_tokens is only the uncached portion", phần còn lại nằm ở cache_read_input_tokenscache_creation_input_tokens. Muốn đối chiếu kích thước prompt, bạn cộng cả ba.

Có một tác dụng phụ đáng giá. Khi đã cộng ba trường, cache_read_input_tokens bằng 0 qua nhiều request lặp lại là tín hiệu rõ ràng: phần prefix đang thay đổi giữa các lần gọi và cache không bao giờ hit.

Ba cái bẫy khi đưa vào production

Về độ trễ, tác giả đo "median 240ms, p95 610ms" trên các call vốn đã mất nhiều giây — không đáng kể ở quy mô 200 lần đếm mỗi ngày. Trong vòng lặp phục vụ từng người dùng thì câu chuyện khác, nên đếm prefix tĩnh (tools + system) một lần lúc khởi động rồi cache lại con số đó.

Về tính cộng dồn, con số không khớp tuyệt đối: theo bài viết, đếm cả body so với cộng từng phần lệch "0 to 4 tokens". Nhỏ, nhưng đủ để bạn nên chừa biên 1% thay vì tin số học là chính xác.

Về rate limit, đừng đếm trong fan-out. Tác giả đếm từng chunk ứng viên ở bước retrieval và dính 429 — endpoint đếm token vẫn có giới hạn riêng. Đếm prompt đã ráp xong, đúng một lần.

Giới hạn thành thật của cách này: bạn biết chính xác input, và không biết gì về output. Vì vậy vẫn phải chừa headroom bằng max_tokens bên trên số token input đã đếm.

Đặt cost guard ở đâu trong pipeline

Nếu bạn muốn gói phần này thành middleware thay vì viết tay, thư viện Python callm vừa công bố trong tuần đi theo hướng decorator. Tác giả mô tả thứ tự xử lý: kiểm tra bảo mật, tra cache, validate theo schema, fallback sang provider khác, rồi "estimated cost is checked against max_cost before the request actually goes out".

Các con số hiệu năng kèm theo là benchmark offline của chính tác giả thư viện, chạy với server giả: overhead "+0.06 ms" mỗi call, tiết kiệm 91% chi phí với traffic dạng FAQ nhưng chỉ 2% khi prompt gần như không lặp, và tỉ lệ thành công đi từ 79,9% lên 99,4% rồi 100% khi thêm retry và fallback trong điều kiện 20% request bị 503 ngẫu nhiên.

Chính tác giả cũng khuyến cáo đo trên traffic của bạn trước khi tin vào mức tiết kiệm đó. Lời khuyên hợp lý: hiệu quả cache phụ thuộc hoàn toàn vào tỉ lệ prompt lặp lại trong hệ thống của bạn, không phải vào thư viện.

Việc nên làm tuần này

Mở đúng chỗ đang ước lượng token trong codebase và kiểm tra ba thứ: nó có dùng tokenizer của đúng nhà cung cấp không, nó có tính toolssystem không, và nó có so sánh với đủ ba trường usage khi caching bật không.

Sau đó log song song ước lượng và số thực tế trong vài ngày. Nếu sai số của bạn cũng chỉ lệch về một phía, guard hiện tại đang không bảo vệ gì cả — nó chỉ khiến bạn ngừng kiểm tra.

Không spam, hủy đăng ký bất kỳ lúc nào.

Bài viết liên quan