赞
踩
tokenizer的model不同于Transformer的model,Transformer的model是一些权重参数,tokenizer的model是一些分词,将一个句子分成分词的中介(模型)。,具体就是tokenizer.py文件打印出来的如token数组所示
Tokenizer tokenizer;
build_tokenizer(&tokenizer, tokenizer_path, transformer.config.vocab_size);
是从tokenizer_path把Tokenizer 结构体中的内容填满
(gdb) print tokenizer
$1 = {vocab = 0x0, vocab_scores = 0x0, sorted_vocab = 0x0, vocab_size = 0, max_token_length = 0, byte_pieces = '\000' <repeats 511 times>}
(gdb) print *t->byte_pieces@5 //输出数组数值要加*
$17 = "\000\000\001\000\002"
一个数字和一个0搭配好
(gdb) print t->byte_pieces[0]
$11 = 0 '\000'
(gdb) print t->byte_pieces[1]
$12 = 0 '\000'
(gdb) print t->byte_pieces[2]
$9 = 1 '\001'
(gdb) print t->byte_pieces[3]
$10 = 0 '\000'
if (fread(t->vocab_scores + i, sizeof(float), 1, file) != 1) { fprintf(stderr, "failed read\n"); exit(EXIT_FAILURE);}
if (fread(&len, sizeof(int), 1, file) != 1) { fprintf(stderr, "failed read\n"); exit(EXIT_FAILURE); }
if (fread(t->vocab[i], len, 1, file) != 1) { fprintf(stderr, "failed read\n"); exit(EXIT_FAILURE); }
t->vocab[i][len] = '\0'; // add the string terminating token
函数
“fread函数原型
size_t fread ( void * buffer , size_t size , size_t count , FILE * stream ) ;
fread参数
buffer
用于接收数据的内存地址
size
要读的每个数据项的字节数,单位是字节
count
要读count个数据项,每个数据项size个字节.
stream
输入流”
fread() 函数内部会自动处理文件指针的位置移动,也即是下一个地址
(gdb) print tokenizer
$30 = {vocab = 0x7f3c2cdbc010, vocab_scores = 0x559d23c249e0, sorted_vocab = 0x0, vocab_size = 32000, max_token_length = 27,
byte_pieces = "\000\000\001\000\002\000\003\000\004\000\005\000\006\000\a\000\b\000\t\000\n\000\v\000\f\000\r\000\016\000\017\000\020\000\021\000\022\000\023\000\024\000\025\000\026\000\027\000\030\000\031\000\032\000\033\000\034\000\035\000\036\000\037\000 \000!\000\"\000#\000$\000%\000&\000'\000(\000)\000*\000+\000,\000-\000.\000/\000\060\000\061\000\062\000\063\000\064\000\065\000\066\000\067\000\070\000\071\000:\000;\000<\000=\000>\000?\000@\000A\000B\000C\000D\000E\000F\000G\000H\000I\000J\000K\000L\000M\000N\000O\000P\000Q\000R\000S\000T\000U\000V\000W\000X\000Y\000Z\000[\000\\\000]\000^\000_\000`\000a\000b\000c\000"...}
具体地,
//地址和内容
(gdb) p *tokenizer->vocab@10
$33 = {0x559d23c44e00 "<unk>", 0x559d23c44e20 "\n<s>\n", 0x559d23c44e40 "\n</s>\n", 0x559d23c44e60 "<0x00>", 0x559d23c44e80 "<0x01>", 0x559d23c44ea0 "<0x02>",
0x559d23c44ec0 "<0x03>", 0x559d23c44ee0 "<0x04>", 0x559d23c44f00 "<0x05>", 0x559d23c44f20 "<0x06>"}
(gdb) p tokenizer->vocab[0]
$32 = 0x559d23c44e00 "<unk>"
(gdb) p tokenizer->vocab[1]
$36 = 0x559d23c44e20 "\n<s>\n"
(gdb) p tokenizer->vocab[2]
$37 = 0x559d23c44e40 "\n</s>\n"
tokens[20100:2100]
[b' \xd0\xbc\xd1\x96\xd0\xb6', b'\xc3\xa9bec', b' clip', b' Nice', b' neben', b' assass', b'itories', b' unity', b' \xd0\xb5\xd0\xbd', b' Institut', b' internationale', b' \xd0\xbd\xd0\xb0\xd1\x83\xd0\xba', b' comand', b' kleine', ...]
tokens[0:10]
[b'<unk>', b'\n<s>\n', b'\n</s>\n', b'<0x00>', b'<0x01>', b'<0x02>', b'<0x03>', b'<0x04>', b'<0x05>', b'<0x06>']
with open(tokenizer_bin, 'wb') as f:
f.write(struct.pack("I", max_token_length)) # 写入最长的token
for bytes, score in zip(tokens, scores):
f.write(struct.pack("fI", score, len(bytes))) # 写入每个词元的得分、词元长度以及实际字节内容
f.write(bytes)
from sentencepiece import SentencePieceProcessor
model_path = "/mnt/workspace/llama2.c/tokenizer.model"
sp_model = SentencePieceProcessor(model_file=model_path)
mm = sp_model.EncodeAsPieces("I love you, baby")
nn = sp_model.encode_as_ids("I love you, baby")
print("分词:",mm)
print("编码",nn)
print("解码",sp_model.decode_ids([306, 5360, 366, 29892, 24354]))
(base) /mnt/workspace> python /mnt/workspace/llama2.c/test_sen.py
分词: ['▁I', '▁love', '▁you', ',', '▁baby']
编码 [306, 5360, 366, 29892, 24354]
解码 I love you, baby
void generate(Transformer *transformer, Tokenizer *tokenizer, Sampler *sampler, char *prompt, int steps)
参数解释:
三个被填充好内容的结构体:*transformer,*tokenizer,*sampler
(gdb) print steps
$4 = 256
(gdb) print prompt
$5 = 0x561f3e0313b6 "Hello"
调用
encode(tokenizer, prompt, 1, 0, prompt_tokens, &num_prompt_tokens);
定义
void encode(Tokenizer* t, char *text, int8_t bos, int8_t eos, int *tokens, int *n_tokens)
参数解释:1为bos,0为eos, int *tokens为prompt分配的内存空间,int *n_tokens为0
S1:将bin文件读进来赋值给t->sorted_vocab,包括string,id,然后进行排序
其中,分配空间sizeof(TokenIndex)
typedef struct {
char *str;
int id;
} TokenIndex;
排序后的t->sorted_vocab
(gdb) print *t->sorted_vocab@10
$11 = {{str = 0x561f3e49fe40 "\n</s>\n", id = 2}, {str = 0x561f3e49fe20 "\n<s>\n", id = 1}, {str = 0x561f3e58a4b0 "\r", id = 30004}, {str = 0x561f3e589410 " ",
id = 29871}, {str = 0x561f3e4d4a80 " \r", id = 6756}, {str = 0x561f3e4a1e60 " ", id = 259}, {str = 0x561f3e4acfc0 " ", id = 1678}, {
str = 0x561f3e4a1f80 " ", id = 268}, {str = 0x561f3e4a3240 " ", id = 418}, {str = 0x561f3e4a4160 " ", id = 539}}
gdb) print t->sorted_vocab[0]
$12 = {str = 0x561f3e49fe40 "\n</s>\n", id = 2}
(gdb) print t->sorted_vocab[1]
$13 = {str = 0x561f3e49fe20 "\n<s>\n", id = 1}
(gdb) print t->sorted_vocab[2]
$14 = {str = 0x561f3e58a4b0 "\r", id = 30004}
S2:初始化tokens(专门存放整数的),有bos,tokens第一个数赋值为1;如果test[0]不是结束符,就将空格的id给tokens数的第二个数。其中,n_tokens作为tokens的索引再变化。
部分代码就是如下
1)tokens
不确定的初始值
(gdb) print *tokens@10
$25 = {-1820597504, 32645, -1820597504, 32645,
1554046432, 22025, 1554046432, 22025,
1207995313, 247541}
有bos,将第一个元素赋为1,第二个仍为随机值
if (bos) tokens[(*n_tokens)++] = 1;
(gdb) print tokens[0]
$27 = 1
(gdb) print tokens[1]
$28 = 32645
if (text[0] != '\0') {
int dummy_prefix = str_lookup(" ", t->sorted_vocab, t->vocab_size);
tokens[(*n_tokens)++] = dummy_prefix;
}
(gdb) print dummy_prefix
$29 = 29871
打印排序前的vocab,对应的起。
(gdb) p t->vocab[29871]
$33 = 0x56095caf8410 " "
S3:遍历输入字符串,如果不是延续字符,将“字符+\0”的形式从索引0开始存到str_buffer中;判断后面的字符是不是延续字符,是,继续加到str_buffer中。(保证str_buffer的格式:要么是只有一个非延续字符,要么是非延续字符+延续字符??)不是,返回当前字符的id,赋给tokens。(将Hello分成一个个字符搞的对应id,也就是tokens里的数值)
部分代码解释如下:
(gdb) print *c
$34 = 72 'H'
(*c & 0xC0) != 0x80
是一段在处理UTF-8编码时常见的位运算表达式,用于检查给定的字符(指向字符的指针 c)是否是一个UTF-8编码中的延续字节。
在UTF-8编码中,每个Unicode码点(字符)被编码为1到4个字节。每个字节的第一个比特位决定了该字节的角色:
首字节(Leading byte):它以 “11xxxxxx” 的形式开始,其中 x 表示可变位。
延续字节(Continuationbyte):它们以 “10xxxxxx” 的形式开始。
xC0 的二进制表示是 11000000
如果这两个比特位不等于 0x80(即二进制的 10000000),那么我们可以确定当前字符不是延续字节,(是延续字节的话,那么是10&11=10,那么就是0x80了), 而是一个首字节或ASCII字符。
> 当运行为Hello中的H
依然对得齐
(gdb) print *tokens@3
$5 = {1, 29871, 29950}
(gdb) p t->vocab[29871]
$33 = 0x56095caf8410 " "
(gdb) p t->vocab[29950]
$6 = 0x558aa1290df0 "H"
到此Hello都有个token存到tokens中。如下:
(gdb) print *tokens@8
$13 = {1, 29871, 29950, 29872, 29880, 29880,
29877, 21898}
(gdb) p t->vocab[29872]
$10 = 0x558aa1290430 "e"
(gdb) p t->vocab[29880]
$11 = 0x558aa1290530 "l"
(gdb) p t->vocab[29877]
$14 = 0x558aa12904d0 "o"
S4:合并,根据 vocab_scores 中的得分合并最佳连续对,如果有合并,那么tokens肯定是要变化的。具体地,根据tokens[i], tokens[i+1]在 t->vocab找到字符串,并将两个存到str_buffer。然后,去t->sorted_vocab找,看有没有。没有继续循环
部分代码如下:
for (int i=0; i < (*n_tokens-1); i++) (gdb) print *n_tokens $16 = 7``` ```c sprintf(str_buffer, "%s%s", t->vocab[tokens[i]], t->vocab[tokens[i+1]]); 将tokens里的连续索引,如i=0时,tokens[i]和tokens[i+1]分别为1,29871,如上面S3输出两个对应值,如下: (gdb) p t->vocab[1] $12 = 0x562c6aa9de20 "\n<s>\n" (gdb) p t->vocab[29871] $21 = 0x558aa1290410 " " (gdb) print str_buffer@5 $6 = {0x562c6aa9ce20 "\n<s>\n ", 0x7ffedb73e490 "`\350s\333\376\177", 0x562c68fda57f <generate+152> "\213E\270\205\300\177-H\213\005\263:", 0x3e800 <error: Cannot access memory at address 0x3e800>, 0x10000003e7f <error: Cannot access memory at address 0x10000003e7f>} 这个一看在sorted_vocab没有
S4_1:有的话,如下
i=1,空格+H
(gdb) print str_buffer@5
$15 = {0x562c6aa9ce20 " H", 0x7ffedb73e490 “`\350s\333\376\177”,
(gdb) print id
$16 = 379
确实有
(gdb) p t->vocab[379]
$21 = 0x562c6aaa0d60 " H"
(gdb) p t->vocab_scores[id]
$22 = -120
然后,判断分数,由于在vocab中,一个字符串和分数是配套好的,如果分数>best_score
那么将分数、id、idx都赋给best,记录分数和位置。
S_5:改变tokens的i个数和数值,While(1)是将Hello有一个合并一个,多个就合并最后一个,一个就合并哪一个,知道没有为止
// merge the consecutive pair (best_idx, best_idx+1) into new token best_id
tokens[best_idx] = best_id;
// delete token at position best_idx+1, shift the entire sequence back 1
for (int i = best_idx+1; i < (*n_tokens-1); i++) {
tokens[i] = tokens[i+1]; //best_idx+1后面的全部前移一个,那么n_token也减少一个
}
(*n_tokens)--; // token length decreased
}
这儿代码好像只对最后一对起效果??
best_id就是tokens里面的数值
best_idx是tokens里的索引
(gdb) p best_id
$26 = 295
(gdb) p best_idx
$25 = 3
原来的tokens
(gdb) print *tokens@8
$13 = {1, 29871, 29950, 29872, 29880, 29880,
29877, 21898}
(gdb) p tokens[3]
$5 = 29872
代码更新后,
tokens[3] = 295;
(gdb) print *tokens@8
$7 = {1, 29871, 29950, 295, 29880, 29880, 29877, 21972}
(gdb) p t->vocab[295]
$8 = 0x55d42b07b2e0 "el"
for (int i = best_idx+1; i < (*n_tokens-1); i++) {
tokens[i] = tokens[i+1];
}
$9 = {1, 29871, 29950, 295, 29880, 29877, 29877, 21972} 少tokens[4],即29880
我没有关tokens多大哈
现在是H el l o了
(gdb) p t->vocab[29950]
$14 = 0x55d42b162df0 "H"
(gdb) p t->vocab[295]
$15 = 0x55d42b07b2e0 "el"
(gdb) p t->vocab[29880]
$16 = 0x55d42b162530 "l"
(gdb) p t->vocab[29877]
$18 = 0x55d42b1624d0 "o"
继续
这段代码是在执行某种合并操作,例如在自定义词元生成算法中(如Byte Pair Encoding, BPE),将连续的两个词元合并成一个新的词元。下面是一个具体的例子来说明这段代码的作用:
假设我们有一个由小到大排序后的词元序列 tokens
,当前包含以下内容:
tokens = [ "a", "b", "c", "ab", "cd", "ef" ]
n_tokens = 6
现在,在某个迭代中,找到了最佳要合并的连续对 (best_idx, best_idx+1)
,比如是索引为2和3的位置,即 "c"
和 "ab"
,它们合并后的新词元 ID 是 best_id
,值为 "cab"
。
执行上述代码段之后:
将合并后的新词元ID替换原序列中的第一个词元:
tokens[best_idx] = best_id; // tokens[2] = "cab"
更新后的 tokens
序列为:["a", "b", "cab", "cd", "ef"]
删除合并后不再需要的第二个词元,并将后面的词元向前移动一位://tokens[i]被后面的覆盖,因此没了
for (int i = best_idx+1; i < (*n_tokens-1); i++) {
tokens[i] = tokens[i+1];
}
执行循环后,tokens
序列变为:["a", "b", "cd", "ef", "ef"]
减少词元数量计数器:
(*n_tokens)--; // token length decreased
现在 n_tokens
的值变为5,表示序列中剩余的有效词元数量。
最终更新后的词元序列是:["a", "b", "cd", "ef"]
。通过这样的合并操作,可以逐步构建出一个更复杂的词汇表或编码方案。
一直到输出
(gdb) p *n_tokens
$2 = 2
(gdb) p *tokens@2
$4 = {1, 15043}
(gdb) p t->vocab[15043]
$5 = 0x559ff19aa660 " Hello"
(gdb) p t->vocab[1]
$9 = 0x559ff1934e20 "\n<s>\n"
还有个结束的,
`if (eos) tokens[(*n_tokens)++] = 2;`
**
**
(gdb) p num_prompt_tokens
$4 = 2
(gdb) p *prompt_tokens@2
$3 = {1, 15043}
(gdb) p tokenizer->vocab[1]
$8 = 0x55dad3df3e20 "\n<s>\n"
(gdb) p tokenizer->vocab[15043]
$5 = 0x55dad3e69660 " Hello"
参考链接:
https://blog.csdn.net/qq_27149279/article/details/131976119
https://mp.weixin.qq.com/s/WqhYL_k_JAUzvI3mQtRhMw
https://blog.csdn.net/qq_37771209/article/details/127664462
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。