ydy9038074 commited on
Commit
4bbff32
·
verified ·
1 Parent(s): b79ebf2

Publish Modilify Mk2 step 4400

Browse files
.gitattributes CHANGED
@@ -1,35 +1,2 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
  *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz 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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .DS_Store
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ .cache/
6
+ .env
7
+ /.upload-fast.sh
LICENSE ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Modilify Non-Commercial Model License 1.0
2
+
3
+ Copyright 2026 Modilify
4
+
5
+ 1. Definitions
6
+
7
+ "Model" means the weights, configuration, inference code, tokenizer, processor,
8
+ architecture definitions, and documentation distributed under or with this license.
9
+ "Derivative Model" means any modified, fine-tuned, distilled, merged, quantized,
10
+ extended, or otherwise adapted version of the Model.
11
+
12
+ "Output" means any text, image, audio, video, 3D asset, latent state, synthetic
13
+ data, or other content generated by executing, querying, or running the Model
14
+ or a Derivative Model.
15
+
16
+ "Non-Commercial Use" means any use, exercise of rights, or deployment conducted
17
+ strictly for personal learning, educational purposes, academic research,
18
+ non-profit scientific investigation, or non-commercial testing and evaluation,
19
+ which does not directly or indirectly generate revenue, commercial advantage,
20
+ or monetary compensation.
21
+
22
+ "You" means the individual or legal entity exercising permissions under this
23
+ license.
24
+
25
+ "High-Risk Use" means a use that can materially affect a person's safety,
26
+ liberty, access to essential services, employment, housing, credit, education,
27
+ legal rights, or medical care, or that controls critical infrastructure,
28
+ weapons, or large-scale biometric surveillance.
29
+
30
+ 2. Copyright Grant
31
+
32
+ Subject to the terms and conditions of this license, Modilify grants You a
33
+ worldwide, perpetual, non-exclusive, royalty-free, non-transferable, revocable
34
+ (as provided herein) copyright license to use, reproduce, prepare derivative
35
+ works of, publicly display, and distribute the Model and Derivative Models
36
+ solely for Non-Commercial Use.
37
+
38
+ 3. Commercial Restrictions, Outputs, and Distillation
39
+
40
+ a. Prohibition on Commercial Use: You shall not use, deploy, host, serve,
41
+ reproduce, sublicense, or distribute the Model or any Derivative Model for
42
+ any commercial purpose, including but not limited to:
43
+ i. operating the Model or Derivative Models as a paid or revenue-generating
44
+ service (including API or cloud-hosted services);
45
+ ii. incorporating the Model or Derivative Models into commercial products,
46
+ enterprise workflows, or profit-seeking internal operations; or
47
+ iii. selling, leasing, or monetizing the Model or Derivative Models in any
48
+ manner.
49
+
50
+ b. Output Restrictions: You shall not use any Output generated by the Model or
51
+ Derivative Models for direct commercial monetization or commercial products.
52
+
53
+ c. Distillation and Competitive Training Prohibition: You shall not use the Model,
54
+ Derivative Models, or any Outputs generated by them (including synthetic data,
55
+ distillation targets, or reasoning traces) to train, fine-tune, distill,
56
+ align, improve, or evaluate any machine learning model that is intended for
57
+ commercial use or that competes with the Model.
58
+
59
+ d. Commercial Inquiries: Any commercial use of the Model or its derivatives
60
+ requires a separate written commercial agreement from Modilify. Inquiries
61
+ regarding commercial licensing may be submitted through the official project
62
+ repository or Modilify's public channels.
63
+
64
+ 4. Patent Grant
65
+
66
+ Each contributor grants You a worldwide, perpetual, non-exclusive, royalty-free,
67
+ irrevocable patent license, except as stated in this section, to make, have made,
68
+ use, and otherwise transfer the Model solely for Non-Commercial Use, where the
69
+ license applies only to those patent claims licensable by that contributor that
70
+ are necessarily infringed by that contributor's contribution alone or in
71
+ combination with the Model. If You institute patent litigation alleging that the
72
+ Model or a contribution constitutes patent infringement, patent licenses granted
73
+ to You under this license terminate as of the filing date.
74
+
75
+ 5. Conditions on Redistribution and Share-Alike
76
+
77
+ If You distribute the Model or a Derivative Model, You must:
78
+
79
+ a. provide recipients a copy of this license;
80
+ b. retain all copyright, patent, trademark, and attribution notices;
81
+ c. state clearly that You modified the Model and document material modifications;
82
+ d. Share-Alike Requirement: license and distribute any Derivative Model solely
83
+ under this exact Modilify Non-Commercial Model License 1.0, without imposing
84
+ any additional restrictions or granting any broader rights than those provided
85
+ herein; and
86
+ e. publish with the distributed model a reasonably accessible impact statement
87
+ (such as within a public model card or repository documentation) describing
88
+ intended uses, material limitations, evaluation scope, known safety risks,
89
+ and risk mitigations for the Derivative Model.
90
+
91
+ The impact statement may be maintained in a public model card or equivalent
92
+ document. You are not required to submit it separately to Modilify.
93
+
94
+ 6. Responsible Use and High-Risk Uses
95
+
96
+ You must not use the Model or a Derivative Model:
97
+
98
+ a. to develop, operate, or materially facilitate weapons, autonomous targeting,
99
+ or systems intended to cause physical harm;
100
+ b. for unlawful mass surveillance, biometric identification without lawful
101
+ authority and appropriate safeguards, or social scoring that determines
102
+ access to rights or essential services;
103
+ c. to exploit children or vulnerable persons, facilitate human trafficking, or
104
+ generate non-consensual intimate content;
105
+ d. to impersonate a person or deceptively represent machine output as an
106
+ authentic human communication where the deception is reasonably likely to
107
+ cause material harm; or
108
+ e. to make a final decision in a High-Risk Use without meaningful qualified
109
+ human review, proportionate testing, monitoring, appeal or correction paths,
110
+ and compliance with applicable law.
111
+
112
+ Before deploying the Model in a High-Risk Use, You must perform safety and impact
113
+ due diligence proportionate to foreseeable harm. At minimum, document the use
114
+ context, evaluate relevant failure modes and affected groups, apply reasonable
115
+ technical and organizational safeguards, monitor material incidents, and update
116
+ or suspend the deployment when its residual risk is not reasonable. Research,
117
+ testing, auditing, and defensive safety work are permitted when conducted with
118
+ appropriate safeguards.
119
+
120
+ 7. Trademarks
121
+
122
+ This license does not grant permission to use the trade names, trademarks,
123
+ service marks, or product names of Modilify or any contributor, except as needed
124
+ for reasonable and customary attribution or to describe the origin of the Model.
125
+
126
+ 8. Third-Party Components
127
+
128
+ If the Model incorporates or links to any third-party components or libraries,
129
+ those components remain subject to their respective applicable licenses and
130
+ terms.
131
+
132
+ 9. Termination and Reinstatement
133
+
134
+ Your rights terminate automatically if You materially violate this license and
135
+ do not cure the violation within 30 days after becoming aware of it. Rights are
136
+ reinstated upon timely cure unless a rights holder provides written notice of a
137
+ substantially similar repeated violation. Sections intended by their nature to
138
+ survive termination remain effective.
139
+
140
+ 10. Disclaimer of Warranty
141
+
142
+ THE MODEL IS PROVIDED "AS IS," WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
143
+ EXPRESS OR IMPLIED, INCLUDING WARRANTIES OF TITLE, NON-INFRINGEMENT,
144
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, ACCURACY, OR SAFETY. YOU ARE
145
+ SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING OR REDISTRIBUTING
146
+ THE MODEL AND ASSUME ALL RISKS ASSOCIATED WITH YOUR EXERCISE OF PERMISSIONS.
147
+
148
+ 11. Limitation of Liability
149
+
150
+ TO THE MAXIMUM EXTENT PERMITTED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR SHALL
151
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
152
+ CONSEQUENTIAL DAMAGES ARISING FROM THIS LICENSE OR THE USE OR INABILITY TO USE
153
+ THE MODEL, HOWEVER CAUSED AND UNDER ANY THEORY OF LIABILITY, EVEN IF ADVISED OF
154
+ THE POSSIBILITY OF SUCH DAMAGES.
155
+
156
+ 12. Governing Law
157
+
158
+ To the maximum extent permitted by applicable law, this license is governed by
159
+ and construed in accordance with applicable laws, excluding conflict-of-law
160
+ rules.
161
+
162
+ 13. Entire License; Severability
163
+
164
+ This document states the complete Modilify license for the Model. If any
165
+ provision is held to be invalid or unenforceable, that provision will be limited
166
+ or eliminated to the minimum extent necessary, and the remaining provisions
167
+ of this license will remain in full force and effect.
LICENSE.apache-2.0 ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2018- The Hugging Face team. All rights reserved.
2
+
3
+ Apache License
4
+ Version 2.0, January 2004
5
+ http://www.apache.org/licenses/
6
+
7
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
+
9
+ 1. Definitions.
10
+
11
+ "License" shall mean the terms and conditions for use, reproduction,
12
+ and distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ "Licensor" shall mean the copyright owner or entity authorized by
15
+ the copyright owner that is granting the License.
16
+
17
+ "Legal Entity" shall mean the union of the acting entity and all
18
+ other entities that control, are controlled by, or are under common
19
+ control with that entity. For the purposes of this definition,
20
+ "control" means (i) the power, direct or indirect, to cause the
21
+ direction or management of such entity, whether by contract or
22
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
23
+ outstanding shares, or (iii) beneficial ownership of such entity.
24
+
25
+ "You" (or "Your") shall mean an individual or Legal Entity
26
+ exercising permissions granted by this License.
27
+
28
+ "Source" form shall mean the preferred form for making modifications,
29
+ including but not limited to software source code, documentation
30
+ source, and configuration files.
31
+
32
+ "Object" form shall mean any form resulting from mechanical
33
+ transformation or translation of a Source form, including but
34
+ not limited to compiled object code, generated documentation,
35
+ and conversions to other media types.
36
+
37
+ "Work" shall mean the work of authorship, whether in Source or
38
+ Object form, made available under the License, as indicated by a
39
+ copyright notice that is included in or attached to the work
40
+ (an example is provided in the Appendix below).
41
+
42
+ "Derivative Works" shall mean any work, whether in Source or Object
43
+ form, that is based on (or derived from) the Work and for which the
44
+ editorial revisions, annotations, elaborations, or other modifications
45
+ represent, as a whole, an original work of authorship. For the purposes
46
+ of this License, Derivative Works shall not include works that remain
47
+ separable from, or merely link (or bind by name) to the interfaces of,
48
+ the Work and Derivative Works thereof.
49
+
50
+ "Contribution" shall mean any work of authorship, including
51
+ the original version of the Work and any modifications or additions
52
+ to that Work or Derivative Works thereof, that is intentionally
53
+ submitted to Licensor for inclusion in the Work by the copyright owner
54
+ or by an individual or Legal Entity authorized to submit on behalf of
55
+ the copyright owner. For the purposes of this definition, "submitted"
56
+ means any form of electronic, verbal, or written communication sent
57
+ to the Licensor or its representatives, including but not limited to
58
+ communication on electronic mailing lists, source code control systems,
59
+ and issue tracking systems that are managed by, or on behalf of, the
60
+ Licensor for the purpose of discussing and improving the Work, but
61
+ excluding communication that is conspicuously marked or otherwise
62
+ designated in writing by the copyright owner as "Not a Contribution."
63
+
64
+ "Contributor" shall mean Licensor and any individual or Legal Entity
65
+ on behalf of whom a Contribution has been received by Licensor and
66
+ subsequently incorporated within the Work.
67
+
68
+ 2. Grant of Copyright License. Subject to the terms and conditions of
69
+ this License, each Contributor hereby grants to You a perpetual,
70
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71
+ copyright license to reproduce, prepare Derivative Works of,
72
+ publicly display, publicly perform, sublicense, and distribute the
73
+ Work and such Derivative Works in Source or Object form.
74
+
75
+ 3. Grant of Patent License. Subject to the terms and conditions of
76
+ this License, each Contributor hereby grants to You a perpetual,
77
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78
+ (except as stated in this section) patent license to make, have made,
79
+ use, offer to sell, sell, import, and otherwise transfer the Work,
80
+ where such license applies only to those patent claims licensable
81
+ by such Contributor that are necessarily infringed by their
82
+ Contribution(s) alone or by combination of their Contribution(s)
83
+ with the Work to which such Contribution(s) was submitted. If You
84
+ institute patent litigation against any entity (including a
85
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
86
+ or a Contribution incorporated within the Work constitutes direct
87
+ or contributory patent infringement, then any patent licenses
88
+ granted to You under this License for that Work shall terminate
89
+ as of the date such litigation is filed.
90
+
91
+ 4. Redistribution. You may reproduce and distribute copies of the
92
+ Work or Derivative Works thereof in any medium, with or without
93
+ modifications, and in Source or Object form, provided that You
94
+ meet the following conditions:
95
+
96
+ (a) You must give any other recipients of the Work or
97
+ Derivative Works a copy of this License; and
98
+
99
+ (b) You must cause any modified files to carry prominent notices
100
+ stating that You changed the files; and
101
+
102
+ (c) You must retain, in the Source form of any Derivative Works
103
+ that You distribute, all copyright, patent, trademark, and
104
+ attribution notices from the Source form of the Work,
105
+ excluding those notices that do not pertain to any part of
106
+ the Derivative Works; and
107
+
108
+ (d) If the Work includes a "NOTICE" text file as part of its
109
+ distribution, then any Derivative Works that You distribute must
110
+ include a readable copy of the attribution notices contained
111
+ within such NOTICE file, excluding those notices that do not
112
+ pertain to any part of the Derivative Works, in at least one
113
+ of the following places: within a NOTICE text file distributed
114
+ as part of the Derivative Works; within the Source form or
115
+ documentation, if provided along with the Derivative Works; or,
116
+ within a display generated by the Derivative Works, if and
117
+ wherever such third-party notices normally appear. The contents
118
+ of the NOTICE file are for informational purposes only and
119
+ do not modify the License. You may add Your own attribution
120
+ notices within Derivative Works that You distribute, alongside
121
+ or as an addendum to the NOTICE text from the Work, provided
122
+ that such additional attribution notices cannot be construed
123
+ as modifying the License.
124
+
125
+ You may add Your own copyright statement to Your modifications and
126
+ may provide additional or different license terms and conditions
127
+ for use, reproduction, or distribution of Your modifications, or
128
+ for any such Derivative Works as a whole, provided Your use,
129
+ reproduction, and distribution of the Work otherwise complies with
130
+ the conditions stated in this License.
131
+
132
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
133
+ any Contribution intentionally submitted for inclusion in the Work
134
+ by You to the Licensor shall be under the terms and conditions of
135
+ this License, without any additional terms or conditions.
136
+ Notwithstanding the above, nothing herein shall supersede or modify
137
+ the terms of any separate license agreement you may have executed
138
+ with Licensor regarding such Contributions.
139
+
140
+ 6. Trademarks. This License does not grant permission to use the trade
141
+ names, trademarks, service marks, or product names of the Licensor,
142
+ except as required for reasonable and customary use in describing the
143
+ origin of the Work and reproducing the content of the NOTICE file.
144
+
145
+ 7. Disclaimer of Warranty. Unless required by applicable law or
146
+ agreed to in writing, Licensor provides the Work (and each
147
+ Contributor provides its Contributions) on an "AS IS" BASIS,
148
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149
+ implied, including, without limitation, any warranties or conditions
150
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151
+ PARTICULAR PURPOSE. You are solely responsible for determining the
152
+ appropriateness of using or redistributing the Work and assume any
153
+ risks associated with Your exercise of permissions under this License.
154
+
155
+ 8. Limitation of Liability. In no event and under no legal theory,
156
+ whether in tort (including negligence), contract, or otherwise,
157
+ unless required by applicable law (such as deliberate and grossly
158
+ negligent acts) or agreed to in writing, shall any Contributor be
159
+ liable to You for damages, including any direct, indirect, special,
160
+ incidental, or consequential damages of any character arising as a
161
+ result of this License or out of the use or inability to use the
162
+ Work (including but not limited to damages for loss of goodwill,
163
+ work stoppage, computer failure or malfunction, or any and all
164
+ other commercial damages or losses), even if such Contributor
165
+ has been advised of the possibility of such damages.
166
+
167
+ 9. Accepting Warranty or Additional Liability. While redistributing
168
+ the Work or Derivative Works thereof, You may choose to offer,
169
+ and charge a fee for, acceptance of support, warranty, indemnity,
170
+ or other liability obligations and/or rights consistent with this
171
+ License. However, in accepting such obligations, You may act only
172
+ on Your own behalf and on Your sole responsibility, not on behalf
173
+ of any other Contributor, and only if You agree to indemnify,
174
+ defend, and hold each Contributor harmless for any liability
175
+ incurred by, or claims asserted against, such Contributor by reason
176
+ of your accepting any such warranty or additional liability.
177
+
178
+ END OF TERMS AND CONDITIONS
179
+
180
+ APPENDIX: How to apply the Apache License to your work.
181
+
182
+ To apply the Apache License to your work, attach the following
183
+ boilerplate notice, with the fields enclosed by brackets "[]"
184
+ replaced with your own identifying information. (Don't include
185
+ the brackets!) The text should be enclosed in the appropriate
186
+ comment syntax for the file format. We also recommend that a
187
+ file or class name and description of purpose be included on the
188
+ same "printed page" as the copyright notice for easier
189
+ identification within third-party archives.
190
+
191
+ Copyright [yyyy] [name of copyright owner]
192
+
193
+ Licensed under the Apache License, Version 2.0 (the "License");
194
+ you may not use this file except in compliance with the License.
195
+ You may obtain a copy of the License at
196
+
197
+ http://www.apache.org/licenses/LICENSE-2.0
198
+
199
+ Unless required by applicable law or agreed to in writing, software
200
+ distributed under the License is distributed on an "AS IS" BASIS,
201
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202
+ See the License for the specific language governing permissions and
203
+ limitations under the License.
204
+
NOTICE.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Attribution
2
+
3
+ Modilify Mk2 uses the text backbone of Google DeepMind's [DiffusionGemma 26B-A4B-it](https://huggingface.co/google/diffusiongemma-26B-A4B-it), whose model card identifies its license as Apache 2.0. The vision tower is not included in this text-only release.
4
+
5
+ The Modilify architecture, rolling-canvas generation, trajectory memory, and trained dense/expert adapters modify the upstream model. This release is the saved step 4400 checkpoint, converted to a self-contained Transformers model. Modilify's contributions use the [Modilify Non-Commercial Model License 1.0](LICENSE). This does not replace upstream rights and terms.
6
+
7
+ The code integrates with Hugging Face Transformers (Copyright 2018–, The Hugging Face team; Apache 2.0) and uses PyTorch and Safetensors as installed dependencies. Those libraries retain their own licenses. The Apache license text is reproduced in [LICENSE.apache-2.0](LICENSE.apache-2.0).
README.md ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: modilify-non-commercial-model-license-1.0
4
+ license_link: LICENSE
5
+ library_name: transformers
6
+ pipeline_tag: text-generation
7
+ tags:
8
+ - diffusion
9
+ - mixture-of-experts
10
+ - custom-code
11
+ - modilify-mk2
12
+ ---
13
+
14
+ # Modilify Mk2
15
+
16
+ Modilify Mk2 is a text diffusion model with learned trajectory memory. Built on the DiffusionGemma 26B-A4B text backbone, it develops a response through repeated revisions of a rolling text canvas. Its memory follows both the current revision process and the content already committed to the response.
17
+
18
+ This repository provides the complete model weights trained on 78M tokens, tokenizer, and a PyTorch / Transformers runtime.
19
+
20
+ ## Architecture
21
+
22
+ Modilify Mk2 works on a 256-token canvas. During each denoising pass, it revises candidate tokens together and uses their confidence and uncertainty to select a prefix to commit. The canvas then advances to continue the response.
23
+
24
+ Two timescales of GDN2 memory connect these passes. Working memory tracks changes within the canvas, with states for individual positions and the canvas as a whole. Persistent memory incorporates experience from committed tokens and carries it into later parts of the response. The model learns these memory components alongside dense and expert LoRA adapters.
25
+
26
+ Training supervises response tokens across the valid canvas, with additional objectives for confidence calibration, prefix readiness, and response termination.
27
+
28
+ | Property | Value |
29
+ | --- | --- |
30
+ | Backbone | DiffusionGemma 26B-A4B, text only |
31
+ | Adaptation | Dense and expert LoRA |
32
+ | Memory | Dual-timescale GDN2 trajectory memory |
33
+ | Canvas size | 256 tokens |
34
+ | Training step | 4400 |
35
+ | Adaptation tokens | 78,332,410 (prompt + response, excluding padding) |
36
+ | Weight precision | BF16, with selected memory parameters in FP32 |
37
+ | Weight files | 16 Safetensors shards, approximately 48.23 GiB |
38
+ | Framework | PyTorch / Hugging Face Transformers |
39
+
40
+ ## Quickstart
41
+
42
+ From the downloaded model directory, use Python 3.12 and install the dependencies:
43
+
44
+ ```bash
45
+ python -m pip install -r requirements.txt
46
+ python inference.py --prompt "Why is the sky blue?" --stream
47
+ ```
48
+
49
+ The CLI selects CUDA, Apple Silicon MPS, or CPU according to availability. Choose a device explicitly with `--device cuda`, `--device mps`, or `--device cpu`. In addition to the weights, allow memory for the prompt cache, trajectory states, and generation workspace. Local inference has been exercised on CPU and MPS.
50
+
51
+ ## Use with Transformers
52
+
53
+ The model loads through `AutoModelForCausalLM`. Set `model_path` to the downloaded directory or `Modilify/Modilify-Mk2` on the Hub.
54
+
55
+ ```python
56
+ import torch
57
+ from transformers import AutoModelForCausalLM, AutoTokenizer
58
+
59
+ model_path = "./"
60
+ device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
61
+
62
+ tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
63
+ model = AutoModelForCausalLM.from_pretrained(
64
+ model_path,
65
+ trust_remote_code=True,
66
+ dtype="auto",
67
+ device_map=device,
68
+ attn_implementation="sdpa",
69
+ ).eval()
70
+
71
+ messages = [{"role": "user", "content": "Explain how rainbows form."}]
72
+ inputs = tokenizer.apply_chat_template(
73
+ messages,
74
+ enable_thinking=True,
75
+ add_generation_prompt=True,
76
+ return_tensors="pt",
77
+ return_dict=True,
78
+ ).to(model.device)
79
+
80
+ with torch.inference_mode():
81
+ output = model.generate(
82
+ **inputs,
83
+ max_new_tokens=256,
84
+ max_denoising_steps=256,
85
+ seed=42,
86
+ )
87
+
88
+ response_ids = output.sequences[0, inputs.input_ids.shape[1]:]
89
+ print(tokenizer.decode(response_ids, skip_special_tokens=True))
90
+ ```
91
+
92
+ ### Thinking and conversations
93
+
94
+ The chat template supports system, user, and assistant turns. Pass the conversation as `messages` and set `enable_thinking=True` to enable reasoning before the answer. The CLI enables thinking by default; `--no-thinking` disables it.
95
+
96
+ ### Generation controls
97
+
98
+ `max_new_tokens` sets the response length budget, while `max_denoising_steps` sets the number of canvas revisions. Both default to 256. Increase the budgets for longer responses; reaching either limit can leave a response unfinished. `output.stop_reason` reports how generation ended.
99
+
100
+ Use `seed` for a single request or `seeds=[...]` for batch requests. For batches, set `tokenizer.padding_side = "left"` and pass the attention mask returned by the tokenizer. The returned `output.sequences` includes the input prompt, and `output.generated_lengths` gives each response's token count.
101
+
102
+ Generation runs through the included diffusion implementation. The weights include the backbone, adapters, and memory components; `from_pretrained` loads them together. `dtype="auto"` preserves the model's mixed precision settings.
103
+
104
+ ## Use and limitations
105
+
106
+ Modilify Mk2 is released for non-commercial research and experimentation with text diffusion, iterative response refinement, and learned generation memory. It accepts text input and produces text, including an optional reasoning channel.
107
+
108
+ Broad capability and safety benchmark results are not yet available for this checkpoint. Generated responses can contain factual errors, repetition, or incomplete reasoning. Review outputs before using them for consequential decisions. The adaptation dataset is not included in this release.
109
+
110
+ ## License and credits
111
+
112
+ The model builds on Google DeepMind's [DiffusionGemma](https://huggingface.co/google/diffusiongemma-26B-A4B-it) and integrates with Hugging Face Transformers. Attribution and upstream licenses are listed in [NOTICE.md](NOTICE.md).
113
+
114
+ Modilify's contributions are available under the [Modilify Non-Commercial Model License 1.0](LICENSE). Commercial use requires a separate agreement.
adapters.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Unfused dense and expert adapters for the published schema25 checkpoint."""
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass
4
+ import math
5
+ import torch
6
+ from torch import nn
7
+ from transformers.models.diffusion_gemma.modeling_diffusion_gemma import DiffusionGemmaTextExperts
8
+ from .mps_ops import attach_expert_lora
9
+
10
+ @dataclass
11
+ class LoRAConfig:
12
+ r: int
13
+ alpha: int
14
+ dropout: float
15
+ target_modules: tuple[str, ...]
16
+ expert_r: int = 8
17
+ expert_alpha: int = 8
18
+
19
+
20
+ def unwrap_model(model: nn.Module) -> nn.Module:
21
+ current = model
22
+ while True:
23
+ if hasattr(current, "module"):
24
+ current = current.module
25
+ continue
26
+ if hasattr(current, "_orig_mod"):
27
+ current = current._orig_mod
28
+ continue
29
+ return current
30
+
31
+
32
+ class LoRALinear(nn.Module):
33
+ def __init__(self, base: nn.Linear, config: LoRAConfig):
34
+ super().__init__()
35
+ if config.r <= 0:
36
+ raise ValueError("LoRA rank must be positive.")
37
+ self.base = base
38
+ self.scaling = config.alpha / config.r
39
+ self.dropout = nn.Dropout(config.dropout) if config.dropout else nn.Identity()
40
+ self.lora_a = nn.Parameter(
41
+ torch.empty(config.r, base.in_features, device=base.weight.device, dtype=base.weight.dtype)
42
+ )
43
+ self.lora_b = nn.Parameter(
44
+ torch.zeros(base.out_features, config.r, device=base.weight.device, dtype=base.weight.dtype)
45
+ )
46
+ nn.init.kaiming_uniform_(self.lora_a, a=math.sqrt(5))
47
+ self.base.requires_grad_(False)
48
+
49
+ def forward(self, inputs: torch.Tensor) -> torch.Tensor:
50
+ dropped = self.dropout(inputs)
51
+ if inputs.device.type == "mps" and torch.is_grad_enabled() and dropped.requires_grad:
52
+ update = torch.nn.functional.linear(
53
+ torch.nn.functional.linear(dropped.float(), self.lora_a.float()),
54
+ self.lora_b.float(),
55
+ ).to(dtype=inputs.dtype)
56
+ else:
57
+ update = torch.nn.functional.linear(
58
+ torch.nn.functional.linear(dropped, self.lora_a), self.lora_b
59
+ )
60
+ return self.base(inputs) + self.scaling * update
61
+
62
+
63
+ def get_parent_module(root: nn.Module, module_name: str) -> tuple[nn.Module, str]:
64
+ parent = root
65
+ parts = module_name.split(".")
66
+ for part in parts[:-1]:
67
+ parent = getattr(parent, part)
68
+ return parent, parts[-1]
69
+
70
+
71
+ def inject_lora(model: nn.Module, config: LoRAConfig) -> int:
72
+ targets = set(config.target_modules)
73
+ replacements = []
74
+ for name, module in model.named_modules():
75
+ if not isinstance(module, nn.Linear) or isinstance(module, LoRALinear):
76
+ continue
77
+ if ".router." in name:
78
+ continue
79
+ in_trunk = ".layers." in name
80
+ if name.rsplit(".", 1)[-1] not in targets or not in_trunk:
81
+ continue
82
+ replacements.append((name, module))
83
+ for name, module in replacements:
84
+ parent, child = get_parent_module(model, name)
85
+ setattr(parent, child, LoRALinear(module, config))
86
+ expert_modules = [
87
+ module for module in model.modules()
88
+ if isinstance(module, DiffusionGemmaTextExperts)
89
+ ]
90
+ for module in expert_modules:
91
+ attach_expert_lora(
92
+ module,
93
+ rank=config.expert_r,
94
+ alpha=config.expert_alpha,
95
+ dropout=config.dropout,
96
+ )
97
+ share_tied_lora_parameters(model)
98
+ if not replacements and not expert_modules:
99
+ raise RuntimeError("No LoRA target modules were found.")
100
+ return len(replacements) + len(expert_modules)
101
+
102
+
103
+ def share_tied_lora_parameters(model: nn.Module) -> None:
104
+ """Use one adapter for the encoder/decoder pair whose base weights are tied."""
105
+ raw = unwrap_model(model)
106
+ encoder = raw.model.encoder.language_model
107
+ decoder = raw.model.decoder
108
+ expert_names = ("lora_gate_up_a", "lora_gate_up_b", "lora_down_a", "lora_down_b")
109
+ for name, decoder_module in decoder.named_modules():
110
+ if not name:
111
+ continue
112
+ try:
113
+ encoder_module = encoder.get_submodule(name)
114
+ except AttributeError:
115
+ continue
116
+ if isinstance(decoder_module, LoRALinear) and isinstance(encoder_module, LoRALinear):
117
+ encoder_module.lora_a = decoder_module.lora_a
118
+ encoder_module.lora_b = decoder_module.lora_b
119
+ for parameter_name in expert_names:
120
+ if hasattr(decoder_module, parameter_name) and hasattr(encoder_module, parameter_name):
121
+ setattr(encoder_module, parameter_name, getattr(decoder_module, parameter_name))
chat_template.jinja ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {#
2
+ Template: Google Gemma 4 Canonical Chat Template
3
+ Author: Google Gemma Engineering Team
4
+ Published: 2026-07-09
5
+ Context: Fixed tool-calling loops, turn closures, and thinking content-ordering.
6
+ #}
7
+ {%- macro format_parameters(properties, required, filter_keys=false) -%}
8
+ {%- set standard_keys = ['description', 'type', 'properties', 'required', 'nullable'] -%}
9
+ {%- set ns = namespace(found_first=false) -%}
10
+ {%- for key, value in properties | dictsort -%}
11
+ {%- set add_comma = false -%}
12
+ {%- if not filter_keys or key not in standard_keys -%}
13
+ {%- if ns.found_first %},{% endif -%}
14
+ {%- set ns.found_first = true -%}
15
+ {{ key }}:{
16
+ {%- if value['description'] -%}
17
+ description:<|"|>{{ value['description'] }}<|"|>
18
+ {%- set add_comma = true -%}
19
+ {%- endif -%}
20
+ {%- if value['type'] | upper == 'STRING' -%}
21
+ {%- if value['enum'] -%}
22
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
23
+ enum:{{ format_argument(value['enum']) }}
24
+ {%- endif -%}
25
+ {%- elif value['type'] | upper == 'ARRAY' -%}
26
+ {%- if value['items'] is mapping and value['items'] -%}
27
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
28
+ items:{
29
+ {%- set ns_items = namespace(found_first=false) -%}
30
+ {%- for item_key, item_value in value['items'] | dictsort -%}
31
+ {%- if item_value is not none -%}
32
+ {%- if ns_items.found_first %},{% endif -%}
33
+ {%- set ns_items.found_first = true -%}
34
+ {%- if item_key == 'properties' -%}
35
+ properties:{
36
+ {%- if item_value is mapping -%}
37
+ {{- format_parameters(item_value, value['items']['required'] | default([])) -}}
38
+ {%- endif -%}
39
+ }
40
+ {%- elif item_key == 'required' -%}
41
+ required:[
42
+ {%- for req_item in item_value -%}
43
+ <|"|>{{- req_item -}}<|"|>
44
+ {%- if not loop.last %},{% endif -%}
45
+ {%- endfor -%}
46
+ ]
47
+ {%- elif item_key == 'type' -%}
48
+ {%- if item_value is string -%}
49
+ type:{{ format_argument(item_value | upper) }}
50
+ {%- else -%}
51
+ type:{{ format_argument(item_value | map('upper') | list) }}
52
+ {%- endif -%}
53
+ {%- else -%}
54
+ {{ item_key }}:{{ format_argument(item_value) }}
55
+ {%- endif -%}
56
+ {%- endif -%}
57
+ {%- endfor -%}
58
+ }
59
+ {%- endif -%}
60
+ {%- endif -%}
61
+ {%- if value['nullable'] %}
62
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
63
+ nullable:true
64
+ {%- endif -%}
65
+ {%- if value['type'] | upper == 'OBJECT' -%}
66
+ {%- if value['properties'] is defined and value['properties'] is mapping -%}
67
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
68
+ properties:{
69
+ {{- format_parameters(value['properties'], value['required'] | default([])) -}}
70
+ }
71
+ {%- elif value is mapping -%}
72
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
73
+ properties:{
74
+ {{- format_parameters(value, value['required'] | default([]), filter_keys=true) -}}
75
+ }
76
+ {%- endif -%}
77
+ {%- if value['required'] -%}
78
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
79
+ required:[
80
+ {%- for item in value['required'] | default([]) -%}
81
+ <|"|>{{- item -}}<|"|>
82
+ {%- if not loop.last %},{% endif -%}
83
+ {%- endfor -%}
84
+ ]
85
+ {%- endif -%}
86
+ {%- endif -%}
87
+ {%- if add_comma %},{%- else -%} {%- set add_comma = true -%} {% endif -%}
88
+ type:<|"|>{{ value['type'] | upper }}<|"|>}
89
+ {%- endif -%}
90
+ {%- endfor -%}
91
+ {%- endmacro -%}
92
+ {%- macro format_function_declaration(tool_data) -%}
93
+ declaration:{{- tool_data['function']['name'] -}}{description:<|"|>{{- tool_data['function']['description'] -}}<|"|>
94
+ {%- set params = tool_data['function']['parameters'] -%}
95
+ {%- if params -%}
96
+ ,parameters:{
97
+ {%- if params['properties'] -%}
98
+ properties:{ {{- format_parameters(params['properties'], params['required']) -}} },
99
+ {%- endif -%}
100
+ {%- if params['required'] -%}
101
+ required:[
102
+ {%- for item in params['required'] -%}
103
+ <|"|>{{- item -}}<|"|>
104
+ {{- ',' if not loop.last -}}
105
+ {%- endfor -%}
106
+ ],
107
+ {%- endif -%}
108
+ {%- if params['type'] -%}
109
+ type:<|"|>{{- params['type'] | upper -}}<|"|>}
110
+ {%- endif -%}
111
+ {%- endif -%}
112
+ {%- if 'response' in tool_data['function'] -%}
113
+ {%- set response_declaration = tool_data['function']['response'] -%}
114
+ ,response:{
115
+ {%- if response_declaration['description'] -%}
116
+ description:<|"|>{{- response_declaration['description'] -}}<|"|>,
117
+ {%- endif -%}
118
+ {%- if response_declaration['type'] | upper == 'OBJECT' -%}
119
+ type:<|"|>{{- response_declaration['type'] | upper -}}<|"|>}
120
+ {%- endif -%}
121
+ {%- endif -%}
122
+ }
123
+ {%- endmacro -%}
124
+ {%- macro format_argument(argument, escape_keys=True) -%}
125
+ {%- if argument is none -%}
126
+ {{- 'null' -}}
127
+ {%- elif argument is string -%}
128
+ {{- '<|"|>' + argument + '<|"|>' -}}
129
+ {%- elif argument is boolean -%}
130
+ {{- 'true' if argument else 'false' -}}
131
+ {%- elif argument is mapping -%}
132
+ {{- '{' -}}
133
+ {%- set ns = namespace(found_first=false) -%}
134
+ {%- for key, value in argument | dictsort -%}
135
+ {%- if ns.found_first %},{% endif -%}
136
+ {%- set ns.found_first = true -%}
137
+ {%- if escape_keys -%}
138
+ {{- '<|"|>' + key + '<|"|>' -}}
139
+ {%- else -%}
140
+ {{- key -}}
141
+ {%- endif -%}
142
+ :{{- format_argument(value, escape_keys=escape_keys) -}}
143
+ {%- endfor -%}
144
+ {{- '}' -}}
145
+ {%- elif argument is sequence -%}
146
+ {{- '[' -}}
147
+ {%- for item in argument -%}
148
+ {{- format_argument(item, escape_keys=escape_keys) -}}
149
+ {%- if not loop.last %},{% endif -%}
150
+ {%- endfor -%}
151
+ {{- ']' -}}
152
+ {%- else -%}
153
+ {{- argument -}}
154
+ {%- endif -%}
155
+ {%- endmacro -%}
156
+ {%- macro strip_thinking(text) -%}
157
+ {%- set ns = namespace(result='') -%}
158
+ {%- for part in text.split('<channel|>') -%}
159
+ {%- if '<|channel>' in part -%}
160
+ {%- set ns.result = ns.result + part.split('<|channel>')[0] -%}
161
+ {%- else -%}
162
+ {%- set ns.result = ns.result + part -%}
163
+ {%- endif -%}
164
+ {%- endfor -%}
165
+ {{- ns.result | trim -}}
166
+ {%- endmacro -%}
167
+
168
+ {%- macro format_tool_response_block(tool_name, response) -%}
169
+ {{- '<|tool_response>' -}}
170
+ {%- if response is mapping -%}
171
+ {{- 'response:' + tool_name + '{' -}}
172
+ {%- for key, value in response | dictsort -%}
173
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
174
+ {%- if not loop.last %},{% endif -%}
175
+ {%- endfor -%}
176
+ {{- '}' -}}
177
+ {%- else -%}
178
+ {{- 'response:' + tool_name + '{value:' + format_argument(response, escape_keys=False) + '}' -}}
179
+ {%- endif -%}
180
+ {{- '<tool_response|>' -}}
181
+ {%- endmacro -%}
182
+
183
+ {#- ===== SETUP ===== -#}
184
+ {%- set ns = namespace(prev_message_type=None, prev_non_tool_role=None) -%}
185
+ {%- set loop_messages = messages -%}
186
+ {%- set enable_thinking = enable_thinking | default(false) -%}
187
+ {%- set preserve_thinking = preserve_thinking | default(false) -%}
188
+ {{- bos_token -}}
189
+ {#- Handle System/Tool Definitions Block -#}
190
+ {%- if enable_thinking or tools or (messages and messages[0]['role'] in ['system', 'developer']) -%}
191
+ {{- '<|turn>system\n' -}}
192
+ {#- Inject Thinking token at the very top of the FIRST system turn -#}
193
+ {%- if enable_thinking -%}
194
+ {{- '<|think|>\n' -}}
195
+ {%- set ns.prev_message_type = 'think' -%}
196
+ {%- endif -%}
197
+ {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}
198
+ {%- if messages[0]['content'] is string -%}
199
+ {{- messages[0]['content'] | trim -}}
200
+ {%- elif messages[0]['content'] is sequence -%}
201
+ {%- for item in messages[0]['content'] -%}
202
+ {{- item['text'] | trim + ' '-}}
203
+ {%- endfor -%}
204
+ {%- endif -%}
205
+ {%- set loop_messages = messages[1:] -%}
206
+ {%- endif -%}
207
+ {%- if tools -%}
208
+ {%- for tool in tools %}
209
+ {{- '<|tool>' -}}
210
+ {{- format_function_declaration(tool) | trim -}}
211
+ {{- '<tool|>' -}}
212
+ {%- endfor %}
213
+ {%- set ns.prev_message_type = 'tool' -%}
214
+ {%- endif -%}
215
+ {{- '<turn|>\n' -}}
216
+ {%- endif %}
217
+
218
+ {#- Pre-scan: find last user message index for reasoning guard -#}
219
+ {%- set ns_turn = namespace(last_user_idx=-1) -%}
220
+ {%- for i in range(loop_messages | length) -%}
221
+ {%- if loop_messages[i]['role'] == 'user' -%}
222
+ {%- set ns_turn.last_user_idx = i -%}
223
+ {%- endif -%}
224
+ {%- endfor -%}
225
+
226
+ {#- Loop through messages -#}
227
+ {%- for message in loop_messages -%}
228
+ {%- if message['role'] != 'tool' -%}
229
+ {%- set ns.prev_message_type = None -%}
230
+ {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}
231
+ {#- Detect continuation using tracked state — O(1) instead of O(n) backward scan -#}
232
+ {%- set continue_same_model_turn = (role == 'model' and ns.prev_non_tool_role == 'assistant') -%}
233
+ {%- if not continue_same_model_turn -%}
234
+ {{- '<|turn>' + role + '\n' }}
235
+
236
+ {%- endif -%}
237
+
238
+ {#- Render reasoning/reasoning_content as thinking channel -#}
239
+ {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}
240
+ {%- set thinking_gate = (loop.index0 > ns_turn.last_user_idx) or (preserve_thinking and message.get('tool_calls')) -%}
241
+ {%- if thinking_text and thinking_gate -%}
242
+ {{- '<|channel>thought\n' + thinking_text + '\n<channel|>' -}}
243
+ {%- endif -%}
244
+
245
+ {%- if message.get('tool_calls') -%}
246
+ {%- for tool_call in message.get('tool_calls') -%}
247
+ {%- set function = tool_call['function'] -%}
248
+ {{- '<|tool_call>call:' + function['name'] + '{' -}}
249
+ {%- if function['arguments'] is mapping -%}
250
+ {%- set ns_args = namespace(found_first=false) -%}
251
+ {%- for key, value in function['arguments'] | dictsort -%}
252
+ {%- if ns_args.found_first %},{% endif -%}
253
+ {%- set ns_args.found_first = true -%}
254
+ {{- key -}}:{{- format_argument(value, escape_keys=False) -}}
255
+ {%- endfor -%}
256
+ {%- elif function['arguments'] is none -%}
257
+ {%- else -%}
258
+ {{- raise_exception(
259
+ "chat_template: tool_calls[].function.arguments must be a "
260
+ "JSON object (mapping), not a string. Deserialize arguments "
261
+ "before passing to the template."
262
+ ) -}}
263
+ {%- endif -%}
264
+ {{- '}<tool_call|>' -}}
265
+ {%- endfor -%}
266
+ {%- set ns.prev_message_type = 'tool_call' -%}
267
+ {%- endif -%}
268
+
269
+ {%- set ns_tr_out = namespace(flag=false) -%}
270
+ {%- if message.get('tool_responses') -%}
271
+ {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}
272
+ {%- for tool_response in message.get('tool_responses') -%}
273
+ {{- format_tool_response_block(tool_response['name'] | default('unknown', true), tool_response['response']) -}}
274
+ {%- set ns_tr_out.flag = true -%}
275
+ {%- set ns.prev_message_type = 'tool_response' -%}
276
+ {%- endfor -%}
277
+ {%- elif message.get('tool_calls') -%}
278
+ {#- OpenAI Chat Completions: forward-scan consecutive role:tool messages -#}
279
+ {%- set ns_tool_scan = namespace(stopped=false) -%}
280
+ {%- for k in range(loop.index0 + 1, loop_messages | length) -%}
281
+ {%- if ns_tool_scan.stopped -%}
282
+ {%- elif loop_messages[k]['role'] != 'tool' -%}
283
+ {%- set ns_tool_scan.stopped = true -%}
284
+ {%- else -%}
285
+ {%- set follow = loop_messages[k] -%}
286
+ {#- Resolve tool_call_id to function name -#}
287
+ {%- set ns_tname = namespace(name=follow.get('name') or 'unknown') -%}
288
+ {%- for tc in message.get('tool_calls') -%}
289
+ {%- if tc.get('id') == follow.get('tool_call_id') -%}
290
+ {%- set ns_tname.name = tc['function']['name'] -%}
291
+ {%- endif -%}
292
+ {%- endfor -%}
293
+ {#- Handle content as string or content-parts array -#}
294
+ {%- set tool_body = follow.get('content') -%}
295
+ {%- if tool_body is string -%}
296
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
297
+ {%- elif tool_body is sequence and tool_body is not string -%}
298
+ {%- set ns_txt = namespace(s='') -%}
299
+ {%- for part in tool_body -%}
300
+ {%- if part.get('type') == 'text' -%}
301
+ {%- set ns_txt.s = ns_txt.s + (part.get('text') | default('')) -%}
302
+ {%- endif -%}
303
+ {%- endfor -%}
304
+ {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}
305
+ {%- for part in tool_body -%}
306
+ {%- if part.get('type') in ['image', 'image_url'] -%}
307
+ {{- '<|image|>' -}}
308
+ {%- elif part.get('type') in ['audio', 'input_audio'] -%}
309
+ {{- '<|audio|>' -}}
310
+ {%- elif part.get('type') == 'video' -%}
311
+ {{- '<|video|>' -}}
312
+ {%- endif -%}
313
+ {%- endfor -%}
314
+ {%- else -%}
315
+ {{- format_tool_response_block(ns_tname.name, tool_body) -}}
316
+ {%- endif -%}
317
+ {%- set ns_tr_out.flag = true -%}
318
+ {%- set ns.prev_message_type = 'tool_response' -%}
319
+ {%- endif -%}
320
+ {%- endfor -%}
321
+ {%- endif -%}
322
+
323
+ {%- set captured_content -%}
324
+ {%- if message.get('content') is string -%}
325
+ {%- if role == 'model' -%}
326
+ {{- strip_thinking(message['content']) -}}
327
+ {%- else -%}
328
+ {{- message['content'] | trim -}}
329
+ {%- endif -%}
330
+ {%- elif message.get('content') is sequence -%}
331
+ {%- for item in message['content'] -%}
332
+ {%- if item.get('type') == 'text' -%}
333
+ {%- if role == 'model' -%}
334
+ {{- strip_thinking(item['text']) -}}
335
+ {%- else -%}
336
+ {{- item['text'] | trim -}}
337
+ {%- endif -%}
338
+ {%- elif item.get('type') in ['image', 'image_url'] -%}
339
+ {{- '<|image|>' -}}
340
+ {%- elif item.get('type') in ['audio', 'input_audio'] -%}
341
+ {{- '<|audio|>' -}}
342
+ {%- elif item.get('type') == 'video' -%}
343
+ {{- '<|video|>' -}}
344
+ {%- endif -%}
345
+ {%- endfor -%}
346
+ {%- endif -%}
347
+ {%- endset -%}
348
+
349
+ {{- captured_content -}}
350
+ {%- set has_content = captured_content | trim | length > 0 -%}
351
+
352
+ {#- Forward-scan: find next non-tool message role for continuation detection -#}
353
+ {%- set next_nt = namespace(role=None, found=false) -%}
354
+ {%- for j in range(loop.index0 + 1, loop_messages | length) -%}
355
+ {%- if not next_nt.found -%}
356
+ {%- if loop_messages[j]['role'] != 'tool' -%}
357
+ {%- set next_nt.role = loop_messages[j]['role'] -%}
358
+ {%- set next_nt.found = true -%}
359
+ {%- endif -%}
360
+ {%- endif -%}
361
+ {%- endfor -%}
362
+
363
+ {%- set continues_into_next = (
364
+ role == 'model'
365
+ and next_nt.role == 'assistant'
366
+ and (not message.get('tool_calls') or ns_tr_out.flag)
367
+ ) -%}
368
+
369
+ {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}
370
+ {{- '<|tool_response>' -}}
371
+ {%- elif continues_into_next -%}
372
+ {%- elif not (ns_tr_out.flag and not has_content and not next_nt.found) -%}
373
+ {{- '<turn|>\n' -}}
374
+ {%- endif -%}
375
+ {%- endif -%}
376
+
377
+ {#- Track previous non-tool role for next iteration (avoids O(n) backward scan) -#}
378
+ {%- set ns.prev_non_tool_role = message['role'] -%}
379
+ {%- endfor -%}
380
+
381
+ {%- if add_generation_prompt -%}
382
+ {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}
383
+ {{- '<|turn>model\n' -}}
384
+ {%- elif ns.prev_message_type == 'tool_response' and enable_thinking -%}
385
+ {{- '<|channel>thought\n' -}}
386
+ {%- endif -%}
387
+ {%- endif -%}
commit_policy.py ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared confidence-and-entropy commit policy for training and inference."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass
7
+
8
+ import math
9
+ import torch
10
+
11
+ from .latent_deliberation import (
12
+ advance_trajectory_clocks,
13
+ should_force_trajectory_jump,
14
+ )
15
+
16
+ JUMP_FAILURE_BUDGET = 2.0
17
+
18
+ FUSED_EPS = 1e-6
19
+
20
+
21
+ def commit_target_confidence_bias(
22
+ target_confidence: float | None,
23
+ failure_budget: float = 0.2,
24
+ budget_safety_ratio: float = 0.85,
25
+ ) -> float:
26
+ if target_confidence is None or target_confidence <= 0.0 or target_confidence >= 1.0:
27
+ return 0.0
28
+ target_failure = min(budget_safety_ratio * failure_budget, 1.0 - target_confidence)
29
+ target_failure = max(target_failure, 1.0e-4)
30
+ target_conf = 1.0 - target_failure
31
+ logit_c = math.log(target_conf / (1.0 - target_conf))
32
+ logit_p = math.log(target_confidence / (1.0 - target_confidence))
33
+ return max(logit_c - logit_p, 0.0)
34
+
35
+
36
+ def fused_commit_confidence(
37
+ proposal_confidence: torch.Tensor,
38
+ token_entropy: torch.Tensor,
39
+ *,
40
+ eps: float = FUSED_EPS,
41
+ entropy_weight: float = 1.0,
42
+ confidence_power: float = 2.0,
43
+ top_k: int | None = None,
44
+ min_p: float | None = None,
45
+ target_confidence: float | None = None,
46
+ failure_budget: float = 0.2,
47
+ **_kwargs: object,
48
+ ) -> torch.Tensor:
49
+ """Fuse proposal confidence with token entropy into effective commit confidence.
50
+
51
+ Effective confidence is defined using an excess-entropy sigmoid:
52
+
53
+ p = clamp(proposal_confidence, eps, 1 - eps)
54
+ h2 = -p * log(p) - (1 - p) * log(1 - p) # binary entropy of p
55
+ excess = max(token_entropy - h2, 0)
56
+ base_fused = sigmoid(logit(p) + bias - entropy_weight * excess)
57
+ fused = base_fused ** confidence_power
58
+
59
+ The helper defaults preserve legacy callers. Runtime entrypoints pass the
60
+ checkpoint's explicit policy parameters; new runs default to power one.
61
+ """
62
+
63
+ p = proposal_confidence.float().nan_to_num(0.5).clamp(min=eps, max=1.0 - eps)
64
+ H = token_entropy.float().nan_to_num(0.0).clamp(min=0.0)
65
+
66
+ # Binary entropy of p, using log1p for the (1-p) term.
67
+ h2 = -p * torch.log(p) - (1.0 - p) * torch.log1p(-p)
68
+
69
+ excess = (H - h2).clamp(min=0.0)
70
+ k_eff = None
71
+ if top_k is not None and top_k > 0:
72
+ k_eff = torch.full_like(p, float(top_k))
73
+ if min_p is not None and min_p > 0:
74
+ thresh = torch.clamp_min(float(min_p) * p, 1.0e-6)
75
+ k_min_p = torch.clamp_min((1.0 - p) / thresh, 1.0)
76
+ k_eff = k_min_p if k_eff is None else torch.minimum(k_eff, k_min_p)
77
+ if k_eff is not None:
78
+ max_excess = (1.0 - p) * torch.log(k_eff)
79
+ excess = torch.minimum(excess, max_excess)
80
+
81
+ # logit(p) = log(p / (1-p)) = log(p) - log1p(-p)
82
+ logit_p = torch.log(p) - torch.log1p(-p)
83
+
84
+ bias = commit_target_confidence_bias(target_confidence, failure_budget=failure_budget)
85
+ base_fused = torch.sigmoid(logit_p + bias - float(entropy_weight) * excess)
86
+ fused = base_fused.pow(float(confidence_power))
87
+ return fused.clamp(min=eps, max=1.0 - eps)
88
+
89
+
90
+ def sampled_commit_log_risk(
91
+ sampled_nll: torch.Tensor, token_entropy: torch.Tensor, *, entropy_weight: float,
92
+ top_k: int | None = None,
93
+ min_p: float | None = None,
94
+ target_confidence: float | None = None,
95
+ failure_budget: float = 0.2,
96
+ **_kwargs: object,
97
+ ) -> torch.Tensor:
98
+ """Stable -log(base fused confidence), without probability saturation.
99
+
100
+ The power-normalized proxy is shared across confidence-power ablations.
101
+ NLL comes directly from log Z minus the sampled score, not log(clamp(p)).
102
+ """
103
+ raw_nll = sampled_nll.float()
104
+ # FP32 reductions can produce a tiny negative log Z - selected score.
105
+ # Correct the forward domain without clipping the sampled-score gradient.
106
+ nll = raw_nll + (raw_nll.detach().clamp_min(0.0) - raw_nll.detach())
107
+ p = torch.exp(-nll)
108
+ complement = -torch.expm1(-nll)
109
+ h2 = p * nll - complement * torch.log(complement.clamp_min(1e-12))
110
+ excess = (token_entropy.float() - h2).clamp_min(0.0)
111
+ k_eff = None
112
+ if top_k is not None and top_k > 0:
113
+ k_eff = torch.full_like(p, float(top_k))
114
+ if min_p is not None and min_p > 0:
115
+ thresh = torch.clamp_min(float(min_p) * p, 1.0e-6)
116
+ k_min_p = torch.clamp_min(complement / thresh, 1.0)
117
+ k_eff = k_min_p if k_eff is None else torch.minimum(k_eff, k_min_p)
118
+ if k_eff is not None:
119
+ excess = torch.minimum(excess, complement * torch.log(k_eff))
120
+ bias = commit_target_confidence_bias(target_confidence, failure_budget=failure_budget)
121
+ u = float(entropy_weight) * excess - bias
122
+ small = torch.log1p(complement * torch.expm1(u.clamp_max(20.0)))
123
+ large = u + torch.log((complement + p * torch.exp(-u)).clamp_min(1e-12))
124
+ return nll + torch.where(u <= 20.0, small, large)
125
+
126
+
127
+ def fused_commit_failure_rate(
128
+ proposal_confidence: torch.Tensor,
129
+ token_entropy: torch.Tensor,
130
+ **kwargs: object,
131
+ ) -> torch.Tensor:
132
+ """Return ``1 - effective_confidence`` from the shared fusion helper."""
133
+
134
+ return 1.0 - fused_commit_confidence(
135
+ proposal_confidence, token_entropy, **kwargs
136
+ )
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class CommitPolicyDecision:
141
+ """One shared training/inference transition from proposal to commit."""
142
+
143
+ normal_lengths: torch.LongTensor
144
+ commit_lengths: torch.LongTensor
145
+ commit_token_ids: torch.LongTensor
146
+ jump_rows: torch.BoolTensor
147
+ ponder_steps: torch.IntTensor
148
+ stagnation_steps: torch.IntTensor
149
+
150
+
151
+ def prefix_failure_commit_lengths(
152
+ failure_rate: torch.Tensor,
153
+ *,
154
+ failure_budget: float,
155
+ valid_mask: torch.BoolTensor | None = None,
156
+ ) -> torch.LongTensor:
157
+ """Return the longest valid prefix satisfying ``cumsum(failure_rate) < budget``."""
158
+
159
+ if failure_rate.ndim != 2:
160
+ raise ValueError("Failure rate must have shape [batch, canvas].")
161
+ if failure_budget <= 0:
162
+ raise ValueError("Commit failure budget must be positive.")
163
+ if valid_mask is None:
164
+ valid_mask = torch.ones_like(failure_rate, dtype=torch.bool)
165
+ if valid_mask.shape != failure_rate.shape:
166
+ raise ValueError("Commit validity mask must match failure rate.")
167
+
168
+ risk = failure_rate.float().clamp(0.0, 1.0) * valid_mask.to(torch.float32)
169
+ cumulative_risk = risk.cumsum(dim=-1)
170
+ contiguous_valid = valid_mask.long().cumprod(dim=-1).bool()
171
+ allowed = cumulative_risk.lt(float(failure_budget)) & contiguous_valid
172
+ return allowed.long().cumprod(dim=-1).sum(dim=-1)
173
+
174
+
175
+ def first_committed_token_lengths(
176
+ proposal: torch.LongTensor,
177
+ commit_lengths: torch.LongTensor,
178
+ token_id: int | Sequence[int],
179
+ *,
180
+ positions: torch.LongTensor | None = None,
181
+ ) -> torch.LongTensor:
182
+ """Clip each committed prefix immediately after its first matching stop token."""
183
+
184
+ if proposal.ndim != 2 or commit_lengths.shape != proposal.shape[:1]:
185
+ raise ValueError("Proposal and commit lengths must share a batch dimension.")
186
+ if positions is None:
187
+ positions = torch.arange(proposal.shape[1], device=proposal.device).unsqueeze(0)
188
+ elif positions.shape != (1, proposal.shape[1]):
189
+ raise ValueError("Commit positions must have shape [1, canvas].")
190
+ committed = positions.lt(commit_lengths[:, None])
191
+ stop_token_ids = (
192
+ (int(token_id),)
193
+ if isinstance(token_id, int)
194
+ else tuple(dict.fromkeys(int(value) for value in token_id))
195
+ )
196
+ if not stop_token_ids:
197
+ raise ValueError("At least one stop token ID is required.")
198
+ matches = proposal.eq(stop_token_ids[0])
199
+ for value in stop_token_ids[1:]:
200
+ matches |= proposal.eq(value)
201
+ matches &= committed
202
+ sentinel = torch.full_like(positions, proposal.shape[1])
203
+ first = torch.where(matches, positions, sentinel).min(dim=-1).values
204
+ clipped = torch.where(first.lt(proposal.shape[1]), first + 1, commit_lengths)
205
+ return torch.minimum(clipped, commit_lengths)
206
+
207
+
208
+ def bounded_prefix_failure_commit_lengths(
209
+ committed_token_ids: torch.LongTensor,
210
+ failure_rate: torch.Tensor,
211
+ *,
212
+ failure_budget: float,
213
+ remaining_lengths: torch.LongTensor,
214
+ stop_token_id: int | Sequence[int],
215
+ valid_mask: torch.BoolTensor | None = None,
216
+ positions: torch.LongTensor | None = None,
217
+ ) -> torch.LongTensor:
218
+ """Apply length and stop-token bounds to the shared failure-rate policy."""
219
+
220
+ if committed_token_ids.shape != failure_rate.shape:
221
+ raise ValueError("Committed token IDs and failure rate must share [batch, canvas].")
222
+ if remaining_lengths.shape != committed_token_ids.shape[:1]:
223
+ raise ValueError("Remaining lengths must have shape [batch].")
224
+ commit_lengths = prefix_failure_commit_lengths(
225
+ failure_rate,
226
+ failure_budget=failure_budget,
227
+ valid_mask=valid_mask,
228
+ )
229
+ commit_lengths = torch.minimum(commit_lengths, remaining_lengths.clamp_min(0))
230
+ return first_committed_token_lengths(
231
+ committed_token_ids,
232
+ commit_lengths,
233
+ stop_token_id,
234
+ positions=positions,
235
+ )
236
+
237
+
238
+ def select_commit_lengths(
239
+ sampled_token_ids: torch.LongTensor,
240
+ normal_failure_rate: torch.Tensor,
241
+ previous_failure_rate: torch.Tensor,
242
+ greedy_token_ids: torch.LongTensor,
243
+ jump_failure_rate: torch.Tensor,
244
+ *,
245
+ ponder_steps: torch.Tensor,
246
+ stagnation_steps: torch.Tensor,
247
+ active_rows: torch.BoolTensor,
248
+ remaining_lengths: torch.LongTensor,
249
+ failure_budget: float,
250
+ stop_token_id: int | Sequence[int],
251
+ stagnation_threshold: int,
252
+ min_progress: float,
253
+ max_ponder_steps: int | None = None,
254
+ valid_mask: torch.BoolTensor | None = None,
255
+ ) -> CommitPolicyDecision:
256
+ """Use normal sampled commits and a fixed-budget greedy JUMP.
257
+
258
+ Progress is measured from the signed change in fused failure rate over the
259
+ frontier region (the union of the previous and current commit prefixes plus
260
+ one blocking position), not from raw confidence/entropy deltas.
261
+ """
262
+
263
+ if not (
264
+ sampled_token_ids.shape
265
+ == normal_failure_rate.shape
266
+ == previous_failure_rate.shape
267
+ == greedy_token_ids.shape
268
+ == jump_failure_rate.shape
269
+ ):
270
+ raise ValueError("Sampled and greedy statistics must share [batch, canvas].")
271
+
272
+ canvas_length = normal_failure_rate.shape[1]
273
+ positions = torch.arange(canvas_length, device=normal_failure_rate.device)[None, :]
274
+ normal = bounded_prefix_failure_commit_lengths(
275
+ sampled_token_ids,
276
+ normal_failure_rate,
277
+ failure_budget=failure_budget,
278
+ remaining_lengths=remaining_lengths,
279
+ stop_token_id=stop_token_id,
280
+ valid_mask=valid_mask,
281
+ positions=positions,
282
+ )
283
+ previous_prefix_length = prefix_failure_commit_lengths(
284
+ previous_failure_rate,
285
+ failure_budget=failure_budget,
286
+ valid_mask=valid_mask,
287
+ )
288
+ frontier_length = torch.maximum(previous_prefix_length, normal) + 1
289
+ valid_lengths = (
290
+ valid_mask.long().sum(dim=-1)
291
+ if valid_mask is not None
292
+ else torch.full_like(frontier_length, canvas_length)
293
+ )
294
+ frontier_length = torch.minimum(frontier_length, valid_lengths)
295
+ progress_mask = positions < frontier_length[:, None]
296
+ if valid_mask is not None:
297
+ progress_mask &= valid_mask
298
+ progress_mask &= active_rows[:, None]
299
+ signed_improvement = (
300
+ previous_failure_rate.float() - normal_failure_rate.float()
301
+ )
302
+ weights = progress_mask.float()
303
+ progress = (
304
+ signed_improvement * weights
305
+ ).sum(dim=-1) / weights.sum(dim=-1).clamp_min(1.0)
306
+ next_ponder, next_stagnation = advance_trajectory_clocks(
307
+ ponder_steps,
308
+ stagnation_steps,
309
+ commit_lengths=normal,
310
+ active_rows=active_rows,
311
+ progress_scores=progress,
312
+ min_progress=min_progress,
313
+ )
314
+ jump_rows = normal.eq(0) & active_rows & should_force_trajectory_jump(
315
+ next_stagnation,
316
+ progress_scores=progress,
317
+ min_progress=min_progress,
318
+ stagnation_threshold=stagnation_threshold,
319
+ ponder_steps=next_ponder,
320
+ max_ponder_steps=max_ponder_steps,
321
+ )
322
+ jump_commit = bounded_prefix_failure_commit_lengths(
323
+ greedy_token_ids,
324
+ jump_failure_rate,
325
+ failure_budget=JUMP_FAILURE_BUDGET,
326
+ remaining_lengths=remaining_lengths,
327
+ stop_token_id=stop_token_id,
328
+ valid_mask=valid_mask,
329
+ positions=positions,
330
+ )
331
+ committed = torch.where(jump_rows, jump_commit, normal)
332
+ commit_token_ids = torch.where(
333
+ jump_rows[:, None],
334
+ greedy_token_ids,
335
+ sampled_token_ids,
336
+ )
337
+ committed = torch.where(active_rows, committed, 0)
338
+ committed_rows = committed.gt(0)
339
+ jump_rows &= committed_rows
340
+ next_ponder = torch.where(
341
+ committed_rows,
342
+ 0,
343
+ next_ponder,
344
+ ).to(torch.int32)
345
+ next_stagnation = torch.where(
346
+ committed_rows,
347
+ 0,
348
+ next_stagnation,
349
+ ).to(torch.int32)
350
+ return CommitPolicyDecision(
351
+ normal_lengths=normal,
352
+ commit_lengths=committed,
353
+ commit_token_ids=commit_token_ids,
354
+ jump_rows=jump_rows,
355
+ ponder_steps=next_ponder,
356
+ stagnation_steps=next_stagnation,
357
+ )
358
+
359
+
360
+ __all__ = [
361
+ "CommitPolicyDecision",
362
+ "JUMP_FAILURE_BUDGET",
363
+ "bounded_prefix_failure_commit_lengths",
364
+ "first_committed_token_lengths",
365
+ "fused_commit_confidence",
366
+ "fused_commit_failure_rate",
367
+ "sampled_commit_log_risk",
368
+ "prefix_failure_commit_lengths",
369
+ "select_commit_lengths",
370
+ ]
config.json ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "ModilifyMk2ForBlockDiffusion"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_modilify_mk2.ModilifyMk2Config",
7
+ "AutoModel": "modeling_modilify_mk2.ModilifyMk2ForBlockDiffusion",
8
+ "AutoModelForCausalLM": "modeling_modilify_mk2.ModilifyMk2ForBlockDiffusion"
9
+ },
10
+ "canvas_length": 256,
11
+ "channel_end_token_id": 101,
12
+ "checkpoint_step": 4400,
13
+ "commit_confidence_power": 1.0,
14
+ "commit_entropy_weight": 1.0,
15
+ "commit_failure_budget": 0.2,
16
+ "commit_gold_alpha": 0.4,
17
+ "commit_gold_weight": 0.2,
18
+ "commit_min_p": 0.05,
19
+ "commit_readiness_beta": 0.02,
20
+ "commit_readiness_budget_margin": 0.02,
21
+ "commit_readiness_failure_weight": 5.0,
22
+ "commit_readiness_loss_weight": 0.5,
23
+ "commit_readiness_objective": "frontier_prefix_budget_v2",
24
+ "commit_readiness_target_tokens": 16,
25
+ "commit_sequence_dim": 1024,
26
+ "commit_sequence_layers": 2,
27
+ "commit_target_confidence": 0.5,
28
+ "commit_top_k": 40,
29
+ "confidence_calibration_loss_weight": 0.01,
30
+ "dtype": "bfloat16",
31
+ "eos_token_id": 1,
32
+ "initializer_range": 0.02,
33
+ "kv_cache_bucket_size": 128,
34
+ "latent_dim": 2816,
35
+ "latent_ffn_dim": 7168,
36
+ "latent_history_kv_rank": 1024,
37
+ "latent_history_length": 16,
38
+ "latent_history_views": 4,
39
+ "latent_local_attention_window": 128,
40
+ "latent_memory_slots": 256,
41
+ "latent_num_heads": 16,
42
+ "latent_num_layers": 4,
43
+ "latent_persistent_bus_unfreeze_steps": 0,
44
+ "latent_tape_probes": 4,
45
+ "latent_tape_scheme": "gdn2_spatial_probe_v1",
46
+ "latent_working_bus_unfreeze_steps": 0,
47
+ "latent_working_last_block_global": true,
48
+ "lora_config": {
49
+ "alpha": 16,
50
+ "dropout": 0.05,
51
+ "expert_alpha": 8,
52
+ "expert_r": 8,
53
+ "r": 16,
54
+ "target_modules": [
55
+ "q_proj",
56
+ "k_proj",
57
+ "v_proj",
58
+ "o_proj",
59
+ "gate_proj",
60
+ "up_proj",
61
+ "down_proj",
62
+ "proj"
63
+ ]
64
+ },
65
+ "memory_architecture": "compact_gdn2_v2",
66
+ "memory_scheme": "dual_timescale_gdn2_trajectory_memory",
67
+ "model_type": "modilify_mk2",
68
+ "persistent_memory_bus": true,
69
+ "persistent_memory_write": "commit_only_transformer",
70
+ "state_schema_version": 25,
71
+ "terminal_stop_loss_weight": 0.01,
72
+ "terminal_stop_target_probability": 0.9,
73
+ "terminal_token_ids": [
74
+ 106,
75
+ 50
76
+ ],
77
+ "text_config": {
78
+ "attention_bias": false,
79
+ "attention_dropout": 0.0,
80
+ "bos_token_id": 2,
81
+ "dtype": "bfloat16",
82
+ "eos_token_id": 1,
83
+ "final_logit_softcapping": 30.0,
84
+ "global_head_dim": 512,
85
+ "head_dim": 256,
86
+ "hidden_activation": "gelu_pytorch_tanh",
87
+ "hidden_size": 2816,
88
+ "initializer_range": 0.02,
89
+ "intermediate_size": 2112,
90
+ "layer_types": [
91
+ "sliding_attention",
92
+ "sliding_attention",
93
+ "sliding_attention",
94
+ "sliding_attention",
95
+ "sliding_attention",
96
+ "full_attention",
97
+ "sliding_attention",
98
+ "sliding_attention",
99
+ "sliding_attention",
100
+ "sliding_attention",
101
+ "sliding_attention",
102
+ "full_attention",
103
+ "sliding_attention",
104
+ "sliding_attention",
105
+ "sliding_attention",
106
+ "sliding_attention",
107
+ "sliding_attention",
108
+ "full_attention",
109
+ "sliding_attention",
110
+ "sliding_attention",
111
+ "sliding_attention",
112
+ "sliding_attention",
113
+ "sliding_attention",
114
+ "full_attention",
115
+ "sliding_attention",
116
+ "sliding_attention",
117
+ "sliding_attention",
118
+ "sliding_attention",
119
+ "sliding_attention",
120
+ "full_attention"
121
+ ],
122
+ "max_position_embeddings": 262144,
123
+ "model_type": "modilify_mk2_text",
124
+ "moe_intermediate_size": 704,
125
+ "num_attention_heads": 16,
126
+ "num_experts": 128,
127
+ "num_global_key_value_heads": 2,
128
+ "num_hidden_layers": 30,
129
+ "num_key_value_heads": 8,
130
+ "pad_token_id": 0,
131
+ "rms_norm_eps": 1e-06,
132
+ "rope_parameters": {
133
+ "full_attention": {
134
+ "partial_rotary_factor": 0.25,
135
+ "rope_theta": 1000000.0,
136
+ "rope_type": "proportional"
137
+ },
138
+ "sliding_attention": {
139
+ "rope_theta": 10000.0,
140
+ "rope_type": "default"
141
+ }
142
+ },
143
+ "sliding_window": 1024,
144
+ "tie_word_embeddings": true,
145
+ "top_k_experts": 8,
146
+ "use_bidirectional_attention": null,
147
+ "vocab_size": 262144
148
+ },
149
+ "tie_word_embeddings": true,
150
+ "token_ce_normalization": "per_sample_exposure_v1",
151
+ "token_ce_supervision": "valid_canvas_v1",
152
+ "token_loss_weight": 1.0,
153
+ "training_bptt_steps": 16,
154
+ "training_prefix_cache": "incremental",
155
+ "training_scheme": "gold_prefix_shared_commit_0_256_committed_ce_calibration_causal_throughput_terminal_sft",
156
+ "transformers_version": "5.14.1",
157
+ "turn_end_token_id": 106,
158
+ "vocab_chunk_size": 32768,
159
+ "working_memory_bus": true,
160
+ "writer_slot_gate": "per_slot"
161
+ }
configuration_modilify_mk2.py ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Strict text-only configuration for the schema25 GDN2 protocol."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import Any
7
+
8
+ from transformers.configuration_utils import PreTrainedConfig
9
+
10
+ from transformers.models.diffusion_gemma import DiffusionGemmaTextConfig
11
+
12
+
13
+ STATE_SCHEMA_VERSION = 25
14
+ # Keep topology stable; CE scope and normalization have separate objective tags.
15
+ TRAINING_SCHEME = (
16
+ "gold_prefix_shared_commit_0_256_committed_ce_calibration_"
17
+ "causal_throughput_terminal_sft"
18
+ )
19
+ MEMORY_SCHEME = "dual_timescale_gdn2_trajectory_memory"
20
+ MEMORY_ARCHITECTURE = "compact_gdn2_v2"
21
+ CONFIG_PROTOCOL_ERROR = "ModilifyMk2 requires schema25 compact_gdn2_v2; older memory topologies require a new run."
22
+ HISTORY_VIEWS = 4
23
+ COMMIT_SEQUENCE_LAYERS = 2
24
+ DENOISE_TEMPERATURE = 0.8
25
+ VOCAB_CHUNK_SIZE = 32_768
26
+ COMMIT_READINESS_OBJECTIVE = "frontier_prefix_budget_v2"
27
+ TOKEN_CE_SUPERVISION = "valid_canvas_v1"
28
+ TOKEN_CE_NORMALIZATION = "per_sample_exposure_v1"
29
+
30
+
31
+ def require_current_checkpoint_protocol(metadata: Mapping[str, Any]) -> None:
32
+ """Reject checkpoints that do not implement the GDN2 state topology."""
33
+
34
+ if (
35
+ metadata.get("state_schema_version") != STATE_SCHEMA_VERSION
36
+ or metadata.get("training_scheme") != TRAINING_SCHEME
37
+ or metadata.get("memory_scheme") != MEMORY_SCHEME
38
+ or metadata.get("memory_architecture") != MEMORY_ARCHITECTURE
39
+ ):
40
+ raise RuntimeError(
41
+ f"ModilifyMk2 checkpoint requires schema{STATE_SCHEMA_VERSION} {MEMORY_ARCHITECTURE}; "
42
+ "older memory topologies cannot be restored. Start from the base model."
43
+ )
44
+
45
+
46
+ class ModilifyMk2TextConfig(DiffusionGemmaTextConfig):
47
+ model_type = "modilify_mk2_text"
48
+ vocab_size: int = 262_144
49
+ hidden_size: int = 2816
50
+ intermediate_size: int = 2112
51
+ num_hidden_layers: int = 30
52
+ num_attention_heads: int = 16
53
+ num_key_value_heads: int = 8
54
+ head_dim: int = 256
55
+ max_position_embeddings: int = 262_144
56
+ sliding_window: int = 1024
57
+ use_bidirectional_attention: str | None = None
58
+ num_global_key_value_heads: int | None = 2
59
+ global_head_dim: int = 512
60
+ num_experts: int | None = 128
61
+ top_k_experts: int | None = 8
62
+ moe_intermediate_size: int | None = 704
63
+
64
+
65
+ class ModilifyMk2Config(PreTrainedConfig):
66
+ """Configuration with recurrent GDN2 trajectory memory."""
67
+
68
+ model_type = "modilify_mk2"
69
+ sub_configs = {"text_config": ModilifyMk2TextConfig}
70
+
71
+ def __init__(
72
+ self,
73
+ text_config: ModilifyMk2TextConfig | dict[str, Any] | None = None,
74
+ *,
75
+ canvas_length: int = 256,
76
+ initializer_range: float = 0.02,
77
+ tie_word_embeddings: bool = True,
78
+ state_schema_version: int = STATE_SCHEMA_VERSION,
79
+ training_scheme: str = TRAINING_SCHEME,
80
+ memory_scheme: str = MEMORY_SCHEME,
81
+ memory_architecture: str = MEMORY_ARCHITECTURE,
82
+ latent_dim: int = 2816,
83
+ latent_ffn_dim: int = 7168,
84
+ latent_memory_slots: int = 256,
85
+ latent_num_layers: int = 4,
86
+ latent_num_heads: int = 16,
87
+ latent_local_attention_window: int = 128,
88
+ latent_history_length: int = 16,
89
+ latent_tape_probes: int = 1,
90
+ latent_tape_scheme: str = "gdn2_spatial_probe_v1",
91
+ latent_history_views: int = HISTORY_VIEWS,
92
+ latent_history_kv_rank: int | None = None,
93
+ latent_working_last_block_global: bool = True,
94
+ working_memory_bus: bool = True,
95
+ persistent_memory_bus: bool = True,
96
+ persistent_memory_write: str = "commit_only_transformer",
97
+ commit_sequence_layers: int = COMMIT_SEQUENCE_LAYERS,
98
+ commit_sequence_dim: int | None = None,
99
+ writer_slot_gate: str = "per_slot",
100
+ latent_working_bus_unfreeze_steps: int = 0,
101
+ latent_persistent_bus_unfreeze_steps: int = 0,
102
+ training_bptt_steps: int = 16,
103
+ kv_cache_bucket_size: int = 128,
104
+ turn_end_token_id: int = 106,
105
+ terminal_token_ids: Sequence[int] | None = None,
106
+ channel_end_token_id: int = 101,
107
+ eos_token_id: int = 1,
108
+ commit_failure_budget: float = 0.2,
109
+ commit_top_k: int | None = 40,
110
+ commit_min_p: float | None = 0.05,
111
+ commit_target_confidence: float | None = 0.5,
112
+ commit_entropy_weight: float = 1.0,
113
+ commit_confidence_power: float = 1.0,
114
+ commit_gold_alpha: float = 0.4,
115
+ commit_gold_weight: float = 1.0,
116
+ commit_readiness_failure_weight: float = 1.0,
117
+ token_loss_weight: float = 1.0,
118
+ token_ce_supervision: str = TOKEN_CE_SUPERVISION,
119
+ token_ce_normalization: str = TOKEN_CE_NORMALIZATION,
120
+ confidence_calibration_loss_weight: float = 0.1,
121
+ commit_readiness_objective: str = COMMIT_READINESS_OBJECTIVE,
122
+ commit_readiness_target_tokens: int = 16,
123
+ commit_readiness_loss_weight: float = 0.1,
124
+ commit_readiness_budget_margin: float = 0.02,
125
+ commit_readiness_beta: float = 0.02,
126
+ terminal_stop_loss_weight: float = 1.0,
127
+ terminal_stop_target_probability: float = 0.95,
128
+ **kwargs: Any,
129
+ ) -> None:
130
+ if any(key.startswith("commit_throughput_") for key in kwargs):
131
+ raise ValueError("Removed commit-throughput configuration fields.")
132
+ if token_ce_supervision != TOKEN_CE_SUPERVISION:
133
+ raise ValueError("Schema25 requires valid-canvas token CE.")
134
+ if state_schema_version != STATE_SCHEMA_VERSION:
135
+ raise RuntimeError(CONFIG_PROTOCOL_ERROR)
136
+ if training_scheme != TRAINING_SCHEME or memory_scheme != MEMORY_SCHEME:
137
+ raise RuntimeError(CONFIG_PROTOCOL_ERROR)
138
+ if memory_architecture != MEMORY_ARCHITECTURE:
139
+ raise RuntimeError("Schema25 requires compact_gdn2_v2 memory; start a new run.")
140
+ if text_config is None:
141
+ text_config = ModilifyMk2TextConfig()
142
+ elif isinstance(text_config, dict):
143
+ text_config = dict(text_config)
144
+ text_config.pop("model_type", None)
145
+ text_config["use_bidirectional_attention"] = None
146
+ text_config = ModilifyMk2TextConfig(**text_config)
147
+ elif not isinstance(text_config, ModilifyMk2TextConfig):
148
+ payload = text_config.to_dict()
149
+ payload.pop("model_type", None)
150
+ text_config = ModilifyMk2TextConfig(**payload)
151
+
152
+ self.text_config = text_config
153
+ self.canvas_length = canvas_length
154
+ self.initializer_range = initializer_range
155
+ self.state_schema_version = STATE_SCHEMA_VERSION
156
+ self.training_scheme = training_scheme
157
+ self.memory_scheme = memory_scheme
158
+ self.memory_architecture = memory_architecture
159
+ self.latent_dim = latent_dim
160
+ self.latent_ffn_dim = latent_ffn_dim
161
+ self.latent_memory_slots = latent_memory_slots
162
+ self.latent_num_layers = latent_num_layers
163
+ self.latent_num_heads = latent_num_heads
164
+ self.latent_local_attention_window = latent_local_attention_window
165
+ self.latent_history_length = latent_history_length
166
+ self.latent_tape_probes = int(latent_tape_probes)
167
+ self.latent_tape_scheme = str(latent_tape_scheme)
168
+ self.latent_history_views = int(latent_history_views)
169
+ if latent_history_kv_rank is None:
170
+ rank = min(1024, latent_dim)
171
+ rank -= rank % max(latent_num_heads, 1)
172
+ if rank <= 0:
173
+ rank = latent_num_heads
174
+ self.latent_history_kv_rank = rank
175
+ else:
176
+ self.latent_history_kv_rank = latent_history_kv_rank
177
+ self.latent_working_last_block_global = bool(latent_working_last_block_global)
178
+ self.working_memory_bus = bool(working_memory_bus)
179
+ self.persistent_memory_bus = bool(persistent_memory_bus)
180
+ self.persistent_memory_write = str(persistent_memory_write)
181
+ self.commit_sequence_layers = int(commit_sequence_layers)
182
+ if commit_sequence_dim is None:
183
+ self.commit_sequence_dim = self.latent_history_kv_rank
184
+ else:
185
+ self.commit_sequence_dim = int(commit_sequence_dim)
186
+ self.writer_slot_gate = str(writer_slot_gate)
187
+ self.latent_working_bus_unfreeze_steps = int(latent_working_bus_unfreeze_steps)
188
+ self.latent_persistent_bus_unfreeze_steps = int(
189
+ latent_persistent_bus_unfreeze_steps
190
+ )
191
+ self.training_bptt_steps = training_bptt_steps
192
+ self.kv_cache_bucket_size = kv_cache_bucket_size
193
+ self.turn_end_token_id = turn_end_token_id
194
+ if terminal_token_ids is None:
195
+ self.terminal_token_ids = (int(turn_end_token_id),)
196
+ else:
197
+ self.terminal_token_ids = tuple(int(token_id) for token_id in terminal_token_ids)
198
+ self.channel_end_token_id = int(channel_end_token_id)
199
+ self.commit_failure_budget = float(commit_failure_budget)
200
+ self.commit_top_k = int(commit_top_k) if commit_top_k is not None else None
201
+ self.commit_min_p = float(commit_min_p) if commit_min_p is not None else None
202
+ self.commit_target_confidence = float(commit_target_confidence) if commit_target_confidence is not None else None
203
+ self.commit_entropy_weight = float(commit_entropy_weight)
204
+ self.commit_confidence_power = float(commit_confidence_power)
205
+ self.commit_gold_alpha = float(commit_gold_alpha)
206
+ self.commit_gold_weight = float(commit_gold_weight)
207
+ self.commit_readiness_failure_weight = float(commit_readiness_failure_weight)
208
+ self.token_loss_weight = token_loss_weight
209
+ self.token_ce_supervision = str(token_ce_supervision)
210
+ self.token_ce_normalization = str(token_ce_normalization)
211
+ self.confidence_calibration_loss_weight = confidence_calibration_loss_weight
212
+ self.commit_readiness_objective = str(commit_readiness_objective)
213
+ self.commit_readiness_target_tokens = int(commit_readiness_target_tokens)
214
+ self.commit_readiness_loss_weight = float(commit_readiness_loss_weight)
215
+ self.commit_readiness_budget_margin = float(commit_readiness_budget_margin)
216
+ self.commit_readiness_beta = float(commit_readiness_beta)
217
+ self.terminal_stop_loss_weight = terminal_stop_loss_weight
218
+ self.terminal_stop_target_probability = terminal_stop_target_probability
219
+ self.vocab_chunk_size = VOCAB_CHUNK_SIZE
220
+ super().__init__(
221
+ tie_word_embeddings=tie_word_embeddings,
222
+ eos_token_id=eos_token_id,
223
+ **kwargs,
224
+ )
225
+ self._validate()
226
+
227
+ def _validate(self) -> None:
228
+ positive = (
229
+ self.canvas_length, self.latent_dim, self.latent_ffn_dim,
230
+ self.latent_memory_slots,
231
+ self.latent_num_layers, self.latent_num_heads,
232
+ self.latent_local_attention_window, self.latent_history_length,
233
+ self.latent_tape_probes,
234
+ self.latent_history_kv_rank,
235
+ self.commit_sequence_layers, self.commit_sequence_dim,
236
+ self.training_bptt_steps, self.kv_cache_bucket_size,
237
+ )
238
+ if any(value <= 0 for value in positive):
239
+ raise ValueError("All schema25 dimensions and intervals must be positive.")
240
+ if self.canvas_length != 256:
241
+ raise ValueError("Schema25 requires a 256-token canvas.")
242
+ if self.latent_tape_scheme != "gdn2_spatial_probe_v1":
243
+ raise ValueError("Schema25 requires GDN2 spatial probes.")
244
+ if self.latent_tape_probes > self.canvas_length:
245
+ raise ValueError("Spatial probes cannot exceed canvas positions.")
246
+ if self.commit_sequence_dim % self.latent_num_heads:
247
+ raise ValueError("`commit_sequence_dim` must be divisible by `latent_num_heads`.")
248
+ if self.latent_working_bus_unfreeze_steps < 0:
249
+ raise ValueError("`latent_working_bus_unfreeze_steps` must be non-negative.")
250
+ if self.latent_persistent_bus_unfreeze_steps < 0:
251
+ raise ValueError(
252
+ "`latent_persistent_bus_unfreeze_steps` must be non-negative."
253
+ )
254
+ if self.latent_persistent_bus_unfreeze_steps < self.latent_working_bus_unfreeze_steps:
255
+ raise ValueError(
256
+ "Persistent bus must unfreeze no earlier than the working bus."
257
+ )
258
+ if self.latent_history_kv_rank > self.latent_dim:
259
+ raise ValueError("`latent_history_kv_rank` must not exceed `latent_dim`.")
260
+ if self.latent_history_kv_rank % self.latent_num_heads:
261
+ raise ValueError("`latent_history_kv_rank` must be divisible by `latent_num_heads`.")
262
+ if not isinstance(self.eos_token_id, int) or self.eos_token_id < 0:
263
+ raise ValueError("ModilifyMk2 requires one non-negative integer EOS token ID.")
264
+ if not isinstance(self.channel_end_token_id, int) or self.channel_end_token_id < 0:
265
+ raise ValueError("`channel_end_token_id` must be a non-negative integer.")
266
+ if not self.terminal_token_ids:
267
+ raise ValueError("`terminal_token_ids` must not be empty.")
268
+ if any(
269
+ not isinstance(token_id, int) or token_id < 0
270
+ for token_id in self.terminal_token_ids
271
+ ):
272
+ raise ValueError("`terminal_token_ids` must be non-negative integers.")
273
+ if self.latent_dim % self.latent_num_heads:
274
+ raise ValueError("`latent_dim` must be divisible by `latent_num_heads`.")
275
+ if self.commit_readiness_objective != COMMIT_READINESS_OBJECTIVE:
276
+ raise ValueError(
277
+ "Unsupported commit-readiness objective: "
278
+ f"{self.commit_readiness_objective!r}."
279
+ )
280
+ import math
281
+ if not all(math.isfinite(v) for v in (
282
+ self.commit_failure_budget, self.commit_entropy_weight,
283
+ self.commit_confidence_power, self.commit_gold_alpha, self.commit_gold_weight,
284
+ self.commit_readiness_failure_weight, self.commit_readiness_loss_weight,
285
+ )):
286
+ raise ValueError("Commit policy parameters must be finite.")
287
+ if self.commit_failure_budget <= 0 or self.commit_entropy_weight < 0:
288
+ raise ValueError("Commit budget must be positive and entropy weight non-negative.")
289
+ if self.commit_top_k is not None and self.commit_top_k <= 0:
290
+ raise ValueError("`commit_top_k` must be a positive integer.")
291
+ if self.commit_min_p is not None and not 0.0 < self.commit_min_p < 1.0:
292
+ raise ValueError("`commit_min_p` must be in (0, 1).")
293
+ if self.commit_target_confidence is not None and not 0.0 < self.commit_target_confidence < 1.0:
294
+ raise ValueError("`commit_target_confidence` must be in (0, 1).")
295
+ if self.commit_confidence_power <= 0 or not 0 < self.commit_gold_alpha < 1:
296
+ raise ValueError("Commit power must be positive and gold alpha in (0, 1).")
297
+ if self.commit_gold_weight <= 0:
298
+ raise ValueError("Commit gold weight must be positive.")
299
+ if self.commit_readiness_failure_weight < 0:
300
+ raise ValueError("Commit readiness failure weight must be non-negative.")
301
+ if self.commit_readiness_target_tokens <= 0:
302
+ raise ValueError("`commit_readiness_target_tokens` must be positive.")
303
+ if self.commit_readiness_loss_weight < 0:
304
+ raise ValueError("`commit_readiness_loss_weight` must be non-negative.")
305
+ if not 0.0 <= self.commit_readiness_budget_margin < self.commit_failure_budget:
306
+ raise ValueError(
307
+ "`commit_readiness_budget_margin` must be in [0, commit_failure_budget)."
308
+ )
309
+ if self.commit_readiness_beta <= 0:
310
+ raise ValueError("`commit_readiness_beta` must be positive.")
311
+ if self.terminal_stop_loss_weight < 0:
312
+ raise ValueError("`terminal_stop_loss_weight` must be non-negative.")
313
+ if not 0.0 < self.terminal_stop_target_probability < 1.0:
314
+ raise ValueError("`terminal_stop_target_probability` must be in (0, 1).")
315
+ loss_weights = (
316
+ self.token_loss_weight,
317
+ self.confidence_calibration_loss_weight,
318
+ self.commit_readiness_loss_weight,
319
+ self.terminal_stop_loss_weight,
320
+ )
321
+ if any(not math.isfinite(weight) or weight < 0 for weight in loss_weights):
322
+ raise ValueError("Schema25 fixed loss weights must be finite and non-negative.")
323
+ if self.token_ce_normalization != TOKEN_CE_NORMALIZATION:
324
+ raise ValueError("Unsupported token CE normalization.")
325
+
326
+ @classmethod
327
+ def from_dict(cls, config_dict: dict[str, Any], **kwargs: Any) -> "ModilifyMk2Config":
328
+ return_unused_kwargs = kwargs.pop("return_unused_kwargs", False)
329
+ payload = dict(config_dict)
330
+ if (payload.get("state_schema_version") != STATE_SCHEMA_VERSION
331
+ or payload.get("memory_architecture") != MEMORY_ARCHITECTURE):
332
+ raise RuntimeError(CONFIG_PROTOCOL_ERROR)
333
+ payload.pop("model_type", None)
334
+ payload.pop("architectures", None)
335
+ for key in tuple(kwargs):
336
+ if key in payload:
337
+ payload[key] = kwargs.pop(key)
338
+ config = cls(**payload)
339
+ for key in tuple(kwargs):
340
+ if hasattr(config, key) or key == "name_or_path":
341
+ setattr(config, key, kwargs.pop(key))
342
+ return (config, kwargs) if return_unused_kwargs else config
343
+
344
+
345
+ __all__ = [
346
+ "ModilifyMk2Config", "ModilifyMk2TextConfig", "CONFIG_PROTOCOL_ERROR",
347
+ "COMMIT_READINESS_OBJECTIVE", "COMMIT_SEQUENCE_LAYERS", "DENOISE_TEMPERATURE",
348
+ "HISTORY_VIEWS", "MEMORY_SCHEME", "MEMORY_ARCHITECTURE", "STATE_SCHEMA_VERSION",
349
+ "TRAINING_SCHEME", "VOCAB_CHUNK_SIZE",
350
+ "TOKEN_CE_SUPERVISION", "TOKEN_CE_NORMALIZATION",
351
+ "require_current_checkpoint_protocol",
352
+ ]
continuous_batching.py ADDED
@@ -0,0 +1,1737 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Continuous batching for ModilifyMk2 behind the Transformers public API shape.
2
+
3
+ The upstream continuous runner is autoregressive: it persists every query in a
4
+ paged cache and emits exactly one token per request and step. ModilifyMk2 instead
5
+ denoises a transient bidirectional canvas and may accept a ragged token chunk.
6
+ This module consequently owns the request runner while preserving the public
7
+ manager lifecycle and ``GenerationOutput`` contract.
8
+
9
+ Accepted prefix K/V is stored per request without padding. Every heavy denoise
10
+ step creates a temporary, left-padded batched cache view. The decoder only
11
+ reads that view, so padding can never become persistent or evict real tokens
12
+ from a sliding-window cache.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import copy
19
+ import hashlib
20
+ import math
21
+ import os
22
+ import queue
23
+ import threading
24
+ import time
25
+ import uuid
26
+ import warnings
27
+ from collections import defaultdict, deque
28
+ from collections.abc import Callable, Generator, Sequence
29
+ from dataclasses import asdict, dataclass, field, is_dataclass, replace
30
+ from typing import Any
31
+
32
+ import torch
33
+ from transformers.cache_utils import Cache, DynamicCache
34
+ from transformers.generation.configuration_utils import ContinuousBatchingConfig
35
+ from transformers.generation.continuous_batching.requests import (
36
+ GenerationOutput,
37
+ RequestStatus,
38
+ )
39
+
40
+ from .commit_policy import fused_commit_failure_rate, select_commit_lengths
41
+ from .generation_modilify_mk2 import (
42
+ ModilifyMk2GenerationConfig,
43
+ ModilifyMk2GenerationOutput,
44
+ ModilifyMk2RollingState,
45
+ NoiseCanvasSampler,
46
+ _add_repetition_history,
47
+ _flatten_token_ids,
48
+ build_denoise_trace_event,
49
+ )
50
+ from .latent_deliberation import LatentDeliberationState, cat_latent_states, infer_commit_reason, slice_latent_state
51
+ from .generation_modilify_mk2 import deterministic_episode_iteration_bound
52
+
53
+
54
+ _TERMINAL_REASONS = frozenset(
55
+ {
56
+ "turn_end",
57
+ "eos",
58
+ "max_new_tokens",
59
+ "max_denoising_steps",
60
+ "episode_watchdog",
61
+ "cancelled",
62
+ "error",
63
+ }
64
+ )
65
+
66
+
67
+ def continuous_config_fingerprint(
68
+ generation_config: Any,
69
+ continuous_batching_config: ContinuousBatchingConfig | None,
70
+ ) -> str:
71
+ """Return a stable-enough in-process fingerprint for persistent reuse."""
72
+
73
+ generation_payload = (
74
+ generation_config.to_dict()
75
+ if hasattr(generation_config, "to_dict")
76
+ else vars(generation_config)
77
+ )
78
+ batching = continuous_batching_config or ContinuousBatchingConfig()
79
+ batching_payload = asdict(batching) if is_dataclass(batching) else vars(batching)
80
+ return repr(
81
+ (
82
+ sorted(generation_payload.items(), key=lambda item: item[0]),
83
+ sorted(batching_payload.items(), key=lambda item: item[0]),
84
+ )
85
+ )
86
+
87
+
88
+ @dataclass
89
+ class ModilifyMk2ContinuousGenerationOutput(GenerationOutput):
90
+ """Official ``GenerationOutput`` plus ModilifyMk2 request-local diagnostics."""
91
+
92
+ stop_reason: str | None = None
93
+ committed_tokens: int = 0
94
+ denoise_steps: int = 0
95
+ no_progress_steps: int = 0
96
+ jump_count: int = 0
97
+ forced_jump_bad_count: int = 0
98
+ heavy_forward_count: int = 0
99
+ latent_context_update_count: int = 0
100
+ average_commit_len: float = 0.0
101
+ tokens_per_forward: float = 0.0
102
+ seed: int | None = None
103
+ scheduler_run_id: str | None = None
104
+ queue_seconds: float = 0.0
105
+ inference_seconds: float = 0.0
106
+ total_seconds: float = 0.0
107
+ last_step_batch_size: int = 0
108
+ is_stream_update: bool = False
109
+ delta_tokens: list[int] = field(default_factory=list)
110
+ state_shift_count: int = 0
111
+ latent_memory_norm: float = 0.0
112
+ state_retention_score: float = 0.0
113
+
114
+ def is_finished(self) -> bool:
115
+ """Treat failed/cancelled requests as terminal for every consumer API."""
116
+
117
+ return self.status in {RequestStatus.FINISHED, RequestStatus.FAILED}
118
+
119
+
120
+ @dataclass
121
+ class ModilifyMk2RequestState:
122
+ """All mutable state required to suspend and re-batch one request."""
123
+
124
+ request_id: str
125
+ prompt_ids: list[int]
126
+ max_new_tokens: int
127
+ eos_token_ids: tuple[int, ...]
128
+ streaming: bool
129
+ record_timestamps: bool
130
+ seed: int
131
+ max_denoising_steps: int | None
132
+ trace_callback: Callable[[dict[str, object]], None] | None = None
133
+ created_time: float = field(default_factory=time.perf_counter)
134
+ status: RequestStatus = RequestStatus.PENDING
135
+ started_time: float = -1.0
136
+ finished_time: float = -1.0
137
+ generated_tokens: list[int] = field(default_factory=list)
138
+ logprobs: list[float] = field(default_factory=list)
139
+ timestamps: list[float] = field(default_factory=list)
140
+ cache: Cache | None = None
141
+ rolling_state: ModilifyMk2RollingState | None = None
142
+ repetition_history: torch.BoolTensor | None = None
143
+ generator: torch.Generator | None = None
144
+ logical_length: int = 0
145
+ max_iterations: int = 0
146
+ reserved_blocks: int = 0
147
+ denoise_steps: int = 0
148
+ jumps: int = 0
149
+ forced_jump_tokens: int = 0
150
+ shifts: int = 0
151
+ stop_reason: str | None = None
152
+ error: str | None = None
153
+ terminal_emitted: bool = False
154
+ last_step_batch_size: int = 0
155
+ last_delta_tokens: list[int] = field(default_factory=list)
156
+
157
+
158
+ def _clone_tensor_row(value: torch.Tensor, row: int) -> torch.Tensor:
159
+ return value[row : row + 1].clone()
160
+
161
+
162
+ def _slice_rolling_state(state: ModilifyMk2RollingState, row: int) -> ModilifyMk2RollingState:
163
+ selected = slice(row, row + 1)
164
+ return ModilifyMk2RollingState(
165
+ canvas=_clone_tensor_row(state.canvas, row),
166
+ confidence=_clone_tensor_row(state.confidence, row),
167
+ entropy=_clone_tensor_row(state.entropy, row),
168
+ age=_clone_tensor_row(state.age, row),
169
+ latent_state=slice_latent_state(state.latent_state, selected),
170
+
171
+
172
+ head=_clone_tensor_row(state.head, row),
173
+ )
174
+
175
+
176
+ def _pack_rolling_states(states: Sequence[ModilifyMk2RollingState]) -> ModilifyMk2RollingState:
177
+ return ModilifyMk2RollingState(
178
+ canvas=torch.cat([state.canvas for state in states], dim=0),
179
+ confidence=torch.cat([state.confidence for state in states], dim=0),
180
+ entropy=torch.cat([state.entropy for state in states], dim=0),
181
+ age=torch.cat([state.age for state in states], dim=0),
182
+ latent_state=cat_latent_states([state.latent_state for state in states]),
183
+
184
+
185
+ head=torch.cat([state.head for state in states], dim=0),
186
+ )
187
+
188
+
189
+ class ModilifyMk2LogicalCachePool:
190
+ """Per-request hole-free cache storage with ephemeral batched read views."""
191
+
192
+ def __init__(self, model: Any, *, max_batch_tokens: int | None = None) -> None:
193
+ self.model = model
194
+ self.text_config = model.config.get_text_config(decoder=True)
195
+ self.device = model.model.decoder.embed_tokens.weight.device
196
+ self.max_batch_tokens = max_batch_tokens
197
+
198
+ def new_cache(self) -> DynamicCache:
199
+ return DynamicCache(config=self.text_config)
200
+
201
+ @torch.inference_mode()
202
+ def prefill(self, prompt_ids: Sequence[int]) -> Cache:
203
+ cache = self.new_cache()
204
+ chunk_size = self.max_batch_tokens or len(prompt_ids)
205
+ for start in range(0, len(prompt_ids), chunk_size):
206
+ stop = min(start + chunk_size, len(prompt_ids))
207
+ tokens = torch.tensor(
208
+ [list(prompt_ids[start:stop])], device=self.device, dtype=torch.long
209
+ )
210
+ mask = torch.ones(1, stop, device=self.device, dtype=torch.bool)
211
+ positions = torch.arange(
212
+ start, stop, device=self.device, dtype=torch.int32
213
+ ).unsqueeze(0)
214
+ cache = self.model.model.encoder(
215
+ input_ids=tokens,
216
+ attention_mask=mask,
217
+ past_key_values=cache,
218
+ position_ids=positions,
219
+ ).past_key_values
220
+ return cache
221
+
222
+ @torch.inference_mode()
223
+ def append(self, state: ModilifyMk2RequestState, token_ids: Sequence[int]) -> None:
224
+ if not token_ids:
225
+ return
226
+ if state.cache is None:
227
+ raise RuntimeError("Cannot append tokens before request prefill.")
228
+ tokens = torch.tensor([list(token_ids)], device=self.device, dtype=torch.long)
229
+ positions = torch.arange(
230
+ state.logical_length,
231
+ state.logical_length + tokens.shape[1],
232
+ device=self.device,
233
+ dtype=torch.int32,
234
+ ).unsqueeze(0)
235
+ mask = torch.ones(
236
+ 1,
237
+ state.logical_length + tokens.shape[1],
238
+ device=self.device,
239
+ dtype=torch.bool,
240
+ )
241
+ state.cache = self.model.model.encoder(
242
+ input_ids=tokens,
243
+ attention_mask=mask,
244
+ past_key_values=state.cache,
245
+ position_ids=positions,
246
+ ).past_key_values
247
+
248
+ def pack(
249
+ self, states: Sequence[ModilifyMk2RequestState]
250
+ ) -> tuple[DynamicCache, torch.BoolTensor, torch.LongTensor]:
251
+ if not states or any(state.cache is None for state in states):
252
+ raise ValueError("Every packed request must have an initialized cache.")
253
+ logical_lengths = torch.tensor(
254
+ [state.logical_length for state in states],
255
+ device=self.device,
256
+ dtype=torch.long,
257
+ )
258
+ maximum_length = int(logical_lengths.max())
259
+ attention_mask = torch.arange(
260
+ maximum_length, device=self.device
261
+ )[None, :].ge(maximum_length - logical_lengths[:, None])
262
+
263
+ packed = self.new_cache()
264
+ source_caches = [state.cache for state in states]
265
+ assert all(cache is not None for cache in source_caches)
266
+ if any(len(cache.layers) != len(packed.layers) for cache in source_caches):
267
+ raise RuntimeError("Request cache layer structures differ.")
268
+
269
+ for layer_index, packed_layer in enumerate(packed.layers):
270
+ source_layers = [cache.layers[layer_index] for cache in source_caches]
271
+ if any(not layer.is_initialized for layer in source_layers):
272
+ raise RuntimeError("Request cache contains an uninitialized layer.")
273
+ stored_lengths = [int(layer.keys.shape[-2]) for layer in source_layers]
274
+ maximum_stored = max(stored_lengths)
275
+
276
+ def padded(name: str) -> torch.Tensor:
277
+ values = []
278
+ for layer, stored_length in zip(source_layers, stored_lengths, strict=True):
279
+ value = getattr(layer, name)
280
+ if stored_length < maximum_stored:
281
+ padding = value.new_zeros(
282
+ value.shape[0],
283
+ value.shape[1],
284
+ maximum_stored - stored_length,
285
+ value.shape[3],
286
+ )
287
+ value = torch.cat((padding, value), dim=-2)
288
+ values.append(value)
289
+ return torch.cat(values, dim=0)
290
+
291
+ keys = padded("keys")
292
+ values = padded("values")
293
+ packed_layer.lazy_initialization(keys, values)
294
+ packed_layer.keys = keys
295
+ packed_layer.values = values
296
+ if hasattr(packed_layer, "cumulative_length"):
297
+ packed_layer.cumulative_length = maximum_length
298
+ return packed, attention_mask, logical_lengths
299
+
300
+
301
+ class ModilifyMk2ContinuousBatchingManager:
302
+ """FIFO/prefill-first continuous manager compatible with Transformers APIs."""
303
+
304
+ def __init__(
305
+ self,
306
+ model: Any,
307
+ generation_config: ModilifyMk2GenerationConfig | None,
308
+ continuous_batching_config: ContinuousBatchingConfig | None,
309
+ workload_hints: Any = None,
310
+ ) -> None:
311
+ del workload_hints
312
+ # Generation must not silently mutate the caller's train/eval mode.
313
+ # Inference mode below disables autograd without changing module-local
314
+ # dropout or other training flags.
315
+ self.model = model
316
+ self.generation_config = copy.deepcopy(
317
+ generation_config or getattr(model, "generation_config", None)
318
+ or ModilifyMk2GenerationConfig.from_model_config(model.config)
319
+ )
320
+ if not isinstance(self.generation_config, ModilifyMk2GenerationConfig):
321
+ payload = self.generation_config.to_dict()
322
+ self.generation_config = ModilifyMk2GenerationConfig(**payload)
323
+ self.continuous_batching_config = copy.deepcopy(
324
+ continuous_batching_config or ContinuousBatchingConfig()
325
+ )
326
+ self.config_fingerprint = continuous_config_fingerprint(
327
+ self.generation_config, self.continuous_batching_config
328
+ )
329
+ self._validate_config()
330
+
331
+ self.device = model.model.decoder.embed_tokens.weight.device
332
+ self.dtype = model.model.decoder.embed_tokens.weight.dtype
333
+ self.cache_pool = ModilifyMk2LogicalCachePool(
334
+ model,
335
+ max_batch_tokens=self.continuous_batching_config.max_batch_tokens,
336
+ )
337
+ self.sampler: NoiseCanvasSampler = model._prepare_sampler(
338
+ self.generation_config, model.config.canvas_length
339
+ )
340
+ pad_token_id = self.generation_config.pad_token_id
341
+ if pad_token_id is None:
342
+ pad_token_id = getattr(model.config, "pad_token_id", None)
343
+ if isinstance(pad_token_id, (list, tuple)):
344
+ pad_token_id = pad_token_id[0]
345
+ self.pad_token_id = int(0 if pad_token_id is None else pad_token_id)
346
+ self.run_id = uuid.uuid4().hex
347
+ self.warmed_up = False
348
+ self.destroyed = False
349
+
350
+ configured_requests = self.continuous_batching_config.max_requests_per_batch
351
+ self.max_requests_per_batch = int(configured_requests or 8)
352
+ max_batch_tokens = self.continuous_batching_config.max_batch_tokens
353
+ if max_batch_tokens is not None:
354
+ token_capacity = int(max_batch_tokens) // int(model.config.canvas_length)
355
+ if token_capacity < 1:
356
+ raise ValueError(
357
+ "`max_batch_tokens` must fit at least one ModilifyMk2 canvas."
358
+ )
359
+ self.max_requests_per_batch = min(
360
+ self.max_requests_per_batch, token_capacity
361
+ )
362
+ self.block_size = int(self.continuous_batching_config.block_size)
363
+ self.block_capacity = self._resolve_block_capacity()
364
+ self._base_seed = (
365
+ int(self.continuous_batching_config.seed)
366
+ if self.continuous_batching_config.seed is not None
367
+ else int(torch.initial_seed())
368
+ )
369
+
370
+ self._condition = threading.Condition(threading.RLock())
371
+ self._pending: deque[ModilifyMk2RequestState] = deque()
372
+ self._active: dict[str, ModilifyMk2RequestState] = {}
373
+ self._known_request_ids: set[str] = set()
374
+ self._cancelled: set[str] = set()
375
+ self._output_queue: queue.Queue[ModilifyMk2ContinuousGenerationOutput] = queue.Queue()
376
+ self._stashed_outputs: dict[
377
+ str, deque[ModilifyMk2ContinuousGenerationOutput]
378
+ ] = defaultdict(deque)
379
+ self._result_handlers: dict[str, tuple[Callable, asyncio.AbstractEventLoop]] = {}
380
+ self._thread: threading.Thread | None = None
381
+ self._finished = threading.Event()
382
+ self.fatal_error: BaseException | None = None
383
+ self._input_closed = False
384
+ self._hard_stop = False
385
+ self._keep_for_next_session = False
386
+ self._request_counter = 0
387
+ self._active_reserved_blocks = 0
388
+ self._stats = {
389
+ "submitted": 0,
390
+ "admitted": 0,
391
+ "completed": 0,
392
+ "failed": 0,
393
+ "cancelled": 0,
394
+ "model_steps": 0,
395
+ "generated_tokens": 0,
396
+ "max_observed_batch_size": 0,
397
+ "peak_reserved_blocks": 0,
398
+ "peak_cache_blocks": 0,
399
+ "active_slot_steps": 0,
400
+ "slot_capacity_steps": 0,
401
+ }
402
+
403
+ turn_end = self.generation_config.turn_end_token_id
404
+ self.turn_end_token_id = int(
405
+ model.config.turn_end_token_id if turn_end is None else turn_end
406
+ )
407
+ self.repetition_penalty = float(self.generation_config.repetition_penalty)
408
+ self.excluded_repetition_token_ids = _flatten_token_ids(
409
+ self.generation_config.repetition_penalty_exclude_token_ids,
410
+ self.generation_config.pad_token_id,
411
+ self.generation_config.bos_token_id,
412
+ self.generation_config.eos_token_id,
413
+ self.generation_config.turn_end_token_id,
414
+ getattr(model.config, "image_token_id", None),
415
+ )
416
+
417
+ def _validate_config(self) -> None:
418
+ config = self.continuous_batching_config
419
+ positive_optional = (
420
+ "num_blocks",
421
+ "max_batch_tokens",
422
+ "max_requests_per_batch",
423
+ )
424
+ if not isinstance(config.block_size, int) or config.block_size < 4:
425
+ raise ValueError("`block_size` must be an integer greater than or equal to 4.")
426
+ for name in positive_optional:
427
+ value = getattr(config, name)
428
+ if value is not None and (not isinstance(value, int) or value <= 0):
429
+ raise ValueError(f"`{name}` must be a positive integer when set.")
430
+ if config.max_blocks_per_request is not None and (
431
+ not isinstance(config.max_blocks_per_request, int)
432
+ or config.max_blocks_per_request < 0
433
+ ):
434
+ raise ValueError("`max_blocks_per_request` must be a non-negative integer.")
435
+ if not isinstance(config.max_queue_size, int) or config.max_queue_size < 0:
436
+ raise ValueError("`max_queue_size` must be a non-negative integer.")
437
+ if config.scheduler_type not in {"fifo", "prefill_first"}:
438
+ raise ValueError("ModilifyMk2 continuous batching supports `fifo` and `prefill_first`.")
439
+ if config.max_memory_percent is not None and not (
440
+ 0.0 < float(config.max_memory_percent) <= 1.0
441
+ ):
442
+ raise ValueError("`max_memory_percent` must be in (0, 1].")
443
+ if config.use_async_batching is True:
444
+ raise ValueError("ModilifyMk2 continuous batching currently uses synchronous model steps.")
445
+ requested_graphs = config.use_cuda_graph
446
+ if requested_graphs is True or (
447
+ isinstance(requested_graphs, tuple) and any(requested_graphs)
448
+ ):
449
+ raise ValueError("CUDA graphs are not supported by the ragged ModilifyMk2 runner.")
450
+ if config.cpu_offload_space is not None and config.cpu_offload_space > 0:
451
+ raise ValueError("CPU cache offload is not supported by the ModilifyMk2 runner.")
452
+ if int(config.default_compile_level or 0) > 0:
453
+ raise ValueError("Continuous ModilifyMk2 compilation is not supported yet.")
454
+ if config.varlen_compile_config is not None or config.decode_compile_config is not None:
455
+ raise ValueError("Continuous ModilifyMk2 compilation is not supported yet.")
456
+ if config.use_default_compile_configs is True:
457
+ raise ValueError("Continuous ModilifyMk2 compilation is not supported yet.")
458
+ if int(config.q_padding_interval_size or 0) > 0 or int(
459
+ config.kv_padding_interval_size or 0
460
+ ) > 0:
461
+ raise ValueError("Compiled continuous padding intervals are not supported.")
462
+ if config.max_cached_graphs is not None:
463
+ raise ValueError("Cached continuous graphs are not supported.")
464
+ if torch.distributed.is_available() and torch.distributed.is_initialized():
465
+ if torch.distributed.get_world_size() > 1:
466
+ raise ValueError(
467
+ "Tensor/distributed parallel continuous batching is not supported."
468
+ )
469
+ if getattr(self.model, "device_mesh", None) is not None or getattr(
470
+ self.model, "_device_mesh", None
471
+ ) is not None:
472
+ raise ValueError("Tensor-parallel continuous batching is not supported.")
473
+ # Prefix sharing would make request ownership and row-local RNG/state
474
+ # ambiguous. Normalize this optimization off rather than silently use it.
475
+ config.allow_block_sharing = False
476
+
477
+ def _available_memory_bytes(self) -> int | None:
478
+ if self.device.type == "cuda" and torch.cuda.is_available():
479
+ free, _ = torch.cuda.mem_get_info(self.device)
480
+ return int(free)
481
+ if self.device.type == "mps" and torch.backends.mps.is_available():
482
+ return max(
483
+ 0,
484
+ int(torch.mps.recommended_max_memory())
485
+ - int(torch.mps.driver_allocated_memory()),
486
+ )
487
+ if self.device.type == "cpu":
488
+ try:
489
+ import psutil
490
+
491
+ return int(psutil.virtual_memory().available)
492
+ except (ImportError, OSError, ValueError):
493
+ pass
494
+ try:
495
+ return int(os.sysconf("SC_AVPHYS_PAGES")) * int(
496
+ os.sysconf("SC_PAGE_SIZE")
497
+ )
498
+ except (OSError, TypeError, ValueError):
499
+ return None
500
+ return None
501
+
502
+ def _estimated_block_bytes(self) -> int:
503
+ config = self.model.config.text_config
504
+ layer_types = list(config.layer_types)
505
+ local_heads = int(config.num_key_value_heads)
506
+ local_dim = int(config.head_dim)
507
+ global_heads = int(
508
+ getattr(config, "num_global_key_value_heads", None) or local_heads
509
+ )
510
+ global_dim = int(getattr(config, "global_head_dim", None) or local_dim)
511
+ per_token = 0
512
+ for layer_type in layer_types:
513
+ if layer_type == "full_attention":
514
+ heads, dimension = global_heads, global_dim
515
+ else:
516
+ heads, dimension = local_heads, local_dim
517
+ per_token += 2 * heads * dimension * torch.empty((), dtype=self.dtype).element_size()
518
+ return max(1, per_token * int(self.continuous_batching_config.block_size))
519
+
520
+ def _resolve_block_capacity(self) -> int | None:
521
+ capacity = self.continuous_batching_config.num_blocks
522
+ percent = self.continuous_batching_config.max_memory_percent
523
+ available = self._available_memory_bytes()
524
+ if percent is None and capacity is None:
525
+ # Never make the default cache silently unbounded. This fraction is
526
+ # applied to currently available device/host memory after model load.
527
+ percent = 0.8
528
+ if percent is not None and available is None:
529
+ raise RuntimeError(
530
+ "Cannot infer available cache memory on this device; set `num_blocks` "
531
+ "explicitly instead of `max_memory_percent`."
532
+ )
533
+ if percent is not None and available is not None:
534
+ memory_blocks = int(
535
+ available * float(percent) / self._estimated_block_bytes()
536
+ )
537
+ capacity = memory_blocks if capacity is None else min(int(capacity), memory_blocks)
538
+ return None if capacity is None else max(0, int(capacity))
539
+
540
+ @staticmethod
541
+ def _block_footprint(reservations: Sequence[int]) -> int:
542
+ """Return persistent plus temporary packed-cache block equivalents."""
543
+
544
+ if not reservations:
545
+ return 0
546
+ return sum(reservations) + len(reservations) * max(reservations)
547
+
548
+ def _current_block_footprint(self) -> int:
549
+ return self._block_footprint(
550
+ [state.reserved_blocks for state in self._active.values()]
551
+ )
552
+
553
+ def _derive_seed(self, request_id: str) -> int:
554
+ digest = hashlib.sha256(
555
+ str(self._base_seed).encode("ascii")
556
+ + b"\0"
557
+ + request_id.encode("utf-8")
558
+ ).digest()
559
+ return int.from_bytes(digest[:8], "big") & ((1 << 63) - 1)
560
+
561
+ @property
562
+ def stats(self) -> dict[str, Any]:
563
+ with self._condition:
564
+ capacity_steps = int(self._stats["slot_capacity_steps"])
565
+ return {
566
+ "scheduler_run_id": self.run_id,
567
+ **self._stats,
568
+ "slot_utilization": (
569
+ float(self._stats["active_slot_steps"]) / capacity_steps
570
+ if capacity_steps
571
+ else 0.0
572
+ ),
573
+ "active_requests": len(self._active),
574
+ "pending_requests": len(self._pending),
575
+ "max_requests_per_batch": self.max_requests_per_batch,
576
+ "block_capacity": -1 if self.block_capacity is None else self.block_capacity,
577
+ "reserved_blocks": self._active_reserved_blocks,
578
+ "cache_blocks": self._current_block_footprint(),
579
+ }
580
+
581
+ def is_running(self) -> bool:
582
+ return self._thread is not None and self._thread.is_alive()
583
+
584
+ def warmup(self) -> None:
585
+ if self.destroyed:
586
+ raise RuntimeError("Cannot warm up a destroyed manager.")
587
+ # CUDA graphs and static-shape compilation are intentionally unsupported;
588
+ # normal eager kernels warm naturally on the first real batch.
589
+ self.warmed_up = True
590
+
591
+ def start(self) -> None:
592
+ if self._keep_for_next_session:
593
+ self._prepare_for_next_session()
594
+ with self._condition:
595
+ if self.destroyed:
596
+ raise RuntimeError("Cannot start a destroyed manager.")
597
+ if self.is_running():
598
+ return
599
+ self._finished.clear()
600
+ self.fatal_error = None
601
+ self._hard_stop = False
602
+ self._thread = threading.Thread(
603
+ target=self._run_generation_loop,
604
+ name=f"modilify_mk2-continuous-{self.run_id[:8]}",
605
+ daemon=True,
606
+ )
607
+ self._thread.start()
608
+
609
+ def join(
610
+ self,
611
+ stop_trigger_time: float | None = None,
612
+ timeout: float | None = None,
613
+ ) -> None:
614
+ """Wait for the current worker, matching the official manager lifecycle."""
615
+
616
+ del stop_trigger_time
617
+ with self._condition:
618
+ thread = self._thread
619
+ if thread is None or thread is threading.current_thread():
620
+ return
621
+ thread.join(timeout=timeout)
622
+ if thread.is_alive():
623
+ raise TimeoutError("Timed out waiting for continuous generation to stop.")
624
+
625
+ def _prepare_for_next_session(self) -> None:
626
+ """Finish an asynchronous prior stop and reopen a cached manager safely."""
627
+
628
+ with self._condition:
629
+ if not self._keep_for_next_session:
630
+ return
631
+ thread = self._thread
632
+ if thread is not None and thread.is_alive():
633
+ thread.join()
634
+ with self._condition:
635
+ if self.destroyed:
636
+ raise RuntimeError("Cannot reuse a destroyed manager.")
637
+ if self._pending or self._active:
638
+ raise RuntimeError("Cannot reuse a manager with unfinished requests.")
639
+ self._input_closed = False
640
+ self._hard_stop = False
641
+ self._keep_for_next_session = False
642
+ self.fatal_error = None
643
+ self._cancelled.clear()
644
+ self._condition.notify_all()
645
+
646
+ def close_input(self) -> None:
647
+ """Stop accepting requests and let the iterator drain all submitted work."""
648
+
649
+ with self._condition:
650
+ self._input_closed = True
651
+ self._condition.notify_all()
652
+
653
+ def stop(
654
+ self,
655
+ block: bool = True,
656
+ timeout: float | None = None,
657
+ keep_for_next_session: bool = False,
658
+ hard_stop: bool = False,
659
+ ) -> None:
660
+ with self._condition:
661
+ self._input_closed = True
662
+ self._hard_stop = bool(hard_stop)
663
+ self._keep_for_next_session = bool(keep_for_next_session)
664
+ if hard_stop:
665
+ self._cancelled.update(self._known_request_ids)
666
+ self._condition.notify_all()
667
+ thread = self._thread
668
+ if hard_stop and (thread is None or not thread.is_alive()):
669
+ self._apply_cancellations()
670
+ if block and thread is not None:
671
+ self.join(timeout=timeout)
672
+ if keep_for_next_session and not self.is_running():
673
+ with self._condition:
674
+ self._input_closed = False
675
+ self._hard_stop = False
676
+ self._keep_for_next_session = False
677
+ self.fatal_error = None
678
+
679
+ def destroy(self) -> None:
680
+ if self.destroyed:
681
+ return
682
+ self.stop(block=True, hard_stop=True)
683
+ self.destroyed = True
684
+ with self._condition:
685
+ self._pending.clear()
686
+ self._active.clear()
687
+ self._condition.notify_all()
688
+
689
+ def add_request(
690
+ self,
691
+ input_ids: list[int],
692
+ request_id: str | None = None,
693
+ max_new_tokens: int | None = None,
694
+ streaming: bool = False,
695
+ record_timestamps: bool = False,
696
+ eos_token_id: int | list[int] | None = None,
697
+ **request_kwargs: Any,
698
+ ) -> str:
699
+ if not input_ids or any(
700
+ not isinstance(token_id, int) or isinstance(token_id, bool)
701
+ for token_id in input_ids
702
+ ):
703
+ raise ValueError("`input_ids` must be a non-empty list of integer token IDs.")
704
+ seed = request_kwargs.pop("seed", None)
705
+ trace_callback = request_kwargs.pop("denoise_trace_callback", None)
706
+ max_denoising_steps = request_kwargs.pop(
707
+ "max_denoising_steps", self.generation_config.max_denoising_steps
708
+ )
709
+ if request_kwargs:
710
+ unsupported = ", ".join(sorted(request_kwargs))
711
+ raise ValueError(f"Unsupported per-request generation options: {unsupported}")
712
+ if trace_callback is not None and not callable(trace_callback):
713
+ raise TypeError("`denoise_trace_callback` must be callable.")
714
+ if trace_callback is not None and self.max_requests_per_batch > 1:
715
+ raise ValueError(
716
+ "ModilifyMk2 denoise tracing remains a batch-size-1 interface; "
717
+ "set `max_requests_per_batch=1`."
718
+ )
719
+ limit = self.generation_config.max_new_tokens if max_new_tokens is None else max_new_tokens
720
+ if not isinstance(limit, int) or limit <= 0:
721
+ raise ValueError("`max_new_tokens` must be a positive integer.")
722
+ if max_denoising_steps is not None and (
723
+ not isinstance(max_denoising_steps, int) or max_denoising_steps <= 0
724
+ ):
725
+ raise ValueError("`max_denoising_steps` must be a positive integer when set.")
726
+
727
+ with self._condition:
728
+ if self.destroyed or self._input_closed:
729
+ raise RuntimeError("Continuous batching manager is not accepting requests.")
730
+ if self.fatal_error is not None:
731
+ raise RuntimeError("Continuous batching manager has failed.") from self.fatal_error
732
+ if request_id is None:
733
+ request_id = f"req_{self._request_counter}"
734
+ self._request_counter += 1
735
+ if request_id in self._known_request_ids:
736
+ raise ValueError(f"Duplicate continuous request ID: {request_id}")
737
+ queue_limit = int(self.continuous_batching_config.max_queue_size)
738
+ deadline = time.monotonic() + 10.0
739
+ while queue_limit and len(self._pending) >= queue_limit:
740
+ if not self.is_running():
741
+ raise queue.Full(
742
+ "Continuous request queue is full; start the manager before "
743
+ "submitting more requests."
744
+ )
745
+ remaining = deadline - time.monotonic()
746
+ if remaining <= 0:
747
+ raise queue.Full("Continuous request queue remained full for 10 seconds.")
748
+ self._condition.wait(timeout=remaining)
749
+ if self.destroyed or self._input_closed:
750
+ raise RuntimeError(
751
+ "Continuous batching manager stopped while waiting for queue space."
752
+ )
753
+ if self.fatal_error is not None:
754
+ raise RuntimeError("Continuous batching manager has failed.") from self.fatal_error
755
+ # The worker can close/fail the manager while this producer is
756
+ # asleep. Recheck under the same lock immediately before append.
757
+ if self.destroyed or self._input_closed:
758
+ raise RuntimeError("Continuous batching manager is not accepting requests.")
759
+ if self.fatal_error is not None:
760
+ raise RuntimeError("Continuous batching manager has failed.") from self.fatal_error
761
+ if request_id in self._known_request_ids:
762
+ raise ValueError(f"Duplicate continuous request ID: {request_id}")
763
+ configured_eos = self.generation_config.eos_token_id if eos_token_id is None else eos_token_id
764
+ if configured_eos is None:
765
+ configured_eos = self.model.config.eos_token_id
766
+ eos_values = (
767
+ [configured_eos]
768
+ if isinstance(configured_eos, int)
769
+ else list(configured_eos or [])
770
+ )
771
+ stop_ids = tuple(
772
+ dict.fromkeys(
773
+ [self.turn_end_token_id, *(int(value) for value in eos_values if int(value) >= 0)]
774
+ )
775
+ )
776
+ resolved_seed = self._derive_seed(request_id) if seed is None else int(seed)
777
+ state = ModilifyMk2RequestState(
778
+ request_id=request_id,
779
+ prompt_ids=list(input_ids),
780
+ max_new_tokens=int(limit),
781
+ eos_token_ids=stop_ids,
782
+ streaming=bool(streaming),
783
+ record_timestamps=bool(record_timestamps),
784
+ seed=resolved_seed & ((1 << 63) - 1),
785
+ max_denoising_steps=max_denoising_steps,
786
+ trace_callback=trace_callback,
787
+ )
788
+ state.reserved_blocks = math.ceil(
789
+ (len(state.prompt_ids) + state.max_new_tokens) / self.block_size
790
+ )
791
+ self._pending.append(state)
792
+ self._known_request_ids.add(request_id)
793
+ self._stats["submitted"] += 1
794
+ self._condition.notify_all()
795
+ return request_id
796
+
797
+ def add_requests(
798
+ self,
799
+ inputs: list[list[int]],
800
+ max_new_tokens: int | None = None,
801
+ streaming: bool = False,
802
+ record_timestamps: bool = False,
803
+ **request_kwargs: Any,
804
+ ) -> list[str]:
805
+ request_ids = request_kwargs.pop("request_ids", None)
806
+ seeds = request_kwargs.pop("seeds", None)
807
+ if request_ids is not None and len(request_ids) != len(inputs):
808
+ raise ValueError("`request_ids` must contain one ID per request.")
809
+ if seeds is not None and len(seeds) != len(inputs):
810
+ raise ValueError("`seeds` must contain one seed per request.")
811
+ result = []
812
+ for index, input_ids in enumerate(inputs):
813
+ per_request = dict(request_kwargs)
814
+ if seeds is not None:
815
+ per_request["seed"] = seeds[index]
816
+ result.append(
817
+ self.add_request(
818
+ input_ids=input_ids,
819
+ request_id=None if request_ids is None else request_ids[index],
820
+ max_new_tokens=max_new_tokens,
821
+ streaming=streaming,
822
+ record_timestamps=record_timestamps,
823
+ **per_request,
824
+ )
825
+ )
826
+ return result
827
+
828
+ def cancel_request(self, request_id: str) -> None:
829
+ with self._condition:
830
+ if request_id in self._known_request_ids:
831
+ self._cancelled.add(request_id)
832
+ self._condition.notify_all()
833
+
834
+ def register_result_handler(self, request_id: str, callback: Callable) -> None:
835
+ loop = asyncio.get_running_loop()
836
+ with self._condition:
837
+ self._result_handlers[request_id] = (callback, loop)
838
+
839
+ def _pop_stashed(self, request_id: str | None):
840
+ with self._condition:
841
+ if request_id is not None:
842
+ values = self._stashed_outputs.get(request_id)
843
+ if values:
844
+ return values.popleft()
845
+ return None
846
+ for values in self._stashed_outputs.values():
847
+ if values:
848
+ return values.popleft()
849
+ return None
850
+
851
+ def _has_stashed_outputs(self) -> bool:
852
+ with self._condition:
853
+ return any(values for values in self._stashed_outputs.values())
854
+
855
+ def get_result(
856
+ self, request_id: str | None = None, timeout: float | None = None
857
+ ) -> ModilifyMk2ContinuousGenerationOutput | None:
858
+ stashed = self._pop_stashed(request_id)
859
+ if stashed is not None:
860
+ return stashed
861
+ if not self.is_running() and self._output_queue.empty():
862
+ return None
863
+ deadline = None if timeout is None else time.monotonic() + timeout
864
+ while True:
865
+ remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
866
+ if remaining == 0.0:
867
+ return None
868
+ try:
869
+ output = self._output_queue.get(timeout=remaining)
870
+ except queue.Empty:
871
+ return None
872
+ if request_id is None or output.request_id == request_id:
873
+ return output
874
+ with self._condition:
875
+ self._stashed_outputs[output.request_id].append(output)
876
+
877
+ def __iter__(self) -> Generator[ModilifyMk2ContinuousGenerationOutput, None, None]:
878
+ while True:
879
+ output = self.get_result(timeout=0.05)
880
+ if output is not None:
881
+ yield output
882
+ continue
883
+ if self._finished.is_set() and self._output_queue.empty():
884
+ if not self._has_stashed_outputs():
885
+ return
886
+
887
+ def request_id_iter(
888
+ self, request_id: str
889
+ ) -> Generator[ModilifyMk2ContinuousGenerationOutput, None, None]:
890
+ while True:
891
+ output = self.get_result(request_id=request_id, timeout=0.05)
892
+ if output is not None:
893
+ yield output
894
+ if output.is_finished():
895
+ return
896
+ elif self._finished.is_set():
897
+ return
898
+
899
+ def _deliver(self, output: ModilifyMk2ContinuousGenerationOutput) -> None:
900
+ handler = None
901
+ with self._condition:
902
+ handler = self._result_handlers.get(output.request_id)
903
+ if output.is_finished():
904
+ self._result_handlers.pop(output.request_id, None)
905
+ if handler is None:
906
+ self._output_queue.put(output)
907
+ else:
908
+ callback, loop = handler
909
+ try:
910
+ loop.call_soon_threadsafe(callback, output)
911
+ except RuntimeError as error:
912
+ # A callback owner may close its event loop while a terminal
913
+ # event is in flight. Preserve the result for pull consumers
914
+ # instead of turning that client race into a worker fatality.
915
+ warnings.warn(
916
+ f"Result callback loop closed for {output.request_id}: {error!r}",
917
+ stacklevel=2,
918
+ )
919
+ self._output_queue.put(output)
920
+
921
+ def _output_for(
922
+ self,
923
+ state: ModilifyMk2RequestState,
924
+ *,
925
+ stream_update: bool = False,
926
+ delta_tokens: Sequence[int] | None = None,
927
+ ) -> ModilifyMk2ContinuousGenerationOutput:
928
+ now = time.perf_counter()
929
+ finished = state.status in {RequestStatus.FINISHED, RequestStatus.FAILED}
930
+ end = state.finished_time if finished else -1.0
931
+ shifts = max(1, state.shifts)
932
+ steps = max(1, state.denoise_steps)
933
+ return ModilifyMk2ContinuousGenerationOutput(
934
+ request_id=state.request_id,
935
+ prompt_ids=list(state.prompt_ids),
936
+ generated_tokens=list(state.generated_tokens),
937
+ logprobs=list(state.logprobs),
938
+ error=state.error,
939
+ status=state.status,
940
+ created_time=state.created_time,
941
+ lifespan=(state.started_time, end),
942
+ timestamps=(list(state.timestamps) if state.record_timestamps else None),
943
+ stop_reason=state.stop_reason,
944
+ committed_tokens=len(state.generated_tokens),
945
+ denoise_steps=state.denoise_steps,
946
+ no_progress_steps=(
947
+ 0
948
+ if state.rolling_state is None
949
+ else int(state.rolling_state.latent_state.stagnation_steps[0])
950
+ ),
951
+ jump_count=state.jumps,
952
+ forced_jump_bad_count=state.forced_jump_tokens,
953
+ heavy_forward_count=state.denoise_steps,
954
+ latent_context_update_count=state.denoise_steps,
955
+ average_commit_len=len(state.generated_tokens) / shifts,
956
+ tokens_per_forward=len(state.generated_tokens) / steps,
957
+ seed=state.seed,
958
+ scheduler_run_id=self.run_id,
959
+ queue_seconds=max(0.0, state.started_time - state.created_time),
960
+ inference_seconds=(
961
+ max(0.0, (end if finished else now) - state.started_time)
962
+ if state.started_time >= 0
963
+ else 0.0
964
+ ),
965
+ total_seconds=max(0.0, (end if finished else now) - state.created_time),
966
+ last_step_batch_size=state.last_step_batch_size,
967
+ is_stream_update=stream_update,
968
+ delta_tokens=list(
969
+ state.last_delta_tokens if delta_tokens is None else delta_tokens
970
+ ),
971
+ state_shift_count=state.shifts,
972
+ latent_memory_norm=(
973
+ 0.0
974
+ if state.rolling_state is None
975
+ else float(
976
+ state.rolling_state.latent_state.memory_slots.float()
977
+ .norm(dim=-1)
978
+ .mean()
979
+ )
980
+ ),
981
+ state_retention_score=1.0 if state.shifts else 0.0,
982
+ )
983
+
984
+ def _finish(
985
+ self,
986
+ state: ModilifyMk2RequestState,
987
+ reason: str,
988
+ error: BaseException | str | None = None,
989
+ ) -> None:
990
+ if state.terminal_emitted:
991
+ return
992
+ if reason not in _TERMINAL_REASONS:
993
+ raise ValueError(f"Unknown continuous stop reason: {reason}")
994
+ state.stop_reason = reason
995
+ state.error = None if error is None else (str(error) if isinstance(error, str) else repr(error))
996
+ state.status = (
997
+ RequestStatus.FAILED
998
+ if reason in {"cancelled", "error"} or error is not None
999
+ else RequestStatus.FINISHED
1000
+ )
1001
+ state.finished_time = time.perf_counter()
1002
+ state.terminal_emitted = True
1003
+ self._stats["completed"] += 1
1004
+ if reason == "cancelled":
1005
+ self._stats["cancelled"] += 1
1006
+ elif error is not None:
1007
+ self._stats["failed"] += 1
1008
+ self._deliver(self._output_for(state))
1009
+
1010
+ def _fail_all_requests(self, error: BaseException) -> None:
1011
+ """Convert an unexpected worker failure into one terminal result per request."""
1012
+
1013
+ self.fatal_error = error
1014
+ with self._condition:
1015
+ pending = list(self._pending)
1016
+ active = list(self._active.values())
1017
+ self._pending.clear()
1018
+ self._active.clear()
1019
+ self._active_reserved_blocks = 0
1020
+ self._input_closed = True
1021
+ for state in [*active, *pending]:
1022
+ self._finish(state, "error", error)
1023
+ self._condition.notify_all()
1024
+
1025
+ def _request_fits(self, state: ModilifyMk2RequestState) -> bool:
1026
+ per_request_limit = self.continuous_batching_config.max_blocks_per_request
1027
+ if per_request_limit not in (None, 0) and state.reserved_blocks > per_request_limit:
1028
+ return False
1029
+ if self.block_capacity is None:
1030
+ return True
1031
+ reservations = [
1032
+ *(active.reserved_blocks for active in self._active.values()),
1033
+ state.reserved_blocks,
1034
+ ]
1035
+ return self._block_footprint(reservations) <= self.block_capacity
1036
+
1037
+ def _request_can_ever_fit(self, state: ModilifyMk2RequestState) -> bool:
1038
+ per_request_limit = self.continuous_batching_config.max_blocks_per_request
1039
+ if per_request_limit not in (None, 0) and state.reserved_blocks > per_request_limit:
1040
+ return False
1041
+ return (
1042
+ self.block_capacity is None
1043
+ or self._block_footprint([state.reserved_blocks]) <= self.block_capacity
1044
+ )
1045
+
1046
+ def _initialize_request(self, state: ModilifyMk2RequestState) -> None:
1047
+ generator = torch.Generator(device=self.device)
1048
+ generator.manual_seed(state.seed)
1049
+ state.generator = generator
1050
+ canvas = self.sampler.initialize_canvas(
1051
+ 1, self.device, generators=[generator]
1052
+ )
1053
+ canvas[:, state.max_new_tokens:] = self.pad_token_id
1054
+ dtype = self.model.model.decoder.embed_tokens.weight.dtype
1055
+ canvas_length = int(self.model.config.canvas_length)
1056
+ latent = LatentDeliberationState.empty(
1057
+ batch_size=1,
1058
+ canvas_length=canvas_length,
1059
+ latent_dim=self.model.config.latent_dim,
1060
+ memory_slots=self.model.config.latent_memory_slots,
1061
+ device=self.device,
1062
+ dtype=dtype,
1063
+ enable_gdn2=True,
1064
+ )
1065
+ state.rolling_state = ModilifyMk2RollingState(
1066
+ canvas=canvas,
1067
+ confidence=torch.zeros(1, canvas_length, device=self.device, dtype=torch.float32),
1068
+ entropy=torch.full(
1069
+ (1, canvas_length),
1070
+ math.log(self.model.config.text_config.vocab_size),
1071
+ device=self.device,
1072
+ dtype=torch.float32,
1073
+ ),
1074
+ age=torch.zeros(1, canvas_length, device=self.device, dtype=torch.int32),
1075
+ latent_state=latent,
1076
+
1077
+
1078
+ head=torch.zeros(1, device=self.device, dtype=torch.long),
1079
+ )
1080
+ state.cache = self.cache_pool.prefill(state.prompt_ids)
1081
+ state.logical_length = len(state.prompt_ids)
1082
+ if self.repetition_penalty != 1.0:
1083
+ state.repetition_history = torch.zeros(
1084
+ self.model.config.text_config.vocab_size,
1085
+ device=self.device,
1086
+ dtype=torch.bool,
1087
+ )
1088
+ prompt = torch.tensor([state.prompt_ids], device=self.device, dtype=torch.long)
1089
+ _add_repetition_history(
1090
+ state.repetition_history.unsqueeze(0),
1091
+ prompt,
1092
+ torch.ones_like(prompt, dtype=torch.bool),
1093
+ self.excluded_repetition_token_ids,
1094
+ )
1095
+ state.max_iterations = deterministic_episode_iteration_bound(
1096
+ torch.tensor([state.max_new_tokens]),
1097
+ max_ponder_steps=self.generation_config.max_ponder_steps,
1098
+ )
1099
+ state.started_time = time.perf_counter()
1100
+ state.status = RequestStatus.DECODING
1101
+
1102
+ def _apply_cancellations(self) -> None:
1103
+ with self._condition:
1104
+ cancelled = set(self._cancelled)
1105
+ self._cancelled.clear()
1106
+ if not cancelled:
1107
+ return
1108
+ retained = deque()
1109
+ while self._pending:
1110
+ state = self._pending.popleft()
1111
+ if state.request_id in cancelled:
1112
+ self._finish(state, "cancelled", "request cancelled")
1113
+ else:
1114
+ retained.append(state)
1115
+ self._pending = retained
1116
+ for request_id in cancelled:
1117
+ state = self._active.pop(request_id, None)
1118
+ if state is not None:
1119
+ self._active_reserved_blocks -= state.reserved_blocks
1120
+ self._finish(state, "cancelled", "request cancelled")
1121
+ self._condition.notify_all()
1122
+
1123
+ def _admit_requests(self) -> None:
1124
+ while True:
1125
+ with self._condition:
1126
+ if len(self._active) >= self.max_requests_per_batch or not self._pending:
1127
+ return
1128
+ state = self._pending[0]
1129
+ if not self._request_can_ever_fit(state):
1130
+ self._pending.popleft()
1131
+ self._finish(
1132
+ state,
1133
+ "error",
1134
+ "request exceeds continuous cache block limits",
1135
+ )
1136
+ self._condition.notify_all()
1137
+ continue
1138
+ if not self._request_fits(state):
1139
+ return
1140
+ self._pending.popleft()
1141
+ self._condition.notify_all()
1142
+ try:
1143
+ self._initialize_request(state)
1144
+ except Exception as error:
1145
+ self._finish(state, "error", error)
1146
+ continue
1147
+ with self._condition:
1148
+ if state.request_id in self._cancelled:
1149
+ self._cancelled.remove(state.request_id)
1150
+ self._finish(state, "cancelled", "request cancelled")
1151
+ continue
1152
+ self._active[state.request_id] = state
1153
+ self._active_reserved_blocks += state.reserved_blocks
1154
+ self._stats["admitted"] += 1
1155
+ self._stats["peak_reserved_blocks"] = max(
1156
+ self._stats["peak_reserved_blocks"],
1157
+ self._active_reserved_blocks,
1158
+ )
1159
+ self._stats["peak_cache_blocks"] = max(
1160
+ self._stats["peak_cache_blocks"],
1161
+ self._current_block_footprint(),
1162
+ )
1163
+ self._condition.notify_all()
1164
+
1165
+ def _select_rowwise_policy(
1166
+ self,
1167
+ states: Sequence[ModilifyMk2RequestState],
1168
+ proposal: torch.LongTensor,
1169
+ normal_failure_rate: torch.Tensor,
1170
+ previous_failure_rate: torch.Tensor,
1171
+ greedy_proposal: torch.LongTensor,
1172
+ jump_failure_rate: torch.Tensor,
1173
+ rolling: ModilifyMk2RollingState,
1174
+ ):
1175
+ decisions = []
1176
+ for row, state in enumerate(states):
1177
+ remaining = state.max_new_tokens - len(state.generated_tokens)
1178
+ decisions.append(
1179
+ select_commit_lengths(
1180
+ sampled_token_ids=proposal[row : row + 1],
1181
+ normal_failure_rate=normal_failure_rate[row : row + 1],
1182
+ previous_failure_rate=previous_failure_rate[row : row + 1],
1183
+ greedy_token_ids=greedy_proposal[row : row + 1],
1184
+ jump_failure_rate=jump_failure_rate[row : row + 1],
1185
+ ponder_steps=rolling.latent_state.ponder_steps[row : row + 1],
1186
+ stagnation_steps=rolling.latent_state.stagnation_steps[row : row + 1],
1187
+ active_rows=torch.ones(1, device=self.device, dtype=torch.bool),
1188
+ remaining_lengths=torch.tensor([remaining], device=self.device),
1189
+ failure_budget=self.model.config.commit_failure_budget,
1190
+ stop_token_id=state.eos_token_ids,
1191
+ max_ponder_steps=self.generation_config.max_ponder_steps,
1192
+ stagnation_threshold=self.generation_config.jump_on_no_progress_after,
1193
+ min_progress=self.generation_config.min_trajectory_progress,
1194
+ )
1195
+ )
1196
+ return (
1197
+ torch.cat([decision.normal_lengths for decision in decisions]),
1198
+ torch.cat([decision.commit_lengths for decision in decisions]),
1199
+ torch.cat([decision.commit_token_ids for decision in decisions]),
1200
+ torch.cat([decision.jump_rows for decision in decisions]),
1201
+ torch.cat([decision.ponder_steps for decision in decisions]),
1202
+ torch.cat([decision.stagnation_steps for decision in decisions]),
1203
+ )
1204
+
1205
+ @torch.inference_mode()
1206
+ def _run_batch_step(self, states: Sequence[ModilifyMk2RequestState]) -> list[str]:
1207
+ if len(states) > 1:
1208
+ # The BF16 trunk changes reductions with batch shape. Routed
1209
+ # experts amplify those differences into different commits, so
1210
+ # run each active row with its independent-request numerics.
1211
+ finished = []
1212
+ for state in states:
1213
+ finished.extend(self._run_batch_step([state]))
1214
+ return finished
1215
+ started = time.perf_counter()
1216
+ rolling_states = [state.rolling_state for state in states]
1217
+ if any(state is None for state in rolling_states):
1218
+ raise RuntimeError("Active request has no rolling state.")
1219
+ rolling = _pack_rolling_states(rolling_states) # type: ignore[arg-type]
1220
+ packed_cache, cache_mask, logical_lengths = self.cache_pool.pack(states)
1221
+ batch_size = len(states)
1222
+ canvas_length = int(self.model.config.canvas_length)
1223
+ decoder_positions = (
1224
+ logical_lengths[:, None]
1225
+ + torch.arange(canvas_length, device=self.device)[None, :]
1226
+ ).to(torch.int32)
1227
+ decoder_mask = torch.cat(
1228
+ (
1229
+ cache_mask,
1230
+ torch.ones(
1231
+ batch_size,
1232
+ canvas_length,
1233
+ device=self.device,
1234
+ dtype=torch.bool,
1235
+ ),
1236
+ ),
1237
+ dim=-1,
1238
+ )
1239
+ repetition_history = None
1240
+ if self.repetition_penalty != 1.0:
1241
+ repetition_history = torch.stack(
1242
+ [state.repetition_history for state in states], dim=0 # type: ignore[list-item]
1243
+ )
1244
+ generators = [state.generator for state in states]
1245
+ if any(generator is None for generator in generators):
1246
+ raise RuntimeError("Active request has no sampling generator.")
1247
+
1248
+ output = self.model(
1249
+ input_ids=None,
1250
+ past_key_values=packed_cache,
1251
+ decoder_input_ids=rolling.canvas,
1252
+ previous_confidence=rolling.confidence,
1253
+ previous_entropy=rolling.entropy,
1254
+ token_age=rolling.age,
1255
+ latent_state=rolling.latent_state,
1256
+
1257
+
1258
+ decoder_position_ids=decoder_positions,
1259
+ decoder_read_cache=True,
1260
+ decoder_attention_mask=decoder_mask,
1261
+ compact_vocab=True,
1262
+ repetition_token_mask=repetition_history,
1263
+ repetition_penalty=self.repetition_penalty,
1264
+ sampling_generators=generators,
1265
+ )
1266
+ required = (
1267
+ output.proposal,
1268
+ output.proposal_confidence,
1269
+ output.token_entropy,
1270
+ output.greedy_proposal,
1271
+ output.greedy_confidence,
1272
+ output.next_latent_state,
1273
+ )
1274
+ if any(value is None for value in required):
1275
+ raise RuntimeError("Compact ModilifyMk2 forward did not return proposal state.")
1276
+ proposal = output.proposal
1277
+ proposal_confidence = output.proposal_confidence
1278
+ token_entropy = output.token_entropy
1279
+ greedy_proposal = output.greedy_proposal
1280
+ greedy_confidence = output.greedy_confidence
1281
+ next_canvas = proposal.clone()
1282
+ next_confidence = proposal_confidence.float()
1283
+ next_latent = replace(
1284
+ output.next_latent_state,
1285
+ confidence=next_confidence.detach().float(),
1286
+ entropy=token_entropy.detach().float(),
1287
+ age=rolling.age + 1,
1288
+ token_changed=next_canvas.ne(rolling.canvas).detach().float(),
1289
+ confidence_delta=next_confidence.detach().float() - rolling.confidence,
1290
+ entropy_delta=token_entropy.detach().float() - rolling.entropy,
1291
+ )
1292
+ remaining_lengths = torch.tensor(
1293
+ [state.max_new_tokens - len(state.generated_tokens) for state in states],
1294
+ device=self.device,
1295
+ dtype=torch.long,
1296
+ )
1297
+ live_mask = torch.arange(canvas_length, device=self.device)[None, :].lt(
1298
+ remaining_lengths[:, None]
1299
+ )
1300
+ next_latent = self.model.latent_deliberation.observe_state(
1301
+ next_latent, output.heavy_hidden_state, output.working_state,
1302
+ live_mask, rolling.head,
1303
+ )
1304
+ next_state = ModilifyMk2RollingState(
1305
+ canvas=next_canvas,
1306
+ confidence=next_confidence,
1307
+ entropy=token_entropy,
1308
+ age=rolling.age + 1,
1309
+ latent_state=next_latent,
1310
+
1311
+
1312
+ head=rolling.head,
1313
+ )
1314
+ normal_failure_rate = fused_commit_failure_rate(
1315
+ proposal_confidence,
1316
+ token_entropy,
1317
+ entropy_weight=self.model.config.commit_entropy_weight,
1318
+ confidence_power=self.model.config.commit_confidence_power,
1319
+ top_k=getattr(self.model.config, "commit_top_k", None),
1320
+ min_p=getattr(self.model.config, "commit_min_p", None),
1321
+ target_confidence=getattr(self.model.config, "commit_target_confidence", None),
1322
+ failure_budget=float(self.model.config.commit_failure_budget),
1323
+ vocab_size=self.model.config.text_config.vocab_size,
1324
+ )
1325
+ jump_failure_rate = fused_commit_failure_rate(
1326
+ greedy_confidence,
1327
+ token_entropy,
1328
+ entropy_weight=self.model.config.commit_entropy_weight,
1329
+ confidence_power=self.model.config.commit_confidence_power,
1330
+ top_k=getattr(self.model.config, "commit_top_k", None),
1331
+ min_p=getattr(self.model.config, "commit_min_p", None),
1332
+ target_confidence=getattr(self.model.config, "commit_target_confidence", None),
1333
+ failure_budget=float(self.model.config.commit_failure_budget),
1334
+ vocab_size=self.model.config.text_config.vocab_size,
1335
+ )
1336
+ previous_failure_rate = fused_commit_failure_rate(
1337
+ rolling.confidence,
1338
+ rolling.entropy,
1339
+ entropy_weight=self.model.config.commit_entropy_weight,
1340
+ confidence_power=self.model.config.commit_confidence_power,
1341
+ top_k=getattr(self.model.config, "commit_top_k", None),
1342
+ min_p=getattr(self.model.config, "commit_min_p", None),
1343
+ target_confidence=getattr(self.model.config, "commit_target_confidence", None),
1344
+ failure_budget=float(self.model.config.commit_failure_budget),
1345
+ vocab_size=self.model.config.text_config.vocab_size,
1346
+ )
1347
+ (
1348
+ normal_commit,
1349
+ commit_lengths,
1350
+ commit_token_ids,
1351
+ jump_rows,
1352
+ next_ponder,
1353
+ next_stagnation,
1354
+ ) = self._select_rowwise_policy(
1355
+ states,
1356
+ proposal,
1357
+ normal_failure_rate,
1358
+ previous_failure_rate,
1359
+ greedy_proposal,
1360
+ jump_failure_rate,
1361
+ rolling,
1362
+ )
1363
+ positions = torch.arange(canvas_length, device=self.device)[None, :]
1364
+ commit_positions = positions.lt(commit_lengths[:, None])
1365
+ policy_prefix_mask = positions.lt(normal_commit[:, None])
1366
+ if bool(jump_rows.any()):
1367
+ next_state = replace(
1368
+ next_state,
1369
+ canvas=torch.where(
1370
+ commit_positions & jump_rows[:, None],
1371
+ commit_token_ids,
1372
+ next_state.canvas,
1373
+ ),
1374
+ )
1375
+ next_state = replace(
1376
+ next_state,
1377
+ latent_state=replace(
1378
+ next_state.latent_state,
1379
+ ponder_steps=next_ponder,
1380
+ stagnation_steps=next_stagnation,
1381
+ ),
1382
+ )
1383
+ unshifted_trace_states = [
1384
+ _slice_rolling_state(next_state, row) for row in range(batch_size)
1385
+ ]
1386
+ if output.working_state is None:
1387
+ raise RuntimeError("Forward did not return working trajectory features.")
1388
+ next_state = self.model._write_committed_memory(
1389
+
1390
+ next_state=next_state,
1391
+ working_state=output.working_state,
1392
+
1393
+ heavy_hidden=output.heavy_hidden_state,
1394
+ commit_token_ids=commit_token_ids,
1395
+ commit_lengths=commit_lengths,
1396
+ prefix_lengths=logical_lengths,
1397
+ commit_reason=infer_commit_reason(
1398
+ commit_lengths,
1399
+ jump_rows=jump_rows,
1400
+ commit_token_ids=commit_token_ids,
1401
+ terminal_token_ids=getattr(
1402
+ self.model.config, "terminal_token_ids", ()
1403
+ ),
1404
+ ),
1405
+ )
1406
+ shifted = self.model._shift_state_rows(
1407
+ next_state,
1408
+ commit_lengths,
1409
+ self.sampler,
1410
+ generators=generators,
1411
+ remaining_lengths=remaining_lengths - commit_lengths,
1412
+ pad_token_id=self.pad_token_id,
1413
+ )
1414
+ shifted_states = [
1415
+ _slice_rolling_state(shifted, row) for row in range(batch_size)
1416
+ ]
1417
+ selected_confidence = torch.where(
1418
+ jump_rows[:, None], greedy_confidence, proposal_confidence
1419
+ ).float()
1420
+
1421
+ finished_ids = []
1422
+ for row, state in enumerate(states):
1423
+ state.last_step_batch_size = batch_size
1424
+ state.denoise_steps += 1
1425
+ commit_length = int(commit_lengths[row])
1426
+ chunk = commit_token_ids[row, :commit_length].detach().cpu().tolist()
1427
+ state.last_delta_tokens = [int(token_id) for token_id in chunk]
1428
+ before = len(state.generated_tokens)
1429
+ try:
1430
+ self.cache_pool.append(state, chunk)
1431
+ except Exception as error:
1432
+ self._finish(state, "error", error)
1433
+ finished_ids.append(state.request_id)
1434
+ continue
1435
+ state.logical_length += commit_length
1436
+ state.generated_tokens.extend(int(token_id) for token_id in chunk)
1437
+ if self.continuous_batching_config.return_logprobs and commit_length:
1438
+ probabilities = selected_confidence[row, :commit_length].clamp_min(
1439
+ torch.finfo(torch.float32).tiny
1440
+ )
1441
+ state.logprobs.extend(probabilities.log().detach().cpu().tolist())
1442
+ if state.record_timestamps and commit_length:
1443
+ state.timestamps.extend([time.perf_counter()] * commit_length)
1444
+ if state.repetition_history is not None and commit_length:
1445
+ tokens = commit_token_ids[row : row + 1]
1446
+ eligible = commit_positions[row : row + 1]
1447
+ _add_repetition_history(
1448
+ state.repetition_history.unsqueeze(0),
1449
+ tokens,
1450
+ eligible,
1451
+ self.excluded_repetition_token_ids,
1452
+ )
1453
+ state.jumps += int(jump_rows[row])
1454
+ if bool(jump_rows[row]):
1455
+ state.forced_jump_tokens += commit_length
1456
+ if commit_length:
1457
+ state.shifts += 1
1458
+ state.rolling_state = shifted_states[row]
1459
+
1460
+ reason = None
1461
+ if self.turn_end_token_id in chunk:
1462
+ reason = "turn_end"
1463
+ elif any(token_id in state.eos_token_ids for token_id in chunk):
1464
+ reason = "eos"
1465
+ elif len(state.generated_tokens) >= state.max_new_tokens:
1466
+ reason = "max_new_tokens"
1467
+ elif (
1468
+ state.max_denoising_steps is not None
1469
+ and state.denoise_steps >= state.max_denoising_steps
1470
+ ):
1471
+ reason = "max_denoising_steps"
1472
+ elif state.denoise_steps >= state.max_iterations:
1473
+ reason = "episode_watchdog"
1474
+
1475
+ elapsed = time.perf_counter() - started
1476
+ if state.trace_callback is not None:
1477
+ trace = build_denoise_trace_event(
1478
+ denoise_step=state.denoise_steps,
1479
+ prefix_length=state.logical_length - commit_length,
1480
+ committed_before=before,
1481
+ committed_after=len(state.generated_tokens),
1482
+ no_progress_steps=int(next_stagnation[row]),
1483
+ policy_prefix_mask=policy_prefix_mask[row : row + 1],
1484
+ commit_length=commit_length,
1485
+ ponder_fallback=bool(jump_rows[row]),
1486
+ state=unshifted_trace_states[row],
1487
+ proposal=proposal[row : row + 1],
1488
+ committed_token_ids=commit_token_ids[row : row + 1, :commit_length],
1489
+ step_elapsed_seconds=elapsed,
1490
+ latent_residual_diagnostics=None,
1491
+ )
1492
+ trace["request_id"] = state.request_id
1493
+ trace["batch_size"] = batch_size
1494
+ try:
1495
+ state.trace_callback(trace)
1496
+ except Exception as error:
1497
+ warnings.warn(
1498
+ f"Denoise trace callback failed for {state.request_id}: {error!r}",
1499
+ stacklevel=2,
1500
+ )
1501
+
1502
+ if reason is not None:
1503
+ self._finish(state, reason)
1504
+ finished_ids.append(state.request_id)
1505
+ elif state.streaming and commit_length:
1506
+ self._deliver(
1507
+ self._output_for(
1508
+ state,
1509
+ stream_update=True,
1510
+ delta_tokens=state.last_delta_tokens,
1511
+ )
1512
+ )
1513
+
1514
+ self._stats["model_steps"] += 1
1515
+ self._stats["generated_tokens"] += int(commit_lengths.sum())
1516
+ self._stats["max_observed_batch_size"] = max(
1517
+ self._stats["max_observed_batch_size"], batch_size
1518
+ )
1519
+ self._stats["active_slot_steps"] += batch_size
1520
+ self._stats["slot_capacity_steps"] += self.max_requests_per_batch
1521
+ return finished_ids
1522
+
1523
+ def _run_step_with_isolation(self, states: Sequence[ModilifyMk2RequestState]) -> None:
1524
+ generator_states = {
1525
+ state.request_id: state.generator.get_state()
1526
+ for state in states
1527
+ if state.generator is not None
1528
+ }
1529
+ try:
1530
+ finished_ids = self._run_batch_step(states)
1531
+ except Exception as batch_error:
1532
+ for state in states:
1533
+ if state.generator is not None:
1534
+ state.generator.set_state(generator_states[state.request_id])
1535
+ if len(states) == 1:
1536
+ self._finish(states[0], "error", batch_error)
1537
+ finished_ids = [states[0].request_id]
1538
+ else:
1539
+ finished_ids = []
1540
+ for state in states:
1541
+ if state.terminal_emitted:
1542
+ finished_ids.append(state.request_id)
1543
+ continue
1544
+ try:
1545
+ finished_ids.extend(self._run_batch_step([state]))
1546
+ except Exception as request_error:
1547
+ self._finish(state, "error", request_error)
1548
+ finished_ids.append(state.request_id)
1549
+ with self._condition:
1550
+ for request_id in dict.fromkeys(finished_ids):
1551
+ state = self._active.pop(request_id, None)
1552
+ if state is not None:
1553
+ self._active_reserved_blocks -= state.reserved_blocks
1554
+ self._condition.notify_all()
1555
+
1556
+ @torch.inference_mode()
1557
+ def _run_generation_loop(self) -> None:
1558
+ try:
1559
+ while True:
1560
+ self._apply_cancellations()
1561
+ if self._hard_stop:
1562
+ self._apply_cancellations()
1563
+ with self._condition:
1564
+ has_active = bool(self._active)
1565
+ # ``prefill_first`` fills every available slot before the next
1566
+ # denoise step. FIFO lets the already-active cohort take its
1567
+ # next step first, then fills slots released by that step.
1568
+ if (
1569
+ self.continuous_batching_config.scheduler_type == "prefill_first"
1570
+ or not has_active
1571
+ ):
1572
+ self._admit_requests()
1573
+ with self._condition:
1574
+ active = list(self._active.values())
1575
+ should_finish = (
1576
+ self._input_closed and not self._pending and not active
1577
+ )
1578
+ if should_finish:
1579
+ return
1580
+ if not active:
1581
+ self._condition.wait(timeout=0.05)
1582
+ continue
1583
+ self._run_step_with_isolation(active)
1584
+ if self.continuous_batching_config.scheduler_type == "fifo":
1585
+ self._admit_requests()
1586
+ except BaseException as error:
1587
+ self._fail_all_requests(error)
1588
+ finally:
1589
+ self._finished.set()
1590
+ with self._condition:
1591
+ self._condition.notify_all()
1592
+
1593
+
1594
+ @torch.inference_mode()
1595
+ def generate_static_batch_with_logical_cache(
1596
+ model: Any,
1597
+ input_ids: torch.LongTensor,
1598
+ attention_mask: torch.BoolTensor | None,
1599
+ generation_config: ModilifyMk2GenerationConfig,
1600
+ *,
1601
+ seeds: Sequence[int] | None = None,
1602
+ max_new_tokens: Sequence[int] | None = None,
1603
+ ) -> ModilifyMk2GenerationOutput:
1604
+ """Run one fixed cohort through the same hole-free continuous engine."""
1605
+
1606
+ batch_size, input_width = input_ids.shape
1607
+ if attention_mask is None:
1608
+ attention_mask = torch.ones_like(input_ids, dtype=torch.bool)
1609
+ else:
1610
+ attention_mask = attention_mask.to(device=input_ids.device, dtype=torch.bool)
1611
+ if attention_mask.shape != input_ids.shape:
1612
+ raise ValueError("`attention_mask` must have the same shape as `input_ids`.")
1613
+ prompts = [
1614
+ input_ids[row, attention_mask[row]].detach().cpu().tolist()
1615
+ for row in range(batch_size)
1616
+ ]
1617
+ if any(not prompt for prompt in prompts):
1618
+ raise ValueError("Every batched ModilifyMk2 prompt must contain at least one token.")
1619
+ if seeds is not None and len(seeds) != batch_size:
1620
+ raise ValueError("`seeds` must contain one seed per batch row.")
1621
+ if max_new_tokens is None:
1622
+ max_new_tokens = [int(generation_config.max_new_tokens)] * batch_size
1623
+ if len(max_new_tokens) != batch_size or any(
1624
+ not isinstance(limit, int) or isinstance(limit, bool) or limit <= 0
1625
+ for limit in max_new_tokens
1626
+ ):
1627
+ raise ValueError("`max_new_tokens` must contain one positive limit per batch row.")
1628
+
1629
+ batching_config = ContinuousBatchingConfig(
1630
+ block_size=max(4, int(getattr(model.config, "kv_cache_bucket_size", 128))),
1631
+ max_batch_tokens=batch_size * int(model.config.canvas_length),
1632
+ max_requests_per_batch=batch_size,
1633
+ allow_block_sharing=False,
1634
+ scheduler_type="prefill_first",
1635
+ )
1636
+ manager = ModilifyMk2ContinuousBatchingManager(
1637
+ model=model,
1638
+ generation_config=generation_config,
1639
+ continuous_batching_config=batching_config,
1640
+ )
1641
+ try:
1642
+ request_ids = []
1643
+ for row, prompt in enumerate(prompts):
1644
+ request_kwargs = {}
1645
+ if seeds is not None:
1646
+ request_kwargs["seed"] = int(seeds[row])
1647
+ request_ids.append(
1648
+ manager.add_request(
1649
+ prompt,
1650
+ request_id=f"static_{row}",
1651
+ max_new_tokens=int(max_new_tokens[row]),
1652
+ streaming=False,
1653
+ max_denoising_steps=generation_config.max_denoising_steps,
1654
+ eos_token_id=generation_config.eos_token_id,
1655
+ **request_kwargs,
1656
+ )
1657
+ )
1658
+ manager.close_input()
1659
+ # A static batch is one fixed cohort: queue every row before the worker
1660
+ # starts so its first heavy forward necessarily contains the full batch.
1661
+ manager.start()
1662
+ final = {}
1663
+ for output in manager:
1664
+ if output.is_finished():
1665
+ final[output.request_id] = output
1666
+ ordered = [final[request_id] for request_id in request_ids]
1667
+ finally:
1668
+ manager.stop(block=True, hard_stop=True)
1669
+ manager.destroy()
1670
+ failures = [output for output in ordered if output.error is not None]
1671
+ if failures:
1672
+ details = "; ".join(
1673
+ f"{output.request_id}: {output.error}" for output in failures
1674
+ )
1675
+ raise RuntimeError(f"Static ModilifyMk2 batch generation failed: {details}")
1676
+
1677
+ lengths = torch.tensor(
1678
+ [len(output.generated_tokens) for output in ordered],
1679
+ device=input_ids.device,
1680
+ dtype=torch.long,
1681
+ )
1682
+ output_width = int(lengths.max()) if lengths.numel() else 0
1683
+ pad_token_id = generation_config.pad_token_id
1684
+ if isinstance(pad_token_id, (list, tuple)):
1685
+ pad_token_id = pad_token_id[0]
1686
+ pad_token_id = int(0 if pad_token_id is None else pad_token_id)
1687
+ generated = torch.full(
1688
+ (batch_size, output_width),
1689
+ pad_token_id,
1690
+ device=input_ids.device,
1691
+ dtype=input_ids.dtype,
1692
+ )
1693
+ for row, output in enumerate(ordered):
1694
+ if output.generated_tokens:
1695
+ generated[row, : len(output.generated_tokens)] = torch.tensor(
1696
+ output.generated_tokens,
1697
+ device=input_ids.device,
1698
+ dtype=input_ids.dtype,
1699
+ )
1700
+
1701
+ def tensor(name: str, *, dtype: torch.dtype) -> torch.Tensor:
1702
+ return torch.tensor(
1703
+ [getattr(output, name) for output in ordered],
1704
+ device=input_ids.device,
1705
+ dtype=dtype,
1706
+ )
1707
+
1708
+ return ModilifyMk2GenerationOutput(
1709
+ sequences=torch.cat((input_ids, generated), dim=-1),
1710
+ generated_lengths=lengths,
1711
+ tokens_per_forward=tensor("tokens_per_forward", dtype=torch.float32),
1712
+ past_key_values=None,
1713
+ stop_reason=tuple(output.stop_reason for output in ordered),
1714
+ committed_tokens=lengths.clone(),
1715
+ denoise_steps=tensor("denoise_steps", dtype=torch.long),
1716
+ no_progress_steps=tensor("no_progress_steps", dtype=torch.long),
1717
+ jump_count=tensor("jump_count", dtype=torch.long),
1718
+ forced_jump_bad_count=tensor("forced_jump_bad_count", dtype=torch.long),
1719
+ heavy_forward_count=tensor("heavy_forward_count", dtype=torch.long),
1720
+ latent_context_update_count=tensor(
1721
+ "latent_context_update_count", dtype=torch.long
1722
+ ),
1723
+ average_commit_len=tensor("average_commit_len", dtype=torch.float32),
1724
+ state_shift_count=tensor("state_shift_count", dtype=torch.long),
1725
+ latent_memory_norm=tensor("latent_memory_norm", dtype=torch.float32),
1726
+ state_retention_score=tensor("state_retention_score", dtype=torch.float32),
1727
+ )
1728
+
1729
+
1730
+ __all__ = [
1731
+ "ModilifyMk2ContinuousBatchingManager",
1732
+ "ModilifyMk2ContinuousGenerationOutput",
1733
+ "ModilifyMk2LogicalCachePool",
1734
+ "ModilifyMk2RequestState",
1735
+ "continuous_config_fingerprint",
1736
+ "generate_static_batch_with_logical_cache",
1737
+ ]
export_manifest.json ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "modilify_mk2_transformers_inference_v1",
3
+ "global_step": 4400,
4
+ "state_schema_version": 25,
5
+ "training_scheme": "gold_prefix_shared_commit_0_256_committed_ce_calibration_causal_throughput_terminal_sft",
6
+ "memory_scheme": "dual_timescale_gdn2_trajectory_memory",
7
+ "memory_architecture": "compact_gdn2_v2",
8
+ "base_model": "google/diffusiongemma-26B-A4B-it",
9
+ "source_manifest_sha256": "e2b64fe2102ca1e002a62e279798abb3be271e3d9640a5afc5823cf233724dbc",
10
+ "precision_policy": "gdn2_small_fp32_v1",
11
+ "adapters": "unmerged",
12
+ "trainable_tensor_count": 632,
13
+ "total_tensor_count": 1323,
14
+ "shards": [
15
+ {
16
+ "file": "model-00001-of-00016.safetensors",
17
+ "bytes": 3898256004,
18
+ "sha256": "d3aa3fc0393a51acec27161569d566774030e0306a74748dd2a7a11fd8b535ec"
19
+ },
20
+ {
21
+ "file": "model-00002-of-00016.safetensors",
22
+ "bytes": 3798073636,
23
+ "sha256": "89339fd6f0521977fff9d4ae498e485ff409dbb8716cc924c8667c8bceaa19cd"
24
+ },
25
+ {
26
+ "file": "model-00003-of-00016.safetensors",
27
+ "bytes": 3290562732,
28
+ "sha256": "b9351e59c7c604b26712960b5d9f3460f5c1aace9ccb348999a2334d93fb613d"
29
+ },
30
+ {
31
+ "file": "model-00004-of-00016.safetensors",
32
+ "bytes": 3319472948,
33
+ "sha256": "1c89ba68a1f56e986dbf43175a7a7a6f2d046b04ac993c2837024f71b5d5923d"
34
+ },
35
+ {
36
+ "file": "model-00005-of-00016.safetensors",
37
+ "bytes": 3290562732,
38
+ "sha256": "4e42f45a5c409649c0ac8a262b26a0f644d10619d5f857568db855d12a865183"
39
+ },
40
+ {
41
+ "file": "model-00006-of-00016.safetensors",
42
+ "bytes": 3290562772,
43
+ "sha256": "da6e7d99f9801cb1b7007044f3baa66e767412d4350211a637174321eacee401"
44
+ },
45
+ {
46
+ "file": "model-00007-of-00016.safetensors",
47
+ "bytes": 3319473028,
48
+ "sha256": "ce1f462e324e162f9c0f41b1db3945290eb06c520da3d8ff0f582cde79a6fa96"
49
+ },
50
+ {
51
+ "file": "model-00008-of-00016.safetensors",
52
+ "bytes": 3290562812,
53
+ "sha256": "a163f6df13b9a3fbe1df187e42700b74d71fa08b331c521be568f71dec77a8f0"
54
+ },
55
+ {
56
+ "file": "model-00009-of-00016.safetensors",
57
+ "bytes": 3290562812,
58
+ "sha256": "eacc2e9ac62fa45f4fd1d391bfff490e327c19df3f70e7835e556287fc9926bd"
59
+ },
60
+ {
61
+ "file": "model-00010-of-00016.safetensors",
62
+ "bytes": 3319473028,
63
+ "sha256": "5fa9c8080a9c3954985226496dee261938f3d05cf81e4436932de593e5a01ae1"
64
+ },
65
+ {
66
+ "file": "model-00011-of-00016.safetensors",
67
+ "bytes": 3290562812,
68
+ "sha256": "7df1df1cea73133a94aec88056e2514e04a3467d42c0dd55ad0b922af2e5e986"
69
+ },
70
+ {
71
+ "file": "model-00012-of-00016.safetensors",
72
+ "bytes": 3290562812,
73
+ "sha256": "797078b4703d602cc3a323dba676fa38bb959b70d455bdc8c673663261eee2d4"
74
+ },
75
+ {
76
+ "file": "model-00013-of-00016.safetensors",
77
+ "bytes": 3319473028,
78
+ "sha256": "35923e8f76bc78ae89b3f83d1cdd175649f64cc898f1862baf543d7cdfbcd0e1"
79
+ },
80
+ {
81
+ "file": "model-00014-of-00016.safetensors",
82
+ "bytes": 3290562812,
83
+ "sha256": "105ceea4ed17c811fb79036c199db1f7d2392a5cc4c05003085f7217e8a938bb"
84
+ },
85
+ {
86
+ "file": "model-00015-of-00016.safetensors",
87
+ "bytes": 3290562812,
88
+ "sha256": "d3569ec55bc4f7e89de00efaedfbca3df44096526291731ec14bd7fc245c3b39"
89
+ },
90
+ {
91
+ "file": "model-00016-of-00016.safetensors",
92
+ "bytes": 1202380654,
93
+ "sha256": "23c9c00fc04b475c744458f498178a6e9a20fd673c36af981dd22e963b7ca49b"
94
+ }
95
+ ],
96
+ "validated_environment": {
97
+ "python": "3.12",
98
+ "torch": "2.14.0",
99
+ "transformers": "5.14.1",
100
+ "safetensors": "0.8.0",
101
+ "accelerate": "1.14.0"
102
+ }
103
+ }
gdn2_memory.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Reference GDN2 matrix memory for trajectory-time recurrence.
2
+
3
+ The state is FP32. Leading dimensions are independent rows or canvas cells;
4
+ only the last three dimensions, [heads, key, value], belong to the rule.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import torch
10
+ from torch import nn
11
+ from torch.nn import functional as F
12
+
13
+
14
+ class _GateProjection(nn.Module):
15
+ """GDN2 gates need channel control, not a second full-width content map."""
16
+
17
+ def __init__(self, source: int, target: int, rank: int) -> None:
18
+ super().__init__()
19
+ self.down = nn.Linear(source, rank, bias=False)
20
+ self.up = nn.Linear(rank, target, bias=False)
21
+
22
+ def forward(self, source: torch.Tensor) -> torch.Tensor:
23
+ return self.up(self.down(source))
24
+
25
+
26
+ class GDN2Memory(nn.Module):
27
+ def __init__(self, input_dim: int, heads: int, key_dim: int, value_dim: int,
28
+ *, observation_dim: int | None = None) -> None:
29
+ super().__init__()
30
+ if min(input_dim, heads, key_dim, value_dim) <= 0:
31
+ raise ValueError("GDN2 dimensions must be positive.")
32
+ self.heads, self.key_dim, self.value_dim = heads, key_dim, value_dim
33
+ observation_dim = input_dim if observation_dim is None else observation_dim
34
+ if observation_dim <= 0:
35
+ raise ValueError("GDN2 observation width must be positive.")
36
+ self.q_proj = nn.Linear(input_dim, heads * key_dim, bias=False)
37
+ self.k_proj = nn.Linear(observation_dim, heads * key_dim, bias=False)
38
+ self.v_proj = nn.Linear(observation_dim, heads * value_dim, bias=False)
39
+ self.f_proj = _GateProjection(observation_dim, heads * key_dim, min(observation_dim, key_dim))
40
+ self.b_proj = nn.Linear(observation_dim, heads * key_dim, bias=False)
41
+ self.w_proj = nn.Linear(observation_dim, heads * value_dim, bias=False)
42
+ self.g_proj = _GateProjection(input_dim, heads * value_dim, min(input_dim, value_dim))
43
+ self.o_proj = nn.Linear(heads * value_dim, input_dim, bias=False)
44
+ self.a_log = nn.Parameter(torch.zeros(heads))
45
+ self.dt_bias = nn.Parameter(torch.full((heads, key_dim), -6.906255))
46
+
47
+ def empty(self, *leading: int, device: torch.device | None = None) -> torch.Tensor:
48
+ return torch.zeros(*leading, self.heads, self.key_dim, self.value_dim,
49
+ device=device, dtype=torch.float32)
50
+
51
+ def _projections(self, source: torch.Tensor):
52
+ source = F.rms_norm(source.float(), (source.shape[-1],), eps=1e-6).to(source.dtype)
53
+ shape = source.shape[:-1]
54
+ key_shape = (*shape, self.heads, self.key_dim)
55
+ value_shape = (*shape, self.heads, self.value_dim)
56
+ k = F.normalize(F.silu(self.k_proj(source).float()).reshape(key_shape), dim=-1)
57
+ v = F.silu(self.v_proj(source).float()).reshape(value_shape)
58
+ head_rate = self.a_log.float().exp().reshape(*((1,) * (source.ndim - 1)), self.heads, 1)
59
+ decay = torch.exp(-head_rate * F.softplus(
60
+ self.f_proj(source).float().reshape(key_shape) + self.dt_bias.float()))
61
+ erase = torch.sigmoid(self.b_proj(source).float().reshape(key_shape))
62
+ write = torch.sigmoid(self.w_proj(source).float().reshape(value_shape))
63
+ return k, v, decay, erase, write
64
+
65
+ def _query(self, source: torch.Tensor) -> torch.Tensor:
66
+ return F.normalize(F.silu(self.q_proj(source).float()).reshape(
67
+ *source.shape[:-1], self.heads, self.key_dim), dim=-1)
68
+
69
+ def _output(self, value: torch.Tensor, source: torch.Tensor) -> torch.Tensor:
70
+ gate = F.silu(self.g_proj(source).float().reshape(value.shape))
71
+ value = F.rms_norm(value, (self.value_dim,), eps=1e-6) * gate
72
+ return self.o_proj(value.flatten(-2).to(source.dtype))
73
+
74
+ def read(self, state: torch.Tensor, source: torch.Tensor) -> torch.Tensor:
75
+ if state.shape != (*source.shape[:-1], self.heads, self.key_dim, self.value_dim):
76
+ raise ValueError("GDN2 state and query leading dimensions differ.")
77
+ value = torch.einsum("...hk,...hkv->...hv", self._query(source), state.float())
78
+ return self._output(value, source)
79
+
80
+ def read_shared(self, state: torch.Tensor, source: torch.Tensor) -> torch.Tensor:
81
+ """Read one row matrix for all queries without a canvas-sized matrix product."""
82
+ if source.ndim != 3 or state.shape != (source.shape[0], self.heads, self.key_dim, self.value_dim):
83
+ raise ValueError("Shared GDN2 read requires [batch, canvas, width] queries.")
84
+ query = self._query(source).transpose(1, 2)
85
+ value = (query @ state.float()).transpose(1, 2)
86
+ return self._output(value, source)
87
+
88
+ @staticmethod
89
+ def _transition(state, k, v, decay, erase, write, valid):
90
+ decayed = state.float() * decay.unsqueeze(-1)
91
+ old = torch.einsum("...hk,...hkv->...hv", erase * k, decayed)
92
+ candidate = decayed + k.unsqueeze(-1) * (write * v - old).unsqueeze(-2)
93
+ return candidate if valid is None else torch.where(
94
+ valid[..., None, None, None], candidate, state.float())
95
+
96
+ def transition(self, state: torch.Tensor, source: torch.Tensor,
97
+ valid: torch.Tensor | None = None) -> torch.Tensor:
98
+ if state.shape != (*source.shape[:-1], self.heads, self.key_dim, self.value_dim):
99
+ raise ValueError("GDN2 state and observation leading dimensions differ.")
100
+ k, v, decay, erase, write = self._projections(source)
101
+ if valid is not None:
102
+ if valid.shape != source.shape[:-1]:
103
+ raise ValueError("GDN2 valid mask must match observation rows.")
104
+ return self._transition(state, k, v, decay, erase, write, valid)
105
+
106
+ def write_sequence(self, state: torch.Tensor, source: torch.Tensor,
107
+ valid: torch.Tensor) -> torch.Tensor:
108
+ """Project a commit packet once, then preserve ordered GDN2 recurrence."""
109
+ if source.ndim != 3 or valid.shape != source.shape[:2]:
110
+ raise ValueError("GDN2 sequence and mask must share [batch, length].")
111
+ if state.shape != (source.shape[0], self.heads, self.key_dim, self.value_dim):
112
+ raise ValueError("GDN2 sequence state shape differs.")
113
+ projected = self._projections(source)
114
+ for index in range(source.shape[1]):
115
+ state = self._transition(state, *(part[:, index] for part in projected), valid[:, index])
116
+ return state
117
+
118
+
119
+ __all__ = ["GDN2Memory"]
gdn2_trajectory.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Dual-timescale matrix state with denoise and commit lifetimes.
2
+
3
+ This module is independent of the decoder and commit policy. It provides the
4
+ reference state transition used when replacing the old history and slot paths.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+
11
+ import torch
12
+ from torch import nn
13
+
14
+ from .gdn2_memory import GDN2Memory
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class GDN2TrajectoryState:
19
+ cells: torch.Tensor
20
+ row: torch.Tensor
21
+ persistent: torch.Tensor
22
+ seen: torch.Tensor
23
+
24
+ def detach(self) -> "GDN2TrajectoryState":
25
+ return GDN2TrajectoryState(
26
+ self.cells.detach(), self.row.detach(), self.persistent.detach(),
27
+ self.seen.detach(),
28
+ )
29
+
30
+ def clear_refill(self, lengths: torch.Tensor, head: torch.Tensor) -> "GDN2TrajectoryState":
31
+ batch, canvas = self.seen.shape
32
+ if lengths.shape != (batch,) or head.shape != (batch,):
33
+ raise ValueError("Commit lengths and canvas heads must be per row.")
34
+ physical = torch.arange(canvas, device=self.seen.device)[None, :]
35
+ overwritten = ((physical - head[:, None]) % canvas) < lengths[:, None]
36
+ return GDN2TrajectoryState(
37
+ self.cells.masked_fill(overwritten[..., None, None, None], 0.0),
38
+ self.row, self.persistent, self.seen & ~overwritten,
39
+ )
40
+
41
+ def shift(self, lengths: torch.Tensor) -> "GDN2TrajectoryState":
42
+ """Shift a non-ring canvas and zero its newly filled tail."""
43
+ batch, canvas = self.seen.shape
44
+ if lengths.shape != (batch,):
45
+ raise ValueError("Commit lengths must be per row.")
46
+ physical = torch.arange(canvas, device=self.seen.device)[None, :] + lengths[:, None]
47
+ kept = physical < canvas
48
+ selected = physical.clamp_max(canvas - 1)
49
+ cells = self.cells.gather(
50
+ 1, selected[..., None, None, None].expand_as(self.cells)
51
+ )
52
+ seen = self.seen.gather(1, selected)
53
+ return GDN2TrajectoryState(
54
+ cells.masked_fill(~kept[..., None, None, None], 0.0),
55
+ self.row, self.persistent, seen & kept,
56
+ )
57
+
58
+
59
+ class GDN2TrajectoryMemory(nn.Module):
60
+ def __init__(self, width: int, *, probes: int = 4,
61
+ working_heads: int = 16, working_key: int = 64,
62
+ working_value: int = 64, persistent_heads: int = 16,
63
+ persistent_key: int = 128, persistent_value: int = 128,
64
+ persistent_observation_dim: int | None = None) -> None:
65
+ super().__init__()
66
+ if probes <= 0:
67
+ raise ValueError("Probe count must be positive.")
68
+ self.probes = probes
69
+ self.cell = GDN2Memory(width, working_heads, working_key, working_value)
70
+ self.row = GDN2Memory(width, working_heads, working_key, working_value)
71
+ self.persistent = GDN2Memory(width, persistent_heads, persistent_key, persistent_value,
72
+ observation_dim=persistent_observation_dim)
73
+ self.probe_embed = nn.Embedding(probes, width)
74
+ nn.init.normal_(self.probe_embed.weight, std=0.02)
75
+
76
+ def empty(self, batch: int, canvas: int, *, device: torch.device | None = None
77
+ ) -> GDN2TrajectoryState:
78
+ return GDN2TrajectoryState(
79
+ self.cell.empty(batch, canvas, device=device),
80
+ self.row.empty(batch, device=device),
81
+ self.persistent.empty(batch, device=device),
82
+ torch.zeros(batch, canvas, device=device, dtype=torch.bool),
83
+ )
84
+
85
+ def read(self, state: GDN2TrajectoryState, query: torch.Tensor) -> torch.Tensor:
86
+ result = (self.cell.read(state.cells, query)
87
+ + self.row.read_shared(state.row, query)
88
+ + self.persistent.read_shared(state.persistent, query))
89
+ return torch.where(state.seen[..., None], result, torch.zeros_like(result))
90
+
91
+ def observe(self, state: GDN2TrajectoryState, observation: torch.Tensor,
92
+ live: torch.Tensor, head: torch.Tensor) -> GDN2TrajectoryState:
93
+ batch, canvas, width = observation.shape
94
+ if live.shape != (batch, canvas) or head.shape != (batch,):
95
+ raise ValueError("Observation mask and head have incorrect shapes.")
96
+ cells = self.cell.transition(state.cells, observation, live)
97
+ seen = state.seen | live
98
+ logical_idx = (head[:, None] + torch.arange(canvas, device=head.device)[None, :]) % canvas
99
+ logical = observation.gather(1, logical_idx[..., None].expand(-1, -1, width))
100
+ logical_live = live.gather(1, logical_idx)
101
+ row_state = state.row
102
+ for probe in range(self.probes):
103
+ lo = canvas * probe // self.probes
104
+ hi = canvas * (probe + 1) // self.probes
105
+ selected = logical_live[:, lo:hi]
106
+ count = selected.sum(dim=1, keepdim=True)
107
+ pooled = (logical[:, lo:hi].float() * selected[..., None]).sum(dim=1)
108
+ pooled = (pooled / count.clamp_min(1)).to(observation.dtype)
109
+ pooled = pooled + self.probe_embed.weight[probe].to(observation.dtype)
110
+ row_state = self.row.transition(row_state, pooled, count[:, 0] > 0)
111
+ return GDN2TrajectoryState(cells, row_state, state.persistent, seen)
112
+
113
+ def commit(self, state: GDN2TrajectoryState, experience: torch.Tensor,
114
+ valid: torch.Tensor) -> GDN2TrajectoryState:
115
+ if experience.shape[:2] != valid.shape:
116
+ raise ValueError("Commit experience and validity shapes differ.")
117
+ long_state = self.persistent.write_sequence(state.persistent, experience, valid)
118
+ return GDN2TrajectoryState(state.cells, state.row, long_state, state.seen)
119
+
120
+
121
+ __all__ = ["GDN2TrajectoryMemory", "GDN2TrajectoryState"]
generation_config.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 2,
3
+ "confidence_threshold": null,
4
+ "eos_token_id": [
5
+ 1,
6
+ 50
7
+ ],
8
+ "jump_on_no_progress_after": 12,
9
+ "max_denoising_steps": 256,
10
+ "max_new_tokens": 256,
11
+ "max_ponder_steps": 64,
12
+ "min_trajectory_progress": 0.005,
13
+ "pad_token_id": 0,
14
+ "repetition_penalty": 1.0,
15
+ "repetition_penalty_exclude_token_ids": [
16
+ 2,
17
+ 1,
18
+ 3,
19
+ 0,
20
+ 4,
21
+ 258881,
22
+ 256000,
23
+ 255999,
24
+ 258883,
25
+ 101,
26
+ 258882,
27
+ 106,
28
+ 52,
29
+ 49,
30
+ 47,
31
+ 51,
32
+ 258880,
33
+ 100,
34
+ 105,
35
+ 48,
36
+ 46,
37
+ 50,
38
+ 98,
39
+ 258884
40
+ ],
41
+ "return_dict_in_generate": true,
42
+ "sampler_config": null,
43
+ "stability_threshold": null,
44
+ "t_max": 0.8,
45
+ "t_min": 0.8,
46
+ "transformers_version": "5.14.1",
47
+ "turn_end_token_id": 106
48
+ }
generation_modilify_mk2.py ADDED
@@ -0,0 +1,1307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Rolling text generation for latent-memory ModilifyMk2."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Sequence
6
+ from contextlib import contextmanager
7
+ from dataclasses import dataclass, replace
8
+ import math
9
+ import time
10
+ from typing import Any
11
+
12
+ import torch
13
+ from transformers.cache_utils import Cache
14
+ from transformers.generation import LogitsProcessorList
15
+ from transformers.generation.streamers import BaseStreamer
16
+ from transformers.modeling_outputs import ModelOutput
17
+
18
+ from transformers.models.diffusion_gemma import (
19
+ DiffusionGemmaGenerationConfig,
20
+ DiffusionGemmaGenerationMixin,
21
+ )
22
+ from .commit_policy import (
23
+ fused_commit_failure_rate,
24
+ select_commit_lengths,
25
+ )
26
+ from .configuration_modilify_mk2 import DENOISE_TEMPERATURE
27
+ from .latent_deliberation import LatentDeliberationState, infer_commit_reason
28
+
29
+
30
+ _PARENT_GENERATION_KEYS = frozenset({
31
+ "max_new_tokens",
32
+ "max_length",
33
+ "return_dict_in_generate",
34
+ "max_denoising_steps",
35
+ "sampler_config",
36
+ "t_min",
37
+ "t_max",
38
+ "stability_threshold",
39
+ "confidence_threshold",
40
+ "cache_implementation",
41
+ "cache_config",
42
+ "disable_compile",
43
+ "bos_token_id",
44
+ "pad_token_id",
45
+ "eos_token_id",
46
+ "_commit_hash",
47
+ "_from_model_config",
48
+ "transformers_version",
49
+ })
50
+ _UNSUPPORTED_GENERATION_FIELDS = (
51
+ "sampler_config",
52
+ "stability_threshold",
53
+ "confidence_threshold",
54
+ "one_token_per_denoise_step",
55
+ "compile_generation",
56
+ "sliding_denoise",
57
+ "adaptive_ponder_budget",
58
+ "force_commit_on_max_steps",
59
+ "ponder_budget_id",
60
+ )
61
+
62
+
63
+ def _reject_unsupported_generation_fields(fields: dict[str, object]) -> None:
64
+ configured = {
65
+ name: value
66
+ for name, value in fields.items()
67
+ if value not in (None, False)
68
+ }
69
+ if configured:
70
+ raise ValueError(
71
+ "ModilifyMk2 accepts only the confidence-prefix commit policy; "
72
+ f"unsupported generation fields: {sorted(configured)}"
73
+ )
74
+
75
+
76
+ def _flatten_token_ids(*values: object) -> set[int]:
77
+ """Normalize scalar and sequence token-ID configuration values."""
78
+
79
+ token_ids: set[int] = set()
80
+ for value in values:
81
+ if value is None:
82
+ continue
83
+ if isinstance(value, int):
84
+ token_ids.add(int(value))
85
+ continue
86
+ if isinstance(value, (list, tuple, set)):
87
+ token_ids.update(int(token_id) for token_id in value if token_id is not None)
88
+ return token_ids
89
+
90
+
91
+ def _add_repetition_history(
92
+ history: torch.BoolTensor,
93
+ token_ids: torch.LongTensor,
94
+ eligible: torch.BoolTensor,
95
+ excluded_token_ids: set[int],
96
+ ) -> None:
97
+ """Add eligible row-local token IDs to a compact [batch, vocab] history."""
98
+
99
+ if token_ids.shape != eligible.shape or token_ids.shape[0] != history.shape[0]:
100
+ raise ValueError("Repetition history token and eligibility shapes must match.")
101
+ eligible = eligible.clone()
102
+ for token_id in excluded_token_ids:
103
+ eligible &= token_ids.ne(token_id)
104
+ if not bool(eligible.any()):
105
+ return
106
+ rows = torch.arange(history.shape[0], device=history.device)[:, None]
107
+ rows = rows.expand_as(token_ids)
108
+ history[rows[eligible], token_ids[eligible]] = True
109
+
110
+
111
+ class ModilifyMk2GenerationConfig(DiffusionGemmaGenerationConfig):
112
+ def __init__(self, **kwargs):
113
+ _reject_unsupported_generation_fields({
114
+ name: kwargs.pop(name)
115
+ for name in _UNSUPPORTED_GENERATION_FIELDS
116
+ if name in kwargs
117
+ })
118
+ self.turn_end_token_id: int | None = kwargs.pop("turn_end_token_id", None)
119
+ self.max_ponder_steps: int = kwargs.pop("max_ponder_steps", 64)
120
+ self.jump_on_no_progress_after: int = kwargs.pop("jump_on_no_progress_after", 12)
121
+ self.min_trajectory_progress: float = float(kwargs.pop("min_trajectory_progress", 0.005))
122
+ self.repetition_penalty: float = float(kwargs.pop("repetition_penalty", 1.0))
123
+ excluded_token_ids = kwargs.pop("repetition_penalty_exclude_token_ids", ())
124
+ self.repetition_penalty_exclude_token_ids: list[int] = list(
125
+ dict.fromkeys(int(token_id) for token_id in excluded_token_ids or ())
126
+ )
127
+ kwargs.pop("t_min", None)
128
+ kwargs.pop("t_max", None)
129
+ parent_kwargs = {
130
+ name: kwargs.pop(name)
131
+ for name in tuple(kwargs)
132
+ if name in _PARENT_GENERATION_KEYS
133
+ }
134
+ super().__init__(
135
+ sampler_config=None,
136
+ stability_threshold=None,
137
+ confidence_threshold=None,
138
+ **parent_kwargs,
139
+ )
140
+ self.t_min = DENOISE_TEMPERATURE
141
+ self.t_max = DENOISE_TEMPERATURE
142
+ self.validate()
143
+
144
+ def update(self, defaults_only=False, allow_custom_entries=False, **kwargs):
145
+ """Apply supported overrides while keeping temperature globally fixed."""
146
+
147
+ _reject_unsupported_generation_fields({
148
+ name: kwargs.pop(name)
149
+ for name in _UNSUPPORTED_GENERATION_FIELDS
150
+ if name in kwargs
151
+ })
152
+ if "turn_end_token_id" in kwargs:
153
+ self.turn_end_token_id = kwargs.pop("turn_end_token_id")
154
+ if "max_ponder_steps" in kwargs:
155
+ self.max_ponder_steps = kwargs.pop("max_ponder_steps")
156
+ if "jump_on_no_progress_after" in kwargs:
157
+ self.jump_on_no_progress_after = kwargs.pop("jump_on_no_progress_after")
158
+ if "min_trajectory_progress" in kwargs:
159
+ self.min_trajectory_progress = float(kwargs.pop("min_trajectory_progress"))
160
+ if "repetition_penalty" in kwargs:
161
+ self.repetition_penalty = float(kwargs.pop("repetition_penalty"))
162
+ if "repetition_penalty_exclude_token_ids" in kwargs:
163
+ excluded_token_ids = kwargs.pop("repetition_penalty_exclude_token_ids")
164
+ self.repetition_penalty_exclude_token_ids = list(
165
+ dict.fromkeys(int(token_id) for token_id in excluded_token_ids or ())
166
+ )
167
+ kwargs.pop("t_min", None)
168
+ kwargs.pop("t_max", None)
169
+ unused = super().update(
170
+ defaults_only=defaults_only,
171
+ allow_custom_entries=allow_custom_entries,
172
+ **kwargs,
173
+ )
174
+ self.sampler_config = None
175
+ self.stability_threshold = None
176
+ self.confidence_threshold = None
177
+ self.t_min = DENOISE_TEMPERATURE
178
+ self.t_max = DENOISE_TEMPERATURE
179
+ return unused
180
+
181
+ def validate(self, **kwargs):
182
+ if self.max_new_tokens is not None and (
183
+ not isinstance(self.max_new_tokens, int) or self.max_new_tokens <= 0
184
+ ):
185
+ raise ValueError(f"`max_new_tokens` must be a positive integer, but got {self.max_new_tokens}")
186
+ if self.max_length is not None and (
187
+ not isinstance(self.max_length, int) or self.max_length <= 0
188
+ ):
189
+ raise ValueError(f"`max_length` must be a positive integer, but got {self.max_length}")
190
+ if self.turn_end_token_id is not None and (
191
+ not isinstance(self.turn_end_token_id, int) or self.turn_end_token_id < 0
192
+ ):
193
+ raise ValueError("`turn_end_token_id` must be a non-negative integer.")
194
+ if not isinstance(self.max_ponder_steps, int) or self.max_ponder_steps <= 0:
195
+ raise ValueError("`max_ponder_steps` must be a positive integer.")
196
+ if not isinstance(self.jump_on_no_progress_after, int) or self.jump_on_no_progress_after <= 0:
197
+ raise ValueError("`jump_on_no_progress_after` must be a positive integer.")
198
+ if not isinstance(self.min_trajectory_progress, (int, float)) or self.min_trajectory_progress < 0:
199
+ raise ValueError("`min_trajectory_progress` must be a non-negative number.")
200
+ if not math.isfinite(self.repetition_penalty) or self.repetition_penalty <= 0:
201
+ raise ValueError("`repetition_penalty` must be a finite positive number.")
202
+ if any(
203
+ not isinstance(token_id, int) or isinstance(token_id, bool) or token_id < 0
204
+ for token_id in self.repetition_penalty_exclude_token_ids
205
+ ):
206
+ raise ValueError(
207
+ "`repetition_penalty_exclude_token_ids` must contain non-negative integers."
208
+ )
209
+
210
+ @classmethod
211
+ def from_model_config(cls, model_config):
212
+ """Build the only generation field owned by the ModilifyMk2 model config."""
213
+
214
+ return cls(turn_end_token_id=model_config.turn_end_token_id)
215
+
216
+ @staticmethod
217
+ def _get_default_generation_params() -> dict[str, object]:
218
+ """Return defaults with no inherited entropy/readiness commit controls."""
219
+
220
+ return {
221
+ "max_new_tokens": 256,
222
+ "max_denoising_steps": 48,
223
+ "t_min": DENOISE_TEMPERATURE,
224
+ "t_max": DENOISE_TEMPERATURE,
225
+ }
226
+
227
+
228
+ @dataclass
229
+ class ModilifyMk2GenerationOutput(ModelOutput):
230
+ sequences: torch.LongTensor
231
+ generated_lengths: torch.LongTensor | None = None
232
+ tokens_per_forward: torch.FloatTensor | None = None
233
+ past_key_values: Cache | None = None
234
+ stop_reason: str | tuple[str, ...] | None = None
235
+ committed_tokens: int | torch.LongTensor | None = None
236
+ denoise_steps: int | torch.LongTensor | None = None
237
+ no_progress_steps: int | torch.LongTensor | None = None
238
+ jump_count: int | torch.LongTensor | None = None
239
+ forced_jump_bad_count: int | torch.LongTensor | None = None
240
+ heavy_forward_count: int | torch.LongTensor | None = None
241
+ latent_context_update_count: int | torch.LongTensor | None = None
242
+ average_commit_len: float | torch.FloatTensor | None = None
243
+ state_shift_count: int | torch.LongTensor | None = None
244
+ latent_memory_norm: float | torch.FloatTensor | None = None
245
+ state_retention_score: float | torch.FloatTensor | None = None
246
+ logits: None = None
247
+ scores: None = None
248
+ hidden_states: None = None
249
+
250
+
251
+ @dataclass
252
+ class ModilifyMk2RollingState:
253
+ """All real iterative state; no vocabulary-sized tensor is retained."""
254
+
255
+ canvas: torch.LongTensor
256
+ confidence: torch.FloatTensor
257
+ entropy: torch.FloatTensor
258
+ age: torch.IntTensor
259
+ latent_state: LatentDeliberationState
260
+ head: torch.LongTensor | None = None
261
+
262
+
263
+ def _prefix_length(prefix_mask: torch.BoolTensor) -> int:
264
+ common = prefix_mask.all(dim=0)
265
+ rejected = (~common).nonzero(as_tuple=False)
266
+ return common.shape[0] if rejected.numel() == 0 else int(rejected[0, 0])
267
+
268
+
269
+ def _tensor_stats(value: torch.Tensor) -> dict[str, float]:
270
+ data = value.detach().float()
271
+ return {
272
+ "min": float(data.min()), "max": float(data.max()),
273
+ "mean": float(data.mean()), "norm": float(data.norm()),
274
+ }
275
+
276
+
277
+ def _memory_diversity_stats(memory_slots: torch.Tensor) -> dict[str, float]:
278
+ """Trace-only collapse diagnostics; the pairwise term is tiny (32 slots)."""
279
+
280
+ memory = memory_slots.detach().float().flatten(2)
281
+ centered = memory - memory.mean(dim=1, keepdim=True)
282
+ maximum_difference = (
283
+ (memory[:, 1:] - memory[:, :-1]).abs().amax()
284
+ if memory.shape[1] > 1 else memory.new_zeros(())
285
+ )
286
+ normalized = torch.nn.functional.normalize(memory, dim=-1, eps=1.0e-6)
287
+ pairwise = normalized @ normalized.transpose(-1, -2)
288
+ off_diagonal = ~torch.eye(memory.shape[1], device=memory.device, dtype=torch.bool)
289
+ return {
290
+ "memory_slot_std": float(centered.square().mean().sqrt()),
291
+ "memory_slot_max_difference": float(maximum_difference),
292
+ "memory_slot_pairwise_cosine_mean": float(pairwise[:, off_diagonal].mean()),
293
+ }
294
+
295
+
296
+ def build_denoise_trace_event(
297
+ *,
298
+ denoise_step: int,
299
+ prefix_length: int,
300
+ committed_before: int,
301
+ committed_after: int,
302
+ no_progress_steps: int,
303
+ policy_prefix_mask: torch.BoolTensor,
304
+ commit_length: int,
305
+ ponder_fallback: bool,
306
+ state: ModilifyMk2RollingState,
307
+ proposal: torch.LongTensor,
308
+ committed_token_ids: torch.LongTensor,
309
+ step_elapsed_seconds: float,
310
+ latent_residual_diagnostics: dict[str, torch.Tensor] | None = None,
311
+ ) -> dict[str, object]:
312
+ return {
313
+ "event": "denoise_step",
314
+ "denoise_step": denoise_step,
315
+ "prefix_length": prefix_length,
316
+ "committed_before": committed_before,
317
+ "committed_after": committed_after,
318
+ "no_progress_steps": no_progress_steps,
319
+ "policy_prefix_count": int(policy_prefix_mask.sum()),
320
+ "policy_prefix_length": _prefix_length(policy_prefix_mask),
321
+ "commit_length": commit_length,
322
+ "ponder_fallback": bool(ponder_fallback),
323
+ "confidence": _tensor_stats(state.confidence),
324
+ "entropy": _tensor_stats(state.entropy),
325
+ "age": _tensor_stats(state.age),
326
+ "token_changed": _tensor_stats(state.latent_state.token_changed),
327
+ "confidence_delta": _tensor_stats(state.latent_state.confidence_delta),
328
+ "entropy_delta": _tensor_stats(state.latent_state.entropy_delta),
329
+ "ponder_steps": int(state.latent_state.ponder_steps[0]),
330
+ "stagnation_steps": int(state.latent_state.stagnation_steps[0]),
331
+ "history_fill": float(state.latent_state.gdn2.seen[0].float().mean()),
332
+ "memory_slots": _tensor_stats(state.latent_state.memory_slots),
333
+ "memory_diversity": _memory_diversity_stats(state.latent_state.memory_slots),
334
+ "latent_residual": {
335
+ name: float(value.detach().float())
336
+ for name, value in (latent_residual_diagnostics or {}).items()
337
+ },
338
+ "proposal_token_ids": proposal[0].detach().cpu().tolist(),
339
+ "draft_token_ids": state.canvas[0].detach().cpu().tolist(),
340
+ "committed_token_ids": committed_token_ids[0].detach().cpu().tolist(),
341
+ "step_elapsed_seconds": step_elapsed_seconds,
342
+ }
343
+
344
+
345
+ class NoiseCanvasSampler:
346
+ """Uniform diffusion noise source with no commit-policy responsibilities."""
347
+
348
+ def __init__(self, *, canvas_length: int, vocab_size: int) -> None:
349
+ self.canvas_length = int(canvas_length)
350
+ self.vocab_size = int(vocab_size)
351
+ self.initial_entropy = math.log(self.vocab_size)
352
+
353
+ def initialize_canvas(
354
+ self,
355
+ batch_size: int,
356
+ device: torch.device,
357
+ generators: Sequence[torch.Generator] | None = None,
358
+ ) -> torch.LongTensor:
359
+ if generators is not None:
360
+ if len(generators) != batch_size:
361
+ raise ValueError("Canvas sampling requires one generator per batch row.")
362
+ return torch.cat(
363
+ [
364
+ torch.randint(
365
+ self.vocab_size,
366
+ (1, self.canvas_length),
367
+ device=device,
368
+ generator=generator,
369
+ )
370
+ for generator in generators
371
+ ],
372
+ dim=0,
373
+ )
374
+ return torch.randint(
375
+ self.vocab_size,
376
+ (batch_size, self.canvas_length),
377
+ device=device,
378
+ )
379
+
380
+
381
+ class ModilifyMk2GenerationMixin(DiffusionGemmaGenerationMixin):
382
+ def init_continuous_batching(
383
+ self,
384
+ generation_config=None,
385
+ continuous_batching_config=None,
386
+ workload_hints=None,
387
+ ):
388
+ """Create the ModilifyMk2 continuous manager behind the official API surface."""
389
+
390
+ from .continuous_batching import (
391
+ ModilifyMk2ContinuousBatchingManager,
392
+ continuous_config_fingerprint,
393
+ )
394
+
395
+ cached = getattr(self, "_cached_continuous_batching_manager", None)
396
+ if isinstance(cached, ModilifyMk2ContinuousBatchingManager) and not cached.destroyed:
397
+ requested_generation = generation_config or getattr(
398
+ self, "generation_config", None
399
+ )
400
+ requested_fingerprint = continuous_config_fingerprint(
401
+ requested_generation,
402
+ continuous_batching_config,
403
+ )
404
+ if cached.config_fingerprint == requested_fingerprint:
405
+ cached._prepare_for_next_session()
406
+ return cached
407
+ cached.destroy()
408
+ delattr(self, "_cached_continuous_batching_manager")
409
+ return ModilifyMk2ContinuousBatchingManager(
410
+ model=self,
411
+ generation_config=generation_config or getattr(self, "generation_config", None),
412
+ continuous_batching_config=continuous_batching_config,
413
+ workload_hints=workload_hints,
414
+ )
415
+
416
+ def destroy_cached_continuous_batching_manager(self) -> None:
417
+ manager = getattr(self, "_cached_continuous_batching_manager", None)
418
+ if manager is not None:
419
+ manager.destroy()
420
+ delattr(self, "_cached_continuous_batching_manager")
421
+
422
+ @contextmanager
423
+ @torch.no_grad()
424
+ def continuous_batching_context_manager(
425
+ self,
426
+ generation_config=None,
427
+ block: bool = True,
428
+ timeout: float | None = None,
429
+ continuous_batching_config=None,
430
+ persistent_manager: bool = False,
431
+ warmup: bool = True,
432
+ workload_hints=None,
433
+ ):
434
+ manager = self.init_continuous_batching(
435
+ generation_config=generation_config,
436
+ continuous_batching_config=continuous_batching_config,
437
+ workload_hints=workload_hints,
438
+ )
439
+ if persistent_manager:
440
+ self._cached_continuous_batching_manager = manager
441
+ if warmup and not manager.warmed_up:
442
+ manager.warmup()
443
+ manager.start()
444
+ try:
445
+ yield manager
446
+ finally:
447
+ manager.stop(
448
+ block=block,
449
+ timeout=timeout,
450
+ keep_for_next_session=persistent_manager,
451
+ )
452
+ if not persistent_manager:
453
+ manager.destroy()
454
+
455
+ @torch.no_grad()
456
+ def generate_batch(
457
+ self,
458
+ inputs: list[list[int]],
459
+ generation_config=None,
460
+ continuous_batching_config=None,
461
+ record_timestamps: bool = False,
462
+ progress_bar: bool = True,
463
+ persistent_manager: bool = False,
464
+ warmup: bool = True,
465
+ **kwargs: Any,
466
+ ) -> dict[str, object]:
467
+ """Official-compatible convenience wrapper over the continuous manager."""
468
+
469
+ del progress_bar
470
+ if any(
471
+ not isinstance(input_ids, list)
472
+ or not input_ids
473
+ or any(
474
+ not isinstance(token_id, int) or isinstance(token_id, bool)
475
+ for token_id in input_ids
476
+ )
477
+ for input_ids in inputs
478
+ ):
479
+ raise ValueError("Every `inputs` row must be a non-empty list of integer token IDs.")
480
+ seeds = kwargs.pop("seeds", None)
481
+ if seeds is not None and len(seeds) != len(inputs):
482
+ raise ValueError("`seeds` must contain one seed per request.")
483
+ if seeds is not None:
484
+ seeds = [int(seed) for seed in seeds]
485
+ if not inputs:
486
+ return {}
487
+ manager = self.init_continuous_batching(
488
+ generation_config=generation_config,
489
+ continuous_batching_config=continuous_batching_config,
490
+ )
491
+ if persistent_manager:
492
+ self._cached_continuous_batching_manager = manager
493
+ if warmup and not manager.warmed_up:
494
+ manager.warmup()
495
+ completed = False
496
+ try:
497
+ request_ids = []
498
+ queue_limit = int(
499
+ manager.continuous_batching_config.max_queue_size or 0
500
+ )
501
+ preload_count = (
502
+ len(inputs) if queue_limit == 0 else min(len(inputs), queue_limit)
503
+ )
504
+ for index, input_ids in enumerate(inputs):
505
+ if index == preload_count and not manager.is_running():
506
+ manager.start()
507
+ request_kwargs = dict(kwargs)
508
+ if seeds is not None:
509
+ request_kwargs["seed"] = int(seeds[index])
510
+ request_ids.append(
511
+ manager.add_request(
512
+ input_ids=input_ids,
513
+ record_timestamps=record_timestamps,
514
+ **request_kwargs,
515
+ )
516
+ )
517
+ manager.close_input()
518
+ # Unlike the open-ended manager API, generate_batch has its whole
519
+ # initial workload. Queue it before starting so the first cohort is
520
+ # filled deterministically up to scheduler/queue capacity.
521
+ if not manager.is_running():
522
+ manager.start()
523
+ final_outputs = {}
524
+ for output in manager:
525
+ if output.status in {
526
+ getattr(output.status.__class__, "FINISHED", output.status),
527
+ getattr(output.status.__class__, "FAILED", output.status),
528
+ }:
529
+ final_outputs[output.request_id] = output
530
+ result = {
531
+ request_id: final_outputs[request_id]
532
+ for request_id in request_ids
533
+ if request_id is not None
534
+ }
535
+ completed = True
536
+ finally:
537
+ manager.stop(
538
+ block=True,
539
+ keep_for_next_session=persistent_manager,
540
+ hard_stop=not completed,
541
+ )
542
+ if not persistent_manager:
543
+ manager.destroy()
544
+ return result
545
+
546
+ def _prepare_sampler(
547
+ self, generation_config: ModilifyMk2GenerationConfig, canvas_length: int | None = None
548
+ ) -> NoiseCanvasSampler:
549
+ del generation_config
550
+ return NoiseCanvasSampler(
551
+ canvas_length=canvas_length or self.config.canvas_length,
552
+ vocab_size=self.config.text_config.vocab_size,
553
+ )
554
+
555
+ def _write_committed_memory(
556
+ self,
557
+ *,
558
+
559
+ next_state: ModilifyMk2RollingState,
560
+ working_state: torch.Tensor,
561
+
562
+ heavy_hidden: torch.Tensor,
563
+ commit_token_ids: torch.LongTensor,
564
+ commit_lengths: torch.Tensor,
565
+ prefix_lengths: torch.Tensor,
566
+ commit_reason: torch.Tensor,
567
+ canvas_head: torch.Tensor | None = None,
568
+ ) -> ModilifyMk2RollingState:
569
+ if not bool(commit_lengths.gt(0).any()):
570
+ return next_state
571
+ batch, canvas = commit_token_ids.shape
572
+ if working_state.shape[:2] != (batch, canvas):
573
+ raise ValueError("Committed token IDs must match the unshifted canvas.")
574
+ max_commit = min(int(commit_lengths.max()), canvas)
575
+ if canvas_head is None:
576
+ selected_ids = commit_token_ids[:, :max_commit]
577
+ else:
578
+ head = canvas_head.to(device=commit_token_ids.device, dtype=torch.long).view(batch, 1)
579
+ physical = (head + torch.arange(max_commit, device=commit_token_ids.device)) % canvas
580
+ selected_ids = commit_token_ids.gather(1, physical)
581
+ with torch.no_grad():
582
+ committed_token_embeddings = self.model.decoder.embed_tokens(selected_ids)
583
+ memory, _diagnostics = self.latent_deliberation.commit_write(
584
+ memory=next_state.latent_state.memory_slots,
585
+ working_state=working_state,
586
+
587
+
588
+ heavy_hidden=heavy_hidden,
589
+ committed_token_embeddings=committed_token_embeddings,
590
+ commit_lengths=commit_lengths,
591
+ prefix_lengths=prefix_lengths,
592
+ commit_reason=commit_reason,
593
+ canvas_head=canvas_head,
594
+ )
595
+ return replace(
596
+ next_state,
597
+ latent_state=replace(
598
+ next_state.latent_state, memory_slots=memory,
599
+ gdn2=replace(next_state.latent_state.gdn2, persistent=memory),
600
+ ),
601
+ )
602
+
603
+ @staticmethod
604
+ def _shift_state_rows(
605
+ state: ModilifyMk2RollingState,
606
+ commit_lengths: torch.LongTensor,
607
+ sampler: NoiseCanvasSampler,
608
+ generators: Sequence[torch.Generator] | None = None,
609
+ remaining_lengths: torch.LongTensor | None = None,
610
+ pad_token_id: int = 0,
611
+ ) -> ModilifyMk2RollingState:
612
+ """Shift every rolling row by its own committed prefix length."""
613
+
614
+ batch_size, canvas_length = state.canvas.shape
615
+ if commit_lengths.shape != (batch_size,):
616
+ raise ValueError("Commit lengths must have shape [batch].")
617
+ if not bool(commit_lengths.gt(0).any()):
618
+ if generators is not None:
619
+ if len(generators) != batch_size:
620
+ raise ValueError("State shifting requires one generator per batch row.")
621
+ # Seeded generation deliberately advances every active request
622
+ # once per denoise step, independent of the other active rows.
623
+ for generator in generators:
624
+ sampler.initialize_canvas(
625
+ 1, state.canvas.device, generators=[generator]
626
+ )
627
+ return state
628
+ positions = torch.arange(canvas_length, device=state.canvas.device)[None, :]
629
+ source = positions + commit_lengths[:, None]
630
+ retained = source.lt(canvas_length)
631
+
632
+ def shift(value: torch.Tensor, fill_value: float | int = 0) -> torch.Tensor:
633
+ index = source.clamp_max(canvas_length - 1)
634
+ index = index.view(
635
+ batch_size, canvas_length, *([1] * (value.ndim - 2))
636
+ ).expand_as(value)
637
+ gathered = value.gather(1, index)
638
+ mask = retained.view(
639
+ batch_size, canvas_length, *([1] * (value.ndim - 2))
640
+ )
641
+ fill = torch.as_tensor(fill_value, device=value.device, dtype=value.dtype)
642
+ return torch.where(mask, gathered, fill)
643
+
644
+ if generators is None:
645
+ tail = sampler.initialize_canvas(batch_size, state.canvas.device)
646
+ else:
647
+ if len(generators) != batch_size:
648
+ raise ValueError("State shifting requires one generator per batch row.")
649
+ tail = torch.zeros_like(state.canvas)
650
+ for row, (commit_length, generator) in enumerate(
651
+ zip(commit_lengths.detach().cpu().tolist(), generators, strict=True)
652
+ ):
653
+ sampled = sampler.initialize_canvas(
654
+ 1,
655
+ state.canvas.device,
656
+ generators=[generator],
657
+ )
658
+ tail[row] = sampled[0]
659
+ canvas = torch.cat((state.canvas, tail), dim=1).gather(1, source)
660
+ if remaining_lengths is not None:
661
+ if remaining_lengths.shape != (batch_size,):
662
+ raise ValueError("Remaining lengths must have shape [batch].")
663
+ canvas = canvas.masked_fill(
664
+ (positions >= (canvas_length - commit_lengths)[:, None])
665
+ & (positions >= remaining_lengths[:, None]),
666
+ pad_token_id,
667
+ )
668
+ unknown_entropy = float(sampler.initial_entropy)
669
+ latent = state.latent_state
670
+ committed = commit_lengths.gt(0)
671
+ shifted_latent = LatentDeliberationState(
672
+ memory_slots=latent.memory_slots.clone(),
673
+ confidence=shift(latent.confidence),
674
+ entropy=shift(latent.entropy, unknown_entropy),
675
+ age=shift(latent.age),
676
+ token_changed=shift(latent.token_changed),
677
+ confidence_delta=shift(latent.confidence_delta),
678
+ entropy_delta=shift(latent.entropy_delta),
679
+ ponder_steps=torch.where(
680
+ committed, torch.zeros_like(latent.ponder_steps), latent.ponder_steps
681
+ ),
682
+ stagnation_steps=torch.where(
683
+ committed, torch.zeros_like(latent.stagnation_steps), latent.stagnation_steps
684
+ ),
685
+ gdn2=None if latent.gdn2 is None else latent.gdn2.shift(commit_lengths),
686
+ )
687
+ return ModilifyMk2RollingState(
688
+ canvas=canvas,
689
+ confidence=shift(state.confidence),
690
+ entropy=shift(state.entropy, unknown_entropy),
691
+ age=shift(state.age),
692
+ latent_state=shifted_latent,
693
+
694
+
695
+ head=state.head,
696
+ )
697
+
698
+ @torch.inference_mode()
699
+ def generate(
700
+ self,
701
+ input_ids: torch.LongTensor | None = None,
702
+ past_key_values: Cache | None = None,
703
+ streamer: BaseStreamer | None = None,
704
+ generation_config: ModilifyMk2GenerationConfig | None = None,
705
+ logits_processor: LogitsProcessorList | None = None,
706
+ denoise_trace_callback: Callable[[dict[str, object]], None] | None = None,
707
+ **kwargs,
708
+ ) -> ModilifyMk2GenerationOutput:
709
+ request_seeds = kwargs.pop("seeds", None)
710
+ scalar_seed = kwargs.pop("seed", None)
711
+ if request_seeds is not None and scalar_seed is not None:
712
+ raise ValueError("Pass either `seed` or `seeds`, not both.")
713
+ generation_config, model_kwargs = self._prepare_generation_config(generation_config, **kwargs)
714
+ if input_ids is None or input_ids.ndim != 2 or input_ids.shape[0] < 1:
715
+ raise ValueError("ModilifyMk2 generation requires `input_ids` with shape [batch, sequence].")
716
+ if logits_processor:
717
+ raise ValueError(
718
+ "ModilifyMk2 uses its built-in exact sampler and does not accept "
719
+ "custom logits processors."
720
+ )
721
+ batch_size, input_width = input_ids.shape
722
+ if scalar_seed is not None:
723
+ request_seeds = [int(scalar_seed) + row for row in range(batch_size)]
724
+ elif batch_size > 1 and request_seeds is None:
725
+ # Static B>1 still uses independent row generators, but its default
726
+ # path must advance the caller's global device RNG just like normal
727
+ # generation instead of repeating torch.initial_seed() forever.
728
+ seed_parts = torch.randint(
729
+ 0,
730
+ (1 << 31) - 1,
731
+ (batch_size, 2),
732
+ device=input_ids.device,
733
+ dtype=torch.int64,
734
+ ).detach().cpu().tolist()
735
+ request_seeds = [
736
+ (int(high) << 31) | int(low) for high, low in seed_parts
737
+ ]
738
+ if request_seeds is not None:
739
+ if len(request_seeds) != batch_size:
740
+ raise ValueError("`seeds` must contain one seed per batch row.")
741
+ sampling_generators = []
742
+ for seed_value in request_seeds:
743
+ generator = torch.Generator(device=input_ids.device)
744
+ generator.manual_seed(int(seed_value) & ((1 << 63) - 1))
745
+ sampling_generators.append(generator)
746
+ else:
747
+ sampling_generators = None
748
+ if batch_size > 1 and streamer is not None:
749
+ raise ValueError("ModilifyMk2 streamers currently support batch size 1 only.")
750
+ if batch_size > 1 and denoise_trace_callback is not None:
751
+ raise ValueError("ModilifyMk2 denoise tracing currently supports batch size 1 only.")
752
+ if batch_size > 1 and past_key_values is not None:
753
+ raise ValueError("Batched ModilifyMk2 generation requires a fresh KV cache.")
754
+ if batch_size > 1:
755
+ from .continuous_batching import generate_static_batch_with_logical_cache
756
+
757
+ attention_mask = model_kwargs.pop("attention_mask", None)
758
+ canonical_mask = (
759
+ torch.ones_like(input_ids, dtype=torch.bool)
760
+ if attention_mask is None
761
+ else attention_mask.to(device=input_ids.device, dtype=torch.bool)
762
+ )
763
+ if canonical_mask.shape != input_ids.shape:
764
+ raise ValueError(
765
+ "`attention_mask` must have the same shape as `input_ids`."
766
+ )
767
+ row_limits = []
768
+ for prompt_length in canonical_mask.long().sum(dim=-1).tolist():
769
+ _, resolved_max_new_tokens = self._prepare_generated_length(
770
+ generation_config, int(prompt_length)
771
+ )
772
+ if resolved_max_new_tokens <= 0:
773
+ raise ValueError(
774
+ "The requested maximum length leaves no room to generate "
775
+ "for every batch row."
776
+ )
777
+ row_limits.append(int(resolved_max_new_tokens))
778
+ provided_positions = model_kwargs.pop("position_ids", None)
779
+ if provided_positions is not None:
780
+ canonical_positions = (
781
+ canonical_mask.long().cumsum(dim=-1).sub(1).clamp_min(0)
782
+ ).to(provided_positions)
783
+ if not torch.equal(provided_positions, canonical_positions):
784
+ raise ValueError(
785
+ "Continuous static batches require canonical row-local `position_ids`."
786
+ )
787
+ if model_kwargs:
788
+ unsupported = ", ".join(sorted(model_kwargs))
789
+ raise ValueError(
790
+ f"Unsupported batched ModilifyMk2 generation arguments: {unsupported}"
791
+ )
792
+ return generate_static_batch_with_logical_cache(
793
+ self,
794
+ input_ids,
795
+ canonical_mask,
796
+ generation_config,
797
+ seeds=request_seeds,
798
+ max_new_tokens=row_limits,
799
+ )
800
+ device = input_ids.device
801
+ dtype = self.model.decoder.embed_tokens.weight.dtype
802
+ canvas_length = self.config.canvas_length
803
+ cached_length = past_key_values.get_seq_length() if past_key_values is not None else 0
804
+ repetition_penalty = float(generation_config.repetition_penalty)
805
+ repetition_enabled = repetition_penalty != 1.0
806
+ if repetition_enabled and cached_length:
807
+ raise ValueError(
808
+ "Repetition penalty requires a fresh KV cache so the complete prompt "
809
+ "token history is available."
810
+ )
811
+ _, max_new_tokens = self._prepare_generated_length(
812
+ generation_config, cached_length + input_width
813
+ )
814
+
815
+ max_iterations = deterministic_episode_iteration_bound(
816
+ torch.tensor([max_new_tokens]),
817
+ max_ponder_steps=generation_config.max_ponder_steps,
818
+ )
819
+ if past_key_values is None:
820
+ past_key_values = self._prepare_cache_for_generation(
821
+ generation_config,
822
+ batch_size=batch_size,
823
+ # Ragged batches append dense, masked cache blocks. In the
824
+ # worst case only one row advances in each block.
825
+ max_length=input_width + batch_size * max_new_tokens,
826
+ )
827
+ expected_mask_width = cached_length + input_width
828
+ cache_attention_mask = model_kwargs.pop(
829
+ "attention_mask",
830
+ torch.ones(
831
+ batch_size, expected_mask_width, dtype=torch.bool, device=device
832
+ ),
833
+ ).bool()
834
+ if cache_attention_mask.shape != (batch_size, expected_mask_width):
835
+ raise ValueError(
836
+ "`attention_mask` must have shape [batch, cached_length + sequence]."
837
+ )
838
+ provided_position_ids = model_kwargs.pop("position_ids", None)
839
+ if provided_position_ids is not None:
840
+ if provided_position_ids.shape != input_ids.shape:
841
+ raise ValueError("`position_ids` must have the same shape as `input_ids`.")
842
+ prompt_positions = provided_position_ids.to(device=device, dtype=torch.int32)
843
+ elif cached_length:
844
+ prompt_positions = torch.arange(
845
+ cached_length,
846
+ cached_length + input_width,
847
+ device=device,
848
+ dtype=torch.int32,
849
+ ).unsqueeze(0)
850
+ else:
851
+ input_mask = cache_attention_mask[:, -input_width:]
852
+ prompt_positions = input_mask.long().cumsum(dim=-1).sub(1).clamp_min(0).to(torch.int32)
853
+ logical_lengths = cache_attention_mask.long().sum(dim=-1)
854
+ if input_width:
855
+ past_key_values = self.model.encoder(
856
+ input_ids=input_ids,
857
+ attention_mask=cache_attention_mask,
858
+ past_key_values=past_key_values,
859
+ position_ids=prompt_positions,
860
+ ).past_key_values
861
+
862
+ sampler = self._prepare_sampler(generation_config, canvas_length)
863
+ latent = LatentDeliberationState.empty(
864
+ batch_size=batch_size, canvas_length=canvas_length,
865
+ latent_dim=self.config.latent_dim, memory_slots=self.config.latent_memory_slots,
866
+ device=device, dtype=dtype, enable_gdn2=True,
867
+ )
868
+ initial_canvas = sampler.initialize_canvas(
869
+ batch_size, device, generators=sampling_generators
870
+ )
871
+ state = ModilifyMk2RollingState(
872
+ canvas=initial_canvas,
873
+ confidence=torch.zeros(
874
+ batch_size, canvas_length, device=device, dtype=torch.float32
875
+ ),
876
+ entropy=torch.full(
877
+ (batch_size, canvas_length), math.log(self.config.text_config.vocab_size),
878
+ device=device, dtype=torch.float32,
879
+ ),
880
+ age=torch.zeros(
881
+ batch_size, canvas_length, device=device, dtype=torch.int32
882
+ ),
883
+ latent_state=latent,
884
+
885
+
886
+ head=torch.zeros(batch_size, device=device, dtype=torch.long),
887
+ )
888
+ turn_end = (
889
+ self.config.turn_end_token_id
890
+ if generation_config.turn_end_token_id is None
891
+ else generation_config.turn_end_token_id
892
+ )
893
+ configured_eos = generation_config.eos_token_id
894
+ if configured_eos is None:
895
+ configured_eos = self.config.eos_token_id
896
+ if isinstance(configured_eos, int):
897
+ configured_eos = [configured_eos]
898
+ stop_token_ids = tuple(
899
+ dict.fromkeys((int(turn_end), *(int(value) for value in configured_eos or ())))
900
+ )
901
+ pad_token_id = generation_config.pad_token_id
902
+ if pad_token_id is None:
903
+ pad_token_id = getattr(self.config, "pad_token_id", None)
904
+ if isinstance(pad_token_id, (list, tuple)):
905
+ pad_token_id = pad_token_id[0]
906
+ pad_token_id = int(0 if pad_token_id is None else pad_token_id)
907
+ state = replace(
908
+ state,
909
+ canvas=state.canvas.masked_fill(
910
+ torch.arange(canvas_length, device=device)[None, :] >= max_new_tokens,
911
+ pad_token_id,
912
+ ),
913
+ )
914
+ excluded_repetition_token_ids = _flatten_token_ids(
915
+ generation_config.repetition_penalty_exclude_token_ids,
916
+ generation_config.pad_token_id,
917
+ generation_config.bos_token_id,
918
+ generation_config.eos_token_id,
919
+ generation_config.turn_end_token_id,
920
+ getattr(self.config, "image_token_id", None),
921
+ )
922
+ repetition_history = None
923
+ if repetition_enabled:
924
+ repetition_history = torch.zeros(
925
+ (batch_size, self.config.text_config.vocab_size),
926
+ dtype=torch.bool,
927
+ device=device,
928
+ )
929
+ _add_repetition_history(
930
+ repetition_history,
931
+ input_ids,
932
+ cache_attention_mask[:, -input_width:],
933
+ excluded_repetition_token_ids,
934
+ )
935
+ generated = torch.full(
936
+ (batch_size, max_new_tokens),
937
+ pad_token_id,
938
+ dtype=input_ids.dtype,
939
+ device=device,
940
+ )
941
+ committed = torch.zeros(batch_size, dtype=torch.long, device=device)
942
+ denoise_steps = torch.zeros_like(committed)
943
+ jumps = torch.zeros_like(committed)
944
+ forced_jump_tokens = torch.zeros_like(committed)
945
+ shifts = torch.zeros_like(committed)
946
+ retention_scores = torch.zeros(batch_size, dtype=torch.float32, device=device)
947
+ stop_codes = torch.zeros_like(committed)
948
+ active_rows = torch.ones(batch_size, dtype=torch.bool, device=device)
949
+ canvas_positions = torch.arange(canvas_length, device=device)[None, :]
950
+ if streamer is not None:
951
+ streamer.put(input_ids.cpu())
952
+
953
+ while bool(active_rows.any()):
954
+ started = time.perf_counter()
955
+ prefix_length = past_key_values.get_seq_length()
956
+ decoder_positions = (
957
+ logical_lengths[:, None]
958
+ + torch.arange(canvas_length, device=device)[None, :]
959
+ ).to(torch.int32)
960
+ denoise_steps += active_rows.long()
961
+ decoder_attention_mask = torch.cat(
962
+ (
963
+ cache_attention_mask,
964
+ torch.ones(
965
+ batch_size,
966
+ canvas_length,
967
+ dtype=torch.bool,
968
+ device=device,
969
+ ),
970
+ ),
971
+ dim=-1,
972
+ )
973
+ output = self(
974
+ input_ids=None, past_key_values=past_key_values,
975
+ decoder_input_ids=state.canvas,
976
+ previous_confidence=state.confidence, previous_entropy=state.entropy,
977
+ token_age=state.age, latent_state=state.latent_state,
978
+
979
+
980
+ decoder_position_ids=decoder_positions, decoder_read_cache=True,
981
+ decoder_attention_mask=decoder_attention_mask,
982
+ compact_vocab=True,
983
+ repetition_token_mask=repetition_history,
984
+ repetition_penalty=repetition_penalty,
985
+ sampling_generators=sampling_generators,
986
+ **model_kwargs,
987
+ )
988
+ if (
989
+ output.proposal is None
990
+ or output.proposal_confidence is None
991
+ or output.token_entropy is None
992
+ or output.greedy_proposal is None
993
+ or output.greedy_confidence is None
994
+ ):
995
+ raise RuntimeError(
996
+ "Compact vocabulary forward did not return proposal statistics."
997
+ )
998
+ proposal = output.proposal
999
+ proposal_confidence = output.proposal_confidence
1000
+ token_entropy = output.token_entropy
1001
+ greedy_proposal = output.greedy_proposal
1002
+ greedy_confidence = output.greedy_confidence
1003
+ next_canvas = proposal.clone()
1004
+ next_confidence = proposal_confidence.float()
1005
+ next_latent = replace(
1006
+ output.next_latent_state,
1007
+ confidence=next_confidence.detach().float(),
1008
+ entropy=token_entropy.detach().float(),
1009
+ age=state.age + 1,
1010
+ token_changed=next_canvas.ne(state.canvas).detach().float(),
1011
+ confidence_delta=next_confidence.detach().float() - state.confidence,
1012
+ entropy_delta=token_entropy.detach().float() - state.entropy,
1013
+ )
1014
+ remaining = torch.tensor(
1015
+ max_new_tokens, device=device, dtype=torch.long
1016
+ ).sub(committed)
1017
+ remaining_canvas = remaining[:, None].gt(canvas_positions)
1018
+ next_latent = self.latent_deliberation.observe_state(
1019
+ next_latent, output.heavy_hidden_state, output.working_state,
1020
+ remaining_canvas, state.head,
1021
+ )
1022
+ next_state = ModilifyMk2RollingState(
1023
+ canvas=next_canvas, confidence=next_confidence,
1024
+ entropy=token_entropy, age=state.age + 1,
1025
+ latent_state=next_latent,
1026
+
1027
+
1028
+ head=state.head,
1029
+ )
1030
+ normal_failure_rate = fused_commit_failure_rate(
1031
+ proposal_confidence, token_entropy,
1032
+ entropy_weight=self.config.commit_entropy_weight,
1033
+ confidence_power=self.config.commit_confidence_power,
1034
+ top_k=getattr(self.config, "commit_top_k", None),
1035
+ min_p=getattr(self.config, "commit_min_p", None),
1036
+ target_confidence=getattr(self.config, "commit_target_confidence", None),
1037
+ failure_budget=float(self.config.commit_failure_budget),
1038
+ vocab_size=self.config.text_config.vocab_size,
1039
+ )
1040
+ jump_failure_rate = fused_commit_failure_rate(
1041
+ greedy_confidence, token_entropy,
1042
+ entropy_weight=self.config.commit_entropy_weight,
1043
+ confidence_power=self.config.commit_confidence_power,
1044
+ top_k=getattr(self.config, "commit_top_k", None),
1045
+ min_p=getattr(self.config, "commit_min_p", None),
1046
+ target_confidence=getattr(self.config, "commit_target_confidence", None),
1047
+ failure_budget=float(self.config.commit_failure_budget),
1048
+ vocab_size=self.config.text_config.vocab_size,
1049
+ )
1050
+ previous_failure_rate = fused_commit_failure_rate(
1051
+ state.confidence, state.entropy,
1052
+ entropy_weight=self.config.commit_entropy_weight,
1053
+ confidence_power=self.config.commit_confidence_power,
1054
+ top_k=getattr(self.config, "commit_top_k", None),
1055
+ min_p=getattr(self.config, "commit_min_p", None),
1056
+ target_confidence=getattr(self.config, "commit_target_confidence", None),
1057
+ failure_budget=float(self.config.commit_failure_budget),
1058
+ vocab_size=self.config.text_config.vocab_size,
1059
+ )
1060
+ policy_decision = select_commit_lengths(
1061
+ sampled_token_ids=proposal,
1062
+ normal_failure_rate=normal_failure_rate,
1063
+ previous_failure_rate=previous_failure_rate,
1064
+ greedy_token_ids=greedy_proposal,
1065
+ jump_failure_rate=jump_failure_rate,
1066
+ ponder_steps=state.latent_state.ponder_steps,
1067
+ stagnation_steps=state.latent_state.stagnation_steps,
1068
+ active_rows=active_rows,
1069
+ remaining_lengths=remaining,
1070
+ failure_budget=self.config.commit_failure_budget,
1071
+ stop_token_id=stop_token_ids,
1072
+ max_ponder_steps=generation_config.max_ponder_steps,
1073
+ stagnation_threshold=generation_config.jump_on_no_progress_after,
1074
+ min_progress=generation_config.min_trajectory_progress,
1075
+ )
1076
+ normal_commit = policy_decision.normal_lengths
1077
+ policy_prefix_mask = canvas_positions.lt(normal_commit[:, None])
1078
+ next_ponder = policy_decision.ponder_steps
1079
+ next_stagnation = policy_decision.stagnation_steps
1080
+ commit_lengths = policy_decision.commit_lengths
1081
+ jump_rows = policy_decision.jump_rows
1082
+ jumps += jump_rows.long()
1083
+ forced_jump_tokens += torch.where(
1084
+ jump_rows, commit_lengths, torch.zeros_like(commit_lengths)
1085
+ )
1086
+ commit_positions = canvas_positions.lt(commit_lengths[:, None])
1087
+ if bool(jump_rows.any()):
1088
+ next_state = replace(
1089
+ next_state,
1090
+ canvas=torch.where(
1091
+ commit_positions & jump_rows[:, None],
1092
+ policy_decision.commit_token_ids,
1093
+ next_state.canvas,
1094
+ ),
1095
+ )
1096
+ next_state = replace(
1097
+ next_state,
1098
+ latent_state=replace(
1099
+ next_state.latent_state,
1100
+ ponder_steps=next_ponder,
1101
+ stagnation_steps=next_stagnation,
1102
+ ),
1103
+ )
1104
+ commit_token_ids = policy_decision.commit_token_ids
1105
+ before = committed.clone()
1106
+ write_rows = torch.arange(batch_size, device=device)[:, None].expand_as(
1107
+ commit_token_ids
1108
+ )
1109
+ write_positions = before[:, None] + canvas_positions
1110
+ generated[
1111
+ write_rows[commit_positions], write_positions[commit_positions]
1112
+ ] = commit_token_ids[commit_positions]
1113
+ if repetition_history is not None:
1114
+ _add_repetition_history(
1115
+ repetition_history,
1116
+ commit_token_ids,
1117
+ commit_positions,
1118
+ excluded_repetition_token_ids,
1119
+ )
1120
+
1121
+ commit_width = int(commit_lengths.max())
1122
+ if commit_width:
1123
+ block_mask = torch.arange(commit_width, device=device)[None, :].lt(
1124
+ commit_lengths[:, None]
1125
+ )
1126
+ committed_block = torch.where(
1127
+ block_mask,
1128
+ commit_token_ids[:, :commit_width],
1129
+ torch.full(
1130
+ (batch_size, commit_width),
1131
+ pad_token_id,
1132
+ device=device,
1133
+ dtype=input_ids.dtype,
1134
+ ),
1135
+ )
1136
+ block_positions = (
1137
+ logical_lengths[:, None]
1138
+ + torch.arange(commit_width, device=device)[None, :]
1139
+ ).to(torch.int32)
1140
+ block_positions = torch.where(
1141
+ block_mask, block_positions, torch.zeros_like(block_positions)
1142
+ )
1143
+ cache_attention_mask = torch.cat(
1144
+ (cache_attention_mask, block_mask), dim=-1
1145
+ )
1146
+ past_key_values = self.model.encoder(
1147
+ input_ids=committed_block,
1148
+ attention_mask=cache_attention_mask,
1149
+ past_key_values=past_key_values,
1150
+ position_ids=block_positions,
1151
+ ).past_key_values
1152
+ if streamer is not None:
1153
+ streamer.put(committed_block.cpu())
1154
+ committed_rows = commit_lengths.gt(0)
1155
+ shifts += committed_rows.long()
1156
+ if (
1157
+ output.working_state is None
1158
+ ):
1159
+ raise RuntimeError("Forward did not return working trajectory features.")
1160
+ next_state = self._write_committed_memory(
1161
+
1162
+ next_state=next_state,
1163
+ working_state=output.working_state,
1164
+
1165
+ heavy_hidden=output.heavy_hidden_state,
1166
+ commit_token_ids=commit_token_ids,
1167
+ commit_lengths=commit_lengths,
1168
+ prefix_lengths=logical_lengths,
1169
+ commit_reason=infer_commit_reason(
1170
+ commit_lengths,
1171
+ jump_rows=jump_rows,
1172
+ commit_token_ids=commit_token_ids,
1173
+ terminal_token_ids=stop_token_ids,
1174
+ ),
1175
+ )
1176
+ shifted = self._shift_state_rows(
1177
+ next_state,
1178
+ commit_lengths,
1179
+ sampler,
1180
+ generators=sampling_generators,
1181
+ remaining_lengths=remaining - commit_lengths,
1182
+ pad_token_id=pad_token_id,
1183
+ )
1184
+ retention_scores += committed_rows.float()
1185
+ state = shifted
1186
+ committed += commit_lengths
1187
+ logical_lengths += commit_lengths
1188
+
1189
+ turn_hits = (
1190
+ commit_token_ids.eq(turn_end) & commit_positions
1191
+ ).any(dim=-1)
1192
+ eos_hits = torch.zeros_like(turn_hits)
1193
+ for token_id in stop_token_ids:
1194
+ if token_id != turn_end:
1195
+ eos_hits |= (
1196
+ commit_token_ids.eq(token_id) & commit_positions
1197
+ ).any(dim=-1)
1198
+ stop_codes = torch.where(
1199
+ stop_codes.eq(0) & turn_hits,
1200
+ torch.ones_like(stop_codes),
1201
+ stop_codes,
1202
+ )
1203
+ stop_codes = torch.where(
1204
+ stop_codes.eq(0) & eos_hits,
1205
+ torch.full_like(stop_codes, 2),
1206
+ stop_codes,
1207
+ )
1208
+ stop_codes = torch.where(
1209
+ stop_codes.eq(0) & committed.ge(max_new_tokens),
1210
+ torch.full_like(stop_codes, 3),
1211
+ stop_codes,
1212
+ )
1213
+ if generation_config.max_denoising_steps is not None:
1214
+ stop_codes = torch.where(
1215
+ stop_codes.eq(0)
1216
+ & denoise_steps.ge(generation_config.max_denoising_steps),
1217
+ torch.full_like(stop_codes, 4),
1218
+ stop_codes,
1219
+ )
1220
+ stop_codes = torch.where(
1221
+ stop_codes.eq(0) & denoise_steps.ge(max_iterations),
1222
+ torch.full_like(stop_codes, 5),
1223
+ stop_codes,
1224
+ )
1225
+ if denoise_trace_callback is not None:
1226
+ denoise_trace_callback(build_denoise_trace_event(
1227
+ denoise_step=int(denoise_steps[0]),
1228
+ prefix_length=prefix_length, committed_before=int(before[0]),
1229
+ committed_after=int(committed[0]),
1230
+ no_progress_steps=int(next_stagnation[0]),
1231
+ policy_prefix_mask=policy_prefix_mask,
1232
+ commit_length=int(commit_lengths[0]),
1233
+ ponder_fallback=bool(jump_rows[0]), state=next_state, proposal=proposal,
1234
+ committed_token_ids=commit_token_ids[:, :commit_width],
1235
+ step_elapsed_seconds=time.perf_counter() - started,
1236
+ latent_residual_diagnostics=output.latent_residual_diagnostics,
1237
+ ))
1238
+ active_rows = stop_codes.eq(0)
1239
+
1240
+ output_width = int(committed.max())
1241
+ sequences = torch.cat((input_ids, generated[:, :output_width]), dim=-1)
1242
+ if streamer is not None:
1243
+ streamer.end()
1244
+ reason_names = {
1245
+ 1: "turn_end",
1246
+ 2: "eos",
1247
+ 3: "max_new_tokens",
1248
+ 4: "max_denoising_steps",
1249
+ 5: "episode_watchdog",
1250
+ }
1251
+ stop_reasons = tuple(
1252
+ reason_names.get(code, "unknown") for code in stop_codes.detach().cpu().tolist()
1253
+ )
1254
+ tokens_per_forward = committed.float() / denoise_steps.clamp_min(1).float()
1255
+ average_commit_len = committed.float() / shifts.clamp_min(1).float()
1256
+ latent_memory_norm = state.latent_state.memory_slots.float().norm(dim=-1).flatten(1).mean(dim=-1)
1257
+ state_retention_score = retention_scores / shifts.clamp_min(1).float()
1258
+
1259
+ def scalar_or_tensor(value: torch.Tensor, *, floating: bool = False):
1260
+ if batch_size > 1:
1261
+ return value
1262
+ item = value[0].item()
1263
+ return float(item) if floating else int(item)
1264
+
1265
+ return ModilifyMk2GenerationOutput(
1266
+ sequences=sequences,
1267
+ generated_lengths=committed.clone(),
1268
+ tokens_per_forward=tokens_per_forward,
1269
+ past_key_values=past_key_values,
1270
+ stop_reason=stop_reasons[0] if batch_size == 1 else stop_reasons,
1271
+ committed_tokens=scalar_or_tensor(committed),
1272
+ denoise_steps=scalar_or_tensor(denoise_steps),
1273
+ no_progress_steps=scalar_or_tensor(state.latent_state.stagnation_steps),
1274
+ jump_count=scalar_or_tensor(jumps),
1275
+ forced_jump_bad_count=scalar_or_tensor(forced_jump_tokens),
1276
+ heavy_forward_count=scalar_or_tensor(denoise_steps),
1277
+ latent_context_update_count=scalar_or_tensor(denoise_steps),
1278
+ average_commit_len=scalar_or_tensor(average_commit_len, floating=True),
1279
+ state_shift_count=scalar_or_tensor(shifts),
1280
+ latent_memory_norm=scalar_or_tensor(latent_memory_norm, floating=True),
1281
+ state_retention_score=scalar_or_tensor(state_retention_score, floating=True),
1282
+ )
1283
+
1284
+
1285
+ __all__ = [
1286
+ "ModilifyMk2GenerationConfig", "ModilifyMk2GenerationMixin", "ModilifyMk2GenerationOutput",
1287
+ "ModilifyMk2RollingState", "NoiseCanvasSampler",
1288
+ "build_denoise_trace_event",
1289
+ ]
1290
+
1291
+
1292
+ def deterministic_episode_iteration_bound(
1293
+ response_lengths: torch.LongTensor,
1294
+ *,
1295
+ max_ponder_steps: int,
1296
+ ) -> int:
1297
+ """Return a safe watchdog bound without assuming canvas-sized jumps.
1298
+
1299
+ Every active row must advance by at least one token no later than the
1300
+ configured no-progress threshold. Normal commits can also be only one
1301
+ token long, so a bound based on the number of canvases is not valid.
1302
+ """
1303
+ if response_lengths.numel() == 0:
1304
+ raise ValueError("`response_lengths` must be non-empty.")
1305
+ if max_ponder_steps <= 0:
1306
+ raise ValueError("`max_ponder_steps` must be positive.")
1307
+ return max(1, int(response_lengths.max()) * max_ponder_steps)
inference.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run the published Transformers model locally or from Hugging Face."""
2
+ from __future__ import annotations
3
+ import argparse
4
+ from pathlib import Path
5
+ import torch
6
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer
7
+
8
+
9
+ def main() -> None:
10
+ parser = argparse.ArgumentParser(description=__doc__)
11
+ parser.add_argument("--model", default=str(Path(__file__).resolve().parent))
12
+ parser.add_argument("--prompt", default="你好,请介绍一下自己。")
13
+ parser.add_argument("--device", choices=("auto", "cpu", "cuda", "mps"), default="auto")
14
+ parser.add_argument("--max-new-tokens", type=int, default=256)
15
+ parser.add_argument("--max-denoising-steps", type=int, default=256)
16
+ parser.add_argument("--seed", type=int, default=42)
17
+ parser.add_argument("--no-thinking", action="store_true")
18
+ parser.add_argument("--stream", action="store_true")
19
+ args = parser.parse_args()
20
+ device = args.device
21
+ if device == "auto":
22
+ device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
23
+ tokenizer = AutoTokenizer.from_pretrained(args.model, trust_remote_code=True)
24
+ model = AutoModelForCausalLM.from_pretrained(
25
+ args.model, trust_remote_code=True, dtype="auto", device_map=device,
26
+ attn_implementation="sdpa",
27
+ ).eval()
28
+ inputs = tokenizer.apply_chat_template(
29
+ [{"role": "user", "content": args.prompt}],
30
+ enable_thinking=not args.no_thinking, add_generation_prompt=True,
31
+ return_tensors="pt", return_dict=True,
32
+ ).to(model.device)
33
+ streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) if args.stream else None
34
+ with torch.inference_mode():
35
+ output = model.generate(**inputs, max_new_tokens=args.max_new_tokens,
36
+ max_denoising_steps=args.max_denoising_steps,
37
+ seed=args.seed, streamer=streamer)
38
+ if streamer is None:
39
+ print(tokenizer.decode(output.sequences[0, inputs.input_ids.shape[1]:], skip_special_tokens=True))
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
latent_deliberation.py ADDED
@@ -0,0 +1,827 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Schema25 Torch GDN2 trajectory state, processor, and memory readers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass, replace
7
+ import weakref
8
+ import math
9
+
10
+ import torch
11
+ from torch import nn
12
+ from torch.nn import functional as F
13
+
14
+ from .gdn2_trajectory import GDN2TrajectoryMemory, GDN2TrajectoryState
15
+
16
+
17
+ COMMIT_REASON_NONE = 0
18
+ COMMIT_REASON_NORMAL = 1
19
+ COMMIT_REASON_FORCED_JUMP = 2
20
+ COMMIT_REASON_TERMINAL = 3
21
+ COMMIT_REASON_FALLBACK = 4
22
+ COMMIT_REASON_TRAINING_RANDOM = 5
23
+ COMMIT_REASON_COUNT = 6
24
+
25
+
26
+ def _sdpa_mask_value(dtype: torch.dtype) -> float:
27
+ """Additive SDPA mask that stays finite on MPS fp16/bf16."""
28
+
29
+ if dtype in (torch.float16, torch.bfloat16):
30
+ return -1.0e4
31
+ return -1.0e9
32
+
33
+
34
+ def _fp32_scaled_dot_product_attention(
35
+ query: torch.Tensor,
36
+ key: torch.Tensor,
37
+ value: torch.Tensor,
38
+ *,
39
+ attn_mask: torch.Tensor | None = None,
40
+ ) -> torch.Tensor:
41
+ """Run latent-memory attention reductions in FP32, then restore dtype.
42
+
43
+ These attention maps are small compared with the frozen decoder, while
44
+ their outputs feed recurrent trajectory and persistent-memory paths. A
45
+ BF16 reduction error therefore compounds across denoise/commit steps and
46
+ is much more expensive than the modest FP32 workspace.
47
+ """
48
+
49
+ output_dtype = query.dtype
50
+ stable_mask = attn_mask
51
+ if stable_mask is not None and stable_mask.is_floating_point():
52
+ stable_mask = stable_mask.float()
53
+ query_fp32 = query.float()
54
+ key_fp32 = key.float()
55
+ use_batched_mm = query.ndim == 4 and key.ndim == 4 and value.ndim == 4
56
+ if use_batched_mm:
57
+ query_length = int(query.shape[-2])
58
+ key_length = int(key.shape[-2])
59
+ scores = torch.bmm(
60
+ query_fp32.reshape(-1, query_length, query.shape[-1]),
61
+ key_fp32.reshape(-1, key_length, key.shape[-1]).transpose(1, 2),
62
+ ).reshape(*query.shape[:-2], query_length, key_length)
63
+ else:
64
+ scores = torch.matmul(query_fp32, key_fp32.transpose(-2, -1))
65
+ scores = scores / math.sqrt(max(query.shape[-1], 1))
66
+ if stable_mask is not None:
67
+ if stable_mask.dtype == torch.bool:
68
+ scores = scores.masked_fill(~stable_mask, _sdpa_mask_value(torch.float32))
69
+ else:
70
+ scores = scores + stable_mask
71
+ probabilities = torch.softmax(scores, dim=-1)
72
+ # All-masked rows are uniform under a finite mask, but keep a NaN
73
+ # barrier for any remaining -inf path that MPS softmax cannot invert.
74
+ probabilities = torch.nan_to_num(probabilities, nan=0.0)
75
+ if use_batched_mm:
76
+ output = torch.bmm(
77
+ probabilities.reshape(-1, query.shape[-2], key.shape[-2]),
78
+ value.float().reshape(-1, key.shape[-2], value.shape[-1]),
79
+ ).reshape(*query.shape[:-2], query.shape[-2], value.shape[-1])
80
+ else:
81
+ output = torch.matmul(probabilities, value.float())
82
+ return output.to(dtype=output_dtype)
83
+
84
+
85
+ @dataclass
86
+ class LatentDeliberationState:
87
+ """Persistent slots plus per-canvas trajectory clocks. No token latents."""
88
+
89
+ memory_slots: torch.Tensor
90
+ confidence: torch.Tensor
91
+ entropy: torch.Tensor
92
+ age: torch.Tensor
93
+ token_changed: torch.Tensor
94
+ confidence_delta: torch.Tensor
95
+ entropy_delta: torch.Tensor
96
+ ponder_steps: torch.Tensor
97
+ stagnation_steps: torch.Tensor
98
+ gdn2: GDN2TrajectoryState | None = None
99
+
100
+ @classmethod
101
+ def empty(
102
+ cls,
103
+ *,
104
+ batch_size: int,
105
+ canvas_length: int,
106
+ latent_dim: int,
107
+ memory_slots: int,
108
+ device: torch.device,
109
+ dtype: torch.dtype,
110
+ enable_gdn2: bool = False,
111
+ ) -> "LatentDeliberationState":
112
+ persistent = (
113
+ torch.zeros(batch_size, 16, 128, 128, device=device, dtype=torch.float32)
114
+ if enable_gdn2 else None
115
+ )
116
+ return cls(
117
+ memory_slots=(persistent if persistent is not None else torch.zeros(
118
+ batch_size, memory_slots, latent_dim, device=device, dtype=dtype
119
+ )),
120
+ confidence=torch.zeros(
121
+ batch_size, canvas_length, device=device, dtype=torch.float32
122
+ ),
123
+ entropy=torch.zeros(
124
+ batch_size, canvas_length, device=device, dtype=torch.float32
125
+ ),
126
+ age=torch.zeros(
127
+ batch_size, canvas_length, device=device, dtype=torch.int32
128
+ ),
129
+ token_changed=torch.zeros(
130
+ batch_size, canvas_length, device=device, dtype=torch.float32
131
+ ),
132
+ confidence_delta=torch.zeros(
133
+ batch_size, canvas_length, device=device, dtype=torch.float32
134
+ ),
135
+ entropy_delta=torch.zeros(
136
+ batch_size, canvas_length, device=device, dtype=torch.float32
137
+ ),
138
+ ponder_steps=torch.zeros(batch_size, device=device, dtype=torch.int32),
139
+ stagnation_steps=torch.zeros(batch_size, device=device, dtype=torch.int32),
140
+ gdn2=GDN2TrajectoryState(
141
+ cells=torch.zeros(batch_size, canvas_length, 16, 64, 64,
142
+ device=device, dtype=torch.float32),
143
+ row=torch.zeros(batch_size, 16, 64, 64,
144
+ device=device, dtype=torch.float32),
145
+ persistent=persistent,
146
+ seen=torch.zeros(batch_size, canvas_length,
147
+ device=device, dtype=torch.bool),
148
+ ) if enable_gdn2 else None,
149
+ )
150
+
151
+ def detach(self) -> "LatentDeliberationState":
152
+ return LatentDeliberationState(
153
+ memory_slots=self.memory_slots.detach(),
154
+ confidence=self.confidence.detach(),
155
+ entropy=self.entropy.detach(),
156
+ age=self.age.detach(),
157
+ token_changed=self.token_changed.detach(),
158
+ confidence_delta=self.confidence_delta.detach(),
159
+ entropy_delta=self.entropy_delta.detach(),
160
+ ponder_steps=self.ponder_steps.detach(),
161
+ stagnation_steps=self.stagnation_steps.detach(),
162
+ gdn2=None if self.gdn2 is None else self.gdn2.detach(),
163
+ )
164
+
165
+ def shift(
166
+ self, committed: int, *, entropy_fill_value: float = 0.0
167
+ ) -> "LatentDeliberationState":
168
+ canvas_length = self.confidence.shape[1]
169
+ if not 0 <= committed <= canvas_length:
170
+ raise ValueError("`committed` must be in [0, canvas_length].")
171
+ if committed == 0:
172
+ return self.detach()
173
+
174
+ def shifted(tensor: torch.Tensor, fill_value: float | int = 0) -> torch.Tensor:
175
+ result = torch.full_like(tensor, fill_value)
176
+ if committed < canvas_length:
177
+ result[:, : canvas_length - committed] = tensor[:, committed:]
178
+ return result
179
+
180
+ return LatentDeliberationState(
181
+ memory_slots=self.memory_slots.clone(),
182
+ confidence=shifted(self.confidence),
183
+ entropy=shifted(self.entropy, entropy_fill_value),
184
+ age=shifted(self.age),
185
+ token_changed=shifted(self.token_changed),
186
+ confidence_delta=shifted(self.confidence_delta),
187
+ entropy_delta=shifted(self.entropy_delta),
188
+ ponder_steps=torch.zeros_like(self.ponder_steps),
189
+ stagnation_steps=torch.zeros_like(self.stagnation_steps),
190
+ gdn2=None if self.gdn2 is None else self.gdn2.shift(
191
+ torch.full((self.confidence.shape[0],), committed,
192
+ device=self.confidence.device, dtype=torch.long)
193
+ ),
194
+ )
195
+
196
+ def advance_ring(
197
+ self,
198
+ commit_lengths: torch.Tensor | int,
199
+ head: torch.Tensor | int,
200
+ *,
201
+ entropy_fill_value: float = 0.0,
202
+ reset_clocks_mask: torch.BoolTensor | None = None,
203
+ ) -> "LatentDeliberationState":
204
+ """Advance ring buffer state without physical tensor shifts, invalidating overwritten slots."""
205
+ batch, canvas_length = self.confidence.shape
206
+ device = self.confidence.device
207
+ if isinstance(commit_lengths, int):
208
+ lengths = torch.full((batch, 1), commit_lengths, device=device, dtype=torch.long)
209
+ else:
210
+ lengths = commit_lengths.to(device=device, dtype=torch.long).view(batch, 1)
211
+ if isinstance(head, int):
212
+ head_tensor = torch.full((batch, 1), head, device=device, dtype=torch.long)
213
+ else:
214
+ head_tensor = head.to(device=device, dtype=torch.long).view(batch, 1)
215
+ positions = torch.arange(canvas_length, device=device)[None, :]
216
+ old_offsets = (positions - head_tensor) % canvas_length
217
+ overwritten = old_offsets < lengths
218
+ fill_entropy = torch.as_tensor(entropy_fill_value, device=device, dtype=self.entropy.dtype)
219
+ committed = lengths.squeeze(1).gt(0) if reset_clocks_mask is None else reset_clocks_mask
220
+ return LatentDeliberationState(
221
+ memory_slots=self.memory_slots.clone(),
222
+ confidence=torch.where(overwritten, torch.zeros_like(self.confidence), self.confidence),
223
+ entropy=torch.where(overwritten, fill_entropy, self.entropy),
224
+ age=torch.where(overwritten, torch.zeros_like(self.age), self.age),
225
+ token_changed=torch.where(overwritten, torch.zeros_like(self.token_changed), self.token_changed),
226
+ confidence_delta=torch.where(overwritten, torch.zeros_like(self.confidence_delta), self.confidence_delta),
227
+ entropy_delta=torch.where(overwritten, torch.zeros_like(self.entropy_delta), self.entropy_delta),
228
+ ponder_steps=torch.where(committed, 0, self.ponder_steps).to(torch.int32),
229
+ stagnation_steps=torch.where(committed, 0, self.stagnation_steps).to(torch.int32),
230
+ gdn2=None if self.gdn2 is None else self.gdn2.clear_refill(
231
+ lengths.squeeze(1), head_tensor.squeeze(1)
232
+ ),
233
+ )
234
+
235
+
236
+ @dataclass
237
+ class LatentProcessorOutput:
238
+ context: torch.Tensor
239
+ working_state: torch.Tensor
240
+ state: LatentDeliberationState
241
+
242
+
243
+ def advance_trajectory_clocks(
244
+ ponder_steps: torch.Tensor,
245
+ stagnation_steps: torch.Tensor,
246
+ *,
247
+ commit_lengths: torch.LongTensor,
248
+ active_rows: torch.BoolTensor,
249
+ progress_scores: torch.Tensor | None = None,
250
+ min_progress: float = 0.0,
251
+ ) -> tuple[torch.IntTensor, torch.IntTensor]:
252
+ """Advance useful-ponder and stagnation clocks for each row."""
253
+
254
+ if min_progress < 0:
255
+ raise ValueError("`min_progress` must be non-negative.")
256
+ if not (
257
+ ponder_steps.shape == stagnation_steps.shape == commit_lengths.shape
258
+ == active_rows.shape
259
+ ):
260
+ raise ValueError("Trajectory clock inputs must share shape [batch].")
261
+ committed = commit_lengths.gt(0)
262
+ waiting = active_rows & ~committed
263
+ next_ponder = torch.where(
264
+ committed, torch.zeros_like(ponder_steps), ponder_steps + waiting.to(torch.int32)
265
+ )
266
+ next_stagnation = torch.where(
267
+ committed,
268
+ torch.zeros_like(stagnation_steps),
269
+ stagnation_steps + waiting.to(torch.int32),
270
+ )
271
+ return next_ponder.to(torch.int32), next_stagnation.to(torch.int32)
272
+
273
+
274
+ def should_force_trajectory_jump(
275
+ stagnation_steps: torch.Tensor,
276
+ *,
277
+ progress_scores: torch.Tensor | None = None,
278
+ min_progress: float = 0.0,
279
+ stagnation_threshold: int,
280
+ ponder_steps: torch.Tensor | None = None,
281
+ max_ponder_steps: int | None = None,
282
+ ) -> torch.BoolTensor:
283
+ jump = stagnation_steps.ge(stagnation_threshold)
284
+ if progress_scores is not None:
285
+ jump = jump & progress_scores.le(float(min_progress))
286
+ if ponder_steps is not None and max_ponder_steps is not None and max_ponder_steps > 0:
287
+ jump = jump | ponder_steps.ge(max_ponder_steps)
288
+ return jump.to(torch.bool)
289
+
290
+
291
+ class _RMSNorm(nn.Module):
292
+ def __init__(self, dim: int, eps: float = 1.0e-6) -> None:
293
+ super().__init__()
294
+ self.weight = nn.Parameter(torch.ones(dim))
295
+ self.eps = eps
296
+
297
+ def forward(self, hidden: torch.Tensor) -> torch.Tensor:
298
+ rms = hidden.float().square().mean(dim=-1, keepdim=True).add(self.eps).rsqrt()
299
+ return (hidden.float() * rms * self.weight.float()).to(dtype=hidden.dtype)
300
+
301
+
302
+ class _SwiGLU(nn.Module):
303
+ def __init__(self, dim: int, hidden: int) -> None:
304
+ super().__init__()
305
+ self.gate = nn.Linear(dim, hidden, bias=False)
306
+ self.up = nn.Linear(dim, hidden, bias=False)
307
+ self.down = nn.Linear(hidden, dim, bias=False)
308
+
309
+ def forward(self, hidden: torch.Tensor) -> torch.Tensor:
310
+ return self.down(F.silu(self.gate(hidden)) * self.up(hidden))
311
+
312
+
313
+ class _RankAttention(nn.Module):
314
+ """Sequence attention in a rank-``kv_rank`` subspace, then map back to ``dim``."""
315
+
316
+ def __init__(self, dim: int, num_heads: int, kv_rank: int) -> None:
317
+ super().__init__()
318
+ if kv_rank % num_heads:
319
+ raise ValueError("`kv_rank` must be divisible by `num_heads`.")
320
+ self.num_heads = num_heads
321
+ self.kv_rank = kv_rank
322
+ self.head_dim = kv_rank // num_heads
323
+ self.q_proj = nn.Linear(dim, kv_rank, bias=False)
324
+ self.k_proj = nn.Linear(dim, kv_rank, bias=False)
325
+ self.v_proj = nn.Linear(dim, kv_rank, bias=False)
326
+ self.o_proj = nn.Linear(kv_rank, dim, bias=False)
327
+ self.q_norm = _RMSNorm(dim)
328
+ self.k_norm = _RMSNorm(dim)
329
+
330
+ def forward(
331
+ self,
332
+ query: torch.Tensor,
333
+ keys: torch.Tensor,
334
+ values: torch.Tensor,
335
+ attn_mask: torch.Tensor | None = None,
336
+ ) -> torch.Tensor:
337
+ batch, queries, _dim = query.shape
338
+ key_len = keys.shape[1]
339
+ heads = self.num_heads
340
+ head_dim = self.head_dim
341
+ query = self.q_proj(self.q_norm(query)).view(batch, queries, heads, head_dim).transpose(1, 2)
342
+ keys = self.k_proj(self.k_norm(keys)).view(batch, key_len, heads, head_dim).transpose(1, 2)
343
+ values = self.v_proj(values).view(batch, key_len, heads, head_dim).transpose(1, 2)
344
+ mask = attn_mask
345
+ if mask is not None and mask.ndim == 2:
346
+ mask = mask.view(1, 1, queries, key_len)
347
+ elif mask is not None and mask.ndim == 3:
348
+ mask = mask.unsqueeze(1)
349
+ context = _fp32_scaled_dot_product_attention(
350
+ query, keys, values, attn_mask=mask
351
+ )
352
+ context = context.transpose(1, 2).reshape(batch, queries, self.kv_rank)
353
+ return self.o_proj(context)
354
+
355
+
356
+ class DecoderMemoryBus(nn.Module):
357
+ """Sidecar readers on full-attention decoder layers.
358
+
359
+ ``alpha`` starts at 0 so the residual is zero. Scale with ``tanh(alpha)``
360
+ rather than a hard ``where(alpha == 0)`` so the gate stays differentiable.
361
+ ``o_proj`` is *not* zeroed: that pair was a dead-gradient product.
362
+ """
363
+
364
+ def __init__(
365
+ self,
366
+ hidden_size: int,
367
+ num_heads: int,
368
+ num_readers: int,
369
+ memory_dim: int,
370
+ kv_rank: int,
371
+ *,
372
+ relative_bias: bool = False,
373
+ address_with_identity: bool = False,
374
+ max_relative_span: int = 256,
375
+ ) -> None:
376
+ super().__init__()
377
+ if num_readers > 0:
378
+ if kv_rank % num_heads:
379
+ raise ValueError("Memory bus rank must be divisible by heads.")
380
+ if hidden_size % num_heads:
381
+ raise ValueError("Memory bus hidden size must be divisible by heads.")
382
+ self.hidden_size = hidden_size
383
+ self.num_heads = num_heads
384
+ self.num_readers = num_readers
385
+ self.kv_rank = kv_rank
386
+ self.head_dim = kv_rank // num_heads if num_heads else kv_rank
387
+ self.relative_bias = relative_bias
388
+ self.address_with_identity = address_with_identity
389
+ self.memory_norm = _RMSNorm(memory_dim)
390
+ self.address_norm = _RMSNorm(memory_dim)
391
+ self.memory_to_hidden = (
392
+ nn.Identity()
393
+ if memory_dim == hidden_size
394
+ else nn.Linear(memory_dim, hidden_size, bias=False)
395
+ )
396
+ self.k_proj = nn.Linear(hidden_size, kv_rank, bias=False)
397
+ self.v_proj = nn.Linear(hidden_size, kv_rank, bias=False)
398
+ self.q_norm = _RMSNorm(hidden_size)
399
+ self.q_proj = nn.ModuleList(
400
+ [nn.Linear(hidden_size, kv_rank, bias=False) for _ in range(num_readers)]
401
+ )
402
+ self.o_proj = nn.ModuleList(
403
+ [nn.Linear(kv_rank, hidden_size, bias=False) for _ in range(num_readers)]
404
+ )
405
+ self.alpha = nn.Parameter(torch.zeros(max(num_readers, 1), max(num_heads, 1)))
406
+ span = max(2 * max_relative_span - 1, 1)
407
+ self.rel_bias = nn.Parameter(torch.zeros(max(num_heads, 1), span))
408
+ self.max_relative_span = max_relative_span
409
+ self.enabled = False
410
+ self.freeze()
411
+
412
+ def freeze(self) -> None:
413
+ self.enabled = False
414
+ for parameter in self.parameters():
415
+ parameter.requires_grad_(False)
416
+
417
+ def unfreeze(self) -> None:
418
+ if self.num_readers <= 0:
419
+ return
420
+ self.enabled = True
421
+ for parameter in self.parameters():
422
+ parameter.requires_grad_(True)
423
+
424
+ def prepare_kv(
425
+ self,
426
+ memory: torch.Tensor,
427
+ slot_identity: torch.Tensor | None = None,
428
+ ) -> tuple[torch.Tensor, torch.Tensor] | None:
429
+ if self.num_readers <= 0:
430
+ return None
431
+ if self.training and not self.enabled:
432
+ return None
433
+ if self.address_with_identity:
434
+ if slot_identity is None:
435
+ raise ValueError("Persistent bus requires slot identity on keys.")
436
+ mapped_keys = self.memory_to_hidden(self.address_norm(memory + slot_identity))
437
+ mapped_values = self.memory_to_hidden(self.memory_norm(memory))
438
+ else:
439
+ mapped_keys = mapped_values = self.memory_to_hidden(self.memory_norm(memory))
440
+ batch, slots, _dim = mapped_keys.shape
441
+ heads = self.num_heads
442
+ head_dim = self.head_dim
443
+ keys = self.k_proj(mapped_keys).view(batch, slots, heads, head_dim).transpose(1, 2)
444
+ values = self.v_proj(mapped_values).view(batch, slots, heads, head_dim).transpose(1, 2)
445
+ return keys, values
446
+
447
+ def _relative_mask(
448
+ self,
449
+ queries: int,
450
+ keys: int,
451
+ device: torch.device,
452
+ dtype: torch.dtype,
453
+ positions: torch.Tensor | None = None,
454
+ ) -> torch.Tensor | None:
455
+ if not self.relative_bias:
456
+ return None
457
+ if positions is not None:
458
+ if positions.shape[1] != queries or queries != keys:
459
+ raise ValueError("Working memory positions must match both canvas axes.")
460
+ relative = (
461
+ positions[:, :, None] - positions[:, None, :] + (keys - 1)
462
+ ).clamp(0, self.rel_bias.shape[1] - 1)
463
+ return self.rel_bias[:, relative].permute(1, 0, 2, 3).to(dtype=dtype)
464
+ q = torch.arange(queries, device=device)
465
+ k = torch.arange(keys, device=device)
466
+ rel = (q[:, None] - k[None, :] + (keys - 1)).clamp(0, self.rel_bias.shape[1] - 1)
467
+ return self.rel_bias[:, rel].to(dtype=dtype)
468
+
469
+ def read(
470
+ self,
471
+ hidden: torch.Tensor,
472
+ reader_index: int,
473
+ keys: torch.Tensor,
474
+ values: torch.Tensor,
475
+ positions: torch.Tensor | None = None,
476
+ *, key_seen: torch.Tensor | None = None,
477
+ ) -> torch.Tensor:
478
+ batch, canvas, _dim = hidden.shape
479
+ heads = self.num_heads
480
+ head_dim = self.head_dim
481
+ query = self.q_proj[reader_index](self.q_norm(hidden))
482
+ query = query.view(batch, canvas, heads, head_dim).transpose(1, 2)
483
+ bias = self._relative_mask(
484
+ canvas, keys.shape[2], hidden.device, query.dtype, positions
485
+ )
486
+ if bias is not None and bias.ndim == 3:
487
+ bias = bias.unsqueeze(0)
488
+ if key_seen is not None:
489
+ key_mask = torch.zeros((batch, 1, 1, keys.shape[2]), device=hidden.device, dtype=torch.float32)
490
+ key_mask = key_mask.masked_fill(~key_seen[:, None, None, :], -1e9)
491
+ bias = key_mask if bias is None else bias.float() + key_mask
492
+ context = _fp32_scaled_dot_product_attention(
493
+ query, keys, values, attn_mask=bias
494
+ )
495
+ if key_seen is not None:
496
+ context = torch.where(key_seen.any(-1)[:, None, None, None], context, torch.zeros_like(context))
497
+ scale = torch.tanh(self.alpha[reader_index]).to(dtype=hidden.dtype).view(1, heads, 1, 1)
498
+ context = context * scale
499
+ context = context.transpose(1, 2).reshape(batch, canvas, self.kv_rank)
500
+ return hidden + self.o_proj[reader_index](context)
501
+
502
+ @torch.no_grad()
503
+ def reset_identity_parameters(self) -> None:
504
+ self.alpha.zero_()
505
+ self.rel_bias.zero_()
506
+
507
+
508
+ def slice_latent_state(
509
+ state: LatentDeliberationState, rows: slice | torch.Tensor
510
+ ) -> LatentDeliberationState:
511
+ return LatentDeliberationState(
512
+ memory_slots=state.memory_slots[rows],
513
+ confidence=state.confidence[rows],
514
+ entropy=state.entropy[rows],
515
+ age=state.age[rows],
516
+ token_changed=state.token_changed[rows],
517
+ confidence_delta=state.confidence_delta[rows],
518
+ entropy_delta=state.entropy_delta[rows],
519
+ ponder_steps=state.ponder_steps[rows],
520
+ stagnation_steps=state.stagnation_steps[rows],
521
+ gdn2=None if state.gdn2 is None else GDN2TrajectoryState(
522
+ cells=state.gdn2.cells[rows], row=state.gdn2.row[rows],
523
+ persistent=state.gdn2.persistent[rows], seen=state.gdn2.seen[rows],
524
+ ),
525
+ )
526
+
527
+
528
+ def cat_latent_states(states: Sequence[LatentDeliberationState]) -> LatentDeliberationState:
529
+ def cat(name: str) -> torch.Tensor:
530
+ return torch.cat([getattr(state, name) for state in states], dim=0)
531
+
532
+ return LatentDeliberationState(
533
+ memory_slots=cat("memory_slots"),
534
+ confidence=cat("confidence"),
535
+ entropy=cat("entropy"),
536
+ age=cat("age"),
537
+ token_changed=cat("token_changed"),
538
+ confidence_delta=cat("confidence_delta"),
539
+ entropy_delta=cat("entropy_delta"),
540
+ ponder_steps=cat("ponder_steps"),
541
+ stagnation_steps=cat("stagnation_steps"),
542
+ gdn2=None if states[0].gdn2 is None else GDN2TrajectoryState(
543
+ cells=torch.cat([state.gdn2.cells for state in states], dim=0),
544
+ row=torch.cat([state.gdn2.row for state in states], dim=0),
545
+ persistent=torch.cat([state.gdn2.persistent for state in states], dim=0),
546
+ seen=torch.cat([state.gdn2.seen for state in states], dim=0),
547
+ ),
548
+ )
549
+
550
+
551
+ def infer_commit_reason(
552
+ commit_lengths: torch.Tensor,
553
+ *,
554
+ jump_rows: torch.Tensor | None = None,
555
+ commit_token_ids: torch.Tensor | None = None,
556
+ terminal_token_ids: Sequence[int] = (),
557
+ training_random: bool = False,
558
+ ) -> torch.Tensor:
559
+ """Return per-row commit-reason codes. No hard skip; writer sees the label."""
560
+
561
+ reasons = torch.full(
562
+ commit_lengths.shape,
563
+ COMMIT_REASON_NONE,
564
+ device=commit_lengths.device,
565
+ dtype=torch.long,
566
+ )
567
+ committed = commit_lengths.gt(0)
568
+ default = (
569
+ COMMIT_REASON_TRAINING_RANDOM if training_random else COMMIT_REASON_NORMAL
570
+ )
571
+ reasons = torch.where(committed, torch.full_like(reasons, default), reasons)
572
+ if jump_rows is not None:
573
+ reasons = torch.where(
574
+ committed & jump_rows.to(dtype=torch.bool),
575
+ torch.full_like(reasons, COMMIT_REASON_FORCED_JUMP),
576
+ reasons,
577
+ )
578
+ if commit_token_ids is not None and terminal_token_ids:
579
+ positions = torch.arange(
580
+ commit_token_ids.shape[1], device=commit_token_ids.device
581
+ )[None, :]
582
+ selected = positions.lt(commit_lengths[:, None])
583
+ terminal = torch.zeros_like(committed)
584
+ for token_id in terminal_token_ids:
585
+ terminal |= (commit_token_ids.eq(int(token_id)) & selected).any(dim=-1)
586
+ reasons = torch.where(
587
+ committed & terminal,
588
+ torch.full_like(reasons, COMMIT_REASON_TERMINAL),
589
+ reasons,
590
+ )
591
+ return reasons
592
+
593
+
594
+ class _CanvasBlock(nn.Module):
595
+ def __init__(self, width: int, heads: int, rank: int, ffn: int,
596
+ window: int, global_attention: bool) -> None:
597
+ super().__init__()
598
+ self.norm = _RMSNorm(width)
599
+ self.attn = _RankAttention(width, heads, rank)
600
+ self.ff_norm = _RMSNorm(width)
601
+ self.ff = _SwiGLU(width, ffn)
602
+ self.window = window
603
+ self.global_attention = global_attention
604
+
605
+ def forward(self, hidden: torch.Tensor, seen: torch.Tensor,
606
+ offsets: torch.Tensor) -> torch.Tensor:
607
+ allowed = seen[:, None, :].expand(-1, hidden.shape[1], -1)
608
+ if not self.global_attention:
609
+ allowed = allowed & ((offsets[:, :, None] - offsets[:, None, :]).abs() < self.window)
610
+ additive = torch.zeros(allowed.shape, device=hidden.device, dtype=torch.float32)
611
+ additive = additive.masked_fill(~allowed, -1e9)
612
+ normed = self.norm(hidden)
613
+ hidden = hidden + self.attn(normed, normed, normed, attn_mask=additive)
614
+ hidden = hidden + self.ff(self.ff_norm(hidden))
615
+ return torch.where(seen[..., None], hidden, torch.zeros_like(hidden))
616
+
617
+
618
+ class _WorkingBus(DecoderMemoryBus):
619
+ def prepare_kv(self, memory: tuple[torch.Tensor, torch.Tensor],
620
+ slot_identity: torch.Tensor | None = None):
621
+ del slot_identity
622
+ working, seen = memory
623
+ pair = super().prepare_kv(working)
624
+ return None if pair is None else (*pair, seen)
625
+
626
+ def read(self, hidden: torch.Tensor, reader_index: int,
627
+ keys: torch.Tensor, values: torch.Tensor, seen: torch.Tensor,
628
+ positions: torch.Tensor | None = None) -> torch.Tensor:
629
+ written = super().read(hidden, reader_index, keys, values, positions, key_seen=seen)
630
+ return torch.where(seen[..., None], written, hidden)
631
+
632
+
633
+ class _PersistentBus(nn.Module):
634
+ def __init__(self, memory: nn.Module, readers: int) -> None:
635
+ super().__init__()
636
+ object.__setattr__(self, "_memory_ref", weakref.ref(memory))
637
+ self.num_readers = readers
638
+ self.alpha = nn.Parameter(torch.zeros(max(readers, 1), 1))
639
+ self.enabled = False
640
+ self.freeze()
641
+
642
+ def freeze(self) -> None:
643
+ self.enabled = False
644
+ self.alpha.requires_grad_(False)
645
+
646
+ def unfreeze(self) -> None:
647
+ self.enabled = self.num_readers > 0
648
+ self.alpha.requires_grad_(self.enabled)
649
+
650
+ def reset_identity_parameters(self) -> None:
651
+ with torch.no_grad():
652
+ self.alpha.zero_()
653
+
654
+ def prepare_kv(self, memory: torch.Tensor,
655
+ seen: torch.Tensor | None = None):
656
+ if self.num_readers <= 0 or (self.training and not self.enabled):
657
+ return None
658
+ return memory, seen
659
+
660
+ def read(self, hidden: torch.Tensor, reader_index: int,
661
+ memory: torch.Tensor, seen: torch.Tensor | None) -> torch.Tensor:
662
+ delta = self._memory_ref().read_shared(memory, hidden)
663
+ if seen is not None:
664
+ delta = delta * seen[..., None].to(delta.dtype)
665
+ return hidden + torch.tanh(self.alpha[reader_index]).to(hidden.dtype) * delta
666
+
667
+
668
+ class LatentDeliberationTransformer(nn.Module):
669
+ def __init__(self, *, hidden_size: int,
670
+ latent_dim: int = 2816, ffn_dim: int = 7168,
671
+ num_layers: int = 4,
672
+ num_heads: int = 16, local_attention_window: int = 128,
673
+ tape_probes: int = 4,
674
+
675
+
676
+ history_kv_rank: int = 1024, num_memory_readers: int = 0,
677
+ num_working_readers: int | None = None,
678
+ num_persistent_readers: int | None = None,
679
+ working_last_block_global: bool = True,
680
+
681
+ commit_sequence_dim: int | None = None,
682
+
683
+ max_canvas_length: int = 256) -> None:
684
+ super().__init__()
685
+ if hidden_size != latent_dim:
686
+ raise ValueError("Schema25 GDN2 requires hidden_size == latent_dim.")
687
+ self.hidden_size = hidden_size
688
+ self.latent_dim = latent_dim
689
+ self.tape_probes = tape_probes
690
+ self.packet_dim = int(commit_sequence_dim or history_kv_rank)
691
+ if self.packet_dim % num_heads:
692
+ raise ValueError("Commit packet rank must be divisible by attention heads.")
693
+ self.trajectory = GDN2TrajectoryMemory(
694
+ hidden_size, probes=tape_probes, persistent_observation_dim=self.packet_dim,
695
+ )
696
+ self.blocks = nn.ModuleList([
697
+ _CanvasBlock(hidden_size, num_heads, history_kv_rank, ffn_dim,
698
+ local_attention_window,
699
+ bool(working_last_block_global and i == num_layers - 1))
700
+ for i in range(num_layers)
701
+ ])
702
+ self.output_norm = _RMSNorm(hidden_size)
703
+ readers = num_memory_readers if num_working_readers is None else num_working_readers
704
+ persistent_readers = (
705
+ num_memory_readers if num_persistent_readers is None else num_persistent_readers
706
+ )
707
+ bus_rank = history_kv_rank if history_kv_rank % num_heads == 0 else num_heads
708
+ self.working_memory_bus = _WorkingBus(
709
+ hidden_size, num_heads, readers, hidden_size, bus_rank,
710
+ relative_bias=True, max_relative_span=max_canvas_length,
711
+ )
712
+ self.persistent_memory_bus = _PersistentBus(
713
+ self.trajectory.persistent, persistent_readers,
714
+ )
715
+ self.experience_in = nn.Linear(hidden_size * 3, self.packet_dim, bias=False)
716
+ self.reason_embed = nn.Embedding(6, self.packet_dim)
717
+ self.activation_checkpointing = True
718
+
719
+ def reset_identity_parameters(self) -> None:
720
+ self.working_memory_bus.reset_identity_parameters()
721
+ self.persistent_memory_bus.reset_identity_parameters()
722
+
723
+
724
+ def logical_seen(self, state: LatentDeliberationState,
725
+ canvas_head: torch.Tensor | None) -> torch.Tensor:
726
+ seen = state.gdn2.seen
727
+ if canvas_head is None:
728
+ return seen
729
+ batch, canvas = seen.shape
730
+ index = (canvas_head[:, None] + torch.arange(canvas, device=seen.device)[None, :]) % canvas
731
+ return seen.gather(1, index)
732
+
733
+ def forward(self, *, token_embeddings: torch.Tensor, confidence: torch.Tensor,
734
+ entropy: torch.Tensor, state: LatentDeliberationState,
735
+
736
+ canvas_head: torch.Tensor | None = None) -> LatentProcessorOutput:
737
+ if state.gdn2 is None:
738
+ raise RuntimeError("Schema25 GDN2 state is missing.")
739
+ batch, canvas, width = token_embeddings.shape
740
+ if state.memory_slots.shape != state.gdn2.persistent.shape:
741
+ raise ValueError("Persistent GDN2 state shape differs from memory slots.")
742
+ memory = replace(state.gdn2, persistent=state.memory_slots)
743
+ hidden = self.trajectory.read(memory, token_embeddings)
744
+ offsets = torch.arange(canvas, device=token_embeddings.device)[None, :].expand(batch, -1)
745
+ if canvas_head is not None:
746
+ offsets = (offsets - canvas_head[:, None]) % canvas
747
+ for block in self.blocks:
748
+ hidden = block(hidden, memory.seen, offsets)
749
+ hidden = self.output_norm(hidden) * memory.seen[..., None].to(hidden.dtype)
750
+ next_state = replace(state, confidence=confidence.float(), entropy=entropy.float(),
751
+ gdn2=memory)
752
+ return LatentProcessorOutput(hidden, hidden, next_state)
753
+
754
+ def observe_state(self, state: LatentDeliberationState,
755
+ heavy: torch.Tensor, working: torch.Tensor,
756
+ live: torch.Tensor, head: torch.Tensor) -> LatentDeliberationState:
757
+ if state.gdn2 is None:
758
+ raise RuntimeError("Schema25 GDN2 state is missing.")
759
+ source = heavy.detach() + working
760
+ updated = self.trajectory.observe(state.gdn2, source, live, head)
761
+ return replace(state, gdn2=updated)
762
+
763
+
764
+ def commit_write(self, *, memory: torch.Tensor, working_state: torch.Tensor,
765
+
766
+ heavy_hidden: torch.Tensor,
767
+ committed_token_embeddings: torch.Tensor,
768
+ commit_lengths: torch.Tensor,
769
+ prefix_lengths: torch.Tensor | None = None,
770
+ commit_reason: torch.Tensor | None = None,
771
+ canvas_head: torch.Tensor | None = None):
772
+ del prefix_lengths
773
+ batch, canvas, width = working_state.shape
774
+ count = int(commit_lengths.max().item())
775
+ if count <= 0:
776
+ zero = memory.new_zeros(())
777
+ return memory, {"gate_mean": zero, "gate_max": zero,
778
+ "gate_gt_01": zero, "gate_gt_05": zero,
779
+ "delta_norm_mean": zero}
780
+ index = torch.arange(count, device=working_state.device)[None, :].expand(batch, -1)
781
+ if canvas_head is not None:
782
+ index = (index + canvas_head[:, None]) % canvas
783
+ selected_working = working_state.gather(
784
+ 1, index[..., None].expand(-1, -1, width)
785
+ )
786
+ selected_heavy = heavy_hidden.detach().gather(
787
+ 1, index[..., None].expand(-1, -1, width)
788
+ )
789
+ if committed_token_embeddings.shape != selected_working.shape:
790
+ raise ValueError("Committed embeddings do not match the prefix.")
791
+ # Canvas processing already supplies bidirectional spatial context.
792
+ # Separate normalized role channels feed the ordered GDN2 writer directly.
793
+ # Unit-floor normalization keeps a zero Working state at zero without
794
+ # amplifying its derivative by 1/sqrt(eps) on the first denoise.
795
+ roles = tuple(F.rms_norm(value.float(), (width,), eps=1.0).to(value.dtype)
796
+ for value in (selected_heavy, selected_working,
797
+ committed_token_embeddings.detach()))
798
+ packet = self.experience_in(torch.cat(roles, dim=-1))
799
+ reason = torch.zeros(batch, device=packet.device, dtype=torch.long) if commit_reason is None else commit_reason.long()
800
+ reason = torch.where((reason == 1) | (reason == 2), 5, reason).clamp(0, 5)
801
+ packet = packet + self.reason_embed(reason)[:, None]
802
+ valid = torch.arange(count, device=packet.device)[None, :] < commit_lengths[:, None]
803
+ written = self.trajectory.persistent.write_sequence(memory, packet, valid)
804
+ delta = written - memory
805
+ zero = memory.new_zeros(())
806
+ return written, {"gate_mean": zero, "gate_max": zero,
807
+ "gate_gt_01": zero, "gate_gt_05": zero,
808
+ "delta_norm_mean": delta.float().norm(dim=(-2, -1)).mean()}
809
+
810
+
811
+ __all__ = [
812
+ "COMMIT_REASON_FALLBACK",
813
+ "COMMIT_REASON_FORCED_JUMP",
814
+ "COMMIT_REASON_NONE",
815
+ "COMMIT_REASON_NORMAL",
816
+ "COMMIT_REASON_TERMINAL",
817
+ "COMMIT_REASON_TRAINING_RANDOM",
818
+ "DecoderMemoryBus",
819
+ "LatentDeliberationState",
820
+ "LatentDeliberationTransformer",
821
+ "LatentProcessorOutput",
822
+ "advance_trajectory_clocks",
823
+ "cat_latent_states",
824
+ "infer_commit_reason",
825
+ "should_force_trajectory_jump",
826
+ "slice_latent_state",
827
+ ]
model-00001-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3aa3fc0393a51acec27161569d566774030e0306a74748dd2a7a11fd8b535ec
3
+ size 3898256004
model-00002-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:89339fd6f0521977fff9d4ae498e485ff409dbb8716cc924c8667c8bceaa19cd
3
+ size 3798073636
model-00003-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b9351e59c7c604b26712960b5d9f3460f5c1aace9ccb348999a2334d93fb613d
3
+ size 3290562732
model-00004-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1c89ba68a1f56e986dbf43175a7a7a6f2d046b04ac993c2837024f71b5d5923d
3
+ size 3319472948
model-00005-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4e42f45a5c409649c0ac8a262b26a0f644d10619d5f857568db855d12a865183
3
+ size 3290562732
model-00006-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da6e7d99f9801cb1b7007044f3baa66e767412d4350211a637174321eacee401
3
+ size 3290562772
model-00007-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ce1f462e324e162f9c0f41b1db3945290eb06c520da3d8ff0f582cde79a6fa96
3
+ size 3319473028
model-00008-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a163f6df13b9a3fbe1df187e42700b74d71fa08b331c521be568f71dec77a8f0
3
+ size 3290562812
model-00009-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eacc2e9ac62fa45f4fd1d391bfff490e327c19df3f70e7835e556287fc9926bd
3
+ size 3290562812
model-00010-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5fa9c8080a9c3954985226496dee261938f3d05cf81e4436932de593e5a01ae1
3
+ size 3319473028
model-00011-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7df1df1cea73133a94aec88056e2514e04a3467d42c0dd55ad0b922af2e5e986
3
+ size 3290562812
model-00012-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:797078b4703d602cc3a323dba676fa38bb959b70d455bdc8c673663261eee2d4
3
+ size 3290562812
model-00013-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35923e8f76bc78ae89b3f83d1cdd175649f64cc898f1862baf543d7cdfbcd0e1
3
+ size 3319473028
model-00014-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:105ceea4ed17c811fb79036c199db1f7d2392a5cc4c05003085f7217e8a938bb
3
+ size 3290562812
model-00015-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d3569ec55bc4f7e89de00efaedfbca3df44096526291731ec14bd7fc245c3b39
3
+ size 3290562812
model-00016-of-00016.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:23c9c00fc04b475c744458f498178a6e9a20fd673c36af981dd22e963b7ca49b
3
+ size 1202380654
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
modeling_modilify_mk2.py ADDED
@@ -0,0 +1,955 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Text-only ModilifyMk2 model with recurrent latent deliberation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ from collections.abc import Sequence
7
+ from dataclasses import dataclass, replace
8
+ from typing import Any
9
+
10
+ import torch
11
+ from torch import nn
12
+ from torch.nn import functional as F
13
+ from torch.utils.checkpoint import checkpoint
14
+ from transformers.cache_utils import Cache
15
+ from transformers.modeling_outputs import BaseModelOutputWithPast
16
+
17
+ from transformers.masking_utils import (
18
+ ALL_MASK_ATTENTION_FUNCTIONS,
19
+ bidirectional_mask_function,
20
+ )
21
+ from transformers.models.diffusion_gemma import (
22
+ DiffusionGemmaDecoderModel,
23
+ DiffusionGemmaEncoderTextModel,
24
+ DiffusionGemmaPreTrainedModel,
25
+ )
26
+ from transformers.models.diffusion_gemma.modeling_diffusion_gemma import (
27
+ DiffusionGemmaRMSNorm,
28
+ DiffusionGemmaTextRouter,
29
+ )
30
+
31
+ from .mps_ops import register_mps_backends
32
+ from .adapters import LoRAConfig, inject_lora
33
+ from .configuration_modilify_mk2 import ModilifyMk2Config, DENOISE_TEMPERATURE
34
+ from .generation_modilify_mk2 import ModilifyMk2GenerationConfig, ModilifyMk2GenerationMixin
35
+ from .latent_deliberation import LatentDeliberationState, LatentProcessorOutput, _sdpa_mask_value
36
+ from .latent_deliberation import LatentDeliberationTransformer
37
+ from .vocab_ops import (
38
+ chunked_vocab_statistics,
39
+ chunked_vocab_training_statistics,
40
+ )
41
+
42
+
43
+ register_mps_backends()
44
+
45
+
46
+ @dataclass
47
+ class ModilifyMk2DecoderOutput(BaseModelOutputWithPast):
48
+ token_embeddings: torch.FloatTensor | None = None
49
+ latent_residual_diagnostics: dict[str, torch.Tensor] | None = None
50
+
51
+
52
+ @dataclass
53
+ class ModilifyMk2ModelOutput(BaseModelOutputWithPast):
54
+ token_embeddings: torch.FloatTensor | None = None
55
+ encoder_last_hidden_state: torch.FloatTensor | None = None
56
+ latent_residual_diagnostics: dict[str, torch.Tensor] | None = None
57
+
58
+
59
+ @dataclass
60
+ class ModilifyMk2BlockDiffusionOutput:
61
+ logits: torch.FloatTensor | None
62
+ heavy_hidden_state: torch.FloatTensor
63
+ next_latent_state: LatentDeliberationState
64
+ gold_nll: torch.FloatTensor | None = None
65
+ loss: torch.FloatTensor | None = None
66
+ past_key_values: Cache | None = None
67
+ hidden_states: tuple[torch.FloatTensor, ...] | None = None
68
+ attentions: tuple[torch.FloatTensor, ...] | None = None
69
+ encoder_last_hidden_state: torch.FloatTensor | None = None
70
+ temporal_context: torch.FloatTensor | None = None
71
+ heavy_context_leaf: torch.FloatTensor | None = None
72
+ temporal_memory_slots: torch.FloatTensor | None = None
73
+ heavy_memory_leaf: torch.FloatTensor | None = None
74
+ working_state: torch.FloatTensor | None = None
75
+ latent_residual_diagnostics: dict[str, torch.Tensor] | None = None
76
+ temperature_gold_nll: torch.FloatTensor | None = None
77
+ proposal: torch.LongTensor | None = None
78
+ proposal_confidence: torch.FloatTensor | None = None
79
+ sampled_nll: torch.FloatTensor | None = None
80
+ token_entropy: torch.FloatTensor | None = None
81
+ greedy_proposal: torch.LongTensor | None = None
82
+ greedy_confidence: torch.FloatTensor | None = None
83
+
84
+ def to_tuple(self) -> tuple[Any, ...]:
85
+ return tuple(value for value in (
86
+ self.loss, self.logits, self.past_key_values, self.hidden_states,
87
+ self.attentions, self.encoder_last_hidden_state,
88
+ self.heavy_hidden_state, self.next_latent_state,
89
+ ) if value is not None)
90
+
91
+ def __getitem__(self, key: str | int) -> Any:
92
+ return getattr(self, key) if isinstance(key, str) else self.to_tuple()[key]
93
+
94
+
95
+ class ModilifyMk2TextRouter(DiffusionGemmaTextRouter):
96
+ """Official router parameters with fp32 softmax so top-k weights stay finite."""
97
+
98
+ def __init__(self, config: Any) -> None:
99
+ super().__init__(config)
100
+ self.norm = DiffusionGemmaRMSNorm(self.hidden_size, eps=self.eps, with_scale=False)
101
+
102
+ def forward(
103
+ self, hidden_states: torch.Tensor
104
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
105
+ hidden_states = self.norm(hidden_states)
106
+ hidden_states = hidden_states * self.scale * self.scalar_root_size
107
+ expert_scores = self.proj(hidden_states)
108
+ # Match the official DiffusionGemma router: bf16 softmax underflows to
109
+ # all-zero top-k rows, then 0/0 NaNs the MoE residual and every loss.
110
+ router_probabilities = F.softmax(expert_scores, dim=-1, dtype=torch.float32)
111
+ top_k_weights, top_k_index = torch.topk(
112
+ router_probabilities,
113
+ k=self.config.top_k_experts,
114
+ dim=-1,
115
+ )
116
+ top_k_weights = top_k_weights / top_k_weights.sum(dim=-1, keepdim=True).clamp_min(
117
+ torch.finfo(torch.float32).tiny
118
+ )
119
+ top_k_weights = top_k_weights * self.per_expert_scale[top_k_index]
120
+ return router_probabilities, top_k_weights, top_k_index
121
+
122
+
123
+ def _finite_decoder_attention_mask(
124
+ mask: torch.Tensor | None,
125
+ *,
126
+ dtype: torch.dtype,
127
+ ) -> torch.Tensor | None:
128
+ """Keep SDPA masks boolean and unmask fully-blocked query rows.
129
+
130
+ Transformers' SDPA mask is True=attend. A fully-False query row makes
131
+ softmax(-inf) in the backward. Converted additive 8k masks also double
132
+ decoder workspace on long prefixes, so stay in bool and only apply the
133
+ torch<2.5 empty-row workaround (pytorch/pytorch#110213).
134
+ """
135
+
136
+ del dtype
137
+ if mask is None:
138
+ return None
139
+ if mask.dtype == torch.bool:
140
+ return mask | ~mask.any(dim=-1, keepdim=True)
141
+ return mask.clamp_min(_sdpa_mask_value(mask.dtype))
142
+
143
+
144
+ def install_modilify_mk2_trunk_semantics(module: nn.Module) -> None:
145
+ """Swap official leaf modules on this instance. Never patch Transformers classes."""
146
+
147
+ for name, child in list(module.named_children()):
148
+ if type(child) is DiffusionGemmaTextRouter:
149
+ replacement = ModilifyMk2TextRouter(child.config)
150
+ replacement.load_state_dict(child.state_dict())
151
+ setattr(module, name, replacement)
152
+ else:
153
+ install_modilify_mk2_trunk_semantics(child)
154
+
155
+
156
+ class ModilifyMk2EncoderModel(DiffusionGemmaPreTrainedModel):
157
+ """Checkpoint-compatible wrapper around the text encoder trunk."""
158
+
159
+ config_class = ModilifyMk2Config
160
+
161
+ def __init__(self, config: ModilifyMk2Config):
162
+ super().__init__(config)
163
+ self.language_model = DiffusionGemmaEncoderTextModel(config.text_config)
164
+ install_modilify_mk2_trunk_semantics(self)
165
+ self.post_init()
166
+
167
+ def get_input_embeddings(self) -> nn.Module:
168
+ return self.language_model.embed_tokens
169
+
170
+ def set_input_embeddings(self, value: nn.Module) -> None:
171
+ self.language_model.embed_tokens = value
172
+
173
+ def forward(self, **kwargs: Any) -> BaseModelOutputWithPast:
174
+ return self.language_model(**kwargs)
175
+
176
+
177
+ class ModilifyMk2DecoderModel(DiffusionGemmaDecoderModel):
178
+ """Diffusion decoder accepting compact self-conditioning embeddings."""
179
+
180
+ config_class = ModilifyMk2Config
181
+ latent_residual_rms_ratio_cap = 0.5
182
+
183
+ def __init__(self, config: ModilifyMk2Config):
184
+ super().__init__(config)
185
+ self.gradient_checkpointing = False
186
+ install_modilify_mk2_trunk_semantics(self)
187
+
188
+ @staticmethod
189
+ def create_diffusion_decoder_attention_mask(
190
+ config: Any,
191
+ inputs_embeds: torch.Tensor,
192
+ past_key_values: Cache,
193
+ decoder_attention_mask: torch.Tensor | dict | None = None,
194
+ ) -> dict[str, torch.Tensor | None]:
195
+ """Official mask builder without the all-True sliding-window skip."""
196
+
197
+ if past_key_values is None:
198
+ raise ValueError(
199
+ "The diffusion mask requires `past_key_values` to construct the next attention mask correctly"
200
+ )
201
+ if (
202
+ decoder_attention_mask is None
203
+ or config._attn_implementation
204
+ not in ALL_MASK_ATTENTION_FUNCTIONS._global_mapping
205
+ ):
206
+ return {"full_attention": None, "sliding_attention": None}
207
+ if isinstance(decoder_attention_mask, dict) and all(
208
+ mask.ndim == 4 for mask in decoder_attention_mask.values()
209
+ ):
210
+ return {
211
+ name: _finite_decoder_attention_mask(
212
+ mask, dtype=inputs_embeds.dtype
213
+ )
214
+ for name, mask in decoder_attention_mask.items()
215
+ }
216
+
217
+ text_config = config.get_text_config() if hasattr(config, "get_text_config") else config
218
+ q_length = inputs_embeds.shape[1]
219
+ q_offset = past_key_values.get_seq_length()
220
+ if isinstance(q_offset, torch.Tensor):
221
+ q_offset = q_offset.to(inputs_embeds.device)
222
+ additional_kv_length = (
223
+ getattr(config, "canvas_length", 0) if past_key_values.is_compileable else 0
224
+ )
225
+ mask_mapping: dict[str, torch.Tensor | None] = {}
226
+ for layer_pattern in set(text_config.layer_types):
227
+ layer_idx = past_key_values.is_sliding.index(
228
+ layer_pattern == "sliding_attention"
229
+ )
230
+ kv_length, kv_offset = past_key_values.get_mask_sizes(q_length, layer_idx)
231
+ kv_length += additional_kv_length
232
+ if layer_pattern == "sliding_attention" and past_key_values.is_compileable:
233
+ sliding_layer = past_key_values.layers[layer_idx]
234
+ max_length = sliding_layer.get_max_length() + additional_kv_length
235
+ if kv_length >= max_length:
236
+ kv_length = max_length
237
+ mask_mapping[layer_pattern] = _finite_decoder_attention_mask(
238
+ ALL_MASK_ATTENTION_FUNCTIONS[config._attn_implementation](
239
+ batch_size=inputs_embeds.shape[0],
240
+ q_length=q_length,
241
+ kv_length=kv_length,
242
+ q_offset=q_offset,
243
+ kv_offset=kv_offset,
244
+ mask_function=bidirectional_mask_function,
245
+ attention_mask=decoder_attention_mask,
246
+ allow_is_causal_skip=False,
247
+ allow_is_bidirectional_skip=True,
248
+ local_size=getattr(text_config, "sliding_window", None),
249
+ dtype=inputs_embeds.dtype,
250
+ config=text_config,
251
+ use_vmap=False,
252
+ device=inputs_embeds.device,
253
+ ),
254
+ dtype=inputs_embeds.dtype,
255
+ )
256
+ return mask_mapping
257
+
258
+ def merge_latent_context(
259
+ self,
260
+ token_embeddings: torch.Tensor,
261
+ latent_context: torch.Tensor | None,
262
+ *,
263
+ collect_diagnostics: bool = True,
264
+ ) -> tuple[torch.Tensor, dict[str, torch.Tensor]]:
265
+ """Map latent context through the frozen native self-conditioning bridge."""
266
+
267
+ if latent_context is None:
268
+ context = torch.zeros_like(token_embeddings)
269
+ else:
270
+ if latent_context.shape != token_embeddings.shape:
271
+ raise ValueError("Temporal context must match canvas hidden-state shape.")
272
+ context = latent_context.to(token_embeddings)
273
+ mapper = self.self_conditioning
274
+ normalized_context = mapper.pre_norm(context)
275
+ mapped_context = mapper.down_proj(
276
+ mapper.act_fn(mapper.gate_proj(normalized_context))
277
+ * mapper.up_proj(normalized_context)
278
+ )
279
+ mapped_fp32 = mapped_context.float()
280
+ # Use the energy directly in the cap denominator. Computing
281
+ # sqrt(E[x²]) and immediately squaring it again has an undefined
282
+ # backward at the identity-init point x=0 (0/0 in d(sqrt)/dx), which
283
+ # poisoned the detached temporal-context VJP on every denoise.
284
+ mapped_token_energy = mapped_fp32.square().mean(dim=-1, keepdim=True)
285
+ token_rms_per_token = (
286
+ token_embeddings.float().square().mean(dim=-1, keepdim=True).sqrt()
287
+ )
288
+ residual_cap = self.latent_residual_rms_ratio_cap * token_rms_per_token
289
+ soft_cap_scale = residual_cap / torch.sqrt(
290
+ mapped_token_energy + residual_cap.square() + 1.0e-12
291
+ )
292
+ mapped_context = (mapped_fp32 * soft_cap_scale).to(dtype=mapped_context.dtype)
293
+ combined = token_embeddings + mapped_context
294
+ diagnostics: dict[str, torch.Tensor] = {}
295
+ if collect_diagnostics:
296
+ token_rms = token_embeddings.detach().float().square().mean().sqrt()
297
+ mapped_rms = mapped_context.detach().float().square().mean().sqrt()
298
+ diagnostics = {
299
+ "token_embedding_rms": token_rms,
300
+ "latent_context_rms": context.detach().float().square().mean().sqrt(),
301
+ "mapped_sc_rms": mapped_rms,
302
+ "actual_residual_rms": mapped_rms,
303
+ "latent_to_embedding_rms_ratio": (
304
+ mapped_rms / token_rms.clamp_min(1.0e-12)
305
+ ),
306
+ "merged_input_rms": combined.detach().float().square().mean().sqrt(),
307
+ }
308
+ return mapper.post_norm(combined), diagnostics
309
+
310
+ def _run_stack(
311
+ self,
312
+ inputs_embeds: torch.Tensor,
313
+ *,
314
+ past_key_values: Cache | None,
315
+ decoder_attention_mask: torch.Tensor | dict | None,
316
+ decoder_position_ids: torch.LongTensor | None,
317
+ **kwargs: Any,
318
+ ) -> torch.Tensor:
319
+ if decoder_position_ids is None:
320
+ prefix = past_key_values.get_seq_length(layer_idx=0) if past_key_values is not None else 0
321
+ decoder_position_ids = torch.arange(
322
+ prefix, prefix + inputs_embeds.shape[1], device=inputs_embeds.device
323
+ ).unsqueeze(0)
324
+ if not isinstance(mask_mapping := decoder_attention_mask, dict):
325
+ mask_mapping = self.create_diffusion_decoder_attention_mask(
326
+ config=self.text_config,
327
+ inputs_embeds=inputs_embeds,
328
+ past_key_values=past_key_values,
329
+ decoder_attention_mask=decoder_attention_mask,
330
+ )
331
+ working_bus = kwargs.pop("working_bus", None)
332
+ working_state = kwargs.pop("working_state", None)
333
+ working_canvas_head = kwargs.pop("working_canvas_head", None)
334
+ persistent_bus = kwargs.pop("persistent_bus", None) or kwargs.pop("memory_bus", None)
335
+ memory_slots = kwargs.pop("memory_slots", None)
336
+ slot_identity = kwargs.pop("slot_identity", None)
337
+ cache = past_key_values
338
+ if self.gradient_checkpointing and self.training and cache is not None:
339
+ cache = copy.copy(cache)
340
+ cache.layers = [copy.copy(layer) for layer in cache.layers]
341
+ hidden = inputs_embeds
342
+ positions = {
343
+ layer_type: self.rotary_emb(hidden, decoder_position_ids, layer_type)
344
+ for layer_type in self.unique_layer_types
345
+ }
346
+ working_kv = None
347
+ if working_bus is not None and working_state is not None:
348
+ working_kv = working_bus.prepare_kv(working_state)
349
+ memory_kv = None
350
+ if persistent_bus is not None and memory_slots is not None:
351
+ memory_kv = persistent_bus.prepare_kv(memory_slots, slot_identity)
352
+ working_reader = 0
353
+ reader_index = 0
354
+ for index in range(self.text_config.num_hidden_layers):
355
+ layer = self.layers[index]
356
+ layer_type = self.text_config.layer_types[index]
357
+ if self.gradient_checkpointing and self.training:
358
+ def layer_forward(
359
+ values: torch.Tensor,
360
+ selected: nn.Module = layer,
361
+ selected_type: str = layer_type,
362
+ ) -> torch.Tensor:
363
+ return selected(
364
+ values,
365
+ position_embeddings=positions[selected_type],
366
+ attention_mask=mask_mapping[selected_type],
367
+ position_ids=decoder_position_ids,
368
+ past_key_values=cache,
369
+ **kwargs,
370
+ )
371
+
372
+ hidden = self._gradient_checkpointing_func(layer_forward, hidden)
373
+ else:
374
+ hidden = layer(
375
+ hidden,
376
+ position_embeddings=positions[layer_type],
377
+ attention_mask=mask_mapping[layer_type],
378
+ position_ids=decoder_position_ids,
379
+ past_key_values=cache,
380
+ **kwargs,
381
+ )
382
+ if layer_type == "full_attention":
383
+ if (
384
+ working_kv is not None
385
+ and working_reader < working_bus.num_readers
386
+ ):
387
+ hidden = working_bus.read(
388
+ hidden, working_reader, *working_kv,
389
+ positions=(
390
+ decoder_position_ids if working_canvas_head is not None else None
391
+ ),
392
+ )
393
+ working_reader += 1
394
+ if (
395
+ memory_kv is not None
396
+ and reader_index < persistent_bus.num_readers
397
+ ):
398
+ hidden = persistent_bus.read(
399
+ hidden, reader_index, memory_kv[0], memory_kv[1]
400
+ )
401
+ reader_index += 1
402
+ return self.norm(hidden)
403
+
404
+ def forward(
405
+ self,
406
+ decoder_input_ids: torch.LongTensor,
407
+ past_key_values: Cache | None = None,
408
+ decoder_token_embeddings: torch.FloatTensor | None = None,
409
+ temporal_context_embeddings: torch.FloatTensor | None = None,
410
+ decoder_attention_mask: torch.Tensor | dict | None = None,
411
+ decoder_position_ids: torch.LongTensor | None = None,
412
+ collect_latent_diagnostics: bool = True,
413
+ memory_slots: torch.Tensor | None = None,
414
+ working_bus: Any | None = None,
415
+ working_state: torch.Tensor | None = None,
416
+ persistent_bus: Any | None = None,
417
+ slot_identity: torch.Tensor | None = None,
418
+ **kwargs: Any,
419
+ ) -> ModilifyMk2DecoderOutput:
420
+ if "use_cache" in kwargs:
421
+ raise ValueError("The diffusion decoder always reads the supplied cache.")
422
+ if decoder_token_embeddings is None:
423
+ token_embeddings = self.embed_tokens(decoder_input_ids)
424
+ else:
425
+ token_embeddings = decoder_token_embeddings
426
+ expected = (*decoder_input_ids.shape, self.text_config.hidden_size)
427
+ if token_embeddings.shape != expected:
428
+ raise ValueError("Precomputed decoder embeddings have the wrong shape.")
429
+ context_embeddings = (
430
+ torch.zeros_like(token_embeddings)
431
+ if temporal_context_embeddings is None
432
+ else temporal_context_embeddings.to(token_embeddings)
433
+ )
434
+ inputs_embeds, diagnostics = self.merge_latent_context(
435
+ token_embeddings,
436
+ context_embeddings if temporal_context_embeddings is not None else None,
437
+ collect_diagnostics=collect_latent_diagnostics,
438
+ )
439
+ hidden = self._run_stack(
440
+ inputs_embeds,
441
+ past_key_values=past_key_values,
442
+ decoder_attention_mask=decoder_attention_mask,
443
+ decoder_position_ids=decoder_position_ids,
444
+ memory_slots=memory_slots,
445
+ working_bus=working_bus,
446
+ working_state=working_state,
447
+ persistent_bus=persistent_bus,
448
+ slot_identity=slot_identity,
449
+ **kwargs,
450
+ )
451
+ return ModilifyMk2DecoderOutput(
452
+ last_hidden_state=hidden,
453
+ past_key_values=past_key_values,
454
+ token_embeddings=token_embeddings,
455
+ latent_residual_diagnostics=diagnostics,
456
+ )
457
+
458
+
459
+ class ModilifyMk2Model(DiffusionGemmaPreTrainedModel):
460
+ config_class = ModilifyMk2Config
461
+ _tied_weights_keys = {
462
+ "encoder.language_model.norm.weight": "decoder.norm.weight",
463
+ r"encoder.language_model.layers\.(?:[^.]+\.)*weight": r"decoder.layers\.(?:[^.]+\.)*weight",
464
+ r"encoder.language_model.layers\.(?:[^.]+\.)*scale": r"decoder.layers\.(?:[^.]+\.)*scale",
465
+ r"encoder.language_model.layers\.(?:[^.]+\.)*per_expert_scale": r"decoder.layers\.(?:[^.]+\.)*per_expert_scale",
466
+ r"encoder.language_model.layers\.(?:[^.]+\.)*gate_up_proj": r"decoder.layers\.(?:[^.]+\.)*gate_up_proj",
467
+ r"encoder.language_model.layers\.(?:[^.]+\.)*down_proj": r"decoder.layers\.(?:[^.]+\.)*down_proj",
468
+ "encoder.language_model.embed_tokens.weight": "decoder.embed_tokens.weight",
469
+ }
470
+
471
+ def __init__(self, config: ModilifyMk2Config):
472
+ super().__init__(config)
473
+ self.encoder = ModilifyMk2EncoderModel(config)
474
+ self.decoder = ModilifyMk2DecoderModel(config)
475
+ install_modilify_mk2_trunk_semantics(self)
476
+ self.post_init()
477
+
478
+ def get_encoder(self):
479
+ return self.encoder
480
+
481
+ def get_decoder(self):
482
+ return self.decoder
483
+
484
+ def get_input_embeddings(self):
485
+ return self.encoder.get_input_embeddings()
486
+
487
+ def set_input_embeddings(self, value):
488
+ self.encoder.set_input_embeddings(value)
489
+ self.decoder.embed_tokens = value
490
+
491
+ def forward(
492
+ self,
493
+ *,
494
+ input_ids: torch.LongTensor | None = None,
495
+ attention_mask: torch.Tensor | dict | None = None,
496
+ past_key_values: Cache | None = None,
497
+ position_ids: torch.LongTensor | None = None,
498
+ decoder_input_ids: torch.LongTensor,
499
+ decoder_token_embeddings: torch.FloatTensor | None = None,
500
+ temporal_context_embeddings: torch.FloatTensor | None = None,
501
+ decoder_attention_mask: torch.Tensor | dict | None = None,
502
+ decoder_position_ids: torch.LongTensor | None = None,
503
+ return_encoder_outputs: bool = True,
504
+ collect_latent_diagnostics: bool = True,
505
+ memory_slots: torch.Tensor | None = None,
506
+ working_bus: Any | None = None,
507
+ working_state: torch.Tensor | None = None,
508
+ persistent_bus: Any | None = None,
509
+ slot_identity: torch.Tensor | None = None,
510
+ **kwargs: Any,
511
+ ) -> ModilifyMk2ModelOutput:
512
+ encoder_hidden = None
513
+ if input_ids is not None:
514
+ encoded = self.encoder(
515
+ input_ids=input_ids,
516
+ attention_mask=attention_mask,
517
+ past_key_values=past_key_values,
518
+ position_ids=position_ids,
519
+ **kwargs,
520
+ )
521
+ past_key_values = encoded.past_key_values
522
+ if return_encoder_outputs:
523
+ encoder_hidden = encoded.last_hidden_state
524
+ elif past_key_values is None:
525
+ raise ValueError("Either `input_ids` or `past_key_values` is required.")
526
+ decoded = self.decoder(
527
+ decoder_input_ids=decoder_input_ids,
528
+ decoder_token_embeddings=decoder_token_embeddings,
529
+ past_key_values=past_key_values,
530
+ temporal_context_embeddings=temporal_context_embeddings,
531
+ decoder_attention_mask=decoder_attention_mask,
532
+ decoder_position_ids=decoder_position_ids,
533
+ collect_latent_diagnostics=collect_latent_diagnostics,
534
+ memory_slots=memory_slots,
535
+ working_bus=working_bus,
536
+ working_state=working_state,
537
+ persistent_bus=persistent_bus,
538
+ slot_identity=slot_identity,
539
+ **kwargs,
540
+ )
541
+ return ModilifyMk2ModelOutput(
542
+ last_hidden_state=decoded.last_hidden_state,
543
+ past_key_values=past_key_values,
544
+ token_embeddings=decoded.token_embeddings,
545
+ encoder_last_hidden_state=encoder_hidden,
546
+ latent_residual_diagnostics=decoded.latent_residual_diagnostics,
547
+ )
548
+
549
+
550
+ class ModilifyMk2ForBlockDiffusion(DiffusionGemmaPreTrainedModel, ModilifyMk2GenerationMixin):
551
+ config_class = ModilifyMk2Config
552
+ _tied_weights_keys = {"lm_head.weight": "model.decoder.embed_tokens.weight"}
553
+ generation_config_class = ModilifyMk2GenerationConfig
554
+ _no_split_modules = ["DiffusionGemmaDecoderLayer", "LatentDeliberationTransformer"]
555
+ supports_gradient_checkpointing = True
556
+
557
+ @torch.no_grad()
558
+ def _init_weights(self, module: nn.Module) -> None:
559
+ super()._init_weights(module)
560
+ if isinstance(module, LatentDeliberationTransformer):
561
+ module.reset_identity_parameters()
562
+ module.working_memory_bus.freeze()
563
+ module.persistent_memory_bus.freeze()
564
+
565
+ def __init__(self, config: ModilifyMk2Config):
566
+ super().__init__(config)
567
+ self.model = ModilifyMk2Model(config)
568
+ layer_types = tuple(getattr(config.text_config, "layer_types", None) or ())
569
+ self.latent_deliberation = LatentDeliberationTransformer(
570
+ hidden_size=config.text_config.hidden_size,
571
+ latent_dim=config.latent_dim,
572
+ ffn_dim=config.latent_ffn_dim,
573
+ num_layers=config.latent_num_layers,
574
+ num_heads=config.latent_num_heads,
575
+ local_attention_window=config.latent_local_attention_window,
576
+ tape_probes=config.latent_tape_probes,
577
+ history_kv_rank=config.latent_history_kv_rank,
578
+ num_memory_readers=sum(layer_type == "full_attention" for layer_type in layer_types),
579
+ num_working_readers=(
580
+ sum(layer_type == "full_attention" for layer_type in layer_types)
581
+ if config.working_memory_bus else 0
582
+ ),
583
+ num_persistent_readers=(
584
+ sum(layer_type == "full_attention" for layer_type in layer_types)
585
+ if config.persistent_memory_bus else 0
586
+ ),
587
+ working_last_block_global=config.latent_working_last_block_global,
588
+ commit_sequence_dim=config.commit_sequence_dim,
589
+ max_canvas_length=config.canvas_length,
590
+ )
591
+ self.lm_head = nn.Linear(config.text_config.hidden_size, config.text_config.vocab_size, bias=False)
592
+ self.final_logit_softcapping = config.text_config.final_logit_softcapping
593
+ self.post_init()
594
+ install_modilify_mk2_trunk_semantics(self)
595
+ if getattr(config, "lora_config", None):
596
+ payload = dict(config.lora_config)
597
+ payload["target_modules"] = tuple(payload["target_modules"])
598
+ inject_lora(self, LoRAConfig(**payload))
599
+ # The same adapters serve the encoder and decoder, just like the
600
+ # tied backbone. Tell from_pretrained/save_pretrained about them.
601
+ self._tied_weights_keys = dict(type(self)._tied_weights_keys)
602
+ self._tied_weights_keys.update({
603
+ name: name.replace("model.encoder.language_model.", "model.decoder.", 1)
604
+ for name, _ in self.named_parameters(remove_duplicate=False)
605
+ if name.startswith("model.encoder.language_model.") and ".lora_" in name
606
+ })
607
+ self.all_tied_weights_keys = self.get_expanded_tied_weights_keys(all_submodels=True)
608
+ self.tie_weights(recompute_mapping=False)
609
+ config.text_config._experts_implementation = "modilify_mk2_lora"
610
+ # These 26 small parameters were trained and saved in FP32. Preserve
611
+ # their dtype even when from_pretrained is called with BF16/FP16.
612
+ self._keep_in_fp32_modules_strict = {
613
+ name for name, _ in self.named_parameters()
614
+ if name.startswith("latent_deliberation.") and (
615
+ name.endswith((".dt_bias", ".a_log")) or
616
+ (name.endswith(".weight") and "norm" in name.rsplit(".", 2)[-2])
617
+ )
618
+ }
619
+
620
+ def _finalize_logits(self, hidden: torch.Tensor) -> torch.Tensor:
621
+ logits = self.lm_head(hidden)
622
+ return torch.tanh(logits / self.final_logit_softcapping) * self.final_logit_softcapping
623
+
624
+ def _compute_gold_nll(
625
+ self, logits: torch.Tensor, labels: torch.LongTensor | None
626
+ ) -> torch.Tensor | None:
627
+ if labels is None:
628
+ return None
629
+ labels = labels.to(logits.device)
630
+ return F.cross_entropy(
631
+ logits.flatten(0, 1), labels.flatten(),
632
+ ignore_index=-100, reduction="none",
633
+ ).view_as(labels)
634
+
635
+ def _prepare_latent_context(
636
+ self,
637
+ decoder_input_ids: torch.LongTensor,
638
+ *,
639
+
640
+
641
+ confidence: torch.Tensor | None,
642
+ entropy: torch.Tensor | None,
643
+ age: torch.Tensor | None,
644
+ latent_state: LatentDeliberationState | None,
645
+ canvas_head: torch.Tensor | None,
646
+ ) -> tuple[torch.Tensor, LatentDeliberationState, torch.Tensor]:
647
+ batch, canvas = decoder_input_ids.shape
648
+ dtype = self.model.decoder.embed_tokens.weight.dtype
649
+ if latent_state is None:
650
+ latent_state = LatentDeliberationState.empty(
651
+ batch_size=batch, canvas_length=canvas,
652
+ latent_dim=self.config.latent_dim, memory_slots=self.config.latent_memory_slots,
653
+ device=decoder_input_ids.device, dtype=dtype, enable_gdn2=True,
654
+ )
655
+ confidence = latent_state.confidence if confidence is None else confidence.squeeze(-1).float()
656
+ entropy = latent_state.entropy if entropy is None else entropy.squeeze(-1).float()
657
+ if age is not None:
658
+ latent_state = replace(
659
+ latent_state,
660
+ age=age.to(device=decoder_input_ids.device, dtype=torch.int32),
661
+ )
662
+ token_embeddings = self.model.decoder.embed_tokens(decoder_input_ids)
663
+ use_outer_checkpoint = (
664
+ self.training
665
+ and torch.is_grad_enabled()
666
+ and self.latent_deliberation.activation_checkpointing
667
+ )
668
+ if use_outer_checkpoint:
669
+ def run_latent_processor(
670
+ embeddings: torch.Tensor,
671
+ memory_slots: torch.Tensor,
672
+ working_cells: torch.Tensor,
673
+ working_row: torch.Tensor,
674
+ state_confidence: torch.Tensor,
675
+ state_entropy: torch.Tensor,
676
+ state_age: torch.Tensor,
677
+ state_token_changed: torch.Tensor,
678
+ state_confidence_delta: torch.Tensor,
679
+ state_entropy_delta: torch.Tensor,
680
+ state_ponder_steps: torch.Tensor,
681
+ state_stagnation_steps: torch.Tensor,
682
+
683
+
684
+ ) -> torch.Tensor:
685
+ checkpoint_state = LatentDeliberationState(
686
+ memory_slots=memory_slots,
687
+ confidence=state_confidence,
688
+ entropy=state_entropy,
689
+ age=state_age,
690
+ token_changed=state_token_changed,
691
+ confidence_delta=state_confidence_delta,
692
+ entropy_delta=state_entropy_delta,
693
+ ponder_steps=state_ponder_steps,
694
+ stagnation_steps=state_stagnation_steps,
695
+ gdn2=replace(
696
+ latent_state.gdn2, cells=working_cells,
697
+ row=working_row, persistent=memory_slots,
698
+ ),
699
+ )
700
+ result = self.latent_deliberation(
701
+ token_embeddings=embeddings,
702
+ confidence=confidence,
703
+ entropy=entropy,
704
+ state=checkpoint_state,
705
+
706
+
707
+ canvas_head=canvas_head,
708
+ )
709
+ return result.context
710
+
711
+ latent_context = checkpoint(
712
+ run_latent_processor,
713
+ token_embeddings,
714
+ latent_state.memory_slots,
715
+ latent_state.gdn2.cells,
716
+ latent_state.gdn2.row,
717
+ latent_state.confidence,
718
+ latent_state.entropy,
719
+ latent_state.age,
720
+ latent_state.token_changed,
721
+ latent_state.confidence_delta,
722
+ latent_state.entropy_delta,
723
+ latent_state.ponder_steps,
724
+ latent_state.stagnation_steps,
725
+
726
+
727
+ use_reentrant=False,
728
+ preserve_rng_state=True,
729
+ )
730
+ next_state = LatentDeliberationState(
731
+ memory_slots=latent_state.memory_slots,
732
+ confidence=confidence.to(dtype=torch.float32),
733
+ entropy=entropy.to(dtype=torch.float32),
734
+ age=latent_state.age,
735
+ token_changed=latent_state.token_changed,
736
+ confidence_delta=latent_state.confidence_delta,
737
+ entropy_delta=latent_state.entropy_delta,
738
+ ponder_steps=latent_state.ponder_steps,
739
+ stagnation_steps=latent_state.stagnation_steps,
740
+ gdn2=latent_state.gdn2,
741
+ )
742
+ else:
743
+ processed: LatentProcessorOutput = self.latent_deliberation(
744
+ token_embeddings=token_embeddings,
745
+ confidence=confidence,
746
+ entropy=entropy,
747
+ state=latent_state,
748
+
749
+
750
+ canvas_head=canvas_head,
751
+ )
752
+ latent_context = processed.context
753
+ next_state = processed.state
754
+ return (
755
+ latent_context,
756
+ next_state,
757
+ token_embeddings,
758
+ )
759
+
760
+ def forward(
761
+ self,
762
+ *,
763
+ input_ids: torch.LongTensor | None = None,
764
+ attention_mask: torch.Tensor | dict | None = None,
765
+ past_key_values: Cache | None = None,
766
+ position_ids: torch.LongTensor | None = None,
767
+ decoder_input_ids: torch.LongTensor,
768
+ previous_confidence: torch.FloatTensor | None = None,
769
+ previous_entropy: torch.FloatTensor | None = None,
770
+ token_age: torch.Tensor | None = None,
771
+ latent_state: LatentDeliberationState | None = None,
772
+
773
+
774
+ canvas_head: torch.Tensor | None = None,
775
+
776
+ decoder_attention_mask: torch.Tensor | dict | None = None,
777
+ decoder_position_ids: torch.LongTensor | None = None,
778
+ labels: torch.LongTensor | None = None,
779
+ return_encoder_outputs: bool = True,
780
+ detach_temporal_context_for_heavy: bool = False,
781
+ compact_vocab: bool = False,
782
+ repetition_token_mask: torch.BoolTensor | None = None,
783
+ repetition_penalty: float = 1.0,
784
+ sampling_generators: Sequence[torch.Generator] | None = None,
785
+ collect_latent_diagnostics: bool = True,
786
+ **kwargs: Any,
787
+ ) -> ModilifyMk2BlockDiffusionOutput:
788
+ (
789
+ latent_context,
790
+ next_state,
791
+ decoder_token_embeddings,
792
+ ) = self._prepare_latent_context(
793
+ decoder_input_ids,
794
+
795
+
796
+ confidence=previous_confidence,
797
+ entropy=previous_entropy,
798
+ age=token_age,
799
+ latent_state=latent_state,
800
+ canvas_head=canvas_head,
801
+ )
802
+ heavy_context = latent_context
803
+ heavy_context_leaf = None
804
+ temporal_memory_slots = next_state.memory_slots
805
+ heavy_memory_slots = temporal_memory_slots
806
+ heavy_memory_leaf = None
807
+ working_bus = self.latent_deliberation.working_memory_bus
808
+ persistent_bus = self.latent_deliberation.persistent_memory_bus
809
+ working_readers_active = (
810
+ working_bus.num_readers > 0
811
+ and (not working_bus.training or working_bus.enabled)
812
+ )
813
+ memory_readers_active = (
814
+ persistent_bus.num_readers > 0
815
+ and (not persistent_bus.training or persistent_bus.enabled)
816
+ )
817
+ if detach_temporal_context_for_heavy and latent_context.requires_grad:
818
+ # Clone so the heavy decoder cannot alias the temporal graph storage.
819
+ # `detach()` alone shares storage; an in-place decoder write would
820
+ # corrupt the later latent backward and can produce non-finite grads.
821
+ heavy_context_leaf = latent_context.detach().clone().requires_grad_(True)
822
+ heavy_context = heavy_context_leaf
823
+ if (
824
+ detach_temporal_context_for_heavy
825
+ and memory_readers_active
826
+ and temporal_memory_slots.requires_grad
827
+ ):
828
+ # Memory readers are part of the heavy decoder too. Keep their
829
+ # backward separate from the recurrent temporal graph for the same
830
+ # reason as the canvas context above; both gradients are replayed
831
+ # together at the TBPTT boundary.
832
+ heavy_memory_leaf = (
833
+ temporal_memory_slots.detach().clone().requires_grad_(True)
834
+ )
835
+ heavy_memory_slots = heavy_memory_leaf
836
+ # The production LoRA path freezes the tied embedding table. Reuse its
837
+ # lookup there; a trainable table needs two independent autograd paths
838
+ # because heavy and temporal backward execute at different times.
839
+ reusable_decoder_embeddings = (
840
+ None
841
+ if self.model.decoder.embed_tokens.weight.requires_grad
842
+ else decoder_token_embeddings
843
+ )
844
+ outputs = self.model(
845
+ input_ids=input_ids, attention_mask=attention_mask,
846
+ past_key_values=past_key_values, position_ids=position_ids,
847
+ decoder_input_ids=decoder_input_ids,
848
+ decoder_token_embeddings=reusable_decoder_embeddings,
849
+ temporal_context_embeddings=heavy_context,
850
+ decoder_attention_mask=decoder_attention_mask,
851
+ decoder_position_ids=decoder_position_ids,
852
+ return_encoder_outputs=return_encoder_outputs,
853
+ collect_latent_diagnostics=collect_latent_diagnostics,
854
+ working_bus=working_bus if working_readers_active else None,
855
+ working_state=(
856
+ heavy_context,
857
+ # The Torch decoder keeps physical ring order and supplies
858
+ # logical positions through RoPE; its mask must stay physical.
859
+ next_state.gdn2.seen,
860
+ ) if working_readers_active else None,
861
+ working_canvas_head=canvas_head,
862
+ persistent_bus=persistent_bus if memory_readers_active else None,
863
+ memory_slots=heavy_memory_slots if memory_readers_active else None,
864
+ slot_identity=(
865
+ next_state.gdn2.seen
866
+ if memory_readers_active else None
867
+ ),
868
+ **kwargs,
869
+ )
870
+ latent_residual_diagnostics = outputs.latent_residual_diagnostics
871
+ proposal = proposal_confidence = token_entropy = None
872
+ greedy_proposal = greedy_confidence = None
873
+ temperature_gold_nll = None
874
+ sampled_nll = None
875
+ if compact_vocab:
876
+ logits = None
877
+ if labels is None:
878
+ (
879
+ proposal,
880
+ proposal_confidence,
881
+ token_entropy,
882
+ greedy_proposal,
883
+ greedy_confidence,
884
+ ) = (
885
+ chunked_vocab_statistics(
886
+ outputs.last_hidden_state.detach(),
887
+ self.lm_head.weight.detach(),
888
+ softcap=self.final_logit_softcapping,
889
+ temperature=DENOISE_TEMPERATURE,
890
+ chunk_size=self.config.vocab_chunk_size,
891
+ repetition_token_mask=repetition_token_mask,
892
+ repetition_penalty=repetition_penalty,
893
+ sampling_generators=sampling_generators,
894
+ top_k=getattr(self.config, "commit_top_k", 40),
895
+ min_p=getattr(self.config, "commit_min_p", 0.05),
896
+ )
897
+ )
898
+ gold_nll = None
899
+ else:
900
+ (
901
+ gold_nll,
902
+ temperature_gold_nll,
903
+ proposal,
904
+ proposal_confidence,
905
+ token_entropy,
906
+ greedy_proposal,
907
+ greedy_confidence,
908
+ sampled_nll,
909
+ ) = chunked_vocab_training_statistics(
910
+ outputs.last_hidden_state,
911
+ self.lm_head.weight,
912
+ labels,
913
+ return_sampled_nll=True,
914
+ softcap=self.final_logit_softcapping,
915
+ temperature=DENOISE_TEMPERATURE,
916
+ chunk_size=self.config.vocab_chunk_size,
917
+ top_k=getattr(self.config, "commit_top_k", 40),
918
+ min_p=getattr(self.config, "commit_min_p", 0.05),
919
+ )
920
+ else:
921
+ logits = self._finalize_logits(outputs.last_hidden_state)
922
+ gold_nll = self._compute_gold_nll(logits, labels)
923
+ loss = None
924
+ if gold_nll is not None and labels is not None:
925
+ valid = labels.to(gold_nll.device).ne(-100)
926
+ loss = gold_nll.masked_fill(~valid, 0.0).sum() / valid.sum().clamp_min(1)
927
+ return ModilifyMk2BlockDiffusionOutput(
928
+ logits=logits, loss=loss,
929
+ past_key_values=outputs.past_key_values,
930
+ encoder_last_hidden_state=outputs.encoder_last_hidden_state,
931
+ heavy_hidden_state=outputs.last_hidden_state,
932
+ next_latent_state=next_state,
933
+ gold_nll=gold_nll,
934
+ temporal_context=latent_context,
935
+ heavy_context_leaf=heavy_context_leaf,
936
+ temporal_memory_slots=temporal_memory_slots,
937
+ heavy_memory_leaf=heavy_memory_leaf,
938
+
939
+ working_state=latent_context,
940
+ latent_residual_diagnostics=latent_residual_diagnostics,
941
+ temperature_gold_nll=temperature_gold_nll,
942
+ proposal=proposal,
943
+ proposal_confidence=proposal_confidence,
944
+ sampled_nll=sampled_nll,
945
+ token_entropy=token_entropy,
946
+ greedy_proposal=greedy_proposal,
947
+ greedy_confidence=greedy_confidence,
948
+ )
949
+
950
+
951
+ __all__ = [
952
+ "ModilifyMk2BlockDiffusionOutput", "ModilifyMk2Config", "ModilifyMk2DecoderModel",
953
+ "ModilifyMk2EncoderModel", "ModilifyMk2ForBlockDiffusion", "ModilifyMk2Model",
954
+ "ModilifyMk2TextRouter", "install_modilify_mk2_trunk_semantics",
955
+ ]
mps_ops.py ADDED
@@ -0,0 +1,921 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MPS-specific kernels that preserve the model's mathematical operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+
7
+ import torch
8
+ from torch import nn
9
+ from transformers.integrations.moe import ALL_EXPERTS_FUNCTIONS, _grouped_linear
10
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
11
+
12
+
13
+ def attach_expert_lora(
14
+ experts: nn.Module,
15
+ *,
16
+ rank: int,
17
+ alpha: int,
18
+ dropout: float,
19
+ ) -> None:
20
+ """Attach packed LoRA parameters to one expert module instance."""
21
+ if rank <= 0:
22
+ raise ValueError("Expert LoRA rank must be positive.")
23
+ if alpha <= 0:
24
+ raise ValueError("Expert LoRA alpha must be positive.")
25
+ if not 0.0 <= dropout < 1.0:
26
+ raise ValueError("Expert LoRA dropout must be in [0, 1).")
27
+ parameter_names = (
28
+ "lora_gate_up_a",
29
+ "lora_gate_up_b",
30
+ "lora_down_a",
31
+ "lora_down_b",
32
+ )
33
+ existing = tuple(hasattr(experts, name) for name in parameter_names)
34
+ if any(existing):
35
+ if not all(existing):
36
+ raise RuntimeError("Expert LoRA parameters are only partially initialized.")
37
+ return
38
+
39
+ experts.lora_scaling = float(alpha) / rank
40
+ experts.lora_dropout = nn.Dropout(dropout) if dropout else nn.Identity()
41
+ experts.lora_gate_up_a = nn.Parameter(
42
+ torch.empty(
43
+ experts.num_experts,
44
+ rank,
45
+ experts.hidden_dim,
46
+ device=experts.gate_up_proj.device,
47
+ dtype=experts.gate_up_proj.dtype,
48
+ )
49
+ )
50
+ experts.lora_gate_up_b = nn.Parameter(
51
+ torch.zeros(
52
+ experts.num_experts,
53
+ 2 * experts.intermediate_dim,
54
+ rank,
55
+ device=experts.gate_up_proj.device,
56
+ dtype=experts.gate_up_proj.dtype,
57
+ )
58
+ )
59
+ experts.lora_down_a = nn.Parameter(
60
+ torch.empty(
61
+ experts.num_experts,
62
+ rank,
63
+ experts.intermediate_dim,
64
+ device=experts.down_proj.device,
65
+ dtype=experts.down_proj.dtype,
66
+ )
67
+ )
68
+ experts.lora_down_b = nn.Parameter(
69
+ torch.zeros(
70
+ experts.num_experts,
71
+ experts.hidden_dim,
72
+ rank,
73
+ device=experts.down_proj.device,
74
+ dtype=experts.down_proj.dtype,
75
+ )
76
+ )
77
+ nn.init.kaiming_uniform_(experts.lora_gate_up_a, a=math.sqrt(5))
78
+ nn.init.kaiming_uniform_(experts.lora_down_a, a=math.sqrt(5))
79
+ experts.gate_up_proj.requires_grad_(False)
80
+ experts.down_proj.requires_grad_(False)
81
+
82
+
83
+ def lora_aware_eager_experts_forward(
84
+ self: nn.Module,
85
+ hidden_states: torch.Tensor,
86
+ top_k_index: torch.Tensor,
87
+ top_k_weights: torch.Tensor,
88
+ ) -> torch.Tensor:
89
+ final_hidden_states = torch.zeros_like(hidden_states)
90
+ with torch.no_grad():
91
+ expert_mask = torch.nn.functional.one_hot(top_k_index, num_classes=self.num_experts)
92
+ expert_mask = expert_mask.permute(2, 1, 0)
93
+ expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero()
94
+
95
+ lora_dropout = getattr(self, "lora_dropout", nn.Identity())
96
+ lora_scaling = getattr(self, "lora_scaling", 1.0)
97
+
98
+ for expert_idx in expert_hit:
99
+ expert_idx = expert_idx[0]
100
+ if expert_idx == self.num_experts:
101
+ continue
102
+ top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
103
+ current_state = hidden_states[token_idx]
104
+ gate_up = nn.functional.linear(current_state, self.gate_up_proj[expert_idx])
105
+ if hasattr(self, "lora_gate_up_a"):
106
+ update = nn.functional.linear(
107
+ nn.functional.linear(lora_dropout(current_state), self.lora_gate_up_a[expert_idx]),
108
+ self.lora_gate_up_b[expert_idx],
109
+ )
110
+ gate_up = gate_up + lora_scaling * update
111
+ gate, up = gate_up.chunk(2, dim=-1)
112
+ current_hidden_states = self.act_fn(gate) * up
113
+ expert_output = nn.functional.linear(current_hidden_states, self.down_proj[expert_idx])
114
+ if hasattr(self, "lora_down_a"):
115
+ update = nn.functional.linear(
116
+ nn.functional.linear(lora_dropout(current_hidden_states), self.lora_down_a[expert_idx]),
117
+ self.lora_down_b[expert_idx],
118
+ )
119
+ expert_output = expert_output + lora_scaling * update
120
+ current_hidden_states = expert_output * top_k_weights[token_idx, top_k_pos, None]
121
+ final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype))
122
+
123
+ return final_hidden_states
124
+
125
+
126
+ def grouped_expert_offsets(
127
+ sorted_expert_ids: torch.Tensor,
128
+ num_experts: int,
129
+ ) -> torch.Tensor:
130
+ """Build grouped-mm offsets without MPS ``histc`` host synchronization."""
131
+ counts = torch.bincount(sorted_expert_ids, minlength=num_experts)[:num_experts]
132
+ return torch.cumsum(counts, dim=0, dtype=torch.int32)
133
+
134
+
135
+ def mps_grouped_mm_experts_forward(
136
+ self: torch.nn.Module,
137
+ hidden_states: torch.Tensor,
138
+ top_k_index: torch.Tensor,
139
+ top_k_weights: torch.Tensor,
140
+ ) -> torch.Tensor:
141
+ """Transformers grouped-mm MoE with histogram-free MPS routing offsets."""
142
+ num_top_k = top_k_index.size(-1)
143
+ num_tokens = hidden_states.size(0)
144
+ hidden_dim = hidden_states.size(-1)
145
+ sample_weights = top_k_weights.reshape(-1)
146
+ expert_ids = top_k_index.reshape(-1)
147
+
148
+ expert_ids_g, perm = torch.sort(expert_ids)
149
+ selected_hidden_states_g = hidden_states[perm // num_top_k]
150
+ sample_weights_g = sample_weights[perm]
151
+ offsets = grouped_expert_offsets(expert_ids_g, self.num_experts)
152
+
153
+ sentinel_mask = (expert_ids_g >= self.num_experts).unsqueeze(-1)
154
+ expert_ids_g.clamp_(max=self.num_experts - 1)
155
+ selected_hidden_states_g.masked_fill_(sentinel_mask, 0.0)
156
+
157
+ gate_up_weights = self.gate_up_proj if self.has_gate else self.up_proj
158
+ gate_up_biases = (
159
+ self.gate_up_proj_bias[expert_ids_g]
160
+ if self.has_gate and self.has_bias
161
+ else self.up_proj_bias[expert_ids_g]
162
+ if self.has_bias
163
+ else None
164
+ )
165
+ projected = _grouped_linear(
166
+ selected_hidden_states_g,
167
+ gate_up_weights,
168
+ offsets,
169
+ bias=gate_up_biases,
170
+ is_transposed=self.is_transposed,
171
+ )
172
+ projected = self._apply_gate(projected) if self.has_gate else self.act_fn(projected)
173
+
174
+ down_biases = self.down_proj_bias[expert_ids_g] if self.has_bias else None
175
+ projected = _grouped_linear(
176
+ projected,
177
+ self.down_proj,
178
+ offsets,
179
+ bias=down_biases,
180
+ is_transposed=self.is_transposed,
181
+ )
182
+ weighted = projected * sample_weights_g.unsqueeze(-1)
183
+ weighted.masked_fill_(sentinel_mask, 0.0)
184
+
185
+ # Scatter directly back to the original top-k order. Constructing the
186
+ # inverse permutation and then gathering performs the same permutation
187
+ # with an extra index tensor and an extra read pass over `weighted`.
188
+ reordered = torch.empty_like(weighted)
189
+ reordered.index_copy_(0, perm, weighted)
190
+ weighted = reordered
191
+ return weighted.view(num_tokens, num_top_k, hidden_dim).sum(dim=1).to(hidden_states.dtype)
192
+
193
+
194
+ class _IndependentExpertLoRA(torch.autograd.Function):
195
+ """Capacity-padded reference path for independent expert LoRA."""
196
+
197
+ @staticmethod
198
+ def forward(
199
+ ctx,
200
+ inputs: torch.Tensor,
201
+ weight_a: torch.Tensor,
202
+ weight_b: torch.Tensor,
203
+ expert_ids: torch.LongTensor,
204
+ counts: torch.LongTensor,
205
+ capacity: int,
206
+ ) -> torch.Tensor:
207
+ offsets = counts.cumsum(0)
208
+ starts = offsets - counts
209
+ slots = torch.arange(inputs.shape[0], device=inputs.device) - starts[expert_ids]
210
+ padded = inputs.new_zeros((weight_a.shape[0], capacity, inputs.shape[-1]))
211
+ padded[expert_ids, slots] = inputs
212
+ low_rank = torch.bmm(padded, weight_a.transpose(1, 2))
213
+ updates = torch.bmm(low_rank, weight_b.transpose(1, 2))
214
+ ctx.save_for_backward(
215
+ padded,
216
+ low_rank,
217
+ weight_a,
218
+ weight_b,
219
+ expert_ids,
220
+ slots,
221
+ )
222
+ return updates[expert_ids, slots]
223
+
224
+ @staticmethod
225
+ def backward(ctx, grad_output: torch.Tensor):
226
+ padded, low_rank, weight_a, weight_b, expert_ids, slots = ctx.saved_tensors
227
+ grad_padded = grad_output.new_zeros(
228
+ (weight_a.shape[0], padded.shape[1], grad_output.shape[-1])
229
+ )
230
+ grad_padded[expert_ids, slots] = grad_output
231
+ grad_weight_b = torch.bmm(grad_padded.transpose(1, 2), low_rank)
232
+ grad_low_rank = torch.bmm(grad_padded, weight_b)
233
+ grad_weight_a = torch.bmm(grad_low_rank.transpose(1, 2), padded)
234
+ grad_inputs_padded = torch.bmm(grad_low_rank, weight_a)
235
+ grad_inputs = grad_inputs_padded[expert_ids, slots]
236
+ return grad_inputs, grad_weight_a, grad_weight_b, None, None, None
237
+
238
+
239
+ _INDEPENDENT_EXPERT_LORA_METAL_SOURCE = r"""
240
+ #include <metal_stdlib>
241
+ using namespace metal;
242
+
243
+ constant uint kRank = 8;
244
+ constant uint kThreads = 256;
245
+
246
+ kernel void expert_lora_forward_a(
247
+ device const bfloat* inputs [[buffer(0)]],
248
+ device const bfloat* weight_a [[buffer(1)]],
249
+ device const long* expert_ids [[buffer(2)]],
250
+ device bfloat* low_rank [[buffer(3)]],
251
+ constant uint& row_count [[buffer(4)]],
252
+ constant uint& input_dim [[buffer(5)]],
253
+ uint row [[threadgroup_position_in_grid]],
254
+ uint lane [[thread_index_in_threadgroup]]) {
255
+ if (row >= row_count) return;
256
+ const uint expert = uint(expert_ids[row]);
257
+ threadgroup float partial[kThreads * kRank];
258
+ for (uint rank = 0; rank < kRank; ++rank) {
259
+ float value = 0.0f;
260
+ const uint a_base = (expert * kRank + rank) * input_dim;
261
+ const uint x_base = row * input_dim;
262
+ for (uint column = lane; column < input_dim; column += kThreads) {
263
+ value += float(inputs[x_base + column]) * float(weight_a[a_base + column]);
264
+ }
265
+ partial[lane * kRank + rank] = value;
266
+ }
267
+ threadgroup_barrier(mem_flags::mem_threadgroup);
268
+ for (uint stride = kThreads / 2; stride > 0; stride >>= 1) {
269
+ if (lane < stride) {
270
+ for (uint rank = 0; rank < kRank; ++rank) {
271
+ partial[lane * kRank + rank] +=
272
+ partial[(lane + stride) * kRank + rank];
273
+ }
274
+ }
275
+ threadgroup_barrier(mem_flags::mem_threadgroup);
276
+ }
277
+ if (lane == 0) {
278
+ for (uint rank = 0; rank < kRank; ++rank) {
279
+ low_rank[row * kRank + rank] = bfloat(partial[rank]);
280
+ }
281
+ }
282
+ }
283
+
284
+ kernel void expert_lora_forward_b(
285
+ device const bfloat* low_rank [[buffer(0)]],
286
+ device const bfloat* weight_b [[buffer(1)]],
287
+ device const long* expert_ids [[buffer(2)]],
288
+ device bfloat* output [[buffer(3)]],
289
+ constant uint& row_count [[buffer(4)]],
290
+ constant uint& output_dim [[buffer(5)]],
291
+ uint index [[thread_position_in_grid]]) {
292
+ const uint total = row_count * output_dim;
293
+ if (index >= total) return;
294
+ const uint row = index / output_dim;
295
+ const uint column = index - row * output_dim;
296
+ const uint expert = uint(expert_ids[row]);
297
+ const uint b_base = (expert * output_dim + column) * kRank;
298
+ float value = 0.0f;
299
+ for (uint rank = 0; rank < kRank; ++rank) {
300
+ value += float(low_rank[row * kRank + rank])
301
+ * float(weight_b[b_base + rank]);
302
+ }
303
+ output[index] = bfloat(value);
304
+ }
305
+
306
+ kernel void expert_lora_backward_low_rank(
307
+ device const bfloat* grad_output [[buffer(0)]],
308
+ device const bfloat* weight_b [[buffer(1)]],
309
+ device const long* expert_ids [[buffer(2)]],
310
+ device bfloat* grad_low_rank [[buffer(3)]],
311
+ constant uint& row_count [[buffer(4)]],
312
+ constant uint& output_dim [[buffer(5)]],
313
+ uint row [[threadgroup_position_in_grid]],
314
+ uint lane [[thread_index_in_threadgroup]]) {
315
+ if (row >= row_count) return;
316
+ const uint expert = uint(expert_ids[row]);
317
+ threadgroup float partial[kThreads * kRank];
318
+ for (uint rank = 0; rank < kRank; ++rank) {
319
+ float value = 0.0f;
320
+ const uint grad_base = row * output_dim;
321
+ for (uint column = lane; column < output_dim; column += kThreads) {
322
+ const uint b_index =
323
+ (expert * output_dim + column) * kRank + rank;
324
+ value += float(grad_output[grad_base + column])
325
+ * float(weight_b[b_index]);
326
+ }
327
+ partial[lane * kRank + rank] = value;
328
+ }
329
+ threadgroup_barrier(mem_flags::mem_threadgroup);
330
+ for (uint stride = kThreads / 2; stride > 0; stride >>= 1) {
331
+ if (lane < stride) {
332
+ for (uint rank = 0; rank < kRank; ++rank) {
333
+ partial[lane * kRank + rank] +=
334
+ partial[(lane + stride) * kRank + rank];
335
+ }
336
+ }
337
+ threadgroup_barrier(mem_flags::mem_threadgroup);
338
+ }
339
+ if (lane == 0) {
340
+ for (uint rank = 0; rank < kRank; ++rank) {
341
+ grad_low_rank[row * kRank + rank] = bfloat(partial[rank]);
342
+ }
343
+ }
344
+ }
345
+
346
+ kernel void expert_lora_backward_inputs(
347
+ device const bfloat* grad_low_rank [[buffer(0)]],
348
+ device const bfloat* weight_a [[buffer(1)]],
349
+ device const long* expert_ids [[buffer(2)]],
350
+ device bfloat* grad_inputs [[buffer(3)]],
351
+ constant uint& row_count [[buffer(4)]],
352
+ constant uint& input_dim [[buffer(5)]],
353
+ uint index [[thread_position_in_grid]]) {
354
+ const uint total = row_count * input_dim;
355
+ if (index >= total) return;
356
+ const uint row = index / input_dim;
357
+ const uint column = index - row * input_dim;
358
+ const uint expert = uint(expert_ids[row]);
359
+ float value = 0.0f;
360
+ for (uint rank = 0; rank < kRank; ++rank) {
361
+ const uint a_index =
362
+ (expert * kRank + rank) * input_dim + column;
363
+ value += float(grad_low_rank[row * kRank + rank])
364
+ * float(weight_a[a_index]);
365
+ }
366
+ grad_inputs[index] = bfloat(value);
367
+ }
368
+
369
+ kernel void expert_lora_backward_a(
370
+ device const bfloat* grad_low_rank [[buffer(0)]],
371
+ device const bfloat* inputs [[buffer(1)]],
372
+ device const long* offsets [[buffer(2)]],
373
+ device bfloat* grad_weight_a [[buffer(3)]],
374
+ constant uint& expert_count [[buffer(4)]],
375
+ constant uint& input_dim [[buffer(5)]],
376
+ uint index [[thread_position_in_grid]]) {
377
+ const uint total = expert_count * kRank * input_dim;
378
+ if (index >= total) return;
379
+ const uint column = index % input_dim;
380
+ const uint rank_expert = index / input_dim;
381
+ const uint rank = rank_expert % kRank;
382
+ const uint expert = rank_expert / kRank;
383
+ const uint start = uint(offsets[expert]);
384
+ const uint stop = uint(offsets[expert + 1]);
385
+ float value = 0.0f;
386
+ for (uint row = start; row < stop; ++row) {
387
+ value += float(grad_low_rank[row * kRank + rank])
388
+ * float(inputs[row * input_dim + column]);
389
+ }
390
+ grad_weight_a[index] = bfloat(value);
391
+ }
392
+
393
+ kernel void expert_lora_backward_b(
394
+ device const bfloat* grad_output [[buffer(0)]],
395
+ device const bfloat* low_rank [[buffer(1)]],
396
+ device const long* offsets [[buffer(2)]],
397
+ device bfloat* grad_weight_b [[buffer(3)]],
398
+ constant uint& expert_count [[buffer(4)]],
399
+ constant uint& output_dim [[buffer(5)]],
400
+ uint index [[thread_position_in_grid]]) {
401
+ const uint total = expert_count * output_dim * kRank;
402
+ if (index >= total) return;
403
+ const uint rank = index % kRank;
404
+ const uint output_expert = index / kRank;
405
+ const uint column = output_expert % output_dim;
406
+ const uint expert = output_expert / output_dim;
407
+ const uint start = uint(offsets[expert]);
408
+ const uint stop = uint(offsets[expert + 1]);
409
+ float value = 0.0f;
410
+ for (uint row = start; row < stop; ++row) {
411
+ value += float(grad_output[row * output_dim + column])
412
+ * float(low_rank[row * kRank + rank]);
413
+ }
414
+ grad_weight_b[index] = bfloat(value);
415
+ }
416
+ """
417
+
418
+
419
+ _independent_expert_lora_metal_library = None
420
+
421
+
422
+ def _get_independent_expert_lora_metal_library():
423
+ global _independent_expert_lora_metal_library
424
+ if _independent_expert_lora_metal_library is None:
425
+ _independent_expert_lora_metal_library = torch.mps.compile_shader(
426
+ _INDEPENDENT_EXPERT_LORA_METAL_SOURCE
427
+ )
428
+ return _independent_expert_lora_metal_library
429
+
430
+
431
+ class _MetalIndependentExpertLoRA(torch.autograd.Function):
432
+ """Independent rank-8 expert A/B with direct BF16 Metal forward/backward."""
433
+
434
+ @staticmethod
435
+ def forward(
436
+ ctx,
437
+ inputs: torch.Tensor,
438
+ weight_a: torch.Tensor,
439
+ weight_b: torch.Tensor,
440
+ expert_ids: torch.LongTensor,
441
+ counts: torch.LongTensor,
442
+ ) -> torch.Tensor:
443
+ inputs = inputs.contiguous()
444
+ weight_a = weight_a.contiguous()
445
+ weight_b = weight_b.contiguous()
446
+ expert_ids = expert_ids.contiguous()
447
+ expert_count, rank, input_dim = weight_a.shape
448
+ row_count = inputs.shape[0]
449
+ output_dim = weight_b.shape[1]
450
+ if rank != 8:
451
+ raise ValueError("The direct Metal expert LoRA kernel requires rank 8.")
452
+ offsets = torch.cat(
453
+ (
454
+ counts.new_zeros(1, dtype=torch.int64),
455
+ counts.cumsum(0, dtype=torch.int64),
456
+ )
457
+ ).contiguous()
458
+ low_rank = inputs.new_empty((row_count, rank))
459
+ output = inputs.new_empty((row_count, output_dim))
460
+ library = _get_independent_expert_lora_metal_library()
461
+ library.expert_lora_forward_a(
462
+ inputs,
463
+ weight_a,
464
+ expert_ids,
465
+ low_rank,
466
+ row_count,
467
+ input_dim,
468
+ threads=(row_count * 256, 1, 1),
469
+ group_size=(256, 1, 1),
470
+ )
471
+ library.expert_lora_forward_b(
472
+ low_rank,
473
+ weight_b,
474
+ expert_ids,
475
+ output,
476
+ row_count,
477
+ output_dim,
478
+ threads=(row_count * output_dim, 1, 1),
479
+ group_size=(256, 1, 1),
480
+ )
481
+ ctx.save_for_backward(
482
+ inputs,
483
+ low_rank,
484
+ weight_a,
485
+ weight_b,
486
+ expert_ids,
487
+ offsets,
488
+ )
489
+ ctx.dimensions = (expert_count, row_count, input_dim, output_dim)
490
+ return output
491
+
492
+ @staticmethod
493
+ def backward(ctx, grad_output: torch.Tensor):
494
+ inputs, low_rank, weight_a, weight_b, expert_ids, offsets = ctx.saved_tensors
495
+ expert_count, row_count, input_dim, output_dim = ctx.dimensions
496
+ grad_output = grad_output.contiguous()
497
+ grad_low_rank = low_rank.new_empty(low_rank.shape)
498
+ grad_inputs = inputs.new_empty(inputs.shape)
499
+ grad_weight_a = weight_a.new_empty(weight_a.shape)
500
+ grad_weight_b = weight_b.new_empty(weight_b.shape)
501
+ library = _get_independent_expert_lora_metal_library()
502
+ library.expert_lora_backward_low_rank(
503
+ grad_output,
504
+ weight_b,
505
+ expert_ids,
506
+ grad_low_rank,
507
+ row_count,
508
+ output_dim,
509
+ threads=(row_count * 256, 1, 1),
510
+ group_size=(256, 1, 1),
511
+ )
512
+ library.expert_lora_backward_inputs(
513
+ grad_low_rank,
514
+ weight_a,
515
+ expert_ids,
516
+ grad_inputs,
517
+ row_count,
518
+ input_dim,
519
+ threads=(row_count * input_dim, 1, 1),
520
+ group_size=(256, 1, 1),
521
+ )
522
+ library.expert_lora_backward_a(
523
+ grad_low_rank,
524
+ inputs,
525
+ offsets,
526
+ grad_weight_a,
527
+ expert_count,
528
+ input_dim,
529
+ threads=(expert_count * 8 * input_dim, 1, 1),
530
+ group_size=(256, 1, 1),
531
+ )
532
+ library.expert_lora_backward_b(
533
+ grad_output,
534
+ low_rank,
535
+ offsets,
536
+ grad_weight_b,
537
+ expert_count,
538
+ output_dim,
539
+ threads=(expert_count * output_dim * 8, 1, 1),
540
+ group_size=(256, 1, 1),
541
+ )
542
+ return grad_inputs, grad_weight_a, grad_weight_b, None, None
543
+
544
+
545
+ def _independent_expert_lora(
546
+ inputs: torch.Tensor,
547
+ weight_a: torch.Tensor,
548
+ weight_b: torch.Tensor,
549
+ expert_ids: torch.LongTensor,
550
+ counts: torch.LongTensor,
551
+ ) -> torch.Tensor:
552
+ """Dispatch the compact independent-expert operator without sharing A/B."""
553
+
554
+ if inputs.shape[0] == 0:
555
+ return inputs.new_empty((0, weight_b.shape[1]))
556
+ if (
557
+ inputs.device.type == "mps"
558
+ and inputs.dtype == torch.bfloat16
559
+ and weight_a.dtype == torch.bfloat16
560
+ and weight_b.dtype == torch.bfloat16
561
+ and weight_a.shape[1] == 8
562
+ and hasattr(torch.mps, "compile_shader")
563
+ ):
564
+ return _MetalIndependentExpertLoRA.apply(
565
+ inputs,
566
+ weight_a,
567
+ weight_b,
568
+ expert_ids,
569
+ counts,
570
+ )
571
+ return _IndependentExpertLoRA.apply(
572
+ inputs,
573
+ weight_a,
574
+ weight_b,
575
+ expert_ids,
576
+ counts,
577
+ int(counts.max().detach().cpu()),
578
+ )
579
+
580
+
581
+ def mps_segmented_experts_forward(
582
+ self: torch.nn.Module,
583
+ hidden_states: torch.Tensor,
584
+ top_k_index: torch.Tensor,
585
+ top_k_weights: torch.Tensor,
586
+ ) -> torch.Tensor:
587
+ """Run GPU-grouped base experts plus independent Metal LoRA A/B."""
588
+ if hidden_states.device.type != "mps":
589
+ return lora_aware_eager_experts_forward(
590
+ self,
591
+ hidden_states,
592
+ top_k_index,
593
+ top_k_weights,
594
+ )
595
+
596
+ num_top_k = top_k_index.size(-1)
597
+ num_tokens, hidden_dim = hidden_states.shape
598
+ sample_weights = top_k_weights.reshape(-1)
599
+ expert_ids = top_k_index.reshape(-1)
600
+ expert_ids_g, permutation = torch.sort(expert_ids)
601
+ selected_hidden_g = hidden_states[permutation // num_top_k]
602
+ sample_weights_g = sample_weights[permutation]
603
+
604
+ counts = torch.bincount(
605
+ expert_ids_g,
606
+ minlength=self.num_experts,
607
+ )[: self.num_experts]
608
+ offsets = torch.cumsum(counts, dim=0, dtype=torch.int32)
609
+ gate_up_weights = self.gate_up_proj if self.has_gate else self.up_proj
610
+ gate_up_bias = None
611
+ if self.has_bias:
612
+ packed_bias = self.gate_up_proj_bias if self.has_gate else self.up_proj_bias
613
+ gate_up_bias = packed_bias[expert_ids_g]
614
+ gate_up_g = _grouped_linear(
615
+ selected_hidden_g,
616
+ gate_up_weights,
617
+ offsets,
618
+ bias=gate_up_bias,
619
+ is_transposed=self.is_transposed,
620
+ )
621
+ if hasattr(self, "lora_gate_up_a") and self.training:
622
+ gate_up_update = _independent_expert_lora(
623
+ self.lora_dropout(selected_hidden_g),
624
+ self.lora_gate_up_a,
625
+ self.lora_gate_up_b,
626
+ expert_ids_g,
627
+ counts,
628
+ )
629
+ gate_up_g = gate_up_g + gate_up_update * self.lora_scaling
630
+ elif hasattr(self, "lora_gate_up_a"):
631
+ gate_up_update = _independent_expert_lora(
632
+ selected_hidden_g,
633
+ self.lora_gate_up_a,
634
+ self.lora_gate_up_b,
635
+ expert_ids_g,
636
+ counts,
637
+ )
638
+ gate_up_g = gate_up_g + gate_up_update * self.lora_scaling
639
+ activated_g = (
640
+ self._apply_gate(gate_up_g)
641
+ if self.has_gate
642
+ else self.act_fn(gate_up_g)
643
+ )
644
+ down_bias = self.down_proj_bias[expert_ids_g] if self.has_bias else None
645
+ projected_g = _grouped_linear(
646
+ activated_g,
647
+ self.down_proj,
648
+ offsets,
649
+ bias=down_bias,
650
+ is_transposed=self.is_transposed,
651
+ )
652
+ if hasattr(self, "lora_down_a") and self.training:
653
+ down_update = _independent_expert_lora(
654
+ self.lora_dropout(activated_g),
655
+ self.lora_down_a,
656
+ self.lora_down_b,
657
+ expert_ids_g,
658
+ counts,
659
+ )
660
+ projected_g = projected_g + down_update * self.lora_scaling
661
+ elif hasattr(self, "lora_down_a"):
662
+ down_update = _independent_expert_lora(
663
+ activated_g,
664
+ self.lora_down_a,
665
+ self.lora_down_b,
666
+ expert_ids_g,
667
+ counts,
668
+ )
669
+ projected_g = projected_g + down_update * self.lora_scaling
670
+ weighted_g = projected_g * sample_weights_g[:, None]
671
+ weighted = torch.empty_like(weighted_g)
672
+ weighted.index_copy_(0, permutation, weighted_g)
673
+ return weighted.view(num_tokens, num_top_k, hidden_dim).sum(dim=1).to(hidden_states.dtype)
674
+
675
+
676
+ _SDPA_MASK_FLOOR = -1.0e9
677
+ _LONG_KV_CHUNK = 256
678
+ _LONG_KV_THRESHOLD = 2048
679
+ _original_sdpa_attention_forward = ALL_ATTENTION_FUNCTIONS.get_interface("sdpa", None)
680
+
681
+
682
+ class _MpsGqaChunkedAttention(torch.autograd.Function):
683
+ """Exact GQA attention that never repeats long KV onto every query head.
684
+
685
+ Transformers' SDPA path `repeat_kv`s 2 global heads to 16 for full
686
+ attention. On an 8k prefix that materializes ~2GB of K and V per layer;
687
+ mlp-moe retains all 5 full layers, and the second denoise still holds the
688
+ first latent graph. MPS then returns NaN LoRA grads with a finite loss.
689
+ Saving compact GQA tensors and reducing K in chunks keeps the math
690
+ identical while staying inside unified memory.
691
+ """
692
+
693
+ @staticmethod
694
+ def forward(
695
+ ctx,
696
+ query: torch.Tensor,
697
+ key: torch.Tensor,
698
+ value: torch.Tensor,
699
+ attn_mask: torch.Tensor | None,
700
+ scale: float,
701
+ chunk_size: int,
702
+ ) -> torch.Tensor:
703
+ query_fp32 = query.float()
704
+ key_fp32 = key.float()
705
+ value_fp32 = value.float()
706
+ batch, query_heads, queries, head_dim = query_fp32.shape
707
+ key_heads = int(key_fp32.shape[1])
708
+ key_len = int(key_fp32.shape[-2])
709
+ repeats = query_heads // key_heads
710
+ packed = batch * query_heads
711
+ query_flat = query_fp32.reshape(packed, queries, head_dim)
712
+ negative_inf = query_fp32.new_full((), _SDPA_MASK_FLOOR)
713
+ running_max = query_fp32.new_full((packed, queries, 1), float("-inf"))
714
+ running_sum = torch.zeros_like(running_max)
715
+ acc = torch.zeros(
716
+ packed, queries, head_dim,
717
+ device=query_fp32.device, dtype=query_fp32.dtype,
718
+ )
719
+ chunk = max(int(chunk_size), 1)
720
+
721
+ def _pack_kv(tensor: torch.Tensor, start: int, stop: int) -> torch.Tensor:
722
+ width = stop - start
723
+ packed_kv = tensor[:, :, start:stop, :].unsqueeze(2).expand(
724
+ batch, key_heads, repeats, width, head_dim
725
+ )
726
+ return packed_kv.reshape(packed, width, head_dim)
727
+
728
+ for start in range(0, key_len, chunk):
729
+ stop = min(start + chunk, key_len)
730
+ key_flat = _pack_kv(key_fp32, start, stop)
731
+ value_flat = _pack_kv(value_fp32, start, stop)
732
+ scores = torch.bmm(query_flat, key_flat.transpose(1, 2)) * scale
733
+ if attn_mask is not None:
734
+ mask_chunk = attn_mask[..., start:stop]
735
+ if mask_chunk.dtype == torch.bool:
736
+ scores = scores.masked_fill(
737
+ ~mask_chunk.expand(batch, query_heads, queries, stop - start).reshape(
738
+ packed, queries, stop - start
739
+ ),
740
+ negative_inf,
741
+ )
742
+ else:
743
+ scores = scores + mask_chunk.float().expand(
744
+ batch, query_heads, queries, stop - start
745
+ ).reshape(packed, queries, stop - start)
746
+ block_max = scores.amax(dim=-1, keepdim=True)
747
+ new_max = torch.maximum(running_max, block_max)
748
+ weights = torch.exp(scores - new_max)
749
+ rescale = torch.exp(running_max - new_max)
750
+ running_sum = running_sum * rescale + weights.sum(dim=-1, keepdim=True)
751
+ acc = acc * rescale + torch.bmm(weights, value_flat)
752
+ running_max = new_max
753
+ output = acc / running_sum.clamp_min(torch.finfo(running_sum.dtype).tiny)
754
+ output = output.view(batch, query_heads, queries, head_dim)
755
+ ctx.save_for_backward(
756
+ query, key, value,
757
+ attn_mask if attn_mask is not None else query.new_empty(0),
758
+ )
759
+ ctx.scale = float(scale)
760
+ ctx.chunk_size = chunk
761
+ ctx.has_mask = attn_mask is not None
762
+ return output.to(dtype=query.dtype)
763
+
764
+ @staticmethod
765
+ def backward(ctx, grad_output: torch.Tensor):
766
+ query, key, value, mask_or_empty = ctx.saved_tensors
767
+ attn_mask = mask_or_empty if ctx.has_mask else None
768
+ scale = ctx.scale
769
+ chunk = ctx.chunk_size
770
+ query_fp32 = query.float()
771
+ key_fp32 = key.float()
772
+ value_fp32 = value.float()
773
+ grad_out = grad_output.float()
774
+ batch, query_heads, queries, head_dim = query_fp32.shape
775
+ key_heads = int(key_fp32.shape[1])
776
+ key_len = int(key_fp32.shape[-2])
777
+ repeats = query_heads // key_heads
778
+ packed = batch * query_heads
779
+ query_flat = query_fp32.reshape(packed, queries, head_dim)
780
+ grad_out_flat = grad_out.reshape(packed, queries, head_dim)
781
+ negative_inf = query_fp32.new_full((), _SDPA_MASK_FLOOR)
782
+ running_max = query_fp32.new_full((packed, queries, 1), float("-inf"))
783
+ running_sum = torch.zeros_like(running_max)
784
+
785
+ def _pack_kv(tensor: torch.Tensor, start: int, stop: int) -> torch.Tensor:
786
+ width = stop - start
787
+ packed_kv = tensor[:, :, start:stop, :].unsqueeze(2).expand(
788
+ batch, key_heads, repeats, width, head_dim
789
+ )
790
+ return packed_kv.reshape(packed, width, head_dim)
791
+
792
+ def _apply_mask(scores: torch.Tensor, start: int, stop: int) -> torch.Tensor:
793
+ if attn_mask is None:
794
+ return scores
795
+ mask_chunk = attn_mask[..., start:stop]
796
+ width = stop - start
797
+ if mask_chunk.dtype == torch.bool:
798
+ return scores.masked_fill(
799
+ ~mask_chunk.expand(batch, query_heads, queries, width).reshape(
800
+ packed, queries, width
801
+ ),
802
+ negative_inf,
803
+ )
804
+ return scores + mask_chunk.float().expand(
805
+ batch, query_heads, queries, width
806
+ ).reshape(packed, queries, width)
807
+
808
+ for start in range(0, key_len, chunk):
809
+ stop = min(start + chunk, key_len)
810
+ scores = _apply_mask(
811
+ torch.bmm(query_flat, _pack_kv(key_fp32, start, stop).transpose(1, 2)) * scale,
812
+ start,
813
+ stop,
814
+ )
815
+ block_max = scores.amax(dim=-1, keepdim=True)
816
+ new_max = torch.maximum(running_max, block_max)
817
+ running_sum = running_sum * torch.exp(running_max - new_max) + torch.exp(
818
+ scores - new_max
819
+ ).sum(dim=-1, keepdim=True)
820
+ running_max = new_max
821
+ grad_query_flat = torch.zeros_like(query_flat)
822
+ grad_key = torch.zeros_like(key_fp32)
823
+ grad_value = torch.zeros_like(value_fp32)
824
+ for start in range(0, key_len, chunk):
825
+ stop = min(start + chunk, key_len)
826
+ width = stop - start
827
+ key_flat = _pack_kv(key_fp32, start, stop)
828
+ value_flat = _pack_kv(value_fp32, start, stop)
829
+ scores = _apply_mask(
830
+ torch.bmm(query_flat, key_flat.transpose(1, 2)) * scale,
831
+ start,
832
+ stop,
833
+ )
834
+ probs = torch.exp(scores - running_max) / running_sum.clamp_min(
835
+ torch.finfo(running_sum.dtype).tiny
836
+ )
837
+ grad_value_packed = torch.bmm(probs.transpose(1, 2), grad_out_flat)
838
+ grad_value[:, :, start:stop, :] = grad_value_packed.view(
839
+ batch, key_heads, repeats, width, head_dim
840
+ ).sum(dim=2)
841
+ grad_probs = torch.bmm(grad_out_flat, value_flat.transpose(1, 2))
842
+ delta = grad_probs - (grad_probs * probs).sum(dim=-1, keepdim=True)
843
+ grad_scores = probs * delta
844
+ grad_query_flat = grad_query_flat + torch.bmm(grad_scores, key_flat) * scale
845
+ grad_key_packed = torch.bmm(grad_scores.transpose(1, 2), query_flat) * scale
846
+ grad_key[:, :, start:stop, :] = grad_key_packed.view(
847
+ batch, key_heads, repeats, width, head_dim
848
+ ).sum(dim=2)
849
+ return (
850
+ grad_query_flat.view(batch, query_heads, queries, head_dim).to(dtype=query.dtype),
851
+ grad_key.to(dtype=key.dtype),
852
+ grad_value.to(dtype=value.dtype),
853
+ None,
854
+ None,
855
+ None,
856
+ )
857
+
858
+
859
+ def mps_safe_sdpa_attention_forward(
860
+ module: nn.Module,
861
+ query: torch.Tensor,
862
+ key: torch.Tensor,
863
+ value: torch.Tensor,
864
+ attention_mask: torch.Tensor | None,
865
+ dropout: float = 0.0,
866
+ scaling: float | None = None,
867
+ is_causal: bool | None = None,
868
+ position_bias: torch.Tensor | None = None,
869
+ **kwargs,
870
+ ) -> tuple[torch.Tensor, None]:
871
+ """Use compact GQA attention for long MPS decoder backwards; else official SDPA."""
872
+
873
+ kv_length = int(key.shape[-2])
874
+ use_chunked = (
875
+ query.device.type == "mps"
876
+ and torch.is_grad_enabled()
877
+ and query.requires_grad
878
+ and kv_length >= _LONG_KV_THRESHOLD
879
+ and dropout == 0.0
880
+ and position_bias is None
881
+ and not kwargs.get("output_attentions", False)
882
+ )
883
+ if not use_chunked:
884
+ return _original_sdpa_attention_forward(
885
+ module, query, key, value, attention_mask,
886
+ dropout=dropout, scaling=scaling, is_causal=is_causal,
887
+ position_bias=position_bias, **kwargs,
888
+ )
889
+ scale = 1.0 if scaling is None else float(scaling)
890
+ output = _MpsGqaChunkedAttention.apply(
891
+ query, key, value, attention_mask, scale, _LONG_KV_CHUNK,
892
+ )
893
+ return output.transpose(1, 2).contiguous(), None
894
+
895
+
896
+ def register_mps_backends() -> None:
897
+ """Register the schema25 expert and attention implementations explicitly."""
898
+
899
+ ALL_EXPERTS_FUNCTIONS.register("lora_eager", lora_aware_eager_experts_forward)
900
+ ALL_EXPERTS_FUNCTIONS.register("modilify_mk2_lora", inference_experts_forward)
901
+ ALL_EXPERTS_FUNCTIONS.register("mps_grouped_mm", mps_grouped_mm_experts_forward)
902
+ ALL_EXPERTS_FUNCTIONS.register("mps_segmented", mps_segmented_experts_forward)
903
+ ALL_ATTENTION_FUNCTIONS.register("sdpa", mps_safe_sdpa_attention_forward)
904
+
905
+
906
+ __all__ = [
907
+ "attach_expert_lora",
908
+ "grouped_expert_offsets",
909
+ "lora_aware_eager_experts_forward",
910
+ "mps_grouped_mm_experts_forward",
911
+ "mps_safe_sdpa_attention_forward",
912
+ "mps_segmented_experts_forward",
913
+ "register_mps_backends",
914
+ ]
915
+
916
+
917
+ def inference_experts_forward(self, hidden_states, top_k_index, top_k_weights):
918
+ """Use the reference MPS path locally and the Torch fallback elsewhere."""
919
+ implementation = (mps_segmented_experts_forward if hidden_states.device.type == "mps"
920
+ else lora_aware_eager_experts_forward)
921
+ return implementation(self, hidden_states, top_k_index, top_k_weights)
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ torch>=2.10
2
+ transformers==5.14.1
3
+ accelerate>=1.14
4
+ safetensors>=0.8
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc8d3a0ce36466ccc1278bf987df5f71db1719b9ca6b4118264f45cb627bfe0f
3
+ size 32169626
tokenizer_config.json ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "audio_token": "<|audio|>",
3
+ "backend": "tokenizers",
4
+ "boa_token": "<|audio>",
5
+ "boi_token": "<|image>",
6
+ "bos_token": "<bos>",
7
+ "eoa_token": "<audio|>",
8
+ "eoc_token": "<channel|>",
9
+ "eoi_token": "<image|>",
10
+ "eos_token": "<eos>",
11
+ "eot_token": "<turn|>",
12
+ "escape_token": "<|\"|>",
13
+ "etc_token": "<tool_call|>",
14
+ "etd_token": "<tool|>",
15
+ "etr_token": "<tool_response|>",
16
+ "extra_special_tokens": [
17
+ "<|video|>"
18
+ ],
19
+ "image_token": "<|image|>",
20
+ "is_local": true,
21
+ "local_files_only": true,
22
+ "mask_token": "<mask>",
23
+ "model_max_length": 1000000000000000019884624838656,
24
+ "model_specific_special_tokens": {
25
+ "audio_token": "<|audio|>",
26
+ "boa_token": "<|audio>",
27
+ "boi_token": "<|image>",
28
+ "eoa_token": "<audio|>",
29
+ "eoc_token": "<channel|>",
30
+ "eoi_token": "<image|>",
31
+ "eot_token": "<turn|>",
32
+ "escape_token": "<|\"|>",
33
+ "etc_token": "<tool_call|>",
34
+ "etd_token": "<tool|>",
35
+ "etr_token": "<tool_response|>",
36
+ "image_token": "<|image|>",
37
+ "soc_token": "<|channel>",
38
+ "sot_token": "<|turn>",
39
+ "stc_token": "<|tool_call>",
40
+ "std_token": "<|tool>",
41
+ "str_token": "<|tool_response>",
42
+ "think_token": "<|think|>"
43
+ },
44
+ "pad_token": "<pad>",
45
+ "padding_side": "left",
46
+ "processor_class": "Gemma4Processor",
47
+ "response_schema": {
48
+ "properties": {
49
+ "content": {
50
+ "type": "string"
51
+ },
52
+ "role": {
53
+ "const": "assistant"
54
+ },
55
+ "thinking": {
56
+ "type": "string"
57
+ },
58
+ "tool_calls": {
59
+ "items": {
60
+ "properties": {
61
+ "function": {
62
+ "properties": {
63
+ "arguments": {
64
+ "additionalProperties": {},
65
+ "type": "object",
66
+ "x-parser": "gemma4-tool-call"
67
+ },
68
+ "name": {
69
+ "type": "string"
70
+ }
71
+ },
72
+ "type": "object",
73
+ "x-regex": "call\\:(?P<name>\\w+)(?P<arguments>\\{.*\\})"
74
+ },
75
+ "type": {
76
+ "const": "function"
77
+ }
78
+ },
79
+ "type": "object"
80
+ },
81
+ "type": "array",
82
+ "x-regex-iterator": "<\\|tool_call>(.*?)<tool_call\\|>"
83
+ }
84
+ },
85
+ "type": "object",
86
+ "x-regex": "(\\<\\|channel\\>thought\\n(?P<thinking>.*?)\\<channel\\|\\>)?(?P<tool_calls>\\<\\|tool_call\\>.*\\<tool_call\\|\\>)?(?P<content>(?:(?!\\<turn\\|\\>)(?!\\<\\|tool_response\\>).)+)?(?:\\<turn\\|\\>|\\<\\|tool_response\\>)?"
87
+ },
88
+ "soc_token": "<|channel>",
89
+ "sot_token": "<|turn>",
90
+ "stc_token": "<|tool_call>",
91
+ "std_token": "<|tool>",
92
+ "str_token": "<|tool_response>",
93
+ "think_token": "<|think|>",
94
+ "tokenizer_class": "GemmaTokenizer",
95
+ "unk_token": "<unk>"
96
+ }
vocab_ops.py ADDED
@@ -0,0 +1,643 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Exact memory-bounded vocabulary projection, sampling, and backward."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Sequence
7
+
8
+ import torch
9
+ from torch.nn import functional as F
10
+
11
+
12
+ def _stable_max(values: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
13
+ """Return ``max`` indices that stay in-range on MPS NaN/-inf rows."""
14
+
15
+ best, index = values.max(dim=-1)
16
+ width = values.shape[-1]
17
+ if width <= 0:
18
+ return best, index
19
+ return best, index.clamp(0, width - 1)
20
+
21
+
22
+ class _ChunkedVocabStatistics(torch.autograd.Function):
23
+ """One projection pass for CE and exact Gumbel-max categorical sampling."""
24
+
25
+ @staticmethod
26
+ def forward(
27
+ ctx,
28
+ hidden: torch.Tensor,
29
+ weight: torch.Tensor,
30
+ labels: torch.LongTensor,
31
+ softcap: float,
32
+ temperature: float,
33
+ chunk_size: int,
34
+ top_k: int | None = 40,
35
+ min_p: float | None = 0.05,
36
+ ) -> tuple[
37
+ torch.Tensor,
38
+ torch.Tensor,
39
+ torch.LongTensor,
40
+ torch.Tensor,
41
+ torch.Tensor,
42
+ torch.LongTensor,
43
+ torch.Tensor,
44
+ ]:
45
+ if hidden.ndim != 2 or weight.ndim != 2:
46
+ raise ValueError("Chunked vocabulary tensors must be matrices.")
47
+ if hidden.shape[1] != weight.shape[1]:
48
+ raise ValueError("Hidden and vocabulary projection dimensions differ.")
49
+ if temperature <= 0 or chunk_size <= 0:
50
+ raise ValueError("Temperature and vocabulary chunk size must be positive.")
51
+ labels = labels.to(device=hidden.device, dtype=torch.long)
52
+ if labels.shape != (hidden.shape[0],):
53
+ raise ValueError("Labels must contain one value per hidden row.")
54
+
55
+ rows = hidden.shape[0]
56
+ row_indices = torch.arange(rows, device=hidden.device)
57
+ valid = labels.ge(0)
58
+ raw_log_z = torch.full(
59
+ (rows,), -torch.inf, device=hidden.device, dtype=torch.float32
60
+ )
61
+ sample_log_z = torch.full_like(raw_log_z, -torch.inf)
62
+ gold_score = torch.zeros_like(raw_log_z)
63
+ best_gumbel = torch.full_like(raw_log_z, -torch.inf)
64
+ selected_score = torch.zeros_like(raw_log_z)
65
+ selected = torch.zeros(rows, device=hidden.device, dtype=torch.long)
66
+ greedy_score = torch.full_like(raw_log_z, -torch.inf)
67
+ greedy = torch.zeros(rows, device=hidden.device, dtype=torch.long)
68
+ moment_max = torch.full_like(raw_log_z, -torch.inf)
69
+ moment_sum = torch.zeros_like(raw_log_z)
70
+ moment_weighted = torch.zeros_like(raw_log_z)
71
+ vocab_size = weight.shape[0]
72
+
73
+ use_constrained = top_k is not None and top_k > 0
74
+ chunk_cand_scores = []
75
+ chunk_cand_tokens = []
76
+
77
+ with torch.no_grad():
78
+ for start in range(0, vocab_size, int(chunk_size)):
79
+ stop = min(start + int(chunk_size), vocab_size)
80
+ raw = F.linear(hidden, weight[start:stop])
81
+ scores = (
82
+ torch.tanh(raw.float() / float(softcap))
83
+ * float(softcap)
84
+ )
85
+ raw_log_z = torch.logaddexp(
86
+ raw_log_z,
87
+ torch.logsumexp(scores, dim=-1),
88
+ )
89
+ sample_scores = scores / float(temperature)
90
+ sample_log_z = torch.logaddexp(
91
+ sample_log_z,
92
+ torch.logsumexp(sample_scores, dim=-1),
93
+ )
94
+
95
+ in_chunk = valid & labels.ge(start) & labels.lt(stop)
96
+ local_gold = (labels - start).clamp(0, stop - start - 1)
97
+ gold_score = torch.where(
98
+ in_chunk,
99
+ scores[row_indices, local_gold],
100
+ gold_score,
101
+ )
102
+
103
+ if use_constrained:
104
+ k = min(int(top_k), stop - start)
105
+ c_score, c_idx = torch.topk(sample_scores, k=k, dim=-1)
106
+ chunk_cand_scores.append(c_score)
107
+ chunk_cand_tokens.append(c_idx + start)
108
+ else:
109
+ uniform = torch.rand(
110
+ sample_scores.shape,
111
+ device=sample_scores.device,
112
+ dtype=torch.float32,
113
+ ).clamp_(
114
+ min=torch.finfo(torch.float32).tiny,
115
+ max=1.0 - torch.finfo(torch.float32).eps,
116
+ )
117
+ gumbel_scores = sample_scores - torch.log(-torch.log(uniform))
118
+ chunk_best, chunk_index = _stable_max(gumbel_scores)
119
+ replace_best = chunk_best.gt(best_gumbel)
120
+ candidate_score = sample_scores.gather(
121
+ 1, chunk_index[:, None]
122
+ ).squeeze(-1)
123
+ best_gumbel = torch.maximum(best_gumbel, chunk_best)
124
+ selected = torch.where(
125
+ replace_best,
126
+ chunk_index + start,
127
+ selected,
128
+ )
129
+ selected_score = torch.where(
130
+ replace_best,
131
+ candidate_score,
132
+ selected_score,
133
+ )
134
+
135
+ chunk_max, chunk_argmax = _stable_max(sample_scores)
136
+ replace_greedy = chunk_max.gt(greedy_score)
137
+ greedy_score = torch.maximum(greedy_score, chunk_max)
138
+ greedy = torch.where(
139
+ replace_greedy,
140
+ chunk_argmax + start,
141
+ greedy,
142
+ )
143
+ shifted = torch.exp(sample_scores - chunk_max[:, None])
144
+ chunk_sum = shifted.sum(dim=-1)
145
+ chunk_weighted = (shifted * sample_scores).sum(dim=-1)
146
+ merged_max = torch.maximum(moment_max, chunk_max)
147
+ previous_scale = torch.exp(moment_max - merged_max)
148
+ chunk_scale = torch.exp(chunk_max - merged_max)
149
+ moment_sum = (
150
+ moment_sum * previous_scale + chunk_sum * chunk_scale
151
+ )
152
+ moment_weighted = (
153
+ moment_weighted * previous_scale
154
+ + chunk_weighted * chunk_scale
155
+ )
156
+ moment_max = merged_max
157
+
158
+ if use_constrained:
159
+ all_scores = torch.cat(chunk_cand_scores, dim=-1)
160
+ all_tokens = torch.cat(chunk_cand_tokens, dim=-1)
161
+ global_k = min(int(top_k), all_scores.shape[-1])
162
+ cand_scores, global_idx = torch.topk(all_scores, k=global_k, dim=-1)
163
+ cand_tokens = all_tokens.gather(dim=-1, index=global_idx)
164
+ if min_p is not None and min_p > 0:
165
+ thresh = greedy_score + math.log(float(min_p))
166
+ valid_mask = cand_scores >= thresh[:, None]
167
+ eligible = cand_scores.masked_fill(~valid_mask, -float("inf"))
168
+ else:
169
+ eligible = cand_scores
170
+ uniform = torch.rand(
171
+ eligible.shape,
172
+ device=eligible.device,
173
+ dtype=torch.float32,
174
+ ).clamp_(
175
+ min=torch.finfo(torch.float32).tiny,
176
+ max=1.0 - torch.finfo(torch.float32).eps,
177
+ )
178
+ gumbel = eligible - torch.log(-torch.log(uniform))
179
+ _, chosen_idx = _stable_max(gumbel)
180
+ selected = cand_tokens.gather(dim=-1, index=chosen_idx[:, None]).squeeze(-1)
181
+ selected_score = cand_scores.gather(dim=-1, index=chosen_idx[:, None]).squeeze(-1)
182
+
183
+ raw_nll = torch.where(
184
+ valid,
185
+ raw_log_z - gold_score,
186
+ torch.zeros_like(raw_log_z),
187
+ )
188
+ temperature_nll = torch.where(
189
+ valid,
190
+ sample_log_z - gold_score / float(temperature),
191
+ torch.zeros_like(sample_log_z),
192
+ )
193
+ confidence = torch.exp(selected_score - sample_log_z).clamp_(0.0, 1.0)
194
+ greedy_confidence = torch.exp(greedy_score - sample_log_z).clamp_(0.0, 1.0)
195
+ entropy = sample_log_z - moment_weighted / moment_sum.clamp_min(
196
+ torch.finfo(torch.float32).tiny
197
+ )
198
+
199
+ ctx.save_for_backward(
200
+ hidden,
201
+ weight,
202
+ labels,
203
+ selected,
204
+ raw_log_z,
205
+ sample_log_z,
206
+ confidence,
207
+ entropy,
208
+ )
209
+ ctx.softcap = float(softcap)
210
+ ctx.temperature = float(temperature)
211
+ ctx.chunk_size = int(chunk_size)
212
+ ctx.mark_non_differentiable(selected, greedy, greedy_confidence)
213
+ return (
214
+ raw_nll,
215
+ temperature_nll,
216
+ selected,
217
+ confidence,
218
+ entropy,
219
+ greedy,
220
+ greedy_confidence,
221
+ sample_log_z - selected_score,
222
+ )
223
+
224
+ @staticmethod
225
+ def backward(
226
+ ctx,
227
+ grad_raw_nll: torch.Tensor | None,
228
+ grad_temperature_nll: torch.Tensor | None,
229
+ grad_selected: torch.Tensor | None,
230
+ grad_confidence: torch.Tensor | None,
231
+ grad_entropy: torch.Tensor | None,
232
+ grad_greedy: torch.Tensor | None,
233
+ grad_greedy_confidence: torch.Tensor | None,
234
+ grad_sampled_nll: torch.Tensor | None,
235
+ ):
236
+ del grad_selected, grad_greedy, grad_greedy_confidence
237
+ (
238
+ hidden,
239
+ weight,
240
+ labels,
241
+ selected,
242
+ raw_log_z,
243
+ sample_log_z,
244
+ confidence,
245
+ entropy,
246
+ ) = ctx.saved_tensors
247
+ grad_raw_nll = (
248
+ torch.zeros_like(raw_log_z)
249
+ if grad_raw_nll is None
250
+ else grad_raw_nll.float()
251
+ )
252
+ grad_temperature_nll = (
253
+ torch.zeros_like(sample_log_z)
254
+ if grad_temperature_nll is None
255
+ else grad_temperature_nll.float()
256
+ )
257
+ grad_confidence = (
258
+ torch.zeros_like(confidence)
259
+ if grad_confidence is None
260
+ else grad_confidence.float()
261
+ )
262
+ valid = labels.ge(0)
263
+ row_indices = torch.arange(hidden.shape[0], device=hidden.device)
264
+ grad_hidden = torch.zeros_like(hidden, dtype=torch.float32)
265
+ confidence_scale = (
266
+ grad_confidence * confidence / ctx.temperature
267
+ )
268
+ if grad_sampled_nll is not None:
269
+ confidence_scale = confidence_scale - grad_sampled_nll.float() / ctx.temperature
270
+ hidden_fp32 = hidden.float()
271
+ for start in range(0, weight.shape[0], ctx.chunk_size):
272
+ stop = min(start + ctx.chunk_size, weight.shape[0])
273
+ # The B16 training shape is 4,096 rows by a 32,768-wide vocabulary
274
+ # chunk. MPS BF16 GEMM can return NaNs for this backward-only
275
+ # projection even when every saved activation is finite. Recompute
276
+ # scores and accumulate the exact VJP in FP32; cast the finished
277
+ # hidden gradient back to the model dtype only once below.
278
+ weight_fp32 = weight[start:stop].float()
279
+ scores = torch.tanh(
280
+ F.linear(hidden_fp32, weight_fp32) / ctx.softcap
281
+ ) * ctx.softcap
282
+ # Non-deterministic MPS GEMM can make a recomputed score slightly
283
+ # exceed the saved log-partition. Clamp the exponent so softmax
284
+ # probabilities stay in [0, 1] instead of overflowing to Inf.
285
+ raw_probability = torch.exp(
286
+ (scores - raw_log_z[:, None]).clamp(max=0.0)
287
+ )
288
+ sample_logit = (
289
+ scores / ctx.temperature - sample_log_z[:, None]
290
+ ).clamp(max=0.0)
291
+ sample_probability = torch.exp(sample_logit)
292
+ score_gradient = (
293
+ raw_probability * (grad_raw_nll * valid)[:, None]
294
+ + sample_probability
295
+ * (grad_temperature_nll * valid)[:, None]
296
+ / ctx.temperature
297
+ - sample_probability * confidence_scale[:, None]
298
+ )
299
+ if grad_entropy is not None:
300
+ grad_entropy_f = grad_entropy.float()
301
+ entropy_grad = -(
302
+ grad_entropy_f / ctx.temperature
303
+ )[:, None] * sample_probability * (
304
+ sample_logit + entropy[:, None]
305
+ )
306
+ score_gradient = score_gradient + entropy_grad
307
+
308
+ gold_in_chunk = valid & labels.ge(start) & labels.lt(stop)
309
+ gold_local = (labels - start).clamp(0, stop - start - 1)
310
+ score_gradient[row_indices, gold_local] -= (
311
+ grad_raw_nll * gold_in_chunk
312
+ + grad_temperature_nll
313
+ * gold_in_chunk
314
+ / ctx.temperature
315
+ )
316
+ selected_in_chunk = selected.ge(start) & selected.lt(stop)
317
+ selected_local = (selected - start).clamp(0, stop - start - 1)
318
+ score_gradient[row_indices, selected_local] += (
319
+ confidence_scale * selected_in_chunk
320
+ )
321
+ score_gradient *= 1.0 - (scores / ctx.softcap).square()
322
+ score_gradient = torch.nan_to_num(
323
+ score_gradient, nan=0.0, posinf=0.0, neginf=0.0
324
+ )
325
+ chunk_gradient = F.linear(
326
+ score_gradient,
327
+ weight_fp32.transpose(0, 1),
328
+ )
329
+ grad_hidden.add_(chunk_gradient)
330
+
331
+ grad_hidden = torch.nan_to_num(
332
+ grad_hidden, nan=0.0, posinf=0.0, neginf=0.0
333
+ )
334
+ return (
335
+ grad_hidden.to(hidden.dtype),
336
+ None,
337
+ None,
338
+ None,
339
+ None,
340
+ None,
341
+ None,
342
+ None,
343
+ )
344
+
345
+
346
+ def chunked_vocab_training_statistics(
347
+ hidden: torch.Tensor,
348
+ weight: torch.Tensor,
349
+ labels: torch.LongTensor,
350
+ *,
351
+ softcap: float,
352
+ temperature: float,
353
+ chunk_size: int,
354
+ top_k: int | None = 40,
355
+ min_p: float | None = 0.05,
356
+ return_sampled_nll: bool = False,
357
+ ) -> tuple[
358
+ torch.Tensor,
359
+ torch.Tensor,
360
+ torch.LongTensor,
361
+ torch.Tensor,
362
+ torch.Tensor,
363
+ torch.LongTensor,
364
+ torch.Tensor,
365
+ ]:
366
+ """Return exact losses plus sampled and greedy proposal statistics."""
367
+
368
+ shape = labels.shape
369
+ outputs = _ChunkedVocabStatistics.apply(
370
+ hidden.reshape(-1, hidden.shape[-1]),
371
+ weight,
372
+ labels.reshape(-1),
373
+ float(softcap),
374
+ float(temperature),
375
+ int(chunk_size),
376
+ top_k,
377
+ min_p,
378
+ )
379
+ (
380
+ raw_nll,
381
+ temperature_nll,
382
+ proposal,
383
+ confidence,
384
+ entropy,
385
+ greedy,
386
+ greedy_confidence,
387
+ sampled_nll,
388
+ ) = outputs
389
+ result = (
390
+ raw_nll.view(shape),
391
+ temperature_nll.view(shape),
392
+ proposal.view(shape),
393
+ confidence.view(shape),
394
+ entropy.view(shape),
395
+ greedy.view(shape),
396
+ greedy_confidence.view(shape),
397
+ )
398
+ return result + (sampled_nll.view(shape),) if return_sampled_nll else result
399
+
400
+
401
+ @torch.no_grad()
402
+ def chunked_vocab_statistics(
403
+ hidden: torch.Tensor,
404
+ weight: torch.Tensor,
405
+ *,
406
+ softcap: float,
407
+ temperature: float,
408
+ chunk_size: int,
409
+ repetition_token_mask: torch.BoolTensor | None = None,
410
+ repetition_penalty: float = 1.0,
411
+ sampling_generators: Sequence[torch.Generator] | None = None,
412
+ top_k: int | None = 40,
413
+ min_p: float | None = 0.05,
414
+ ) -> tuple[
415
+ torch.LongTensor,
416
+ torch.Tensor,
417
+ torch.Tensor,
418
+ torch.LongTensor,
419
+ torch.Tensor,
420
+ ]:
421
+ """Return exact inference statistics, optionally penalizing seen tokens."""
422
+
423
+ if not math.isfinite(repetition_penalty) or repetition_penalty <= 0:
424
+ raise ValueError("`repetition_penalty` must be a finite positive number.")
425
+
426
+ # Preserve the original custom-autograd path exactly when disabled. In
427
+ # particular, this keeps the same chunk-local RNG draws and default output.
428
+ if repetition_penalty != 1.0 or sampling_generators is not None:
429
+ if hidden.ndim < 2 or weight.ndim != 2:
430
+ raise ValueError("Chunked vocabulary tensors must have matrix features.")
431
+ if hidden.shape[-1] != weight.shape[1]:
432
+ raise ValueError("Hidden and vocabulary projection dimensions differ.")
433
+ if temperature <= 0 or chunk_size <= 0:
434
+ raise ValueError("Temperature and vocabulary chunk size must be positive.")
435
+ expected_mask_shape = (hidden.shape[0], weight.shape[0])
436
+ if repetition_penalty != 1.0:
437
+ if repetition_token_mask is None or repetition_token_mask.shape != expected_mask_shape:
438
+ raise ValueError(
439
+ "`repetition_token_mask` must have shape [batch, vocabulary]."
440
+ )
441
+ if repetition_token_mask.dtype != torch.bool:
442
+ raise ValueError("`repetition_token_mask` must be a boolean tensor.")
443
+ if sampling_generators is not None and len(sampling_generators) != hidden.shape[0]:
444
+ raise ValueError(
445
+ "`sampling_generators` must contain one generator per batch row."
446
+ )
447
+
448
+ output_shape = hidden.shape[:-1]
449
+ flat_hidden = hidden.reshape(-1, hidden.shape[-1])
450
+ rows_per_batch = math.prod(hidden.shape[1:-1])
451
+ batch_indices = torch.arange(
452
+ flat_hidden.shape[0], device=hidden.device
453
+ ).div(rows_per_batch, rounding_mode="floor")
454
+ sample_log_z = torch.full(
455
+ (flat_hidden.shape[0],),
456
+ -torch.inf,
457
+ device=hidden.device,
458
+ dtype=torch.float32,
459
+ )
460
+ best_gumbel = torch.full_like(sample_log_z, -torch.inf)
461
+ selected_score = torch.zeros_like(sample_log_z)
462
+ selected = torch.zeros(
463
+ flat_hidden.shape[0], device=hidden.device, dtype=torch.long
464
+ )
465
+ greedy_score = torch.full_like(sample_log_z, -torch.inf)
466
+ greedy = torch.zeros_like(selected)
467
+ moment_max = torch.full_like(sample_log_z, -torch.inf)
468
+ moment_sum = torch.zeros_like(sample_log_z)
469
+ moment_weighted = torch.zeros_like(sample_log_z)
470
+
471
+ use_constrained = top_k is not None and top_k > 0
472
+ chunk_cand_scores = []
473
+ chunk_cand_tokens = []
474
+
475
+ for start in range(0, weight.shape[0], int(chunk_size)):
476
+ stop = min(start + int(chunk_size), weight.shape[0])
477
+ raw = F.linear(flat_hidden, weight[start:stop])
478
+ scores = torch.tanh(raw.float() / float(softcap)) * float(softcap)
479
+ if repetition_penalty != 1.0:
480
+ assert repetition_token_mask is not None
481
+ seen = repetition_token_mask[:, start:stop].index_select(
482
+ 0, batch_indices
483
+ )
484
+ penalized = torch.where(
485
+ scores < 0,
486
+ scores * float(repetition_penalty),
487
+ scores / float(repetition_penalty),
488
+ )
489
+ scores = torch.where(seen, penalized, scores)
490
+ sample_scores = scores / float(temperature)
491
+ sample_log_z = torch.logaddexp(
492
+ sample_log_z, torch.logsumexp(sample_scores, dim=-1)
493
+ )
494
+
495
+ if use_constrained:
496
+ k = min(int(top_k), stop - start)
497
+ c_score, c_idx = torch.topk(sample_scores, k=k, dim=-1)
498
+ chunk_cand_scores.append(c_score)
499
+ chunk_cand_tokens.append(c_idx + start)
500
+ else:
501
+ if sampling_generators is None:
502
+ uniform = torch.rand(
503
+ sample_scores.shape,
504
+ device=sample_scores.device,
505
+ dtype=torch.float32,
506
+ )
507
+ else:
508
+ per_request_shape = (
509
+ rows_per_batch,
510
+ sample_scores.shape[-1],
511
+ )
512
+ uniform = torch.cat(
513
+ [
514
+ torch.rand(
515
+ per_request_shape,
516
+ device=sample_scores.device,
517
+ dtype=torch.float32,
518
+ generator=generator,
519
+ )
520
+ for generator in sampling_generators
521
+ ],
522
+ dim=0,
523
+ )
524
+ uniform = uniform.clamp_(
525
+ min=torch.finfo(torch.float32).tiny,
526
+ max=1.0 - torch.finfo(torch.float32).eps,
527
+ )
528
+ gumbel_scores = sample_scores - torch.log(-torch.log(uniform))
529
+ chunk_best, chunk_index = _stable_max(gumbel_scores)
530
+ replace_best = chunk_best.gt(best_gumbel)
531
+ candidate_score = sample_scores.gather(
532
+ 1, chunk_index[:, None]
533
+ ).squeeze(-1)
534
+ best_gumbel = torch.maximum(best_gumbel, chunk_best)
535
+ selected = torch.where(replace_best, chunk_index + start, selected)
536
+ selected_score = torch.where(
537
+ replace_best, candidate_score, selected_score
538
+ )
539
+
540
+ chunk_max, chunk_argmax = _stable_max(sample_scores)
541
+ replace_greedy = chunk_max.gt(greedy_score)
542
+ greedy_score = torch.maximum(greedy_score, chunk_max)
543
+ greedy = torch.where(replace_greedy, chunk_argmax + start, greedy)
544
+ shifted = torch.exp(sample_scores - chunk_max[:, None])
545
+ chunk_sum = shifted.sum(dim=-1)
546
+ chunk_weighted = (shifted * sample_scores).sum(dim=-1)
547
+ merged_max = torch.maximum(moment_max, chunk_max)
548
+ previous_scale = torch.exp(moment_max - merged_max)
549
+ chunk_scale = torch.exp(chunk_max - merged_max)
550
+ moment_sum = moment_sum * previous_scale + chunk_sum * chunk_scale
551
+ moment_weighted = (
552
+ moment_weighted * previous_scale + chunk_weighted * chunk_scale
553
+ )
554
+ moment_max = merged_max
555
+
556
+ if use_constrained:
557
+ all_scores = torch.cat(chunk_cand_scores, dim=-1)
558
+ all_tokens = torch.cat(chunk_cand_tokens, dim=-1)
559
+ global_k = min(int(top_k), all_scores.shape[-1])
560
+ cand_scores, global_idx = torch.topk(all_scores, k=global_k, dim=-1)
561
+ cand_tokens = all_tokens.gather(dim=-1, index=global_idx)
562
+ if min_p is not None and min_p > 0:
563
+ thresh = greedy_score + math.log(float(min_p))
564
+ valid_mask = cand_scores >= thresh[:, None]
565
+ eligible = cand_scores.masked_fill(~valid_mask, -float("inf"))
566
+ else:
567
+ eligible = cand_scores
568
+ if sampling_generators is None:
569
+ uniform = torch.rand(
570
+ eligible.shape,
571
+ device=eligible.device,
572
+ dtype=torch.float32,
573
+ )
574
+ else:
575
+ per_request_shape = (
576
+ rows_per_batch,
577
+ eligible.shape[-1],
578
+ )
579
+ uniform = torch.cat(
580
+ [
581
+ torch.rand(
582
+ per_request_shape,
583
+ device=eligible.device,
584
+ dtype=torch.float32,
585
+ generator=generator,
586
+ )
587
+ for generator in sampling_generators
588
+ ],
589
+ dim=0,
590
+ )
591
+ uniform = uniform.clamp_(
592
+ min=torch.finfo(torch.float32).tiny,
593
+ max=1.0 - torch.finfo(torch.float32).eps,
594
+ )
595
+ gumbel = eligible - torch.log(-torch.log(uniform))
596
+ _, chosen_idx = _stable_max(gumbel)
597
+ selected = cand_tokens.gather(dim=-1, index=chosen_idx[:, None]).squeeze(-1)
598
+ selected_score = cand_scores.gather(dim=-1, index=chosen_idx[:, None]).squeeze(-1)
599
+
600
+ confidence = torch.exp(selected_score - sample_log_z).clamp_(0.0, 1.0)
601
+ greedy_confidence = torch.exp(greedy_score - sample_log_z).clamp_(0.0, 1.0)
602
+ entropy = sample_log_z - moment_weighted / moment_sum.clamp_min(
603
+ torch.finfo(torch.float32).tiny
604
+ )
605
+ return (
606
+ selected.view(output_shape),
607
+ confidence.view(output_shape),
608
+ entropy.view(output_shape),
609
+ greedy.view(output_shape),
610
+ greedy_confidence.view(output_shape),
611
+ )
612
+
613
+ labels = torch.full(
614
+ hidden.shape[:-1],
615
+ -100,
616
+ device=hidden.device,
617
+ dtype=torch.long,
618
+ )
619
+ (
620
+ _,
621
+ _,
622
+ proposal,
623
+ confidence,
624
+ entropy,
625
+ greedy,
626
+ greedy_confidence,
627
+ ) = chunked_vocab_training_statistics(
628
+ hidden,
629
+ weight,
630
+ labels,
631
+ softcap=softcap,
632
+ temperature=temperature,
633
+ chunk_size=chunk_size,
634
+ top_k=top_k,
635
+ min_p=min_p,
636
+ )
637
+ return proposal, confidence, entropy, greedy, greedy_confidence
638
+
639
+
640
+ __all__ = [
641
+ "chunked_vocab_statistics",
642
+ "chunked_vocab_training_statistics",
643
+ ]