senthil090/schemalm-v2-lfm2-350m
016
SchemaLM v2 — LFM2.5 350M
A 350M parameter model fine-tuned for JSON-schema-conditioned information extraction. Give it a JSON Schema and any unstructured text — it returns a JSON object that matches the schema exactly.
Base model: `LiquidAI/LFM2.5-350M` (SSM hybrid, ideal for edge/CPU deployment)
Benchmark Results
Evaluated on a 25-case internal benchmark covering easy/medium/hard extraction across 7 domains.
Training
- Dataset: 49K synthetic schema-extraction examples (93 schemas, 29 domains, nested object/array coverage)
- Method: PEFT/LoRA SFT — rank 32, alpha 64, ~2% trainable parameters
- Epochs: 5 | LR: 2e-4 (cosine) | Max seq: 768 tokens
- Precision: bf16 on Ampere/Hopper, fp16 otherwise
Usage
The model follows the ChatML format: system message = JSON Schema, user message = unstructured text, assistant response = JSON.
Basic (Transformers)
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch, json
model_id = "senthil090/schemalm-v2-lfm2-350m"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id, torch_dtype=torch.bfloat16, device_map="auto"
)
model.eval()
def extract(schema: dict, text: str, max_new_tokens: int = 256) -> dict:
messages = [
{"role": "system", "content": json.dumps(schema)},
{"role": "user", "content": text},
]
prompt = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
out = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=False,
repetition_penalty=1.08,
)
raw = tokenizer.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
return json.loads(raw.strip())Example 1 — Sprint planning note → structured task
schema = {
"type": "object",
"properties": {
"task_title": {"type": "string"},
"assignee": {"type": "string"},
"priority": {"type": "string", "enum": ["critical", "high", "medium", "low"]},
"story_points": {"type": "integer"},
"status": {"type": "string", "enum": ["todo", "in_progress", "done"]},
"epic": {"type": "string"},
},
"required": ["task_title", "assignee", "priority"],
}
text = (
"Assign the Redis caching implementation to Priya for Sprint 14. "
"It's high priority under the Performance epic, estimated 5 story points. "
"Mark it as in progress since she already started."
)
result = extract(schema, text)
# → {
# "task_title": "Redis caching implementation",
# "assignee": "Priya",
# "priority": "high",
# "story_points": 5,
# "status": "in_progress",
# "epic": "Performance"
# }Example 2 — Nested support ticket (nested objects + arrays)
schema = {
"type": "object",
"properties": {
"customer": {
"type": "object",
"properties": {
"name": {"type": "string"},
"company": {"type": "string"},
"email": {"type": "string", "format": "email"},
},
"required": ["name", "company"],
},
"issue": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"severity": {"type": "string", "enum": ["low", "medium", "high", "critical"]},
},
"required": ["summary", "severity"],
},
"actions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"task": {"type": "string"},
},
"required": ["owner", "task"],
},
},
},
"required": ["customer", "issue"],
}
text = (
"Support note: customer Priya Shah from Velocity Corp, email priya@velocity.io. "
"Dashboard charts won't load — severity high. "
"Action: Sarah to open an engineering ticket. Marcus to send ETA to customer."
)
result = extract(schema, text)
# → {
# "customer": {"name": "Priya Shah", "company": "Velocity Corp", "email": "priya@velocity.io"},
# "issue": {"summary": "Dashboard charts won't load", "severity": "high"},
# "actions": [
# {"owner": "Sarah", "task": "open an engineering ticket"},
# {"owner": "Marcus", "task": "send ETA to customer"}
# ]
# }Example 3 — Compliance finding with date and enum fields
schema = {
"type": "object",
"properties": {
"finding_id": {"type": "string"},
"standard": {"type": "string", "enum": ["ISO27001", "SOC2", "HIPAA", "GDPR", "PCI-DSS"]},
"clause": {"type": "string"},
"severity": {"type": "string", "enum": ["critical", "major", "minor", "observation"]},
"description": {"type": "string"},
"due_date": {"type": "string", "format": "date"},
"owner": {"type": "string"},
"status": {"type": "string", "enum": ["open", "in_remediation", "closed"]},
},
"required": ["standard", "severity", "description", "due_date"],
}
text = (
"Audit finding AF-2024-089: ISO27001 clause A.9.4.2 violation — "
"privileged accounts are not protected with MFA. Severity: major. "
"Remediation owner: Chen, due by 2024-09-30. Status: in_remediation."
)
result = extract(schema, text)
# → {
# "finding_id": "AF-2024-089",
# "standard": "ISO27001",
# "clause": "A.9.4.2",
# "severity": "major",
# "description": "Privileged accounts are not protected with MFA",
# "due_date": "2024-09-30",
# "owner": "Chen",
# "status": "in_remediation"
# }Example 4 — CPU inference (no GPU)
model = AutoModelForCausalLM.from_pretrained(
"senthil090/schemalm-v2-lfm2-350m",
torch_dtype=torch.float32, # fp32 for CPU
)
model.eval()
# Same extract() function as above — just slower (~5–15s per call on a modern CPU)Limitations
- Best on extraction tasks (reading text and pulling fields) — not a general-purpose chat model
- Weaker on very long schemas (>50 fields) or schemas with complex
pattern/formatconstraints - Sales numeric extraction and phone-call transcripts have lower accuracy (~50–60%) — targeted in v2.1 hardcase training
- Not suitable for code generation, reasoning, or creative tasks
Citation
@misc{schemalm-v2,
title = {SchemaLM v2 — JSON Schema-Conditioned Information Extraction},
author = {Senthil},
year = {2025},
url = {https://huggingface.co/senthil090/schemalm-v2-lfm2-350m}
}