CoolFace
Datasetpublic

ChipYTY/titans_NPC

Titans - Pytorch Unofficial implementation of Titans in Pytorch. Will also contain some explorations into architectures beyond their simple 1-4 layer MLP for the neural memory module, if it works well to any degree. Paper review by Yannic Quick Colab Run Appreciation Eryk for sharing his early experimental results with me, positive for 2 layer MLP Install $ pip install titans-pytorch Usage import torch from titans_pytorch import… See the full description on the dataset page: https://huggingface.co/datasets/ChipYTY/titans_NPC.

sourceHugging Faceupdated 8mo agoView on Hugging Face
0likes123downloads
eval_qwen_baseline.py516 linesDownload Raw Back to examples
1"""2Qwen3-4B baseline evaluation on BABILong QA1 (32k).3 4This script evaluates the pretrained Qwen model WITHOUT any training,5using the same chunk-based streaming approach as the Titans training script.6 7Purpose: Establish a baseline to compare with Titans memory-augmented models.8"""9 10import os11import json12import math13import argparse14import logging15from dataclasses import dataclass16from typing import Optional, Dict, List17 18import torch19import torch.nn as nn20import torch.nn.functional as F21from torch.utils.data import Dataset, DataLoader22from tqdm import tqdm23 24logging.basicConfig(25    level=logging.INFO,26    format="%(asctime)s - %(levelname)s - %(message)s"27)28logger = logging.getLogger(__name__)29 30 31@dataclass32class EvalConfig:33    # paths - same as training config34    model_path: str = "/data/huangyifei/huggingface_cache/hub/models--Qwen--Qwen3-4B-Instruct-2507/snapshots/cdbee75f17c01a7cc42f958dc650907174af0554"35    data_path: str = "/data/yty/BABILong/babilong-train-5k-samples/data/qa1/32k.json"36    output_dir: str = "./outputs/qwen_baseline_eval"37 38    # streaming settings - same as training39    chunk_size: int = 819240    max_length: int = 3276841    answer_reserve_tokens: int = 6442 43    # evaluation44    batch_size: int = 1  # use 1 for simplicity in baseline eval45    max_samples: Optional[int] = 500  # same as training default46    print_examples: int = 2047 48    # precision49    bf16: bool = True50    fp16: bool = False51    use_tf32: bool = True52 53    seed: int = 4254 55 56class BABILongDataset(Dataset):57    """Same dataset class as training script for consistency."""58 59    def __init__(60        self,61        data_path: str,62        tokenizer,63        max_length: int = 32768,64        answer_reserve_tokens: int = 64,65        max_samples: Optional[int] = None,66    ):67        self.tokenizer = tokenizer68        self.max_length = max_length69        self.answer_reserve_tokens = answer_reserve_tokens70 71        logger.info(f"Loading dataset: {data_path}")72        with open(data_path, "r") as f:73            self.data = json.load(f)74 75        if max_samples:76            self.data = self.data[:max_samples]77 78        logger.info(f"Dataset size: {len(self.data)}")79 80    def __len__(self):81        return len(self.data)82 83    def __getitem__(self, idx):84        item = self.data[idx]85        text = f"{item['input']}\n\nQuestion: {item['question']}\nAnswer:"86        target = item["target"]87 88        pad_id = self.tokenizer.pad_token_id or 089        reserve = int(self.answer_reserve_tokens)90 91        prompt_ids = self.tokenizer(92            text,93            max_length=max(self.max_length - reserve, 1),94            truncation=True,95            add_special_tokens=True,96            return_tensors="pt",97        ).input_ids.squeeze(0)98 99        answer_ids = self.tokenizer(100            f" {target}",101            add_special_tokens=False,102            return_tensors="pt",103        ).input_ids.squeeze(0)104 105        available = max(self.max_length - prompt_ids.numel(), 0)106        answer_ids = answer_ids[:available]107 108        input_ids = torch.cat([prompt_ids, answer_ids], dim=0)[: self.max_length]109 110        labels = torch.full_like(input_ids, fill_value=-100)111        if answer_ids.numel() > 0:112            start = prompt_ids.numel()113            end = min(start + answer_ids.numel(), labels.numel())114            labels[start:end] = input_ids[start:end]115 116        seq_len = input_ids.numel()117        if seq_len < self.max_length:118            pad_len = self.max_length - seq_len119            input_ids = F.pad(input_ids, (0, pad_len), value=int(pad_id))120            labels = F.pad(labels, (0, pad_len), value=-100)121            attention_mask = torch.cat(122                [torch.ones(seq_len, dtype=torch.long), torch.zeros(pad_len, dtype=torch.long)],123                dim=0,124            )125        else:126            attention_mask = torch.ones(self.max_length, dtype=torch.long)127 128        return {129            "input_ids": input_ids.to(dtype=torch.long),130            "labels": labels.to(dtype=torch.long),131            "attention_mask": attention_mask,132            "target_text": target,  # keep original target for comparison133        }134 135 136def collate_fn(batch):137    # separate target_text from tensor fields138    target_texts = [b.pop("target_text") for b in batch]139    tensor_batch = {k: torch.stack([b[k] for b in batch], dim=0) for k in batch[0].keys()}140    tensor_batch["target_texts"] = target_texts141    return tensor_batch142 143 144class QwenChunkwiseEvaluator:145    """146    Evaluates Qwen model using chunk-wise streaming (same as training).147    148    Key difference from training: NO memory module, just pure Qwen forward pass.149    Each chunk is processed independently with KV cache reset between samples.150    """151 152    def __init__(self, model, tokenizer, config: EvalConfig, device: torch.device):153        self.model = model154        self.tokenizer = tokenizer155        self.config = config156        self.device = device157        self.hidden_size = model.config.hidden_size158 159    def _split_into_chunks(self, seq_len: int, chunk_size: int):160        """Split sequence into chunks, same as training."""161        chunks = []162        for start in range(0, seq_len, chunk_size):163            end = min(start + chunk_size, seq_len)164            chunks.append((start, end))165        return chunks166 167    @torch.no_grad()168    def evaluate_sample(169        self,170        input_ids: torch.Tensor,171        attention_mask: torch.Tensor,172        labels: torch.Tensor,173    ) -> Dict:174        """175        Evaluate a single sample using chunk-wise streaming.176        177        Process:178        1. Split input into chunks179        2. Process each chunk through Qwen (with overlap for next-token prediction)180        3. Collect predictions only for answer tokens (labels != -100)181        4. Compute loss, token accuracy, and EM accuracy182        """183        batch_size, seq_len = input_ids.shape184        chunk_size = self.config.chunk_size185        chunks = self._split_into_chunks(seq_len, chunk_size)186 187        loss_fct_sum = nn.CrossEntropyLoss(reduction="sum")188        total_loss_sum = 0.0189        total_loss_tokens = 0190 191        pred_tokens: List[int] = []192        target_tokens: List[int] = []193 194        for start, end in chunks:195            # Include 1 overlap token for next-token prediction at chunk boundaries196            proc_start = max(0, start - 1)197            chunk_ids = input_ids[:, proc_start:end]198            chunk_labels = labels[:, proc_start:end]199            chunk_mask = attention_mask[:, proc_start:end]200 201            # Forward pass through Qwen202            outputs = self.model(203                input_ids=chunk_ids,204                attention_mask=chunk_mask,205                use_cache=False,206                output_hidden_states=False,207                return_dict=True,208            )209            logits = outputs.logits  # [batch, seq, vocab]210 211            # Compute loss and predictions for answer tokens212            if chunk_labels is not None and (chunk_labels != -100).any():213                # Shift for next-token prediction214                shift_logits = logits[:, :-1, :].contiguous()215                shift_labels = chunk_labels[:, 1:].contiguous()216 217                valid = shift_labels != -100218                if valid.any():219                    valid_logits = shift_logits[valid]220                    valid_targets = shift_labels[valid]221 222                    # Compute loss223                    chunk_loss = loss_fct_sum(valid_logits.float(), valid_targets)224                    total_loss_sum += chunk_loss.item()225                    total_loss_tokens += valid_targets.numel()226 227                    # Collect predictions228                    pred_ids = torch.argmax(valid_logits, dim=-1)229                    pred_tokens.extend(pred_ids.cpu().tolist())230                    target_tokens.extend(valid_targets.cpu().tolist())231 232        # Compute metrics233        if total_loss_tokens > 0:234            avg_loss = total_loss_sum / total_loss_tokens235        else:236            avg_loss = 0.0237 238        # Token accuracy239        if len(pred_tokens) > 0:240            tok_correct = sum(p == t for p, t in zip(pred_tokens, target_tokens))241            tok_acc = tok_correct / len(pred_tokens)242        else:243            tok_acc = 0.0244 245        # EM accuracy (exact match of decoded strings)246        if len(pred_tokens) > 0:247            pred_text = self.tokenizer.decode(pred_tokens, skip_special_tokens=True).strip()248            target_text = self.tokenizer.decode(target_tokens, skip_special_tokens=True).strip()249            em_match = (pred_text == target_text)250        else:251            pred_text = ""252            target_text = ""253            em_match = False254 255        return {256            "loss": avg_loss,257            "tok_acc": tok_acc,258            "em_match": em_match,259            "pred_text": pred_text,260            "target_text": target_text,261            "num_tokens": len(pred_tokens),262        }263 264    @torch.no_grad()265    def evaluate_dataset(self, dataloader: DataLoader, print_examples: int = 10) -> Dict:266        """Evaluate entire dataset."""267        self.model.eval()268 269        total_loss = 0.0270        total_batches = 0271        total_tok_correct = 0272        total_tok_total = 0273        total_em_correct = 0274        total_em_total = 0275        printed = 0276 277        pbar = tqdm(dataloader, desc="Evaluating", dynamic_ncols=True)278        for batch in pbar:279            input_ids = batch["input_ids"].to(self.device)280            attention_mask = batch["attention_mask"].to(self.device)281            labels = batch["labels"].to(self.device)282            target_texts = batch["target_texts"]283 284            # Process each sample in batch285            for i in range(input_ids.shape[0]):286                result = self.evaluate_sample(287                    input_ids[i:i+1],288                    attention_mask[i:i+1],289                    labels[i:i+1],290                )291 292                if result["num_tokens"] > 0:293                    total_loss += result["loss"]294                    total_batches += 1295                    total_tok_correct += int(result["tok_acc"] * result["num_tokens"])296                    total_tok_total += result["num_tokens"]297                    total_em_correct += int(result["em_match"])298                    total_em_total += 1299 300                    # Print examples301                    if printed < print_examples:302                        logger.info(303                            f"[EVAL SAMPLE {printed + 1}] "304                            f"pred={repr(result['pred_text'])} | "305                            f"label={repr(result['target_text'])} | "306                            f"match={result['em_match']}"307                        )308                        printed += 1309 310                # Update progress bar311                if total_em_total > 0:312                    pbar.set_postfix({313                        "em_acc": f"{total_em_correct / total_em_total * 100:.1f}%",314                        "tok_acc": f"{total_tok_correct / max(total_tok_total, 1) * 100:.1f}%",315                    })316 317        # Compute final metrics318        avg_loss = total_loss / max(total_batches, 1)319        tok_acc = total_tok_correct / max(total_tok_total, 1)320        em_acc = total_em_correct / max(total_em_total, 1)321 322        return {323            "loss": avg_loss,324            "tok_acc": tok_acc,325            "em_acc": em_acc,326            "total_samples": total_em_total,327            "total_tokens": total_tok_total,328        }329 330 331def main():332    from transformers import AutoModelForCausalLM, AutoTokenizer333 334    parser = argparse.ArgumentParser(description="Evaluate Qwen baseline on BABILong")335    parser.add_argument("--model_path", type=str, default=None, help="Path to Qwen model")336    parser.add_argument("--data_path", type=str, default=None, help="Path to BABILong data")337    parser.add_argument("--output_dir", type=str, default=None, help="Output directory")338    parser.add_argument("--max_samples", type=int, default=None, help="Max samples to evaluate")339    parser.add_argument("--chunk_size", type=int, default=None, help="Chunk size for streaming")340    parser.add_argument("--batch_size", type=int, default=1, help="Batch size")341    parser.add_argument("--print_examples", type=int, default=20, help="Number of examples to print")342    parser.add_argument("--eval_split", type=str, default="eval", choices=["train", "eval", "all"],343                        help="Which split to evaluate: train (90%), eval (10%), or all")344    args = parser.parse_args()345 346    config = EvalConfig()347    if args.model_path:348        config.model_path = args.model_path349    if args.data_path:350        config.data_path = args.data_path351    if args.output_dir:352        config.output_dir = args.output_dir353    if args.max_samples is not None:354        config.max_samples = args.max_samples355    if args.chunk_size is not None:356        config.chunk_size = args.chunk_size357    if args.batch_size:358        config.batch_size = args.batch_size359    if args.print_examples is not None:360        config.print_examples = args.print_examples361 362    torch.manual_seed(config.seed)363 364    # Device setup365    if torch.cuda.is_available():366        device = torch.device("cuda")367    else:368        device = torch.device("cpu")369 370    # TF32 settings371    if torch.cuda.is_available() and config.use_tf32:372        torch.backends.cuda.matmul.allow_tf32 = True373        torch.backends.cudnn.allow_tf32 = True374        try:375            torch.set_float32_matmul_precision("high")376        except Exception:377            pass378 379    logger.info("=" * 60)380    logger.info("Qwen3-4B Baseline Evaluation (NO TRAINING)")381    logger.info("=" * 60)382    logger.info(f"model_path: {config.model_path}")383    logger.info(f"data_path: {config.data_path}")384    logger.info(f"output_dir: {config.output_dir}")385    logger.info(f"max_samples: {config.max_samples}")386    logger.info(f"max_length: {config.max_length}")387    logger.info(f"chunk_size: {config.chunk_size}")388    logger.info(f"eval_split: {args.eval_split}")389    logger.info("=" * 60)390 391    # Load tokenizer392    logger.info("Loading tokenizer...")393    tokenizer = AutoTokenizer.from_pretrained(config.model_path, trust_remote_code=True)394    if tokenizer.pad_token is None:395        tokenizer.pad_token = tokenizer.eos_token396 397    # Disable flash-attn checks398    try:399        import transformers400        from transformers.utils import import_utils as _import_utils401 402        def _disabled(*args, **kwargs):403            return False404 405        _import_utils.is_flash_attn_2_available = _disabled406        if hasattr(transformers, "utils") and hasattr(transformers.utils, "is_flash_attn_2_available"):407            transformers.utils.is_flash_attn_2_available = _disabled408        _import_utils.is_torchao_available = _disabled409        if hasattr(transformers, "utils") and hasattr(transformers.utils, "is_torchao_available"):410            transformers.utils.is_torchao_available = _disabled411    except Exception as e:412        logger.warning(f"Disable checks failed (ignored): {e}")413 414    # Load model415    logger.info("Loading model...")416    torch_dtype = torch.bfloat16 if config.bf16 else (torch.float16 if config.fp16 else torch.float32)417    model = AutoModelForCausalLM.from_pretrained(418        config.model_path,419        torch_dtype=torch_dtype,420        device_map=None,421        trust_remote_code=True,422        attn_implementation="sdpa",423        low_cpu_mem_usage=True,424    )425    model.to(device)426    model.config.use_cache = False427    model.eval()428    logger.info(f"Model loaded: {model.config.hidden_size} hidden size, {model.config.num_hidden_layers} layers")429 430    # Load dataset431    logger.info("Loading dataset...")432    full_dataset = BABILongDataset(433        config.data_path,434        tokenizer,435        max_length=config.max_length,436        answer_reserve_tokens=config.answer_reserve_tokens,437        max_samples=config.max_samples,438    )439 440    # Split dataset same as training (90% train, 10% eval)441    train_size = int(0.9 * len(full_dataset))442    eval_size = len(full_dataset) - train_size443    train_dataset, eval_dataset = torch.utils.data.random_split(444        full_dataset,445        [train_size, eval_size],446        generator=torch.Generator().manual_seed(config.seed),447    )448 449    # Select which split to evaluate450    if args.eval_split == "train":451        dataset = train_dataset452        split_name = "train"453    elif args.eval_split == "eval":454        dataset = eval_dataset455        split_name = "eval"456    else:  # all457        dataset = full_dataset458        split_name = "all"459 460    logger.info(f"Evaluating on '{split_name}' split: {len(dataset)} samples")461 462    dataloader = DataLoader(463        dataset,464        batch_size=config.batch_size,465        shuffle=False,466        collate_fn=collate_fn,467        num_workers=0,468    )469 470    # Create evaluator471    evaluator = QwenChunkwiseEvaluator(model, tokenizer, config, device)472 473    # Run evaluation474    logger.info("Starting evaluation...")475    results = evaluator.evaluate_dataset(dataloader, print_examples=config.print_examples)476 477    # Print results478    ppl = math.exp(min(20.0, results["loss"]))479    logger.info("=" * 60)480    logger.info("EVALUATION RESULTS (Qwen Baseline - NO TRAINING)")481    logger.info("=" * 60)482    logger.info(f"Split: {split_name}")483    logger.info(f"Total samples: {results['total_samples']}")484    logger.info(f"Total answer tokens: {results['total_tokens']}")485    logger.info(f"Loss: {results['loss']:.4f}")486    logger.info(f"Perplexity: {ppl:.3f}")487    logger.info(f"Token Accuracy: {results['tok_acc'] * 100:.2f}%")488    logger.info(f"EM Accuracy: {results['em_acc'] * 100:.2f}%")489    logger.info("=" * 60)490 491    # Save results492    os.makedirs(config.output_dir, exist_ok=True)493    results_path = os.path.join(config.output_dir, f"baseline_results_{split_name}.json")494    with open(results_path, "w") as f:495        json.dump({496            "split": split_name,497            "total_samples": int(results["total_samples"]),498            "total_tokens": int(results["total_tokens"]),499            "loss": float(results["loss"]),500            "perplexity": float(ppl),501            "tok_acc_pct": float(results["tok_acc"] * 100),502            "em_acc_pct": float(results["em_acc"] * 100),503            "config": {504                "model_path": config.model_path,505                "data_path": config.data_path,506                "max_samples": config.max_samples,507                "max_length": config.max_length,508                "chunk_size": config.chunk_size,509            }510        }, f, indent=2)511    logger.info(f"Results saved to: {results_path}")512 513 514if __name__ == "__main__":515    main()516