Рефакторинг: единообразие оформления кода (пробелы, кавычки, пустые строки), без изменения логики по всему проекту.

This commit is contained in:
Sergey Penkovsky
2025-10-06 22:57:19 +03:00
parent 332cad6159
commit 712278e33c
49 changed files with 2324 additions and 2004 deletions

View File

@@ -12,84 +12,82 @@ class HFTokenizerAdapter:
Упрощенный адаптер для кастомных токенизаторов llm.
Предоставляет совместимый с HuggingFace интерфейс.
"""
def __init__(self, llm_tokenizer: BaseTokenizer):
"""
Инициализация адаптера.
Args:
llm_tokenizer: Кастомный токенизатор из llm
"""
self.llm_tokenizer = llm_tokenizer
# Получаем словарь и размер
self._vocab = llm_tokenizer.get_vocab()
self.vocab_size = llm_tokenizer.get_vocab_size()
# Устанавливаем специальные токены
self.pad_token = getattr(llm_tokenizer, 'pad_token', '<pad>')
self.unk_token = getattr(llm_tokenizer, 'unk_token', '<unk>')
self.bos_token = getattr(llm_tokenizer, 'bos_token', '<bos>')
self.eos_token = getattr(llm_tokenizer, 'eos_token', '<eos>')
self.pad_token = getattr(llm_tokenizer, "pad_token", "<pad>")
self.unk_token = getattr(llm_tokenizer, "unk_token", "<unk>")
self.bos_token = getattr(llm_tokenizer, "bos_token", "<bos>")
self.eos_token = getattr(llm_tokenizer, "eos_token", "<eos>")
# Сохраняем ID специальных токенов
self.pad_token_id = getattr(llm_tokenizer, 'pad_token_id', 0)
self.unk_token_id = getattr(llm_tokenizer, 'unk_token_id', 1)
self.bos_token_id = getattr(llm_tokenizer, 'bos_token_id', 2)
self.eos_token_id = getattr(llm_tokenizer, 'eos_token_id', 3)
self.pad_token_id = getattr(llm_tokenizer, "pad_token_id", 0)
self.unk_token_id = getattr(llm_tokenizer, "unk_token_id", 1)
self.bos_token_id = getattr(llm_tokenizer, "bos_token_id", 2)
self.eos_token_id = getattr(llm_tokenizer, "eos_token_id", 3)
def __call__(self, text: str, **kwargs):
"""
Вызов токенизатора с параметрами как у HuggingFace.
Args:
text: Входной текст
**kwargs: Параметры токенизации
Returns:
dict: Словарь с токенами
"""
return_tensors = kwargs.get('return_tensors', None)
padding = kwargs.get('padding', False)
truncation = kwargs.get('truncation', False)
max_length = kwargs.get('max_length', None)
add_special_tokens = kwargs.get('add_special_tokens', True)
return_tensors = kwargs.get("return_tensors", None)
padding = kwargs.get("padding", False)
truncation = kwargs.get("truncation", False)
max_length = kwargs.get("max_length", None)
add_special_tokens = kwargs.get("add_special_tokens", True)
# Кодируем текст
#input_ids = self.llm_tokenizer.encode(
# text,
# input_ids = self.llm_tokenizer.encode(
# text,
# add_special_tokens=add_special_tokens
#)
# )
if isinstance(text, str):
input_ids = self.llm_tokenizer.encode(
text,
add_special_tokens=add_special_tokens
text, add_special_tokens=add_special_tokens
)
input_ids = [input_ids] # <-- оборачиваем в batch
else:
# Список строк, батч-режим!
input_ids = [
self.llm_tokenizer.encode(
t,
add_special_tokens=add_special_tokens
) for t in text
self.llm_tokenizer.encode(t, add_special_tokens=add_special_tokens)
for t in text
]
# Применяем truncation
if truncation and max_length is not None and len(input_ids) > max_length:
input_ids = input_ids[:max_length]
# Применяем padding
if padding and max_length is not None and len(input_ids) < max_length:
input_ids = input_ids + [self.pad_token_id] * (max_length - len(input_ids))
# Конвертируем в тензоры если нужно
if return_tensors == "pt":
import torch
input_ids = torch.tensor([input_ids])
return {"input_ids": input_ids}
def encode(
self,
text: str,
@@ -99,11 +97,11 @@ class HFTokenizerAdapter:
truncation: bool = False,
max_length: Optional[int] = None,
return_tensors: Optional[str] = None,
**kwargs
**kwargs,
) -> Union[List[int], List[List[int]]]:
"""
Кодирует текст в последовательность токенов.
Args:
text: Входной текст
text_pair: Второй текст (для парных задач)
@@ -112,84 +110,91 @@ class HFTokenizerAdapter:
truncation: Обрезать последовательность
max_length: Максимальная длина
return_tensors: Возвращать тензоры
Returns:
Список токенов или список списков токенов
"""
# Кодируем основной текст
token_ids = self.llm_tokenizer.encode(
text,
add_special_tokens=add_special_tokens
text, add_special_tokens=add_special_tokens
)
# Обрабатываем text_pair если есть
if text_pair is not None:
pair_ids = self.llm_tokenizer.encode(
text_pair,
add_special_tokens=False
)
pair_ids = self.llm_tokenizer.encode(text_pair, add_special_tokens=False)
token_ids.extend(pair_ids)
# Применяем truncation
if truncation and max_length is not None and len(token_ids) > max_length:
token_ids = token_ids[:max_length]
# Применяем padding
if padding and max_length is not None and len(token_ids) < max_length:
token_ids = token_ids + [self.pad_token_id] * (max_length - len(token_ids))
# Конвертируем в тензоры если нужно
if return_tensors == "pt":
import torch
return torch.tensor([token_ids])
elif return_tensors == "np":
import numpy as np
return np.array([token_ids])
return token_ids
def decode(
self,
token_ids: Union[int, List[int], List[List[int]]],
skip_special_tokens: bool = True,
**kwargs
**kwargs,
) -> str:
"""
Декодирует последовательность токенов в текст.
Args:
token_ids: ID токенов
skip_special_tokens: Пропускать специальные токены
Returns:
str: Декодированный текст
"""
# Обрабатываем разные форматы входных данных
if isinstance(token_ids, int):
token_ids = [token_ids]
elif isinstance(token_ids, list) and len(token_ids) > 0 and isinstance(token_ids[0], list):
elif (
isinstance(token_ids, list)
and len(token_ids) > 0
and isinstance(token_ids[0], list)
):
# Список списков - берем первый элемент
token_ids = token_ids[0]
# Фильтруем специальные токены если нужно
if skip_special_tokens:
special_ids = {self.pad_token_id, self.unk_token_id, self.bos_token_id, self.eos_token_id}
special_ids = {
self.pad_token_id,
self.unk_token_id,
self.bos_token_id,
self.eos_token_id,
}
token_ids = [tid for tid in token_ids if tid not in special_ids]
return self.llm_tokenizer.decode(token_ids)
def tokenize(self, text: str, **kwargs) -> List[str]:
"""
Токенизирует текст в список строковых токенов.
Args:
text: Входной текст
Returns:
List[str]: Список токенов
"""
return self.llm_tokenizer.tokenize(text)
def pad(
self,
encoded_inputs,
@@ -202,7 +207,7 @@ class HFTokenizerAdapter:
):
"""
Pad a list of encoded inputs.
Args:
encoded_inputs: List of encoded inputs
padding: Padding strategy
@@ -211,7 +216,7 @@ class HFTokenizerAdapter:
return_attention_mask: Return attention mask
return_tensors: Return tensors
verbose: Verbose mode
Returns:
Padded inputs
"""
@@ -224,47 +229,62 @@ class HFTokenizerAdapter:
# Обрабатываем разные типы данных
if isinstance(input_ids, int):
seq_len = 1
elif hasattr(input_ids, 'shape'):
seq_len = input_ids.shape[-1] if len(input_ids.shape) > 1 else len(input_ids)
elif hasattr(input_ids, "shape"):
seq_len = (
input_ids.shape[-1]
if len(input_ids.shape) > 1
else len(input_ids)
)
else:
seq_len = len(input_ids)
max_len = max(max_len, seq_len)
if max_length is not None:
max_len = min(max_len, max_length)
# Применяем padding
for item in encoded_inputs:
input_ids = item["input_ids"]
# Получаем текущую длину
if isinstance(input_ids, int):
current_len = 1
elif hasattr(input_ids, 'shape'):
current_len = input_ids.shape[-1] if len(input_ids.shape) > 1 else len(input_ids)
elif hasattr(input_ids, "shape"):
current_len = (
input_ids.shape[-1]
if len(input_ids.shape) > 1
else len(input_ids)
)
else:
current_len = len(input_ids)
if current_len < max_len:
# Дополняем pad_token_id
padding_length = max_len - current_len
# Обрабатываем разные типы данных
if isinstance(input_ids, int):
item["input_ids"] = [input_ids] + [self.pad_token_id] * padding_length
elif hasattr(input_ids, 'shape'):
item["input_ids"] = [input_ids] + [
self.pad_token_id
] * padding_length
elif hasattr(input_ids, "shape"):
import torch
padding_tensor = torch.full((padding_length,), self.pad_token_id, dtype=input_ids.dtype)
padding_tensor = torch.full(
(padding_length,), self.pad_token_id, dtype=input_ids.dtype
)
item["input_ids"] = torch.cat([input_ids, padding_tensor])
else:
item["input_ids"] = input_ids + [self.pad_token_id] * padding_length
item["input_ids"] = (
input_ids + [self.pad_token_id] * padding_length
)
# Добавляем attention_mask если требуется
if "attention_mask" in item:
mask = item["attention_mask"]
if isinstance(mask, int):
item["attention_mask"] = [mask] + [0] * padding_length
elif hasattr(mask, 'shape'):
elif hasattr(mask, "shape"):
padding_mask = torch.zeros(padding_length, dtype=mask.dtype)
item["attention_mask"] = torch.cat([mask, padding_mask])
else:
@@ -272,44 +292,49 @@ class HFTokenizerAdapter:
elif return_attention_mask:
if isinstance(input_ids, int):
item["attention_mask"] = [1] + [0] * padding_length
elif hasattr(input_ids, 'shape'):
elif hasattr(input_ids, "shape"):
attention_mask = torch.ones(current_len, dtype=torch.long)
padding_mask = torch.zeros(padding_length, dtype=torch.long)
item["attention_mask"] = torch.cat([attention_mask, padding_mask])
item["attention_mask"] = torch.cat(
[attention_mask, padding_mask]
)
else:
item["attention_mask"] = [1] * current_len + [0] * padding_length
item["attention_mask"] = [1] * current_len + [
0
] * padding_length
# Конвертируем в тензоры если требуется
if return_tensors == "pt":
import torch
for key in list(encoded_inputs[0].keys()):
if isinstance(encoded_inputs[0][key], list):
for i in range(len(encoded_inputs)):
encoded_inputs[i][key] = torch.tensor(encoded_inputs[i][key])
return encoded_inputs
def get_vocab(self) -> Dict[str, int]:
"""Возвращает словарь токенизатора."""
return self._vocab
def __len__(self) -> int:
"""Возвращает размер словаря."""
return self.vocab_size
def save_pretrained(self, save_directory: str, **kwargs):
"""
Сохраняет токенизатор в формате HuggingFace.
Args:
save_directory: Директория для сохранения
**kwargs: Дополнительные параметры
"""
import os
# Создаем директорию если не существует
os.makedirs(save_directory, exist_ok=True)
# Сохраняем конфигурацию токенизатора
tokenizer_config = {
"tokenizer_class": self.__class__.__name__,
@@ -324,77 +349,81 @@ class HFTokenizerAdapter:
"bos_token_id": self.bos_token_id,
"eos_token_id": self.eos_token_id,
}
config_path = os.path.join(save_directory, "tokenizer_config.json")
with open(config_path, 'w', encoding='utf-8') as f:
with open(config_path, "w", encoding="utf-8") as f:
json.dump(tokenizer_config, f, ensure_ascii=False, indent=2)
# Сохраняем словарь
vocab_path = os.path.join(save_directory, "vocab.json")
with open(vocab_path, 'w', encoding='utf-8') as f:
with open(vocab_path, "w", encoding="utf-8") as f:
json.dump(self._vocab, f, ensure_ascii=False, indent=2)
print(f"✅ Токенизатор сохранен в {save_directory}")
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs):
"""
Загружает адаптированный токенизатор.
Args:
pretrained_model_name_or_path: Путь к сохраненному токенизатору
**kwargs: Дополнительные параметры
Returns:
HFTokenizerAdapter: Загруженный адаптер
"""
import os
# Проверяем, является ли путь директорией с файлами токенизатора
if os.path.isdir(pretrained_model_name_or_path):
# Загружаем из директории
config_path = os.path.join(pretrained_model_name_or_path, "tokenizer_config.json")
config_path = os.path.join(
pretrained_model_name_or_path, "tokenizer_config.json"
)
vocab_path = os.path.join(pretrained_model_name_or_path, "vocab.json")
if not os.path.exists(config_path) or not os.path.exists(vocab_path):
raise FileNotFoundError(
f"Файлы токенизатора не найдены в {pretrained_model_name_or_path}"
)
# Загружаем конфигурацию
with open(config_path, 'r', encoding='utf-8') as f:
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
# Определяем тип токенизатора llm
llm_tokenizer_type = config.get("llm_tokenizer_type", "BPETokenizer")
if llm_tokenizer_type == "BPETokenizer":
# Создаем BPETokenizer и загружаем словарь
llm_tokenizer = BPETokenizer()
# Загружаем словарь
with open(vocab_path, 'r', encoding='utf-8') as f:
with open(vocab_path, "r", encoding="utf-8") as f:
vocab = json.load(f)
llm_tokenizer.vocab = vocab
llm_tokenizer.inverse_vocab = {v: k for k, v in vocab.items()}
llm_tokenizer.vocab_size = len(vocab)
# Устанавливаем специальные токены
llm_tokenizer.pad_token = config.get("pad_token", "<pad>")
llm_tokenizer.unk_token = config.get("unk_token", "<unk>")
llm_tokenizer.bos_token = config.get("bos_token", "<bos>")
llm_tokenizer.eos_token = config.get("eos_token", "<eos>")
llm_tokenizer.pad_token_id = config.get("pad_token_id", 0)
llm_tokenizer.unk_token_id = config.get("unk_token_id", 1)
llm_tokenizer.bos_token_id = config.get("bos_token_id", 2)
llm_tokenizer.eos_token_id = config.get("eos_token_id", 3)
return cls(llm_tokenizer, **kwargs)
else:
raise ValueError(f"Неподдерживаемый тип токенизатора: {llm_tokenizer_type}")
raise ValueError(
f"Неподдерживаемый тип токенизатора: {llm_tokenizer_type}"
)
else:
# Пытаемся загрузить как файл llm токенизатора
try:
@@ -409,10 +438,10 @@ class HFTokenizerAdapter:
def create_hf_tokenizer(llm_tokenizer: BaseTokenizer) -> HFTokenizerAdapter:
"""
Создает адаптер HuggingFace для кастомного токенизатора.
Args:
llm_tokenizer: Токенизатор из библиотеки llm
Returns:
HFTokenizerAdapter: Адаптированный токенизатор
"""
@@ -422,7 +451,7 @@ def create_hf_tokenizer(llm_tokenizer: BaseTokenizer) -> HFTokenizerAdapter:
def convert_to_hf_format(llm_tokenizer: BaseTokenizer, save_directory: str):
"""
Конвертирует кастомный токенизатор в формат HuggingFace.
Args:
llm_tokenizer: Токенизатор из llm
save_directory: Директория для сохранения