Chuyển đến nội dung chính

Tokenization Transformers v5: Kiến trúc và Hướng dẫn Di trú

Nâng cấp lên Transformers v5. Hướng dẫn thực tế về backend Rust hợp nhất, thay đổi API, và các mẫu di trú so sánh v4 vs v5 cho mã hóa và chat.
· 8 phút đọc

Khám phá cùng AI

Tokenization là phần của ngăn xếp HuggingFace Transformer mà chúng ta hầu như không để ý—cho đến khi nó “rò rỉ” vào đầu ra. Chỉ một khác biệt nhỏ về khoảng trắng cũng có thể làm thay đổi ranh giới token, quá trình giải mã có thể tạo ra chuỗi ngoài dự kiến, và prompt hội thoại có thể âm thầm hỏng khi dùng template. 

Transformers v5 xử lý những khó chịu này bằng việc thiết kế lại tokenization để tiêu chuẩn hóa hành vi giữa các triển khai, đơn giản hóa bề mặt API, và giúp việc kiểm tra nội bộ tokenizer dễ hơn khi cần debug.

Trong hướng dẫn này, tôi sẽ bắt đầu bằng việc giải thích những điều cốt lõi mà tokenization tạo ra và cách pipeline vận hành, sau đó hướng dẫn bạn những thay đổi thực tế trong v5 dưới góc nhìn di trú. Bạn sẽ thấy các mẫu mã so sánh v4 và v5 cho mã hóa, giải mã, chat template, và tuần tự hóa để có thể cập nhật codebase của mình.

Nếu bạn mới làm quen với tokenization, tôi khuyến nghị xem khóa học Giới thiệu Xử lý Ngôn ngữ Tự nhiên bằng Python.

Tokenization là gì?

Một định nghĩa rất phổ biến về tokenization là chuyển văn bản thành token ID, điều này đúng nhưng chưa đầy đủ. Trong môi trường sản xuất, tokenization được hiểu tốt hơn như một bản giao kèo. Với văn bản thô, tokenizer phải tạo ra một encoding có cấu trúc mà mô hình có thể tiêu thụ một cách an toàn.

Tối thiểu, bản giao kèo đó gồm có:

  • input_ids, là các token ID dạng số nguyên, ánh xạ vào bảng embedding của mô hình.
  • Kèm theo đó là attention_mask, chỉ ra vị trí nào tương ứng với token thực so với padding.
  • Offsets/alignments (nếu có) ánh xạ các khoảng token về các khoảng ký tự hoặc từ trong văn bản gốc, cần thiết cho highlighting, NER, và quy gán nguồn.
  • Ngữ nghĩa token đặc biệt phải được giữ nguyên, nghĩa là vị trí và ID của BOS/EOS/PAD/UNK phải khớp với quy ước dùng trong quá trình huấn luyện mô hình.
  • Với mô hình hội thoại, định dạng chat là một phần của giao kèo, vì vậy ta thường áp dụng chat template của mô hình trước khi chạy tokenization.

Trong Transformers, những trách nhiệm này nằm ở lớp tokenizer (không phải bên trong forward pass của mô hình), đó là lý do AutoTokenizer là điểm vào chuẩn. Ví dụ đơn giản:

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("HuggingFaceTB/SmolLM3-3B")
enc = tokenizer("Hello world")
print(enc["input_ids"])
print(tokenizer.convert_ids_to_tokens(enc["input_ids"]))

Đây là đầu ra kỳ vọng:

[9906, 1917]
['Hello', 'Ġworld']

Ở đây, enc là một đối tượng BatchEncoding (một bộ chứa dạng dict) nắm giữ các trường sẵn sàng cho mô hình do tokenizer tạo ra (như input_ids, và thường có attention_mask). Kết quả trả về [9906, 1917] là các token ID dạng số nguyên cho chuỗi đầu vào cùng với ['Hello', 'Ġworld'], thể hiện các chuỗi token tương ứng. 

Lưu ý: Tiền tố Ġ là quy ước phổ biến cho biết token bao gồm một khoảng trắng phía trước, vì vậy Ġworld biểu thị " world" chứ không phải "world" không có khoảng trắng.

Tokenization như một Pipeline: v5 giúp debug thế nào

Lỗi tokenization thường xuất hiện như những vấn đề nhỏ như dư khoảng trắng, tách Unicode kỳ lạ, số lượng token tăng đột biến, hoặc prompt chat không tuân theo vai trò một cách chính xác. 

Cách đơn giản nhất để debug là xem tokenization như một pipeline gồm nhiều giai đoạn. Khi bạn biết mỗi giai đoạn chịu trách nhiệm điều gì, bạn có thể cô lập vấn đề nhanh chóng thay vì đoán mò. Ghi nhớ vài điểm sau:

Tokenization là pipeline có thể lắp ghép

Mục tiêu đầu tiên là phá vỡ quan niệm sai lầm rằng tokenizer chỉ là một WordPiece. Thực tế, nó là chuỗi các giai đoạn biến đổi riêng biệt, gồm normalization, pre-tokenization, mô hình hóa subword, hậu xử lý, và giải mã. Mỗi giai đoạn kiểm soát một phần khác nhau của hành vi cuối cùng.

Mô hình tư duy dễ debug

Việc chia tokenization thành các giai đoạn cho ta một “bản đồ” xử lý sự cố đáng tin cậy:

  • Các “dị thường” về khoảng trắng thường đến từ pre-tokenizer hoặc decoder
  • Unicode thường bắt nguồn ở normalizer.
  • Hiện tượng bùng nổ token thường do mô hình subword gây ra, cùng với vocab/merges của nó
  • Lỗi định dạng chat chủ yếu đến từ template hoặc hậu xử lý

Tường minh và có thể kiểm tra theo thiết kế

Transformers v5 giúp tokenizer dễ kiểm tra hơn, đặc biệt với những tokenizer dựa trên engine tokenizers viết bằng Rust. 

Khi dùng backend Rust, ta có thể xem các thành phần nền tảng qua tokenizer._tokenizer (normalizer, pre-tokenizer, model, post-processor, decoder). Chúng ta không dùng _tokenizer hàng ngày, nhưng nó hữu ích trong bản v5, vốn cho phép quan sát nội bộ khi cần debug.

Điều này dẫn tự nhiên đến phân biệt “engine” và “wrapper”:

  • Engine (tokenizers): Triển khai Rust cấp thấp thực hiện cơ chế cốt lõi như chuẩn hóa, tách, phân đoạn subword, và tạo token ID (và offsets khi có).
  • Wrapper (transformers): Lớp hướng mô hình biến các cơ chế đó thành đầu vào sẵn sàng cho mô hình bằng cách thực thi các quy ước như xử lý token đặc biệt, chat template, quy tắc padding/cắt ngắn, và đầu ra tensor theo framework.

Có gì thay đổi trong Transformers v5?

Transformers v5 là bản phát hành chuẩn hóa lớn, và tokenization là một trong những khu vực có thay đổi thấy rõ với người dùng. Bản phát hành giảm trùng lặp triển khai, làm cho hành vi tokenizer nhất quán hơn giữa các mô hình, và giúp tokenizer dễ quan sát và (trong một số trường hợp) huấn luyện từ đầu. Dưới đây là những thay đổi ảnh hưởng nhiều nhất đến người dùng hằng ngày.

Mỗi mô hình một triển khai tokenizer

Ở các bản trước, nhiều mô hình có:

  • một tokenizer Python chậm
  • một triển khai tokenizer nhanh dựa trên Rust

Trong v5, Transformers hợp nhất về một tệp tokenizer cho mỗi mô hình và ưu tiên đường dẫn dựa trên Rust. Lý do tokenizer dựa trên Rust phù hợp là:

  • Loại bỏ trùng lặp giữa triển khai chậm và nhanh.
  • Giảm lỗi sai khác do khác biệt tinh vi về hành vi giữa các triển khai đó.
  • Triển khai này thu nhỏ và đơn giản hóa bề mặt kiểm thử, vì chỉ còn một đường code chính để xác thực.
  • Đồng thời thiết lập một “nguồn sự thật” duy nhất cho hành vi tokenizer giữa các mô hình.

Di trú API mã hóa (encoding)

API encode_plus() của v4 bị phản đối sử dụng, thay vào đó gọi trực tiếp tokenizer. Dưới đây là khác biệt giữa v4 và v5:

Phiên bản v4

enc = tokenizer.encode_plus(
    text,
    truncation=True,
    padding="max_length",
    max_length=128,
    return_tensors="pt",
)

Phiên bản v5

enc = tokenizer(
    text,
    truncation=True,
    padding="max_length",
    max_length=128,
    return_tensors="pt",
)

Chuyển từ encode_plus() sang một điểm vào duy nhất tokenizer() giúp giảm các lỗi thực tế thường gặp trong mã sản xuất. 

Trong v4, có nhiều phương thức mã hóa dẫn đến khác biệt tinh vi về padding, cắt ngắn, batching, hoặc chuyển đổi tensor tùy theo đường bạn dùng, và thường chỉ lộ ra khi chuyển từ đầu vào đơn sang tải công việc theo lô. 

v5 tiêu chuẩn hóa mọi thứ trên một đường mã hóa luôn trả về BatchEncoding, giúp hành vi dễ dự đoán hơn và giảm cả lỗi góc cạnh lẫn lượng mã cần để giữ pipeline tokenization nhất quán.

Di trú API giải mã (decoding)

v5 hợp nhất giải mã để decode() xử lý cả đầu vào đơn và theo lô, căn chỉnh hành vi decode với encode.

Phiên bản v4

texts = tokenizer.batch_decode(batch_ids, skip_special_tokens=True)

Phiên bản v5

texts = tokenizer.decode(batch_ids, skip_special_tokens=True)

Trong v5, giải mã được thiết kế phản chiếu mã hóa. Giống như tokenizer() có thể nhận một chuỗi hoặc một lô chuỗi, decode() có thể nhận một dãy token ID đơn hoặc một lô dãy. 

Điểm chính là kiểu giá trị trả về: nếu bạn truyền một dãy, bạn sẽ nhận về một chuỗi; nếu bạn truyền một lô (như list[list[int]]), bạn sẽ nhận về một danh sách chuỗi. API gọn hơn nhìn chung, nhưng cũng có nghĩa mọi mã giả định decode() luôn trả về chuỗi cần được cập nhật để xử lý cả hai trường hợp.

Chat template

Trong v4, apply_chat_template trả về input_ids thô để tương thích ngược. Trong v5, nó trả về BatchEncoding như các phương thức tokenizer khác.

Phiên bản v4

input_ids = tokenizer.apply_chat_template(messages, return_tensors="pt")

Phiên bản v5

enc = tokenizer.apply_chat_template(messages, return_tensors="pt")
input_ids = enc["input_ids"]
attention_mask = enc["attention_mask"]

Trong v5, apply_chat_template() được coi là một điểm vào tokenizer đúng nghĩa thay vì một trợ lý đặc biệt. Thay vì chỉ trả về input_ids, nó trả về đầy đủ một đối tượng BatchEncoding (với các trường như input_ids và attention_mask). 

Điều này quan trọng vì đầu vào đã định dạng chat giờ đây tích hợp vào cùng workflow batching, padding, cắt ngắn, và trả về tensor như tokenization thông thường. Nhờ vậy không cần mã bổ sung để dựng lại mask hoặc căn chỉnh thủ công trước khi gửi vào mô hình.

Tuần tự hóa tokenizer

Trong v5, tuần tự hóa tokenizer được đơn giản hóa bằng cách hợp nhất metadata về token đặc biệt và token bổ sung vào ít hiện vật hơn, đồng thời vẫn hỗ trợ tệp kế thừa để tương thích ngược. Điều này giảm rủi ro “nguồn sự thật” đa tệp bị lệch và giúp tài sản tokenizer dễ tải và tái sử dụng hơn trong các công cụ hạ nguồn hoạt động ngoài thư viện Transformers.

Tokenizer như kiến trúc có thể cấu hình

Trong v5, chuyển dịch chính là coi tokenizer như một pipeline được định nghĩa cùng với các hiện vật được học, thay vì chỉ là thứ ta nạp từ tệp tuần tự hóa.

  • Lớp tokenizer quy định cách văn bản được chuẩn hóa, chia thành đoạn, chuyển thành token subword, và cuối cùng giải mã lại thành văn bản.
  • Từ vựng và quy tắc gộp (merge) là các hiện vật được học, tức là các phần đã huấn luyện quyết định token nào tồn tại và cách văn bản được nén thành token ID.
  • Sự tách bạch này cho phép các quy trình “ưu tiên kiến trúc”. Với các tokenizer được hỗ trợ, bạn có thể khởi tạo một tokenizer theo thiết kế của mô hình rồi nạp hoặc huấn luyện vocab/merges riêng, giúp tùy biến và huấn luyện lại dễ dàng hơn nhiều.

Lời kết 

Tokenization trong Transformers v5 là bước tiến lớn hướng tới chuẩn hóa và đơn giản hóa. Giờ đây có một đường hành vi “chuẩn” hơn (giảm chênh lệch chậm/nhanh), bề mặt API đơn giản hơn, chat templating trả về cùng đối tượng BatchEncoding có cấu trúc như tokenization thường, và tuần tự hóa hợp nhất giúp giảm giả định mong manh ở cấp độ tệp. 

Nó cũng thúc đẩy mô hình tư duy “ưu tiên kiến trúc” nơi tokenizer dễ kiểm tra và, trong các trường hợp hỗ trợ, có thể huấn luyện từ đầu. Để di trú suôn sẻ từ v4, hãy thay encode_plus() bằng tokenizer(), thay batch_decode() bằng decode(), cập nhật các điểm gọi apply_chat_template() để làm việc với BatchEncoding, tránh hardcode tên tệp tokenizer cũ khi lưu, và kỳ vọng các khác biệt lặt vặt của tokenizer chậm sẽ hội tụ về hành vi dựa trên tokenizers.

Nếu bạn muốn tìm hiểu thêm về các bản phát hành gần đây, đây là ghi chú phát hành. Tôi cũng gợi ý lộ trình kỹ năng Hugging Face Fundamentals để thực hành trực tiếp.

Câu hỏi thường gặp về Tokenization v5

Tôi còn cần chọn giữa lớp tokenizer "Nhanh" (Rust) và "Chậm" (Python) không?

Không. Transformers v5 hợp nhất còn một triển khai duy nhất cho mỗi mô hình. Thư viện mặc định dùng backend Rust tối ưu cao (thư viện tokenizers) cho tất cả mô hình được hỗ trợ, loại bỏ nhu cầu chọn thủ công giữa LlamaTokenizerFast và LlamaTokenizer.

Mã encode_plus hiện tại của tôi có dừng hoạt động ngay không?

Chưa ngay lập tức, nhưng đã bị phản đối sử dụng. Dù v5 hiện có thể vẫn hỗ trợ encode_plus với cảnh báo, rất khuyến nghị chuyển sang phương thức gọi trực tiếp tokenizer(__call__). Điều này giúp bạn có hành vi batching hợp nhất mới và giúp mã nguồn sẵn sàng cho tương lai.

Vì sao apply_chat_template giờ trả về dict BatchEncoding thay vì chuỗi?

Để xử lý đầu vào chat như bất kỳ đầu vào mô hình nào khác. Bằng việc trả về một dictionary (chứa input_ids và attention_mask), v5 cho phép dữ liệu định dạng chat được batching, padding và cắt ngắn ngay lập tức mà không cần bước tokenization thứ hai.

Tôi còn có thể dùng return_tensors="tf" hoặc return_tensors="jax" không?

Không. Là một phần thay đổi kiến trúc v5, Transformers đã ngừng hỗ trợ chính thức backend TensorFlow và Flax/JAX để tập trung hoàn toàn vào PyTorch. Bạn sẽ cần dựa vào backend PyTorch hoặc xuất mô hình sang các định dạng như ONNX/GGUF cho các framework khác.

Làm sao debug các lỗi tokenization "im lặng" trong v5?

Hãy dùng phần nội bộ có thể quan sát. Trong v5, bạn có thể truy cập pipeline Rust nền tảng qua tokenizer._tokenizer. Điều này cho phép bạn kiểm tra từng giai đoạn—như Normalizer hoặc Pre-tokenizer—một cách độc lập để xem chính xác tại đâu chuỗi bị tách hay biến đổi ngoài ý muốn.

Aashi Dutt

Tác giả

Aashi Dutt

Tôi là Chuyên gia Google Developers trong lĩnh vực ML (Gen AI), Chuyên gia Kaggle 3x và Đại sứ Women Techmakers với hơn 3 năm kinh nghiệm trong ngành công nghệ. Tôi đồng sáng lập một startup công nghệ y tế vào năm 2020 và hiện đang theo học thạc sĩ khoa học máy tính tại Georgia Tech, chuyên sâu về học máy.

Chủ đề

Liên quan