Text Generation
Transformers
Safetensors
a2d-qwen3
fill-mask
conversational
Jforeverss commited on
Commit
8cf65f0
·
verified ·
1 Parent(s): d6e8d8e

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +598 -1
README.md CHANGED
@@ -1 +1,598 @@
1
- Pythagoras-Prover-Diffusion-4B
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model:
4
+ - Qwen/Qwen3-4B
5
+ pipeline_tag: text-generation
6
+ library_name: transformers
7
+ datasets:
8
+ - Pythagoras-LM/SFT_Dataset
9
+ ---
10
+ <div align="center">
11
+ <img src="https://github.com/Pythagoras-LM/Pythagoras-LM.github.io/blob/main/figures/pythagoras-without-background.png?raw=true" width="240" alt="Pythagoras-Prover logo"><br>
12
+ <h1>Pythagoras-Prover</h1>
13
+ </div>
14
+
15
+ <div align="center">
16
+
17
+ [![Website](https://img.shields.io/badge/%F0%9F%A4%96%20Homepage-Pythagoras-536af5?color=536af5&logoColor=white)](https://pythagoras-lm.github.io/)
18
+ [![Paper](https://img.shields.io/badge/Paper-arXiv-b31b1b?logo=arxiv&logoColor=white)](https://arxiv.org/abs/XXXX.XXXXX)
19
+ [![GitHub](https://img.shields.io/badge/GitHub-Code-black.svg?logo=github)](https://github.com/Pythagoras-LM/Pythagoras-Prover)
20
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
21
+
22
+ </div>
23
+
24
+
25
+ ## 1. Introduction
26
+
27
+ We introduce **Pythagoras-Prover**, a compute-efficient family of open-source large language models for formal theorem proving in Lean 4. The family comprises two autoregressive provers at 4B and 32B parameters, together with **Pythagoras-Prover-Diffusion**, the first diffusion-based theorem prover, which iteratively refines Lean proofs at inference time. All three models are artefacts of a single methodological approach: a scalable, Lean-verified synthetic data pipeline. At its centre is **Augmented Lean Formalisation** (ALF), a structured mutation scheme that expands a verified seed corpus into formal variants without per-instance Lean compilation, then re-uses them as a self-distillation signal during training. This design lets careful data construction stand in for raw scale, closing much of the gap between small open provers and their largest counterparts — without relying on inference-time self-correction.
28
+
29
+ <div align="center">
30
+ <img src="https://github.com/Pythagoras-LM/Pythagoras-LM.github.io/blob/main/figures/prover_fig1_v2_hi.png?raw=true" alt="Pythagoras-Prover benchmark overview" width="90%">
31
+ </div>
32
+
33
+
34
+ ## 2. Model Summary
35
+
36
+ ---
37
+
38
+ **A Lean-Verified Synthetic Data Pipeline**
39
+
40
+ - Natural-language problems from general-math and competition sources are autoformalised into Lean and gated on the type-checker using predominantly sub-30B open models, with an auto-informalisation and alignment step discarding faithful-but-wrong formalisations to yield a verified seed corpus partitioned into easy, medium, and hard tiers.
41
+ - A rubric-guided distillation stage re-prompts on each rejected instance to target the specific Lean type-checker error responsible for its failure, lifting autoformalisation success and roughly doubling the verified training set.
42
+
43
+ ---
44
+
45
+ **Model Training**
46
+
47
+ - LoRA-only supervised fine-tuning of Qwen3-4B and Qwen3-32B under an 8K context, paired with a dynamic proof-reasoning filter and a difficulty-ordered easy→medium→hard curriculum, followed by reinforcement learning with a Lean-compilation reward and a final continued-SFT stage on the ALF corpus.
48
+
49
+ ---
50
+
51
+ **Augmented Lean Formalisation**
52
+ - ALF emits one structured variant per category — simplification, generalisation, lemma proposal, proof-step decomposition, and reformulation — for every seed statement, replacing per-instance Lean verification with a cheap statement-alignment check and expanding the seed corpus into roughly 2M formal variants.
53
+ - The post-RL prover proves the mutations, and these self-distilled proofs form a corpus that trains both the autoregressive and diffusion provers from a single recipe.
54
+
55
+ ---
56
+
57
+ **The Smallest Efficient Open-Source Lean Theorem Prover**
58
+
59
+ - We train Pythagoras-Prover-4B, one of the smallest and most compute-efficient open-source Lean theorem provers to date, reaching 86.07% on MiniF2F-Test at Pass@32 and surpassing the prior state of the art at a fraction of its parameter count.
60
+ - Pythagoras-Prover-Diffusion adapts a block-diffusion formulation with a tactic-based masking objective aligned to the discrete reasoning steps of Lean — to our knowledge the first demonstration that a diffusion language model can verifiably solve Lean theorems at non-trivial rates.
61
+
62
+ ---
63
+
64
+ The resulting models set a new bar for compute-efficient formal proving. **Pythagoras-Prover-32B** achieves state-of-the-art performance among open-source provers, reaching **93.03%** on MiniF2F-Test and solving **93 of 672** problems on PutnamBench, while **Pythagoras-Prover-4B** outperforms DeepSeek-Prover-V2-671B on MiniF2F-Test despite being roughly **167× smaller** — with no self-correction or test-time reinforcement learning. We additionally release **MiniF2F-ALF**, an ALF-mutated companion benchmark on which every evaluated prover degrades.
65
+
66
+ ## 3. Benchmark Performance
67
+
68
+ We evaluate Pythagoras-Prover on three Lean 4 benchmarks — MiniF2F-Test, PutnamBench, and the MiniF2F-ALF benchmark we introduce — under a single unified protocol (Lean 4.9.0-rc1, a 30,000-token generation limit, and a verbatim-statement pass criterion). Across all three, Pythagoras-Prover matches or exceeds open-source provers an order of magnitude larger, and does so **without** inference-time self-correction or test-time reinforcement learning.
69
+
70
+
71
+ <div align="center">
72
+ <table style="margin: 0 auto;">
73
+ <thead>
74
+ <tr>
75
+ <th>Method</th>
76
+ <th>#Params</th>
77
+ <th>Pass@32</th>
78
+ <th>Pass@1024</th>
79
+ <th>Best (N)</th>
80
+ </tr>
81
+ </thead>
82
+ <tbody>
83
+ <tr><td>Goedel-Prover-SFT</td><td>7B</td><td>57.6</td><td>–</td><td>62.7 (3200)</td></tr>
84
+ <tr><td>STP</td><td>7B</td><td>–</td><td>–</td><td>67.6 (25600)</td></tr>
85
+ <tr><td>Kimina-Prover-Preview-72B</td><td>72B</td><td>68.85</td><td>–</td><td>80.74 (8192)</td></tr>
86
+ <tr><td>DeepSeek-Prover-V2-7B</td><td>7B</td><td>75.6</td><td>–</td><td>82.0 (8192)</td></tr>
87
+ <tr><td>DeepSeek-Prover-V2-671B</td><td>671B</td><td>82.4</td><td>–</td><td>88.9 (8192)</td></tr>
88
+ <tr><td>Kimina-Prover-8B-Distill</td><td>8B</td><td>77.86</td><td>–</td><td>–</td></tr>
89
+ <tr><td>Kimina-Prover-70B</td><td>70B</td><td>84.0</td><td>87.7</td><td>92.2 (TTRL)</td></tr>
90
+ <tr><td>Goedel-Prover-V2-8B</td><td>8B</td><td>84.6</td><td>87.9</td><td>90.2 (8192)</td></tr>
91
+ <tr><td>&nbsp;&nbsp;+ Self-Correction</td><td>8B</td><td>86.7</td><td>89.3</td><td>–</td></tr>
92
+ <tr><td>Goedel-Prover-V2-32B</td><td>32B</td><td>88.1</td><td>91.8</td><td>92.2 (8192)</td></tr>
93
+ <tr><td>&nbsp;&nbsp;+ Self-Correction</td><td>32B</td><td>90.4</td><td>92.6</td><td>–</td></tr>
94
+ <tr><td><strong>Pythagoras-Prover-4B</strong></td><td>4B</td><td><strong>86.07</strong></td><td><strong>88.11</strong></td><td><strong>89.75 (2048)</strong></td></tr>
95
+ <tr><td><strong>Pythagoras-Prover-32B</strong></td><td>32B</td><td><strong>89.75</strong></td><td><strong>92.62</strong></td><td><strong>93.03 (2048)</strong></td></tr>
96
+ </tbody>
97
+ </table>
98
+ <!-- table caption -->
99
+ <caption align="bottom"><strong>Table 1</strong>: <em>Pythagoras-Prover-4B exceeds DeepSeek-Prover-V2-671B's pass@8192 result (88.9%) at pass@2048 — a quarter of the budget and ~167× fewer parameters. Pythagoras-Prover-32B sets the strongest reported MiniF2F-Test pass rate without self-correction or test-time RL.</em></caption>
100
+ </div>
101
+
102
+
103
+ <br>
104
+
105
+ <div align="center">
106
+ <table style="margin: 0 auto;">
107
+ <thead>
108
+ <tr>
109
+ <th>#</th>
110
+ <th>Model</th>
111
+ <th>num-solved</th>
112
+ <th>compute</th>
113
+ </tr>
114
+ </thead>
115
+ <tbody>
116
+ <tr><td>1</td><td><strong>Pythagoras-Prover-32B</strong></td><td><strong>93</strong></td><td><strong>Pass@2048</strong></td></tr>
117
+ <tr><td>1</td><td><strong>Pythagoras-Prover-32B</strong></td><td><strong>59</strong></td><td><strong>Pass@64</strong></td></tr>
118
+ <tr><td>1</td><td><strong>Pythagoras-Prover-32B</strong></td><td><strong>48</strong></td><td><strong>Pass@32</strong></td></tr>
119
+ <tr><td>2</td><td>Goedel-Prover-V2-32B (self-correction mode)</td><td>86</td><td>Pass@184</td></tr>
120
+ <tr><td>2</td><td>Goedel-Prover-V2-32B (self-correction mode)</td><td>57</td><td>Pass@32</td></tr>
121
+ <tr><td>2</td><td>Goedel-Prover-V2-32B</td><td>43</td><td>Pass@32</td></tr>
122
+ <tr><td>3</td><td>DeepSeek-Prover-V2-671B</td><td>47</td><td>Pass@1024</td></tr>
123
+ <tr><td>3</td><td>DeepSeek-Prover-V2-671B</td><td>22</td><td>Pass@32</td></tr>
124
+ <tr><td>4</td><td>DSP+</td><td>23</td><td>Pass@128</td></tr>
125
+ <tr><td>5</td><td>Bourbaki</td><td>14</td><td>Pass@512</td></tr>
126
+ <tr><td>6</td><td>Kimina-Prover-7B-Distill</td><td>10</td><td>Pass@192</td></tr>
127
+ <tr><td>7</td><td>Self-play Theorem Prover</td><td>8</td><td>Pass@3200</td></tr>
128
+ <tr><td>8</td><td>Goedel-Prover-SFT</td><td>7</td><td>Pass@512</td></tr>
129
+ <tr><td>9</td><td>ABEL (closed-source)</td><td>7</td><td>Pass@596</td></tr>
130
+ </tbody>
131
+ </table>
132
+ <!-- table caption -->
133
+ <caption align="bottom"><strong>Table 2</strong>: <em>PutnamBench leaderboard (problems solved out of 657). Pythagoras-Prover-32B takes the top rank, solving 93 problems at Pass@2048 — 7 more than the previous best (Goedel-Prover-V2-32B, 86 at Pass@184 in self-correction mode) and nearly double DeepSeek-Prover-V2-671B's 47 at Pass@1024, despite being roughly 20× smaller. Seed-Prover (331 solved) is omitted from the ranked rows as it is closed-source with undisclosed test-time compute.</em></caption>
134
+ </div>
135
+
136
+ <br>
137
+
138
+ <div align="center">
139
+ <table style="margin: 0 auto;">
140
+ <thead>
141
+ <tr>
142
+ <th>Model</th>
143
+ <th>Pass@32</th>
144
+ </tr>
145
+ </thead>
146
+ <tbody>
147
+ <tr><td>DeepSeek-Prover-V2-671B</td><td>79.71</td></tr>
148
+ <tr><td>Goedel-Prover-V2-8B</td><td>82.58</td></tr>
149
+ <tr><td>Goedel-Prover-V2-32B</td><td>83.61</td></tr>
150
+ <tr><td><strong>Pythagoras-Prover-4B</strong></td><td><strong>83.19</strong></td></tr>
151
+ <tr><td><strong>Pythagoras-Prover-32B</strong></td><td><strong>85.04</strong></td></tr>
152
+ </tbody>
153
+ </table>
154
+ <!-- table caption -->
155
+ <caption align="bottom"><strong>Table 3</strong>: <em>Performance of current state-of-the-art provers on MiniF2F-ALF (Pass@32, %). As MiniF2F-ALF is introduced in this work, all results are evaluated by us under a unified setup.</em></caption>
156
+ </div>
157
+
158
+ <br>
159
+
160
+ <div align="center">
161
+ <table style="margin: 0 auto;">
162
+ <thead>
163
+ <tr>
164
+ <th>Model</th>
165
+ <th>Performance</th>
166
+ </tr>
167
+ </thead>
168
+ <tbody>
169
+ <tr><td>Pythagoras-Prover</td><td>86.1%</td></tr>
170
+ <tr><td>Pythagoras-Prover∗</td><td>74.6%</td></tr>
171
+ <tr><td><strong>Diffusion Pythagoras-Prover</strong></td><td><strong>63.3%</strong></td></tr>
172
+ </tbody>
173
+ </table>
174
+ <caption align="bottom"><strong>Table 4</strong>: <em>Diffusion Theorem Proving Performance (Pass@32). ∗ denotes the setting where training tokens are restricted to 4096 and evaluation is performed solely at 8192 tokens.</em></caption>
175
+ </div>
176
+
177
+ ## 4. Model & Dataset Downloads
178
+
179
+ We release our Pythagoras-Prover models, the training dataset and the new MiniF2F-ALF benchmark for future future research.
180
+
181
+ <div align="center">
182
+
183
+ | Model | Download |
184
+ | -------- | -------- |
185
+ | Pythagoras-Prover-32B | Coming Soon |
186
+ | Pythagoras-Prover-4B | [🤗Download](https://huggingface.co/Pythagoras-LM/Pythagoras-Prover-4B) |
187
+ | Pythagoras-Prover-Diffusion-4B | [🤗Download](https://huggingface.co/Pythagoras-LM/Pythagoras-Prover-Diffusion-4B) |
188
+
189
+ </div>
190
+
191
+
192
+ <div align="center">
193
+
194
+ | Dataset | Download |
195
+ | -------- | -------- |
196
+ | Pythagoras-Prover-SFT | [🤗Download](https://huggingface.co/datasets/Pythagoras-LM/SFT_Dataset) |
197
+ | Pythagoras-Prover-Distill-4B | Coming Soon |
198
+ | Pythagoras-Prover-Distill-32B | Coming Soon |
199
+ </div>
200
+
201
+ ## 5. Quick Start
202
+
203
+ This checkpoint is an A2D/BD3LM diffusion prover. It should be decoded with a diffusion sampler rather than `AutoModelForCausalLM.generate`. The snippet below is self-contained: it registers the A2D Qwen3 model class, implements the BD3LM block-diffusion sampler used for MiniF2F evaluation, loads the checkpoint with Transformers, and runs one Lean theorem-proving prompt. The decoding settings match the MiniF2F evaluation setting used for the reported Pass@32 result: bf16, SDPA attention, 8192 generated tokens, 8192 diffusion steps, and block size 8.
204
+
205
+ ````python
206
+ import copy
207
+ import math
208
+ import torch
209
+ import torch.nn.functional as F
210
+ import transformers
211
+ from transformers import AutoConfig, AutoModel, AutoModelForMaskedLM, AutoTokenizer
212
+ from transformers.cache_utils import DynamicCache
213
+ from transformers.modeling_attn_mask_utils import _prepare_4d_attention_mask
214
+ from transformers.modeling_outputs import BaseModelOutputWithPast
215
+
216
+
217
+ class A2DQwen3Config(transformers.Qwen3Config):
218
+ model_type = "a2d-qwen3"
219
+
220
+
221
+ class A2DQwen3Model(transformers.Qwen3Model):
222
+ def forward(
223
+ self,
224
+ input_ids=None,
225
+ attention_mask=None,
226
+ position_ids=None,
227
+ past_key_values=None,
228
+ inputs_embeds=None,
229
+ use_cache=None,
230
+ cache_position=None,
231
+ **kwargs,
232
+ ):
233
+ if (input_ids is None) == (inputs_embeds is None):
234
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
235
+
236
+ if inputs_embeds is None:
237
+ inputs_embeds = self.embed_tokens(input_ids)
238
+
239
+ if use_cache and past_key_values is None:
240
+ past_key_values = DynamicCache(config=self.config)
241
+
242
+ if cache_position is None:
243
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
244
+ cache_position = torch.arange(
245
+ past_seen_tokens,
246
+ past_seen_tokens + inputs_embeds.shape[1],
247
+ device=inputs_embeds.device,
248
+ )
249
+
250
+ if position_ids is None:
251
+ position_ids = cache_position.unsqueeze(0)
252
+
253
+ if not isinstance(causal_mask_mapping := attention_mask, dict):
254
+ if attention_mask is None:
255
+ attention_mask = torch.ones(
256
+ inputs_embeds.shape[:2],
257
+ device=inputs_embeds.device,
258
+ dtype=torch.long,
259
+ )
260
+
261
+ if not (isinstance(attention_mask, torch.Tensor) and attention_mask.ndim == 4):
262
+ attention_mask = _prepare_4d_attention_mask(attention_mask, self.dtype)
263
+
264
+ causal_mask_mapping = {"full_attention": attention_mask}
265
+ if self.has_sliding_layers:
266
+ causal_mask_mapping["sliding_attention"] = attention_mask
267
+
268
+ hidden_states = inputs_embeds
269
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
270
+
271
+ for decoder_layer in self.layers[: self.config.num_hidden_layers]:
272
+ hidden_states = decoder_layer(
273
+ hidden_states,
274
+ attention_mask=causal_mask_mapping[decoder_layer.attention_type],
275
+ position_ids=position_ids,
276
+ past_key_values=past_key_values,
277
+ use_cache=use_cache,
278
+ cache_position=cache_position,
279
+ position_embeddings=position_embeddings,
280
+ **kwargs,
281
+ )
282
+
283
+ hidden_states = self.norm(hidden_states)
284
+ return BaseModelOutputWithPast(
285
+ last_hidden_state=hidden_states,
286
+ past_key_values=past_key_values if use_cache else None,
287
+ )
288
+
289
+
290
+ class A2DQwen3LMHeadModel(transformers.Qwen3ForCausalLM):
291
+ config_class = A2DQwen3Config
292
+
293
+ def __init__(self, config):
294
+ transformers.Qwen3PreTrainedModel.__init__(self, config)
295
+ self.model = A2DQwen3Model(config)
296
+ self.vocab_size = config.vocab_size
297
+ self.lm_head = torch.nn.Linear(config.hidden_size, config.vocab_size, bias=False)
298
+ self.post_init()
299
+
300
+
301
+ AutoConfig.register("a2d-qwen3", A2DQwen3Config)
302
+ AutoModel.register(A2DQwen3Config, A2DQwen3LMHeadModel)
303
+ AutoModelForMaskedLM.register(A2DQwen3Config, A2DQwen3LMHeadModel)
304
+
305
+
306
+ def prepare_for_sampling(x, block_size, pad_token_id):
307
+ batch_size, seq_len = x.shape
308
+ device = x.device
309
+
310
+ valid = x != pad_token_id
311
+ pos_raw = torch.cumsum(valid.to(torch.long), dim=-1)
312
+ logical_pos = pos_raw - 1
313
+ position_ids = torch.where(valid, logical_pos, torch.zeros_like(logical_pos)).to(
314
+ device=device,
315
+ dtype=torch.long,
316
+ )
317
+
318
+ pos = torch.arange(seq_len, device=device)
319
+ block_ids = torch.div(pos, block_size, rounding_mode="floor")
320
+ block_ids = block_ids.view(1, seq_len).expand(batch_size, -1)
321
+ block_ids = torch.where(valid, block_ids, torch.full_like(block_ids, -1))
322
+
323
+ bid_q = block_ids.view(batch_size, 1, seq_len, 1)
324
+ bid_k = block_ids.view(batch_size, 1, 1, seq_len)
325
+ attention_mask = (bid_k <= bid_q) & (bid_q >= 0) & (bid_k >= 0)
326
+ return attention_mask, position_ids
327
+
328
+
329
+ def add_gumbel_noise(logits, temperature):
330
+ if temperature == 0:
331
+ return logits
332
+ logits = logits.to(torch.float64)
333
+ noise = torch.rand_like(logits, dtype=torch.float64)
334
+ return logits.exp() / ((-torch.log(noise)) ** temperature)
335
+
336
+
337
+ def get_num_transfer_tokens(mask_index, steps):
338
+ mask_num = mask_index.sum(dim=1, keepdim=True)
339
+ num_transfer_tokens = torch.zeros(
340
+ mask_num.size(0),
341
+ steps,
342
+ device=mask_index.device,
343
+ dtype=torch.int64,
344
+ )
345
+ for i in range(mask_num.size(0)):
346
+ for t, s, j in zip(range(steps, 0, -1), range(steps - 1, -1, -1), range(steps)):
347
+ reverse_transfer_prob = 1 - (s / t)
348
+ transfer = torch.round(mask_num[i, 0].to(torch.float64) * reverse_transfer_prob).to(torch.int64)
349
+ transfer = torch.minimum(transfer, mask_num[i, 0])
350
+ num_transfer_tokens[i, j] = transfer
351
+ mask_num[i, 0] -= transfer
352
+ if mask_num[i, 0].item() == 0:
353
+ break
354
+
355
+ rows = []
356
+ max_len = 0
357
+ for row in num_transfer_tokens:
358
+ nonzero = row[row > 0]
359
+ rows.append(nonzero)
360
+ max_len = max(max_len, nonzero.numel())
361
+
362
+ padded_rows = []
363
+ for row in rows:
364
+ if row.numel() < max_len:
365
+ row = torch.cat([row, torch.zeros(max_len - row.numel(), dtype=row.dtype, device=row.device)])
366
+ padded_rows.append(row)
367
+ return torch.stack(padded_rows, dim=0)
368
+
369
+
370
+ def diffusion_step_block(logits, x_block, mask_block, num_transfer_step, temperature):
371
+ batch_size, block_len, _ = logits.shape
372
+ logits_with_noise = add_gumbel_noise(logits, temperature=temperature)
373
+ x0 = torch.argmax(logits_with_noise, dim=-1)
374
+
375
+ probs = F.softmax(logits, dim=-1)
376
+ x0_probs = torch.gather(probs, dim=-1, index=x0.unsqueeze(-1)).squeeze(-1)
377
+
378
+ x0 = torch.where(mask_block, x0, x_block)
379
+ neg_inf = torch.full_like(x0_probs, -float("inf"))
380
+ confidence = torch.where(mask_block, x0_probs, neg_inf)
381
+
382
+ transfer = torch.zeros_like(x0, dtype=torch.bool)
383
+ for batch_idx in range(batch_size):
384
+ k = int(num_transfer_step[batch_idx].item())
385
+ if k <= 0:
386
+ continue
387
+ valid_count = (confidence[batch_idx] > -float("inf")).sum().item()
388
+ if valid_count == 0:
389
+ continue
390
+ _, selected = torch.topk(confidence[batch_idx], min(k, valid_count))
391
+ transfer[batch_idx, selected] = True
392
+
393
+ x_block_new = x_block.clone()
394
+ x_block_new[transfer] = x0[transfer]
395
+ return x_block_new
396
+
397
+
398
+ @torch.no_grad()
399
+ def generate_bd3lm(
400
+ model,
401
+ tokenizer,
402
+ input_ids,
403
+ max_new_tokens=8192,
404
+ steps=8192,
405
+ block_size=8,
406
+ temperature=1.0,
407
+ ):
408
+ if isinstance(input_ids, torch.Tensor):
409
+ input_ids = input_ids.tolist()
410
+ if input_ids and isinstance(input_ids[0], int):
411
+ input_ids = [input_ids]
412
+
413
+ device = model.device
414
+ mask_id = tokenizer.mask_token_id
415
+ pad_id = tokenizer.pad_token_id
416
+ eos_id = tokenizer.eos_token_id
417
+
418
+ inputs = [torch.tensor(ids, dtype=torch.long, device=device) for ids in input_ids]
419
+ prompt_lens = [ids.shape[0] for ids in inputs]
420
+ max_prompt_len = max(prompt_lens)
421
+ padded_prompt_len = math.ceil(max_prompt_len / block_size) * block_size
422
+
423
+ x = torch.full(
424
+ (len(inputs), padded_prompt_len),
425
+ pad_id,
426
+ dtype=torch.long,
427
+ device=device,
428
+ )
429
+ for batch_idx, ids in enumerate(inputs):
430
+ x[batch_idx, padded_prompt_len - ids.shape[0] : padded_prompt_len] = ids
431
+
432
+ num_blocks = math.ceil(max_new_tokens / block_size)
433
+ steps_per_block = math.ceil(steps / num_blocks)
434
+ done = torch.zeros((len(inputs),), dtype=torch.bool, device=device)
435
+ generated = 0
436
+
437
+ for _ in range(num_blocks):
438
+ if done.all():
439
+ break
440
+
441
+ prefix_len = x.shape[1]
442
+ cur_block_len = min(block_size, max_new_tokens - generated)
443
+ if cur_block_len <= 0:
444
+ break
445
+
446
+ prefix_attention_mask, prefix_position_ids = prepare_for_sampling(
447
+ x,
448
+ block_size=block_size,
449
+ pad_token_id=pad_id,
450
+ )
451
+ prefix_outputs = model(
452
+ x,
453
+ attention_mask=prefix_attention_mask,
454
+ position_ids=prefix_position_ids,
455
+ use_cache=True,
456
+ )
457
+ prefix_cache = prefix_outputs.past_key_values
458
+
459
+ new_block = torch.full(
460
+ (x.shape[0], cur_block_len),
461
+ mask_id,
462
+ dtype=torch.long,
463
+ device=device,
464
+ )
465
+ x = torch.cat([x, new_block], dim=1)
466
+ total_len = x.shape[1]
467
+
468
+ block_mask_index = x[:, -cur_block_len:] == mask_id
469
+ num_transfer_tokens = get_num_transfer_tokens(block_mask_index, steps_per_block)
470
+
471
+ full_attention_mask, full_position_ids = prepare_for_sampling(
472
+ x,
473
+ block_size=block_size,
474
+ pad_token_id=pad_id,
475
+ )
476
+ block_attention_mask = full_attention_mask[:, :, prefix_len:total_len, :]
477
+ block_position_ids = full_position_ids[:, prefix_len:total_len]
478
+
479
+ for step_idx in range(num_transfer_tokens.size(1)):
480
+ x_block = x[:, prefix_len:total_len]
481
+ mask_block = x_block == mask_id
482
+ if not mask_block.any():
483
+ break
484
+
485
+ logits = model(
486
+ x_block,
487
+ attention_mask=block_attention_mask,
488
+ position_ids=block_position_ids,
489
+ past_key_values=copy.deepcopy(prefix_cache),
490
+ use_cache=False,
491
+ ).logits
492
+
493
+ x[:, prefix_len:total_len] = diffusion_step_block(
494
+ logits=logits,
495
+ x_block=x_block,
496
+ mask_block=mask_block,
497
+ num_transfer_step=num_transfer_tokens[:, step_idx],
498
+ temperature=temperature,
499
+ )
500
+
501
+ if eos_id is not None:
502
+ done = done | (x[:, prefix_len:total_len] == eos_id).any(dim=1)
503
+ generated += cur_block_len
504
+
505
+ return x
506
+
507
+
508
+ def trim_completion(tokenizer, sequence_ids, prompt_ids):
509
+ sequence_ids = list(sequence_ids)
510
+ while sequence_ids and sequence_ids[0] == tokenizer.pad_token_id:
511
+ sequence_ids.pop(0)
512
+
513
+ start = len(prompt_ids)
514
+ end = len(sequence_ids)
515
+ stop_ids = {tokenizer.eos_token_id}
516
+ if getattr(tokenizer, "eot_token_id", None) is not None:
517
+ stop_ids.add(tokenizer.eot_token_id)
518
+
519
+ for idx in range(start, len(sequence_ids)):
520
+ if sequence_ids[idx] in stop_ids:
521
+ end = idx
522
+ break
523
+
524
+ return tokenizer.decode(sequence_ids[start:end], skip_special_tokens=True)
525
+
526
+ model_id = "Pythagoras-LM/Pythagoras-Prover-Diffusion-4B"
527
+
528
+ formal_statement = """
529
+ import Mathlib
530
+ import Aesop
531
+
532
+ set_option maxHeartbeats 0
533
+
534
+ open BigOperators Real Nat Topology Rat
535
+
536
+ /-- The volume of a cone is given by the formula $V = \frac{1}{3}Bh$, where $B$ is the area of the base and $h$ is the height. The area of the base of a cone is 30 square units, and its height is 6.5 units. What is the number of cubic units in its volume? Show that it is 65.-/
537
+ theorem mathd_algebra_478 (b h v : ℝ) (h₀ : 0 < b ∧ 0 < h ∧ 0 < v) (h₁ : v = 1 / 3 * (b * h))
538
+ (h₂ : b = 30) (h₃ : h = 13 / 2) : v = 65 := by
539
+ sorry
540
+ """.strip()
541
+
542
+ prompt = """
543
+ Complete the following Lean 4 code:
544
+
545
+ ```lean4
546
+ {}```
547
+
548
+ Before producing the Lean 4 code to formally prove the given theorem, provide a detailed proof plan outlining the main proof steps and strategies.
549
+ The plan should highlight key ideas, intermediate lemmas, and proof structures that will guide the construction of the final formal proof.
550
+ """.strip()
551
+
552
+ if not torch.cuda.is_available():
553
+ raise RuntimeError("CUDA is required for practical inference with this 4B diffusion checkpoint.")
554
+
555
+ tokenizer = AutoTokenizer.from_pretrained(model_id)
556
+ if tokenizer.eos_token != "<|im_end|>":
557
+ tokenizer.eos_token = "<|im_end|>"
558
+ if tokenizer.pad_token_id is None:
559
+ tokenizer.pad_token = tokenizer.eos_token or tokenizer.bos_token
560
+
561
+ model = AutoModelForMaskedLM.from_pretrained(
562
+ model_id,
563
+ dtype=torch.bfloat16,
564
+ device_map={"": 0},
565
+ attn_implementation="sdpa",
566
+ ).eval()
567
+
568
+ chat = [{"role": "user", "content": prompt.format(formal_statement)}]
569
+ input_ids = tokenizer.apply_chat_template(
570
+ chat,
571
+ tokenize=True,
572
+ add_generation_prompt=True,
573
+ enable_thinking=False,
574
+ )
575
+
576
+ outputs = generate_bd3lm(
577
+ model,
578
+ tokenizer,
579
+ input_ids,
580
+ max_new_tokens=8192,
581
+ steps=8192,
582
+ block_size=8,
583
+ temperature=1.0,
584
+ )
585
+
586
+ completion = trim_completion(tokenizer, outputs[0].tolist(), input_ids)
587
+ print(completion)
588
+ ````
589
+
590
+ # Cite
591
+ ```
592
+ @article{leang2026pythagoras,
593
+ title={Pythagoras-Prover: Advancing Efficient Formal Proving via Augmented Lean Formalisation},
594
+ author={Leang, Joshua Ong Jun and Zhao, Zheng and Stoian, Mihaela Catalina and Xu, Qiyuan and Li, Haonan and Li, Wenda and Cohen, Shay B. and Giunchiglia, Eleonora},
595
+ journal={arXiv preprint arXiv:2606.12594},
596
+ year={2026}
597
+ }
598
+ ```