LFM3-1.7B AI Text Detector

Scores how likely an English text is AI-generated, from 0 (human) to 100 (AI). It is a LoRA adapter on LiquidAI/LFM3-1.7B plus a linear 2-class head read out at the last token, trained on rasbt/human-vs-ai-50k.

Try it in the browser: ameenazeemi/AI-Text-Detector

Results

Split Texts Accuracy
Validation 5,085 99.94%
Test 5,066 99.98%

On the test set, 1 of 2,547 human texts was flagged as AI and all 2,519 AI texts were caught.

Test accuracy by length:

Words Texts Accuracy
0-100 1,044 99.9%
100-250 1,163 100.0%
250-500 1,246 100.0%
500-1000 951 100.0%
1000+ 662 100.0%

A calibration temperature of 1.007 was fitted on the validation logits. It is close to 1, so the raw scores were already well calibrated.

Files

File Content
adapter_model.safetensors, adapter_config.json LoRA (r=16, alpha=32) on attention, short-convolution and MLP projections
head.safetensors Linear(2048, 2), logits [human, AI]
detector_config.json base model and revision, max_length, temperature, labels
tokenizer files LFM3 tokenizer, left padding

Usage

import json
import torch
import torch.nn as nn
from huggingface_hub import hf_hub_download
from peft import PeftModel
from safetensors.torch import load_file
from transformers import AutoModel, AutoTokenizer

repo = "ameenazeemi/LFM3-1.7B-AI-Text-Detector"
cfg = json.load(open(hf_hub_download(repo, "detector_config.json")))

tokenizer = AutoTokenizer.from_pretrained(repo)
base = AutoModel.from_pretrained(cfg["base_model"], revision=cfg["base_revision"],
                                 dtype=torch.bfloat16, trust_remote_code=True)
backbone = PeftModel.from_pretrained(base, repo).merge_and_unload().eval()
head = nn.Linear(backbone.config.hidden_size, 2)
head.load_state_dict(load_file(hf_hub_download(repo, "head.safetensors")))

@torch.no_grad()
def ai_score(text):
    enc = tokenizer(text, truncation=True, max_length=cfg["max_length"], return_tensors="pt")
    hidden = backbone(input_ids=enc["input_ids"], attention_mask=enc["attention_mask"]).last_hidden_state
    logits = head(hidden[:, -1, :].float()) / cfg["temperature"]
    return 100 * logits.softmax(dim=-1)[0, 1].item()

print(ai_score("Paste a paragraph here."))

For batches, pad on the left (the saved tokenizer already does), so position -1 is always the last real token.

Training

  • Data: all 40,559 training texts (balanced). The dataset groups its splits by source document, so related texts never cross splits.
  • Model: LFM3-1.7B backbone with LoRA plus a fully trained linear head on the last token's hidden state. Cross-entropy loss.
  • Schedule: 2 epochs, up to 1,024 tokens per text, effective batch size 16, AdamW (LoRA lr 1e-4, head lr 1e-3, weight decay 0.01), cosine schedule with 10% warmup, bf16 autocast, gradient checkpointing.
  • Model selection: best validation accuracy.
  • Hardware: one 16 GB laptop GPU (5.6 GB peak), about 11 hours.

The method follows Sebastian Raschka's Building an AI Text Detector From Scratch.

Limitations

  • These are in-distribution numbers. The AI texts come from a handful of LLMs answering generated prompts, and the human texts were written before ChatGPT. Expect lower accuracy on other generators, AI text that people edited or paraphrased, non-English text, and very short texts (under about 100 words).
  • The score is the classifier's probability on this data distribution. It does not prove who wrote a text. Do not use it on its own to make decisions about people, such as academic misconduct cases.
  • The adapter is a derivative of LFM3-1.7B and is subject to the LFM Open License v1.0.
Downloads last month
17
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for ameenazeemi/LFM3-1.7B-AI-Text-Detector

Adapter
(9)
this model

Dataset used to train ameenazeemi/LFM3-1.7B-AI-Text-Detector