Lacuna Altus

Mind the gap, before you apply.

Code, training pipeline and the Chrome extension: github.com/cucoleadan/lacuna-ai.

Lacuna Altus scores how a job posting fits a CV, in the browser, on the user's device. It is the large tier (395M parameters) of the Lacuna Chrome extension and the teacher of Lacuna Celer, the fast default.

It is ModernBERT-large fine-tuned with five heads on the LLM-labelled pairs below.

File Precision Size Note
model_fp16.onnx fp16 790 MB Identical predictions to PyTorch. Needs a GPU with about 2 GB free

A 4-bit build (297 MB) was tested and not published: it lost 0.05 verdict F1 on validation, most of what the large model is for.

What it does

Reads a CV and a job posting together (up to 4,096 tokens) and predicts, in one pass:

Head Classes Meaning
verdict apply / stretch / skip Overall fit for this candidate
location yes / unclear / no Can this person work where the job requires?
seniority below / at / above Candidate's level vs. the level the posting asks for
must_met no / yes Are the posting's must-have requirements covered?
line_met (per job line) none / yes / partial / no For each line of the posting: is it a requirement, and does the CV cover it?
line_must (per job line) probability Is that requirement a must-have?

The per-line heads are what make the gaps concrete: the job's must-have lines the CV does not cover.

It is a triage aid for job seekers, used on their own CV. It ranks postings so you read the promising ones first and says where a posting does not fit you. It does not decide anything.

Intended use and limits

  • For job seekers only. Do not use it to screen, rank or reject candidates. It was not built or evaluated for that, and hiring decisions about people need human judgement (and, in many places, legal safeguards).
  • Labels come from an LLM, not from recruiters. The model learned DeepSeek V4.1 Flash's judgement; it can be no better than that judgement, and has its biases.
  • Synthetic CVs. Training CVs were written by an LLM from realistic career paths; real CVs are messier. Normalise your CV the same way (see Input format).
  • English only, mostly tech, product, data, design, marketing, sales and operations roles, Europe-heavy locations.
  • Weak spots: "too senior for me" postings are often missed; personal taste (industry, company type) is not modelled; US-only remote roles are treated as "not eligible" for people outside the US, which is right for employees but not for contractors.
  • Location no should not hide a job outright unless the model is confident; the reference extension hides only at P(no) ≥ 0.8 and shows "unclear" otherwise.

Training data

  • About 82,000 public job postings from company career sites and job boards (not redistributed).
  • 2,217 synthetic CVs, each built from a sampled career path (roles taken from real postings, dates, location, languages, CV style and quality) and written by an LLM.
  • 12,699 labelled CV × posting pairs. Per CV: its target job, a similar job, one at another level, one outside the person's work location, plus over-qualified and stretch pairs. Split by CV: 10,171 train / 1,270 validation / 1,258 test.
  • Labels: DeepSeek V4.1 Flash read each pair with a fixed rubric (requirements, met / partial / not met, location eligibility, years and level); the verdict is derived from those answers by rules, not asked directly.
  • CVs pass through one normalisation (normalize_cv: block-aware text, ALL-CAPS headings, "- " bullets, contact details removed) in training and at inference.

Input format

[CLS] normalize_cv(cv) [SEP] job [SEP]        # truncated longest-first to 4,096 tokens
job = "Title: …\nCompany: …\nLocation: …\nWorkplace: …\n\n" + posting as markdown

ONNX inputs input_ids, attention_mask (int64, [batch, seq]). Outputs: verdict, location, seniority, must_met logits [batch, k]; line_met [batch, seq, 4] and line_must [batch, seq] per token. Average the per-token line outputs over each posting line's tokens (lines of 15+ characters), then softmax / sigmoid. Divide head logits by the temperatures in jobfit.json before the softmax for calibrated probabilities. Labels and temperatures are in jobfit.json.

Evaluation

Macro-F1 on held-out CVs (the test CVs never appear in training, also not in the distillation pairs).

Model Params Verdict Seniority Location Must-haves Req. lines
Lacuna Altus (this model) 395M 0.671 0.731 0.901 0.806 0.715
Lacuna Celer (distilled from Altus) 68M 0.686 0.718 0.903 0.810 0.709
Ettin-68M, same data, no distillation 68M 0.654 0.667 0.891 0.804 0.686

Test split, 1,258 pairs. Honest summary: after distillation, the small model is as good as this one on held-out data (differences are within seed noise; Altus is a single run). Altus is offered for users with a capable GPU who want the original rather than the copy, and it was the best model on validation verdict among all runs at selection time.

Training: 6 epochs, lr 3e-5, 8-bit AdamW, 4,096 tokens, 2× T4 GPUs, about 7 hours.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for lacuna-ai/lacuna-altus

Quantized
(27)
this model