import json
import subprocess
from typing import List, Dict
import numpy as np
from torch.utils.data import Dataset
import torch
import os
os.environ["TOKENIZERS_PARALLELISM"] = "false"
def count_lines(file_name):
out = subprocess.getoutput("wc -l %s" % file_name)
return int(out.split()[0])
def texts_to_bin(input_path, tokenizer, content_key="content"):
max_buffered_length = 1 * 1024 * 1024
output_path = f'{input_path}.bin'
with open(input_path, "r", encoding="utf-8") as reader:
with open(output_path, "wb") as writer:
buffered_ids = []
i = 0
while True:
line = reader.readline()
if not line:
break
content = json.loads(line).get(content_key, "")
if not content:
continue
# 将数据序列化为二进制格式
tokenized = tokenizer(content)
buffered_ids += tokenized["input_ids"]
if len(buffered_ids) >= max_buffered_length:
arr = np.array(buffered_ids, dtype=np.uint16)
writer.write(arr.tobytes())
buffered_ids.clear()
i += 1
print(f"write {i}m bytes") if i % 100 == 0 else None
# 处理最后一段不满max_buffer_length的token序列
if len(buffered_ids) > 0:
arr = np.array(buffered_ids, dtype=np.uint16)
writer.write(arr.tobytes())
print(f"write arr: {len(arr)}")
return output_path
class PretrainBinDataset(Dataset):
def __init__(self, data_path, max_tokens):
with open(data_path) as f:
f.seek(0, 2)
self.total_tokens = f.tell() // np.dtype("uint16").itemsize
print(f"total_tokens: {self.total_tokens}")
self.data = np.memmap(data_path, dtype=np.uint16, shape=(self.total_tokens//max_tokens, max_tokens))
def __len__(self):
return self.data.shape[0]
def __getitem__(self, index):
assert isinstance(index, int)
item = self.data[index]
input = item[:-1].astype(np.int64)
target = item[1:].astype(np.int64) # 在计算交叉熵损失时要求目标输出为长整型
return torch.from_numpy(input), torch.from_numpy(target)
class PretrainDataset(Dataset):
def __init__(self, data_paths, tokenizer, max_length=512):
super().__init__()
self.tokenizer = tokenizer
self.max_length = max_length
self.samples = self.load_data(data_paths)
@staticmethod
def load_data(paths) -> List[Dict]:
samples = []
for path in paths:
with open(path, 'r', encoding='utf-8') as f:
for line in f:
data = json.loads(line.strip())
samples.append(data)
return samples
def __len__(self):
return len(self.samples)
def __getitem__(self, index):
sample = self.samples[index]
# 构建输入文本
text = sample['text']
encoding = self.tokenizer(
text,
max_length=self.max_length,
padding=False,
truncation=True,
return_tensors='pt'
)
input_ids = encoding.input_ids.squeeze()
return torch.tensor(input_ids, dtype=torch.long)
class SFTDataset(Dataset):
def __init__(self, jsonl_path, tokenizer, max_length=1024):
super().__init__()
self.tokenizer = tokenizer
self.max_length = max_length
self.samples = self.load_data(jsonl_path)
self.bos_id = tokenizer('assistant', add_special_tokens=False).input_ids
self.eos_id = tokenizer('', add_special_tokens=False).input_ids
def __len__(self):
return len(self.samples)
def load_data(self, path):
samples = []
with open(path, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
data = json.loads(line.strip())
samples.append(data)
return samples
def _create_chat_prompt(self, conversations):
"""构建符合ChatML格式的对话"""
messages = []
for i, turn in enumerate(conversations):
role = 'user' if i % 2 == 0 else 'assistant'
messages.append({"role": role, "content": turn['content']})
return self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False
)
def _generate_loss_mask(self, input_ids):
loss_mask = [0] * len(input_ids)
i = 0
while i < len(input_ids):
if input_ids[i:i + len(self.bos_id)] == self.bos_id:
start = i + len(self.bos_id)
end = start
while end < len(input_ids):
if input_ids[end:end + len(self.eos_id)] == self.eos_id:
break
end += 1
for j in range(start + 1, min(end + len(self.eos_id) + 1, self.max_length)):
loss_mask[j] = 1
i = end + len(self.eos_id) if end < len(input_ids) else len(input_ids)
else:
i += 1
return loss_mask
def __getitem__(self, index):
sample = self.samples[index]
# 构建对话提示
prompt = self._create_chat_prompt(sample['conversations'])
input_ids = self.tokenizer(prompt).input_ids[:self.max_length]
input_ids += [self.tokenizer.pad_token_id] * (self.max_length - len(input_ids))
# 生成动态损失掩码
loss_mask = self._generate_loss_mask(input_ids)
# 构建训练数据
X = torch.tensor(input_ids[:-1], dtype=torch.long)
Y = torch.tensor(input_ids[1:], dtype=torch.long)
loss_mask = torch.tensor(loss_mask[1:], dtype=torch.long) # 对齐预测位置
return X, Y, loss_mask
class DPODataset(Dataset):
def __init__(self, file_path, tokenizer, max_length=4096):
super().__init__()
self.tokenizer = tokenizer
self.max_length = max_length
self.padding = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else 0
self.bos_id = tokenizer('assistant', add_special_tokens=False).input_ids
self.eos_id = tokenizer('', add_special_tokens=False).input_ids
with open(file_path, 'r', encoding='utf-8') as f:
self.data = []
for line in f:
line = line.strip()
obj = json.loads(line)
self.data.append(obj)
def __len__(self):
return len(self.data)
def __getitem__(self, index):
item = self.data[index]
chosen = item['chosen'] # 是一个 list,里面包含若干 {role, content}
rejected = item['rejected'] # 同上
chosen_prompt = self.tokenizer.apply_chat_template(
chosen, tokenize=False, add_generation_prompt=False
)
rejected_prompt = self.tokenizer.apply_chat_template(
rejected, tokenize=False, add_generation_prompt=False
)
chosen_encoding = self.tokenizer(
chosen_prompt, truncation=True, max_length=self.max_length, padding='max_length'
)
rejected_encoding = self.tokenizer(
rejected_prompt, truncation=True, max_length=self.max_length, padding='max_length'
)
chosen_input_ids = chosen_encoding['input_ids']
chosen_loss_mask = self._generate_loss_mask(chosen_input_ids)
rejected_input_ids = rejected_encoding['input_ids']
rejected_loss_mask = self._generate_loss_mask(rejected_input_ids)
x_chosen = torch.tensor(chosen_input_ids[:-1], dtype=torch.long)
y_chosen = torch.tensor(chosen_input_ids[1:], dtype=torch.long)
mask_chosen = torch.tensor(chosen_loss_mask[1:], dtype=torch.long)
x_rejected = torch.tensor(rejected_input_ids[:-1], dtype=torch.long)
y_rejected = torch.tensor(rejected_input_ids[1:], dtype=torch.long)
mask_rejected = torch.tensor(rejected_loss_mask[1:], dtype=torch.long)
return {
'x_chosen': x_chosen,
'y_chosen': y_chosen,
'mask_chosen': mask_chosen,
'x_rejected': x_rejected,
'y_rejected': y_rejected,
'mask_rejected': mask_rejected
}
def _generate_loss_mask(self, input_ids):
loss_mask = [0] * len(input_ids)
i = 0
while i < len(input_ids):
if input_ids[i:i + len(self.bos_id)] == self.bos_id:
start = i + len(self.bos_id)
end = start
while end < len(input_ids):
if input_ids[end:end + len(self.eos_id)] == self.eos_id:
break
end += 1
for j in range(start + 1, min(end + len(self.eos_id) + 1, self.max_length)):
loss_mask[j] = 1
i = end + len(self.eos_id) if end < len(input_ids) else len(input_ids)
else:
i += 1
return loss_mask
if __name__ == '__main__':
c = count_lines("test")
print(c)