Instructions to use manuelgscom/factusight-2B-GGUF with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use manuelgscom/factusight-2B-GGUF with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf manuelgscom/factusight-2B-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf manuelgscom/factusight-2B-GGUF:Q4_K_M
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf manuelgscom/factusight-2B-GGUF:Q4_K_M # Run inference directly in the terminal: llama cli -hf manuelgscom/factusight-2B-GGUF:Q4_K_M
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf manuelgscom/factusight-2B-GGUF:Q4_K_M # Run inference directly in the terminal: ./llama-cli -hf manuelgscom/factusight-2B-GGUF:Q4_K_M
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf manuelgscom/factusight-2B-GGUF:Q4_K_M # Run inference directly in the terminal: ./build/bin/llama-cli -hf manuelgscom/factusight-2B-GGUF:Q4_K_M
Use Docker
docker model run hf.co/manuelgscom/factusight-2B-GGUF:Q4_K_M
- LM Studio
- Jan
- vLLM
How to use manuelgscom/factusight-2B-GGUF with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "manuelgscom/factusight-2B-GGUF" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "manuelgscom/factusight-2B-GGUF", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/manuelgscom/factusight-2B-GGUF:Q4_K_M
- Ollama
How to use manuelgscom/factusight-2B-GGUF with Ollama:
ollama run hf.co/manuelgscom/factusight-2B-GGUF:Q4_K_M
- Unsloth Desktop
- Pi
How to use manuelgscom/factusight-2B-GGUF with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf manuelgscom/factusight-2B-GGUF:Q4_K_M
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "manuelgscom/factusight-2B-GGUF:Q4_K_M" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use manuelgscom/factusight-2B-GGUF with Docker Model Runner:
docker model run hf.co/manuelgscom/factusight-2B-GGUF:Q4_K_M
- Lemonade
How to use manuelgscom/factusight-2B-GGUF with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull manuelgscom/factusight-2B-GGUF:Q4_K_M
Run and chat with the model
lemonade run user.factusight-2B-GGUF-Q4_K_M
List all available models
lemonade list
- Hermes Agent
How to use manuelgscom/factusight-2B-GGUF with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf manuelgscom/factusight-2B-GGUF:Q4_K_M
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default manuelgscom/factusight-2B-GGUF:Q4_K_M
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use manuelgscom/factusight-2B-GGUF with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf manuelgscom/factusight-2B-GGUF:Q4_K_M
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "manuelgscom/factusight-2B-GGUF:Q4_K_M" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
factusight-2B (GGUF)
A fine-tune of Qwen/Qwen3.5-2B that reads an invoice or receipt image (or the pages of a PDF) and returns its key data as a flat JSON object: invoice number, dates, issuer and receiver (name, tax ID, address, email, phone), subtotal, VAT, Spanish IRPF withholding, total, description, payment method and currency. It is the Flash model of FactuSight, a desktop application that extracts invoice data 100 % offline, with llama.cpp on the user's own machine.
- Author: Manuel García Sanz · Website: https://factusight.com
- Format: GGUF, Q4_K_M (1,274.4 MB) + vision projector (mmproj) F16 (668.2 MB)
- In FactuSight: Flash mode (Modo Flash), the fastest tier, included in the Individual (desktop) edition
- Speed: about 1.5 s per one-page invoice on an RTX 5070 Ti, using ~2.9 GB of VRAM (details below)
- Runs with: llama.cpp (
llama-server,llama-cli), fully offline
| Model | FactuSight tier | Q4_K_M + mmproj | VRAM (approx.) | Use it when |
|---|---|---|---|---|
| factusight-2B | Flash | 1.94 GB | ~2.9 GB | low-end GPUs or CPU only, maximum speed |
| factusight-4B | Standard | 3.38 GB | ~4.7 GB | the balanced default |
| factusight-9B | High Precision | 6.55 GB | ~7.4 GB | GPUs with 8 GB or more, hardest documents |
All three share the same prompt contract, output format and client script; only the size changes.
Resumen en español
factusight-2B es un ajuste fino de Qwen3.5-2B que lee la imagen de una factura o un ticket (o las páginas de un PDF) y devuelve sus datos clave en un JSON plano: número de factura, fechas, emisor y receptor (nombre, NIF/CIF, dirección, email, teléfono), base imponible, IVA, retención de IRPF, total, descripción, forma de pago y moneda. Es el modelo Flash (Modo Flash) de FactuSight, una aplicación de escritorio que extrae los datos de las facturas sin conexión, con llama.cpp en el propio equipo: ningún documento sale de la máquina. Está pensado sobre todo para facturas españolas (IVA, IRPF, NIF/CIF/NIE). Para usarlo por tu cuenta, arranca llama-server con el modelo y su mmproj y ejecuta python factusight_extract.py factura.pdf (instrucciones abajo). El prompt es fijo: el modelo se entrenó con ese contrato exacto. Puede equivocarse: revisa siempre los datos antes de contabilizarlos. Licencia CC BY-NC 4.0 (uso no comercial); para uso comercial, escribe a contact@factusight.com.
What it is and intended use
invoice.pdf / .jpg / .png
│
▼
┌──────────────────────────┐ PDF: first 3 pages, rendered at 300 DPI
│ 1. render pages │ (optional: embedded PDF text as auxiliary context)
└────────────┬─────────────┘
▼
┌──────────────────────────────────────────────────────────┐
│ 2. prompt = fixed system prompt │
│ + page images │
│ + requested fields + field-specific rules │
└────────────┬─────────────────────────────────────────────┘
▼
┌──────────────────────────┐
│ 3. factusight-2B │ llama.cpp + mmproj, thinking disabled
└────────────┬─────────────┘
▼
flat JSON: up to 22 fields, each a string or null
│
▼ (inside the FactuSight app only)
validation heuristics ─► review screen ─► encrypted local DB ─► CSV / XLSX
- One task, one contract. The model was trained only to answer the prompt described below with a JSON object. It is not a chat model and does not explain or reason.
- Field selection. You ask for any subset of the 22 fields; the model answers with exactly those keys, in canonical order.
- Honest nulls. A field that is not printed on the document comes back as
nullinstead of being guessed (for example, no due date, no IRPF line, no email). - Normalized values. Amounts use a dot and two decimals (
"1234.50"), dates useDD/MM/YYYY, tax IDs keep their letters. - Local. Everything runs on your machine; no document or result is sent anywhere.
Example
Sample invoice with fictitious customer data, also published as examples/pccomponentes.pdf. Note: this layout was part of the training data, so it illustrates the input and output format; it is not a measure of accuracy.
Real output of factusight-2B (Q4_K_M, image only, all 22 fields) with the command in Quick start:
{
"invoice_number": "402024/042028",
"issue_date": "20/11/2024",
"due_date": null,
"issuer_name": "PC Componentes y Multimedia SLU",
"issuer_tax_id": "B73347494",
"issuer_address": "Avda Europa, 2-3, Pol.Ind. Las Salinas, 30840, Alhama de Murcia, Murcia",
"issuer_email": null,
"issuer_phone": null,
"receiver_name": "Garcia Perez, Fran",
"receiver_tax_id": "15087658A",
"receiver_address": "Celso Emilio Ferreiro 71, Población: Zaragoza, CP: 50695",
"receiver_email": null,
"receiver_phone": "635336688",
"subtotal": "247.85",
"tax_rate": "21.00",
"tax_amount": "52.05",
"irpf_tax_rate": null,
"irpf_tax_amount": null,
"total": "299.90",
"description": "AOC Q27G3XMN/BK 27\" Mini LED VA QHD HDR1000 180Hz, Abono de portes, Portes",
"payment_method": "Paypal",
"currency": "EUR"
}
Files
| File | What it is | Size (bytes) | SHA-256 |
|---|---|---|---|
factusight-2B-1.0.0-Q4_K_M.gguf |
Language model, Q4_K_M | 1,274,396,704 | 877ad8281f73e0aaa8a697ba2010e9cfdd87241d963c7b85fffa96b9372c7b68 |
factusight-2B-1.0.0-mmproj-F16.gguf |
Vision projector (mmproj), F16. Required | 668,226,816 | f48cef0ebdfda0b1e704510e94138de4d66ac7dc03bdc842065ba7bb3c8c3888 |
factusight_extract.py |
Reference client for llama-server (stdlib only); --print-prompt prints the exact user prompt for any fields and page count |
||
prompts/system.txt |
System prompt, exact text | ||
prompts/user_all_fields.txt |
User prompt for a one-page document, all 22 fields | ||
examples/pccomponentes.pdf |
Sample invoice used above | ||
LICENSE, NOTICE, LICENSE.Qwen3.5 |
CC BY-NC 4.0; base model notice (Apache-2.0) |
sha256sum factusight-2B-1.0.0-Q4_K_M.gguf factusight-2B-1.0.0-mmproj-F16.gguf
These are byte-for-byte the files used by the FactuSight application. Use the F16 mmproj: it is the one the application uses and the one tested here.
Quick start
Tested with llama.cpp b8664 (CUDA). Any recent build with Qwen3.5 vision support and the --image-max-tokens option should work.
Download:
pip install -U huggingface_hub
hf download manuelgscom/factusight-2B-GGUF --local-dir factusight-2B
cd factusight-2B
Option A: llama-server + the reference client (recommended)
llama-server -m factusight-2B-1.0.0-Q4_K_M.gguf \
--mmproj factusight-2B-1.0.0-mmproj-F16.gguf \
--jinja --reasoning off -c 12288 -ngl 99 --image-max-tokens 1280 --port 8080
pip install pypdfium2 pillow # only needed for PDF input
python factusight_extract.py examples/pccomponentes.pdf
python factusight_extract.py my_ticket.jpg --fields invoice_number,issue_date,total,payment_method
python factusight_extract.py my_invoice.pdf --with-pdf-text # also send the PDF's embedded text
factusight_extract.py renders PDFs at 300 DPI (first 3 pages), builds the exact prompt for the fields you ask for, calls the OpenAI-compatible endpoint /v1/chat/completions and prints the JSON. Read it to integrate the model into your own code: it is a single short file that depends only on the Python standard library (plus pypdfium2 for PDFs).
Option B: one shot with llama-cli
For a one-page image with all 22 fields, the prompt files are ready to use:
pdftoppm -r 300 -png -singlefile examples/pccomponentes.pdf page # -> page.png
llama-cli -m factusight-2B-1.0.0-Q4_K_M.gguf \
--mmproj factusight-2B-1.0.0-mmproj-F16.gguf \
-ngl 99 -c 12288 --image-max-tokens 1280 --jinja --reasoning off \
--temp 0.2 --top-k 10 --top-p 0.9 --seed 42 -n 2048 \
-sysf prompts/system.txt --image page.png -p "$(cat prompts/user_all_fields.txt)" \
-st --simple-io --no-display-prompt
Other fields or several pages. The user prompt changes with the fields you ask for (and with the page count), so generate it with --print-prompt, which only prints the text and needs no server. Pass the pages in order, comma-separated:
python factusight_extract.py --print-prompt --pages 2 \
--fields invoice_number,issue_date,issuer_name,issuer_tax_id,total > user.txt
llama-cli -m factusight-2B-1.0.0-Q4_K_M.gguf \
--mmproj factusight-2B-1.0.0-mmproj-F16.gguf \
-ngl 99 -c 12288 --image-max-tokens 1280 --jinja --reasoning off \
--temp 0.2 --top-k 10 --top-p 0.9 --seed 42 -n 576 \
-sysf prompts/system.txt --image page1.png,page2.png -p "$(cat user.txt)" \
-st --simple-io --no-display-prompt
-n is the output-token cap the application uses, clamp(256 + 64 × fields, 512, 2048): 576 for 5 fields, 2048 for 22. With a PDF as argument (python factusight_extract.py invoice.pdf --print-prompt --with-pdf-text), the page count and the PDF text are taken from the file.
Without a GPU, use -ngl 0: it works, but expect tens of seconds per document.
Prompt contract
The model was trained on this format and nothing else. Change it and accuracy drops.
Chat structure
Qwen3.5's own chat template (--jinja), thinking disabled (--reasoning off, or chat_template_kwargs: {"enable_thinking": false}), and exactly two messages:
- system: the fixed system prompt.
- user: first one image per page (at most 3, in order), then one text part.
System prompt
Exact text (ends with a newline), also in prompts/system.txt:
You are a document data extraction system.
Analyze the provided invoice/receipt image and extract the requested fields.
Return ONLY a valid JSON object with the field IDs as keys.
Do NOT include any explanation, markdown formatting, or additional text.
General rules:
- If a field is not found in the document, set its value to null. Do not guess or invent values.
- Numbers: use dot as decimal separator, two decimal places, no thousand separators.
- Dates: format as DD/MM/YYYY.
User text
Up to three blocks, joined with a blank line ("\n\n"):
┌───────────────────────────────────────────────────────────────┐
│ [only if pages > 1] multi-page notice │
├───────────────────────────────────────────────────────────────┤
│ [optional] embedded PDF text, as auxiliary context │
├───────────────────────────────────────────────────────────────┤
│ [always] field list + field-specific rules │
└───────────────────────────────────────────────────────────────┘
1. Multi-page notice (only for 2 or 3 pages; {N} is the page count):
This invoice/receipt spans {N} pages. The images above are consecutive pages of the same document. Extract data from ALL pages.
IMPORTANT: The invoice data (issuer, receiver, amounts) is typically on page 1. Ignore contract terms, privacy policies, legal notices, or appendices on subsequent pages — do NOT use them to identify the issuer or receiver.
2. PDF text (optional; for digital PDFs). Image-only works well, as in the example above. If you add it, the sentence in brackets goes only for multi-page documents, and each page's text is preceded by a [Page N] line:
Use the image as the source of truth. The extracted PDF text below is auxiliary and may be incomplete or noisy.[ For multi-page documents, the text includes [Page N] markers to show which PDF page each text block comes from.]
--- Extracted PDF text ---
{text}
--- End of extracted PDF text ---
3. Field list and rules. The heading, then one line per requested field in the canonical order of the table below (whatever order you ask for them in), ending with a newline:
Extract the following fields from this document:
- "invoice_number": Invoice identification number
- "issue_date": Date when the document was issued
...
| # | Field ID | Description line (exact text after - "id": ) |
Value |
|---|---|---|---|
| 1 | invoice_number |
Invoice identification number | text |
| 2 | issue_date |
Date when the document was issued | DD/MM/YYYY |
| 3 | due_date |
Payment deadline date | DD/MM/YYYY |
| 4 | issuer_name |
Name or business name of the issuer | text |
| 5 | issuer_tax_id |
Tax identification number of the issuer (seller/supplier issuing the invoice). CIF/NIF/DNI etc. | text |
| 6 | issuer_address |
Full postal address of the issuer (including postal code, city, province/state, country, etc.) | text |
| 7 | issuer_email |
Email address of the issuer | text |
| 8 | issuer_phone |
Phone number of the issuer | text |
| 9 | receiver_name |
Name or business name of the receiver | text |
| 10 | receiver_tax_id |
Tax identification number of the receiver (customer/buyer/bill-to party). CIF/NIF/DNI etc. | text |
| 11 | receiver_address |
Full postal address of the receiver (including postal code, city, province/state, country, etc.) | text |
| 12 | receiver_email |
Email address of the receiver | text |
| 13 | receiver_phone |
Phone number of the receiver | text |
| 14 | subtotal |
Amount before taxes | amount "0.00" |
| 15 | tax_rate |
VAT percentage applied | percent "21.00" |
| 16 | tax_amount |
VAT amount | amount |
| 17 | irpf_tax_rate |
IRPF withholding percentage applied (Spanish income tax retention) | percent |
| 18 | irpf_tax_amount |
IRPF withholding amount (Spanish income tax retention) | amount, positive |
| 19 | total |
Total invoice amount (subtotal + taxes - IRPF withholding if present) | amount |
| 20 | description |
General description of the service or product | text |
| 21 | payment_method |
Payment method indicated | text |
| 22 | currency |
Document currency (EUR, USD, etc.) | ISO code |
Then, if at least one rule applies, a blank line, the heading IMPORTANT — field-specific rules: and only the rules whose trigger fields were requested, in this order, one per line, ending with a newline:
Field-specific rules (exact text) and their triggers
| Included if you request | Rule line |
|---|---|
issue_date |
- issue_date: Look for words such as issue date, invoice date, or billing date. |
due_date |
- due_date: Look for words such as due date, deadline, vencimiento, etc. If you DO NOT found them, return null |
payment_method |
- payment_method: return the instrument (KEYWORDS: card, transfer, PayPal, cash, direct debit, RedSys, Tarjeta, Domiciliación SEPA, etc). Do NOT return status phrases (paid, completed, abonado, pagada). Rule: If you find a KEYWORD near amounts or payment terms, that's the payment method. If only status is present, then return null. |
subtotal |
- subtotal: net amount after discounts/offers, before tax (base imponible). If the document shows both a gross/merchandise amount and a separate net amount (after discounts), always return the net amount. Look for labels such as 'Net Amount', 'Base Imponible', 'Importe Neto', 'Neto', 'Subtotal'. |
tax_rate or tax_amount |
- tax_rate and tax_amount: return the value ONLY if a numeric tax rate or tax amount is explicitly printed on the document. If the tax line shows 0% or 0.00, return that value. But if the document only states it is tax-exempt (e.g. 'exenta de IVA', 'VAT exempt') without printing a numeric rate or amount, or if no tax line is visible at all, return null — do NOT infer or calculate a value. |
irpf_tax_rate or irpf_tax_amount |
- irpf_tax_rate and irpf_tax_amount: return the value ONLY if a numeric IRPF withholding rate or amount is explicitly printed on the document. If no IRPF or withholding line is visible at all, return null — do NOT infer 0 or 0.00. Always return positive values (e.g. 275.06, not -275.06). |
any issuer_* or receiver_* field |
- issuer_* data and receiver_* data: If you find a block that is labeled with or near words such as 'factura para', 'facturar a', 'bill to', 'customer', or similar, that block is ALWAYS the receiver, and the other block is ALWAYS the issuer. Ignore footer/legal/GDPR/provider text and any company mentioned only there. If the invoice was generated by an invoicing platform (e.g. MiAutónomo, FacturaDirecta, Holded, Quaderno), the issuer is the person or company that bills, NOT the platform — ignore platform branding, legal footers, and DPO/privacy contacts. |
issuer_name or receiver_name |
- issuer_name and receiver_name: return null if no explicit person or company name is found. Do NOT use a tax ID, email address, or other identifier as a substitute for a name. |
issuer_phone or receiver_phone |
- issuer_phone and receiver_phone: If you find 9 digit numbers (without letters) near the party block, that is the phone number of the party. If you find more than one phone number for one party, assign the first one you find. |
issuer_email or receiver_email |
- issuer_email and receiver_email: must contain an '@' symbol. |
issuer_tax_id or receiver_tax_id |
- issuer_tax_id and receiver_tax_id: A tax ID (CIF/NIF/DNI/NIE) is an alphanumeric code like 12345678A, B12345678, or X1234567L — it is NOT part of the address. If you see such a code in a party block, extract it as the tax_id. Include the country/letters prefix or suffix. Assign each party's own tax ID - do not swap or duplicate it if one is missing. |
total |
- total: return the invoice total (subtotal + taxes - IRPF). Do NOT return the 'Amount Due' or balance remaining after payment — on paid invoices this is often 0.00, which is NOT the total. |
invoice_number |
- invoice_number: return only the number/code, without labels such as Invoice, Factura, Nº, Núm., No., or #. |
prompts/user_all_fields.txt is the complete user text for a one-page document with all 22 fields; python factusight_extract.py --print-prompt --fields ... --pages N prints any other combination exactly.
Sampling
The values used by the FactuSight application:
| Parameter | Value |
|---|---|
| temperature | 0.2 |
| top_k / top_p | 10 / 0.9 |
| repetition penalties | none |
| seed | 42 |
| max output tokens | clamp(256 + 64 × number_of_fields, 512, 2048) |
| context | 12288 tokens |
| image tokens | up to 1280 per page (--image-max-tokens 1280) |
| reasoning / thinking | off |
| grammar / JSON schema | none needed |
Output
One JSON object, pretty-printed, with exactly the requested keys in canonical order. Every value is a string or null: no numbers, no nested objects, no line items.
- Amounts and percentages: dot as decimal separator, two decimals, no thousands separator (
"1234.50","21.00"). IRPF values are positive. - Dates:
DD/MM/YYYY. nullwhen the field is not printed on the document.
The FactuSight application parses the answer tolerantly (it strips code fences or stray text if they ever appear) and then runs its own validation heuristics: tax-ID plausibility, tax maths consistency, phone and email checks, tax IDs found inside addresses, and so on. If you integrate the model yourself, validate its output too, especially amounts and tax IDs, before using it for accounting.
Hardware and speed
Measured by the author with factusight_extract.py on the example invoice above (one page, all 22 fields, image only), llama.cpp b8664, all layers on the GPU, warm server:
| Model | GPU | Time per invoice | VRAM used |
|---|---|---|---|
| factusight-2B | RTX 5070 Ti 16 GB | ~1.5 s | ~2.9 GB |
| factusight-4B | RTX 5070 Ti 16 GB | ~2.5 s | ~4.7 GB |
| factusight-9B | RTX 5070 Ti 16 GB | ~4.2 s | ~7.4 GB |
Times grow with the number of pages and fields. A rough guide for VRAM: model file + mmproj + about 1 GB. The models also run on CPU or with partial offload (-ngl N), more slowly.
Training
| Base | Qwen/Qwen3.5-2B (loaded from the unsloth/Qwen3.5-2B mirror) |
| Method | LoRA in bf16 (not QLoRA) on the vision encoder and the language model (attention + MLP), merged into the weights. 23.1 M (1.03 %) trainable parameters |
| LoRA | rank 16, alpha 16, dropout 0 |
| Hyperparameters | lr 1e-4 (linear, 10 warmup steps), 8-bit AdamW, weight decay 0.01, effective batch 8 (8 per device × 1 accumulation), 1 epoch (451 steps), max sequence length 8192, 256–1280 image tokens per page; loss on the JSON answer only |
| Software | Unsloth 2026.4.1, TRL SFTTrainer, Transformers 5.5.0, PyTorch 2.10 (CUDA 12.8) |
| Hardware | 1× RTX 5090 (32 GB), 51 minutes |
| Export | Merged, converted to GGUF and quantized to Q4_K_M; vision projector exported in F16 |
Data. 3,608 training examples built from 1,052 documents: 1,000 synthetic invoices and receipts plus 52 real invoices with their data modified, reviewed by hand. Each document is paired with 4 prompts that ask for different field subsets (5–8, 9–13, 14–18 and all 22 fields), so the model learns to answer exactly what is asked. The synthetic documents are rendered from HTML templates (48 template packs, 14 kinds of business: telecom, SaaS, utilities, restaurants, fuel stations, transport, hotels, marketplaces…) at 300 DPI, mostly in Spanish with some English and mixed documents, one to three pages, about 40 % in greyscale, a mix of digital PDFs (with and without usable text) and scanned-looking images, and about 20 % with mild degradation (small rotation, blur, contrast and brightness changes, edge shadows, JPEG recompression). Spanish tax content throughout: VAT, IRPF withholding, NIF/CIF/NIE. A separate set of 600 examples from 150 held-out synthetic documents was used for validation during training. The training data and the training recipe are not published.
Limitations
- Spanish invoices first. Trained mainly on invoices issued in Spain (VAT, IRPF, NIF/CIF/NIE). Other countries, languages and tax systems work only partially; simplified receipts and tickets are less covered than full invoices.
- Fixed set of fields. 22 header-level fields only: no line items, no multiple tax rates in one document (one
tax_rate/tax_amount), no free-form questions. - Up to 3 pages, as trained. Longer documents should be trimmed to the pages that hold the invoice data.
- It can be wrong. Misread digits, swapped parties or a missing field are possible, more often with blurry photos, handwriting or unusual layouts. Always review the data before using it for accounting or tax purposes. It is not tax advice.
- One prompt, no thinking. Other prompts, chat use or reasoning mode are unsupported.
- Requires the mmproj. The GGUF alone is text-only; without the vision projector the model cannot see the document.
Changes from the base model
- Fine-tuned with LoRA (merged) for the contract above.
- Tokenizer and chat template unchanged from Qwen3.5-2B.
- Converted to GGUF, quantized to Q4_K_M; vision projector in F16;
general.*metadata rewritten (nameFactuSight-Flash, author FactuSight, version 1.0.0).
License
The weights in this repository are released under CC BY-NC 4.0: free to use, share and adapt for non-commercial purposes, with attribution. For commercial use, contact contact@factusight.com.
The base model, Qwen3.5-2B by the Qwen team (Alibaba Cloud), is licensed under the Apache License 2.0; its license text is in LICENSE.Qwen3.5 and the attribution in NOTICE.
Citation
@misc{factusight_2b_2026,
title = {factusight-2B: a fine-tuned Qwen3.5-2B for offline invoice data extraction},
author = {García Sanz, Manuel},
year = {2026},
howpublished = {\url{https://huggingface.co/manuelgscom/factusight-2B-GGUF}}
}
- Downloads last month
- -
4-bit
