项目文件夹

文件
2026-07-13 13:24:13 +08:00

169 行
8.2 KiB
Python

import os
import random
import math
from dataclasses import dataclass, field
import torch
import torch.nn.functional as F
from fairseq import metrics
from fairseq.criterions import FairseqCriterion, register_criterion
from fairseq.dataclass import FairseqDataclass
OURS_TEMPLATE = "There is a special magic number inside a lot of irrelevant text. Find it and memorize them. I will quiz you about the magic number there. {context} "
RANDOM_NEEDLE_CITIES = [
'Chicago', 'Yangon', 'Antananarivo', 'Colombo', 'Almaty', 'Sydney', 'Chicago', 'Mexico City',
'Seattle', 'Lagos', 'Amsterdam', 'Belgrade', 'Cairo', 'Baghdad', 'Damascus', 'Kigali', 'Dakar',
'Dakar', 'Sofia', 'Kigali', 'Victoria', 'Tashkent', 'Mumbai', 'Barcelona', 'Almaty', 'Amman',
'Toronto', 'Bratislava', 'Johannesburg', 'Thimphu', 'Bangkok', 'Santiago', 'Cairo', 'San Francisco',
'Lagos', 'Amsterdam', 'Paris', 'Rabat', 'Santiago', 'Copenhagen', 'Madrid', 'Kigali',
'Ho Chi Minh City', 'Sarajevo', 'Delhi', 'Istanbul', 'Ho Chi Minh City', 'Khartoum', 'Helsinki',
'Doha', 'Istanbul', 'Kuala Lumpur', 'Budapest', 'Shanghai', 'Moscow', 'Los Angeles', 'Oslo',
'Johannesburg', 'Berlin', 'Bangalore', 'Tokyo', 'Melbourne', 'Barcelona', 'Chicago', 'Port Louis',
'Lisbon', 'Nairobi', 'Kampala', 'Lima', 'Maputo', 'Vancouver', 'Dubai', 'Khartoum', 'Jakarta',
'Madrid', 'Yerevan', 'Beirut', 'Athens', 'Chicago', 'Paris', 'Bucharest', 'Copenhagen', 'Brussels',
'Damascus', 'Seattle', 'Los Angeles', 'Yerevan', 'Victoria', 'Tunis', 'Astana', 'Seoul',
'Buenos Aires', 'Bangkok', 'Colombo', 'Brussels', 'Khartoum', 'Doha', 'San Francisco', 'Vienna', 'Jakarta'
]
QUESTION_TEMPLATE = "What is the special magic {city} number? The special magic {city} number is "
# NEEDLE_TEMPLATE = "The special magic {city} number is {rnd_number} . Remember it. The special magic {city} number is {rnd_number} . "
NEEDLE_TEMPLATE = "The special magic {city} number is {rnd_number} . "
@dataclass
class NeedleHaystackEvalConfig(FairseqDataclass):
max_len_b: int = field(
default=5,
metadata={"help":"max_len_b"}
)
tokens_per_sample: int = field(
default=16384,
)
interval: int = field(
default=1024,
)
needle_file_path: str = field(
default="/mnt/msranlp/yutao/data/PaulGrahamEssays",
)
@register_criterion("needle_haystack", dataclass=NeedleHaystackEvalConfig)
class NeedleHaystackEvalCriterion(FairseqCriterion):
def __init__(self, cfg: NeedleHaystackEvalConfig, task):
super().__init__(task)
self.cfg = cfg
self.essay_list = os.listdir(cfg.needle_file_path) * 5000
def generate_garbage(self, length):
current_text = ""
current_length = 0
while True:
essay = random.choice(self.essay_list)
essay = open(os.path.join(self.cfg.needle_file_path, essay)).read().splitlines()
for line in essay:
tokens = self.task.tokenizer.encode(line + " ")
if current_length + len(tokens) > length:
return current_text
current_text += line + " "
current_length += len(tokens)
def generate_prompt_landmark(self, prefix_length, suffix_length):
"""Generates a text file and inserts an passkey at a random position."""
city = random.choice(RANDOM_NEEDLE_CITIES)
magic_number = random.randint(1, 50000)
garbage_prefix = self.generate_garbage(prefix_length)
garbage_suffix = self.generate_garbage(suffix_length)
information_line = NEEDLE_TEMPLATE.format(city=city, rnd_number=magic_number)
final_question = QUESTION_TEMPLATE.format(city=city)
lines = [
garbage_prefix,
information_line,
garbage_suffix,
final_question,
]
context = "\n".join(lines)
return OURS_TEMPLATE.format(context=context), str(magic_number)
def forward(self, model, sample, reduce=True):
"""Compute the loss for the given sample.
Returns a tuple with three elements:
1) the loss
2) the sample size, which is used as the denominator for the gradient
3) logging outputs to display while training
"""
model.eval()
all_retrieval_result = {}
random.seed(0)
for context_length in range(self.cfg.interval, self.cfg.tokens_per_sample + 1, self.cfg.interval):
all_length = (context_length - 150)
local_retrieval_result = []
depth_number = 10
for depth_ratio in range(0, depth_number + 1):
prefix_length = int(all_length * depth_ratio / depth_number)
suffix_length = all_length - prefix_length
n_correct = 0
times = 10
for _ in range(times):
prompt, pass_key = self.generate_prompt_landmark(prefix_length, suffix_length)
prompt_tokens = self.task.tokenizer.encode(prompt, bos=True)
prompt_tokens = torch.tensor([prompt_tokens], device="cuda")
print(prompt_tokens.shape)
output = self.generate(model, prompt_tokens)
pred = self.task.tokenizer.decode(output[0, prompt_tokens.shape[1]:])
print("Answer: ", pass_key)
print("Pred: ", pred)
if pass_key in pred:
n_correct += 1
local_retrieval_result.append(n_correct / times)
all_retrieval_result[context_length] = local_retrieval_result
print(all_retrieval_result)
return 0, 1, {"loss": 0}
def generate(self, model, net_input, generate_tokens=20, chunk_length = 32768):
output_tokens = torch.cat((net_input, torch.full((net_input.shape[0], generate_tokens), self.task.tokenizer.pad_id).long().cuda()), dim=1)
begin_pad_index = torch.where(output_tokens == self.task.tokenizer.pad_id)[1].min().item()
incremental_state = {}
eos_reached = torch.tensor([False] * net_input.shape[0], device="cuda")
# prefilling
for begin_index in range(0, begin_pad_index - 1, chunk_length):
end_index = min(begin_index + chunk_length, begin_pad_index - 1)
_, _ = model(output_tokens[:, begin_index : end_index], incremental_state=incremental_state, start_pos=begin_index, skip_cross_decoder=True, is_prefilling=True)
# generation
for index in range(begin_pad_index, output_tokens.shape[1]):
generation_net_output, _ = model(output_tokens[:, index - 1].unsqueeze(-1), incremental_state=incremental_state, start_pos=index - 1, skip_cross_decoder=False, is_prefilling=False)
generation_net_output[:, :, self.task.tokenizer.bos_id] = -math.inf
generation_net_output[:, :, self.task.tokenizer.pad_id] = -math.inf
next_tokens = torch.argmax(generation_net_output[:, -1, :], dim=-1)
pad_tokens = output_tokens[:, index]
next_tokens = torch.where((pad_tokens == self.task.tokenizer.pad_id) & ~eos_reached, next_tokens, pad_tokens)
output_tokens[:, index] = next_tokens
eos_reached |= (
next_tokens == self.task.tokenizer.eos_id
)
if all(eos_reached):
break
return output_tokens
@staticmethod
def reduce_metrics(logging_outputs) -> None:
"""Aggregate logging outputs from data parallel training."""
loss_sum = sum(log.get("loss", 0) for log in logging_outputs)
metric_sum = sum(log.get("metric", 0) for log in logging_outputs)
nsentences = sum(log.get("nsentences", 0) for log in logging_outputs)
ntokens = sum(log.get("ntokens", 0) for log in logging_outputs)
metrics.log_scalar(
"loss", loss_sum / ntokens, ntokens, round=3
)
metrics.log_scalar(
"metric", metric_sum / nsentences, nsentences, round=3
)
@staticmethod
def logging_outputs_can_be_summed() -> bool:
"""
Whether the logging outputs returned by `forward` can be summed
across workers prior to calling `reduce_metrics`. Setting this
to True will improves distributed training speed.
"""
return True