Text Generation
Transformers
Safetensors
modilify_mk2
feature-extraction
diffusion
mixture-of-experts
custom-code
modilify-mk2
conversational
custom_code
Instructions to use modilify/Modilify-Mk2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use modilify/Modilify-Mk2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="modilify/Modilify-Mk2", trust_remote_code=True) messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("modilify/Modilify-Mk2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use modilify/Modilify-Mk2 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "modilify/Modilify-Mk2" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "modilify/Modilify-Mk2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/modilify/Modilify-Mk2
- SGLang
How to use modilify/Modilify-Mk2 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "modilify/Modilify-Mk2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "modilify/Modilify-Mk2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "modilify/Modilify-Mk2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "modilify/Modilify-Mk2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use modilify/Modilify-Mk2 with Docker Model Runner:
docker model run hf.co/modilify/Modilify-Mk2
Publish Modilify Mk2 step 4400
Browse files- .gitattributes +1 -34
- .gitignore +7 -0
- LICENSE +167 -0
- LICENSE.apache-2.0 +204 -0
- NOTICE.md +7 -0
- README.md +114 -0
- adapters.py +121 -0
- chat_template.jinja +387 -0
- commit_policy.py +370 -0
- config.json +161 -0
- configuration_modilify_mk2.py +352 -0
- continuous_batching.py +1737 -0
- export_manifest.json +103 -0
- gdn2_memory.py +119 -0
- gdn2_trajectory.py +121 -0
- generation_config.json +48 -0
- generation_modilify_mk2.py +1307 -0
- inference.py +43 -0
- latent_deliberation.py +827 -0
- model-00001-of-00016.safetensors +3 -0
- model-00002-of-00016.safetensors +3 -0
- model-00003-of-00016.safetensors +3 -0
- model-00004-of-00016.safetensors +3 -0
- model-00005-of-00016.safetensors +3 -0
- model-00006-of-00016.safetensors +3 -0
- model-00007-of-00016.safetensors +3 -0
- model-00008-of-00016.safetensors +3 -0
- model-00009-of-00016.safetensors +3 -0
- model-00010-of-00016.safetensors +3 -0
- model-00011-of-00016.safetensors +3 -0
- model-00012-of-00016.safetensors +3 -0
- model-00013-of-00016.safetensors +3 -0
- model-00014-of-00016.safetensors +3 -0
- model-00015-of-00016.safetensors +3 -0
- model-00016-of-00016.safetensors +3 -0
- model.safetensors.index.json +0 -0
- modeling_modilify_mk2.py +955 -0
- mps_ops.py +921 -0
- requirements.txt +4 -0
- tokenizer.json +3 -0
- tokenizer_config.json +96 -0
- vocab_ops.py +643 -0
.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 |
-
|
| 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 |
+
]
|