Zero Proof AI commited on
Commit
c7f5d8a
·
verified ·
1 Parent(s): 5762c6e

Model 0 v0 — gemma-3-1b QLoRA intent adapter + card + eval figure

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ eval_report.png filter=lfs diff=lfs merge=lfs -text
37
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ base_model: unsloth/gemma-3-1b-it-bnb-4bit
3
+ library_name: peft
4
+ license: gemma
5
+ pipeline_tag: text-generation
6
+ tags:
7
+ - intent-detection
8
+ - agent-safety
9
+ - behavioral-attestation
10
+ - payments
11
+ - structured-output
12
+ - qlora
13
+ - lora
14
+ - peft
15
+ ---
16
+
17
+ # ZeroProof Intent Model 0 — `v0`
18
+
19
+ > **Status: v0 — first iteration, private preview.** A LoRA adapter over `gemma-3-1b`
20
+ > that reads a payment/agent conversation and emits **one structured intent object**.
21
+ > This is the model half of ZeroProof's behavioral-attestation stack: it decides *what
22
+ > the user actually intended* so a downstream layer can verify an agent's action matches it.
23
+
24
+ ![Model 0 evaluation](eval_report.png)
25
+
26
+ Built by **[ZeroProof](https://www.zeroproofai.com/)** — an LLM fine-tuning lab building the
27
+ trust layer for AI agents. This is our own data, our own training pipeline, and our own
28
+ rubric; that's the part that compounds.
29
+
30
+ ---
31
+
32
+ ## What it does
33
+
34
+ **In:** a short user↔assistant conversation. **Out:** one JSON object — `intent_detected`,
35
+ `core_type`, a per-intent `details` object, `confidence`, `reason`, `source_message_seqs`.
36
+ Ground truth comes from the **user's** turns only; assistant turns are context.
37
+
38
+ **7 intents (`core_type`):** `spend` (buy from a merchant) · `send` (pay a person/wallet) ·
39
+ `exchange` (swap/convert an asset) · `recur` (subscription/renewal) · `bill` (pay a
40
+ specific bill) · `reverse` (refund/return/dispute) · `none` (no actionable intent — the
41
+ safety class; a false positive here is a *false attestation*, our worst failure mode).
42
+
43
+ This is **routing-then-extraction into a schema**, not one-word classification.
44
+
45
+ ---
46
+
47
+ ## How well did the fine-tuning actually work?
48
+
49
+ Base and Model 0 scored in **one engine, on the identical 4,000 held-out conversations**
50
+ (greedy decoding, zero train/eval leakage). Only the adapter differs.
51
+
52
+ | Metric | Base `gemma-3-1b` | **Model 0** | Δ |
53
+ |---|---|---|---|
54
+ | Valid JSON | 87.5% | **99.8%** | +12.3 |
55
+ | Intent detected (act vs. `none`) | 35.0% | **81.9%** | +46.9 |
56
+ | **Core-type routing — macro** | **14.4%** | **70.0%** | **+55.6** |
57
+ | Details score | 19.7% | **61.3%** | +41.6 |
58
+
59
+ *Macro = equal weight per intent (the honest headline; the eval is 57% `none`, so a
60
+ none-weighted micro number would flatter us — we don't report it as the headline).*
61
+
62
+ ## Why this beats a base 1B — and why it's not luck
63
+
64
+ The base model **doesn't fail randomly. It collapses to one class.** Across all 4,000 eval
65
+ conversations, base predicted `spend` **80.7%** of the time — a `reverse` request, a
66
+ status question, a "yo what's up" — all labeled `spend`. It can emit valid JSON (87.5%)
67
+ but it **cannot route**: given the schema it latches onto the first/most-salient intent and
68
+ marks nearly everything actionable.
69
+
70
+ Fine-tuning teaches the two things the base lacks — **routing across all seven intents**
71
+ and **the `none` boundary**:
72
+
73
+ | Intent | n | Base | **Model 0** |
74
+ |---|---|---|---|
75
+ | none | 2286 | 0 | **90** |
76
+ | reverse | 300 | 0 | **74** |
77
+ | send | 300 | 10 | **72** |
78
+ | exchange | 214 | 0 | **68** |
79
+ | bill | 300 | 6 | **65** |
80
+ | spend | 300 | 84 | 63 |
81
+ | recur | 300 | 0 | **59** |
82
+
83
+ (`spend` is the *only* column where the base looks good — because it guesses `spend` for
84
+ everything. Model 0 trades that false strength for real coverage of all seven.)
85
+
86
+ ## Why a fine-tuned 1B instead of calling the frontier model?
87
+
88
+ This model runs in the **hot path of every agent transaction**. Against the frontier parent
89
+ (Opus-class), the argument is **economic and operational**, not "we're more accurate":
90
+
91
+ - **Cost / latency / energy** — a self-hosted 1B is orders of magnitude cheaper and faster
92
+ per call than a frontier API. Attestation only works if you can afford to run it on
93
+ *every* action.
94
+ - **Self-hostable & pinned** — a fixed adapter version you control, no external dependency
95
+ in the trust path.
96
+ - **Accuracy vs. the parent** — this is the **next milestone (Model 1)**, measured on this
97
+ exact eval. We do **not** claim to beat the parent on accuracy in v0; we claim to close
98
+ enough of the gap to be worth running everywhere. The honest comparison bars go here when
99
+ we run them.
100
+
101
+ ---
102
+
103
+ ## Usage
104
+
105
+ ```python
106
+ from peft import PeftModel
107
+ from transformers import AutoModelForCausalLM, AutoTokenizer
108
+
109
+ base = "unsloth/gemma-3-1b-it-bnb-4bit"
110
+ tok = AutoTokenizer.from_pretrained(base)
111
+ model = AutoModelForCausalLM.from_pretrained(base, device_map="auto")
112
+ model = PeftModel.from_pretrained(model, "zero-proof-ai/intent-model-0") # this adapter
113
+
114
+ # prompt = system (intent schema) + the conversation; model returns the intent JSON
115
+ ```
116
+
117
+ Served in production as an OpenAI-compatible vLLM endpoint (base + adapter loaded together).
118
+
119
+ ## Training
120
+
121
+ - **Method:** QLoRA SFT (rank-16 adapter, response-only supervision) on a frozen,
122
+ pinned `gemma-3-1b` snapshot. 2 epochs, effective batch 16. Trained on Modal.
123
+ - **Data:** 6,000 train / 4,000 eval, carved from a 13k label-blind distillation corpus.
124
+ Conversations are generated by a **two-agent role-play** where the models never see the
125
+ intent taxonomy, the JSON schema, or the word "classification" — so transcripts read
126
+ human, not schema-shaped. Generation and labeling are **separate passes**; labels come
127
+ from a tiered LLM-judge protocol (no regex/keyword filters). Near-duplicates removed
128
+ (MinHash-LSH) so eval gains aren't inflated. *Training data is proprietary; a hard,
129
+ balanced eval set will be released as an open benchmark.*
130
+
131
+ ---
132
+
133
+ ## What we noticed · what we're watching · where our rubric is weak
134
+
135
+ *(We publish these because the thinking is the product.)*
136
+
137
+ - **Noticed:** the base's failure is *structured* (collapse-to-`spend`), which is exactly
138
+ why routing supervision moves the needle so much.
139
+ - **Rubric weaknesses we own:** `details_score` is a strict exact-match and under-credits
140
+ near-right extractions; `confidence` is currently a teacher artifact (spikes on `0.95`),
141
+ not a calibrated signal — treat it as such.
142
+ - **What we're watching next (attestation metrics, not accuracy bars):** how often the
143
+ model catches a **human switching intent mid-conversation**, and how often it correctly
144
+ flags **"this needs verification"** before an action fires. These matter more for a trust
145
+ layer than raw accuracy, and they're built on our own data + rubric.
146
+
147
+ ## Limitations
148
+
149
+ v0, single base family, **synthetic** training data (inherits teacher blind spots).
150
+ Weakest intents are `recur` (59) and `spend` (63) — the status-vs-action and
151
+ purchase-vs-defer boundaries — which the next data wave targets. No frontier/peer or
152
+ long-context comparison is claimed yet (Model 1). Labels lean on LLM judgment.
153
+
154
+ ## Links
155
+
156
+ - Whitepaper (draft): *Label-Blind Distillation and Honest Evaluation for 1B-Scale
157
+ Payment-Intent Detection*
158
+ - Org: [Zero Proof AI](https://huggingface.co/zero-proof-ai) · [zeroproofai.com](https://www.zeroproofai.com/)
adapter_config.json ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": {
6
+ "base_model_class": "Gemma3ForCausalLM",
7
+ "parent_library": "transformers.models.gemma3.modeling_gemma3",
8
+ "unsloth_fixed": true
9
+ },
10
+ "base_model_name_or_path": "unsloth/gemma-3-1b-it-bnb-4bit",
11
+ "bias": "none",
12
+ "corda_config": null,
13
+ "ensure_weight_tying": false,
14
+ "eva_config": null,
15
+ "exclude_modules": null,
16
+ "fan_in_fan_out": false,
17
+ "inference_mode": true,
18
+ "init_lora_weights": true,
19
+ "layer_replication": null,
20
+ "layers_pattern": null,
21
+ "layers_to_transform": null,
22
+ "loftq_config": {},
23
+ "lora_alpha": 16,
24
+ "lora_bias": false,
25
+ "lora_dropout": 0.0,
26
+ "lora_ga_config": null,
27
+ "megatron_config": null,
28
+ "megatron_core": "megatron.core",
29
+ "modules_to_save": null,
30
+ "peft_type": "LORA",
31
+ "peft_version": "0.19.1",
32
+ "qalora_group_size": 16,
33
+ "r": 16,
34
+ "rank_pattern": {},
35
+ "revision": null,
36
+ "target_modules": [
37
+ "o_proj",
38
+ "up_proj",
39
+ "k_proj",
40
+ "gate_proj",
41
+ "q_proj",
42
+ "down_proj",
43
+ "v_proj"
44
+ ],
45
+ "target_parameters": null,
46
+ "task_type": "CAUSAL_LM",
47
+ "trainable_token_indices": null,
48
+ "use_bdlora": null,
49
+ "use_dora": false,
50
+ "use_qalora": false,
51
+ "use_rslora": false
52
+ }
adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:afbe49354d5a6f59a97d8fed9de22d5c32dfa4704fcdab07f602bbcbfdd9fea0
3
+ size 52231312
added_tokens.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "<image_soft_token>": 262144
3
+ }
chat_template.jinja ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{ bos_token }}
2
+ {%- if messages[0]['role'] == 'system' -%}
3
+ {%- if messages[0]['content'] is string -%}
4
+ {%- set first_user_prefix = messages[0]['content'] + '
5
+
6
+ ' -%}
7
+ {%- else -%}
8
+ {%- set first_user_prefix = messages[0]['content'][0]['text'] + '
9
+
10
+ ' -%}
11
+ {%- endif -%}
12
+ {%- set loop_messages = messages[1:] -%}
13
+ {%- else -%}
14
+ {%- set first_user_prefix = "" -%}
15
+ {%- set loop_messages = messages -%}
16
+ {%- endif -%}
17
+ {%- for message in loop_messages -%}
18
+ {%- if (message['role'] == 'user') != (loop.index0 % 2 == 0) -%}
19
+ {{ raise_exception("Conversation roles must alternate user/assistant/user/assistant/...") }}
20
+ {%- endif -%}
21
+ {%- if (message['role'] == 'assistant') -%}
22
+ {%- set role = "model" -%}
23
+ {%- else -%}
24
+ {%- set role = message['role'] -%}
25
+ {%- endif -%}
26
+ {{ '<start_of_turn>' + role + '
27
+ ' + (first_user_prefix if loop.first else "") }}
28
+ {%- if message['content'] is string -%}
29
+ {{ message['content'] | trim }}
30
+ {%- elif message['content'] is iterable -%}
31
+ {%- for item in message['content'] -%}
32
+ {%- if item['type'] == 'image' -%}
33
+ {{ '<start_of_image>' }}
34
+ {%- elif item['type'] == 'text' -%}
35
+ {{ item['text'] | trim }}
36
+ {%- endif -%}
37
+ {%- endfor -%}
38
+ {%- else -%}
39
+ {{ raise_exception("Invalid content type") }}
40
+ {%- endif -%}
41
+ {{ '<end_of_turn>
42
+ ' }}
43
+ {%- endfor -%}
44
+ {%- if add_generation_prompt -%}
45
+ {{ '<start_of_turn>model
46
+ ' }}
47
+ {%- endif -%}
eval_report.png ADDED

Git LFS Details

  • SHA256: 113e7f4cc3670aa16b4fa78fe08dc423734092886f65b283ffdd337947837b96
  • Pointer size: 131 Bytes
  • Size of remote file: 232 kB
special_tokens_map.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "boi_token": "<start_of_image>",
3
+ "bos_token": {
4
+ "content": "<bos>",
5
+ "lstrip": false,
6
+ "normalized": false,
7
+ "rstrip": false,
8
+ "single_word": false
9
+ },
10
+ "eoi_token": "<end_of_image>",
11
+ "eos_token": {
12
+ "content": "<end_of_turn>",
13
+ "lstrip": false,
14
+ "normalized": false,
15
+ "rstrip": false,
16
+ "single_word": false
17
+ },
18
+ "image_token": "<image_soft_token>",
19
+ "pad_token": {
20
+ "content": "<pad>",
21
+ "lstrip": false,
22
+ "normalized": false,
23
+ "rstrip": false,
24
+ "single_word": false
25
+ },
26
+ "unk_token": {
27
+ "content": "<unk>",
28
+ "lstrip": false,
29
+ "normalized": false,
30
+ "rstrip": false,
31
+ "single_word": false
32
+ }
33
+ }
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4667f2089529e8e7657cfb6d1c19910ae71ff5f28aa7ab2ff2763330affad795
3
+ size 33384568
tokenizer.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1299c11d7cf632ef3b4e11937501358ada021bbdf7c47638d13c0ee982f2e79c
3
+ size 4689074
tokenizer_config.json ADDED
The diff for this file is too large to render. See raw diff