Hôm nay mình sẽ nghiên cứu về Tokens.
Mình tò mò là làm sao để AI hiểu mình đang nói gì.
Trước hết mình sẽ theo chatGPT để viết đoạn code làm cái công việc y chang hôm qua dùng pipeline.
Phải biết rằng, máy móc không giao tiếp bằng ngôn ngữ như chúng ta, chúng chỉ là những con số được phiên qua ngôn ngữ tự nhiên thông qua một cuốn từ điển.
Mình có cuốn từ điển Tuna1 như sau: (chú ý, Hello ≠ hello)
| Chỉ số | Từ |
|---|---|
| 1 | Hello |
| 2 | My |
| 3 | name |
| 4 | is |
| 5 | Tuan |
| 6 | Tana |
Khi đó mình nhập vào:
Cái quá trình này là Token hóa (Tokenazier)
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
# transfer text to token - this is like function to transfer another text
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
#test - show the encoded text
text = "Hello, how are you"
encoded = tokenizer(text)
print(encoded)
print("input_ids ",encoded["input_ids"])
print("tokens: ", tokenizer.convert_ids_to_tokens(encoded["input_ids"]))
Với dòng tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) như việc mình lấy dữ liệu từ cuốn từ điển.
>>> python qwen_direct.py
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
{'input_ids': [23913, 21018, 15588, 305, 40], 'attention_mask': [1, 1, 1, 1, 1]}
input_ids [23913, 21018, 15588, 305, 40]
tokens: ['HI', 'ĠHi', 'Ġhi', 'Ġh', 'I']
Lúc này mình tò mò là từ con số sao người ta có thể biến thành từ qua hàm tokenizer.convert_ids_to_tokens được nên mình đã thử print(tokenizer.convert_ids_to_tokens([1,2,3])) được kết quả sau:
>>> print(tokenizer.convert_ids_to_tokens([1,2,3])
['"', '#', '$']
À hóa ra mỗi số đại diện cho một token khác nhau!
HI, Hi, hI, hiMình tiếp tục nghĩ rằng: Vậy thì HI, Hi, hI, hi khác gì nhau không?
>>> text = "HI Hi hi hI"
>>> encoded = tokenizer(text)
>>> print(tokenizer.convert_ids_to_tokens(encoded["input_ids")))
['HI', 'ĠHi', 'Ġhi', 'Ġh', 'I']
Bất ngờ chưa! 4 từ nhưng tách ra 5 tokens. Vậy là cơ chế hoạt động của token nó phải kiểu mỗi từ 1 token mà là chữ in hoa, dấu cách là 1 token chăng? Kiểu nó tách ‘Ġh’ và ‘I’ là 2 token khác nhau.
hI lại bị tách thành 2 token?Thực ra hI thành 2 token là do trong từ điển nó không có từ hI do bộ dữ liệu huấn luyện xuất hiện chưa thường xuyên. Vậy là token hóa phụ thuộc vào bộ “từ điển” của mô hình.
Ví dụ về bộ từ điển bị thiếu hI
| Từ | Token tương ứng | id |
|---|---|---|
| ‘h’ | ‘h’ | 1 |
| ‘ h’ | ‘Ġh’ | 2 |
| ‘I’ | ‘I’ | 3 |
| ‘hi’ | ‘hi’ | 4 |
Khi chương trình chạy thì tự động tải model về thôi, nhưng mà muốn dọn dẹp thì phải làm sao. Đây là vấn đề cũng cần được quan tâm khi học nhiều. Vì nếu cứ dồn lại thì dung lượng máy chắc chắn sẽ không đủ.
>hf cache ls
ID SIZE LAST_ACCESSED LAST_MODIFIED REFS
-------------------------------- ------ -------------- ------------- ----
model/5CD-AI/Vintern-1B-v2 1.9G 2 months ago 2 months ago main
model/Qwen/Qwen2.5-0.5B-Instruct 999.6M 16 minutes ago 18 hours ago main
model/Qwen/Qwen3-0.6B 726.0 4 hours ago 4 hours ago main
Found 3 repo(s) for a total of 3 revision(s) and 2.9G on disk.
hf cache rm model/Qwen/Qwen2.5-0.5B-Instruct
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype="auto",
device_map="auto",
)
tourch_dtype: kiểu dữ liệu để lưu trọng số. (float32, float64, bfloat32, auto - tự động chọn)
device_map: chọn thiết bị (CPU, GPU0, đa GPU, auto là tự chọn)
Chúng ta dùng lệnh này để nạp mô hình Qwen kia vào để sẵn sàng xài.
content = input("Enter input: ")
message = [
{"role" : "user",
"content" : content,
}
]
Trong đó message là list, còn {} là kiểu dictionary
list
└── dictionary
├── "role" → "user"
└── "content" → nội dung người dùng nhập
inputs = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
).to(model.device)
Khối lệnh này sẽ thực hiện chuỗi hành động sau:
messages
→ định dạng đúng kiểu hội thoại của Qwen
→ tách thành token
→ đổi token thành ID
→ đóng các ID vào PyTorch tensor
→ chuyển tensor sang cùng thiết bị với model
→ lưu kết quả vào inputs
.apply_chat_templatetokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
)
Đây là phương thức để đọc danh sách từ message, định dạng theo quy ước hội thoại của Qwen, tokenize kết quả nếu được yêu câu, trả về dữ liệu chuẩn để đưa vào model.
Dữ liệu khi đưa vào model phải giống với dữ liệu lúc huấn luyện cơ!
Tưởng tượng rằng bạn huấn luyện chú chó của bạn. Khi bạn vỗ tay, chú chó sẽ chạy lại, vậy khi bạn gõ kẻng thì chú chó sẽ chẳng làm gì. Tương tự với dữ liệu AI, bạn đưa vào không đúng với định dạng lúc huấn luyện thì nó cũng chẳng thể hiểu.
Giờ hãy xem một tin nhắn đơn giản được mô tả như sau
messages = [
{
"role": "user",
"content": "What is Python?",
},
]
Nhưng mô hình Qwen không đọc dữ liệu thế này, nó đọc dữ liệu được chuẩn hóa như sau
<|im_start|>user
What is Python?<|im_end|>
Các kí hiệu<|im_start|> và <|im_end|> giúp Qwen phân biệt ai đang nói (user, assistant), nội dung lời nói bắt đầu và kết thúc khi nào.
Một cuộc hội thoại sẽ trông như thế này
messages = [
{
"role": "system",
"content": "You are a helpful assistant.",
},
{
"role": "user",
"content": "What is Python?",
},
{
"role": "assistant",
"content": "Python is a programming language.",
},
{
"role": "user",
"content": "Who created it?",
},
]
Sau khi chuẩn hóa nó sẽ thế này
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
What is Python?<|im_end|>
<|im_start|>assistant
Python is a programming language.<|im_end|>
<|im_start|>user
Who created it?<|im_end|>
<|im_start|>assistant
Để ý thấy dòng cuối có <|im_start|>assistant là được thêm tự động do có dòng add_generation_prompt=True. Nghĩa là báo cho Qwen phần tiếp theo do assistant viết.
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
# transfer text to token - this is like function to transfer another text
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
# load model
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype="auto",
device_map="auto",
)
# get user input
content = input("Enter input: ")
message = [
{"role" : "user",
"content" : content,
}
]
# convert conversation into tensor which model can be understood
inputs = tokenizer.apply_chat_template(
message,
add_generation_prompt=True,
tokenize=False,
)
print(inputs)
Sau đó nhập input = Hi, I’m Tuna ta được kết quả sau:
Enter input: Hi, I'm Tuna
<|im_start|>system
You are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>
<|im_start|>user
Hi, I'm Tuna<|im_end|>
<|im_start|>assistant
Thật ra khi dùng apply_chat_template thì mặc định tokenize = true, thế nên ở chương trình trên, mình đặt tokenize = false để quan sát văn bản thô. À nhớ đạt return_dict=False để xem kết quả nhá!
inputs = tokenizer.apply_chat_template(
message,
add_generation_prompt=True,
tokenize=True, # you can remove this line
return_dict=False,
)
Với cùng input = Hi, I’m Tuna thì sẽ được kết quả:
Enter input: Hi, I'm Tuna
[151644, 8948, 198, 2610, 525, 1207, 16948, 11, 3465, 553, 54364, 14817, 13, 1446, 525, 264, 10950, 17847, 13, 151645, 198, 151644, 872, 198, 13048, 11, 358, 2776, 350, 8565, 151645, 198, 151644, 77091, 198]
Ta sửa dòng return_dict=True để chuyển thành cấu trúc giống dictionary:
{
"input_ids": ...,
"attention_mask": ...
}
Kết quả:
Enter input: Hi, I'm Tuna
{'input_ids': [151644, 8948, 198, 2610, 525, 1207, 16948, 11, 3465, 553, 54364, 14817, 13, 1446, 525, 264, 10950, 17847, 13, 151645, 198, 151644, 872, 198, 13048, 11, 358, 2776, 350, 8565, 151645, 198, 151644, 77091, 198], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
Ta thêm dòng return_tensors="pt” để chuyển kết quả thành PyTorch tensor. Ngoài ra còn có thể đóng gói dữ liệu này dưới nhiều dạng khác:
return_tensors="pt" # PyTorch Tensor
return_tensors="tf" # TensorFlow Tensor
return_tensors="np" # NumPy Array
Sau khi thêm return_tensors=”pt”, ta được:
{'input_ids': tensor([[151644, 8948, 198, 2610, 525, 1207, 16948, 11, 3465,
553, 54364, 14817, 13, 1446, 525, 264, 10950, 17847,
13, 151645, 198, 151644, 872, 198, 13048, 11, 358,
2776, 350, 8565, 151645, 198, 151644, 77091, 198]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, , , ,
, , , , , , , , , , ]])}
inputs = inputs.to(model.device)
Lệnh này giúp đưa cái inputs chuyển vào nơi chứa model để model xử lí. Tưởng tượng bạn muốn nhờ vả anh A thì bạn phải đem đống công việc bạn cần nhờ qua nhà anh A.
generated_ids = model.generate(
**inputs,
max_new_tokens=50,
do_sample=False,
)
Sau khi prompt, đây là cách mô hình nhìn vào input. Ví dụ nếu input là thế này
<|im_start|>user
What is Python?<|im_end|>
<|im_start|>assistant
Thì AI sẽ sinh từ để nối vào chuỗi
Prompt -> Python -> is -> ...
Quá trình này sẽ dừng khi đạt max_new_tokens hoặc gặp kí tự kết thúc <|im_end|>
<|im_start|>user
What is Python?<|im_end|>
<|im_start|>assistant
Python is a morden ... <|im_end|>
model.generate(**inputs)
Tương đương với
model.generate(
input_ids=inputs["input_ids"],
attention_mask=inputs["attention_mask"],
)
Nghĩa là mở cái dictionary thành các tham số riêng biệt.
Trong đó:
input_length = inputs["input_ids"].shape[-1]
answer_ids = generated_ids[0][input_length:]
Giờ ta có input là thế này:
<|im_start|>user
What is Python?<|im_end|>
<|im_start|>assistant
Và output thế này:
<|im_start|>user
What is Python?<|im_end|>
<|im_start|>assistant
Python is a morden ... <|im_end|>
Nhưng ta chỉ cần lấy khúc
Python is a morden ... <|im_end|>
Vậy là ta sẽ lấy output bỏ đi phần input là ra phần answer!
Đầu tiên ta cần xem cái input nó dài bao nhiêu
input_length = inputs["input_ids"].shape[-1]
Khi ta dùng .shapethì sẽ trả về một torch.Size([])
# torch.Size([1, 23])
# ↑ ↑
# batch seq_len (số token trong prompt)
Nên lấy .shape[-1] để lấy số token trong prompt.
in kết quả của generated_ids[0] ta được như bên dưới.
Enter input: What is Python?
tensor([151644, 8948, 198, 2610, 525, 1207, 16948, 11, 3465,
553, 54364, 14817, 13, 1446, 525, 264, 10950, 17847,
13, 151645, 198, 151644, 872, 198, 3838, 374, 13027,
30, 151645, 198, 151644, 77091, 198, 30280, 374, 264,
1550, 11591, 15473, 4128, 429, 572, 7881, 553, 12157,
78, 5242, 16465, 1242, 323, 1156, 5880, 304, 220,
, , , , , , , , ,
, , , , , , , , ,
, , , , , , , , ,
, ], device=)
Giờ ta lấy phần tử bắt đầu từ sau kích thước là oke.
seq[start:end] # lấy từ start đến end-1
seq[start:] # lấy từ start đến hết ← đây là case của mình
seq[:end] # lấy từ đầu đến end-1
seq[:] # lấy toàn bộ
Ta lấy từ ví trí input_length
answer_ids = generated_ids[0][input_length:]
answer = tokenizer.decode(
answer_ids,
skip_special_tokens=True,
)
Cái dòng skip_special_tokens=True để xóa các kí tự đặc biệt như <|im_start|>, <|im_end|>
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
# transfer text to token - this is like function to transfer another text
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
# load model
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype="auto",
device_map="auto",
)
# get user input
content = input("Enter input: ")
message = [
{"role" : "user",
"content" : content,
}
]
# convert conversation into tensor which model can be understood
inputs = tokenizer.apply_chat_template(
message,
add_generation_prompt=True,
tokenize=True,
return_dict=True,
return_tensors="pt",
)
# move inputs into the place that model is in
inputs = inputs.to(model.device)
# create answer
generated_ids = model.generate(
**inputs,
max_new_tokens=50,
do_sample=False,
)
# get answer only - cut input, prompt
input_length = inputs["input_ids"].shape[-1]
answer_ids = generated_ids[0][input_length:]
# convert token to text
answer = tokenizer.decode(
answer_ids,
skip_special_tokens = True, #help remove <|im_start|>, <|im_end|>
)
print("AI: ", answer)
Trong hôm nay mình đã viết được hoàn chỉnh chương trình để có thể load một mô hình đã được huấn luyện để có thể hỏi và trả lời. Đó là bước tiến lớn để hiểu hơn cơ chế token, cách AI đọc yêu cầu cũng như cách mà AI sinh ra câu trả lời.
Loading comments...