Skip to content
89 changes: 75 additions & 14 deletions megatron/tokenizer/gpt2_tokenization.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def bytes_to_unicode():

def get_pairs(word):
"""Return set of symbol pairs in a word.

Word is represented as tuple of symbols (symbols being variable-length strings).
"""
pairs = set()
Expand Down Expand Up @@ -164,8 +163,8 @@ def __init__(self, vocab_file, merges_file, errors='replace',
bpe_data = open(merges_file, encoding='utf-8').read().split('\n')[1:-1]
bpe_merges = [tuple(merge.split()) for merge in bpe_data]
self.bpe_ranks = dict(zip(bpe_merges, range(len(bpe_merges))))
self.cache = {}

#self.cache = {}
#self.normalized_cache = {}
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
# Should haved added re.IGNORECASE so BPE merges can happen for
# capitalized versions of contractions
self.pat = re.compile(
Expand All @@ -191,10 +190,11 @@ def set_special_tokens(self, special_tokens):
for i, tok in enumerate(special_tokens))
self.special_tokens_decoder = {v: k for k, v in self.special_tokens.items()}
logger.info("Special tokens {}".format(self.special_tokens))


@lru_cache(1000000)
def bpe(self, token):
if token in self.cache:
return self.cache[token]
#if token in self.cache:
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
# return self.cache[token]
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
word = tuple(token)
pairs = get_pairs(word)

Expand Down Expand Up @@ -230,20 +230,81 @@ def bpe(self, token):
else:
pairs = get_pairs(word)
word = ' '.join(word)
self.cache[token] = word
#self.cache[token] = word
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
return word

def tokenize(self, text):

# copied from https://github.com/huggingface/transformers/blob/master/src/transformers/models/bert/tokenization_bert.py
def _tokenize_chinese_chars(self, text):
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
"""Adds whitespace around any CJK character."""
# original note from the Bert Tokenizer:
# This was added on November 1st, 2018 for the multilingual and Chinese
# models. This is also applied to the English models now, but it doesn't
# matter since the English models were not trained on any Chinese data
# and generally don't have any Chinese data in them (there are Chinese
# characters in the vocabulary because Wikipedia does have some Chinese
# words in the English Wikipedia.).
output = []
for char in text:
cp = ord(char)
if self._is_chinese_char(cp):
output.append(" ")
output.append(char)
output.append(" ")
else:
output.append(char)
return "".join(output)

#copied from https://github.com/huggingface/transformers/blob/master/src/transformers/models/bert/tokenization_bert.py
def _is_chinese_char(self, cp):
"""Checks whether CP is the codepoint of a CJK character."""
# This defines a "chinese character" as anything in the CJK Unicode block:
# https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_(Unicode_block)
#
# Note that the CJK Unicode block is NOT all Japanese and Korean characters,
# despite its name. The modern Korean Hangul alphabet is a different block,
# as is Japanese Hiragana and Katakana. Those alphabets are used to write
# space-separated words, so they are not treated specially and handled
# like the all of the other languages.
if (
(cp >= 0x4E00 and cp <= 0x9FFF)
or (cp >= 0x3400 and cp <= 0x4DBF) #
or (cp >= 0x20000 and cp <= 0x2A6DF) #
or (cp >= 0x2A700 and cp <= 0x2B73F) #
or (cp >= 0x2B740 and cp <= 0x2B81F) #
or (cp >= 0x2B820 and cp <= 0x2CEAF) #
or (cp >= 0xF900 and cp <= 0xFAFF)
or (cp >= 0x2F800 and cp <= 0x2FA1F) #
): #
return True

return False

@lru_cache(1000000)
def normalize_token_py3(self, token):
token = ''.join(self.byte_encoder[b] for b in token.encode('utf-8'))
ret = [bpe_token for bpe_token in self.bpe(token).split(' ')]
return ret

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def normalize_token_py3(self, token):
token = ''.join(self.byte_encoder[b] for b in token.encode('utf-8'))
ret = [bpe_token for bpe_token in self.bpe(token).split(' ')]
return ret
def normalize_token(self, token):
# I think it's safe to say that sys.version_info[0] is constant across the whole run.
if sys.version_info[0] != 2:
token = ''.join(self.byte_encoder[ord(b)] for b in token)
else:
token = ''.join(self.byte_encoder[b] for b in token.encode('utf-8'))
ret = [bpe_token for bpe_token in self.bpe(token).split(' ')]
return ret

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the if statement being outside the for loop is better.


def tokenize(self, text, max_token_len_cache=9, tokenize_chinese_chars=False):
""" Tokenize a string. """
bpe_tokens = []
if sys.version_info[0] == 2:
if tokenize_chinese_chars:
text = self._tokenize_chinese_chars(text)
for token in re.findall(self.pat, text):
token = ''.join(self.byte_encoder[ord(b)] for b in token)
bpe_tokens.extend(bpe_token for bpe_token in self.bpe(token).split(' '))
Comment on lines +253 to +254

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
token = ''.join(self.byte_encoder[ord(b)] for b in token)
bpe_tokens.extend(bpe_token for bpe_token in self.bpe(token).split(' '))
bpe_tokens.extend(self.normalize_token(token))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the if statement being outside the for loop is better.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a better version would just to ignore that case since python 2 is not supported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but then it would be inconsistent since we STILL have code that tests for python2.

@stas00 stas00 Aug 4, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove that code and anywhere else where similar branches are found?

return bpe_tokens
for token in re.findall(self.pat, text):
if sys.version_info[0] == 2:
token = ''.join(self.byte_encoder[ord(b)] for b in token)
else:
if tokenize_chinese_chars:
text = self._tokenize_chinese_chars(text)
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
if len(token) <= max_token_len_cache:
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
bpe_tokens.extend(self.normalize_token_py3(token))
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
else:
token = ''.join(self.byte_encoder[b] for b in token.encode('utf-8'))
bpe_tokens.extend(bpe_token for bpe_token in self.bpe(token).split(' '))
bpe_tokens.extend(bpe_token for bpe_token in self.bpe(token).split(' '))
Comment thread
huu4ontocord marked this conversation as resolved.
Outdated
return bpe_tokens

def convert_tokens_to_ids(self, tokens):
""" Converts a sequence of tokens into ids using the vocab. """
ids = []
Expand Down