Publish verified checkpoint and losslessly compressed study evidence
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- LICENSE +202 -0
- NOTICE +16 -0
- README.md +159 -0
- checkpoint/adapter/README.md +24 -0
- checkpoint/adapter/adapter_config.json +295 -0
- checkpoint/adapter/adapter_model.safetensors +3 -0
- checkpoint/joint_head.safetensors +3 -0
- checkpoint/reference.json +124 -0
- checkpoint/training_config.json +719 -0
- code/.dockerignore +14 -0
- code/.python-version +1 -0
- code/Dockerfile +23 -0
- code/LICENSE +202 -0
- code/NOTICE +16 -0
- code/README.md +142 -0
- code/docs/clef.md +121 -0
- code/docs/demo-hosting.md +156 -0
- code/docs/evaluation-protocol.md +163 -0
- code/docs/interfaces.md +15 -0
- code/docs/rules.md +87 -0
- code/docs/tutorials/00-foundation.md +57 -0
- code/docs/tutorials/01-game.md +153 -0
- code/docs/tutorials/02-baselines.md +140 -0
- code/docs/tutorials/03-data.md +189 -0
- code/docs/tutorials/04-gpu-feasibility.md +300 -0
- code/docs/tutorials/05-evaluation.md +281 -0
- code/docs/tutorials/06-release.md +234 -0
- code/pyproject.toml +59 -0
- code/release/dataset-card.md +84 -0
- code/release/model-card.md +125 -0
- code/reports/clef-development.json +66 -0
- code/reports/dataset-manifest.json +539 -0
- code/reports/evaluation-hardware.json +42 -0
- code/reports/final-audit-source.md +149 -0
- code/reports/final-audit.json +0 -0
- code/reports/final-study.md +229 -0
- code/reports/final-summary.json +1297 -0
- code/reports/gpu-feasibility.json +210 -0
- code/reports/selected-checkpoint-reload.json +21 -0
- code/reports/study-preregistration.md +25 -0
- code/reports/study-training.json +696 -0
- code/reports/training-launch-source.json +0 -0
- code/reports/training-source-supplement.json +11 -0
- code/reports/validation-summary.json +158 -0
- code/scripts/benchmark_clef.py +51 -0
- code/scripts/build_release.py +640 -0
- code/scripts/evaluate_clef.py +616 -0
- code/scripts/export_demo.py +213 -0
- code/scripts/gpu_session.py +123 -0
- code/scripts/prepare_checkpoint.py +168 -0
LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright 2026 Alibaba Cloud
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
NOTICE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Stackcraft
|
| 2 |
+
Copyright 2026 Stackcraft contributors.
|
| 3 |
+
|
| 4 |
+
Stackcraft is an independent educational falling-block game and fine-tuning study.
|
| 5 |
+
It is not affiliated with or endorsed by Tetris, Cloudflare, or the Qwen project.
|
| 6 |
+
|
| 7 |
+
The optional trained model derives from Cloudflare/Clef-Flash (Apache-2.0),
|
| 8 |
+
revision 17f0b0ad64efb65d273590632833508766b2aae6, which derives from
|
| 9 |
+
Qwen/Qwen3.5-9B. Original backbone weights and native joint-schema implementation
|
| 10 |
+
remain upstream dependencies; this repository does not redistribute the backbone.
|
| 11 |
+
Stackcraft adds game-specific LoRA matrices and trains a separate native decision
|
| 12 |
+
head. The trained head and LoRA weights must be loaded together.
|
| 13 |
+
|
| 14 |
+
Upstream model: https://huggingface.co/Cloudflare/clef-flash
|
| 15 |
+
Base architecture: https://huggingface.co/Qwen/Qwen3.5-9B
|
| 16 |
+
Python package dependency licenses remain with their respective authors.
|
README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: Cloudflare/clef-flash
|
| 4 |
+
base_model_relation: adapter
|
| 5 |
+
library_name: peft
|
| 6 |
+
language:
|
| 7 |
+
- en
|
| 8 |
+
tags:
|
| 9 |
+
- clef
|
| 10 |
+
- decision-model
|
| 11 |
+
- lora
|
| 12 |
+
- imitation-learning
|
| 13 |
+
- stackcraft
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Stackcraft Clef-Flash
|
| 17 |
+
|
| 18 |
+
Selected checkpoint: **epoch-02**, chosen on validation before final tests.
|
| 19 |
+
This is a small, synthetic imitation-learning study of a falling-block placement
|
| 20 |
+
policy. It is not a generally capable game agent, reinforcement-learning result,
|
| 21 |
+
or competitive Tetris implementation.
|
| 22 |
+
|
| 23 |
+
The artifact contains a rank-4 LoRA adapter **and a separately trained FP32 native
|
| 24 |
+
joint decision head** for `Cloudflare/clef-flash`, pinned revision
|
| 25 |
+
`17f0b0ad64efb65d273590632833508766b2aae6`. Load both. The frozen BF16 backbone
|
| 26 |
+
is downloaded separately; these files are not a standalone model. Clef-flash is
|
| 27 |
+
based on Qwen3.5-9B. The native schema head scores every supplied legal placement
|
| 28 |
+
in one forward pass, with no generated text or command parsing.
|
| 29 |
+
|
| 30 |
+
Related releases: [source and tutorials](https://github.com/kkarimi/stackcraft),
|
| 31 |
+
[model](https://huggingface.co/nima1/stackcraft-clef-flash-lora),
|
| 32 |
+
[dataset](https://huggingface.co/datasets/nima1/stackcraft-data), and
|
| 33 |
+
[planned CPU demo](https://huggingface.co/spaces/nima1/stackcraft).
|
| 34 |
+
The hosted demo awaits Hugging Face's Docker-account subscription requirement;
|
| 35 |
+
[local Docker instructions](https://github.com/kkarimi/stackcraft/blob/main/docs/demo-hosting.md)
|
| 36 |
+
work without that subscription.
|
| 37 |
+
|
| 38 |
+
## Intended use
|
| 39 |
+
|
| 40 |
+
Learn how to build deterministic environments, generate search-teacher labels,
|
| 41 |
+
train a custom decision model, preserve its native head, and evaluate complete
|
| 42 |
+
games with paired uncertainty. The playable demo uses recorded model replays;
|
| 43 |
+
it does not provide public live GPU inference.
|
| 44 |
+
|
| 45 |
+
The observation is a 10×20 occupied-cell grid, current tetromino, exactly one next
|
| 46 |
+
piece, rules and every legal placement with its coordinates. The action selects
|
| 47 |
+
an orientation and column, then drops vertically. There is no hold, wall kick,
|
| 48 |
+
tuck, gravity timer, T-spin bonus or changing speed. Hidden future pieces and
|
| 49 |
+
sequence seeds are excluded from neural input. Probabilities reflect the model's
|
| 50 |
+
choice distribution; they are not calibrated chances of clearing a line or winning.
|
| 51 |
+
|
| 52 |
+
## Training and data
|
| 53 |
+
|
| 54 |
+
The study uses 827 training and 215 validation positions generated from fixed
|
| 55 |
+
seven-bag streams. Random, heuristic and search policies collect states. Every
|
| 56 |
+
label comes from a fixed two-placement search using the current piece and one
|
| 57 |
+
preview, with a board-height/holes/bumpiness/line-clear heuristic. These are
|
| 58 |
+
synthetic teacher preferences, not human demonstrations or globally optimal moves.
|
| 59 |
+
Exact duplicate observations are removed across splits. Six source hashes and
|
| 60 |
+
exact file hashes accompany the dataset manifest.
|
| 61 |
+
|
| 62 |
+
Preregistered training: seed 42, two epochs, batch 1, accumulation 8, rank 4/alpha 8,
|
| 63 |
+
no LoRA dropout, AdamW lr 1e-5/weight_decay 0.01, gradient norm cap 1.0, label smoothing
|
| 64 |
+
0.05 and Brier sum weight0.1. BF16 backbone, FP32 head, text-layer LoRA including
|
| 65 |
+
both hybrid attention types and MLPs. Vision, output and other original parameters
|
| 66 |
+
remain frozen. There are 132,582,404 trainable parameters: 121,762,820 in the
|
| 67 |
+
joint head and 10,819,584 in LoRA. This is why the head is a substantial part of
|
| 68 |
+
the saved artifact, even though most backbone weights stay frozen. Select only between epoch 01/02 using lowest finite target NLL on
|
| 69 |
+
all 215 validation positions; exact ties choose the earlier epoch.
|
| 70 |
+
|
| 71 |
+
## Evaluation and limitations
|
| 72 |
+
|
| 73 |
+
Final preregistered comparison: 200 untouched seeds 30000–30199, 200-piece cap,
|
| 74 |
+
selected model versus unchanged native BF16-head Clef, unchanged FP32-head
|
| 75 |
+
ablation, random legal placement and a cheap heuristic. Primary metric is lines
|
| 76 |
+
cleared. Report score, survival, cap hits, failures and latency. Paired 95% bootstrap
|
| 77 |
+
intervals use 10000 episode resamples with seed 2026. Failed episodes remain in the
|
| 78 |
+
analysis, assigned zero primary outcomes with partial outcomes reported separately.
|
| 79 |
+
|
| 80 |
+
Verified final outcomes on all 200 paired seeds, with a 200-piece cap. Lines, score and placed-piece summaries use the preregistered zero-on-error policy. Cap hits indicate censored survival.
|
| 81 |
+
|
| 82 |
+
| Player | Lines mean (median) | Score mean (median) | Placed mean (median) | Cap hits | Errors |
|
| 83 |
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
| 84 |
+
| base | 0.070 (0.0) | 7.000 (0.0) | 26.020 (26.0) | 0.0% | 0.0% |
|
| 85 |
+
| base-fp32 | 0.105 (0.0) | 10.500 (0.0) | 26.000 (26.0) | 0.0% | 0.0% |
|
| 86 |
+
| trained | 16.810 (16.0) | 1745.500 (1650.0) | 83.565 (82.0) | 0.0% | 0.0% |
|
| 87 |
+
| random | 0.140 (0.0) | 14.000 (0.0) | 26.645 (27.0) | 0.0% | 0.0% |
|
| 88 |
+
| heuristic | 76.530 (77.0) | 8306.500 (8300.0) | 200.000 (200.0) | 100.0% | 0.0% |
|
| 89 |
+
|
| 90 |
+
Each paired 95% bootstrap interval resamples complete episode differences. Trained versus native base is the primary comparison; the heuristic and FP32-head comparisons are separate checks. Intervals are not adjusted for multiple comparisons.
|
| 91 |
+
|
| 92 |
+
| Comparison (first minus second) | Mean lines difference | Paired 95% interval |
|
| 93 |
+
| --- | ---: | ---: |
|
| 94 |
+
| trained − base | 16.740 | [15.740, 17.770] |
|
| 95 |
+
| trained − heuristic | -59.720 | [-60.805, -58.620] |
|
| 96 |
+
| trained − base-fp32 | 16.705 | [15.705, 17.740] |
|
| 97 |
+
| base-fp32 − base | 0.035 | [-0.020, 0.090] |
|
| 98 |
+
|
| 99 |
+
Decision latency includes recorded first-call effects, tokenization and policy overhead, but excludes game rendering and replay playback.
|
| 100 |
+
|
| 101 |
+
| Player | Decision mean ms | Median ms | p95 ms | Invalid decisions |
|
| 102 |
+
| --- | ---: | ---: | ---: | ---: |
|
| 103 |
+
| base | 149.110 | 139.381 | 244.916 | 0 |
|
| 104 |
+
| base-fp32 | 150.188 | 140.968 | 247.880 | 0 |
|
| 105 |
+
| trained | 206.814 | 171.844 | 309.000 | 0 |
|
| 106 |
+
| random | 0.001 | 0.001 | 0.003 | 0 |
|
| 107 |
+
| heuristic | 0.199 | 0.139 | 0.281 | 0 |
|
| 108 |
+
|
| 109 |
+
The full reports retain raw outcomes, all failed seeds and probability logs.
|
| 110 |
+
|
| 111 |
+
Fine-tuning improved complete-game play over both unchanged Clef references in
|
| 112 |
+
this study. The fixed heuristic still cleared substantially more lines and used
|
| 113 |
+
about one thousandth of the trained model's decision time on this workstation.
|
| 114 |
+
All five policies had zero errors and invalid decisions. Every heuristic game hit
|
| 115 |
+
the 200-piece cap; no neural or random game did. This is evidence of task adaptation,
|
| 116 |
+
not a reason to prefer this 9B model over the heuristic for the simplified game.
|
| 117 |
+
See [the readable study report](code/reports/final-study.md) and
|
| 118 |
+
[the hardware/runtime snapshot](code/reports/evaluation-hardware.json).
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
Only one training seed, one small synthetic dataset, one simplified game ruleset
|
| 122 |
+
and one hardware/software configuration are studied. The finite cap censors
|
| 123 |
+
survival. Validation teacher agreement does not establish game quality. Test
|
| 124 |
+
uncertainty does not capture variation across training seeds.
|
| 125 |
+
|
| 126 |
+
## Loading
|
| 127 |
+
|
| 128 |
+
Use the released Stackcraft source and locked ML dependencies. Generic
|
| 129 |
+
`AutoModel` or adapter-only loading omits the custom decision head and is incorrect.
|
| 130 |
+
From the downloaded model repository's `code/` directory, install the locked ML
|
| 131 |
+
environment with `uv sync --locked --extra ml` and cache the pinned upstream
|
| 132 |
+
snapshot as described in [Tutorial 02](code/docs/tutorials/02-baselines.md).
|
| 133 |
+
Run this example with `uv run --locked --extra ml python`; the sibling
|
| 134 |
+
`../checkpoint` directory contains the released adapter and head:
|
| 135 |
+
|
| 136 |
+
```python
|
| 137 |
+
from stackcraft.clef import ClefPlayer
|
| 138 |
+
from stackcraft.training import load_checkpoint
|
| 139 |
+
from stackcraft.engine import new_game
|
| 140 |
+
from stackcraft.players import observe
|
| 141 |
+
|
| 142 |
+
player = ClefPlayer.from_pretrained(trust_pinned_code=True, local_files_only=True)
|
| 143 |
+
load_checkpoint(player.model, "../checkpoint")
|
| 144 |
+
print(player.choose(observe(new_game(42))))
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
`trust_pinned_code=True` executes reviewed, hash-checked upstream Python. It is
|
| 148 |
+
not a sandbox. The measured RTX 5090 inference baseline allocated 19.7 GB; bounded
|
| 149 |
+
LoRA training allocated 23.2 GB. Load only with sufficient free VRAM. Context is
|
| 150 |
+
limited to 4096 tokens, with explicit rejection before any state truncation.
|
| 151 |
+
|
| 152 |
+
See the released tutorials, training configuration, artifact hashes and evaluation
|
| 153 |
+
report for exact reproduction. Apache-2.0; see LICENSE and NOTICE for upstream
|
| 154 |
+
attribution. This project is independent of Cloudflare, Qwen and Tetris.
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
## Bundle layout
|
| 158 |
+
|
| 159 |
+
`checkpoint/` preserves the selected checkpoint bytes. `code/` contains the source, locked environment, tests, scripts and milestone tutorials. `evidence.zip` losslessly compresses the frozen selection, both validation candidates and complete final evaluation. From the model repository root, run `python -m zipfile -e evidence.zip .` after download verification to restore `evidence/` and all original relative paths. `evidence-files.json` records each raw file's byte count and SHA256; extraction preserves the original evidence hashes. From `code/`, run `uv sync --locked --extra ml`; load `../checkpoint` using the native Stackcraft loader. No access to a private GitHub repository is required.
|
checkpoint/adapter/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model: Cloudflare/clef-flash
|
| 3 |
+
library_name: peft
|
| 4 |
+
tags:
|
| 5 |
+
- lora
|
| 6 |
+
- stackcraft
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
# Stackcraft LoRA adapter component
|
| 10 |
+
|
| 11 |
+
This Stackcraft adapter adapts [Cloudflare/clef-flash](https://huggingface.co/Cloudflare/clef-flash),
|
| 12 |
+
released by Cloudflare and pinned at revision `17f0b0ad64efb65d273590632833508766b2aae6`.
|
| 13 |
+
The original model card and license are provided in that upstream repository.
|
| 14 |
+
|
| 15 |
+
This directory is only one component of the Stackcraft checkpoint. Load the
|
| 16 |
+
parent checkpoint's `joint_head.safetensors` and this adapter together through
|
| 17 |
+
Stackcraft's `load_checkpoint` function. The native decision head contains learned
|
| 18 |
+
parameters; loading only the LoRA adapter does not reproduce the trained model.
|
| 19 |
+
Use the exact pinned base and the parent `training_config.json` contract.
|
| 20 |
+
|
| 21 |
+
This component card does not claim improved performance or publication status.
|
| 22 |
+
See the release's full model card and evaluation report for measured results,
|
| 23 |
+
limitations, training provenance and reproduction commands. A prepared candidate
|
| 24 |
+
has not necessarily been selected by validation.
|
checkpoint/adapter/adapter_config.json
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"alora_invocation_tokens": null,
|
| 3 |
+
"alpha_pattern": {},
|
| 4 |
+
"arrow_config": null,
|
| 5 |
+
"auto_mapping": {
|
| 6 |
+
"base_model_class": "Qwen3_5ForConditionalGeneration",
|
| 7 |
+
"parent_library": "transformers.models.qwen3_5.modeling_qwen3_5"
|
| 8 |
+
},
|
| 9 |
+
"base_model_name_or_path": "Cloudflare/clef-flash",
|
| 10 |
+
"bias": "none",
|
| 11 |
+
"corda_config": null,
|
| 12 |
+
"ensure_weight_tying": false,
|
| 13 |
+
"eva_config": null,
|
| 14 |
+
"exclude_modules": null,
|
| 15 |
+
"fan_in_fan_out": false,
|
| 16 |
+
"inference_mode": true,
|
| 17 |
+
"init_lora_weights": true,
|
| 18 |
+
"kasa_config": null,
|
| 19 |
+
"layer_replication": null,
|
| 20 |
+
"layers_pattern": null,
|
| 21 |
+
"layers_to_transform": null,
|
| 22 |
+
"loftq_config": {},
|
| 23 |
+
"lora_alpha": 8,
|
| 24 |
+
"lora_bias": false,
|
| 25 |
+
"lora_dropout": 0.0,
|
| 26 |
+
"lora_ga_config": null,
|
| 27 |
+
"megatron_config": null,
|
| 28 |
+
"megatron_core": "megatron.core",
|
| 29 |
+
"modules_to_save": null,
|
| 30 |
+
"monteclora_config": null,
|
| 31 |
+
"peft_type": "LORA",
|
| 32 |
+
"peft_version": "0.21.2",
|
| 33 |
+
"qalora_group_size": 16,
|
| 34 |
+
"r": 4,
|
| 35 |
+
"rank_pattern": {},
|
| 36 |
+
"revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 37 |
+
"target_modules": [
|
| 38 |
+
"model.language_model.layers.0.linear_attn.in_proj_a",
|
| 39 |
+
"model.language_model.layers.0.linear_attn.in_proj_b",
|
| 40 |
+
"model.language_model.layers.0.linear_attn.in_proj_qkv",
|
| 41 |
+
"model.language_model.layers.0.linear_attn.in_proj_z",
|
| 42 |
+
"model.language_model.layers.0.linear_attn.out_proj",
|
| 43 |
+
"model.language_model.layers.0.mlp.down_proj",
|
| 44 |
+
"model.language_model.layers.0.mlp.gate_proj",
|
| 45 |
+
"model.language_model.layers.0.mlp.up_proj",
|
| 46 |
+
"model.language_model.layers.1.linear_attn.in_proj_a",
|
| 47 |
+
"model.language_model.layers.1.linear_attn.in_proj_b",
|
| 48 |
+
"model.language_model.layers.1.linear_attn.in_proj_qkv",
|
| 49 |
+
"model.language_model.layers.1.linear_attn.in_proj_z",
|
| 50 |
+
"model.language_model.layers.1.linear_attn.out_proj",
|
| 51 |
+
"model.language_model.layers.1.mlp.down_proj",
|
| 52 |
+
"model.language_model.layers.1.mlp.gate_proj",
|
| 53 |
+
"model.language_model.layers.1.mlp.up_proj",
|
| 54 |
+
"model.language_model.layers.10.linear_attn.in_proj_a",
|
| 55 |
+
"model.language_model.layers.10.linear_attn.in_proj_b",
|
| 56 |
+
"model.language_model.layers.10.linear_attn.in_proj_qkv",
|
| 57 |
+
"model.language_model.layers.10.linear_attn.in_proj_z",
|
| 58 |
+
"model.language_model.layers.10.linear_attn.out_proj",
|
| 59 |
+
"model.language_model.layers.10.mlp.down_proj",
|
| 60 |
+
"model.language_model.layers.10.mlp.gate_proj",
|
| 61 |
+
"model.language_model.layers.10.mlp.up_proj",
|
| 62 |
+
"model.language_model.layers.11.mlp.down_proj",
|
| 63 |
+
"model.language_model.layers.11.mlp.gate_proj",
|
| 64 |
+
"model.language_model.layers.11.mlp.up_proj",
|
| 65 |
+
"model.language_model.layers.11.self_attn.k_proj",
|
| 66 |
+
"model.language_model.layers.11.self_attn.o_proj",
|
| 67 |
+
"model.language_model.layers.11.self_attn.q_proj",
|
| 68 |
+
"model.language_model.layers.11.self_attn.v_proj",
|
| 69 |
+
"model.language_model.layers.12.linear_attn.in_proj_a",
|
| 70 |
+
"model.language_model.layers.12.linear_attn.in_proj_b",
|
| 71 |
+
"model.language_model.layers.12.linear_attn.in_proj_qkv",
|
| 72 |
+
"model.language_model.layers.12.linear_attn.in_proj_z",
|
| 73 |
+
"model.language_model.layers.12.linear_attn.out_proj",
|
| 74 |
+
"model.language_model.layers.12.mlp.down_proj",
|
| 75 |
+
"model.language_model.layers.12.mlp.gate_proj",
|
| 76 |
+
"model.language_model.layers.12.mlp.up_proj",
|
| 77 |
+
"model.language_model.layers.13.linear_attn.in_proj_a",
|
| 78 |
+
"model.language_model.layers.13.linear_attn.in_proj_b",
|
| 79 |
+
"model.language_model.layers.13.linear_attn.in_proj_qkv",
|
| 80 |
+
"model.language_model.layers.13.linear_attn.in_proj_z",
|
| 81 |
+
"model.language_model.layers.13.linear_attn.out_proj",
|
| 82 |
+
"model.language_model.layers.13.mlp.down_proj",
|
| 83 |
+
"model.language_model.layers.13.mlp.gate_proj",
|
| 84 |
+
"model.language_model.layers.13.mlp.up_proj",
|
| 85 |
+
"model.language_model.layers.14.linear_attn.in_proj_a",
|
| 86 |
+
"model.language_model.layers.14.linear_attn.in_proj_b",
|
| 87 |
+
"model.language_model.layers.14.linear_attn.in_proj_qkv",
|
| 88 |
+
"model.language_model.layers.14.linear_attn.in_proj_z",
|
| 89 |
+
"model.language_model.layers.14.linear_attn.out_proj",
|
| 90 |
+
"model.language_model.layers.14.mlp.down_proj",
|
| 91 |
+
"model.language_model.layers.14.mlp.gate_proj",
|
| 92 |
+
"model.language_model.layers.14.mlp.up_proj",
|
| 93 |
+
"model.language_model.layers.15.mlp.down_proj",
|
| 94 |
+
"model.language_model.layers.15.mlp.gate_proj",
|
| 95 |
+
"model.language_model.layers.15.mlp.up_proj",
|
| 96 |
+
"model.language_model.layers.15.self_attn.k_proj",
|
| 97 |
+
"model.language_model.layers.15.self_attn.o_proj",
|
| 98 |
+
"model.language_model.layers.15.self_attn.q_proj",
|
| 99 |
+
"model.language_model.layers.15.self_attn.v_proj",
|
| 100 |
+
"model.language_model.layers.16.linear_attn.in_proj_a",
|
| 101 |
+
"model.language_model.layers.16.linear_attn.in_proj_b",
|
| 102 |
+
"model.language_model.layers.16.linear_attn.in_proj_qkv",
|
| 103 |
+
"model.language_model.layers.16.linear_attn.in_proj_z",
|
| 104 |
+
"model.language_model.layers.16.linear_attn.out_proj",
|
| 105 |
+
"model.language_model.layers.16.mlp.down_proj",
|
| 106 |
+
"model.language_model.layers.16.mlp.gate_proj",
|
| 107 |
+
"model.language_model.layers.16.mlp.up_proj",
|
| 108 |
+
"model.language_model.layers.17.linear_attn.in_proj_a",
|
| 109 |
+
"model.language_model.layers.17.linear_attn.in_proj_b",
|
| 110 |
+
"model.language_model.layers.17.linear_attn.in_proj_qkv",
|
| 111 |
+
"model.language_model.layers.17.linear_attn.in_proj_z",
|
| 112 |
+
"model.language_model.layers.17.linear_attn.out_proj",
|
| 113 |
+
"model.language_model.layers.17.mlp.down_proj",
|
| 114 |
+
"model.language_model.layers.17.mlp.gate_proj",
|
| 115 |
+
"model.language_model.layers.17.mlp.up_proj",
|
| 116 |
+
"model.language_model.layers.18.linear_attn.in_proj_a",
|
| 117 |
+
"model.language_model.layers.18.linear_attn.in_proj_b",
|
| 118 |
+
"model.language_model.layers.18.linear_attn.in_proj_qkv",
|
| 119 |
+
"model.language_model.layers.18.linear_attn.in_proj_z",
|
| 120 |
+
"model.language_model.layers.18.linear_attn.out_proj",
|
| 121 |
+
"model.language_model.layers.18.mlp.down_proj",
|
| 122 |
+
"model.language_model.layers.18.mlp.gate_proj",
|
| 123 |
+
"model.language_model.layers.18.mlp.up_proj",
|
| 124 |
+
"model.language_model.layers.19.mlp.down_proj",
|
| 125 |
+
"model.language_model.layers.19.mlp.gate_proj",
|
| 126 |
+
"model.language_model.layers.19.mlp.up_proj",
|
| 127 |
+
"model.language_model.layers.19.self_attn.k_proj",
|
| 128 |
+
"model.language_model.layers.19.self_attn.o_proj",
|
| 129 |
+
"model.language_model.layers.19.self_attn.q_proj",
|
| 130 |
+
"model.language_model.layers.19.self_attn.v_proj",
|
| 131 |
+
"model.language_model.layers.2.linear_attn.in_proj_a",
|
| 132 |
+
"model.language_model.layers.2.linear_attn.in_proj_b",
|
| 133 |
+
"model.language_model.layers.2.linear_attn.in_proj_qkv",
|
| 134 |
+
"model.language_model.layers.2.linear_attn.in_proj_z",
|
| 135 |
+
"model.language_model.layers.2.linear_attn.out_proj",
|
| 136 |
+
"model.language_model.layers.2.mlp.down_proj",
|
| 137 |
+
"model.language_model.layers.2.mlp.gate_proj",
|
| 138 |
+
"model.language_model.layers.2.mlp.up_proj",
|
| 139 |
+
"model.language_model.layers.20.linear_attn.in_proj_a",
|
| 140 |
+
"model.language_model.layers.20.linear_attn.in_proj_b",
|
| 141 |
+
"model.language_model.layers.20.linear_attn.in_proj_qkv",
|
| 142 |
+
"model.language_model.layers.20.linear_attn.in_proj_z",
|
| 143 |
+
"model.language_model.layers.20.linear_attn.out_proj",
|
| 144 |
+
"model.language_model.layers.20.mlp.down_proj",
|
| 145 |
+
"model.language_model.layers.20.mlp.gate_proj",
|
| 146 |
+
"model.language_model.layers.20.mlp.up_proj",
|
| 147 |
+
"model.language_model.layers.21.linear_attn.in_proj_a",
|
| 148 |
+
"model.language_model.layers.21.linear_attn.in_proj_b",
|
| 149 |
+
"model.language_model.layers.21.linear_attn.in_proj_qkv",
|
| 150 |
+
"model.language_model.layers.21.linear_attn.in_proj_z",
|
| 151 |
+
"model.language_model.layers.21.linear_attn.out_proj",
|
| 152 |
+
"model.language_model.layers.21.mlp.down_proj",
|
| 153 |
+
"model.language_model.layers.21.mlp.gate_proj",
|
| 154 |
+
"model.language_model.layers.21.mlp.up_proj",
|
| 155 |
+
"model.language_model.layers.22.linear_attn.in_proj_a",
|
| 156 |
+
"model.language_model.layers.22.linear_attn.in_proj_b",
|
| 157 |
+
"model.language_model.layers.22.linear_attn.in_proj_qkv",
|
| 158 |
+
"model.language_model.layers.22.linear_attn.in_proj_z",
|
| 159 |
+
"model.language_model.layers.22.linear_attn.out_proj",
|
| 160 |
+
"model.language_model.layers.22.mlp.down_proj",
|
| 161 |
+
"model.language_model.layers.22.mlp.gate_proj",
|
| 162 |
+
"model.language_model.layers.22.mlp.up_proj",
|
| 163 |
+
"model.language_model.layers.23.mlp.down_proj",
|
| 164 |
+
"model.language_model.layers.23.mlp.gate_proj",
|
| 165 |
+
"model.language_model.layers.23.mlp.up_proj",
|
| 166 |
+
"model.language_model.layers.23.self_attn.k_proj",
|
| 167 |
+
"model.language_model.layers.23.self_attn.o_proj",
|
| 168 |
+
"model.language_model.layers.23.self_attn.q_proj",
|
| 169 |
+
"model.language_model.layers.23.self_attn.v_proj",
|
| 170 |
+
"model.language_model.layers.24.linear_attn.in_proj_a",
|
| 171 |
+
"model.language_model.layers.24.linear_attn.in_proj_b",
|
| 172 |
+
"model.language_model.layers.24.linear_attn.in_proj_qkv",
|
| 173 |
+
"model.language_model.layers.24.linear_attn.in_proj_z",
|
| 174 |
+
"model.language_model.layers.24.linear_attn.out_proj",
|
| 175 |
+
"model.language_model.layers.24.mlp.down_proj",
|
| 176 |
+
"model.language_model.layers.24.mlp.gate_proj",
|
| 177 |
+
"model.language_model.layers.24.mlp.up_proj",
|
| 178 |
+
"model.language_model.layers.25.linear_attn.in_proj_a",
|
| 179 |
+
"model.language_model.layers.25.linear_attn.in_proj_b",
|
| 180 |
+
"model.language_model.layers.25.linear_attn.in_proj_qkv",
|
| 181 |
+
"model.language_model.layers.25.linear_attn.in_proj_z",
|
| 182 |
+
"model.language_model.layers.25.linear_attn.out_proj",
|
| 183 |
+
"model.language_model.layers.25.mlp.down_proj",
|
| 184 |
+
"model.language_model.layers.25.mlp.gate_proj",
|
| 185 |
+
"model.language_model.layers.25.mlp.up_proj",
|
| 186 |
+
"model.language_model.layers.26.linear_attn.in_proj_a",
|
| 187 |
+
"model.language_model.layers.26.linear_attn.in_proj_b",
|
| 188 |
+
"model.language_model.layers.26.linear_attn.in_proj_qkv",
|
| 189 |
+
"model.language_model.layers.26.linear_attn.in_proj_z",
|
| 190 |
+
"model.language_model.layers.26.linear_attn.out_proj",
|
| 191 |
+
"model.language_model.layers.26.mlp.down_proj",
|
| 192 |
+
"model.language_model.layers.26.mlp.gate_proj",
|
| 193 |
+
"model.language_model.layers.26.mlp.up_proj",
|
| 194 |
+
"model.language_model.layers.27.mlp.down_proj",
|
| 195 |
+
"model.language_model.layers.27.mlp.gate_proj",
|
| 196 |
+
"model.language_model.layers.27.mlp.up_proj",
|
| 197 |
+
"model.language_model.layers.27.self_attn.k_proj",
|
| 198 |
+
"model.language_model.layers.27.self_attn.o_proj",
|
| 199 |
+
"model.language_model.layers.27.self_attn.q_proj",
|
| 200 |
+
"model.language_model.layers.27.self_attn.v_proj",
|
| 201 |
+
"model.language_model.layers.28.linear_attn.in_proj_a",
|
| 202 |
+
"model.language_model.layers.28.linear_attn.in_proj_b",
|
| 203 |
+
"model.language_model.layers.28.linear_attn.in_proj_qkv",
|
| 204 |
+
"model.language_model.layers.28.linear_attn.in_proj_z",
|
| 205 |
+
"model.language_model.layers.28.linear_attn.out_proj",
|
| 206 |
+
"model.language_model.layers.28.mlp.down_proj",
|
| 207 |
+
"model.language_model.layers.28.mlp.gate_proj",
|
| 208 |
+
"model.language_model.layers.28.mlp.up_proj",
|
| 209 |
+
"model.language_model.layers.29.linear_attn.in_proj_a",
|
| 210 |
+
"model.language_model.layers.29.linear_attn.in_proj_b",
|
| 211 |
+
"model.language_model.layers.29.linear_attn.in_proj_qkv",
|
| 212 |
+
"model.language_model.layers.29.linear_attn.in_proj_z",
|
| 213 |
+
"model.language_model.layers.29.linear_attn.out_proj",
|
| 214 |
+
"model.language_model.layers.29.mlp.down_proj",
|
| 215 |
+
"model.language_model.layers.29.mlp.gate_proj",
|
| 216 |
+
"model.language_model.layers.29.mlp.up_proj",
|
| 217 |
+
"model.language_model.layers.3.mlp.down_proj",
|
| 218 |
+
"model.language_model.layers.3.mlp.gate_proj",
|
| 219 |
+
"model.language_model.layers.3.mlp.up_proj",
|
| 220 |
+
"model.language_model.layers.3.self_attn.k_proj",
|
| 221 |
+
"model.language_model.layers.3.self_attn.o_proj",
|
| 222 |
+
"model.language_model.layers.3.self_attn.q_proj",
|
| 223 |
+
"model.language_model.layers.3.self_attn.v_proj",
|
| 224 |
+
"model.language_model.layers.30.linear_attn.in_proj_a",
|
| 225 |
+
"model.language_model.layers.30.linear_attn.in_proj_b",
|
| 226 |
+
"model.language_model.layers.30.linear_attn.in_proj_qkv",
|
| 227 |
+
"model.language_model.layers.30.linear_attn.in_proj_z",
|
| 228 |
+
"model.language_model.layers.30.linear_attn.out_proj",
|
| 229 |
+
"model.language_model.layers.30.mlp.down_proj",
|
| 230 |
+
"model.language_model.layers.30.mlp.gate_proj",
|
| 231 |
+
"model.language_model.layers.30.mlp.up_proj",
|
| 232 |
+
"model.language_model.layers.31.mlp.down_proj",
|
| 233 |
+
"model.language_model.layers.31.mlp.gate_proj",
|
| 234 |
+
"model.language_model.layers.31.mlp.up_proj",
|
| 235 |
+
"model.language_model.layers.31.self_attn.k_proj",
|
| 236 |
+
"model.language_model.layers.31.self_attn.o_proj",
|
| 237 |
+
"model.language_model.layers.31.self_attn.q_proj",
|
| 238 |
+
"model.language_model.layers.31.self_attn.v_proj",
|
| 239 |
+
"model.language_model.layers.4.linear_attn.in_proj_a",
|
| 240 |
+
"model.language_model.layers.4.linear_attn.in_proj_b",
|
| 241 |
+
"model.language_model.layers.4.linear_attn.in_proj_qkv",
|
| 242 |
+
"model.language_model.layers.4.linear_attn.in_proj_z",
|
| 243 |
+
"model.language_model.layers.4.linear_attn.out_proj",
|
| 244 |
+
"model.language_model.layers.4.mlp.down_proj",
|
| 245 |
+
"model.language_model.layers.4.mlp.gate_proj",
|
| 246 |
+
"model.language_model.layers.4.mlp.up_proj",
|
| 247 |
+
"model.language_model.layers.5.linear_attn.in_proj_a",
|
| 248 |
+
"model.language_model.layers.5.linear_attn.in_proj_b",
|
| 249 |
+
"model.language_model.layers.5.linear_attn.in_proj_qkv",
|
| 250 |
+
"model.language_model.layers.5.linear_attn.in_proj_z",
|
| 251 |
+
"model.language_model.layers.5.linear_attn.out_proj",
|
| 252 |
+
"model.language_model.layers.5.mlp.down_proj",
|
| 253 |
+
"model.language_model.layers.5.mlp.gate_proj",
|
| 254 |
+
"model.language_model.layers.5.mlp.up_proj",
|
| 255 |
+
"model.language_model.layers.6.linear_attn.in_proj_a",
|
| 256 |
+
"model.language_model.layers.6.linear_attn.in_proj_b",
|
| 257 |
+
"model.language_model.layers.6.linear_attn.in_proj_qkv",
|
| 258 |
+
"model.language_model.layers.6.linear_attn.in_proj_z",
|
| 259 |
+
"model.language_model.layers.6.linear_attn.out_proj",
|
| 260 |
+
"model.language_model.layers.6.mlp.down_proj",
|
| 261 |
+
"model.language_model.layers.6.mlp.gate_proj",
|
| 262 |
+
"model.language_model.layers.6.mlp.up_proj",
|
| 263 |
+
"model.language_model.layers.7.mlp.down_proj",
|
| 264 |
+
"model.language_model.layers.7.mlp.gate_proj",
|
| 265 |
+
"model.language_model.layers.7.mlp.up_proj",
|
| 266 |
+
"model.language_model.layers.7.self_attn.k_proj",
|
| 267 |
+
"model.language_model.layers.7.self_attn.o_proj",
|
| 268 |
+
"model.language_model.layers.7.self_attn.q_proj",
|
| 269 |
+
"model.language_model.layers.7.self_attn.v_proj",
|
| 270 |
+
"model.language_model.layers.8.linear_attn.in_proj_a",
|
| 271 |
+
"model.language_model.layers.8.linear_attn.in_proj_b",
|
| 272 |
+
"model.language_model.layers.8.linear_attn.in_proj_qkv",
|
| 273 |
+
"model.language_model.layers.8.linear_attn.in_proj_z",
|
| 274 |
+
"model.language_model.layers.8.linear_attn.out_proj",
|
| 275 |
+
"model.language_model.layers.8.mlp.down_proj",
|
| 276 |
+
"model.language_model.layers.8.mlp.gate_proj",
|
| 277 |
+
"model.language_model.layers.8.mlp.up_proj",
|
| 278 |
+
"model.language_model.layers.9.linear_attn.in_proj_a",
|
| 279 |
+
"model.language_model.layers.9.linear_attn.in_proj_b",
|
| 280 |
+
"model.language_model.layers.9.linear_attn.in_proj_qkv",
|
| 281 |
+
"model.language_model.layers.9.linear_attn.in_proj_z",
|
| 282 |
+
"model.language_model.layers.9.linear_attn.out_proj",
|
| 283 |
+
"model.language_model.layers.9.mlp.down_proj",
|
| 284 |
+
"model.language_model.layers.9.mlp.gate_proj",
|
| 285 |
+
"model.language_model.layers.9.mlp.up_proj"
|
| 286 |
+
],
|
| 287 |
+
"target_parameters": null,
|
| 288 |
+
"task_type": null,
|
| 289 |
+
"trainable_token_indices": null,
|
| 290 |
+
"use_bdlora": null,
|
| 291 |
+
"use_dora": false,
|
| 292 |
+
"use_qalora": false,
|
| 293 |
+
"use_rslora": false,
|
| 294 |
+
"velora_config": null
|
| 295 |
+
}
|
checkpoint/adapter/adapter_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3ed169fe68b20a370bb856f9d91842543374ff3c98fd3bbe139ef2d0b97877a3
|
| 3 |
+
size 43352408
|
checkpoint/joint_head.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4f21c0f315cf1bcf852a9887d158928855eaa9bdcab6b9770d518abf3727fa1b
|
| 3 |
+
size 487063576
|
checkpoint/reference.json
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"absolute_tolerance": 0.0001,
|
| 3 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 4 |
+
"dataset_train_sha256": "edd682761db95a4f25bb30a284489c54d9336a36da0a9b19d2cda860b428baa8",
|
| 5 |
+
"max_length": 4096,
|
| 6 |
+
"probabilities": [
|
| 7 |
+
{
|
| 8 |
+
"r0x0": 0.2526208162307739,
|
| 9 |
+
"r0x1": 0.12492077797651291,
|
| 10 |
+
"r0x2": 0.08399814367294312,
|
| 11 |
+
"r0x3": 0.05339020863175392,
|
| 12 |
+
"r0x4": 0.04619518667459488,
|
| 13 |
+
"r0x5": 0.044043805450201035,
|
| 14 |
+
"r0x6": 0.0331004299223423,
|
| 15 |
+
"r0x7": 0.07307567447423935,
|
| 16 |
+
"r1x0": 0.06716147065162659,
|
| 17 |
+
"r1x1": 0.008080358617007732,
|
| 18 |
+
"r1x2": 0.004807046614587307,
|
| 19 |
+
"r1x3": 0.004841183312237263,
|
| 20 |
+
"r1x4": 0.005607179831713438,
|
| 21 |
+
"r1x5": 0.004920979030430317,
|
| 22 |
+
"r1x6": 0.004632336087524891,
|
| 23 |
+
"r1x7": 0.004383244551718235,
|
| 24 |
+
"r1x8": 0.01221535075455904,
|
| 25 |
+
"r2x0": 0.02925245091319084,
|
| 26 |
+
"r2x1": 0.009518387727439404,
|
| 27 |
+
"r2x2": 0.006364461965858936,
|
| 28 |
+
"r2x3": 0.007643744349479675,
|
| 29 |
+
"r2x4": 0.0082625737413764,
|
| 30 |
+
"r2x5": 0.00851234421133995,
|
| 31 |
+
"r2x6": 0.007145871873944998,
|
| 32 |
+
"r2x7": 0.012282103300094604,
|
| 33 |
+
"r3x0": 0.03042912669479847,
|
| 34 |
+
"r3x1": 0.006548034492880106,
|
| 35 |
+
"r3x2": 0.004051720257848501,
|
| 36 |
+
"r3x3": 0.003932017832994461,
|
| 37 |
+
"r3x4": 0.004734409507364035,
|
| 38 |
+
"r3x5": 0.0048585254698991776,
|
| 39 |
+
"r3x6": 0.0044471994042396545,
|
| 40 |
+
"r3x7": 0.00464443676173687,
|
| 41 |
+
"r3x8": 0.019378365948796272
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"r0x0": 0.05243745446205139,
|
| 45 |
+
"r0x1": 0.0021382851991802454,
|
| 46 |
+
"r0x2": 0.0015760728856548667,
|
| 47 |
+
"r0x3": 0.002449487801641226,
|
| 48 |
+
"r0x4": 0.032596979290246964,
|
| 49 |
+
"r0x5": 0.37996670603752136,
|
| 50 |
+
"r0x6": 0.13670691847801208,
|
| 51 |
+
"r0x7": 0.12666313350200653,
|
| 52 |
+
"r1x0": 0.0826447382569313,
|
| 53 |
+
"r1x1": 0.00209549767896533,
|
| 54 |
+
"r1x2": 0.0008976972894743085,
|
| 55 |
+
"r1x3": 0.0008652249234728515,
|
| 56 |
+
"r1x4": 0.0014294564025476575,
|
| 57 |
+
"r1x5": 0.028665941208600998,
|
| 58 |
+
"r1x6": 0.008725162595510483,
|
| 59 |
+
"r1x7": 0.005233264062553644,
|
| 60 |
+
"r1x8": 0.009060735814273357,
|
| 61 |
+
"r2x0": 0.03716142103075981,
|
| 62 |
+
"r2x1": 0.002115321345627308,
|
| 63 |
+
"r2x2": 0.005341287236660719,
|
| 64 |
+
"r2x3": 0.001745575456880033,
|
| 65 |
+
"r2x4": 0.0019583813846111298,
|
| 66 |
+
"r2x5": 0.019835354760289192,
|
| 67 |
+
"r2x6": 0.007319271564483643,
|
| 68 |
+
"r2x7": 0.009388658218085766,
|
| 69 |
+
"r3x0": 0.005738606210798025,
|
| 70 |
+
"r3x1": 0.0012146560475230217,
|
| 71 |
+
"r3x2": 0.0012203187216073275,
|
| 72 |
+
"r3x3": 0.0019278834806755185,
|
| 73 |
+
"r3x4": 0.012783590704202652,
|
| 74 |
+
"r3x5": 0.0051281689666211605,
|
| 75 |
+
"r3x6": 0.0024644453078508377,
|
| 76 |
+
"r3x7": 0.0036620264872908592,
|
| 77 |
+
"r3x8": 0.0068422844633460045
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"r0x0": 0.44664159417152405,
|
| 81 |
+
"r0x1": 0.00948373880237341,
|
| 82 |
+
"r0x2": 0.003061962779611349,
|
| 83 |
+
"r0x3": 0.0024329586885869503,
|
| 84 |
+
"r0x4": 0.0033077853731811047,
|
| 85 |
+
"r0x5": 0.0038714406546205282,
|
| 86 |
+
"r0x6": 0.004481742158532143,
|
| 87 |
+
"r0x7": 0.4447571933269501,
|
| 88 |
+
"r1x0": 0.02148980088531971,
|
| 89 |
+
"r1x1": 0.003787056542932987,
|
| 90 |
+
"r1x2": 0.0029788941610604525,
|
| 91 |
+
"r1x3": 0.002205177675932646,
|
| 92 |
+
"r1x4": 0.002177440095692873,
|
| 93 |
+
"r1x5": 0.002208774909377098,
|
| 94 |
+
"r1x6": 0.0032660418655723333,
|
| 95 |
+
"r1x7": 0.02149323932826519,
|
| 96 |
+
"r1x8": 0.02235523797571659
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"r0x0": 0.07569620758295059,
|
| 100 |
+
"r0x1": 0.004194475244730711,
|
| 101 |
+
"r0x2": 0.002426150254905224,
|
| 102 |
+
"r0x3": 0.002500610426068306,
|
| 103 |
+
"r0x4": 0.002711081877350807,
|
| 104 |
+
"r0x5": 0.0028261507395654917,
|
| 105 |
+
"r0x6": 0.0035828864201903343,
|
| 106 |
+
"r0x7": 0.006198260933160782,
|
| 107 |
+
"r1x0": 0.5267710089683533,
|
| 108 |
+
"r1x1": 0.2791306972503662,
|
| 109 |
+
"r1x2": 0.05139099806547165,
|
| 110 |
+
"r1x3": 0.012985441833734512,
|
| 111 |
+
"r1x4": 0.005060354247689247,
|
| 112 |
+
"r1x5": 0.0031180710066109896,
|
| 113 |
+
"r1x6": 0.002313777571544051,
|
| 114 |
+
"r1x7": 0.014574174769222736,
|
| 115 |
+
"r1x8": 0.004519620444625616
|
| 116 |
+
}
|
| 117 |
+
],
|
| 118 |
+
"row_ids": [
|
| 119 |
+
"seed-10000-turn-0",
|
| 120 |
+
"seed-10000-turn-1",
|
| 121 |
+
"seed-10000-turn-2",
|
| 122 |
+
"seed-10000-turn-3"
|
| 123 |
+
]
|
| 124 |
+
}
|
checkpoint/training_config.json
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base_model": "Cloudflare/clef-flash",
|
| 3 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 4 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 5 |
+
"extra": {
|
| 6 |
+
"config": {
|
| 7 |
+
"batch_size": 1,
|
| 8 |
+
"brier_weight": 0.1,
|
| 9 |
+
"clip_gradient_norm": 1.0,
|
| 10 |
+
"epochs": 2,
|
| 11 |
+
"gradient_accumulation": 8,
|
| 12 |
+
"label_smoothing": 0.05,
|
| 13 |
+
"learning_rate": 1e-05,
|
| 14 |
+
"max_length": 4096,
|
| 15 |
+
"mode": "lora",
|
| 16 |
+
"optimizer": "AdamW",
|
| 17 |
+
"rank": 4,
|
| 18 |
+
"seed": 42,
|
| 19 |
+
"selection": "external validation only; this script does not choose a checkpoint",
|
| 20 |
+
"weight_decay": 0.01
|
| 21 |
+
},
|
| 22 |
+
"dataset_config_sha256": "65b72544c80cf19f4ac3b12ca109d0ebda18fb70eadb4b170a55b1e8bb3b4f6d",
|
| 23 |
+
"dataset_counts": {
|
| 24 |
+
"train": 827,
|
| 25 |
+
"validation": 215
|
| 26 |
+
},
|
| 27 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 28 |
+
"dataset_source_commit": "70d84bd8dc5d4a60f3b96455a57d9e6f9416d109",
|
| 29 |
+
"dataset_split_sha256": {
|
| 30 |
+
"train": "edd682761db95a4f25bb30a284489c54d9336a36da0a9b19d2cda860b428baa8",
|
| 31 |
+
"validation": "eff9cdc5932e935959ac4d26dce6470d335090f7428a91930001954266d133bc"
|
| 32 |
+
},
|
| 33 |
+
"epoch": 2,
|
| 34 |
+
"examples": 827,
|
| 35 |
+
"mean_training_loss": 1.9667175294878694,
|
| 36 |
+
"optimizer_steps": 104,
|
| 37 |
+
"peak_allocated_bytes": 23226543104,
|
| 38 |
+
"peak_reserved_bytes": 24178065408,
|
| 39 |
+
"seconds": 744.6554337799898,
|
| 40 |
+
"shuffle_order_sha256": "b9d20efe77959592c99e9247a58d844be69df50db595bd87c6d7e595f5d2e0c4",
|
| 41 |
+
"source_commit": "d9fcd05c02e9327d4d791f2338725092cede8bdd",
|
| 42 |
+
"source_dirty": true,
|
| 43 |
+
"source_hashes": {
|
| 44 |
+
"scripts/train_clef.py": "e83586cf0fd0d7bb8c4aa15f9b858b9f9ffdb915622e355deb9513788d5054aa",
|
| 45 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 46 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 47 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8"
|
| 48 |
+
},
|
| 49 |
+
"test_trajectories_used": false,
|
| 50 |
+
"validation_used_for_training": false
|
| 51 |
+
},
|
| 52 |
+
"format_version": 1,
|
| 53 |
+
"head_shapes": {
|
| 54 |
+
"evidence_layers.0.attention.in_proj_bias": [
|
| 55 |
+
3072
|
| 56 |
+
],
|
| 57 |
+
"evidence_layers.0.attention.in_proj_weight": [
|
| 58 |
+
3072,
|
| 59 |
+
1024
|
| 60 |
+
],
|
| 61 |
+
"evidence_layers.0.attention.out_proj.bias": [
|
| 62 |
+
1024
|
| 63 |
+
],
|
| 64 |
+
"evidence_layers.0.attention.out_proj.weight": [
|
| 65 |
+
1024,
|
| 66 |
+
1024
|
| 67 |
+
],
|
| 68 |
+
"evidence_layers.0.feedforward.0.bias": [
|
| 69 |
+
4096
|
| 70 |
+
],
|
| 71 |
+
"evidence_layers.0.feedforward.0.weight": [
|
| 72 |
+
4096,
|
| 73 |
+
1024
|
| 74 |
+
],
|
| 75 |
+
"evidence_layers.0.feedforward.3.bias": [
|
| 76 |
+
1024
|
| 77 |
+
],
|
| 78 |
+
"evidence_layers.0.feedforward.3.weight": [
|
| 79 |
+
1024,
|
| 80 |
+
4096
|
| 81 |
+
],
|
| 82 |
+
"evidence_layers.0.feedforward_norm.bias": [
|
| 83 |
+
1024
|
| 84 |
+
],
|
| 85 |
+
"evidence_layers.0.feedforward_norm.weight": [
|
| 86 |
+
1024
|
| 87 |
+
],
|
| 88 |
+
"evidence_layers.0.memory_norm.bias": [
|
| 89 |
+
1024
|
| 90 |
+
],
|
| 91 |
+
"evidence_layers.0.memory_norm.weight": [
|
| 92 |
+
1024
|
| 93 |
+
],
|
| 94 |
+
"evidence_layers.0.query_norm.bias": [
|
| 95 |
+
1024
|
| 96 |
+
],
|
| 97 |
+
"evidence_layers.0.query_norm.weight": [
|
| 98 |
+
1024
|
| 99 |
+
],
|
| 100 |
+
"evidence_layers.1.attention.in_proj_bias": [
|
| 101 |
+
3072
|
| 102 |
+
],
|
| 103 |
+
"evidence_layers.1.attention.in_proj_weight": [
|
| 104 |
+
3072,
|
| 105 |
+
1024
|
| 106 |
+
],
|
| 107 |
+
"evidence_layers.1.attention.out_proj.bias": [
|
| 108 |
+
1024
|
| 109 |
+
],
|
| 110 |
+
"evidence_layers.1.attention.out_proj.weight": [
|
| 111 |
+
1024,
|
| 112 |
+
1024
|
| 113 |
+
],
|
| 114 |
+
"evidence_layers.1.feedforward.0.bias": [
|
| 115 |
+
4096
|
| 116 |
+
],
|
| 117 |
+
"evidence_layers.1.feedforward.0.weight": [
|
| 118 |
+
4096,
|
| 119 |
+
1024
|
| 120 |
+
],
|
| 121 |
+
"evidence_layers.1.feedforward.3.bias": [
|
| 122 |
+
1024
|
| 123 |
+
],
|
| 124 |
+
"evidence_layers.1.feedforward.3.weight": [
|
| 125 |
+
1024,
|
| 126 |
+
4096
|
| 127 |
+
],
|
| 128 |
+
"evidence_layers.1.feedforward_norm.bias": [
|
| 129 |
+
1024
|
| 130 |
+
],
|
| 131 |
+
"evidence_layers.1.feedforward_norm.weight": [
|
| 132 |
+
1024
|
| 133 |
+
],
|
| 134 |
+
"evidence_layers.1.memory_norm.bias": [
|
| 135 |
+
1024
|
| 136 |
+
],
|
| 137 |
+
"evidence_layers.1.memory_norm.weight": [
|
| 138 |
+
1024
|
| 139 |
+
],
|
| 140 |
+
"evidence_layers.1.query_norm.bias": [
|
| 141 |
+
1024
|
| 142 |
+
],
|
| 143 |
+
"evidence_layers.1.query_norm.weight": [
|
| 144 |
+
1024
|
| 145 |
+
],
|
| 146 |
+
"field_norm.bias": [
|
| 147 |
+
1024
|
| 148 |
+
],
|
| 149 |
+
"field_norm.weight": [
|
| 150 |
+
1024
|
| 151 |
+
],
|
| 152 |
+
"global_projection.weight": [
|
| 153 |
+
1024,
|
| 154 |
+
4096
|
| 155 |
+
],
|
| 156 |
+
"hidden_norm.bias": [
|
| 157 |
+
4096
|
| 158 |
+
],
|
| 159 |
+
"hidden_norm.weight": [
|
| 160 |
+
4096
|
| 161 |
+
],
|
| 162 |
+
"joint_logit_scale": [],
|
| 163 |
+
"layers.0.linear1.bias": [
|
| 164 |
+
4096
|
| 165 |
+
],
|
| 166 |
+
"layers.0.linear1.weight": [
|
| 167 |
+
4096,
|
| 168 |
+
1024
|
| 169 |
+
],
|
| 170 |
+
"layers.0.linear2.bias": [
|
| 171 |
+
1024
|
| 172 |
+
],
|
| 173 |
+
"layers.0.linear2.weight": [
|
| 174 |
+
1024,
|
| 175 |
+
4096
|
| 176 |
+
],
|
| 177 |
+
"layers.0.multihead_attn.in_proj_bias": [
|
| 178 |
+
3072
|
| 179 |
+
],
|
| 180 |
+
"layers.0.multihead_attn.in_proj_weight": [
|
| 181 |
+
3072,
|
| 182 |
+
1024
|
| 183 |
+
],
|
| 184 |
+
"layers.0.multihead_attn.out_proj.bias": [
|
| 185 |
+
1024
|
| 186 |
+
],
|
| 187 |
+
"layers.0.multihead_attn.out_proj.weight": [
|
| 188 |
+
1024,
|
| 189 |
+
1024
|
| 190 |
+
],
|
| 191 |
+
"layers.0.norm1.bias": [
|
| 192 |
+
1024
|
| 193 |
+
],
|
| 194 |
+
"layers.0.norm1.weight": [
|
| 195 |
+
1024
|
| 196 |
+
],
|
| 197 |
+
"layers.0.norm2.bias": [
|
| 198 |
+
1024
|
| 199 |
+
],
|
| 200 |
+
"layers.0.norm2.weight": [
|
| 201 |
+
1024
|
| 202 |
+
],
|
| 203 |
+
"layers.0.norm3.bias": [
|
| 204 |
+
1024
|
| 205 |
+
],
|
| 206 |
+
"layers.0.norm3.weight": [
|
| 207 |
+
1024
|
| 208 |
+
],
|
| 209 |
+
"layers.0.self_attn.in_proj_bias": [
|
| 210 |
+
3072
|
| 211 |
+
],
|
| 212 |
+
"layers.0.self_attn.in_proj_weight": [
|
| 213 |
+
3072,
|
| 214 |
+
1024
|
| 215 |
+
],
|
| 216 |
+
"layers.0.self_attn.out_proj.bias": [
|
| 217 |
+
1024
|
| 218 |
+
],
|
| 219 |
+
"layers.0.self_attn.out_proj.weight": [
|
| 220 |
+
1024,
|
| 221 |
+
1024
|
| 222 |
+
],
|
| 223 |
+
"layers.1.linear1.bias": [
|
| 224 |
+
4096
|
| 225 |
+
],
|
| 226 |
+
"layers.1.linear1.weight": [
|
| 227 |
+
4096,
|
| 228 |
+
1024
|
| 229 |
+
],
|
| 230 |
+
"layers.1.linear2.bias": [
|
| 231 |
+
1024
|
| 232 |
+
],
|
| 233 |
+
"layers.1.linear2.weight": [
|
| 234 |
+
1024,
|
| 235 |
+
4096
|
| 236 |
+
],
|
| 237 |
+
"layers.1.multihead_attn.in_proj_bias": [
|
| 238 |
+
3072
|
| 239 |
+
],
|
| 240 |
+
"layers.1.multihead_attn.in_proj_weight": [
|
| 241 |
+
3072,
|
| 242 |
+
1024
|
| 243 |
+
],
|
| 244 |
+
"layers.1.multihead_attn.out_proj.bias": [
|
| 245 |
+
1024
|
| 246 |
+
],
|
| 247 |
+
"layers.1.multihead_attn.out_proj.weight": [
|
| 248 |
+
1024,
|
| 249 |
+
1024
|
| 250 |
+
],
|
| 251 |
+
"layers.1.norm1.bias": [
|
| 252 |
+
1024
|
| 253 |
+
],
|
| 254 |
+
"layers.1.norm1.weight": [
|
| 255 |
+
1024
|
| 256 |
+
],
|
| 257 |
+
"layers.1.norm2.bias": [
|
| 258 |
+
1024
|
| 259 |
+
],
|
| 260 |
+
"layers.1.norm2.weight": [
|
| 261 |
+
1024
|
| 262 |
+
],
|
| 263 |
+
"layers.1.norm3.bias": [
|
| 264 |
+
1024
|
| 265 |
+
],
|
| 266 |
+
"layers.1.norm3.weight": [
|
| 267 |
+
1024
|
| 268 |
+
],
|
| 269 |
+
"layers.1.self_attn.in_proj_bias": [
|
| 270 |
+
3072
|
| 271 |
+
],
|
| 272 |
+
"layers.1.self_attn.in_proj_weight": [
|
| 273 |
+
3072,
|
| 274 |
+
1024
|
| 275 |
+
],
|
| 276 |
+
"layers.1.self_attn.out_proj.bias": [
|
| 277 |
+
1024
|
| 278 |
+
],
|
| 279 |
+
"layers.1.self_attn.out_proj.weight": [
|
| 280 |
+
1024,
|
| 281 |
+
1024
|
| 282 |
+
],
|
| 283 |
+
"layers.2.linear1.bias": [
|
| 284 |
+
4096
|
| 285 |
+
],
|
| 286 |
+
"layers.2.linear1.weight": [
|
| 287 |
+
4096,
|
| 288 |
+
1024
|
| 289 |
+
],
|
| 290 |
+
"layers.2.linear2.bias": [
|
| 291 |
+
1024
|
| 292 |
+
],
|
| 293 |
+
"layers.2.linear2.weight": [
|
| 294 |
+
1024,
|
| 295 |
+
4096
|
| 296 |
+
],
|
| 297 |
+
"layers.2.multihead_attn.in_proj_bias": [
|
| 298 |
+
3072
|
| 299 |
+
],
|
| 300 |
+
"layers.2.multihead_attn.in_proj_weight": [
|
| 301 |
+
3072,
|
| 302 |
+
1024
|
| 303 |
+
],
|
| 304 |
+
"layers.2.multihead_attn.out_proj.bias": [
|
| 305 |
+
1024
|
| 306 |
+
],
|
| 307 |
+
"layers.2.multihead_attn.out_proj.weight": [
|
| 308 |
+
1024,
|
| 309 |
+
1024
|
| 310 |
+
],
|
| 311 |
+
"layers.2.norm1.bias": [
|
| 312 |
+
1024
|
| 313 |
+
],
|
| 314 |
+
"layers.2.norm1.weight": [
|
| 315 |
+
1024
|
| 316 |
+
],
|
| 317 |
+
"layers.2.norm2.bias": [
|
| 318 |
+
1024
|
| 319 |
+
],
|
| 320 |
+
"layers.2.norm2.weight": [
|
| 321 |
+
1024
|
| 322 |
+
],
|
| 323 |
+
"layers.2.norm3.bias": [
|
| 324 |
+
1024
|
| 325 |
+
],
|
| 326 |
+
"layers.2.norm3.weight": [
|
| 327 |
+
1024
|
| 328 |
+
],
|
| 329 |
+
"layers.2.self_attn.in_proj_bias": [
|
| 330 |
+
3072
|
| 331 |
+
],
|
| 332 |
+
"layers.2.self_attn.in_proj_weight": [
|
| 333 |
+
3072,
|
| 334 |
+
1024
|
| 335 |
+
],
|
| 336 |
+
"layers.2.self_attn.out_proj.bias": [
|
| 337 |
+
1024
|
| 338 |
+
],
|
| 339 |
+
"layers.2.self_attn.out_proj.weight": [
|
| 340 |
+
1024,
|
| 341 |
+
1024
|
| 342 |
+
],
|
| 343 |
+
"layers.3.linear1.bias": [
|
| 344 |
+
4096
|
| 345 |
+
],
|
| 346 |
+
"layers.3.linear1.weight": [
|
| 347 |
+
4096,
|
| 348 |
+
1024
|
| 349 |
+
],
|
| 350 |
+
"layers.3.linear2.bias": [
|
| 351 |
+
1024
|
| 352 |
+
],
|
| 353 |
+
"layers.3.linear2.weight": [
|
| 354 |
+
1024,
|
| 355 |
+
4096
|
| 356 |
+
],
|
| 357 |
+
"layers.3.multihead_attn.in_proj_bias": [
|
| 358 |
+
3072
|
| 359 |
+
],
|
| 360 |
+
"layers.3.multihead_attn.in_proj_weight": [
|
| 361 |
+
3072,
|
| 362 |
+
1024
|
| 363 |
+
],
|
| 364 |
+
"layers.3.multihead_attn.out_proj.bias": [
|
| 365 |
+
1024
|
| 366 |
+
],
|
| 367 |
+
"layers.3.multihead_attn.out_proj.weight": [
|
| 368 |
+
1024,
|
| 369 |
+
1024
|
| 370 |
+
],
|
| 371 |
+
"layers.3.norm1.bias": [
|
| 372 |
+
1024
|
| 373 |
+
],
|
| 374 |
+
"layers.3.norm1.weight": [
|
| 375 |
+
1024
|
| 376 |
+
],
|
| 377 |
+
"layers.3.norm2.bias": [
|
| 378 |
+
1024
|
| 379 |
+
],
|
| 380 |
+
"layers.3.norm2.weight": [
|
| 381 |
+
1024
|
| 382 |
+
],
|
| 383 |
+
"layers.3.norm3.bias": [
|
| 384 |
+
1024
|
| 385 |
+
],
|
| 386 |
+
"layers.3.norm3.weight": [
|
| 387 |
+
1024
|
| 388 |
+
],
|
| 389 |
+
"layers.3.self_attn.in_proj_bias": [
|
| 390 |
+
3072
|
| 391 |
+
],
|
| 392 |
+
"layers.3.self_attn.in_proj_weight": [
|
| 393 |
+
3072,
|
| 394 |
+
1024
|
| 395 |
+
],
|
| 396 |
+
"layers.3.self_attn.out_proj.bias": [
|
| 397 |
+
1024
|
| 398 |
+
],
|
| 399 |
+
"layers.3.self_attn.out_proj.weight": [
|
| 400 |
+
1024,
|
| 401 |
+
1024
|
| 402 |
+
],
|
| 403 |
+
"memory_projection.weight": [
|
| 404 |
+
1024,
|
| 405 |
+
4096
|
| 406 |
+
],
|
| 407 |
+
"option_context_projection.weight": [
|
| 408 |
+
1024,
|
| 409 |
+
4096
|
| 410 |
+
],
|
| 411 |
+
"option_lexical_projection.weight": [
|
| 412 |
+
1024,
|
| 413 |
+
4096
|
| 414 |
+
],
|
| 415 |
+
"option_norm.bias": [
|
| 416 |
+
1024
|
| 417 |
+
],
|
| 418 |
+
"option_norm.weight": [
|
| 419 |
+
1024
|
| 420 |
+
],
|
| 421 |
+
"option_question_projection.weight": [
|
| 422 |
+
1024,
|
| 423 |
+
4096
|
| 424 |
+
],
|
| 425 |
+
"option_summary_norm.bias": [
|
| 426 |
+
1024
|
| 427 |
+
],
|
| 428 |
+
"option_summary_norm.weight": [
|
| 429 |
+
1024
|
| 430 |
+
],
|
| 431 |
+
"prior_logit_scale": [],
|
| 432 |
+
"question_projection.weight": [
|
| 433 |
+
1024,
|
| 434 |
+
4096
|
| 435 |
+
],
|
| 436 |
+
"residual_gate": [],
|
| 437 |
+
"residual_scorer.0.bias": [
|
| 438 |
+
1024
|
| 439 |
+
],
|
| 440 |
+
"residual_scorer.0.weight": [
|
| 441 |
+
1024,
|
| 442 |
+
4096
|
| 443 |
+
],
|
| 444 |
+
"residual_scorer.3.bias": [
|
| 445 |
+
1
|
| 446 |
+
],
|
| 447 |
+
"residual_scorer.3.weight": [
|
| 448 |
+
1,
|
| 449 |
+
1024
|
| 450 |
+
],
|
| 451 |
+
"type_embedding.weight": [
|
| 452 |
+
3,
|
| 453 |
+
1024
|
| 454 |
+
]
|
| 455 |
+
},
|
| 456 |
+
"head_type": "native-joint-schema-fp32-gathered-rows-v1",
|
| 457 |
+
"lora": {
|
| 458 |
+
"alpha": 8,
|
| 459 |
+
"dropout": 0.0,
|
| 460 |
+
"rank": 4,
|
| 461 |
+
"target_modules": [
|
| 462 |
+
"model.language_model.layers.0.linear_attn.in_proj_a",
|
| 463 |
+
"model.language_model.layers.0.linear_attn.in_proj_b",
|
| 464 |
+
"model.language_model.layers.0.linear_attn.in_proj_qkv",
|
| 465 |
+
"model.language_model.layers.0.linear_attn.in_proj_z",
|
| 466 |
+
"model.language_model.layers.0.linear_attn.out_proj",
|
| 467 |
+
"model.language_model.layers.0.mlp.down_proj",
|
| 468 |
+
"model.language_model.layers.0.mlp.gate_proj",
|
| 469 |
+
"model.language_model.layers.0.mlp.up_proj",
|
| 470 |
+
"model.language_model.layers.1.linear_attn.in_proj_a",
|
| 471 |
+
"model.language_model.layers.1.linear_attn.in_proj_b",
|
| 472 |
+
"model.language_model.layers.1.linear_attn.in_proj_qkv",
|
| 473 |
+
"model.language_model.layers.1.linear_attn.in_proj_z",
|
| 474 |
+
"model.language_model.layers.1.linear_attn.out_proj",
|
| 475 |
+
"model.language_model.layers.1.mlp.down_proj",
|
| 476 |
+
"model.language_model.layers.1.mlp.gate_proj",
|
| 477 |
+
"model.language_model.layers.1.mlp.up_proj",
|
| 478 |
+
"model.language_model.layers.10.linear_attn.in_proj_a",
|
| 479 |
+
"model.language_model.layers.10.linear_attn.in_proj_b",
|
| 480 |
+
"model.language_model.layers.10.linear_attn.in_proj_qkv",
|
| 481 |
+
"model.language_model.layers.10.linear_attn.in_proj_z",
|
| 482 |
+
"model.language_model.layers.10.linear_attn.out_proj",
|
| 483 |
+
"model.language_model.layers.10.mlp.down_proj",
|
| 484 |
+
"model.language_model.layers.10.mlp.gate_proj",
|
| 485 |
+
"model.language_model.layers.10.mlp.up_proj",
|
| 486 |
+
"model.language_model.layers.11.mlp.down_proj",
|
| 487 |
+
"model.language_model.layers.11.mlp.gate_proj",
|
| 488 |
+
"model.language_model.layers.11.mlp.up_proj",
|
| 489 |
+
"model.language_model.layers.11.self_attn.k_proj",
|
| 490 |
+
"model.language_model.layers.11.self_attn.o_proj",
|
| 491 |
+
"model.language_model.layers.11.self_attn.q_proj",
|
| 492 |
+
"model.language_model.layers.11.self_attn.v_proj",
|
| 493 |
+
"model.language_model.layers.12.linear_attn.in_proj_a",
|
| 494 |
+
"model.language_model.layers.12.linear_attn.in_proj_b",
|
| 495 |
+
"model.language_model.layers.12.linear_attn.in_proj_qkv",
|
| 496 |
+
"model.language_model.layers.12.linear_attn.in_proj_z",
|
| 497 |
+
"model.language_model.layers.12.linear_attn.out_proj",
|
| 498 |
+
"model.language_model.layers.12.mlp.down_proj",
|
| 499 |
+
"model.language_model.layers.12.mlp.gate_proj",
|
| 500 |
+
"model.language_model.layers.12.mlp.up_proj",
|
| 501 |
+
"model.language_model.layers.13.linear_attn.in_proj_a",
|
| 502 |
+
"model.language_model.layers.13.linear_attn.in_proj_b",
|
| 503 |
+
"model.language_model.layers.13.linear_attn.in_proj_qkv",
|
| 504 |
+
"model.language_model.layers.13.linear_attn.in_proj_z",
|
| 505 |
+
"model.language_model.layers.13.linear_attn.out_proj",
|
| 506 |
+
"model.language_model.layers.13.mlp.down_proj",
|
| 507 |
+
"model.language_model.layers.13.mlp.gate_proj",
|
| 508 |
+
"model.language_model.layers.13.mlp.up_proj",
|
| 509 |
+
"model.language_model.layers.14.linear_attn.in_proj_a",
|
| 510 |
+
"model.language_model.layers.14.linear_attn.in_proj_b",
|
| 511 |
+
"model.language_model.layers.14.linear_attn.in_proj_qkv",
|
| 512 |
+
"model.language_model.layers.14.linear_attn.in_proj_z",
|
| 513 |
+
"model.language_model.layers.14.linear_attn.out_proj",
|
| 514 |
+
"model.language_model.layers.14.mlp.down_proj",
|
| 515 |
+
"model.language_model.layers.14.mlp.gate_proj",
|
| 516 |
+
"model.language_model.layers.14.mlp.up_proj",
|
| 517 |
+
"model.language_model.layers.15.mlp.down_proj",
|
| 518 |
+
"model.language_model.layers.15.mlp.gate_proj",
|
| 519 |
+
"model.language_model.layers.15.mlp.up_proj",
|
| 520 |
+
"model.language_model.layers.15.self_attn.k_proj",
|
| 521 |
+
"model.language_model.layers.15.self_attn.o_proj",
|
| 522 |
+
"model.language_model.layers.15.self_attn.q_proj",
|
| 523 |
+
"model.language_model.layers.15.self_attn.v_proj",
|
| 524 |
+
"model.language_model.layers.16.linear_attn.in_proj_a",
|
| 525 |
+
"model.language_model.layers.16.linear_attn.in_proj_b",
|
| 526 |
+
"model.language_model.layers.16.linear_attn.in_proj_qkv",
|
| 527 |
+
"model.language_model.layers.16.linear_attn.in_proj_z",
|
| 528 |
+
"model.language_model.layers.16.linear_attn.out_proj",
|
| 529 |
+
"model.language_model.layers.16.mlp.down_proj",
|
| 530 |
+
"model.language_model.layers.16.mlp.gate_proj",
|
| 531 |
+
"model.language_model.layers.16.mlp.up_proj",
|
| 532 |
+
"model.language_model.layers.17.linear_attn.in_proj_a",
|
| 533 |
+
"model.language_model.layers.17.linear_attn.in_proj_b",
|
| 534 |
+
"model.language_model.layers.17.linear_attn.in_proj_qkv",
|
| 535 |
+
"model.language_model.layers.17.linear_attn.in_proj_z",
|
| 536 |
+
"model.language_model.layers.17.linear_attn.out_proj",
|
| 537 |
+
"model.language_model.layers.17.mlp.down_proj",
|
| 538 |
+
"model.language_model.layers.17.mlp.gate_proj",
|
| 539 |
+
"model.language_model.layers.17.mlp.up_proj",
|
| 540 |
+
"model.language_model.layers.18.linear_attn.in_proj_a",
|
| 541 |
+
"model.language_model.layers.18.linear_attn.in_proj_b",
|
| 542 |
+
"model.language_model.layers.18.linear_attn.in_proj_qkv",
|
| 543 |
+
"model.language_model.layers.18.linear_attn.in_proj_z",
|
| 544 |
+
"model.language_model.layers.18.linear_attn.out_proj",
|
| 545 |
+
"model.language_model.layers.18.mlp.down_proj",
|
| 546 |
+
"model.language_model.layers.18.mlp.gate_proj",
|
| 547 |
+
"model.language_model.layers.18.mlp.up_proj",
|
| 548 |
+
"model.language_model.layers.19.mlp.down_proj",
|
| 549 |
+
"model.language_model.layers.19.mlp.gate_proj",
|
| 550 |
+
"model.language_model.layers.19.mlp.up_proj",
|
| 551 |
+
"model.language_model.layers.19.self_attn.k_proj",
|
| 552 |
+
"model.language_model.layers.19.self_attn.o_proj",
|
| 553 |
+
"model.language_model.layers.19.self_attn.q_proj",
|
| 554 |
+
"model.language_model.layers.19.self_attn.v_proj",
|
| 555 |
+
"model.language_model.layers.2.linear_attn.in_proj_a",
|
| 556 |
+
"model.language_model.layers.2.linear_attn.in_proj_b",
|
| 557 |
+
"model.language_model.layers.2.linear_attn.in_proj_qkv",
|
| 558 |
+
"model.language_model.layers.2.linear_attn.in_proj_z",
|
| 559 |
+
"model.language_model.layers.2.linear_attn.out_proj",
|
| 560 |
+
"model.language_model.layers.2.mlp.down_proj",
|
| 561 |
+
"model.language_model.layers.2.mlp.gate_proj",
|
| 562 |
+
"model.language_model.layers.2.mlp.up_proj",
|
| 563 |
+
"model.language_model.layers.20.linear_attn.in_proj_a",
|
| 564 |
+
"model.language_model.layers.20.linear_attn.in_proj_b",
|
| 565 |
+
"model.language_model.layers.20.linear_attn.in_proj_qkv",
|
| 566 |
+
"model.language_model.layers.20.linear_attn.in_proj_z",
|
| 567 |
+
"model.language_model.layers.20.linear_attn.out_proj",
|
| 568 |
+
"model.language_model.layers.20.mlp.down_proj",
|
| 569 |
+
"model.language_model.layers.20.mlp.gate_proj",
|
| 570 |
+
"model.language_model.layers.20.mlp.up_proj",
|
| 571 |
+
"model.language_model.layers.21.linear_attn.in_proj_a",
|
| 572 |
+
"model.language_model.layers.21.linear_attn.in_proj_b",
|
| 573 |
+
"model.language_model.layers.21.linear_attn.in_proj_qkv",
|
| 574 |
+
"model.language_model.layers.21.linear_attn.in_proj_z",
|
| 575 |
+
"model.language_model.layers.21.linear_attn.out_proj",
|
| 576 |
+
"model.language_model.layers.21.mlp.down_proj",
|
| 577 |
+
"model.language_model.layers.21.mlp.gate_proj",
|
| 578 |
+
"model.language_model.layers.21.mlp.up_proj",
|
| 579 |
+
"model.language_model.layers.22.linear_attn.in_proj_a",
|
| 580 |
+
"model.language_model.layers.22.linear_attn.in_proj_b",
|
| 581 |
+
"model.language_model.layers.22.linear_attn.in_proj_qkv",
|
| 582 |
+
"model.language_model.layers.22.linear_attn.in_proj_z",
|
| 583 |
+
"model.language_model.layers.22.linear_attn.out_proj",
|
| 584 |
+
"model.language_model.layers.22.mlp.down_proj",
|
| 585 |
+
"model.language_model.layers.22.mlp.gate_proj",
|
| 586 |
+
"model.language_model.layers.22.mlp.up_proj",
|
| 587 |
+
"model.language_model.layers.23.mlp.down_proj",
|
| 588 |
+
"model.language_model.layers.23.mlp.gate_proj",
|
| 589 |
+
"model.language_model.layers.23.mlp.up_proj",
|
| 590 |
+
"model.language_model.layers.23.self_attn.k_proj",
|
| 591 |
+
"model.language_model.layers.23.self_attn.o_proj",
|
| 592 |
+
"model.language_model.layers.23.self_attn.q_proj",
|
| 593 |
+
"model.language_model.layers.23.self_attn.v_proj",
|
| 594 |
+
"model.language_model.layers.24.linear_attn.in_proj_a",
|
| 595 |
+
"model.language_model.layers.24.linear_attn.in_proj_b",
|
| 596 |
+
"model.language_model.layers.24.linear_attn.in_proj_qkv",
|
| 597 |
+
"model.language_model.layers.24.linear_attn.in_proj_z",
|
| 598 |
+
"model.language_model.layers.24.linear_attn.out_proj",
|
| 599 |
+
"model.language_model.layers.24.mlp.down_proj",
|
| 600 |
+
"model.language_model.layers.24.mlp.gate_proj",
|
| 601 |
+
"model.language_model.layers.24.mlp.up_proj",
|
| 602 |
+
"model.language_model.layers.25.linear_attn.in_proj_a",
|
| 603 |
+
"model.language_model.layers.25.linear_attn.in_proj_b",
|
| 604 |
+
"model.language_model.layers.25.linear_attn.in_proj_qkv",
|
| 605 |
+
"model.language_model.layers.25.linear_attn.in_proj_z",
|
| 606 |
+
"model.language_model.layers.25.linear_attn.out_proj",
|
| 607 |
+
"model.language_model.layers.25.mlp.down_proj",
|
| 608 |
+
"model.language_model.layers.25.mlp.gate_proj",
|
| 609 |
+
"model.language_model.layers.25.mlp.up_proj",
|
| 610 |
+
"model.language_model.layers.26.linear_attn.in_proj_a",
|
| 611 |
+
"model.language_model.layers.26.linear_attn.in_proj_b",
|
| 612 |
+
"model.language_model.layers.26.linear_attn.in_proj_qkv",
|
| 613 |
+
"model.language_model.layers.26.linear_attn.in_proj_z",
|
| 614 |
+
"model.language_model.layers.26.linear_attn.out_proj",
|
| 615 |
+
"model.language_model.layers.26.mlp.down_proj",
|
| 616 |
+
"model.language_model.layers.26.mlp.gate_proj",
|
| 617 |
+
"model.language_model.layers.26.mlp.up_proj",
|
| 618 |
+
"model.language_model.layers.27.mlp.down_proj",
|
| 619 |
+
"model.language_model.layers.27.mlp.gate_proj",
|
| 620 |
+
"model.language_model.layers.27.mlp.up_proj",
|
| 621 |
+
"model.language_model.layers.27.self_attn.k_proj",
|
| 622 |
+
"model.language_model.layers.27.self_attn.o_proj",
|
| 623 |
+
"model.language_model.layers.27.self_attn.q_proj",
|
| 624 |
+
"model.language_model.layers.27.self_attn.v_proj",
|
| 625 |
+
"model.language_model.layers.28.linear_attn.in_proj_a",
|
| 626 |
+
"model.language_model.layers.28.linear_attn.in_proj_b",
|
| 627 |
+
"model.language_model.layers.28.linear_attn.in_proj_qkv",
|
| 628 |
+
"model.language_model.layers.28.linear_attn.in_proj_z",
|
| 629 |
+
"model.language_model.layers.28.linear_attn.out_proj",
|
| 630 |
+
"model.language_model.layers.28.mlp.down_proj",
|
| 631 |
+
"model.language_model.layers.28.mlp.gate_proj",
|
| 632 |
+
"model.language_model.layers.28.mlp.up_proj",
|
| 633 |
+
"model.language_model.layers.29.linear_attn.in_proj_a",
|
| 634 |
+
"model.language_model.layers.29.linear_attn.in_proj_b",
|
| 635 |
+
"model.language_model.layers.29.linear_attn.in_proj_qkv",
|
| 636 |
+
"model.language_model.layers.29.linear_attn.in_proj_z",
|
| 637 |
+
"model.language_model.layers.29.linear_attn.out_proj",
|
| 638 |
+
"model.language_model.layers.29.mlp.down_proj",
|
| 639 |
+
"model.language_model.layers.29.mlp.gate_proj",
|
| 640 |
+
"model.language_model.layers.29.mlp.up_proj",
|
| 641 |
+
"model.language_model.layers.3.mlp.down_proj",
|
| 642 |
+
"model.language_model.layers.3.mlp.gate_proj",
|
| 643 |
+
"model.language_model.layers.3.mlp.up_proj",
|
| 644 |
+
"model.language_model.layers.3.self_attn.k_proj",
|
| 645 |
+
"model.language_model.layers.3.self_attn.o_proj",
|
| 646 |
+
"model.language_model.layers.3.self_attn.q_proj",
|
| 647 |
+
"model.language_model.layers.3.self_attn.v_proj",
|
| 648 |
+
"model.language_model.layers.30.linear_attn.in_proj_a",
|
| 649 |
+
"model.language_model.layers.30.linear_attn.in_proj_b",
|
| 650 |
+
"model.language_model.layers.30.linear_attn.in_proj_qkv",
|
| 651 |
+
"model.language_model.layers.30.linear_attn.in_proj_z",
|
| 652 |
+
"model.language_model.layers.30.linear_attn.out_proj",
|
| 653 |
+
"model.language_model.layers.30.mlp.down_proj",
|
| 654 |
+
"model.language_model.layers.30.mlp.gate_proj",
|
| 655 |
+
"model.language_model.layers.30.mlp.up_proj",
|
| 656 |
+
"model.language_model.layers.31.mlp.down_proj",
|
| 657 |
+
"model.language_model.layers.31.mlp.gate_proj",
|
| 658 |
+
"model.language_model.layers.31.mlp.up_proj",
|
| 659 |
+
"model.language_model.layers.31.self_attn.k_proj",
|
| 660 |
+
"model.language_model.layers.31.self_attn.o_proj",
|
| 661 |
+
"model.language_model.layers.31.self_attn.q_proj",
|
| 662 |
+
"model.language_model.layers.31.self_attn.v_proj",
|
| 663 |
+
"model.language_model.layers.4.linear_attn.in_proj_a",
|
| 664 |
+
"model.language_model.layers.4.linear_attn.in_proj_b",
|
| 665 |
+
"model.language_model.layers.4.linear_attn.in_proj_qkv",
|
| 666 |
+
"model.language_model.layers.4.linear_attn.in_proj_z",
|
| 667 |
+
"model.language_model.layers.4.linear_attn.out_proj",
|
| 668 |
+
"model.language_model.layers.4.mlp.down_proj",
|
| 669 |
+
"model.language_model.layers.4.mlp.gate_proj",
|
| 670 |
+
"model.language_model.layers.4.mlp.up_proj",
|
| 671 |
+
"model.language_model.layers.5.linear_attn.in_proj_a",
|
| 672 |
+
"model.language_model.layers.5.linear_attn.in_proj_b",
|
| 673 |
+
"model.language_model.layers.5.linear_attn.in_proj_qkv",
|
| 674 |
+
"model.language_model.layers.5.linear_attn.in_proj_z",
|
| 675 |
+
"model.language_model.layers.5.linear_attn.out_proj",
|
| 676 |
+
"model.language_model.layers.5.mlp.down_proj",
|
| 677 |
+
"model.language_model.layers.5.mlp.gate_proj",
|
| 678 |
+
"model.language_model.layers.5.mlp.up_proj",
|
| 679 |
+
"model.language_model.layers.6.linear_attn.in_proj_a",
|
| 680 |
+
"model.language_model.layers.6.linear_attn.in_proj_b",
|
| 681 |
+
"model.language_model.layers.6.linear_attn.in_proj_qkv",
|
| 682 |
+
"model.language_model.layers.6.linear_attn.in_proj_z",
|
| 683 |
+
"model.language_model.layers.6.linear_attn.out_proj",
|
| 684 |
+
"model.language_model.layers.6.mlp.down_proj",
|
| 685 |
+
"model.language_model.layers.6.mlp.gate_proj",
|
| 686 |
+
"model.language_model.layers.6.mlp.up_proj",
|
| 687 |
+
"model.language_model.layers.7.mlp.down_proj",
|
| 688 |
+
"model.language_model.layers.7.mlp.gate_proj",
|
| 689 |
+
"model.language_model.layers.7.mlp.up_proj",
|
| 690 |
+
"model.language_model.layers.7.self_attn.k_proj",
|
| 691 |
+
"model.language_model.layers.7.self_attn.o_proj",
|
| 692 |
+
"model.language_model.layers.7.self_attn.q_proj",
|
| 693 |
+
"model.language_model.layers.7.self_attn.v_proj",
|
| 694 |
+
"model.language_model.layers.8.linear_attn.in_proj_a",
|
| 695 |
+
"model.language_model.layers.8.linear_attn.in_proj_b",
|
| 696 |
+
"model.language_model.layers.8.linear_attn.in_proj_qkv",
|
| 697 |
+
"model.language_model.layers.8.linear_attn.in_proj_z",
|
| 698 |
+
"model.language_model.layers.8.linear_attn.out_proj",
|
| 699 |
+
"model.language_model.layers.8.mlp.down_proj",
|
| 700 |
+
"model.language_model.layers.8.mlp.gate_proj",
|
| 701 |
+
"model.language_model.layers.8.mlp.up_proj",
|
| 702 |
+
"model.language_model.layers.9.linear_attn.in_proj_a",
|
| 703 |
+
"model.language_model.layers.9.linear_attn.in_proj_b",
|
| 704 |
+
"model.language_model.layers.9.linear_attn.in_proj_qkv",
|
| 705 |
+
"model.language_model.layers.9.linear_attn.in_proj_z",
|
| 706 |
+
"model.language_model.layers.9.linear_attn.out_proj",
|
| 707 |
+
"model.language_model.layers.9.mlp.down_proj",
|
| 708 |
+
"model.language_model.layers.9.mlp.gate_proj",
|
| 709 |
+
"model.language_model.layers.9.mlp.up_proj"
|
| 710 |
+
]
|
| 711 |
+
},
|
| 712 |
+
"loss": {
|
| 713 |
+
"brier_reduction": "sum_options",
|
| 714 |
+
"brier_weight": 0.1,
|
| 715 |
+
"label_smoothing": 0.05
|
| 716 |
+
},
|
| 717 |
+
"mode": "lora",
|
| 718 |
+
"native_source_sha256": "0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3"
|
| 719 |
+
}
|
code/.dockerignore
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Send only game source and locked build inputs to Docker.
|
| 2 |
+
**
|
| 3 |
+
!Dockerfile
|
| 4 |
+
!.dockerignore
|
| 5 |
+
!pyproject.toml
|
| 6 |
+
!uv.lock
|
| 7 |
+
!README.md
|
| 8 |
+
!LICENSE
|
| 9 |
+
!NOTICE
|
| 10 |
+
!src/
|
| 11 |
+
!src/stackcraft/
|
| 12 |
+
!src/stackcraft/**
|
| 13 |
+
**/__pycache__/
|
| 14 |
+
**/*.pyc
|
code/.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.13
|
code/Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.13-slim-bookworm@sha256:5024f48ba9441d4b13a95d3945abc6365538e3a31109833367a1923523c6efed AS builder
|
| 2 |
+
COPY --from=ghcr.io/astral-sh/uv:0.12.23@sha256:61d393e44e249f2e4b526b6c7ddcecce245946826e608e11c93ad4f5bba55b21 /uv /usr/local/bin/uv
|
| 3 |
+
ENV UV_PROJECT_ENVIRONMENT=/opt/venv \
|
| 4 |
+
UV_PYTHON_DOWNLOADS=never \
|
| 5 |
+
UV_LINK_MODE=copy
|
| 6 |
+
WORKDIR /opt/build
|
| 7 |
+
COPY pyproject.toml uv.lock README.md LICENSE NOTICE ./
|
| 8 |
+
COPY src/ ./src/
|
| 9 |
+
RUN uv sync --locked --no-dev --no-editable --python /usr/local/bin/python
|
| 10 |
+
|
| 11 |
+
FROM python:3.13-slim-bookworm@sha256:5024f48ba9441d4b13a95d3945abc6365538e3a31109833367a1923523c6efed AS runtime
|
| 12 |
+
RUN useradd --create-home --uid 1000 stackcraft
|
| 13 |
+
COPY --from=builder --chown=1000:1000 /opt/venv /opt/venv
|
| 14 |
+
COPY --chown=1000:1000 LICENSE NOTICE /home/stackcraft/
|
| 15 |
+
USER 1000
|
| 16 |
+
WORKDIR /home/stackcraft
|
| 17 |
+
ENV PORT=7860 \
|
| 18 |
+
PYTHONUNBUFFERED=1 \
|
| 19 |
+
PYTHONDONTWRITEBYTECODE=1
|
| 20 |
+
EXPOSE 7860
|
| 21 |
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
| 22 |
+
CMD /opt/venv/bin/python -c "import os, urllib.request; urllib.request.urlopen('http://127.0.0.1:' + os.environ.get('PORT', '7860') + '/health', timeout=3)"
|
| 23 |
+
CMD ["/bin/sh", "-c", "exec /opt/venv/bin/stackcraft serve --host 0.0.0.0 --port \"${PORT:-7860}\""]
|
code/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright 2026 Alibaba Cloud
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
code/NOTICE
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Stackcraft
|
| 2 |
+
Copyright 2026 Stackcraft contributors.
|
| 3 |
+
|
| 4 |
+
Stackcraft is an independent educational falling-block game and fine-tuning study.
|
| 5 |
+
It is not affiliated with or endorsed by Tetris, Cloudflare, or the Qwen project.
|
| 6 |
+
|
| 7 |
+
The optional trained model derives from Cloudflare/Clef-Flash (Apache-2.0),
|
| 8 |
+
revision 17f0b0ad64efb65d273590632833508766b2aae6, which derives from
|
| 9 |
+
Qwen/Qwen3.5-9B. Original backbone weights and native joint-schema implementation
|
| 10 |
+
remain upstream dependencies; this repository does not redistribute the backbone.
|
| 11 |
+
Stackcraft adds game-specific LoRA matrices and trains a separate native decision
|
| 12 |
+
head. The trained head and LoRA weights must be loaded together.
|
| 13 |
+
|
| 14 |
+
Upstream model: https://huggingface.co/Cloudflare/clef-flash
|
| 15 |
+
Base architecture: https://huggingface.co/Qwen/Qwen3.5-9B
|
| 16 |
+
Python package dependency licenses remain with their respective authors.
|
code/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stackcraft
|
| 2 |
+
|
| 3 |
+
A playable, deterministic falling-block game for studying decision models. The goal is a fair comparison of a heuristic player, unchanged Clef-flash, and a fine-tuned model on identical piece sequences.
|
| 4 |
+
|
| 5 |
+
**Status:** training, validation selection, fresh-process reload and the 1,000-game
|
| 6 |
+
held-out evaluation are complete. The final report and recorded demo passed review. See [the release tutorial](docs/tutorials/06-release.md)
|
| 7 |
+
for milestones and acceptance criteria.
|
| 8 |
+
|
| 9 |
+
## Releases
|
| 10 |
+
|
| 11 |
+
- [Planned CPU demo](https://huggingface.co/spaces/nima1/stackcraft) — hosting awaits the account requirement below
|
| 12 |
+
- [Model adapter and decision head](https://huggingface.co/nima1/stackcraft-clef-flash-lora)
|
| 13 |
+
- [Synthetic dataset](https://huggingface.co/datasets/nima1/stackcraft-data)
|
| 14 |
+
- [Source and release verification](https://github.com/kkarimi/stackcraft)
|
| 15 |
+
|
| 16 |
+
The demo replays actual model decisions alongside live human play. It needs no
|
| 17 |
+
GPU hosting. All four destinations are approved for public release. Hugging Face
|
| 18 |
+
currently requires a PRO subscription to create the Docker CPU Space; the local
|
| 19 |
+
demo works, and hosted publication awaits that account requirement.
|
| 20 |
+
|
| 21 |
+
## What the experiment found
|
| 22 |
+
|
| 23 |
+
On 200 paired sequences per player, with a 200-piece cap:
|
| 24 |
+
|
| 25 |
+
| Player | Mean lines | Mean pieces placed | Mean decision time |
|
| 26 |
+
| --- | ---: | ---: | ---: |
|
| 27 |
+
| Unchanged Clef-flash | 0.070 | 26.020 | 149.11 ms |
|
| 28 |
+
| Unchanged FP32-head control | 0.105 | 26.000 | 150.19 ms |
|
| 29 |
+
| Fine-tuned Clef-flash | 16.810 | 83.565 | 206.81 ms |
|
| 30 |
+
| Random legal placement | 0.140 | 26.645 | 0.0015 ms |
|
| 31 |
+
| Fixed heuristic | 76.530 | 200.000 | 0.1985 ms |
|
| 32 |
+
|
| 33 |
+
Fine-tuning improved mean lines over native Clef by **16.74**, with a paired 95%
|
| 34 |
+
bootstrap interval of **[15.74, 17.77]**. The same improvement remains against the
|
| 35 |
+
unchanged FP32-head control. The heuristic cleared substantially more lines than
|
| 36 |
+
the trained model and was about 1,042 times faster per decision on this machine.
|
| 37 |
+
All players had zero errors or invalid decisions. The heuristic reached the cap
|
| 38 |
+
in every game; its uncapped survival was not measured.
|
| 39 |
+
|
| 40 |
+
This is a successful adaptation experiment and a clear example of why a cheap
|
| 41 |
+
baseline matters. It does not establish that a large model is the best tool for
|
| 42 |
+
this game. See [the full study report](reports/final-study.md) for medians,
|
| 43 |
+
uncertainty, validation results, limitations and reproducible evidence.
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
## Run locally
|
| 47 |
+
|
| 48 |
+
Install [uv](https://docs.astral.sh/uv/), then from this directory:
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
uv sync --locked
|
| 52 |
+
uv run --locked stackcraft serve --port 8087
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
Open http://127.0.0.1:8087. The game needs no GPU or model credential. Sessions are local, in memory, and expire when the server restarts; download a replay to retain a game. The local service retains 128 sessions and limits each to 2,000 placements. Use a single server worker. The game does not require an account.
|
| 56 |
+
|
| 57 |
+
## Development
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
uv run --locked ruff check .
|
| 61 |
+
uv run --locked ruff format --check .
|
| 62 |
+
uv run --locked ty check src/stackcraft --exclude src/stackcraft/training.py
|
| 63 |
+
uv run --locked pytest
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
These commands check the game without installing ML packages. Full-repository
|
| 67 |
+
type checking also covers training scripts and requires the ML extra:
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
uv run --locked --extra ml ty check
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
Installing that extra does not run GPU inference. When keeping an ML environment,
|
| 74 |
+
use `--extra ml` on uv commands to prevent synchronization from removing those
|
| 75 |
+
optional packages.
|
| 76 |
+
|
| 77 |
+
The committed lockfile fixes dependency versions; `uv sync --locked` refuses to silently update it. Python 3.13 is selected by `.python-version`. No pip activation or global dependency installation is needed.
|
| 78 |
+
|
| 79 |
+
Read [tutorial 00](docs/tutorials/00-foundation.md) for the setup rationale, [the rules](docs/rules.md) for the exact game, and [the API contract](docs/interfaces.md) for integration. This is a simplified turn-based placement game, with no gravity timer, hold or wall kicks. It is not affiliated with Tetris.
|
| 80 |
+
|
| 81 |
+
## Offline baseline tournament
|
| 82 |
+
|
| 83 |
+
```bash
|
| 84 |
+
uv run --locked stackcraft tournament --seed-start 1000 --episodes 20 --max-pieces 100 --output runs/development.json
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
This runs random and fixed heuristic players on identical seeds, records every decision and replay, and refuses to overwrite an existing result. See [tutorial02](docs/tutorials/02-baselines.md). Development scores are not held-out model results.
|
| 88 |
+
|
| 89 |
+
## Expert-labelled data
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
uv run --locked stackcraft generate-data --output data/study-v1
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
This generates training/validation episodes from fixed disjoint seed pools, labels moves with bounded search, removes duplicate observations and writes a hashed manifest. Final test seeds remain reserved. See [tutorial03](docs/tutorials/03-data.md).
|
| 96 |
+
|
| 97 |
+
Optional Clef tooling uses `uv sync --locked --extra ml`. Its native encoder, real GPU inference, finite head/LoRA gradients and exact fresh-process checkpoint reload have been verified. Full-study quality is a separate evaluation. See [native adapter documentation](docs/clef.md).
|
| 98 |
+
|
| 99 |
+
## The model used here
|
| 100 |
+
|
| 101 |
+
The base is **Cloudflare/clef-flash**, built on Qwen3.5-9B, pinned at
|
| 102 |
+
`17f0b0ad64efb65d273590632833508766b2aae6`. It produces probabilities over every
|
| 103 |
+
supplied legal move instead of generating text. Training updates rank-4 text-layer
|
| 104 |
+
LoRA matrices and the native joint decision head; the original backbone is frozen.
|
| 105 |
+
The checkpoint must save and load both the adapter and head.
|
| 106 |
+
|
| 107 |
+
On the RTX 5090, the short native inference pilot allocated 19.7 GB and averaged
|
| 108 |
+
144 ms per decision. The training probe allocated 23.2 GB; both head-only and LoRA-plus-head
|
| 109 |
+
checkpoints reproduced their reference probabilities exactly in fresh processes.
|
| 110 |
+
These are engineering measurements, not evidence of better game play. See
|
| 111 |
+
[the measured feasibility report](reports/gpu-feasibility.json).
|
| 112 |
+
|
| 113 |
+
## Follow the milestones
|
| 114 |
+
|
| 115 |
+
1. [Foundation and modern Python tools](docs/tutorials/00-foundation.md)
|
| 116 |
+
2. [Deterministic game and replay](docs/tutorials/01-game.md)
|
| 117 |
+
3. [Cheap and unchanged-model baselines](docs/tutorials/02-baselines.md)
|
| 118 |
+
4. [Search-teacher data and leakage checks](docs/tutorials/03-data.md)
|
| 119 |
+
5. [Real GPU training and checkpoint fidelity](docs/tutorials/04-gpu-feasibility.md)
|
| 120 |
+
6. [Validation selection and paired game evaluation](docs/tutorials/05-evaluation.md)
|
| 121 |
+
7. [Release, public reproduction and fresh-download checks](docs/tutorials/06-release.md)
|
| 122 |
+
|
| 123 |
+
Each tutorial explains the commands, evidence, design choices and alternatives.
|
| 124 |
+
The [evaluation protocol](docs/evaluation-protocol.md) freezes 200 test sequences
|
| 125 |
+
and a 200-piece cap before results. It compares the selected model with the native
|
| 126 |
+
base, an unchanged FP32-head ablation, random moves and a fast heuristic.
|
| 127 |
+
|
| 128 |
+
The training runner intentionally accepts only the exact frozen study dataset.
|
| 129 |
+
A newly generated dataset can have different provenance hashes even if its labels
|
| 130 |
+
match. For exact reproduction use the released dataset or Tutorial03's source-hash
|
| 131 |
+
checked reconstruction. A new dataset or hyperparameter study should be recorded
|
| 132 |
+
as a new experiment, not quietly substituted into this study.
|
| 133 |
+
|
| 134 |
+
## Demo hosting
|
| 135 |
+
|
| 136 |
+
The [Docker deployment guide](docs/demo-hosting.md) builds a CPU-only image with
|
| 137 |
+
no model credentials or Torch. Human gameplay is live; model comparisons replay
|
| 138 |
+
recorded decisions and probabilities. Playback speed is separate from measured
|
| 139 |
+
inference latency. Local packaging has been verified. Release verification is recorded in the GitHub repository.
|
| 140 |
+
|
| 141 |
+
Apache-2.0. See [LICENSE](LICENSE) and [NOTICE](NOTICE). Stackcraft is independent
|
| 142 |
+
of Cloudflare, Qwen and Tetris.
|
code/docs/clef.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Native Clef adapter
|
| 2 |
+
|
| 3 |
+
Status: native encoding, real-weight BF16 inference and bounded head/LoRA GPU
|
| 4 |
+
training have passed. Fresh-process checkpoint parity and full held-out quality
|
| 5 |
+
are separate gates; see the plan and reports for their current status. Fake
|
| 6 |
+
components in unit tests exercise mapping and failure handling only.
|
| 7 |
+
|
| 8 |
+
## Pinned upstream contract
|
| 9 |
+
|
| 10 |
+
- Model: `Cloudflare/clef-flash`.
|
| 11 |
+
- Revision: `17f0b0ad64efb65d273590632833508766b2aae6`.
|
| 12 |
+
- Native file: `joint_schema_model.py`.
|
| 13 |
+
- Source SHA256: `0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3`.
|
| 14 |
+
- Observation encoding: `stackcraft-clef-v1`.
|
| 15 |
+
|
| 16 |
+
Inspected source: [pinned native implementation](https://huggingface.co/Cloudflare/clef-flash/blob/17f0b0ad64efb65d273590632833508766b2aae6/joint_schema_model.py).
|
| 17 |
+
The loader pins a complete snapshot and supplies its local directory to upstream
|
| 18 |
+
`load_release_model`; passing only the repository name to upstream would download
|
| 19 |
+
an unpinned snapshot. `import_pinned_source` checks exact file bytes before
|
| 20 |
+
executing them. Callers must explicitly set `trust_pinned_code=True`: this executes
|
| 21 |
+
reviewed upstream Python with the process's permissions, not in a sandbox.
|
| 22 |
+
|
| 23 |
+
The unchanged loader constructs `Qwen3_5ForConditionalGeneration`, loads a separate
|
| 24 |
+
`JointSchemaHead`, then constructs `AutoProcessor`. Its processor configuration is
|
| 25 |
+
multimodal even though this application passes only text. Optional ML dependencies
|
| 26 |
+
must therefore support that native construction; importing `stackcraft.clef` or
|
| 27 |
+
playing the game itself requires none of them.
|
| 28 |
+
|
| 29 |
+
## Input and outputs
|
| 30 |
+
|
| 31 |
+
`observation_record(observation)` includes the 20 board rows, current piece, exactly
|
| 32 |
+
one preview piece, rules and coordinate definitions. Occupied cells are `#` and
|
| 33 |
+
empty cells are `.`; colors carry no decision-relevant information in these rules.
|
| 34 |
+
The native choice question contains every legal action ID with its rotation,
|
| 35 |
+
column, landing row and four absolute cell coordinates. It does not include a seed,
|
| 36 |
+
hidden stream, score-derived helper features, or heuristic candidate pruning.
|
| 37 |
+
|
| 38 |
+
Native choice encoding sorts option IDs lexicographically. Probabilities must be
|
| 39 |
+
mapped using `EncodedQuestion.option_ids`, rather than the input dictionary order.
|
| 40 |
+
The adapter reads `model(collate_records([encoded], pad_id, device))[0][0]` and takes
|
| 41 |
+
FP32 softmax. It retains unrounded float probabilities. Upstream `systemone()`
|
| 42 |
+
rounds probabilities to four decimals, which is unsuitable for precise validation
|
| 43 |
+
and probability metrics. On exact probability ties, Stackcraft uses the shared
|
| 44 |
+
engine action order.
|
| 45 |
+
|
| 46 |
+
## Context integrity
|
| 47 |
+
|
| 48 |
+
The native encoder reserves schema tokens and silently truncates state tokens to
|
| 49 |
+
fit `max_length`. Losing some board rows would change the game state presented to
|
| 50 |
+
the model. Before encoding, Stackcraft counts every separately tokenized segment
|
| 51 |
+
used by the pinned encoder. It rejects the entire decision if complete input does
|
| 52 |
+
not fit the configured limit, then checks the resulting length and option IDs.
|
| 53 |
+
It never removes choices or board rows to make a record fit.
|
| 54 |
+
|
| 55 |
+
The default limit is 4,096 tokens, a memory-conscious initial setting, not a claim
|
| 56 |
+
about the backbone's advertised context. If observed positions exceed it, measure
|
| 57 |
+
the required size and preregister a consistently larger limit for both base and
|
| 58 |
+
trained models. Any different text representation needs a new encoding version.
|
| 59 |
+
|
| 60 |
+
## Integration example
|
| 61 |
+
|
| 62 |
+
After ML dependencies and the pinned snapshot are available, and available GPU
|
| 63 |
+
memory has been checked:
|
| 64 |
+
|
| 65 |
+
```python
|
| 66 |
+
from stackcraft.clef import ClefPlayer
|
| 67 |
+
from stackcraft.engine import new_game
|
| 68 |
+
from stackcraft.players import observe
|
| 69 |
+
|
| 70 |
+
player = ClefPlayer.from_pretrained(
|
| 71 |
+
trust_pinned_code=True,
|
| 72 |
+
local_files_only=True,
|
| 73 |
+
device="cuda",
|
| 74 |
+
max_length=4096,
|
| 75 |
+
)
|
| 76 |
+
decision = player.choose(observe(new_game(42)))
|
| 77 |
+
print(decision.action_id, decision.probabilities, player.last_input_tokens)
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
This example loads real 9B weights. Real inference passed on an RTX 5090.
|
| 81 |
+
The loader does not stop other workloads, allocate rented compute, or implicitly
|
| 82 |
+
download missing weights by default. A caller can deliberately set
|
| 83 |
+
`local_files_only=False` to obtain this pinned snapshot. Model identity, source
|
| 84 |
+
hash and encoding version must accompany measurements.
|
| 85 |
+
|
| 86 |
+
`player.runtime_config` records model ID, player/checkpoint revision, pinned base
|
| 87 |
+
revision, encoding version, native source SHA256 and configured context limit.
|
| 88 |
+
After each successful decision it also records the actual first model parameter's
|
| 89 |
+
dtype and device. These are initially null until inference succeeds; construction
|
| 90 |
+
does not consume model parameters or import Torch for metadata. The native release
|
| 91 |
+
uses one device and dtype; this field is not a full precision inventory for future
|
| 92 |
+
mixed-precision training models. `last_input_tokens` records the successful
|
| 93 |
+
decision's complete encoded length. Tournament artifacts should snapshot runtime
|
| 94 |
+
metadata and retain token counts per decision, rather than infer them from defaults.
|
| 95 |
+
|
| 96 |
+
Run the offline adapter checks with:
|
| 97 |
+
|
| 98 |
+
```sh
|
| 99 |
+
uv run --locked pytest tests/test_clef.py
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
CPU verification used the actual `Qwen3VLProcessor` and native `encode_record`,
|
| 103 |
+
without constructing a backbone or loading weights. Fourteen fixtures covered
|
| 104 |
+
all seven pieces on an empty board and on a dense 15-row patterned stack, including
|
| 105 |
+
34-choice positions. Exact token counts matched in every case: 848–2,248 tokens,
|
| 106 |
+
all within 4,096. Rejecting a limit one token below each required size also passes
|
| 107 |
+
the dedicated integration check. These fixtures do not establish a universal
|
| 108 |
+
maximum; every inference request still runs preflight.
|
| 109 |
+
|
| 110 |
+
Reproduce that optional check after installing the ML extra and caching pinned
|
| 111 |
+
tokenizer/processor/source files:
|
| 112 |
+
|
| 113 |
+
```sh
|
| 114 |
+
STACKCRAFT_TEST_NATIVE_ENCODING=1 uv run --locked --extra ml pytest tests/test_clef.py
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
The two-seed real-weight development baseline used19.69GB peak allocated memory
|
| 118 |
+
and averaged144ms per decision; reports/clef-development.json records the result.
|
| 119 |
+
These development measurements do not establish trained-model quality. Training must call native
|
| 120 |
+
`ClefModel.forward` with gradients enabled; `choose()` intentionally uses inference
|
| 121 |
+
mode and must not be used as a training path.
|
code/docs/demo-hosting.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Host the playable game and recorded comparison
|
| 2 |
+
|
| 3 |
+
The demo serves the actual Python game and validated recorded model decisions. It
|
| 4 |
+
requires no GPU, Hugging Face token, Torch installation or model download. Human
|
| 5 |
+
moves are live simulator calls; comparison boards replay decisions measured during
|
| 6 |
+
the evaluation. Playback timing is deliberately separate from inference latency.
|
| 7 |
+
|
| 8 |
+
This is a deployment package, not evidence of publication. Create or upload a new
|
| 9 |
+
Space only after the project owner confirms its destination and visibility.
|
| 10 |
+
|
| 11 |
+
## Export the fixed example after evaluation
|
| 12 |
+
|
| 13 |
+
The demonstration seed is **30000**, chosen before inspecting results. Do not pick
|
| 14 |
+
a better-looking seed after the evaluation. A single game illustrates behavior;
|
| 15 |
+
the aggregate evaluation report establishes the quantitative result.
|
| 16 |
+
|
| 17 |
+
After the final tournament has completed, export to a new file:
|
| 18 |
+
|
| 19 |
+
```bash
|
| 20 |
+
uv run --locked python scripts/export_demo.py \
|
| 21 |
+
--input runs/final-evaluation/report.json \
|
| 22 |
+
--players base trained heuristic \
|
| 23 |
+
--seed 30000 \
|
| 24 |
+
--output runs/release-demo/baseline-demo.json
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
The input path above is an example; use the actual complete evaluation report.
|
| 28 |
+
`--input` can be repeated for separate tournament or individual episode artifacts.
|
| 29 |
+
The exporter requires two to four distinct players, an identical piece cap, and
|
| 30 |
+
exactly one recording per selected player at the fixed seed. The optional
|
| 31 |
+
`base-fp32` player is a precision ablation, not the primary unchanged model.
|
| 32 |
+
|
| 33 |
+
The exporter replays every selected action with the authoritative engine and
|
| 34 |
+
checks final outcomes, stop reasons, observations, probability distributions and
|
| 35 |
+
decision/action alignment. It preserves errors as errors. It keeps complete
|
| 36 |
+
replays, compact decision probabilities, measured decision times, actual player
|
| 37 |
+
revisions, runtime metadata, source filenames and SHA-256 hashes. It strips the
|
| 38 |
+
large repeated observation payloads because the game can reconstruct them.
|
| 39 |
+
Neither missing players nor invalid records are replaced with simulated model
|
| 40 |
+
results. Existing outputs are never overwritten.
|
| 41 |
+
|
| 42 |
+
After the results page's publication destination is confirmed, optionally add
|
| 43 |
+
`--report-url` with its absolute HTTPS URL. This adds a "Full study results" link
|
| 44 |
+
to the race without changing source hashes or recorded decisions. Omit it until
|
| 45 |
+
the actual destination is known; the UI hides the link when absent or invalid.
|
| 46 |
+
|
| 47 |
+
The JSON is compatible with the race manifest loaded from
|
| 48 |
+
`src/stackcraft/web/baseline-demo.json`. Before a release, review the exported
|
| 49 |
+
example and replace that packaged asset explicitly. The checked-in development
|
| 50 |
+
asset must remain labelled as development until actual evaluation replays replace
|
| 51 |
+
it. Do not rename a heuristic recording to make a missing model board appear.
|
| 52 |
+
|
| 53 |
+
## Build the game-only container
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
docker build -t stackcraft-demo:local .
|
| 57 |
+
docker run --rm --name stackcraft-demo-local \
|
| 58 |
+
-p 127.0.0.1:7860:7860 stackcraft-demo:local
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
Open `http://127.0.0.1:7860`. The container runs as UID 1000 and listens on all
|
| 62 |
+
interfaces inside the container; this local command exposes it only on loopback.
|
| 63 |
+
The Dockerfile uses Python 3.13 and a versioned uv executable. Its build stage runs
|
| 64 |
+
`uv sync --locked --no-dev --no-editable` with no ML extra. Installing the project
|
| 65 |
+
rather than an editable source link checks that the wheel contains HTML, CSS,
|
| 66 |
+
JavaScript and the replay JSON. The runtime stage contains that environment and
|
| 67 |
+
uses the CLI directly, with no dependency resolution at startup. This follows the
|
| 68 |
+
[uv container installation guidance](https://docs.astral.sh/uv/guides/integration/docker/).
|
| 69 |
+
|
| 70 |
+
`.dockerignore` allows only the source and locked build inputs into the build
|
| 71 |
+
context. Local credentials, model weights, training runs, `.venv`, raw datasets
|
| 72 |
+
and Git history are excluded. The image does not need them to reconstruct games.
|
| 73 |
+
The dependency lock freezes Python packages. Both the Python base and uv image
|
| 74 |
+
are pinned by digest; the verified Python base supplies 3.13.16. Update these
|
| 75 |
+
explicitly when upgrading the deployment, and record the final application image
|
| 76 |
+
identity for the published build.
|
| 77 |
+
|
| 78 |
+
The container defaults to port 7860. To change it locally:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
docker run --rm --name stackcraft-demo-local \
|
| 82 |
+
-e PORT=8090 -p 127.0.0.1:8090:8090 stackcraft-demo:local
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
This container setting does not change the normal CLI's default local port 8080.
|
| 86 |
+
Use a free host port; do not stop another application merely to reclaim 8080.
|
| 87 |
+
|
| 88 |
+
## Configure a Hugging Face Docker Space
|
| 89 |
+
|
| 90 |
+
As checked on 2026-10-06, Hugging Face requires a PRO subscription for a personal
|
| 91 |
+
account to create a Docker Space, including `cpu-basic`. That hardware has no
|
| 92 |
+
hourly charge, but it is not subscription-free hosting. Static Spaces have different
|
| 93 |
+
requirements and cannot run this Python HTTP service unchanged. See the
|
| 94 |
+
[official Spaces overview](https://huggingface.co/docs/hub/spaces-overview).
|
| 95 |
+
The initial `nima1/stackcraft` creation returned HTTP 402; no subscription or paid
|
| 96 |
+
hardware was purchased. Resolve the account requirement or choose another CPU host
|
| 97 |
+
before following the deployment steps below.
|
| 98 |
+
|
| 99 |
+
Once publication is authorized, the Space repository needs the Dockerfile,
|
| 100 |
+
`.dockerignore`, `pyproject.toml`, `uv.lock`, `src/` including the reviewed replay
|
| 101 |
+
asset, and a README. Put this configuration at the top of the Space README:
|
| 102 |
+
|
| 103 |
+
```yaml
|
| 104 |
+
---
|
| 105 |
+
title: Stackcraft
|
| 106 |
+
emoji: 🧩
|
| 107 |
+
colorFrom: green
|
| 108 |
+
colorTo: purple
|
| 109 |
+
sdk: docker
|
| 110 |
+
app_port: 7860
|
| 111 |
+
---
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
The README body should link the model, dataset, code, aggregate report and
|
| 115 |
+
reproduction tutorials, and explain which activity is live. The Space configuration
|
| 116 |
+
uses `sdk: docker` and the same exposed port as the application. UID 1000 matches
|
| 117 |
+
Hugging Face's documented container permissions. No GPU hardware upgrade or model
|
| 118 |
+
credential is needed for this recorded demonstration. See the official
|
| 119 |
+
[Docker Spaces documentation](https://huggingface.co/docs/hub/spaces-sdks-docker).
|
| 120 |
+
|
| 121 |
+
Hugging Face builds from the Space repository. Upload only the explicit release
|
| 122 |
+
files, not the entire working directory. The source repository's visibility and
|
| 123 |
+
the Space's visibility are separate publication choices.
|
| 124 |
+
|
| 125 |
+
## Check the actual deployment
|
| 126 |
+
|
| 127 |
+
Run these checks against the built image and repeat them against the published
|
| 128 |
+
Space URL after release:
|
| 129 |
+
|
| 130 |
+
1. `/health` returns `{"status":"ok"}` and `/` loads its packaged assets.
|
| 131 |
+
2. A human can rotate, place a piece, restart and download a replay.
|
| 132 |
+
3. Opening that replay reproduces the same score, lines and board.
|
| 133 |
+
4. The race clearly says recorded; scrub to both the first and last frame.
|
| 134 |
+
5. The shorter run stays at its actual final state. An error is labelled as an
|
| 135 |
+
error, and reaching the episode cap is distinct from top-out.
|
| 136 |
+
6. The selected action and any probability alternatives come from the recorded
|
| 137 |
+
decision at that position. They are not an explanation generated after the run.
|
| 138 |
+
7. "Start live board · seed N" explicitly starts a new human game beside the
|
| 139 |
+
recorded players using the exact recording seed. Check the same episode cap,
|
| 140 |
+
keyboard controls, per-move recording synchronization and the live/recorded labels.
|
| 141 |
+
Scrubbing recorded moves must leave the human board unchanged. "Match my move"
|
| 142 |
+
restores the shared piece index. Switching Play/race preserves the human session;
|
| 143 |
+
"Leave comparison" returns it to uncapped ordinary play without a reset. On narrow
|
| 144 |
+
screens boards stack so controls remain readable; check for horizontal overflow.
|
| 145 |
+
8. The running image imports the game without Torch. No requests download model
|
| 146 |
+
weights, and no inference service is contacted.
|
| 147 |
+
|
| 148 |
+
Game sessions live in process memory. A container restart discards them, and the
|
| 149 |
+
service retains a bounded number of sessions. Save a replay for durable evidence.
|
| 150 |
+
Use one application worker with the current in-memory session store; independent
|
| 151 |
+
worker processes would not share game IDs. A larger deployment would need a
|
| 152 |
+
shared session store and request limits, rather than silently adding workers.
|
| 153 |
+
|
| 154 |
+
Keep the final image identity, released manifest hash and successful URL checks in
|
| 155 |
+
`record.md`. A local Dockerfile or a successful upload alone does not establish
|
| 156 |
+
that the public comparison works.
|
code/docs/evaluation-protocol.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stackcraft evaluation protocol — completed study, frozen procedure
|
| 2 |
+
|
| 3 |
+
The final test used all **200 episode seeds, 30000–30199**, at the frozen 200-piece
|
| 4 |
+
cap, with all five players and no errors. Training, validation selection, reload
|
| 5 |
+
verification, final evaluation and the independent CPU audit are complete. The
|
| 6 |
+
[final study](../reports/final-study.md) reports the measured outcomes and
|
| 7 |
+
[compact JSON](../reports/final-summary.json) preserves exact statistics and hashes.
|
| 8 |
+
Release verification is tracked separately in the source repository.
|
| 9 |
+
|
| 10 |
+
The procedure below records decisions frozen before test results were opened;
|
| 11 |
+
completion does not authorize tuning against these now-disclosed sequences.
|
| 12 |
+
`evaluation.py` consumes existing episode artifacts and position predictions;
|
| 13 |
+
it does not invoke models, select checkpoints, or start games.
|
| 14 |
+
|
| 15 |
+
## Decisions that must be frozen before opening the test results
|
| 16 |
+
|
| 17 |
+
The final episode cap is now **frozen at 200 pieces**, using all 200 reserved seeds.
|
| 18 |
+
This was chosen before inspecting any test results. The unchanged-model development
|
| 19 |
+
pilot measured approximately 144 ms per decision. If all three neural players reach
|
| 20 |
+
every cap, 120,000 decisions would take roughly 4.8 hours at that rate, excluding
|
| 21 |
+
loading and reporting. Earlier base development games died much sooner; runtime
|
| 22 |
+
must still be measured and reported rather than promised. Each episode is saved
|
| 23 |
+
atomically so interrupted evaluation can resume without discarding difficult games.
|
| 24 |
+
|
| 25 |
+
Freeze the selected trained checkpoint and original pinned Clef revision, input
|
| 26 |
+
encoding, precision, hardware/runtime configuration, heuristic revision, all
|
| 27 |
+
seeds, cap, inference failure behavior, bootstrap parameters and compute budget.
|
| 28 |
+
The same legal placements and one-piece preview must reach every player. Primary
|
| 29 |
+
base is the pinned native model with its original BF16 head. Training uses an FP32
|
| 30 |
+
head; therefore an explicitly named `base-fp32` ablation keeps the original weights
|
| 31 |
+
but uses the same FP32 wrapper as training. Report trained-versus-native-base as
|
| 32 |
+
the primary comparison and trained-versus-base-fp32 to separate training from head
|
| 33 |
+
precision effects. Backbone BF16, input encoding, context limit, device and source
|
| 34 |
+
revision must agree. A changed base quantization would be a separate comparison.
|
| 35 |
+
|
| 36 |
+
Checkpoint selection uses validation only. Teacher-action agreement and target
|
| 37 |
+
cross-entropy on validation positions are diagnostic selection metrics, separate
|
| 38 |
+
from complete-game outcomes. A model that imitates the expert more closely may
|
| 39 |
+
still play worse. Preregister any training configuration search and tie rule;
|
| 40 |
+
`paired_report` never makes those choices from test results.
|
| 41 |
+
|
| 42 |
+
## Paired episode outcomes
|
| 43 |
+
|
| 44 |
+
Primary outcome is lines cleared. Secondary outcomes are game score and pieces
|
| 45 |
+
placed. Retain every planned seed for each player, including failed episodes.
|
| 46 |
+
Duplicate player/seed pairs, mismatched seed sets, caps or rules, and changing
|
| 47 |
+
player identity/runtime within a comparison are rejected by the report function.
|
| 48 |
+
Its schema checks are not a replacement for verifying replay integrity when
|
| 49 |
+
episodes are collected.
|
| 50 |
+
|
| 51 |
+
An inference exception or illegal decision ends that player's episode. For the
|
| 52 |
+
primary analysis, assign **zero lines, score and pieces to any errored episode**.
|
| 53 |
+
Keep its original partial achievements in `observed_before_error`, retain its seed,
|
| 54 |
+
and report its error. This explicit reliability penalty prevents a model from
|
| 55 |
+
benefiting statistically from dropping difficult failures. The same policy applies
|
| 56 |
+
to base, trained and heuristic. Never describe a failed episode as a successful
|
| 57 |
+
zero-line game; status and error counts remain separate.
|
| 58 |
+
|
| 59 |
+
Report mean and median metrics, cap-hit rate, failed episodes, error rate and
|
| 60 |
+
invalid-decision count. Survival is censored at the cap: a cap hit means at least
|
| 61 |
+
that many pieces, not a measured death at that turn. This protocol cannot establish
|
| 62 |
+
uncapped longevity or full competitive Tetris performance.
|
| 63 |
+
|
| 64 |
+
For each seed, subtract base outcome from trained outcome. Resample these paired
|
| 65 |
+
episode differences with replacement, **10,000 bootstrap replicates, RNG seed
|
| 66 |
+
2026**, and report their mean plus the percentile 95% interval. Resampling turns
|
| 67 |
+
would falsely treat correlated moves within a game as independent observations.
|
| 68 |
+
Pieces and score get descriptive intervals; lines are the single primary endpoint.
|
| 69 |
+
The intervals are conditional on the tested checkpoint and episode distribution;
|
| 70 |
+
one trained seed does not characterize training-seed variability.
|
| 71 |
+
|
| 72 |
+
`positive_mean_lines_signal` is true only if there are at least two pairs, the
|
| 73 |
+
primary interval's lower bound exceeds zero, and both base and trained episode
|
| 74 |
+
error rates are at or below the declared threshold (**default 0%**). This flag is
|
| 75 |
+
evidence for that measured comparison, not an automatic claim of general
|
| 76 |
+
superiority, approval to publish, or a way to select a checkpoint on the test set.
|
| 77 |
+
The heuristic remains a separate required comparator. Run the same paired analysis
|
| 78 |
+
against it and disclose whether the model actually beats that cheap baseline.
|
| 79 |
+
|
| 80 |
+
## Timing and input lengths
|
| 81 |
+
|
| 82 |
+
Summarize decision latency mean, median and nearest-rank p95 from all attempts,
|
| 83 |
+
including failed attempts. These are timings recorded around `Player.choose`;
|
| 84 |
+
they include tokenization and policy overhead, exclude game rendering/replay, and
|
| 85 |
+
should not be advertised as pure GPU kernel time. CUDA probability transfer must
|
| 86 |
+
complete before `choose` returns. Record cold loading separately and identify any
|
| 87 |
+
warmup excluded before measured episodes. Never mix cached-replay speed with live
|
| 88 |
+
model latency. Input-token summaries use only events containing `input_tokens`
|
| 89 |
+
and report their count, so missing token metadata remains visible.
|
| 90 |
+
|
| 91 |
+
## Position predictions
|
| 92 |
+
|
| 93 |
+
`summarize_positions` reports agreement with the fixed teacher label, target NLL
|
| 94 |
+
and multiclass Brier score (sum over choices, averaged over positions). The fixed
|
| 95 |
+
label uses engine-order tie-breaking; agreement therefore penalizes choosing a
|
| 96 |
+
different equally valued action. Consider a separately named optimal-value tie
|
| 97 |
+
agreement metric later if preregistered; do not silently change the definition.
|
| 98 |
+
|
| 99 |
+
Missing or invalid predictions stay in the agreement denominator as failures.
|
| 100 |
+
Aggregate NLL/Brier are available only with a complete legal probability
|
| 101 |
+
distribution for every position. Partial diagnostic means have explicit subset
|
| 102 |
+
denominators and cannot be presented as full-set results. A zero probability on
|
| 103 |
+
the teacher label means infinite NLL; JSON stores a null mean plus an explicit
|
| 104 |
+
`nll_is_infinite` flag and zero-target count, rather than silently clipping it.
|
| 105 |
+
|
| 106 |
+
```python
|
| 107 |
+
from stackcraft.evaluation import paired_report, summarize_positions
|
| 108 |
+
|
| 109 |
+
# Load complete, previously collected artifacts; this runs no model or game.
|
| 110 |
+
report = paired_report(episodes, trained_id="trained", base_id="base")
|
| 111 |
+
position_report = summarize_positions(validation_rows, predictions_by_record_id)
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
Toy tests verify known constant paired improvements, mismatched-pair rejection,
|
| 115 |
+
failure retention, metadata consistency and proper probability-score arithmetic.
|
| 116 |
+
Passing them verifies report mechanics, not any model's game performance.
|
| 117 |
+
|
| 118 |
+
## Execute and resume a frozen evaluation
|
| 119 |
+
|
| 120 |
+
`scripts/evaluate_clef.py positions --dataset data/study-v1 --checkpoint PATH
|
| 121 |
+
--output NEWDIR` evaluates the entire validation split and writes unrounded
|
| 122 |
+
predictions plus NLL/agreement/Brier evidence. Its default players are native base,
|
| 123 |
+
base-fp32 and trained. Model loading is sequential to fit one GPU; this is real
|
| 124 |
+
inference and must follow memory admission. `--players trained` evaluates another
|
| 125 |
+
candidate without rerunning the unchanged baselines.
|
| 126 |
+
|
| 127 |
+
After validation, run `scripts/select_checkpoint.py` with both explicit epoch
|
| 128 |
+
checkpoint/report pairs and a new output directory, as shown in Tutorial 05.
|
| 129 |
+
It recomputes metrics from the raw predictions, verifies both checkpoint trees,
|
| 130 |
+
selects by the preregistered rule, and copies self-contained validation evidence.
|
| 131 |
+
Its `selection.json` contains schema_version 1,
|
| 132 |
+
selection_split `validation`, selected_key, decision_rule, checkpoint_sha256 mapping,
|
| 133 |
+
validation_metrics, validation_evidence `{path,sha256}`, test_seeds, their canonical
|
| 134 |
+
JSON SHA256 as test_seeds_sha256, max_pieces 200, encoding_version, max_length,
|
| 135 |
+
players, head_dtypes, bootstrap_samples 10000, bootstrap_seed 2026, max_error_rate
|
| 136 |
+
0.0. Validation evidence must be the script's position report for the same checkpoint;
|
| 137 |
+
its path is relative to selection.json. Checkpoint hashes include every checkpoint
|
| 138 |
+
file. Set head_dtypes to `{base: bfloat16, base-fp32: float32, trained: float32}`
|
| 139 |
+
when all three neural players are included.
|
| 140 |
+
|
| 141 |
+
Both epoch candidates and their eligibility reasons are mandatory in the selection
|
| 142 |
+
artifact. Before opening test seeds, the evaluator checks all 215 positions,
|
| 143 |
+
complete finite probability metrics, zero inference errors, checkpoint/dataset
|
| 144 |
+
binding, and the lowest-NLL winner with an earlier-epoch tie rule. All five players,
|
| 145 |
+
including the FP32-head ablation, are required by this study.
|
| 146 |
+
|
| 147 |
+
```sh
|
| 148 |
+
uv run --locked --extra ml python scripts/evaluate_clef.py tournament \
|
| 149 |
+
--checkpoint PATH --seeds 30000:30200 --max-pieces 200 \
|
| 150 |
+
--final-test --selection-file selection.json --output runs/final-evaluation
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
The default includes all five players. The final-test switch is an explicit
|
| 154 |
+
acknowledgment that these held-out seeds will now be consumed. Without it, the
|
| 155 |
+
script rejects any overlap with the reserved pool before loading a player.
|
| 156 |
+
|
| 157 |
+
Every completed episode is stored as `PLAYER/seed-N.json`; `progress.json` records
|
| 158 |
+
progress, and the final `report.json` includes all episodes and paired comparisons.
|
| 159 |
+
Repeat exactly the same command with `--resume` after an interruption. The script
|
| 160 |
+
checks request, source hashes, selected checkpoint, frozen protocol and saved replay
|
| 161 |
+
outcomes, skips completed players without loading them, and resumes missing seeds.
|
| 162 |
+
It never retries or deletes a recorded error episode. Loading is timed separately;
|
| 163 |
+
there are no silently excluded warmup decisions.
|
code/docs/interfaces.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Initial implementation contract
|
| 2 |
+
|
| 3 |
+
The Python engine is authoritative. These names are shared across independently implemented engine, service, and UI. All board coordinates have x increasing right and y increasing down; rows are top to bottom. Board cells are 0 for empty or 1 through 7 for piece colors. Board width is 10, height is 20. Four normalized orientations are enumerated and duplicate shapes removed.
|
| 4 |
+
|
| 5 |
+
`schema.py` defines frozen dataclasses: `Placement(id: str, rotation: int, x: int, y: int, cells: tuple[tuple[int,int], ...])`; `GameState(board: tuple[tuple[int,...],...], seed: int, piece_index: int, current: str, next_piece: str, score: int=0, lines: int=0, terminal: bool=False)`; `Transition(state: GameState, action: Placement, cleared: int)`.
|
| 6 |
+
|
| 7 |
+
`pieces.py`: `piece_at(seed: int, index: int) -> str`, `rotations(piece: str) -> tuple[tuple[tuple[int,int],...],...]`. Piece colors map in order `I,O,T,S,Z,J,L` to 1..7. Deterministic sequence is versioned, uses a local random generator, and is identical regardless of player pace.
|
| 8 |
+
|
| 9 |
+
`engine.py`: `new_game(seed: int) -> GameState`, `legal_actions(state: GameState) -> tuple[Placement,...]`, `step(state: GameState, action_id: str) -> Transition`. Action IDs are `r{rotation}x{x}`. Placements are straight vertical hard drops starting fully within the board. No legal placement means terminal. An illegal or terminal-state action raises `ValueError` without mutating state. `legal_actions` includes landing y and absolute cell positions so clients can display a ghost without computing game rules. Clear rows simultaneously, score 100/300/500/800 for 1/2/3/4 lines, advance piece index, and detect next-piece top-out.
|
| 10 |
+
|
| 11 |
+
`replay.py`: `make_replay(seed: int, actions: list[str]) -> dict` computes a JSON-serializable artifact containing `schema_version: 1`, `rules_version: "stackcraft-v1"`, seed, action IDs, and final score/lines/pieces/terminal. `replay_states(artifact: dict) -> list[GameState]` validates versions and actions and returns initial plus successive states; validate final summary if provided.
|
| 12 |
+
|
| 13 |
+
Service HTTP endpoints (all JSON unless noted): `GET /` serves the game; `POST /api/games` with `{seed: int}` returns a session snapshot; `POST /api/games/{id}/moves` with `{action_id: str, expected_pieces: int}` returns its next snapshot; `GET /api/games/{id}` snapshot; `GET /api/games/{id}/replay` returns replay artifact; `POST /api/replays` with an artifact returns `{frames: [snapshot_without_id,...]}`. `GET /health` returns `{status:"ok"}`. Snapshots contain `id`, `board`, `current`, `next_piece`, `score`, `lines`, `pieces` (piece index), `terminal`, `legal_actions` (serialized Placement). Error responses have readable `detail`; unknown session 404, invalid action/replay 422, stale expected_pieces 409. On uncertain move response the client refreshes state before allowing another move; replay retries must not advance a second piece. API never reveals future piece stream to model adapters (seed exists only in server storage/replay).
|
| 14 |
+
|
| 15 |
+
UI uses these routes directly with fetch; no second simulator. Arrow left/right selects legal placements at current rotation, up rotates selection, space/Enter drops. Buttons allow all actions. Restart requests the seed input again. Download replay and load replay use the above endpoints, with frame stepping. UI source belongs in `src/stackcraft/web/` and uses vanilla JS/CSS/HTML to avoid a second package manager. Label the game as turn-based placement; no hold, tucks, kicks, T-spin bonuses or gravity timer. Comparison boards will be added after baselines.
|
code/docs/rules.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stackcraft v1 rules
|
| 2 |
+
|
| 3 |
+
Rules version: `stackcraft-v1`. Replay schema version: `1`.
|
| 4 |
+
|
| 5 |
+
Stackcraft is a turn-based tetromino placement game. Its research rules deliberately
|
| 6 |
+
exclude real-time gravity, hold, wall kicks, tucks, lock delay, T-spins, combos,
|
| 7 |
+
back-to-back bonuses, and level multipliers. It is not a full competitive Tetris
|
| 8 |
+
implementation. Humans and every bot use the same legal-placement enumerator.
|
| 9 |
+
|
| 10 |
+
## Board and pieces
|
| 11 |
+
|
| 12 |
+
The board has 10 columns and 20 rows. Coordinates increase rightward (`x`) and
|
| 13 |
+
downward (`y`); `(0, 0)` is the top-left cell. Empty cells are `0`. Piece colors
|
| 14 |
+
are integer IDs `1..7` corresponding to `I,O,T,S,Z,J,L`.
|
| 15 |
+
|
| 16 |
+
Each piece has four cells. Orientation zero is defined in `pieces.py`. Other
|
| 17 |
+
orientations are successive clockwise rotations, normalized so their minimum x
|
| 18 |
+
and y are zero; duplicate orientations are removed. `I,S,Z` have two orientations,
|
| 19 |
+
`O` has one, and `T,J,L` have four. Rotation numbers refer to this unique list,
|
| 20 |
+
not to a wall-kick rotation system.
|
| 21 |
+
|
| 22 |
+
## Piece stream and information
|
| 23 |
+
|
| 24 |
+
Each seven-piece bag contains every shape once. Bag number `b` uses a private
|
| 25 |
+
Python `Random` instance seeded with the string `stackcraft-v1:{seed}:{b}` and
|
| 26 |
+
an explicitly defined descending Fisher-Yates shuffle: at position `p` swap with
|
| 27 |
+
`int(random() * (p + 1))`. This avoids dependence on global random state, request
|
| 28 |
+
order, and higher-level shuffle implementation changes. The locked Python
|
| 29 |
+
environment and stream fixture tests provide an additional reproduction check.
|
| 30 |
+
|
| 31 |
+
Players observe the board, current piece, and exactly one next piece. Seeds,
|
| 32 |
+
bag state, and later pieces must not appear in model observations. Replays contain
|
| 33 |
+
a seed for reconstruction, so replay metadata is not a valid model observation.
|
| 34 |
+
Piece index is the count of successfully placed pieces, initially zero.
|
| 35 |
+
|
| 36 |
+
## Legal moves and top-out
|
| 37 |
+
|
| 38 |
+
For each unique orientation and fitting column, place the normalized shape at
|
| 39 |
+
`y=0`, fully within the board. If any of those cells is occupied, this placement
|
| 40 |
+
is unavailable, even if there is space below. Otherwise move the shape down one
|
| 41 |
+
row at a time until the next step would collide or leave the board. This is the
|
| 42 |
+
only landing for that orientation and column. A piece cannot pass through a
|
| 43 |
+
block or slide underneath an overhang.
|
| 44 |
+
|
| 45 |
+
The action ID is `r{rotation}x{x}`, for example `r1x4`. Legal actions include the
|
| 46 |
+
landing y-coordinate and all four absolute cell coordinates. They are ordered
|
| 47 |
+
by rotation, then x, making deterministic tie-breaking possible. If there are no
|
| 48 |
+
legal moves, the game is terminal. Top-out is evaluated for the next current
|
| 49 |
+
piece after the preceding move's row clears. There are no hidden spawn rows.
|
| 50 |
+
|
| 51 |
+
## Clearing and scoring
|
| 52 |
+
|
| 53 |
+
After placing a piece, remove every full row simultaneously. Remaining rows keep
|
| 54 |
+
their relative order and empty rows are added at the top. Award these points:
|
| 55 |
+
|
| 56 |
+
| Rows cleared by this move | Points |
|
| 57 |
+
| --- | --- |
|
| 58 |
+
| 0 | 0 |
|
| 59 |
+
| 1 | 100 |
|
| 60 |
+
| 2 | 300 |
|
| 61 |
+
| 3 | 500 |
|
| 62 |
+
| 4 | 800 |
|
| 63 |
+
|
| 64 |
+
Advance to the preview piece and reveal one new preview. Lines and score are
|
| 65 |
+
cumulative; there are no movement or hard-drop points. Lines cleared are the
|
| 66 |
+
primary research outcome. Score, placed pieces, and top-out are additional
|
| 67 |
+
outcomes. Evaluation episode caps are separate from these rules and must be
|
| 68 |
+
reported; reaching a cap does not imply top-out.
|
| 69 |
+
|
| 70 |
+
## State, errors, and replay
|
| 71 |
+
|
| 72 |
+
`GameState`, `Placement`, and `Transition` are frozen dataclasses. Boards and
|
| 73 |
+
cell collections are tuples. `step(state, action_id)` validates an ID against
|
| 74 |
+
the current legal moves and returns a new state. Invalid moves, including any
|
| 75 |
+
move after top-out, raise `ValueError`; the input state remains unchanged.
|
| 76 |
+
|
| 77 |
+
`place(board, piece, action)` is a lower-level helper for expert and heuristic
|
| 78 |
+
afterboard evaluation. It reads no seed or future piece. Its action must already
|
| 79 |
+
come from `legal_actions` for that board and piece; it checks cell bounds and
|
| 80 |
+
occupation, but does not repeat path validation. External move IDs use `step`.
|
| 81 |
+
|
| 82 |
+
A replay stores schema/rules versions, seed, action IDs, and final score, lines,
|
| 83 |
+
placed pieces and terminal status. Import re-simulates every move. Invalid moves,
|
| 84 |
+
incompatible versions and inconsistent final summaries are rejected. Partial
|
| 85 |
+
games are valid. A consistent replay is reproducible evidence, not a signed
|
| 86 |
+
attestation of who played: changing actions and recomputing their summary creates
|
| 87 |
+
another valid game.
|
code/docs/tutorials/00-foundation.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Milestone 00: a reproducible experiment starts with a reproducible game
|
| 2 |
+
|
| 3 |
+
The eventual question is whether training helps a decision model play better. Before loading a model, establish a game that behaves identically for two players given the same pieces. Otherwise a score difference could reflect easier pieces or different rules.
|
| 4 |
+
|
| 5 |
+
## 1. Create an isolated Python project
|
| 6 |
+
|
| 7 |
+
This repository uses Python 3.13, a `src/stackcraft` package, and uv. Run from the repository root:
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
uv sync --locked
|
| 11 |
+
uv run --locked stackcraft --help
|
| 12 |
+
uv run --locked stackcraft --version
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
The help lists `serve`, `tournament` and `generate-data`; the version is `stackcraft 0.1.0`. uv creates `.venv` and installs the package locally. `pyproject.toml` describes dependencies while `uv.lock` records exact resolved versions. Committing both lets another person recreate the environment. `--locked` detects a stale lock instead of silently selecting new versions.
|
| 16 |
+
|
| 17 |
+
Development dependencies are a dependency group, installed by default, rather than an optional product feature. This keeps pytest and linting separate from game requirements. The ML extra isolates Torch and large model dependencies so someone can play the game without a GPU. The code does not import a model from the CLI startup path.
|
| 18 |
+
|
| 19 |
+
## 2. Write the rules before comparing players
|
| 20 |
+
|
| 21 |
+
Read `docs/rules.md` and `docs/interfaces.md`. All players receive the same board, current piece, and one-piece preview. They select an orientation and column; Python computes the vertical landing. A fixed seven-bag seed makes piece order reproducible. A seed is stored for replay, but never shown to a model: exposing it or later pieces would let one player gain information unavailable to another.
|
| 22 |
+
|
| 23 |
+
The simplified rules omit real-time gravity and wall kicks. A full Tetris implementation would mix planning ability with timing and control. We first isolate the decision we want to learn: which legal placement is best? The UI must disclose those rules.
|
| 24 |
+
|
| 25 |
+
## 3. Keep one authoritative engine
|
| 26 |
+
|
| 27 |
+
Python simulates placements and emits legal actions with landing cells. The browser draws these cells and sends an action ID back. It does not recalculate collisions. Two engines in different languages could diverge and make replays unreliable. Frozen state objects and pure transition functions make mistakes easier to reproduce and test.
|
| 28 |
+
|
| 29 |
+
FastAPI serves the small JSON API and static HTML/CSS/JavaScript together. This first version needs no separate Node package manager or frontend framework. That reduces setup friction while leaving room to add one when the interface genuinely needs it.
|
| 30 |
+
|
| 31 |
+
## 4. Verify behavior and source quality
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
uv run --locked ruff check .
|
| 35 |
+
uv run --locked ruff format --check .
|
| 36 |
+
uv run --locked ty check src/stackcraft --exclude src/stackcraft/training.py
|
| 37 |
+
uv run --locked pytest
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
These commands check the game without installing ML packages. Full-repository
|
| 41 |
+
type checking also covers training scripts and requires the ML extra:
|
| 42 |
+
|
| 43 |
+
```bash
|
| 44 |
+
uv run --locked --extra ml ty check
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
Installing that extra does not run GPU inference. When keeping an ML environment,
|
| 48 |
+
use `--extra ml` on uv commands to prevent synchronization from removing those
|
| 49 |
+
optional packages.
|
| 50 |
+
|
| 51 |
+
Ruff catches common source mistakes and applies one formatting style. ty checks Python types. pytest verifies outcomes such as a cleared row, an unchanged state after an illegal move, and a replay that reaches the original final board. Passing checks does not prove model quality; that requires a later held-out tournament.
|
| 52 |
+
|
| 53 |
+
## 5. Preserve decisions and evidence
|
| 54 |
+
|
| 55 |
+
`plan.md` tracks milestones; `record.md` stores concise results and experiment intentions. Code and tutorials use local Git commits. Environments, weights, raw datasets, credentials and checkpoints are ignored. Later reports will refer to their hashes and release revisions rather than placing gigabytes in Git.
|
| 56 |
+
|
| 57 |
+
The next milestone adds the engine, playable board and replay. Model training starts only after those behaviors can be verified independently.
|
code/docs/tutorials/01-game.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Milestone 1: build a game whose decisions can be replayed
|
| 2 |
+
|
| 3 |
+
The deliverable is a playable turn-based game backed by a deterministic Python
|
| 4 |
+
engine. Before interpreting a model's performance, we need confidence that every
|
| 5 |
+
player follows identical rules and that saved decisions reconstruct the game.
|
| 6 |
+
Read [the complete rules](../rules.md) before extending the simulator.
|
| 7 |
+
|
| 8 |
+
## 1. Install and start the game
|
| 9 |
+
|
| 10 |
+
From the repository root, install the committed environment and start the server:
|
| 11 |
+
|
| 12 |
+
```sh
|
| 13 |
+
uv sync --locked
|
| 14 |
+
uv run --locked stackcraft serve --host 127.0.0.1 --port 8080
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
Open `http://127.0.0.1:8080`. Choose a seed and start a game. Left/right select a
|
| 18 |
+
column, up changes orientation, and Space or Enter drops the selected placement.
|
| 19 |
+
Buttons provide the same controls. There is no gravity timer: take as long as
|
| 20 |
+
you need. The preview shows exactly one future piece.
|
| 21 |
+
|
| 22 |
+
Use restart with the same seed to receive the same piece stream. Download the
|
| 23 |
+
replay, load it again, and step through its frames. The imported game is simulated
|
| 24 |
+
again by Python; the browser does not trust a claimed score in the file.
|
| 25 |
+
|
| 26 |
+
Why this design: real-time input would mix decision quality with timing and
|
| 27 |
+
keyboard control. Placement decisions are an interpretable first experiment.
|
| 28 |
+
The browser displays authoritative legal moves rather than maintaining a second
|
| 29 |
+
simulator that could drift away from training rules.
|
| 30 |
+
|
| 31 |
+
## 2. Inspect the pure engine
|
| 32 |
+
|
| 33 |
+
```python
|
| 34 |
+
from stackcraft.engine import legal_actions, new_game, step
|
| 35 |
+
|
| 36 |
+
before = new_game(seed=42)
|
| 37 |
+
move = legal_actions(before)[0]
|
| 38 |
+
transition = step(before, move.id)
|
| 39 |
+
assert before.piece_index == 0
|
| 40 |
+
assert transition.state.piece_index == 1
|
| 41 |
+
print(move.id, move.cells, transition.cleared)
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
Every state is immutable. `step` returns a new state and the resolved placement;
|
| 45 |
+
it does not modify `before`. This makes it possible to examine multiple candidate
|
| 46 |
+
moves from exactly the same position without accidentally advancing the real game.
|
| 47 |
+
Actions are ordered by orientation then column so ties can be resolved reproducibly.
|
| 48 |
+
|
| 49 |
+
The engine starts each candidate shape at row zero and drops it vertically. It
|
| 50 |
+
checks every step rather than picking the lowest empty space: the latter would
|
| 51 |
+
incorrectly let pieces pass through overhangs. Row clears happen simultaneously,
|
| 52 |
+
then the next piece is checked for top-out.
|
| 53 |
+
|
| 54 |
+
Why not use a mutable board everywhere: shared mutable state makes search branches,
|
| 55 |
+
HTTP sessions, and replay easier to contaminate accidentally. Frozen dataclasses
|
| 56 |
+
and tuple boards make those mistakes visible. For game-scale boards, clarity is
|
| 57 |
+
more valuable than prematurely optimizing away small copies.
|
| 58 |
+
|
| 59 |
+
## 3. Separate hidden state from observations
|
| 60 |
+
|
| 61 |
+
The seed determines independently shuffled seven-piece bags. A bag includes every
|
| 62 |
+
shape once; it is not seven independent random draws. `piece_at(seed, index)` is
|
| 63 |
+
random-access and uses its own PRNG, so opening another game cannot alter a stream.
|
| 64 |
+
|
| 65 |
+
Only the current piece and one preview are legitimate player inputs. The engine
|
| 66 |
+
and replay know the seed to reconstruct games, but a model or search expert must
|
| 67 |
+
not exploit it to look further ahead. The lower-level `place` helper returns an
|
| 68 |
+
afterboard and row-clear count without reading any piece stream. Later baselines
|
| 69 |
+
and the search expert can therefore evaluate a candidate safely.
|
| 70 |
+
|
| 71 |
+
## 4. Reconstruct and validate a replay
|
| 72 |
+
|
| 73 |
+
```python
|
| 74 |
+
import json
|
| 75 |
+
|
| 76 |
+
from stackcraft.replay import make_replay, replay_states
|
| 77 |
+
|
| 78 |
+
artifact = make_replay(42, [move.id])
|
| 79 |
+
frames = replay_states(json.loads(json.dumps(artifact)))
|
| 80 |
+
assert frames == [before, transition.state]
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
The replay contains actions rather than a pile of unverified board screenshots.
|
| 84 |
+
Its rules version is necessary: changing rotations or scoring must not silently
|
| 85 |
+
reinterpret an old experiment. Import also checks the final summary against the
|
| 86 |
+
recomputed outcome and rejects moves after terminal state.
|
| 87 |
+
|
| 88 |
+
This detects inconsistent files, not malicious authorship. Someone can construct
|
| 89 |
+
a different valid action sequence and summary; publication provenance and artifact
|
| 90 |
+
hashes will be separate experiment records.
|
| 91 |
+
|
| 92 |
+
## 5. Check the behavioural contract
|
| 93 |
+
|
| 94 |
+
```sh
|
| 95 |
+
uv run --locked pytest tests/test_pieces.py tests/test_engine.py tests/test_replay.py
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
These tests cover shape orientations, all empty-board placements, boundaries,
|
| 99 |
+
collisions, blocked entry paths, one through four simultaneous row clears,
|
| 100 |
+
score accumulation, top-out, deterministic piece streams, state immutability,
|
| 101 |
+
replay round-trips and tampering. Tests for the service cover request handling
|
| 102 |
+
separately. Browser inspection must additionally check readable ghost pieces,
|
| 103 |
+
keyboard/button actions, restart and replay controls; engine tests cannot prove
|
| 104 |
+
that an interface is usable.
|
| 105 |
+
|
| 106 |
+
Next, measure random and heuristic players before adding a model. A playable
|
| 107 |
+
game establishes the experimental environment; it does not yet demonstrate that
|
| 108 |
+
fine-tuning helps.
|
| 109 |
+
|
| 110 |
+
## Watch recorded players side by side
|
| 111 |
+
|
| 112 |
+
Choose **Watch a race** to load the packaged recordings for unchanged Clef-flash,
|
| 113 |
+
the Stackcraft fine-tune and the heuristic. The release's `baseline-demo.json`
|
| 114 |
+
contains their actual action traces for **seed 30000**, fixed before inspecting
|
| 115 |
+
results, with the same **200-piece cap**. The service reconstructs every frame
|
| 116 |
+
through the authoritative replay engine. This replaces the earlier development
|
| 117 |
+
Random/Heuristic example; it does not rerun model inference in your browser.
|
| 118 |
+
|
| 119 |
+
At the final frame of this illustrative seed, unchanged Clef has topped out after
|
| 120 |
+
23 pieces without clearing a line, the fine-tune has topped out after 82 pieces
|
| 121 |
+
with 16 lines, and the heuristic has reached the 200-piece cap with 78 lines. These
|
| 122 |
+
are one game's outcomes, not the aggregate result across 200 held-out seeds.
|
| 123 |
+
Consult the full study report to assess overall performance and uncertainty.
|
| 124 |
+
|
| 125 |
+
The boards are synchronized by placements, not decision time. Playback is
|
| 126 |
+
explicitly recorded and does not imply live inference or model speed. A player
|
| 127 |
+
that tops out keeps its final board visible while other recordings continue.
|
| 128 |
+
Expand a recorded choice to inspect its actual selected-action probability and
|
| 129 |
+
up to three alternatives; these are model outputs, not generated explanations.
|
| 130 |
+
The provenance section lists the source artifact hashes. Returning to Play
|
| 131 |
+
preserves the human game.
|
| 132 |
+
|
| 133 |
+
## Play alongside recorded opponents
|
| 134 |
+
|
| 135 |
+
The race view offers an explicit **Start live board · seed N** action. This starts
|
| 136 |
+
a new human game with the recording's seed and episode cap; merely opening or
|
| 137 |
+
leaving the race tab does not reset the current game. The human board is labelled
|
| 138 |
+
**LIVE HUMAN**, while opponents remain labelled as recorded. Both use the Python
|
| 139 |
+
engine's legal placements and the same deterministic piece stream.
|
| 140 |
+
|
| 141 |
+
Each human placement advances the recorded timeline to the same piece count. A
|
| 142 |
+
recording that ended earlier stays on its final frame. You can scrub the recorded
|
| 143 |
+
timeline independently without changing your board; **Match my move** resynchronizes
|
| 144 |
+
it. The original board DOM and session controller move between views, avoiding a
|
| 145 |
+
second simulator or conflicting copies of live state.
|
| 146 |
+
|
| 147 |
+
At the recording's episode cap, human placement controls stop and show **Cap
|
| 148 |
+
reached**, distinct from top-out. Switching to Play retains that comparison cap
|
| 149 |
+
and the same session. **Leave comparison** removes comparison mode and returns
|
| 150 |
+
the existing game to ordinary play; only starting/restarting a game resets it.
|
| 151 |
+
Pending moves temporarily disable view changes so a late response cannot update
|
| 152 |
+
a different session. Desktop boards align side by side; narrow screens stack them
|
| 153 |
+
without shrinking the human controls.
|
code/docs/tutorials/02-baselines.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tutorial 02 — Build a comparison before training
|
| 2 |
+
|
| 3 |
+
This milestone establishes offline opponents and a replayable tournament. **Unchanged Clef now has a small real-GPU development benchmark.** These results cannot establish an advantage from fine-tuning.
|
| 4 |
+
|
| 5 |
+
## Give every player the same information
|
| 6 |
+
|
| 7 |
+
`players.observe(state)` creates a frozen `Observation`: board, current piece, exactly
|
| 8 |
+
one next piece, legal placements, and the rules version. It omits the episode seed,
|
| 9 |
+
piece index, and random generator. A seed would reveal the entire future sequence;
|
| 10 |
+
giving it to a search player would invalidate the intended experiment. The tournament
|
| 11 |
+
retains the seed separately so another person can replay an episode.
|
| 12 |
+
|
| 13 |
+
Players implement `choose(observation) -> Decision`. A decision identifies one legal
|
| 14 |
+
placement and can provide a complete probability distribution. The runner rejects
|
| 15 |
+
illegal moves, missing probabilities, nonfinite probabilities, and incorrect totals.
|
| 16 |
+
An exception ends that player's episode as an explicit error. A random fallback would
|
| 17 |
+
hide deployment failures and turn the measured player into a different policy.
|
| 18 |
+
|
| 19 |
+
## Start with two cheap opponents
|
| 20 |
+
|
| 21 |
+
`RandomPlayer(seed=0)` samples uniformly from legal placements using its own random
|
| 22 |
+
generator. Its seed is unrelated to the game's sequence seed. The tournament creates
|
| 23 |
+
a fresh instance for each episode, so earlier game length cannot alter later choices.
|
| 24 |
+
|
| 25 |
+
`HeuristicPlayer` scores every legal afterboard with these fixed, untuned weights:
|
| 26 |
+
|
| 27 |
+
value = -aggregate_height - 4*holes - bumpiness + 8*cleared_lines
|
| 28 |
+
|
| 29 |
+
Aggregate height sums the ten column heights. A hole is an empty cell below a filled
|
| 30 |
+
cell in its column. Bumpiness sums differences between adjacent column heights. The
|
| 31 |
+
line bonus uses lines cleared by this move. All features describe the board after
|
| 32 |
+
simultaneous row removal. Ties follow the engine's rotation-then-column ordering.
|
| 33 |
+
These intentionally simple integer weights are a declared baseline, not an optimized
|
| 34 |
+
or competitive Tetris claim. The heuristic receives the preview but does not use it;
|
| 35 |
+
the later search expert will be able to search one additional placement.
|
| 36 |
+
|
| 37 |
+
We call the authoritative engine's `place` helper to obtain afterboards. Calling
|
| 38 |
+
`step` on the full episode state would expose the hidden future sequence, and a
|
| 39 |
+
separate simulator could disagree with the game's actual rules.
|
| 40 |
+
|
| 41 |
+
## Preregister the development pilot
|
| 42 |
+
|
| 43 |
+
Before running any tournament, the pilot is fixed as: development seeds **0–19**,
|
| 44 |
+
100-piece cap, random RNG seed 0, the weights above, and two players (random and
|
| 45 |
+
heuristic). Question: does the runner produce repeatable paired games, and does the
|
| 46 |
+
simple board heuristic behave plausibly? This is engineering development, not a
|
| 47 |
+
held-out model study. Do not put these seeds into the final test pool or use this
|
| 48 |
+
pilot to claim model improvement. Stop after 40 episodes or a runner failure.
|
| 49 |
+
|
| 50 |
+
Expected artifact: `runs/baselines-development.json`. Record mean/median lines,
|
| 51 |
+
score, pieces placed, cap-hit and error rates, and decision latency. Acceptance is
|
| 52 |
+
valid replays and no unexplained errors; poor heuristic play is reportable, not a
|
| 53 |
+
reason to secretly select nicer seeds. Starting commit is
|
| 54 |
+
`53db6bd0e7f42723bc63db7ad56c4ac139a04031`, plus the uncommitted baseline
|
| 55 |
+
implementation recorded in Git at integration.
|
| 56 |
+
|
| 57 |
+
## Reproduce the pilot
|
| 58 |
+
|
| 59 |
+
From the repository, after `uv sync --locked --group dev`:
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
uv run --locked python - <<'PY'
|
| 63 |
+
import json
|
| 64 |
+
from pathlib import Path
|
| 65 |
+
from stackcraft.players import HeuristicPlayer, RandomPlayer
|
| 66 |
+
from stackcraft.tournament import run_tournament
|
| 67 |
+
|
| 68 |
+
result = run_tournament({'random': RandomPlayer, 'heuristic': HeuristicPlayer}, range(20), 100)
|
| 69 |
+
output = Path('runs/baselines-development.json')
|
| 70 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 71 |
+
output.write_text(json.dumps(result, indent=2) + '\n')
|
| 72 |
+
print(json.dumps(result['summary'], indent=2))
|
| 73 |
+
PY
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Each episode records visible observations, chosen action IDs, optional probabilities,
|
| 77 |
+
the complete action replay, final outcomes, explicit errors, and decision times.
|
| 78 |
+
`replay_states(episode['replay'])` reconstructs its final board independently. All
|
| 79 |
+
players receive identical piece sequences for each seed, even when one dies earlier.
|
| 80 |
+
|
| 81 |
+
The cap censors survival: 100 pieces means "survived at least 100", not "would die
|
| 82 |
+
at 100". Report cap-hit rate alongside survival. Latency times only `choose`, not
|
| 83 |
+
rendering, observation construction, engine transition, or replay validation. It
|
| 84 |
+
includes Python overhead and is machine dependent. Game content is deterministic;
|
| 85 |
+
latency is deliberately excluded when checking repeatability.
|
| 86 |
+
|
| 87 |
+
## Development pilot result
|
| 88 |
+
|
| 89 |
+
The preregistered pilot ran on 2026-10-05. All 40 episode replays matched their
|
| 90 |
+
recorded final outcomes; neither player produced an error.
|
| 91 |
+
|
| 92 |
+
| Player | Mean lines | Median lines | Mean score | Mean pieces | Cap-hit rate | Mean decision time |
|
| 93 |
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
| 94 |
+
| Random | 0.10 | 0 | 10 | 26.9 | 0% | 0.0016 ms |
|
| 95 |
+
| Heuristic | 35.95 | 36 | 3,900 | 100 | 100% | 0.179 ms |
|
| 96 |
+
|
| 97 |
+
Every heuristic episode reached the cap. This supports using a longer development
|
| 98 |
+
cap to estimate the cost of final evaluation; it does not measure unlimited survival.
|
| 99 |
+
The pilot ran in approximately 3.37 seconds including independent replay checks
|
| 100 |
+
and writing JSON. Timing is illustrative local CPU execution, with no GPU model.
|
| 101 |
+
Artifact SHA-256:
|
| 102 |
+
`ed4973c69496cdc0ebb5720e3beaae621406988a6d298233c10ab962e1c499af`.
|
| 103 |
+
Regeneration preserves game outcomes but changes timing fields and therefore the
|
| 104 |
+
full artifact hash.
|
| 105 |
+
|
| 106 |
+
## Actual unchanged Clef development run
|
| 107 |
+
|
| 108 |
+
The pinned native Clef-flash backbone and joint head ran on RTX 5090 in BF16.
|
| 109 |
+
First cache the exact upstream snapshot from the repository root:
|
| 110 |
+
|
| 111 |
+
```bash
|
| 112 |
+
uv run --locked --extra ml hf download Cloudflare/clef-flash \
|
| 113 |
+
--revision 17f0b0ad64efb65d273590632833508766b2aae6
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
This installs the optional ML dependencies and downloads files; it does not load
|
| 117 |
+
the model onto the GPU. The benchmark loader uses local files only and will fail
|
| 118 |
+
on a fresh machine without this step. Once at least25GiB GPU memory is free, run:
|
| 119 |
+
|
| 120 |
+
```bash
|
| 121 |
+
uv run --locked --extra ml python scripts/benchmark_clef.py \
|
| 122 |
+
--output runs/clef-development-repeat.json --episodes 2 --max-pieces 30
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
Choose a new output path if it already exists. Do not stop other workloads without
|
| 126 |
+
authorization. The loader explicitly trusts the pinned, hash-checked upstream
|
| 127 |
+
Python implementation; see Tutorial04 for that trust boundary and memory checks.
|
| 128 |
+
|
| 129 |
+
On development seeds0–1 (30-piece cap), Clef averaged0lines and27.5pieces;
|
| 130 |
+
the heuristic averaged9lines and reached30pieces both times. No player errored.
|
| 131 |
+
Clef's55decisions averaged144ms (p95 241ms), after a separate warmup.
|
| 132 |
+
These two episodes establish feasibility, not statistical performance. Raw probabilities,
|
| 133 |
+
input-token counts, pinned revisions and timing are retained in
|
| 134 |
+
`runs/clef-development-v1.json`. Reference PyTorch attention kernels were used;
|
| 135 |
+
faster optional kernels were not installed, so latency is implementation-specific.
|
| 136 |
+
The GLM service was temporarily stopped under user authorization and restored;
|
| 137 |
+
its in-container health endpoint returned `{"status":"ok"}`.
|
| 138 |
+
|
| 139 |
+
Use fresh held-out episode seeds for the final study. A large neural model can lose
|
| 140 |
+
to a few lines of board arithmetic; the report must make that outcome visible.
|
code/docs/tutorials/03-data.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tutorial 03 — Teach from search without leaking future pieces
|
| 2 |
+
|
| 3 |
+
The dataset records recommendations from a bounded search expert. These are
|
| 4 |
+
synthetic labels from a specified algorithm, not human labels or globally optimal
|
| 5 |
+
Tetris moves. The expert sees the current piece and one preview, exactly as other
|
| 6 |
+
players do.
|
| 7 |
+
|
| 8 |
+
## Write the experiment before collecting examples
|
| 9 |
+
|
| 10 |
+
Starting commit: `412be98cf90bc7e3e60cd39d8bf9d479bcf65d27`, plus the expert/data
|
| 11 |
+
implementation committed at integration. Preregistered pilot: training seeds
|
| 12 |
+
10000–10023, validation seeds 20000–20005, at most 40 pieces per episode. Cycle
|
| 13 |
+
behavior policies random, heuristic, expert by episode index within each split.
|
| 14 |
+
Label every visited nonterminal state with the expert. Generate the pilot twice
|
| 15 |
+
and require identical records, manifest and JSONL hashes. Stop each job at 120
|
| 16 |
+
seconds or the first audit failure. Output the first bundle under `data/pilot-v1`.
|
| 17 |
+
|
| 18 |
+
Reserve seeds 30000–30199 for final evaluation; generate no trajectories from
|
| 19 |
+
them during dataset work. Development seeds 0–19 and 1000 are excluded from all
|
| 20 |
+
dataset pools. A separate development comparison uses only seeds 0–4 and a
|
| 21 |
+
100-piece cap, with heuristic and expert. Its purpose is to inspect search behavior
|
| 22 |
+
and cost, not measure neural-model improvement. Do not tune weights using these
|
| 23 |
+
results. Expected comparison artifact: `runs/expert-development.json`.
|
| 24 |
+
|
| 25 |
+
## Search only information available to the player
|
| 26 |
+
|
| 27 |
+
For each legal current placement, the expert clears completed rows and enumerates
|
| 28 |
+
all legal placements of the visible preview. It scores:
|
| 29 |
+
|
| 30 |
+
8 * current_lines + max(preview_afterboard_value)
|
| 31 |
+
|
| 32 |
+
The afterboard value is the same untuned `-height -4*holes -bumpiness +8*lines`
|
| 33 |
+
heuristic from Tutorial 02. If the preview has no legal placement, its value is
|
| 34 |
+
-10000. This is a two-placement search with no pruning. Equal values select the
|
| 35 |
+
first engine action in rotation/column order.
|
| 36 |
+
|
| 37 |
+
The search constructs a synthetic state with dummy seed/index only to enumerate
|
| 38 |
+
geometry. It calls `place`, never `step`, so it cannot query the piece after the
|
| 39 |
+
preview. Tests make sequence lookup fail during search to enforce that boundary.
|
| 40 |
+
Searching more known future pieces would give the teacher information unavailable
|
| 41 |
+
to the model; it would solve a different task. Long-horizon search is also more
|
| 42 |
+
expensive and is unnecessary for this first imitation-learning experiment.
|
| 43 |
+
|
| 44 |
+
## Collect recovery states as well as expert play
|
| 45 |
+
|
| 46 |
+
Only collecting expert trajectories produces many tidy boards and few recovery
|
| 47 |
+
situations. Cycling random, greedy heuristic, and expert behavior visits different
|
| 48 |
+
board conditions. Every behavior's state receives the same expert label. The
|
| 49 |
+
random policy has its own RNG, seeded with episode seed plus one million; this
|
| 50 |
+
controls collection reproducibility without exposing any seed to the teacher.
|
| 51 |
+
|
| 52 |
+
Each row retains the raw visible observation, action values for every legal move,
|
| 53 |
+
chosen move, behavior policy, episode/turn, rules, teacher revision and source
|
| 54 |
+
commit. Provenance fields are for auditing; training inputs must use only
|
| 55 |
+
`observation`. In particular, never serialize the whole dataset row as model state.
|
| 56 |
+
|
| 57 |
+
## Split episodes first, then remove duplicate observations
|
| 58 |
+
|
| 59 |
+
Seed pools are disjoint before generation. All retained states from one episode
|
| 60 |
+
remain in one split. Exact visible observations can still repeat across episodes,
|
| 61 |
+
especially empty boards. Deduplication hashes the full observation after replacing
|
| 62 |
+
board colors with occupied/empty cells, because colors do not affect game dynamics.
|
| 63 |
+
Current piece, preview, legal geometry, and rules remain in that hash.
|
| 64 |
+
|
| 65 |
+
Process training before validation and keep the first unique observation. Remove
|
| 66 |
+
later duplicates within either split and across splits, recording both counts.
|
| 67 |
+
Require duplicate teacher labels and action values to agree; a conflict stops the
|
| 68 |
+
run. This deterministic policy sacrifices some validation rows rather than letting
|
| 69 |
+
identical positions inflate reported generalization. Full episode IDs and seed
|
| 70 |
+
reservations are checked separately from observation hashes.
|
| 71 |
+
|
| 72 |
+
## Reproduce and audit
|
| 73 |
+
|
| 74 |
+
```python
|
| 75 |
+
from pathlib import Path
|
| 76 |
+
from stackcraft.data import DatasetConfig, audit_dataset, generate_dataset, write_dataset
|
| 77 |
+
from stackcraft.provenance import source_identity
|
| 78 |
+
|
| 79 |
+
config = DatasetConfig()
|
| 80 |
+
identity = source_identity(Path.cwd())
|
| 81 |
+
commit = identity["source_commit"] + ("+working-tree" if identity["source_dirty"] else "")
|
| 82 |
+
output = Path("data/pilot-v1")
|
| 83 |
+
assert not output.exists(), "Choose a new output directory"
|
| 84 |
+
bundle = generate_dataset(config, source_commit=commit)
|
| 85 |
+
assert bundle == generate_dataset(config, source_commit=bundle.manifest["source_commit"])
|
| 86 |
+
print(audit_dataset(bundle.records, bundle.manifest))
|
| 87 |
+
write_dataset(bundle, output)
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
Run with `uv run --locked python` from the repository root or the public bundle's
|
| 91 |
+
`code/` directory. Source identity uses that exact Git root or verifies the bundled
|
| 92 |
+
source manifest, so private Git access is not required. This creates a new pilot
|
| 93 |
+
with the current source identity, not necessarily the original study's bytes.
|
| 94 |
+
The writer refuses existing artifact files so a new experiment cannot silently
|
| 95 |
+
replace recorded evidence.
|
| 96 |
+
The manifest stores the complete configuration, seed reservations, teacher formula
|
| 97 |
+
revision, collection and exclusion counts, and SHA-256 hashes of canonical JSONL.
|
| 98 |
+
Auditing reconstructs legal placements and verifies label argmax with the tie rule,
|
| 99 |
+
finite values, provenance, duplicate exclusion, and absence of hidden observation
|
| 100 |
+
fields. It does not prove that the teacher is optimal. Evaluation must still test
|
| 101 |
+
complete games, not just how accurately a model copies these labels.
|
| 102 |
+
|
| 103 |
+
The manifest also fingerprints `engine.py`, `pieces.py`, `schema.py`,
|
| 104 |
+
`players/__init__.py`, `expert.py`, and `data.py`. During development, append
|
| 105 |
+
`+working-tree` to the commit identifier when source edits are not committed.
|
| 106 |
+
Before study training, freeze the dataset against its committed generator source.
|
| 107 |
+
Preserve those exact bytes through publication; later documentation or packaging
|
| 108 |
+
commits are not reasons to regenerate the training dataset. A source hash records
|
| 109 |
+
what ran; it must not imply that an earlier commit includes later code.
|
| 110 |
+
|
| 111 |
+
## Pilot observations
|
| 112 |
+
|
| 113 |
+
The preregistered 2026-10-05 pilot collected 833 training and 217 validation
|
| 114 |
+
positions. Six repeated training positions and two validation positions already
|
| 115 |
+
present in training were removed, leaving **827 training / 215 validation** rows.
|
| 116 |
+
Both complete generation runs produced identical bundles. Each took about 9.05
|
| 117 |
+
seconds; a separate structural audit took 0.49 seconds on the development machine.
|
| 118 |
+
|
| 119 |
+
The historical pilot's JSONL hashes are:
|
| 120 |
+
|
| 121 |
+
- Train: `bccf62673fb3160ac957650d400edcd6efb28e56b5e212940aba3281fb4bb82d`.
|
| 122 |
+
- Validation: `e6e87cad6aea503ef41894d7aa53cd80d635d540c7d2f2942f79a033692cce76`.
|
| 123 |
+
|
| 124 |
+
These artifacts used the original pilot source recorded in their manifest. A later
|
| 125 |
+
change allowed the explicit `+working-tree` commit suffix; that change does not
|
| 126 |
+
retroactively change which code produced this pilot. A final committed regeneration
|
| 127 |
+
will have different provenance hashes even when game examples remain the same.
|
| 128 |
+
|
| 129 |
+
For an additional label check, an independently written nested loop recomputed
|
| 130 |
+
every action value on 16 positions: first and highest-stack examples from each of
|
| 131 |
+
the six split/behavior groups, then further highest-stack positions to reach 16.
|
| 132 |
+
The sample included height-20 recovery boards. All values and chosen labels
|
| 133 |
+
matched. Evidence is `runs/data-pilot-independent-audit.json`. This checks the
|
| 134 |
+
implementation against the declared search formula, not optimality of that formula.
|
| 135 |
+
|
| 136 |
+
The five-seed development tournament produced mean lines 37.8 for the heuristic
|
| 137 |
+
and 38.6 for search; both reached the 100-piece cap in all five games with no
|
| 138 |
+
errors. Mean search decision time was 8.33 ms versus 0.183 ms for the heuristic.
|
| 139 |
+
The collection and comparison jobs ran concurrently on CPU, so these times are
|
| 140 |
+
illustrative, not isolated performance benchmarks. Five capped games are insufficient
|
| 141 |
+
to establish broad superiority. No neural model was evaluated.
|
| 142 |
+
|
| 143 |
+
## Reproduce from the public source bundle without private Git access
|
| 144 |
+
|
| 145 |
+
The model release includes `code/` with source, a lockfile and a source manifest.
|
| 146 |
+
From that directory, `uv sync --locked` installs the game/data tools. The regular
|
| 147 |
+
`stackcraft generate-data` command records the bundle's source identity and flags
|
| 148 |
+
modified source files. Its provenance will therefore differ from the original
|
| 149 |
+
M3 commit even when generator logic is unchanged.
|
| 150 |
+
|
| 151 |
+
The probe and full-training commands in later tutorials use `data/study-v1`.
|
| 152 |
+
For a fresh reproduction, the command below creates that directory with the exact
|
| 153 |
+
original study bytes. Alternatively, place the released dataset's `train.jsonl`,
|
| 154 |
+
`validation.jsonl` and `manifest.json` in that directory and verify them using the
|
| 155 |
+
release audit. Do not substitute the newly generated pilot above for the study.
|
| 156 |
+
|
| 157 |
+
To reproduce the original study bytes exactly, first verify that every generator
|
| 158 |
+
source file matches the original dataset manifest, then use its recorded commit
|
| 159 |
+
in the generator API. This avoids falsely stamping changed code with an old commit:
|
| 160 |
+
|
| 161 |
+
```bash
|
| 162 |
+
uv run --locked python - <<'PY'
|
| 163 |
+
import hashlib
|
| 164 |
+
import json
|
| 165 |
+
from pathlib import Path
|
| 166 |
+
from stackcraft.data import DatasetConfig, generate_dataset, write_dataset
|
| 167 |
+
|
| 168 |
+
manifest = json.loads(Path('reports/dataset-manifest.json').read_text())
|
| 169 |
+
for name, expected in manifest['source_hashes'].items():
|
| 170 |
+
path = Path('src/stackcraft') / name
|
| 171 |
+
assert hashlib.sha256(path.read_bytes()).hexdigest() == expected, name
|
| 172 |
+
output = Path('data/study-v1')
|
| 173 |
+
assert not output.exists(), 'Choose a new output directory'
|
| 174 |
+
bundle = generate_dataset(DatasetConfig(), source_commit=manifest['source_commit'])
|
| 175 |
+
assert bundle.manifest == manifest, 'Generated manifest differs from the frozen study'
|
| 176 |
+
write_dataset(bundle, output)
|
| 177 |
+
for split in ('train', 'validation'):
|
| 178 |
+
actual = hashlib.sha256((output / f'{split}.jsonl').read_bytes()).hexdigest()
|
| 179 |
+
assert actual == manifest['splits'][split]['sha256'], split
|
| 180 |
+
print('Both original split hashes match')
|
| 181 |
+
PY
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
If `data/study-v1` already exists, retain and audit it rather than overwrite it.
|
| 185 |
+
For a separate regeneration, choose a new output directory and supply that path
|
| 186 |
+
with `--dataset` in subsequent probe, training and evaluation commands. Use only
|
| 187 |
+
the audited downloaded dataset or the exact hash-matching reconstruction to
|
| 188 |
+
reproduce study training. If any source hash fails, investigate or generate a
|
| 189 |
+
clearly new dataset; do not suppress the check or reuse the original study's identity.
|
code/docs/tutorials/04-gpu-feasibility.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tutorial 04 — Prove training and reload before running an experiment
|
| 2 |
+
|
| 3 |
+
This milestone asks a narrow engineering question: can the real pinned Clef-flash
|
| 4 |
+
model take gradient steps on this GPU, save all learned parameters, and reproduce
|
| 5 |
+
its probabilities in a fresh process? It does not ask whether the model plays
|
| 6 |
+
better. The latter requires complete games on the held-out sequences in Milestone 5.
|
| 7 |
+
|
| 8 |
+
**The bounded real-weight training and fresh-process reload gate passed.** Both
|
| 9 |
+
head-only and LoRA-plus-head artifacts reproduced their reference probabilities
|
| 10 |
+
exactly. The first LoRA reload exposed a saved-configuration compatibility issue;
|
| 11 |
+
the corrected loader and successful retry are documented below. This establishes
|
| 12 |
+
local training feasibility, not improved game performance.
|
| 13 |
+
|
| 14 |
+
## Start from the native decision model
|
| 15 |
+
|
| 16 |
+
The base is `Cloudflare/clef-flash`, revision
|
| 17 |
+
`17f0b0ad64efb65d273590632833508766b2aae6`. The local loader verifies the pinned
|
| 18 |
+
`joint_schema_model.py` source hash and loads an explicitly resolved snapshot.
|
| 19 |
+
It requires `trust_pinned_code=True`: pinning and inspecting Python source controls
|
| 20 |
+
which code executes; it does not turn that code into a sandbox.
|
| 21 |
+
|
| 22 |
+
An observation becomes one native choice question containing every legal
|
| 23 |
+
placement. The answer is a distribution over action IDs, not a generated command
|
| 24 |
+
or natural-language explanation. `encode_observation` checks the complete token
|
| 25 |
+
budget before native encoding, because silently truncating a board would change
|
| 26 |
+
the task. Training uses only the visible board, current piece, one preview and
|
| 27 |
+
legal placements. Dataset seed and episode metadata remain outside the input.
|
| 28 |
+
|
| 29 |
+
The native encoder sorts option IDs lexicographically. `decision_loss` locates the
|
| 30 |
+
teacher's action in `encoded.questions[0].option_ids`; it must not reuse the
|
| 31 |
+
engine's rotation/column index as a target index. Both orders contain the same
|
| 32 |
+
legal moves, but their order can differ.
|
| 33 |
+
|
| 34 |
+
Do not train through `systemone()` or `ClefPlayer.choose()`: those are inference
|
| 35 |
+
paths. The probe calls `native.collate_records` and then `model(batch)` with
|
| 36 |
+
normal autograd enabled. `choose()` is appropriate for the before/after reference
|
| 37 |
+
probabilities, where gradients are unnecessary.
|
| 38 |
+
|
| 39 |
+
## Keep the backbone small in memory and the head stable
|
| 40 |
+
|
| 41 |
+
`src/stackcraft/training.py` implements two bounded configurations:
|
| 42 |
+
|
| 43 |
+
| Configuration | Updated parameters | Purpose |
|
| 44 |
+
| --- | --- | --- |
|
| 45 |
+
| Head only | Native joint decision head | Isolate head training and checkpoint handling before a backbone backward pass. |
|
| 46 |
+
| Rank-4 LoRA plus head | Small added text-layer matrices and the native head | Check whether the task can adapt both text representations and decision scoring. |
|
| 47 |
+
|
| 48 |
+
The backbone remains BF16. The native decision head is converted to FP32, and
|
| 49 |
+
`FP32DecisionHead` disables autocast inside the head. Its floating inputs are
|
| 50 |
+
converted to FP32 while token IDs and masks retain their original types. This
|
| 51 |
+
keeps the trainable scoring operations in full precision without converting the
|
| 52 |
+
whole multi-billion-parameter backbone.
|
| 53 |
+
|
| 54 |
+
The head reads selected rows of the output vocabulary embedding. Converting the
|
| 55 |
+
entire vocabulary matrix to FP32 first creates a large temporary allocation
|
| 56 |
+
(roughly 3.79 GiB for this release). `GatheredFloat32Embedding` instead implements
|
| 57 |
+
`weight[indices].float()`: select the needed rows first, then cast. This preserves
|
| 58 |
+
the actual native head computation and autograd connections. A tiny actual-native-
|
| 59 |
+
head test compares outputs and gradients with the full-cast reference exactly.
|
| 60 |
+
That test checks the optimization, not full-model training feasibility.
|
| 61 |
+
|
| 62 |
+
LoRA targets are full module paths under the text transformer layers. They include
|
| 63 |
+
both ordinary attention projections and Qwen3.5's linear-attention projections,
|
| 64 |
+
as well as the MLP projections. Matching only suffixes such as `q_proj` could
|
| 65 |
+
accidentally select the vision encoder; matching only ordinary attention would
|
| 66 |
+
miss the hybrid backbone. The vision tower, vocabulary output layer and unrelated
|
| 67 |
+
modules are excluded. Original backbone parameters are frozen. The probe uses
|
| 68 |
+
rank 4, alpha 8, no LoRA dropout and no trained backbone biases.
|
| 69 |
+
|
| 70 |
+
Non-reentrant gradient checkpointing recomputes intermediate activations during
|
| 71 |
+
backward instead of retaining all of them. This trades time for memory while
|
| 72 |
+
preserving gradients through frozen parts of the backbone into LoRA matrices.
|
| 73 |
+
A tiny actual Qwen3.5 hybrid-backbone test checks nonzero LoRA gradients in ordinary
|
| 74 |
+
attention, linear attention and MLP layers. These are stronger plumbing checks
|
| 75 |
+
than testing a stand-in linear model alone, but still do not establish the real
|
| 76 |
+
9B GPU memory requirement.
|
| 77 |
+
|
| 78 |
+
We start with BF16 rather than immediately adding 4-bit quantized training. The
|
| 79 |
+
native model has a custom head and loader; another quantization layer would add
|
| 80 |
+
an unverified compatibility dependency. If BF16 fails, record the failure and
|
| 81 |
+
make a bounded next decision rather than silently changing the experiment.
|
| 82 |
+
|
| 83 |
+
## Prepare before borrowing the GPU
|
| 84 |
+
|
| 85 |
+
Run these commands from the repository root or the public bundle's `code/`
|
| 86 |
+
directory. Install the ML tools and cache the pinned release before testing:
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
uv sync --locked --extra ml --group dev
|
| 90 |
+
uv run --locked --extra ml hf download Cloudflare/clef-flash \
|
| 91 |
+
--revision 17f0b0ad64efb65d273590632833508766b2aae6
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
Downloading only populates the cache; it does not load GPU weights. It can run
|
| 95 |
+
while other GPU services remain available. The actual native-head tests need
|
| 96 |
+
this cached source. Explicitly enable the native tokenizer/encoder check too:
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
STACKCRAFT_TEST_NATIVE_ENCODING=1 uv run --locked --extra ml \
|
| 100 |
+
pytest tests/test_training.py tests/test_clef.py
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
These checks use CPU fixtures and the real tokenizer/source; they do not load
|
| 104 |
+
the full backbone or establish GPU feasibility. Inspect the test summary: a
|
| 105 |
+
skipped test is not a passed native-model test.
|
| 106 |
+
|
| 107 |
+
The training probe expects the audited `data/study-v1` bundle established by
|
| 108 |
+
Tutorial03's exact-study reconstruction or a verified dataset download. If you
|
| 109 |
+
used a different directory, pass that same path with `--dataset` below.
|
| 110 |
+
It validates the train/validation bundle, then uses only the first four training
|
| 111 |
+
positions. It does not sample held-out test seeds. Record the current source
|
| 112 |
+
commit and any uncommitted probe changes in `record.md` before running.
|
| 113 |
+
|
| 114 |
+
This workstation normally runs a local GLM Docker service. The user explicitly
|
| 115 |
+
authorized temporarily stopping that service and restoring it after these
|
| 116 |
+
Stackcraft experiments. `scripts/gpu_session.py` is specific to that approved
|
| 117 |
+
service; it is not a generic instruction to stop another machine's workloads.
|
| 118 |
+
It first requires the configured container to be running, then stops it, runs a
|
| 119 |
+
bounded child job, and attempts restoration during cleanup. The training script
|
| 120 |
+
itself never stops services. It refuses to load unless CUDA is available and at
|
| 121 |
+
least **25 GiB is free**. Free-memory admission is a precondition, not a guarantee
|
| 122 |
+
that backward will fit.
|
| 123 |
+
|
| 124 |
+
## Run the bounded real-weight probe
|
| 125 |
+
|
| 126 |
+
The initial budget is two configurations and at most 30 minutes of child-job
|
| 127 |
+
time. The commands below allocate 20 minutes to the training probe and 10 minutes
|
| 128 |
+
to the separate reload check. Choose fresh output and session-record paths on a
|
| 129 |
+
retry; neither existing evidence nor checkpoints should be overwritten.
|
| 130 |
+
|
| 131 |
+
```bash
|
| 132 |
+
uv run --locked --extra ml python scripts/gpu_session.py \
|
| 133 |
+
--timeout 1200 --record runs/gpu-training-probe-v1.json -- \
|
| 134 |
+
uv run --locked --extra ml python scripts/probe_clef_training.py \
|
| 135 |
+
--dataset data/study-v1 --output runs/clef-training-probe-v1
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
The probe first records unchanged native probabilities. It measures the initial
|
| 139 |
+
probability drift introduced by moving the head to FP32, before training; a
|
| 140 |
+
precision change must not be mistaken for a learned improvement. It then runs
|
| 141 |
+
three head-only steps. After saving that checkpoint, it releases the model and
|
| 142 |
+
loads a fresh unchanged base for five LoRA-plus-head steps. The LoRA experiment
|
| 143 |
+
therefore does not inherit the head-only warm-up.
|
| 144 |
+
|
| 145 |
+
Both configurations use batch size one, AdamW with learning rate `1e-5`, norm
|
| 146 |
+
clipping at 1.0, and this loss:
|
| 147 |
+
|
| 148 |
+
smoothed_cross_entropy(label_smoothing=0.05)
|
| 149 |
+
+ 0.1 * sum_over_options((probability - one_hot_teacher_label)**2)
|
| 150 |
+
|
| 151 |
+
The cross-entropy term learns the expert action. Smoothing avoids assigning the
|
| 152 |
+
entire training target to one action. The Brier term also penalizes the predicted
|
| 153 |
+
probability distribution's distance from the teacher target. These fixed choices
|
| 154 |
+
are a probe configuration, not evidence of calibrated confidence or optimal
|
| 155 |
+
hyperparameters.
|
| 156 |
+
|
| 157 |
+
For every step, the probe checks finite loss and gradients and records nonzero
|
| 158 |
+
head/LoRA gradient sums, tokens, elapsed time, and CUDA peak allocated/reserved
|
| 159 |
+
bytes. Before/after parameter hashes check that intended parameters changed and
|
| 160 |
+
frozen parameters did not. Hashing the entire frozen backbone costs time but
|
| 161 |
+
checks something that `requires_grad=False` alone cannot prove: what bytes actually
|
| 162 |
+
changed during this run.
|
| 163 |
+
|
| 164 |
+
Inspect `runs/clef-training-probe-v1/report.json` and the session record. A timeout,
|
| 165 |
+
CUDA error, absent gradient or failed hash check is a failed gate. A report left at
|
| 166 |
+
`running` after forced termination is incomplete evidence, even if some checkpoint
|
| 167 |
+
files exist. Do not start long training merely because loss fell on four examples.
|
| 168 |
+
|
| 169 |
+
## Save the head as well as the adapter
|
| 170 |
+
|
| 171 |
+
A LoRA checkpoint has this structure:
|
| 172 |
+
|
| 173 |
+
```text
|
| 174 |
+
checkpoint/
|
| 175 |
+
joint_head.safetensors
|
| 176 |
+
adapter/
|
| 177 |
+
adapter_config.json
|
| 178 |
+
adapter_model.safetensors
|
| 179 |
+
training_config.json
|
| 180 |
+
reference.json
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
The head is an independently trained part of Clef; saving only the PEFT adapter
|
| 184 |
+
would omit learned parameters. `joint_head.safetensors` stores FP32 native head
|
| 185 |
+
keys without wrapper-specific prefixes. The adapter stores only the added LoRA
|
| 186 |
+
weights, not another copy of the frozen base. Metadata identifies the base
|
| 187 |
+
revision, native source hash, input encoding, head format, LoRA settings and
|
| 188 |
+
teacher rows. The loader checks this contract, head shapes and dtypes, and actual
|
| 189 |
+
saved adapter configuration before restoring weights.
|
| 190 |
+
|
| 191 |
+
The head-only probe writes `head-checkpoint/` without an adapter. It is diagnostic
|
| 192 |
+
output, not a selected release model.
|
| 193 |
+
|
| 194 |
+
## Require a separate fresh-process reload
|
| 195 |
+
|
| 196 |
+
Only after the first probe succeeds, run:
|
| 197 |
+
|
| 198 |
+
```bash
|
| 199 |
+
uv run --locked --extra ml python scripts/gpu_session.py \
|
| 200 |
+
--timeout 600 --record runs/gpu-training-reload-v1.json -- \
|
| 201 |
+
uv run --locked --extra ml python scripts/probe_clef_training.py \
|
| 202 |
+
--dataset data/study-v1 --output runs/clef-training-reload-v1 \
|
| 203 |
+
--reload runs/clef-training-probe-v1/checkpoint
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
This starts another Python process, loads the unchanged pinned base, restores both
|
| 207 |
+
adapter and head, and evaluates the same four training observations. Require the
|
| 208 |
+
same option IDs and a **maximum absolute probability difference of at most
|
| 209 |
+
`1e-4`** across all recorded options. The tolerance was declared before the run.
|
| 210 |
+
Compare unrounded probabilities; rounded display values can conceal a mismatch.
|
| 211 |
+
|
| 212 |
+
A same-process test could accidentally reuse trained parameters that were never
|
| 213 |
+
saved. The fresh process tests the actual inference artifact. A passing reload
|
| 214 |
+
establishes serialization fidelity on these references, not general game skill
|
| 215 |
+
or correctness on every possible input.
|
| 216 |
+
|
| 217 |
+
## Restore service and record the limits
|
| 218 |
+
|
| 219 |
+
Inspect both `restored_running` and `restored_healthy` after success and failure.
|
| 220 |
+
The wrapper checks `/health` inside the restored container and allows up to 120
|
| 221 |
+
seconds for readiness, retrying transient timeouts and malformed startup responses.
|
| 222 |
+
It does not query the unrelated service on host port 8080. A running process alone
|
| 223 |
+
would not establish readiness. Nested cleanup ensures a child-termination error
|
| 224 |
+
still reaches restoration; repeated ordinary interrupts are ignored during that
|
| 225 |
+
cleanup. Preserve the wrapper record, probe reports and console logs, including
|
| 226 |
+
errors. Mocked tests exercise these recovery paths without touching Docker.
|
| 227 |
+
|
| 228 |
+
Process cleanup cannot guarantee recovery after `SIGKILL`, host power loss or a
|
| 229 |
+
Docker daemon failure. If the wrapper was forcibly terminated, inspect the exact
|
| 230 |
+
approved container and any remaining job before acting. Restore the existing
|
| 231 |
+
service only after the experiment has released GPU memory; do not start another
|
| 232 |
+
probe simply because the last terminal stopped printing output.
|
| 233 |
+
|
| 234 |
+
The earlier M2 real-weight development pilot is useful context: on this RTX 5090,
|
| 235 |
+
it recorded 55 unchanged-Clef decisions at a mean of about **0.144 seconds per
|
| 236 |
+
decision**, with **19,686,539,776 bytes** peak CUDA allocation (about 19.7 GB,
|
| 237 |
+
18.3 GiB). The benchmark script took 12.60 seconds; the stop/job/restore wrapper
|
| 238 |
+
interval was about 14.20 seconds and recorded successful restoration. These are
|
| 239 |
+
inference measurements on two short development games, not training memory or
|
| 240 |
+
held-out quality results. Sources are `runs/clef-development-v1.json` and
|
| 241 |
+
`runs/gpu-baseline-session.json`.
|
| 242 |
+
|
| 243 |
+
## Measured training probe and the first reload failure
|
| 244 |
+
|
| 245 |
+
The real-weight probe passed on 2026-10-05 using the RTX 5090 and Torch
|
| 246 |
+
`2.14.1+cu130`. Evidence is `runs/probe-v1/report.json`; the actual run directory
|
| 247 |
+
differs from the fresh reproduction paths shown above. It used training positions
|
| 248 |
+
`seed-10000-turn-0` through `seed-10000-turn-3`, with 1,302–2,252 encoded tokens.
|
| 249 |
+
|
| 250 |
+
| Measurement | Head-only probe | LoRA-plus-head probe |
|
| 251 |
+
| --- | ---: | ---: |
|
| 252 |
+
| Optimization steps | 3 | 5 |
|
| 253 |
+
| Observed step times | 0.154–0.328 s | 0.670–1.322 s |
|
| 254 |
+
| Peak allocated CUDA bytes | 21,323,760,640 | 23,180,389,888 |
|
| 255 |
+
| Peak reserved CUDA bytes | 21,541,945,344 | 23,595,057,152 |
|
| 256 |
+
| Intended parameter changes | Verified | Verified |
|
| 257 |
+
| Frozen parameter hashes | Unchanged | Unchanged |
|
| 258 |
+
|
| 259 |
+
All reported losses and gradients were finite, with nonzero gradients in the
|
| 260 |
+
intended groups. Total probe time was **51.81 seconds**, including model reload,
|
| 261 |
+
parameter hashing and checkpoint work. This is not an estimate of a full epoch:
|
| 262 |
+
only four short training positions were exercised. Peak reserved memory is the
|
| 263 |
+
PyTorch allocator's reservation, not the same quantity as all memory reported by
|
| 264 |
+
`nvidia-smi`.
|
| 265 |
+
|
| 266 |
+
The FP32 head changed initial probabilities by at most **0.00074412** before any
|
| 267 |
+
training. That is a precision effect relative to the original BF16 head, not a
|
| 268 |
+
failed serialization comparison. The `1e-4` reload threshold compares the saved
|
| 269 |
+
trained FP32-head model with its own fresh reload.
|
| 270 |
+
|
| 271 |
+
The first fresh reload, `runs/probe-reload-v1/report.json`, failed after 4.15
|
| 272 |
+
seconds because the saved adapter configuration differed from our metadata. PEFT
|
| 273 |
+
automatically shortened **248 full target paths to 12 module suffixes**. The strict
|
| 274 |
+
loader correctly refused a mismatch; the failure does not by itself mean the
|
| 275 |
+
weight tensors are corrupt. Accepting suffixes without checking what they match
|
| 276 |
+
could attach an adapter to unintended modules. The correction must expand saved
|
| 277 |
+
target selectors against the actual pinned backbone and require exactly the
|
| 278 |
+
intended full target set, rejecting any extra destinations. This is why fresh
|
| 279 |
+
reload is a milestone gate rather than a final packaging chore.
|
| 280 |
+
|
| 281 |
+
The corrected loader resolves saved selectors against the pinned backbone and
|
| 282 |
+
requires the exact intended destinations. The retry preserved the original
|
| 283 |
+
checkpoint and failed report; it changed validation of equivalent target selectors,
|
| 284 |
+
not the trained weights. A regression test covers target shortening and rejects
|
| 285 |
+
selectors that also match unintended modules.
|
| 286 |
+
|
| 287 |
+
Both independent fresh-process checks then passed:
|
| 288 |
+
|
| 289 |
+
| Artifact | Report | Maximum probability difference | Process time |
|
| 290 |
+
| --- | --- | ---: | ---: |
|
| 291 |
+
| Head only | `runs/probe-head-reload-v1/report.json` | 0.0 | 5.44 s |
|
| 292 |
+
| LoRA plus head | `runs/probe-reload-v2/report.json` | 0.0 | 5.65 s |
|
| 293 |
+
|
| 294 |
+
The LoRA retry used source commit
|
| 295 |
+
`f7be7ceee9c38566dfebad0ebf5f1c04958faee3`. The dataset manifest SHA-256 was
|
| 296 |
+
`aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94` throughout.
|
| 297 |
+
The approved GLM service was restored and passed its health check after these
|
| 298 |
+
probes. **M4 is complete.** Milestone 5 can now train a selected checkpoint and
|
| 299 |
+
measure complete games; the four-position probe does not demonstrate that the
|
| 300 |
+
trained policy wins or generalizes.
|
code/docs/tutorials/05-evaluation.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tutorial 05 — Select on validation, then measure complete games
|
| 2 |
+
|
| 3 |
+
Training, validation selection, selected-checkpoint reload and the held-out game
|
| 4 |
+
evaluation are complete. Epoch 02 cleared **16.81 lines per game**, versus **0.07**
|
| 5 |
+
for native Clef and **76.53** for the cheap heuristic on all 200 reserved seeds.
|
| 6 |
+
This tutorial gives the frozen procedure and explains the measured outcome. See
|
| 7 |
+
[the final study](../../reports/final-study.md) for full results and limitations,
|
| 8 |
+
and [the compact JSON](../../reports/final-summary.json) for exact metrics and hashes.
|
| 9 |
+
The experiment results are complete; release verification is tracked separately.
|
| 10 |
+
|
| 11 |
+
## Separate learning, selection and final testing
|
| 12 |
+
|
| 13 |
+
The two candidate checkpoints are epochs 01 and 02 of the same seed-42 LoRA run.
|
| 14 |
+
Their training configuration and dataset are fixed before comparison. Evaluate
|
| 15 |
+
both on **all 215 validation positions** and select the lowest finite mean target
|
| 16 |
+
negative log likelihood (NLL). An exact tie chooses the earlier epoch. A candidate
|
| 17 |
+
with inference errors, missing probability distributions, or nonfinite NLL is
|
| 18 |
+
ineligible. If both are ineligible, stop before opening the test set.
|
| 19 |
+
|
| 20 |
+
NLL measures how much probability the model assigns to the teacher's recommended
|
| 21 |
+
placement. Selecting on a smooth probability score can distinguish checkpoints
|
| 22 |
+
even when their top choices tie. Teacher agreement and Brier score remain useful
|
| 23 |
+
diagnostics, but cannot override the preregistered rule after results are visible.
|
| 24 |
+
|
| 25 |
+
Copying the teacher is not the same as playing well. Each small mistake changes
|
| 26 |
+
the next board; the model eventually encounters states different from its training
|
| 27 |
+
examples. The teacher itself uses a limited search horizon and a heuristic value
|
| 28 |
+
function. Final evaluation must therefore play complete games on unseen sequences.
|
| 29 |
+
|
| 30 |
+
## Train exactly the preregistered candidates
|
| 31 |
+
|
| 32 |
+
Pass the real GPU feasibility and fresh-process reload gates in Tutorial 04 first.
|
| 33 |
+
Use the exact 827/215-row dataset from Tutorial 03. The training runner rejects a
|
| 34 |
+
substituted dataset even when its labels look similar: hashes bind the experiment
|
| 35 |
+
to specific bytes and provenance. Use a fresh run directory when reproducing.
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
uv sync --locked --extra ml --group dev
|
| 39 |
+
uv run --locked --extra ml python scripts/train_clef.py \
|
| 40 |
+
--dataset data/study-v1 --output runs/study-v1 \
|
| 41 |
+
--mode lora --epochs 2 --learning-rate 1e-5 \
|
| 42 |
+
--accumulation 8 --max-length 4096
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
This command needs a free GPU. On the original workstation only, run it through
|
| 46 |
+
the authorized stop/restore wrapper from Tutorial 04 with a 3,600-second timeout.
|
| 47 |
+
The script uses seed 42, batch size one, rank-4/alpha-8 LoRA with zero dropout,
|
| 48 |
+
AdamW weight decay 0.01, gradient norm clipping at 1.0, and the fixed smoothed
|
| 49 |
+
cross-entropy plus Brier loss from Tutorial 04. The backbone stays BF16 and frozen;
|
| 50 |
+
LoRA matrices and the native FP32 head learn. Each epoch visits all training rows
|
| 51 |
+
in a recorded deterministic shuffled order. Validation rows never train the model.
|
| 52 |
+
|
| 53 |
+
Accumulating eight single-example gradients fits the memory budget while averaging
|
| 54 |
+
over several boards per optimizer step. The final group has only three rows and
|
| 55 |
+
is divided by three, not eight. Two complete epochs give two declared candidates
|
| 56 |
+
without an open-ended search. Neither falling loss nor a promising demo authorizes
|
| 57 |
+
extra epochs after the test set has been opened. The script defaults to one epoch,
|
| 58 |
+
so the explicit `--epochs 2` above is essential for reproducing this study.
|
| 59 |
+
|
| 60 |
+
The completed run processed 827 examples and 104 optimizer steps in each epoch:
|
| 61 |
+
|
| 62 |
+
| Epoch | Mean training loss | Time |
|
| 63 |
+
| --- | ---: | ---: |
|
| 64 |
+
| 01 | 2.577967 | 742.48 s |
|
| 65 |
+
| 02 | 1.966718 | 744.66 s |
|
| 66 |
+
|
| 67 |
+
Total training time including hashes and checkpoint work was 1,511.25 seconds
|
| 68 |
+
(about 25.2 minutes). Peak CUDA allocation was 23.23 GB, or about 21.64 GiB.
|
| 69 |
+
The head and LoRA parameters changed, while all frozen parameter hashes remained
|
| 70 |
+
unchanged. These are training measurements, not held-out performance. See
|
| 71 |
+
[the training report](../../reports/study-training.json),
|
| 72 |
+
[the original launch source](../../reports/training-launch-source.json), and
|
| 73 |
+
[the archived preregistration](../../reports/study-preregistration.md).
|
| 74 |
+
|
| 75 |
+
## Prepare checkpoint metadata before freezing it
|
| 76 |
+
|
| 77 |
+
PEFT can write a generic adapter README with placeholders and a local cache path.
|
| 78 |
+
Keep raw training outputs immutable. Prepare candidate copies before validation:
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
uv run --locked --extra ml python scripts/prepare_checkpoint.py \
|
| 82 |
+
--source runs/study-v1/epoch-01 --output checkpoints/candidates/epoch-01
|
| 83 |
+
uv run --locked --extra ml python scripts/prepare_checkpoint.py \
|
| 84 |
+
--source runs/study-v1/epoch-02 --output checkpoints/candidates/epoch-02
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
The script replaces only `adapter/README.md`, compares every other file byte for
|
| 88 |
+
byte, and saves a sibling preparation record. It does not alter learned weights
|
| 89 |
+
or training settings. Validate and select these exact copies. Once selected,
|
| 90 |
+
even a README edit would break the frozen checkpoint file mapping. See Tutorial 06
|
| 91 |
+
for how release documentation stays outside the checkpoint directory.
|
| 92 |
+
|
| 93 |
+
## Generate validation evidence
|
| 94 |
+
|
| 95 |
+
These commands load real GPU models. Use the memory admission and service-restoration
|
| 96 |
+
procedure from Tutorial 04. The evaluator never downloads weights implicitly or
|
| 97 |
+
stops another workload. Replace paths if your run directory differs.
|
| 98 |
+
|
| 99 |
+
```sh
|
| 100 |
+
uv run --locked --extra ml python scripts/evaluate_clef.py positions \
|
| 101 |
+
--dataset data/study-v1 --checkpoint checkpoints/candidates/epoch-01 \
|
| 102 |
+
--players base base-fp32 trained --output runs/validation-epoch-01
|
| 103 |
+
|
| 104 |
+
uv run --locked --extra ml python scripts/evaluate_clef.py positions \
|
| 105 |
+
--dataset data/study-v1 --checkpoint checkpoints/candidates/epoch-02 \
|
| 106 |
+
--players trained --output runs/validation-epoch-02
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
Every validation row gets an unrounded probability record or an explicit error.
|
| 110 |
+
The position report binds those records to checkpoint file hashes and the dataset
|
| 111 |
+
manifest. Aggregate NLL and Brier are unavailable if any probability distribution
|
| 112 |
+
is missing; a successful subset cannot masquerade as a complete evaluation.
|
| 113 |
+
|
| 114 |
+
The first command also measures two unchanged-model references. `base` retains
|
| 115 |
+
the original native BF16 head. `base-fp32` keeps those same learned weights but
|
| 116 |
+
uses our FP32 head wrapper. Training uses that wrapper, so the extra comparison
|
| 117 |
+
shows whether a change comes from numerical precision rather than learning.
|
| 118 |
+
The primary comparison remains trained versus native base. All three share the
|
| 119 |
+
same complete observation encoding and BF16 backbone.
|
| 120 |
+
|
| 121 |
+
## Freeze the choice before running test episodes
|
| 122 |
+
|
| 123 |
+
```sh
|
| 124 |
+
uv run --locked --extra ml python scripts/select_checkpoint.py \
|
| 125 |
+
--dataset data/study-v1 \
|
| 126 |
+
--candidate01 checkpoints/candidates/epoch-01 runs/validation-epoch-01/report.json \
|
| 127 |
+
--candidate02 checkpoints/candidates/epoch-02 runs/validation-epoch-02/report.json \
|
| 128 |
+
--output runs/selection-v1
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
The selector independently recomputes metrics from every raw prediction against
|
| 132 |
+
the audited validation rows. It verifies actual checkpoint bytes, training seed
|
| 133 |
+
and configuration, epoch identity, common dataset and evaluation provenance. It
|
| 134 |
+
accepts exactly the two preregistered candidates. It then copies both validation
|
| 135 |
+
reports, prediction files and checkpoint metadata into a self-contained evidence
|
| 136 |
+
directory. The large model weights remain in their checkpoint directories and
|
| 137 |
+
are referenced by hashes.
|
| 138 |
+
|
| 139 |
+
`selection.json` records the selected epoch, decision rule, both candidates,
|
| 140 |
+
their eligibility, validation metrics, and the frozen test protocol. The runner
|
| 141 |
+
rechecks that the selected epoch actually wins the declared rule. Manual editing
|
| 142 |
+
of a label such as "best model" is not sufficient to unlock final evaluation.
|
| 143 |
+
Use a new output directory for a new selection attempt; existing evidence is not
|
| 144 |
+
silently overwritten.
|
| 145 |
+
|
| 146 |
+
## Inspect selection and reload the selected artifact
|
| 147 |
+
|
| 148 |
+
The measured validation results used all 215 positions for every policy, with
|
| 149 |
+
zero errors and complete probability distributions:
|
| 150 |
+
|
| 151 |
+
| Policy | Teacher agreement | Mean target NLL | Mean Brier score |
|
| 152 |
+
| --- | ---: | ---: | ---: |
|
| 153 |
+
| Native base | 21/215 (9.77%) | 2.952702 | 0.923173 |
|
| 154 |
+
| Unchanged FP32 head | 22/215 (10.23%) | 2.952393 | 0.923102 |
|
| 155 |
+
| Epoch 01 | 89/215 (41.40%) | 1.860870 | 0.722190 |
|
| 156 |
+
| Epoch 02 | 92/215 (42.79%) | 1.775923 | 0.708980 |
|
| 157 |
+
|
| 158 |
+
The declared NLL rule selected epoch 02. The saved
|
| 159 |
+
[validation summary](../../reports/validation-summary.json) identifies its complete
|
| 160 |
+
file hashes and the selection hash. Teacher agreement improved on these positions;
|
| 161 |
+
that still does not establish better complete-game play.
|
| 162 |
+
|
| 163 |
+
Before consuming test seeds, reload the selected artifact in another process:
|
| 164 |
+
|
| 165 |
+
```bash
|
| 166 |
+
uv run --locked --extra ml python scripts/probe_clef_training.py \
|
| 167 |
+
--dataset data/study-v1 --reload checkpoints/candidates/epoch-02 \
|
| 168 |
+
--output runs/selected-reload-v1
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
This command also needs the GPU admission/restoration procedure. In this study it
|
| 172 |
+
reproduced all four training-position reference distributions with **0.0 maximum
|
| 173 |
+
absolute difference**, below the predeclared `1e-4` tolerance. See
|
| 174 |
+
[the reload report](../../reports/selected-checkpoint-reload.json).
|
| 175 |
+
The references use training positions, so this check does not consume test games.
|
| 176 |
+
|
| 177 |
+
## Run the paired test once
|
| 178 |
+
|
| 179 |
+
Use the selected checkpoint path from the evidence, not whichever epoch you hoped
|
| 180 |
+
would win. The reserved pool is all **200 seeds, 30000–30199**, with a **200-piece
|
| 181 |
+
cap**. Each player gets the same piece sequence for a given seed. There are five
|
| 182 |
+
players: native base, FP32-head base ablation, selected trained model, random and
|
| 183 |
+
the fixed cheap heuristic.
|
| 184 |
+
|
| 185 |
+
```sh
|
| 186 |
+
uv run --locked --extra ml python scripts/evaluate_clef.py tournament \
|
| 187 |
+
--checkpoint SELECTED_CHECKPOINT \
|
| 188 |
+
--seeds 30000:30200 --max-pieces 200 --final-test \
|
| 189 |
+
--selection-file runs/selection-v1/selection.json \
|
| 190 |
+
--output runs/final-evaluation
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
`--final-test` explicitly acknowledges that these held-out seeds are being consumed.
|
| 194 |
+
Without it, any overlap with the reserved pool is rejected before model loading.
|
| 195 |
+
The cap and full seed pool cannot silently shrink to finish sooner. The initial
|
| 196 |
+
development timing estimate permits hours in the worst case; short early deaths
|
| 197 |
+
can make actual evaluation much faster.
|
| 198 |
+
|
| 199 |
+
Each completed episode is saved atomically, with actions, probabilities, runtime
|
| 200 |
+
configuration, errors and a replay. To continue an interrupted run, repeat the same
|
| 201 |
+
command with `--resume`. The runner checks source/dependency hashes, checkpoint
|
| 202 |
+
identity, selection, and saved replay outcomes. It skips completed players without
|
| 203 |
+
loading their models again. Recorded errors remain part of the experiment, rather
|
| 204 |
+
than being retried until a nicer result appears.
|
| 205 |
+
|
| 206 |
+
## Read the report without hiding failures
|
| 207 |
+
|
| 208 |
+
The primary outcome is lines cleared. Score and pieces survived are secondary.
|
| 209 |
+
The report includes means, medians, cap-hit rate, failure counts, latency and input
|
| 210 |
+
lengths. The [hardware snapshot](../../reports/evaluation-hardware.json) records
|
| 211 |
+
the RTX 5090, driver, CPU, Python and inference settings observed during this run.
|
| 212 |
+
Compare latency only with that environment in mind; the snapshot does not measure
|
| 213 |
+
energy consumption or continuous GPU clock behavior. A game reaching 200 pieces survived **at least** 200; its eventual death
|
| 214 |
+
was not observed. Always report cap-hit rates with survival statistics.
|
| 215 |
+
|
| 216 |
+
For the primary reliability-adjusted analysis, any errored episode receives zero
|
| 217 |
+
lines, score and pieces. Its measured partial outcome remains visible separately.
|
| 218 |
+
Dropping failures would bias the result toward easier successful games. Both base
|
| 219 |
+
and trained must have zero episode errors for the default positive-signal flag.
|
| 220 |
+
|
| 221 |
+
For each seed, subtract base lines from trained lines, then bootstrap these paired
|
| 222 |
+
differences 10,000 times with RNG seed 2026. Report the mean and percentile 95%
|
| 223 |
+
interval. Resampling complete episode pairs respects the fact that moves within
|
| 224 |
+
one game are correlated. Treating every move as independent would exaggerate the
|
| 225 |
+
amount of evidence.
|
| 226 |
+
|
| 227 |
+
A lower interval bound above zero supports improved mean lines for this checkpoint
|
| 228 |
+
and seed distribution. It does not establish competitive Tetris ability, training
|
| 229 |
+
seed stability, or superiority over the heuristic. Report the trained-versus-
|
| 230 |
+
heuristic comparison even if it is embarrassing. A reproducible negative result
|
| 231 |
+
with clear limitations is a stronger portfolio artifact than a selective win.
|
| 232 |
+
|
| 233 |
+
After test results are opened, do not use them to choose between epochs or change
|
| 234 |
+
the observation format. A subsequent experiment needs a new preregistered study
|
| 235 |
+
and fresh held-out sequences. The current report should preserve failures and
|
| 236 |
+
limitations alongside the playable recorded comparison.
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
## Completed outcome and what it teaches
|
| 240 |
+
|
| 241 |
+
All five players completed the 200 paired seeds at the declared 200-piece cap:
|
| 242 |
+
|
| 243 |
+
| Player | Mean lines | Median lines | Mean pieces | Cap hits |
|
| 244 |
+
| --- | ---: | ---: | ---: | ---: |
|
| 245 |
+
| Native base | 0.070 | 0 | 26.020 | 0/200 |
|
| 246 |
+
| Unchanged FP32 head | 0.105 | 0 | 26.000 | 0/200 |
|
| 247 |
+
| Selected epoch 02 | 16.810 | 16 | 83.565 | 0/200 |
|
| 248 |
+
| Random | 0.140 | 0 | 26.645 | 0/200 |
|
| 249 |
+
| Fixed heuristic | 76.530 | 77 | 200.000 | 200/200 |
|
| 250 |
+
|
| 251 |
+
There were zero inference errors and zero invalid decisions. The paired mean line
|
| 252 |
+
gain over native base was **16.74 [15.74, 17.77]** (95% percentile bootstrap,
|
| 253 |
+
10,000 replicates, seed 2026). Against the unchanged FP32 head, the gain was
|
| 254 |
+
**16.705 [15.705, 17.74]**, so the observed improvement cannot be attributed to head
|
| 255 |
+
precision alone. Against the heuristic the difference was
|
| 256 |
+
**−59.72 [−60.805, −58.619875]**. Both the improvement and this large shortfall belong
|
| 257 |
+
in the result. The heuristic's eventual survival remains unknown because every
|
| 258 |
+
one of its games reached the cap.
|
| 259 |
+
|
| 260 |
+
The selected model's mean decision latency was **206.8 ms**, compared with the
|
| 261 |
+
heuristic's **0.1985 ms**, about 1,042 times slower on these measured workloads.
|
| 262 |
+
Policies visited different states, and neural inference used the GPU while the
|
| 263 |
+
heuristic used the CPU. This is an operational comparison, not an isolated
|
| 264 |
+
same-input speed benchmark. The final wrapper took 85.1 minutes, including model
|
| 265 |
+
loading, report writing and service restoration. Energy and monetary costs were
|
| 266 |
+
not measured. The simple heuristic remains the practical choice for this game.
|
| 267 |
+
|
| 268 |
+
An independent CPU audit replayed all 1,000 saved episodes and independently
|
| 269 |
+
recomputed every paired interval. See [its results](../../reports/final-audit.json).
|
| 270 |
+
The learned model passes the narrow improvement test over its unchanged base;
|
| 271 |
+
that does not turn its 42.79% teacher agreement into a general measure of game
|
| 272 |
+
quality. The final report includes score/survival intervals, all latency summaries,
|
| 273 |
+
hardware, source identities and portable release evidence paths.
|
| 274 |
+
|
| 275 |
+
To replicate the analysis, first verify the report/checkpoint/dataset hashes in
|
| 276 |
+
[final-summary.json](../../reports/final-summary.json), then use the frozen source
|
| 277 |
+
and commands above in fresh output directories. Reproducing inference needs the
|
| 278 |
+
GPU runtime; inspecting metrics and replaying saved actions do not. Reproduction
|
| 279 |
+
uses the already disclosed sequences, so it is not a new blind test. Any follow-up
|
| 280 |
+
training or design change needs a separate declared experiment and fresh test
|
| 281 |
+
seeds. No post-test tuning was used to produce the results reported here.
|
code/docs/tutorials/06-release.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tutorial 06 — Ship an experiment another person can inspect
|
| 2 |
+
|
| 3 |
+
Publication is the last milestone, after training, validation selection and held-out
|
| 4 |
+
evaluation. A model upload alone is insufficient: the artifact must load, its claims
|
| 5 |
+
must match measured games, and the demo must distinguish recorded model decisions
|
| 6 |
+
from live human play. This tutorial describes the release process; publication links
|
| 7 |
+
and verified results are recorded in the final release report when available.
|
| 8 |
+
|
| 9 |
+
## Separate the four deliverables
|
| 10 |
+
|
| 11 |
+
The source repository contains the simulator, native model adapter, training and
|
| 12 |
+
evaluation scripts, tests, locked environment and tutorials. The model repository
|
| 13 |
+
contains the selected checkpoint's LoRA weights and separately trained decision
|
| 14 |
+
head, plus source and losslessly compressed evidence needed to reproduce the study. The dataset repository
|
| 15 |
+
contains exact train/validation JSONL files and the generator manifest. The demo
|
| 16 |
+
repository runs the game and replays cached model decisions without downloading a
|
| 17 |
+
9B model or renting GPU hardware.
|
| 18 |
+
|
| 19 |
+
The source bundle also contains `source-manifest.json` with its source commit and
|
| 20 |
+
file hashes. Scripts use an exact Git root when available, otherwise they verify
|
| 21 |
+
that manifest. Changed, missing or newly added source files mark the run as modified;
|
| 22 |
+
an unrelated parent repository is never mistaken for Stackcraft's source identity.
|
| 23 |
+
See Tutorial03 for hash-checked reproduction of the original dataset bytes without
|
| 24 |
+
access to the original Git repository.
|
| 25 |
+
|
| 26 |
+
These are different visibility decisions. A private GitHub repository does not
|
| 27 |
+
make files inside a public model repository or public Space private. In particular,
|
| 28 |
+
the reproducibility source in the model release and the Space's game source are
|
| 29 |
+
public if those repositories are public. Review the concrete local bundle, proposed
|
| 30 |
+
owners and visibility before creating external repositories.
|
| 31 |
+
|
| 32 |
+
The owner approved all four destinations as public on 2026-10-06: `kkarimi/stackcraft` on GitHub and
|
| 33 |
+
`nima1/stackcraft-clef-flash-lora`, `nima1/stackcraft-data`, and Space
|
| 34 |
+
`nima1/stackcraft` on Hugging Face. This approval explicitly includes the source
|
| 35 |
+
inside the public model and Space repositories. The final release report in GitHub
|
| 36 |
+
records immutable revisions and actual download/hosting verification.
|
| 37 |
+
|
| 38 |
+
## Preserve evidence while preparing checkpoint metadata
|
| 39 |
+
|
| 40 |
+
Raw epoch checkpoints remain under the training run directory. PEFT can generate
|
| 41 |
+
an `adapter/README.md` with placeholders and a local cache path. Before validation,
|
| 42 |
+
copy each epoch checkpoint into a separate candidate directory and replace only
|
| 43 |
+
that documentation with accurate upstream attribution and loading instructions.
|
| 44 |
+
Compare every other file's SHA256 with the raw checkpoint and record the mapping.
|
| 45 |
+
The preparation script performs these checks and writes a sibling provenance file:
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
uv run --locked --extra ml python scripts/prepare_checkpoint.py \
|
| 49 |
+
--source runs/study-v1/epoch-01 --output checkpoints/candidates/epoch-01
|
| 50 |
+
uv run --locked --extra ml python scripts/prepare_checkpoint.py \
|
| 51 |
+
--source runs/study-v1/epoch-02 --output checkpoints/candidates/epoch-02
|
| 52 |
+
```
|
| 53 |
+
This edits no learned weights or training configuration.
|
| 54 |
+
|
| 55 |
+
Validate those exact candidate directories, then freeze the selected directory's
|
| 56 |
+
complete file/hash mapping. Do not revise it after selection. Model cards, reports
|
| 57 |
+
and release links belong outside `model/checkpoint/`, so adding release documentation
|
| 58 |
+
does not silently change the checkpoint used in validation and test evaluation.
|
| 59 |
+
|
| 60 |
+
A final model repository must contain both `joint_head.safetensors` and
|
| 61 |
+
`adapter/adapter_model.safetensors`. Loading only the adapter discards the learned
|
| 62 |
+
joint head. Loading the selected checkpoint requires the pinned upstream
|
| 63 |
+
`Cloudflare/clef-flash` snapshot and Stackcraft's strict loader, not a generic
|
| 64 |
+
text-generation pipeline. See Tutorial04 for the reason and serialization checks.
|
| 65 |
+
|
| 66 |
+
## Assemble and audit a local release
|
| 67 |
+
|
| 68 |
+
Wait for all five players to finish all 200 games, then export the demonstration
|
| 69 |
+
from the complete final report. Seed 30000 was fixed before test results; do not
|
| 70 |
+
choose a more flattering game after seeing outcomes.
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
uv run --locked --extra ml python scripts/export_demo.py \
|
| 74 |
+
--input runs/final-evaluation/report.json \
|
| 75 |
+
--players base trained heuristic --seed 30000 \
|
| 76 |
+
--output runs/demo-final.json
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
The exporter validates the stored replays, observations and decisions without
|
| 80 |
+
loading a model. Inspect the output, then deliberately replace the packaged
|
| 81 |
+
historical development recording and review that Git diff:
|
| 82 |
+
|
| 83 |
+
```bash
|
| 84 |
+
cp runs/demo-final.json src/stackcraft/web/baseline-demo.json
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
Run the local game and check the final three-player comparison plus the optional
|
| 88 |
+
live human board. Then stage a new release directory:
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
uv run --locked --extra ml python scripts/build_release.py \
|
| 92 |
+
--checkpoint checkpoints/candidates/epoch-02 \
|
| 93 |
+
--selection runs/selection-v1/selection.json \
|
| 94 |
+
--evaluation runs/final-evaluation --dataset data/study-v1 \
|
| 95 |
+
--output runs/release-v1
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
Epoch 02 is the measured selected checkpoint for this study; use your frozen
|
| 99 |
+
selection's actual path when reproducing. The builder rejects incomplete final
|
| 100 |
+
evaluation, mismatched hashes or a missing selected-checkpoint reference. It also
|
| 101 |
+
requires the packaged demo to match the exact fixed-seed export from that report.
|
| 102 |
+
|
| 103 |
+
After a public report destination is confirmed, optionally pass the same
|
| 104 |
+
`--report-url https://APPROVED_REPORT_LOCATION` to **both** exporter and builder.
|
| 105 |
+
Use new output directories and deliberately update the packaged demo again.
|
| 106 |
+
Omitting the URL from only one command causes the exact-manifest check to fail.
|
| 107 |
+
These commands prepare local files only; neither creates a remote repository nor
|
| 108 |
+
uploads anything.
|
| 109 |
+
|
| 110 |
+
The builder stages separate model, dataset and demo directories and a release
|
| 111 |
+
manifest. Raw evaluation and selection files are packed in `model/evidence.zip`
|
| 112 |
+
with an `evidence/` path prefix. `evidence-files.json` records every uncompressed
|
| 113 |
+
file's SHA256 and size; the builder rechecks archive entry bytes before dropping
|
| 114 |
+
its temporary uncompressed copy. Compression changes transport size, not any
|
| 115 |
+
record, checkpoint or frozen evidence hash.
|
| 116 |
+
|
| 117 |
+
After verifying the downloaded release manifest, extract from the model repository
|
| 118 |
+
root when you need the raw reports or per-episode files:
|
| 119 |
+
|
| 120 |
+
```bash
|
| 121 |
+
python -m zipfile -e evidence.zip .
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
This restores the `evidence/evaluation/` and `evidence/selection/` paths used by
|
| 125 |
+
the study report. Model inference itself does not require extracting the logs.
|
| 126 |
+
It includes only explicit source/build inputs. Environments, credentials,
|
| 127 |
+
Git history, local service data, raw upstream backbone weights and arbitrary files
|
| 128 |
+
from the working directory are excluded. This is safer and more reproducible than
|
| 129 |
+
uploading the repository directory recursively and hoping ignore patterns cover it.
|
| 130 |
+
|
| 131 |
+
Audit the concrete staged files:
|
| 132 |
+
|
| 133 |
+
- Model and dataset cards describe synthetic data, teacher bias, simplified rules,
|
| 134 |
+
the selected training configuration and actual complete-game outcomes.
|
| 135 |
+
- Negative results, model errors, the FP32-head ablation and the cheap heuristic
|
| 136 |
+
remain visible. There are no unmeasured claims of superiority or calibrated confidence.
|
| 137 |
+
- Every planned test seed is represented for every preregistered player, including
|
| 138 |
+
failures; aggregate uncertainty agrees with the stored episode outcomes.
|
| 139 |
+
- The fixed demo seed is30000, chosen before results, with actual action probabilities.
|
| 140 |
+
- Dataset bytes and checkpoint bytes match their frozen hashes. The trained
|
| 141 |
+
checkpoint's probability references identify the same four training positions.
|
| 142 |
+
- LICENSE and NOTICE retain the Apache-2.0 attribution for Clef-flash/Qwen and
|
| 143 |
+
identify Stackcraft as an independent project.
|
| 144 |
+
- Cards have no placeholder metrics or paths that require the author's machine.
|
| 145 |
+
|
| 146 |
+
A release manifest identifies bytes, not scientific correctness. Independent replay
|
| 147 |
+
checks, selection review and honest documentation are still needed. Keep the
|
| 148 |
+
manifest hash and source commit with the final release report.
|
| 149 |
+
|
| 150 |
+
## Validate the game-only deployment
|
| 151 |
+
|
| 152 |
+
Follow [the deployment guide](../demo-hosting.md) to build the pinned Docker image.
|
| 153 |
+
It installs through uv's lockfile with no ML extra, runs as UID1000 and listens on
|
| 154 |
+
port7860 for a Docker Space. Use the same image configuration locally first.
|
| 155 |
+
|
| 156 |
+
Test a human game, a downloaded/reloaded replay, the recorded comparison, probability
|
| 157 |
+
displays, scrubbing to final outcomes and returning to the preserved human session.
|
| 158 |
+
Check desktop and phone layouts. The recorded race must not imply that changing
|
| 159 |
+
playback speed changes inference speed. It illustrates one sequence; the report
|
| 160 |
+
summarizes all200 paired sequences.
|
| 161 |
+
|
| 162 |
+
The public demonstration needs CPU hosting because neural inference already happened
|
| 163 |
+
during evaluation. This removes ongoing GPU expense and makes the portfolio usable
|
| 164 |
+
when the training machine is offline. An interactive live-model endpoint would be a
|
| 165 |
+
separate deployment with admission, latency and concurrency requirements.
|
| 166 |
+
|
| 167 |
+
## Publish only the approved bundle
|
| 168 |
+
|
| 169 |
+
Confirm concrete destinations and visibility after the local bundle is reviewable.
|
| 170 |
+
Use existing CLI authentication; do not put access tokens in code, Git, model cards,
|
| 171 |
+
commands or chat. Create the approved GitHub repository and push the reviewed local
|
| 172 |
+
commits. Create the approved model, dataset and Docker Space repositories, uploading
|
| 173 |
+
the corresponding staged directories only. The Space uses `cpu-basic`, with no hourly hardware charge. Creating a personal
|
| 174 |
+
Docker Space nevertheless requires a Hugging Face PRO subscription under the
|
| 175 |
+
current policy; see [the deployment guide](../demo-hosting.md). No subscription
|
| 176 |
+
purchase or paid GPU upgrade is authorized by this study.
|
| 177 |
+
|
| 178 |
+
Save immutable Hugging Face commit revisions returned by each upload, together with
|
| 179 |
+
the Git commit and model/dataset/demo URLs. Repository names alone are mutable;
|
| 180 |
+
revisions identify which bytes were checked. A successful upload does not establish
|
| 181 |
+
that the Space builds or the model loads.
|
| 182 |
+
|
| 183 |
+
## Verify the actual release, not the local staging copy
|
| 184 |
+
|
| 185 |
+
Download the uploaded model and dataset into a new directory using those exact
|
| 186 |
+
revisions. Compare every released file against the release manifest. Run the dataset
|
| 187 |
+
audit on downloaded JSONL files and its manifest. Use a fresh Python process to load
|
| 188 |
+
the downloaded checkpoint onto the pinned upstream backbone and compare its four
|
| 189 |
+
raw reference probability distributions at maximum absolute tolerance1e-4.
|
| 190 |
+
|
| 191 |
+
Use `huggingface_hub.snapshot_download` with each recorded immutable revision and
|
| 192 |
+
an empty `local_dir` for each repository. Use `repo_type="dataset"` for the dataset
|
| 193 |
+
and `repo_type="space"` for the demo. `local_dir` materializes regular files; passing
|
| 194 |
+
a shared cache snapshot directory directly can expose symlinks, which the verifier
|
| 195 |
+
rejects. Keep the original local `release-manifest.json` separately as the trusted
|
| 196 |
+
comparison, rather than trusting a manifest fetched with the same download.
|
| 197 |
+
|
| 198 |
+
From the reviewed source checkout, verify all three downloaded repositories:
|
| 199 |
+
|
| 200 |
+
```bash
|
| 201 |
+
uv run --locked --extra ml python scripts/verify_release.py \
|
| 202 |
+
--trusted-manifest LOCAL_RELEASE/release-manifest.json \
|
| 203 |
+
--model DOWNLOADED_MODEL \
|
| 204 |
+
--dataset DOWNLOADED_DATASET \
|
| 205 |
+
--demo DOWNLOADED_SPACE \
|
| 206 |
+
--output runs/released-files-verification.json
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
This verifies file sizes and SHA256 hashes, checks each uploaded per-repository
|
| 210 |
+
manifest, and reruns the dataset schema/split audit. It rejects missing or extra
|
| 211 |
+
payloads and links, except narrowly defined Hugging Face download bookkeeping,
|
| 212 |
+
which it lists in the report. It does not establish publisher authenticity,
|
| 213 |
+
prove that a download was fresh, or load the neural model. Save the download
|
| 214 |
+
revisions and command separately, then perform the GPU parity check below.
|
| 215 |
+
|
| 216 |
+
The existing probe script can perform that final serialization check:
|
| 217 |
+
|
| 218 |
+
```bash
|
| 219 |
+
uv run --locked --extra ml python scripts/probe_clef_training.py \
|
| 220 |
+
--dataset DOWNLOADED_DATASET \
|
| 221 |
+
--reload DOWNLOADED_MODEL/checkpoint \
|
| 222 |
+
--output runs/released-model-reload
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
Run this only with sufficient free GPU memory. On the original workstation, use the
|
| 226 |
+
approved GLM stop/restore wrapper and verify restored health afterward. Another
|
| 227 |
+
machine must arrange its own workloads; the workstation-specific wrapper is not a
|
| 228 |
+
portable permission to stop services.
|
| 229 |
+
|
| 230 |
+
Check the published Space's `/health`, static assets, human moves and recorded race
|
| 231 |
+
in a browser. Verify model/dataset links and any private GitHub access expectations.
|
| 232 |
+
Record failures and correct the actual published bundle before declaring release
|
| 233 |
+
complete. Then update the plan's M6 status and final outcomes with verified links,
|
| 234 |
+
revisions, hashes, measured limitations and fresh-download evidence.
|
code/pyproject.toml
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["hatchling"]
|
| 3 |
+
build-backend = "hatchling.build"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "stackcraft"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "A reproducible falling-block playground for decision models"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
requires-python = ">=3.13,<3.14"
|
| 11 |
+
dependencies = ["fastapi", "uvicorn"]
|
| 12 |
+
|
| 13 |
+
[project.optional-dependencies]
|
| 14 |
+
ml = [
|
| 15 |
+
"torch==2.14.1",
|
| 16 |
+
"torchvision",
|
| 17 |
+
"transformers==5.18.0",
|
| 18 |
+
"peft==0.21.2",
|
| 19 |
+
"safetensors>=0.8,<1",
|
| 20 |
+
"huggingface-hub>=1.33,<2",
|
| 21 |
+
"pillow>=12,<13",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
[project.scripts]
|
| 25 |
+
stackcraft = "stackcraft.cli:main"
|
| 26 |
+
|
| 27 |
+
[dependency-groups]
|
| 28 |
+
dev = [
|
| 29 |
+
"pytest",
|
| 30 |
+
"ruff",
|
| 31 |
+
"ty",
|
| 32 |
+
"httpx2>=2.13.1",
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
[tool.hatch.build.targets.wheel]
|
| 36 |
+
packages = ["src/stackcraft"]
|
| 37 |
+
|
| 38 |
+
[tool.ruff]
|
| 39 |
+
target-version = "py313"
|
| 40 |
+
line-length = 100
|
| 41 |
+
|
| 42 |
+
[tool.ruff.lint]
|
| 43 |
+
select = ["E", "F", "I", "UP", "B"]
|
| 44 |
+
|
| 45 |
+
[tool.pytest.ini_options]
|
| 46 |
+
testpaths = ["tests"]
|
| 47 |
+
addopts = "-ra"
|
| 48 |
+
|
| 49 |
+
[tool.uv]
|
| 50 |
+
required-version = ">=0.12.23,<0.13"
|
| 51 |
+
|
| 52 |
+
[tool.uv.sources]
|
| 53 |
+
torch = [{ index = "pytorch-cu130", marker = "sys_platform == 'linux'" }]
|
| 54 |
+
torchvision = [{ index = "pytorch-cu130", marker = "sys_platform == 'linux'" }]
|
| 55 |
+
|
| 56 |
+
[[tool.uv.index]]
|
| 57 |
+
name = "pytorch-cu130"
|
| 58 |
+
url = "https://download.pytorch.org/whl/cu130"
|
| 59 |
+
explicit = true
|
code/release/dataset-card.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- synthetic
|
| 7 |
+
- imitation-learning
|
| 8 |
+
- games
|
| 9 |
+
- stackcraft
|
| 10 |
+
size_categories:
|
| 11 |
+
- 1K<n<10K
|
| 12 |
+
configs:
|
| 13 |
+
- config_name: default
|
| 14 |
+
data_files:
|
| 15 |
+
- split: train
|
| 16 |
+
path: train.jsonl
|
| 17 |
+
- split: validation
|
| 18 |
+
path: validation.jsonl
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
# Stackcraft search-teacher positions
|
| 22 |
+
|
| 23 |
+
A synthetic dataset for a deterministic, simplified falling-block placement game.
|
| 24 |
+
**827 training positions and 215 validation positions**, generated entirely by
|
| 25 |
+
code. No human demonstrations, personal data, web scraping or human row-by-row
|
| 26 |
+
label review are claimed. Independent agent review recomputed 16 pilot labels;
|
| 27 |
+
that sample audit is not a review of every row.
|
| 28 |
+
|
| 29 |
+
Each row contains a visible board, current piece, one next piece, all legal actions,
|
| 30 |
+
a teacher action, teacher action values and provenance. The board uses integer
|
| 31 |
+
piece colors; the model encoding reduces them to occupancy because color does not
|
| 32 |
+
affect these rules. Metadata includes seed and turn for reproduction; **do not
|
| 33 |
+
feed seed or hidden sequence metadata to the policy**. Stackcraft's encoder feeds
|
| 34 |
+
only the visible observation. Read the dataset schema and tutorial03 before use.
|
| 35 |
+
|
| 36 |
+
Related releases: [source and tutorials](https://github.com/kkarimi/stackcraft),
|
| 37 |
+
[model](https://huggingface.co/nima1/stackcraft-clef-flash-lora),
|
| 38 |
+
[dataset](https://huggingface.co/datasets/nima1/stackcraft-data), and
|
| 39 |
+
[planned CPU demo](https://huggingface.co/spaces/nima1/stackcraft).
|
| 40 |
+
The hosted demo awaits Hugging Face's Docker-account subscription requirement;
|
| 41 |
+
[local Docker instructions](https://github.com/kkarimi/stackcraft/blob/main/docs/demo-hosting.md)
|
| 42 |
+
work without that subscription.
|
| 43 |
+
|
| 44 |
+
## Generation
|
| 45 |
+
|
| 46 |
+
The teacher searches the current placement and exactly one preview placement.
|
| 47 |
+
It rewards current line clears and the best next afterboard, using fixed weights
|
| 48 |
+
for aggregate height, holes, bumpiness and cleared lines. No later pieces are
|
| 49 |
+
available. Labels are heuristic recommendations, not proofs of optimal play.
|
| 50 |
+
|
| 51 |
+
Collection cycles random, heuristic and search-expert policies, at a 40-piece cap,
|
| 52 |
+
to expose different board conditions. Training seeds 10000–10023 and validation
|
| 53 |
+
seeds 20000–20005 are disjoint. Six within-training and two cross-split duplicate
|
| 54 |
+
observations were excluded. Final test seeds 30000–30199 are reserved for complete
|
| 55 |
+
game evaluation; there is no test-position split for training or selection.
|
| 56 |
+
|
| 57 |
+
Source commit: `70d84bd8dc5d4a60f3b96455a57d9e6f9416d109`.
|
| 58 |
+
The bundle includes `manifest.json` with source/configuration and split hashes.
|
| 59 |
+
|
| 60 |
+
- train SHA256: `edd682761db95a4f25bb30a284489c54d9336a36da0a9b19d2cda860b428baa8`
|
| 61 |
+
- validation SHA256: `eff9cdc5932e935959ac4d26dce6470d335090f7428a91930001954266d133bc`
|
| 62 |
+
|
| 63 |
+
The model release includes public source under `code/` and the exact regeneration
|
| 64 |
+
recipe in `code/docs/tutorials/03-data.md`. Use its **Reproduce from the public
|
| 65 |
+
source bundle without private Git access** procedure: first verify all six generator
|
| 66 |
+
source hashes against this dataset's `manifest.json`, then generate with the
|
| 67 |
+
original source-commit provenance. Access to the original Git repository is not
|
| 68 |
+
required. The procedure reproduced both JSONL hashes exactly on the study machine.
|
| 69 |
+
Ordinary `stackcraft generate-data --output NEWDIR` records the current source
|
| 70 |
+
identity; a new source commit deliberately changes provenance fields and therefore
|
| 71 |
+
file hashes even if labels are identical. A regeneration and an independent
|
| 72 |
+
sample-label audit preceded study training.
|
| 73 |
+
|
| 74 |
+
## Limits
|
| 75 |
+
|
| 76 |
+
The study is small and entirely synthetic. Teacher bias, narrow collection seeds,
|
| 77 |
+
short collection episodes and one-step preview limit generalization. Duplicate
|
| 78 |
+
removal prevents exact overlap, not all structural similarity. It is not full
|
| 79 |
+
real-time Tetris: no hold, kicks, tucks or gravity timing. Colors and piece IDs
|
| 80 |
+
are program-generated; Stackcraft uses original game visuals and branding.
|
| 81 |
+
|
| 82 |
+
Use teacher agreement as a diagnostic and complete held-out games for outcome
|
| 83 |
+
measurement. Do not call a model better solely because it copies these labels.
|
| 84 |
+
Apache-2.0; see LICENSE and accompanying generation/reproduction tutorials.
|
code/release/model-card.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: Cloudflare/clef-flash
|
| 4 |
+
base_model_relation: adapter
|
| 5 |
+
library_name: peft
|
| 6 |
+
language:
|
| 7 |
+
- en
|
| 8 |
+
tags:
|
| 9 |
+
- clef
|
| 10 |
+
- decision-model
|
| 11 |
+
- lora
|
| 12 |
+
- imitation-learning
|
| 13 |
+
- stackcraft
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# Stackcraft Clef-Flash
|
| 17 |
+
|
| 18 |
+
**Release preparation: full-study results and selected checkpoint are pending.**
|
| 19 |
+
This is a small, synthetic imitation-learning study of a falling-block placement
|
| 20 |
+
policy. It is not a generally capable game agent, reinforcement-learning result,
|
| 21 |
+
or competitive Tetris implementation.
|
| 22 |
+
|
| 23 |
+
The artifact contains a rank-4 LoRA adapter **and a separately trained FP32 native
|
| 24 |
+
joint decision head** for `Cloudflare/clef-flash`, pinned revision
|
| 25 |
+
`17f0b0ad64efb65d273590632833508766b2aae6`. Load both. The frozen BF16 backbone
|
| 26 |
+
is downloaded separately; these files are not a standalone model. Clef-flash is
|
| 27 |
+
based on Qwen3.5-9B. The native schema head scores every supplied legal placement
|
| 28 |
+
in one forward pass, with no generated text or command parsing.
|
| 29 |
+
|
| 30 |
+
Related releases: [source and tutorials](https://github.com/kkarimi/stackcraft),
|
| 31 |
+
[model](https://huggingface.co/nima1/stackcraft-clef-flash-lora),
|
| 32 |
+
[dataset](https://huggingface.co/datasets/nima1/stackcraft-data), and
|
| 33 |
+
[planned CPU demo](https://huggingface.co/spaces/nima1/stackcraft).
|
| 34 |
+
The hosted demo awaits Hugging Face's Docker-account subscription requirement;
|
| 35 |
+
[local Docker instructions](https://github.com/kkarimi/stackcraft/blob/main/docs/demo-hosting.md)
|
| 36 |
+
work without that subscription.
|
| 37 |
+
|
| 38 |
+
## Intended use
|
| 39 |
+
|
| 40 |
+
Learn how to build deterministic environments, generate search-teacher labels,
|
| 41 |
+
train a custom decision model, preserve its native head, and evaluate complete
|
| 42 |
+
games with paired uncertainty. The playable demo uses recorded model replays;
|
| 43 |
+
it does not provide public live GPU inference.
|
| 44 |
+
|
| 45 |
+
The observation is a 10×20 occupied-cell grid, current tetromino, exactly one next
|
| 46 |
+
piece, rules and every legal placement with its coordinates. The action selects
|
| 47 |
+
an orientation and column, then drops vertically. There is no hold, wall kick,
|
| 48 |
+
tuck, gravity timer, T-spin bonus or changing speed. Hidden future pieces and
|
| 49 |
+
sequence seeds are excluded from neural input. Probabilities reflect the model's
|
| 50 |
+
choice distribution; they are not calibrated chances of clearing a line or winning.
|
| 51 |
+
|
| 52 |
+
## Training and data
|
| 53 |
+
|
| 54 |
+
The study uses 827 training and 215 validation positions generated from fixed
|
| 55 |
+
seven-bag streams. Random, heuristic and search policies collect states. Every
|
| 56 |
+
label comes from a fixed two-placement search using the current piece and one
|
| 57 |
+
preview, with a board-height/holes/bumpiness/line-clear heuristic. These are
|
| 58 |
+
synthetic teacher preferences, not human demonstrations or globally optimal moves.
|
| 59 |
+
Exact duplicate observations are removed across splits. Six source hashes and
|
| 60 |
+
exact file hashes accompany the dataset manifest.
|
| 61 |
+
|
| 62 |
+
Preregistered training: seed 42, two epochs, batch 1, accumulation 8, rank 4/alpha 8,
|
| 63 |
+
no LoRA dropout, AdamW lr 1e-5/weight_decay 0.01, gradient norm cap 1.0, label smoothing
|
| 64 |
+
0.05 and Brier sum weight0.1. BF16 backbone, FP32 head, text-layer LoRA including
|
| 65 |
+
both hybrid attention types and MLPs. Vision, output and other original parameters
|
| 66 |
+
remain frozen. There are 132,582,404 trainable parameters: 121,762,820 in the
|
| 67 |
+
joint head and 10,819,584 in LoRA. This is why the head is a substantial part of
|
| 68 |
+
the saved artifact, even though most backbone weights stay frozen. Select only between epoch 01/02 using lowest finite target NLL on
|
| 69 |
+
all 215 validation positions; exact ties choose the earlier epoch.
|
| 70 |
+
|
| 71 |
+
## Evaluation and limitations
|
| 72 |
+
|
| 73 |
+
Final preregistered comparison: 200 untouched seeds 30000–30199, 200-piece cap,
|
| 74 |
+
selected model versus unchanged native BF16-head Clef, unchanged FP32-head
|
| 75 |
+
ablation, random legal placement and a cheap heuristic. Primary metric is lines
|
| 76 |
+
cleared. Report score, survival, cap hits, failures and latency. Paired 95% bootstrap
|
| 77 |
+
intervals use 10000 episode resamples with seed 2026. Failed episodes remain in the
|
| 78 |
+
analysis, assigned zero primary outcomes with partial outcomes reported separately.
|
| 79 |
+
|
| 80 |
+
**Insert verified held-out results here before publication.**
|
| 81 |
+
|
| 82 |
+
Fine-tuning improved complete-game play over both unchanged Clef references in
|
| 83 |
+
this study. The fixed heuristic still cleared substantially more lines and used
|
| 84 |
+
about one thousandth of the trained model's decision time on this workstation.
|
| 85 |
+
All five policies had zero errors and invalid decisions. Every heuristic game hit
|
| 86 |
+
the 200-piece cap; no neural or random game did. This is evidence of task adaptation,
|
| 87 |
+
not a reason to prefer this 9B model over the heuristic for the simplified game.
|
| 88 |
+
See [the readable study report](code/reports/final-study.md) and
|
| 89 |
+
[the hardware/runtime snapshot](code/reports/evaluation-hardware.json).
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
Only one training seed, one small synthetic dataset, one simplified game ruleset
|
| 93 |
+
and one hardware/software configuration are studied. The finite cap censors
|
| 94 |
+
survival. Validation teacher agreement does not establish game quality. Test
|
| 95 |
+
uncertainty does not capture variation across training seeds.
|
| 96 |
+
|
| 97 |
+
## Loading
|
| 98 |
+
|
| 99 |
+
Use the released Stackcraft source and locked ML dependencies. Generic
|
| 100 |
+
`AutoModel` or adapter-only loading omits the custom decision head and is incorrect.
|
| 101 |
+
From the downloaded model repository's `code/` directory, install the locked ML
|
| 102 |
+
environment with `uv sync --locked --extra ml` and cache the pinned upstream
|
| 103 |
+
snapshot as described in [Tutorial 02](code/docs/tutorials/02-baselines.md).
|
| 104 |
+
Run this example with `uv run --locked --extra ml python`; the sibling
|
| 105 |
+
`../checkpoint` directory contains the released adapter and head:
|
| 106 |
+
|
| 107 |
+
```python
|
| 108 |
+
from stackcraft.clef import ClefPlayer
|
| 109 |
+
from stackcraft.training import load_checkpoint
|
| 110 |
+
from stackcraft.engine import new_game
|
| 111 |
+
from stackcraft.players import observe
|
| 112 |
+
|
| 113 |
+
player = ClefPlayer.from_pretrained(trust_pinned_code=True, local_files_only=True)
|
| 114 |
+
load_checkpoint(player.model, "../checkpoint")
|
| 115 |
+
print(player.choose(observe(new_game(42))))
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
`trust_pinned_code=True` executes reviewed, hash-checked upstream Python. It is
|
| 119 |
+
not a sandbox. The measured RTX 5090 inference baseline allocated 19.7 GB; bounded
|
| 120 |
+
LoRA training allocated 23.2 GB. Load only with sufficient free VRAM. Context is
|
| 121 |
+
limited to 4096 tokens, with explicit rejection before any state truncation.
|
| 122 |
+
|
| 123 |
+
See the released tutorials, training configuration, artifact hashes and evaluation
|
| 124 |
+
report for exact reproduction. Apache-2.0; see LICENSE and NOTICE for upstream
|
| 125 |
+
attribution. This project is independent of Cloudflare, Qwen and Tetris.
|
code/reports/clef-development.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": 1,
|
| 3 |
+
"rules_version": "stackcraft-v1",
|
| 4 |
+
"seeds": [
|
| 5 |
+
0,
|
| 6 |
+
1
|
| 7 |
+
],
|
| 8 |
+
"max_pieces": 30,
|
| 9 |
+
"summary": {
|
| 10 |
+
"random": {
|
| 11 |
+
"episodes": 2,
|
| 12 |
+
"mean_lines": 0,
|
| 13 |
+
"median_lines": 0.0,
|
| 14 |
+
"mean_score": 0,
|
| 15 |
+
"mean_pieces": 27,
|
| 16 |
+
"cap_hit_rate": 0,
|
| 17 |
+
"error_rate": 0,
|
| 18 |
+
"timing": {
|
| 19 |
+
"count": 54,
|
| 20 |
+
"total_seconds": 9.454792598262429e-05,
|
| 21 |
+
"mean_seconds": 1.7508875181967461e-06,
|
| 22 |
+
"median_seconds": 1.580017851665616e-06,
|
| 23 |
+
"p95_seconds": 2.9500224627554417e-06
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"heuristic": {
|
| 27 |
+
"episodes": 2,
|
| 28 |
+
"mean_lines": 9,
|
| 29 |
+
"median_lines": 9.0,
|
| 30 |
+
"mean_score": 1200,
|
| 31 |
+
"mean_pieces": 30,
|
| 32 |
+
"cap_hit_rate": 1,
|
| 33 |
+
"error_rate": 0,
|
| 34 |
+
"timing": {
|
| 35 |
+
"count": 60,
|
| 36 |
+
"total_seconds": 0.010729207948315889,
|
| 37 |
+
"mean_seconds": 0.00017882013247193148,
|
| 38 |
+
"median_seconds": 0.00013837897859048098,
|
| 39 |
+
"p95_seconds": 0.00027111798408441246
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
"clef-base": {
|
| 43 |
+
"episodes": 2,
|
| 44 |
+
"mean_lines": 0,
|
| 45 |
+
"median_lines": 0.0,
|
| 46 |
+
"mean_score": 0,
|
| 47 |
+
"mean_pieces": 27.5,
|
| 48 |
+
"cap_hit_rate": 0,
|
| 49 |
+
"error_rate": 0,
|
| 50 |
+
"timing": {
|
| 51 |
+
"count": 55,
|
| 52 |
+
"total_seconds": 7.934351755975513,
|
| 53 |
+
"mean_seconds": 0.1442609410177366,
|
| 54 |
+
"median_seconds": 0.1331863310188055,
|
| 55 |
+
"p95_seconds": 0.24066273399512284
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
},
|
| 59 |
+
"development_only": true,
|
| 60 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 61 |
+
"peak_allocated_bytes": 19686539776,
|
| 62 |
+
"peak_reserved_bytes": 20002635776,
|
| 63 |
+
"elapsed_seconds": 12.598100575996796,
|
| 64 |
+
"artifact_sha256": "3c56622dc2a95eb89df4cae87ecce4bb2a4afb26e47c43553a9e7f826e971e93",
|
| 65 |
+
"replays_verified": 6
|
| 66 |
+
}
|
code/reports/dataset-manifest.json
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"config": {
|
| 3 |
+
"behavior_cycle": [
|
| 4 |
+
"random",
|
| 5 |
+
"heuristic",
|
| 6 |
+
"expert"
|
| 7 |
+
],
|
| 8 |
+
"max_pieces": 40,
|
| 9 |
+
"reserved_test_seeds": [
|
| 10 |
+
30000,
|
| 11 |
+
30001,
|
| 12 |
+
30002,
|
| 13 |
+
30003,
|
| 14 |
+
30004,
|
| 15 |
+
30005,
|
| 16 |
+
30006,
|
| 17 |
+
30007,
|
| 18 |
+
30008,
|
| 19 |
+
30009,
|
| 20 |
+
30010,
|
| 21 |
+
30011,
|
| 22 |
+
30012,
|
| 23 |
+
30013,
|
| 24 |
+
30014,
|
| 25 |
+
30015,
|
| 26 |
+
30016,
|
| 27 |
+
30017,
|
| 28 |
+
30018,
|
| 29 |
+
30019,
|
| 30 |
+
30020,
|
| 31 |
+
30021,
|
| 32 |
+
30022,
|
| 33 |
+
30023,
|
| 34 |
+
30024,
|
| 35 |
+
30025,
|
| 36 |
+
30026,
|
| 37 |
+
30027,
|
| 38 |
+
30028,
|
| 39 |
+
30029,
|
| 40 |
+
30030,
|
| 41 |
+
30031,
|
| 42 |
+
30032,
|
| 43 |
+
30033,
|
| 44 |
+
30034,
|
| 45 |
+
30035,
|
| 46 |
+
30036,
|
| 47 |
+
30037,
|
| 48 |
+
30038,
|
| 49 |
+
30039,
|
| 50 |
+
30040,
|
| 51 |
+
30041,
|
| 52 |
+
30042,
|
| 53 |
+
30043,
|
| 54 |
+
30044,
|
| 55 |
+
30045,
|
| 56 |
+
30046,
|
| 57 |
+
30047,
|
| 58 |
+
30048,
|
| 59 |
+
30049,
|
| 60 |
+
30050,
|
| 61 |
+
30051,
|
| 62 |
+
30052,
|
| 63 |
+
30053,
|
| 64 |
+
30054,
|
| 65 |
+
30055,
|
| 66 |
+
30056,
|
| 67 |
+
30057,
|
| 68 |
+
30058,
|
| 69 |
+
30059,
|
| 70 |
+
30060,
|
| 71 |
+
30061,
|
| 72 |
+
30062,
|
| 73 |
+
30063,
|
| 74 |
+
30064,
|
| 75 |
+
30065,
|
| 76 |
+
30066,
|
| 77 |
+
30067,
|
| 78 |
+
30068,
|
| 79 |
+
30069,
|
| 80 |
+
30070,
|
| 81 |
+
30071,
|
| 82 |
+
30072,
|
| 83 |
+
30073,
|
| 84 |
+
30074,
|
| 85 |
+
30075,
|
| 86 |
+
30076,
|
| 87 |
+
30077,
|
| 88 |
+
30078,
|
| 89 |
+
30079,
|
| 90 |
+
30080,
|
| 91 |
+
30081,
|
| 92 |
+
30082,
|
| 93 |
+
30083,
|
| 94 |
+
30084,
|
| 95 |
+
30085,
|
| 96 |
+
30086,
|
| 97 |
+
30087,
|
| 98 |
+
30088,
|
| 99 |
+
30089,
|
| 100 |
+
30090,
|
| 101 |
+
30091,
|
| 102 |
+
30092,
|
| 103 |
+
30093,
|
| 104 |
+
30094,
|
| 105 |
+
30095,
|
| 106 |
+
30096,
|
| 107 |
+
30097,
|
| 108 |
+
30098,
|
| 109 |
+
30099,
|
| 110 |
+
30100,
|
| 111 |
+
30101,
|
| 112 |
+
30102,
|
| 113 |
+
30103,
|
| 114 |
+
30104,
|
| 115 |
+
30105,
|
| 116 |
+
30106,
|
| 117 |
+
30107,
|
| 118 |
+
30108,
|
| 119 |
+
30109,
|
| 120 |
+
30110,
|
| 121 |
+
30111,
|
| 122 |
+
30112,
|
| 123 |
+
30113,
|
| 124 |
+
30114,
|
| 125 |
+
30115,
|
| 126 |
+
30116,
|
| 127 |
+
30117,
|
| 128 |
+
30118,
|
| 129 |
+
30119,
|
| 130 |
+
30120,
|
| 131 |
+
30121,
|
| 132 |
+
30122,
|
| 133 |
+
30123,
|
| 134 |
+
30124,
|
| 135 |
+
30125,
|
| 136 |
+
30126,
|
| 137 |
+
30127,
|
| 138 |
+
30128,
|
| 139 |
+
30129,
|
| 140 |
+
30130,
|
| 141 |
+
30131,
|
| 142 |
+
30132,
|
| 143 |
+
30133,
|
| 144 |
+
30134,
|
| 145 |
+
30135,
|
| 146 |
+
30136,
|
| 147 |
+
30137,
|
| 148 |
+
30138,
|
| 149 |
+
30139,
|
| 150 |
+
30140,
|
| 151 |
+
30141,
|
| 152 |
+
30142,
|
| 153 |
+
30143,
|
| 154 |
+
30144,
|
| 155 |
+
30145,
|
| 156 |
+
30146,
|
| 157 |
+
30147,
|
| 158 |
+
30148,
|
| 159 |
+
30149,
|
| 160 |
+
30150,
|
| 161 |
+
30151,
|
| 162 |
+
30152,
|
| 163 |
+
30153,
|
| 164 |
+
30154,
|
| 165 |
+
30155,
|
| 166 |
+
30156,
|
| 167 |
+
30157,
|
| 168 |
+
30158,
|
| 169 |
+
30159,
|
| 170 |
+
30160,
|
| 171 |
+
30161,
|
| 172 |
+
30162,
|
| 173 |
+
30163,
|
| 174 |
+
30164,
|
| 175 |
+
30165,
|
| 176 |
+
30166,
|
| 177 |
+
30167,
|
| 178 |
+
30168,
|
| 179 |
+
30169,
|
| 180 |
+
30170,
|
| 181 |
+
30171,
|
| 182 |
+
30172,
|
| 183 |
+
30173,
|
| 184 |
+
30174,
|
| 185 |
+
30175,
|
| 186 |
+
30176,
|
| 187 |
+
30177,
|
| 188 |
+
30178,
|
| 189 |
+
30179,
|
| 190 |
+
30180,
|
| 191 |
+
30181,
|
| 192 |
+
30182,
|
| 193 |
+
30183,
|
| 194 |
+
30184,
|
| 195 |
+
30185,
|
| 196 |
+
30186,
|
| 197 |
+
30187,
|
| 198 |
+
30188,
|
| 199 |
+
30189,
|
| 200 |
+
30190,
|
| 201 |
+
30191,
|
| 202 |
+
30192,
|
| 203 |
+
30193,
|
| 204 |
+
30194,
|
| 205 |
+
30195,
|
| 206 |
+
30196,
|
| 207 |
+
30197,
|
| 208 |
+
30198,
|
| 209 |
+
30199
|
| 210 |
+
],
|
| 211 |
+
"train_seeds": [
|
| 212 |
+
10000,
|
| 213 |
+
10001,
|
| 214 |
+
10002,
|
| 215 |
+
10003,
|
| 216 |
+
10004,
|
| 217 |
+
10005,
|
| 218 |
+
10006,
|
| 219 |
+
10007,
|
| 220 |
+
10008,
|
| 221 |
+
10009,
|
| 222 |
+
10010,
|
| 223 |
+
10011,
|
| 224 |
+
10012,
|
| 225 |
+
10013,
|
| 226 |
+
10014,
|
| 227 |
+
10015,
|
| 228 |
+
10016,
|
| 229 |
+
10017,
|
| 230 |
+
10018,
|
| 231 |
+
10019,
|
| 232 |
+
10020,
|
| 233 |
+
10021,
|
| 234 |
+
10022,
|
| 235 |
+
10023
|
| 236 |
+
],
|
| 237 |
+
"validation_seeds": [
|
| 238 |
+
20000,
|
| 239 |
+
20001,
|
| 240 |
+
20002,
|
| 241 |
+
20003,
|
| 242 |
+
20004,
|
| 243 |
+
20005
|
| 244 |
+
]
|
| 245 |
+
},
|
| 246 |
+
"config_sha256": "65b72544c80cf19f4ac3b12ca109d0ebda18fb70eadb4b170a55b1e8bb3b4f6d",
|
| 247 |
+
"deduplication": {
|
| 248 |
+
"exclusions": {
|
| 249 |
+
"train": {
|
| 250 |
+
"cross_split": 0,
|
| 251 |
+
"within_split": 6
|
| 252 |
+
},
|
| 253 |
+
"validation": {
|
| 254 |
+
"cross_split": 2,
|
| 255 |
+
"within_split": 0
|
| 256 |
+
}
|
| 257 |
+
},
|
| 258 |
+
"normalization": "board occupancy"
|
| 259 |
+
},
|
| 260 |
+
"rules_version": "stackcraft-v1",
|
| 261 |
+
"schema_version": 1,
|
| 262 |
+
"seed_pools": {
|
| 263 |
+
"reserved_test": [
|
| 264 |
+
30000,
|
| 265 |
+
30001,
|
| 266 |
+
30002,
|
| 267 |
+
30003,
|
| 268 |
+
30004,
|
| 269 |
+
30005,
|
| 270 |
+
30006,
|
| 271 |
+
30007,
|
| 272 |
+
30008,
|
| 273 |
+
30009,
|
| 274 |
+
30010,
|
| 275 |
+
30011,
|
| 276 |
+
30012,
|
| 277 |
+
30013,
|
| 278 |
+
30014,
|
| 279 |
+
30015,
|
| 280 |
+
30016,
|
| 281 |
+
30017,
|
| 282 |
+
30018,
|
| 283 |
+
30019,
|
| 284 |
+
30020,
|
| 285 |
+
30021,
|
| 286 |
+
30022,
|
| 287 |
+
30023,
|
| 288 |
+
30024,
|
| 289 |
+
30025,
|
| 290 |
+
30026,
|
| 291 |
+
30027,
|
| 292 |
+
30028,
|
| 293 |
+
30029,
|
| 294 |
+
30030,
|
| 295 |
+
30031,
|
| 296 |
+
30032,
|
| 297 |
+
30033,
|
| 298 |
+
30034,
|
| 299 |
+
30035,
|
| 300 |
+
30036,
|
| 301 |
+
30037,
|
| 302 |
+
30038,
|
| 303 |
+
30039,
|
| 304 |
+
30040,
|
| 305 |
+
30041,
|
| 306 |
+
30042,
|
| 307 |
+
30043,
|
| 308 |
+
30044,
|
| 309 |
+
30045,
|
| 310 |
+
30046,
|
| 311 |
+
30047,
|
| 312 |
+
30048,
|
| 313 |
+
30049,
|
| 314 |
+
30050,
|
| 315 |
+
30051,
|
| 316 |
+
30052,
|
| 317 |
+
30053,
|
| 318 |
+
30054,
|
| 319 |
+
30055,
|
| 320 |
+
30056,
|
| 321 |
+
30057,
|
| 322 |
+
30058,
|
| 323 |
+
30059,
|
| 324 |
+
30060,
|
| 325 |
+
30061,
|
| 326 |
+
30062,
|
| 327 |
+
30063,
|
| 328 |
+
30064,
|
| 329 |
+
30065,
|
| 330 |
+
30066,
|
| 331 |
+
30067,
|
| 332 |
+
30068,
|
| 333 |
+
30069,
|
| 334 |
+
30070,
|
| 335 |
+
30071,
|
| 336 |
+
30072,
|
| 337 |
+
30073,
|
| 338 |
+
30074,
|
| 339 |
+
30075,
|
| 340 |
+
30076,
|
| 341 |
+
30077,
|
| 342 |
+
30078,
|
| 343 |
+
30079,
|
| 344 |
+
30080,
|
| 345 |
+
30081,
|
| 346 |
+
30082,
|
| 347 |
+
30083,
|
| 348 |
+
30084,
|
| 349 |
+
30085,
|
| 350 |
+
30086,
|
| 351 |
+
30087,
|
| 352 |
+
30088,
|
| 353 |
+
30089,
|
| 354 |
+
30090,
|
| 355 |
+
30091,
|
| 356 |
+
30092,
|
| 357 |
+
30093,
|
| 358 |
+
30094,
|
| 359 |
+
30095,
|
| 360 |
+
30096,
|
| 361 |
+
30097,
|
| 362 |
+
30098,
|
| 363 |
+
30099,
|
| 364 |
+
30100,
|
| 365 |
+
30101,
|
| 366 |
+
30102,
|
| 367 |
+
30103,
|
| 368 |
+
30104,
|
| 369 |
+
30105,
|
| 370 |
+
30106,
|
| 371 |
+
30107,
|
| 372 |
+
30108,
|
| 373 |
+
30109,
|
| 374 |
+
30110,
|
| 375 |
+
30111,
|
| 376 |
+
30112,
|
| 377 |
+
30113,
|
| 378 |
+
30114,
|
| 379 |
+
30115,
|
| 380 |
+
30116,
|
| 381 |
+
30117,
|
| 382 |
+
30118,
|
| 383 |
+
30119,
|
| 384 |
+
30120,
|
| 385 |
+
30121,
|
| 386 |
+
30122,
|
| 387 |
+
30123,
|
| 388 |
+
30124,
|
| 389 |
+
30125,
|
| 390 |
+
30126,
|
| 391 |
+
30127,
|
| 392 |
+
30128,
|
| 393 |
+
30129,
|
| 394 |
+
30130,
|
| 395 |
+
30131,
|
| 396 |
+
30132,
|
| 397 |
+
30133,
|
| 398 |
+
30134,
|
| 399 |
+
30135,
|
| 400 |
+
30136,
|
| 401 |
+
30137,
|
| 402 |
+
30138,
|
| 403 |
+
30139,
|
| 404 |
+
30140,
|
| 405 |
+
30141,
|
| 406 |
+
30142,
|
| 407 |
+
30143,
|
| 408 |
+
30144,
|
| 409 |
+
30145,
|
| 410 |
+
30146,
|
| 411 |
+
30147,
|
| 412 |
+
30148,
|
| 413 |
+
30149,
|
| 414 |
+
30150,
|
| 415 |
+
30151,
|
| 416 |
+
30152,
|
| 417 |
+
30153,
|
| 418 |
+
30154,
|
| 419 |
+
30155,
|
| 420 |
+
30156,
|
| 421 |
+
30157,
|
| 422 |
+
30158,
|
| 423 |
+
30159,
|
| 424 |
+
30160,
|
| 425 |
+
30161,
|
| 426 |
+
30162,
|
| 427 |
+
30163,
|
| 428 |
+
30164,
|
| 429 |
+
30165,
|
| 430 |
+
30166,
|
| 431 |
+
30167,
|
| 432 |
+
30168,
|
| 433 |
+
30169,
|
| 434 |
+
30170,
|
| 435 |
+
30171,
|
| 436 |
+
30172,
|
| 437 |
+
30173,
|
| 438 |
+
30174,
|
| 439 |
+
30175,
|
| 440 |
+
30176,
|
| 441 |
+
30177,
|
| 442 |
+
30178,
|
| 443 |
+
30179,
|
| 444 |
+
30180,
|
| 445 |
+
30181,
|
| 446 |
+
30182,
|
| 447 |
+
30183,
|
| 448 |
+
30184,
|
| 449 |
+
30185,
|
| 450 |
+
30186,
|
| 451 |
+
30187,
|
| 452 |
+
30188,
|
| 453 |
+
30189,
|
| 454 |
+
30190,
|
| 455 |
+
30191,
|
| 456 |
+
30192,
|
| 457 |
+
30193,
|
| 458 |
+
30194,
|
| 459 |
+
30195,
|
| 460 |
+
30196,
|
| 461 |
+
30197,
|
| 462 |
+
30198,
|
| 463 |
+
30199
|
| 464 |
+
],
|
| 465 |
+
"train": [
|
| 466 |
+
10000,
|
| 467 |
+
10001,
|
| 468 |
+
10002,
|
| 469 |
+
10003,
|
| 470 |
+
10004,
|
| 471 |
+
10005,
|
| 472 |
+
10006,
|
| 473 |
+
10007,
|
| 474 |
+
10008,
|
| 475 |
+
10009,
|
| 476 |
+
10010,
|
| 477 |
+
10011,
|
| 478 |
+
10012,
|
| 479 |
+
10013,
|
| 480 |
+
10014,
|
| 481 |
+
10015,
|
| 482 |
+
10016,
|
| 483 |
+
10017,
|
| 484 |
+
10018,
|
| 485 |
+
10019,
|
| 486 |
+
10020,
|
| 487 |
+
10021,
|
| 488 |
+
10022,
|
| 489 |
+
10023
|
| 490 |
+
],
|
| 491 |
+
"validation": [
|
| 492 |
+
20000,
|
| 493 |
+
20001,
|
| 494 |
+
20002,
|
| 495 |
+
20003,
|
| 496 |
+
20004,
|
| 497 |
+
20005
|
| 498 |
+
]
|
| 499 |
+
},
|
| 500 |
+
"source_commit": "70d84bd8dc5d4a60f3b96455a57d9e6f9416d109",
|
| 501 |
+
"source_hashes": {
|
| 502 |
+
"data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 503 |
+
"engine.py": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
|
| 504 |
+
"expert.py": "608ea7c1516083807d58dbd9bec82d505c1d4bca61e14579f4821e51607e08b5",
|
| 505 |
+
"pieces.py": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
|
| 506 |
+
"players/__init__.py": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
|
| 507 |
+
"schema.py": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5"
|
| 508 |
+
},
|
| 509 |
+
"source_note": "File hashes identify source; commit may precede working-tree edits.",
|
| 510 |
+
"splits": {
|
| 511 |
+
"train": {
|
| 512 |
+
"behavior_records": {
|
| 513 |
+
"heuristic": 318,
|
| 514 |
+
"random": 190,
|
| 515 |
+
"search-expert": 319
|
| 516 |
+
},
|
| 517 |
+
"collected": 833,
|
| 518 |
+
"records": 827,
|
| 519 |
+
"sha256": "edd682761db95a4f25bb30a284489c54d9336a36da0a9b19d2cda860b428baa8"
|
| 520 |
+
},
|
| 521 |
+
"validation": {
|
| 522 |
+
"behavior_records": {
|
| 523 |
+
"heuristic": 80,
|
| 524 |
+
"random": 56,
|
| 525 |
+
"search-expert": 79
|
| 526 |
+
},
|
| 527 |
+
"collected": 217,
|
| 528 |
+
"records": 215,
|
| 529 |
+
"sha256": "eff9cdc5932e935959ac4d26dce6470d335090f7428a91930001954266d133bc"
|
| 530 |
+
}
|
| 531 |
+
},
|
| 532 |
+
"teacher": {
|
| 533 |
+
"optimal": false,
|
| 534 |
+
"preview_pieces": 1,
|
| 535 |
+
"search": "exhaustive legal placements"
|
| 536 |
+
},
|
| 537 |
+
"teacher_revision": "search-v1-preview1-height1-holes4-bump1-lines8-topout10000",
|
| 538 |
+
"test_trajectories_generated": false
|
| 539 |
+
}
|
code/reports/evaluation-hardware.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cpu_model": "AMD Ryzen 9 9950X3D 16-Core Processor",
|
| 3 |
+
"evaluation_request_sha256": "a507e3b947d603b02f56b6df12b160e9e182ee5706d2ac64d8e9f93358f79e62",
|
| 4 |
+
"gpus": [
|
| 5 |
+
{
|
| 6 |
+
"configured_power_limit_watts": 575.0,
|
| 7 |
+
"driver_version": "610.57.04",
|
| 8 |
+
"name": "NVIDIA GeForce RTX 5090",
|
| 9 |
+
"reported_total_memory_mib": 32607
|
| 10 |
+
}
|
| 11 |
+
],
|
| 12 |
+
"host_reported_memory_kib": 129391464,
|
| 13 |
+
"kernel": "7.2.5-3-omarchy",
|
| 14 |
+
"logical_cpu_count": 32,
|
| 15 |
+
"observed_at_utc": "2026-10-05T21:20:14.982799+00:00",
|
| 16 |
+
"python_version": "3.13.16",
|
| 17 |
+
"schema_version": 1,
|
| 18 |
+
"scope": "Read-only hardware snapshot during final evaluation; no energy usage or continuous clock/temperature measurement",
|
| 19 |
+
"trained_runtime_config": {
|
| 20 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 21 |
+
"cpu_threads": 8,
|
| 22 |
+
"cuda_matmul_allow_tf32": false,
|
| 23 |
+
"cuda_version": "13.0",
|
| 24 |
+
"device": "cuda:0",
|
| 25 |
+
"dtype": "torch.bfloat16",
|
| 26 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 27 |
+
"evaluation_head": "fp32-gathered-rows",
|
| 28 |
+
"float32_matmul_precision": "highest",
|
| 29 |
+
"head_dtype": "float32",
|
| 30 |
+
"max_length": 4096,
|
| 31 |
+
"model_id": "Cloudflare/clef-flash",
|
| 32 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:checkpoint-sha256-b8133e9775a82c6b952fc1a1b1c805c5bc993fbcd919e81f663ad35e122ab7c7:stackcraft-clef-v1",
|
| 33 |
+
"source_sha256": "0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3",
|
| 34 |
+
"versions": {
|
| 35 |
+
"huggingface-hub": "1.33.0",
|
| 36 |
+
"peft": "0.21.2",
|
| 37 |
+
"safetensors": "0.8.0",
|
| 38 |
+
"torch": "2.14.1+cu130",
|
| 39 |
+
"transformers": "5.18.0"
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
}
|
code/reports/final-audit-source.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Independent final evidence audit
|
| 2 |
+
|
| 3 |
+
This archives the exact CPU audit script run after evaluation. Its SHA256 is
|
| 4 |
+
recorded in [the audit result](final-audit.json). It assumes the original local
|
| 5 |
+
`runs/`, dataset and checkpoint layout; it is an execution record, not a portable
|
| 6 |
+
release-verification CLI. Use `scripts/verify_release.py` for release downloads.
|
| 7 |
+
No new games or model inference were run by this audit.
|
| 8 |
+
|
| 9 |
+
```text
|
| 10 |
+
"""Independent CPU-only audit of frozen final evidence; reconstructs saved actions only."""
|
| 11 |
+
import hashlib
|
| 12 |
+
import importlib.util
|
| 13 |
+
import json
|
| 14 |
+
import math
|
| 15 |
+
import random
|
| 16 |
+
import statistics
|
| 17 |
+
import time
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
from types import SimpleNamespace
|
| 20 |
+
|
| 21 |
+
ROOT = Path.cwd()
|
| 22 |
+
OUT = ROOT / 'runs/final-review/audit.json'
|
| 23 |
+
assert not OUT.exists(), 'Refuse to overwrite audit'
|
| 24 |
+
start = time.monotonic()
|
| 25 |
+
def read(p):
|
| 26 |
+
return json.loads(Path(p).read_text())
|
| 27 |
+
def sha(p):
|
| 28 |
+
h = hashlib.sha256()
|
| 29 |
+
with Path(p).open('rb') as f:
|
| 30 |
+
while chunk := f.read(1048576):
|
| 31 |
+
h.update(chunk)
|
| 32 |
+
return h.hexdigest()
|
| 33 |
+
def module(name):
|
| 34 |
+
spec = importlib.util.spec_from_file_location(name, ROOT / 'scripts' / name)
|
| 35 |
+
obj = importlib.util.module_from_spec(spec)
|
| 36 |
+
spec.loader.exec_module(obj)
|
| 37 |
+
return obj
|
| 38 |
+
|
| 39 |
+
evaluator = module('evaluate_clef.py')
|
| 40 |
+
exporter = module('export_demo.py')
|
| 41 |
+
request = read('runs/final-evaluation/request.json')
|
| 42 |
+
report_path = Path('runs/final-evaluation/report.json')
|
| 43 |
+
report = read(report_path)
|
| 44 |
+
print('Loaded final report; validating frozen identities.', flush=True)
|
| 45 |
+
players = ['base', 'base-fp32', 'trained', 'random', 'heuristic']
|
| 46 |
+
seeds = list(range(30000, 30200))
|
| 47 |
+
assert request['players'] == players
|
| 48 |
+
assert set(report['players']) == set(players)
|
| 49 |
+
for key, expected in [('mode', 'tournament'), ('final_test', True), ('max_pieces', 200), ('seeds', seeds)]:
|
| 50 |
+
assert report[key] == request[key] == expected, key
|
| 51 |
+
for file, expected in request['source_hashes'].items():
|
| 52 |
+
assert sha(file) == expected, file
|
| 53 |
+
assert report['provenance']['source_hashes'] == request['source_hashes']
|
| 54 |
+
assert report['provenance']['installed_versions'] == request['installed_versions']
|
| 55 |
+
checkpoint = Path(request['checkpoint'])
|
| 56 |
+
checkpoint_hashes = evaluator.checkpoint_hashes(checkpoint)
|
| 57 |
+
assert checkpoint_hashes == request['checkpoint_sha256'] == report['checkpoint_sha256']
|
| 58 |
+
selection_path = Path(request['selection_file'])
|
| 59 |
+
assert sha(selection_path) == request['selection_sha256']
|
| 60 |
+
assert read(selection_path) == report['selection']
|
| 61 |
+
args = SimpleNamespace(seeds=tuple(seeds), final_test=True, max_pieces=200, players=players,
|
| 62 |
+
selection_file=selection_path, checkpoint=checkpoint, max_length=4096)
|
| 63 |
+
evaluator.validate_selection(args, checkpoint_hashes)
|
| 64 |
+
evaluator.validate_neural_runtimes(report['players'])
|
| 65 |
+
episodes = report['episodes']
|
| 66 |
+
assert len(episodes) == 1000
|
| 67 |
+
indexed = {(e['player_id'], e['seed']): e for e in episodes}
|
| 68 |
+
assert len(indexed) == 1000
|
| 69 |
+
assert set(indexed) == {(p, s) for p in players for s in seeds}
|
| 70 |
+
metrics = ('lines', 'score', 'pieces')
|
| 71 |
+
summaries = {}
|
| 72 |
+
episode_hashes = {}
|
| 73 |
+
def summary(values):
|
| 74 |
+
if not values:
|
| 75 |
+
return dict(count=0, mean=None, median=None, p95=None)
|
| 76 |
+
return dict(count=len(values), mean=statistics.mean(values), median=statistics.median(values),
|
| 77 |
+
p95=sorted(values)[math.ceil(.95 * len(values)) - 1])
|
| 78 |
+
for player in players:
|
| 79 |
+
metadata = read(Path('runs/final-evaluation') / player / 'player.json')
|
| 80 |
+
assert metadata == report['players'][player]
|
| 81 |
+
rows = []
|
| 82 |
+
for seed in seeds:
|
| 83 |
+
path = Path('runs/final-evaluation') / player / f'seed-{seed}.json'
|
| 84 |
+
episode = read(path)
|
| 85 |
+
assert episode == indexed[player, seed], str(path)
|
| 86 |
+
assert episode['player']['revision'] == metadata['revision']
|
| 87 |
+
assert episode['player']['runtime_config'] == metadata['runtime_config']
|
| 88 |
+
assert episode['max_pieces'] == 200
|
| 89 |
+
exporter.validated_player(episode, 0)
|
| 90 |
+
if player in ('base', 'base-fp32', 'trained'):
|
| 91 |
+
assert all(d['probabilities'] and 0 < d['input_tokens'] <= 4096 for d in episode['decisions'])
|
| 92 |
+
episode_hashes[str(path)] = sha(path)
|
| 93 |
+
rows.append(episode)
|
| 94 |
+
primary = {m: summary([0 if e['errors'] else e['outcome'][m] for e in rows]) for m in metrics}
|
| 95 |
+
observed = {m: summary([e['outcome'][m] for e in rows]) for m in metrics}
|
| 96 |
+
failed = [e['seed'] for e in rows if e['errors']]
|
| 97 |
+
cap_count = sum(e['outcome']['cap_hit'] for e in rows)
|
| 98 |
+
events = [d for e in rows for d in e['decisions']]
|
| 99 |
+
computed = dict(episodes=200, failed_episodes=len(failed), error_rate=len(failed)/200,
|
| 100 |
+
invalid_decisions=sum(er['kind']=='invalid_decision' for e in rows for er in e['errors']),
|
| 101 |
+
cap_hit_rate=cap_count/200, failure_adjusted=primary, observed_before_error=observed,
|
| 102 |
+
latency_seconds=summary([d['decision_seconds'] for d in events]),
|
| 103 |
+
input_tokens=summary([d['input_tokens'] for d in events if d.get('input_tokens') is not None]),
|
| 104 |
+
failed_seeds=failed)
|
| 105 |
+
for comparison in ('trained_vs_base', 'trained_vs_heuristic', 'trained_vs_base_fp32', 'base_fp32_vs_base'):
|
| 106 |
+
stored = report[comparison]['players'][player]
|
| 107 |
+
for key, value in computed.items():
|
| 108 |
+
assert stored[key] == value, (comparison, player, key)
|
| 109 |
+
summaries[player] = {**computed, 'cap_count': cap_count}
|
| 110 |
+
print(f'{player}: all 200 saved replays/observations/decisions verified; mean lines={primary["lines"]["mean"]}', flush=True)
|
| 111 |
+
comparisons = {}
|
| 112 |
+
for key, left, right in [('trained_vs_base','trained','base'), ('trained_vs_heuristic','trained','heuristic'),
|
| 113 |
+
('trained_vs_base_fp32','trained','base-fp32'), ('base_fp32_vs_base','base-fp32','base')]:
|
| 114 |
+
stored = report[key]
|
| 115 |
+
assert stored['trained_id'] == left and stored['base_id'] == right
|
| 116 |
+
assert stored['seeds'] == seeds and stored['matches_reserved_test_pool'] is True
|
| 117 |
+
assert stored['max_pieces'] == 200 and stored['selection_performed'] is False
|
| 118 |
+
assert stored['max_error_rate'] == 0
|
| 119 |
+
comparisons[key] = {}
|
| 120 |
+
for metric in metrics:
|
| 121 |
+
def score(p, seed):
|
| 122 |
+
e = indexed[p, seed]
|
| 123 |
+
return 0 if e['errors'] else e['outcome'][metric]
|
| 124 |
+
deltas = [score(left,s)-score(right,s) for s in seeds]
|
| 125 |
+
rng = random.Random(2026)
|
| 126 |
+
means = sorted(sum(rng.choice(deltas) for _ in seeds)/200 for _ in range(10000))
|
| 127 |
+
def percentile(q):
|
| 128 |
+
n = 9999*q
|
| 129 |
+
low = int(n)
|
| 130 |
+
return means[low] + (means[math.ceil(n)]-means[low])*(n-low)
|
| 131 |
+
expected = dict(episodes=200, mean_difference=statistics.mean(deltas), ci95_lower=percentile(.025),
|
| 132 |
+
ci95_upper=percentile(.975), method='paired episode percentile bootstrap',
|
| 133 |
+
bootstrap_samples=10000, bootstrap_seed=2026)
|
| 134 |
+
assert expected == stored['paired_trained_minus_base'][metric], (key, metric)
|
| 135 |
+
comparisons[key][metric] = expected
|
| 136 |
+
acceptable = not summaries[left]['failed_seeds'] and not summaries[right]['failed_seeds']
|
| 137 |
+
assert stored['errors_acceptable'] == acceptable
|
| 138 |
+
assert stored['positive_mean_lines_signal'] == (comparisons[key]['lines']['ci95_lower'] > 0 and acceptable)
|
| 139 |
+
print(f'{key}: all three paired metrics and bootstrap intervals independently match.', flush=True)
|
| 140 |
+
result = dict(status='passed', audit_script_sha256=sha(__file__), elapsed_seconds=time.monotonic()-start,
|
| 141 |
+
report_sha256=sha(report_path), request_sha256=sha('runs/final-evaluation/request.json'),
|
| 142 |
+
selection_sha256=sha(selection_path), checkpoint_sha256=checkpoint_hashes,
|
| 143 |
+
source_hashes=request['source_hashes'], episode_file_sha256=episode_hashes,
|
| 144 |
+
episodes_verified=1000, summaries=summaries, comparisons=comparisons,
|
| 145 |
+
method='Saved replay reconstruction; independent statistics and random.choice paired bootstrap. No new policy decisions/model loads.')
|
| 146 |
+
OUT.write_text(json.dumps(result, indent=2, allow_nan=False)+'\n')
|
| 147 |
+
print(json.dumps({k: result[k] for k in ('status','elapsed_seconds','report_sha256','selection_sha256','episodes_verified')}), flush=True)
|
| 148 |
+
|
| 149 |
+
```
|
code/reports/final-audit.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
code/reports/final-study.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stackcraft: completed Clef fine-tuning study
|
| 2 |
+
|
| 3 |
+
Fine-tuning improved Clef's play in this small, turn-based block game, but a fixed
|
| 4 |
+
arithmetic heuristic remained much stronger and cheaper. The selected model
|
| 5 |
+
cleared **16.81 lines per game**, compared with **0.07** for the unchanged native
|
| 6 |
+
model and **76.53** for the heuristic. The paired trained-minus-native improvement
|
| 7 |
+
was **16.74 lines, 95% bootstrap interval [15.74, 17.77]**.
|
| 8 |
+
|
| 9 |
+
These are results from all **200 held-out piece sequences**, seeds 30000–30199,
|
| 10 |
+
with a 200-piece cap for each of five players: 1,000 completed episodes. There
|
| 11 |
+
were **zero inference errors and zero invalid decisions**. Training, selection,
|
| 12 |
+
reload verification, final evaluation and an independent computational audit are
|
| 13 |
+
complete. Publication and fresh-download verification are tracked separately in
|
| 14 |
+
the [source repository](https://github.com/kkarimi/stackcraft).
|
| 15 |
+
|
| 16 |
+
The [compact machine-readable report](final-summary.json) contains full-precision
|
| 17 |
+
summaries, all paired intervals, runtime configuration and evidence hashes.
|
| 18 |
+
|
| 19 |
+
## What was tested
|
| 20 |
+
|
| 21 |
+
Stackcraft uses a 10×20 board, seven tetrominoes and deterministic seven-bag piece
|
| 22 |
+
sequences. A turn selects a legal rotation-and-column placement that drops
|
| 23 |
+
vertically. There is no hold, wall kick, tuck, T-spin scoring or real-time movement
|
| 24 |
+
deadline. Scores are 100/300/500/800 for clearing one/two/three/four lines in a move.
|
| 25 |
+
Each player receives the board, current piece, one next-piece preview and the same
|
| 26 |
+
legal options. Seed, sequence index and future random state are excluded from the
|
| 27 |
+
policy observation. This tests decisions over structured state, not visual play.
|
| 28 |
+
|
| 29 |
+
The neural players use `Cloudflare/clef-flash`, pinned to revision
|
| 30 |
+
`17f0b0ad64efb65d273590632833508766b2aae6`, with the same full observation encoding,
|
| 31 |
+
option order, BF16 backbone and 4,096-token limit. The three neural conditions are:
|
| 32 |
+
|
| 33 |
+
- **Native base:** unchanged weights and original BF16 decision head.
|
| 34 |
+
- **FP32 base:** unchanged weights, using the FP32 head wrapper also used in training.
|
| 35 |
+
- **Trained:** selected rank-4 LoRA adapters plus the learned FP32 native decision head.
|
| 36 |
+
|
| 37 |
+
**Random** chooses uniformly among legal options using its own recorded RNG.
|
| 38 |
+
**Heuristic** greedily scores each resulting board with weights frozen before final evaluation:
|
| 39 |
+
`−aggregate height − 4 × holes − bumpiness + 8 × cleared lines`. It does not use
|
| 40 |
+
the available preview. The lookahead teacher used for labeling is distinct from
|
| 41 |
+
this greedy heuristic and was not one of the five final tournament players.
|
| 42 |
+
|
| 43 |
+
## Held-out game outcomes
|
| 44 |
+
|
| 45 |
+
Entries show **mean / median** across all 200 episodes for each player. Survival
|
| 46 |
+
means pieces placed. A cap hit means survival of *at least* 200 pieces, not death
|
| 47 |
+
at piece 200.
|
| 48 |
+
|
| 49 |
+
| Player | Lines | Score | Pieces survived | Cap hits | Errors / invalid decisions |
|
| 50 |
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
| 51 |
+
| Native base | 0.070 / 0 | 7.0 / 0 | 26.020 / 26 | 0/200 | 0 / 0 |
|
| 52 |
+
| FP32 base | 0.105 / 0 | 10.5 / 0 | 26.000 / 26 | 0/200 | 0 / 0 |
|
| 53 |
+
| Trained | 16.810 / 16 | 1745.5 / 1650 | 83.565 / 82 | 0/200 | 0 / 0 |
|
| 54 |
+
| Random | 0.140 / 0 | 14.0 / 0 | 26.645 / 27 | 0/200 | 0 / 0 |
|
| 55 |
+
| Heuristic | 76.530 / 77 | 8306.5 / 8300 | 200.000 / 200 | 200/200 | 0 / 0 |
|
| 56 |
+
|
| 57 |
+
No failures were dropped. The frozen analysis assigns zero lines, score and pieces
|
| 58 |
+
to any errored episode and preserves the observed partial outcome separately.
|
| 59 |
+
Because none failed, failure-adjusted and observed summaries coincide here.
|
| 60 |
+
|
| 61 |
+
For each comparison, subtract the second player's outcome from the first on the
|
| 62 |
+
same seed. Resample the 200 paired differences with replacement 10,000 times,
|
| 63 |
+
using bootstrap seed 2026, and take the percentile 95% interval. Resampling whole
|
| 64 |
+
episodes preserves dependence among moves within a game.
|
| 65 |
+
|
| 66 |
+
| Comparison | Mean line difference | Paired 95% interval |
|
| 67 |
+
| --- | ---: | ---: |
|
| 68 |
+
| Trained − native base (primary) | +16.740 | [15.740, 17.770] |
|
| 69 |
+
| Trained − FP32 base | +16.705 | [15.705, 17.740] |
|
| 70 |
+
| Trained − heuristic | −59.720 | [−60.805, −58.619875] |
|
| 71 |
+
| FP32 base − native base | +0.035 | [−0.020, 0.090] |
|
| 72 |
+
|
| 73 |
+
The trained gain persists against the unchanged FP32 head. Head precision alone
|
| 74 |
+
does not explain that gain in this experiment. The FP32-versus-native interval
|
| 75 |
+
includes zero. This is not an ablation separating the effects of learned LoRA
|
| 76 |
+
from learned head weights: both were trained together.
|
| 77 |
+
|
| 78 |
+
The primary trained-minus-native score difference was +1738.5 [1633.0, 1848.5],
|
| 79 |
+
and survival difference was +57.545 pieces [54.930, 60.210]. All twelve intervals
|
| 80 |
+
(lines, score and pieces for all four comparisons) are preserved in
|
| 81 |
+
[final-summary.json](final-summary.json). Lines are the primary endpoint; score
|
| 82 |
+
and survival intervals are descriptive secondary results, without a multiplicity
|
| 83 |
+
correction. These intervals describe episode variability for one selected
|
| 84 |
+
checkpoint, not variability across training runs.
|
| 85 |
+
|
| 86 |
+
## Data, training and checkpoint selection
|
| 87 |
+
|
| 88 |
+
The disclosed synthetic dataset contains **827 training positions and 215
|
| 89 |
+
validation positions**. Separate episode seed pools supplied mixed random,
|
| 90 |
+
heuristic and lookahead-expert trajectories, sampled for at most 40 moves per
|
| 91 |
+
episode. Occupancy-normalized deduplication removed six within-training duplicates
|
| 92 |
+
and two cross-split duplicates. The teacher enumerates placements of the current
|
| 93 |
+
piece and the single visible preview, scoring line clears and the resulting board;
|
| 94 |
+
it cannot inspect unseen pieces. Its labels are bounded-search recommendations,
|
| 95 |
+
not proven optimal moves. See the [dataset manifest](dataset-manifest.json) and
|
| 96 |
+
[Tutorial 03](../docs/tutorials/03-data.md).
|
| 97 |
+
|
| 98 |
+
One seed-42 training run produced exactly two preregistered epoch candidates.
|
| 99 |
+
Training used BF16 frozen backbone weights without quantization, rank-4/alpha-8
|
| 100 |
+
LoRA with zero dropout, and the full FP32 native joint head: 132,582,404 trainable
|
| 101 |
+
parameters. The fixed loss was cross-entropy with 0.05 label smoothing plus Brier
|
| 102 |
+
loss weighted 0.1. AdamW used learning rate 1e-5 and weight decay 0.01, with batch
|
| 103 |
+
size one, accumulation eight and gradient clipping at 1.0. The final accumulation
|
| 104 |
+
group was normalized by its actual three examples. Each epoch processed all 827
|
| 105 |
+
training positions in 104 optimizer steps. Frozen parameter hashes stayed unchanged.
|
| 106 |
+
|
| 107 |
+
Selection used the lowest finite mean target negative log likelihood (NLL) on
|
| 108 |
+
**all 215 validation positions**, with exact ties assigned to the earlier epoch.
|
| 109 |
+
Candidates with errors or incomplete probabilities were ineligible. Both were
|
| 110 |
+
eligible; epoch 02 won. Test games did not choose the checkpoint.
|
| 111 |
+
|
| 112 |
+
| Validation condition | Teacher agreement | Mean target NLL | Mean Brier |
|
| 113 |
+
| --- | ---: | ---: | ---: |
|
| 114 |
+
| Native base | 21/215 (9.77%) | 2.952702 | 0.923173 |
|
| 115 |
+
| FP32 base | 22/215 (10.23%) | 2.952393 | 0.923102 |
|
| 116 |
+
| Epoch 01 | 89/215 (41.40%) | 1.860870 | 0.722190 |
|
| 117 |
+
| Epoch 02, selected | 92/215 (42.79%) | 1.775923 | 0.708980 |
|
| 118 |
+
|
| 119 |
+
**42.79% teacher agreement is not 42.79% game quality.** It measures agreement
|
| 120 |
+
with one chosen teacher action, including its deterministic tie rule. Small errors
|
| 121 |
+
change subsequent states, and imitation can fail on boards absent from training.
|
| 122 |
+
Complete games provide a separate behavioral measurement.
|
| 123 |
+
|
| 124 |
+
The selected checkpoint reloaded in a fresh process with **0.0 maximum absolute
|
| 125 |
+
probability difference** on four fixed training-position references, below the
|
| 126 |
+
1e-4 tolerance. This checks those reference distributions, not all possible boards
|
| 127 |
+
or downloaded publication artifacts. Full details are in the
|
| 128 |
+
[training](study-training.json), [validation](validation-summary.json) and
|
| 129 |
+
[reload](selected-checkpoint-reload.json) reports. The training report's status
|
| 130 |
+
reflects its creation before external validation; later evidence completes that
|
| 131 |
+
stage. The [archived preregistration](study-preregistration.md) preserves the
|
| 132 |
+
pre-test choices; it is a local source archive, not a signed external registration.
|
| 133 |
+
|
| 134 |
+
## Runtime and practical cost
|
| 135 |
+
|
| 136 |
+
The workstation used an NVIDIA RTX 5090 (reported 32,607 MiB), AMD Ryzen 9 9950X3D,
|
| 137 |
+
eight Torch CPU threads and about 123 GiB host RAM. Runtime was Python 3.13.16,
|
| 138 |
+
Torch 2.14.1+cu130, CUDA 13.0, Transformers 5.18.0 and PEFT 0.21.2. TF32 was disabled.
|
| 139 |
+
The [hardware snapshot](evaluation-hardware.json) records the remaining details.
|
| 140 |
+
|
| 141 |
+
| Player | Mean decision ms | Median ms | p95 ms | Decisions | Local model initialization s |
|
| 142 |
+
| --- | ---: | ---: | ---: | ---: | ---: |
|
| 143 |
+
| Native base | 149.110 | 139.381 | 244.916 | 5,204 | 3.866 |
|
| 144 |
+
| FP32 base | 150.188 | 140.968 | 247.880 | 5,200 | 2.176 |
|
| 145 |
+
| Trained | 206.814 | 171.844 | 309.000 | 16,713 | 2.528 |
|
| 146 |
+
| Random | 0.001497 | 0.001250 | 0.002879 | 5,329 | 0.000035 |
|
| 147 |
+
| Heuristic | 0.198510 | 0.139489 | 0.280528 | 40,000 | 0.000002 |
|
| 148 |
+
|
| 149 |
+
Latencies cover all `Player.choose` calls, including encoding and probability
|
| 150 |
+
transfer, with no excluded warmup. They exclude rendering, replay serialization
|
| 151 |
+
and model loading. Initialization was measured separately with weights already
|
| 152 |
+
cached locally; these are not download or reliably disk-cold timings. The neural
|
| 153 |
+
players' mean input lengths were 1,354.30, 1,349.73 and 1,532.11 tokens respectively
|
| 154 |
+
(native, FP32, trained); the trained p95 was 2,259 tokens.
|
| 155 |
+
|
| 156 |
+
The trained model took about **1,042 times** the heuristic's mean decision time
|
| 157 |
+
while clearing far fewer lines. This is an observed workload comparison: neural
|
| 158 |
+
players use the GPU, the heuristic uses the CPU, and policies visit different
|
| 159 |
+
boards. It is not a controlled same-state kernel benchmark. For a practical game
|
| 160 |
+
bot under these rules, the heuristic is the clear choice. The fine-tuning result
|
| 161 |
+
is useful as a reproducible learning experiment, not a reason to replace the
|
| 162 |
+
heuristic in production.
|
| 163 |
+
|
| 164 |
+
Training took **1,511.25 seconds (25.2 minutes)** including checkpoint and hash
|
| 165 |
+
work; peak CUDA allocation was **23.23 GB (21.64 GiB)** and reservation 24.18 GB.
|
| 166 |
+
The final evaluation wrapper took **5,103.11 seconds (85.1 minutes)** including
|
| 167 |
+
loading, reporting and the local service stop/restore health checks. This was
|
| 168 |
+
within the pre-test multi-hour budget. These durations exclude earlier development,
|
| 169 |
+
validation and dataset generation. No energy consumption or monetary cost was
|
| 170 |
+
measured; the GPU's configured 575 W ceiling is not an energy-use measurement.
|
| 171 |
+
|
| 172 |
+
## A fixed replay illustration
|
| 173 |
+
|
| 174 |
+
Seed **30000**, the first reserved seed and the declared demo sequence, provides
|
| 175 |
+
an exploratory illustration. These values come from saved replays; no additional
|
| 176 |
+
games were generated for this description.
|
| 177 |
+
|
| 178 |
+
| Player | Lines | Pieces | Final maximum height | Final holes |
|
| 179 |
+
| --- | ---: | ---: | ---: | ---: |
|
| 180 |
+
| Native base | 0 | 23 | 20 | 86 |
|
| 181 |
+
| FP32 base | 0 | 26 | 20 | 79 |
|
| 182 |
+
| Trained | 16 | 82 | 20 | 28 |
|
| 183 |
+
| Random | 0 | 24 | 20 | 88 |
|
| 184 |
+
| Heuristic | 78 | 200 (cap) | 3 | 1 |
|
| 185 |
+
|
| 186 |
+
A hole is an empty cell below an occupied cell in its column. These are different
|
| 187 |
+
endpoints: the heuristic is still alive at the cap, whereas the other players
|
| 188 |
+
have topped out. The trained player clears lines but eventually reaches the top.
|
| 189 |
+
The table describes this replay; it does not establish why the model fails or
|
| 190 |
+
prove a causal relationship between final holes and the aggregate result.
|
| 191 |
+
|
| 192 |
+
## Reproducibility and remaining limits
|
| 193 |
+
|
| 194 |
+
The independently implemented CPU audit checked all 1,000 persisted episodes:
|
| 195 |
+
replayed states, observations, choices, outcomes, source/checkpoint/selection
|
| 196 |
+
bindings and aggregate statistics. It independently recomputed all twelve paired
|
| 197 |
+
intervals and matched the report. The [audit results](final-audit.json) and
|
| 198 |
+
[archived audit source](final-audit-source.md) preserve that check; its script hash
|
| 199 |
+
and compact outcome are also included in the summary. This is an independent code-path check by another agent,
|
| 200 |
+
not an external scientific replication.
|
| 201 |
+
|
| 202 |
+
The full evaluation JSON is identified by SHA-256
|
| 203 |
+
`5585574b77e81c8d7dd44464c4dfb09f9511ccec0d9150c3ea44793760416857`.
|
| 204 |
+
The frozen selection SHA-256 is
|
| 205 |
+
`025b62587a26bd8cc78c80f362524b8c178190df5188b317299121afa6b1203b`.
|
| 206 |
+
Dataset manifest, checkpoint files, evaluation source files and supporting report
|
| 207 |
+
hashes are all included in [final-summary.json](final-summary.json).
|
| 208 |
+
|
| 209 |
+
The prepared model-release layout contains `code/` with these reports, source,
|
| 210 |
+
locked environment, tests and tutorials; `evidence.zip` losslessly preserves
|
| 211 |
+
`evidence/evaluation/` with the full
|
| 212 |
+
report, request and per-player episode records; and `evidence/selection/` with
|
| 213 |
+
the frozen choice and both candidates' validation evidence. Extract the ZIP from
|
| 214 |
+
the model repository root to restore these exact paths; `evidence-files.json`
|
| 215 |
+
records their unchanged uncompressed hashes and sizes. These portable paths
|
| 216 |
+
are relative to the model repository root and do not require access to private Git
|
| 217 |
+
history. See the [model release](https://huggingface.co/nima1/stackcraft-clef-flash-lora),
|
| 218 |
+
[dataset](https://huggingface.co/datasets/nima1/stackcraft-data), and
|
| 219 |
+
[CPU demo](https://huggingface.co/spaces/nima1/stackcraft).
|
| 220 |
+
[Tutorial 05](../docs/tutorials/05-evaluation.md) gives the reproduction procedure.
|
| 221 |
+
|
| 222 |
+
The principal limitations are one training seed, a small synthetic dataset, an
|
| 223 |
+
approximate teacher, a single structured encoding and simplified rules. Training
|
| 224 |
+
positions cover shorter trajectories than the 200-piece test horizon. All 200
|
| 225 |
+
heuristic games reached the cap, so their uncapped longevity is unknown. The weak
|
| 226 |
+
unchanged-model results characterize this pinned model and encoding, not every
|
| 227 |
+
possible way to use Clef. No model, prompt, dataset, policy or checkpoint was tuned
|
| 228 |
+
after opening this final test. Future improvements require a new declared study
|
| 229 |
+
and fresh held-out sequences; these results remain the record of this experiment.
|
code/reports/final-summary.json
ADDED
|
@@ -0,0 +1,1297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"checkpoint_sha256": {
|
| 3 |
+
"adapter/README.md": "bf1fa63446e8ee38c5ac154ade9138a0e2354e3215201f59473102eeb5206fa0",
|
| 4 |
+
"adapter/adapter_config.json": "bbbce7f19e186b4a79c39d03786bb190d6084e7990a601e8e28672cf1c969741",
|
| 5 |
+
"adapter/adapter_model.safetensors": "3ed169fe68b20a370bb856f9d91842543374ff3c98fd3bbe139ef2d0b97877a3",
|
| 6 |
+
"joint_head.safetensors": "4f21c0f315cf1bcf852a9887d158928855eaa9bdcab6b9770d518abf3727fa1b",
|
| 7 |
+
"reference.json": "adacc63684d28598f8016602bab790cd3967317986b6934dd94a5e14fabe571f",
|
| 8 |
+
"training_config.json": "3ead31d0a3ccb6a62914c3853ee26332f6412382c5196e40b461182e64d8b329"
|
| 9 |
+
},
|
| 10 |
+
"comparisons": {
|
| 11 |
+
"base_fp32_vs_base": {
|
| 12 |
+
"base_id": "base",
|
| 13 |
+
"errors_acceptable": true,
|
| 14 |
+
"paired_trained_minus_base": {
|
| 15 |
+
"lines": {
|
| 16 |
+
"bootstrap_samples": 10000,
|
| 17 |
+
"bootstrap_seed": 2026,
|
| 18 |
+
"ci95_lower": -0.02,
|
| 19 |
+
"ci95_upper": 0.09,
|
| 20 |
+
"episodes": 200,
|
| 21 |
+
"mean_difference": 0.035,
|
| 22 |
+
"method": "paired episode percentile bootstrap"
|
| 23 |
+
},
|
| 24 |
+
"pieces": {
|
| 25 |
+
"bootstrap_samples": 10000,
|
| 26 |
+
"bootstrap_seed": 2026,
|
| 27 |
+
"ci95_lower": -0.405,
|
| 28 |
+
"ci95_upper": 0.36,
|
| 29 |
+
"episodes": 200,
|
| 30 |
+
"mean_difference": -0.02,
|
| 31 |
+
"method": "paired episode percentile bootstrap"
|
| 32 |
+
},
|
| 33 |
+
"score": {
|
| 34 |
+
"bootstrap_samples": 10000,
|
| 35 |
+
"bootstrap_seed": 2026,
|
| 36 |
+
"ci95_lower": -2.0,
|
| 37 |
+
"ci95_upper": 9.0,
|
| 38 |
+
"episodes": 200,
|
| 39 |
+
"mean_difference": 3.5,
|
| 40 |
+
"method": "paired episode percentile bootstrap"
|
| 41 |
+
}
|
| 42 |
+
},
|
| 43 |
+
"positive_mean_lines_signal": false,
|
| 44 |
+
"trained_id": "base-fp32"
|
| 45 |
+
},
|
| 46 |
+
"trained_vs_base": {
|
| 47 |
+
"base_id": "base",
|
| 48 |
+
"errors_acceptable": true,
|
| 49 |
+
"paired_trained_minus_base": {
|
| 50 |
+
"lines": {
|
| 51 |
+
"bootstrap_samples": 10000,
|
| 52 |
+
"bootstrap_seed": 2026,
|
| 53 |
+
"ci95_lower": 15.74,
|
| 54 |
+
"ci95_upper": 17.77,
|
| 55 |
+
"episodes": 200,
|
| 56 |
+
"mean_difference": 16.74,
|
| 57 |
+
"method": "paired episode percentile bootstrap"
|
| 58 |
+
},
|
| 59 |
+
"pieces": {
|
| 60 |
+
"bootstrap_samples": 10000,
|
| 61 |
+
"bootstrap_seed": 2026,
|
| 62 |
+
"ci95_lower": 54.93,
|
| 63 |
+
"ci95_upper": 60.21,
|
| 64 |
+
"episodes": 200,
|
| 65 |
+
"mean_difference": 57.545,
|
| 66 |
+
"method": "paired episode percentile bootstrap"
|
| 67 |
+
},
|
| 68 |
+
"score": {
|
| 69 |
+
"bootstrap_samples": 10000,
|
| 70 |
+
"bootstrap_seed": 2026,
|
| 71 |
+
"ci95_lower": 1633.0,
|
| 72 |
+
"ci95_upper": 1848.5,
|
| 73 |
+
"episodes": 200,
|
| 74 |
+
"mean_difference": 1738.5,
|
| 75 |
+
"method": "paired episode percentile bootstrap"
|
| 76 |
+
}
|
| 77 |
+
},
|
| 78 |
+
"positive_mean_lines_signal": true,
|
| 79 |
+
"trained_id": "trained"
|
| 80 |
+
},
|
| 81 |
+
"trained_vs_base_fp32": {
|
| 82 |
+
"base_id": "base-fp32",
|
| 83 |
+
"errors_acceptable": true,
|
| 84 |
+
"paired_trained_minus_base": {
|
| 85 |
+
"lines": {
|
| 86 |
+
"bootstrap_samples": 10000,
|
| 87 |
+
"bootstrap_seed": 2026,
|
| 88 |
+
"ci95_lower": 15.705,
|
| 89 |
+
"ci95_upper": 17.74,
|
| 90 |
+
"episodes": 200,
|
| 91 |
+
"mean_difference": 16.705,
|
| 92 |
+
"method": "paired episode percentile bootstrap"
|
| 93 |
+
},
|
| 94 |
+
"pieces": {
|
| 95 |
+
"bootstrap_samples": 10000,
|
| 96 |
+
"bootstrap_seed": 2026,
|
| 97 |
+
"ci95_lower": 55.01,
|
| 98 |
+
"ci95_upper": 60.185,
|
| 99 |
+
"episodes": 200,
|
| 100 |
+
"mean_difference": 57.565,
|
| 101 |
+
"method": "paired episode percentile bootstrap"
|
| 102 |
+
},
|
| 103 |
+
"score": {
|
| 104 |
+
"bootstrap_samples": 10000,
|
| 105 |
+
"bootstrap_seed": 2026,
|
| 106 |
+
"ci95_lower": 1629.4875,
|
| 107 |
+
"ci95_upper": 1845.0,
|
| 108 |
+
"episodes": 200,
|
| 109 |
+
"mean_difference": 1735,
|
| 110 |
+
"method": "paired episode percentile bootstrap"
|
| 111 |
+
}
|
| 112 |
+
},
|
| 113 |
+
"positive_mean_lines_signal": true,
|
| 114 |
+
"trained_id": "trained"
|
| 115 |
+
},
|
| 116 |
+
"trained_vs_heuristic": {
|
| 117 |
+
"base_id": "heuristic",
|
| 118 |
+
"errors_acceptable": true,
|
| 119 |
+
"paired_trained_minus_base": {
|
| 120 |
+
"lines": {
|
| 121 |
+
"bootstrap_samples": 10000,
|
| 122 |
+
"bootstrap_seed": 2026,
|
| 123 |
+
"ci95_lower": -60.805,
|
| 124 |
+
"ci95_upper": -58.619875,
|
| 125 |
+
"episodes": 200,
|
| 126 |
+
"mean_difference": -59.72,
|
| 127 |
+
"method": "paired episode percentile bootstrap"
|
| 128 |
+
},
|
| 129 |
+
"pieces": {
|
| 130 |
+
"bootstrap_samples": 10000,
|
| 131 |
+
"bootstrap_seed": 2026,
|
| 132 |
+
"ci95_lower": -118.925125,
|
| 133 |
+
"ci95_upper": -113.86987500000001,
|
| 134 |
+
"episodes": 200,
|
| 135 |
+
"mean_difference": -116.435,
|
| 136 |
+
"method": "paired episode percentile bootstrap"
|
| 137 |
+
},
|
| 138 |
+
"score": {
|
| 139 |
+
"bootstrap_samples": 10000,
|
| 140 |
+
"bootstrap_seed": 2026,
|
| 141 |
+
"ci95_lower": -6681.5125,
|
| 142 |
+
"ci95_upper": -6436.0,
|
| 143 |
+
"episodes": 200,
|
| 144 |
+
"mean_difference": -6561,
|
| 145 |
+
"method": "paired episode percentile bootstrap"
|
| 146 |
+
}
|
| 147 |
+
},
|
| 148 |
+
"positive_mean_lines_signal": false,
|
| 149 |
+
"trained_id": "trained"
|
| 150 |
+
}
|
| 151 |
+
},
|
| 152 |
+
"conclusion": "Selected training checkpoint improves mean lines over both unchanged Clef references; fixed heuristic is much stronger and cheaper.",
|
| 153 |
+
"evidence": {
|
| 154 |
+
"audit_source": {
|
| 155 |
+
"release_path": "code/reports/final-audit-source.md",
|
| 156 |
+
"sha256": "61db8b121130ffd2eeb0a9c667f53f0c21647e865e93d44fbb9967e71d870020",
|
| 157 |
+
"size_bytes": 8166
|
| 158 |
+
},
|
| 159 |
+
"dataset": {
|
| 160 |
+
"release_path": "code/reports/dataset-manifest.json",
|
| 161 |
+
"sha256": "f591ec4c4b853942bd0a89331280b8c3491b3eb87f0b2a69ae4821b9623bd936",
|
| 162 |
+
"size_bytes": 8175
|
| 163 |
+
},
|
| 164 |
+
"evaluation": {
|
| 165 |
+
"release_path": "evidence/evaluation/report.json",
|
| 166 |
+
"sha256": "5585574b77e81c8d7dd44464c4dfb09f9511ccec0d9150c3ea44793760416857",
|
| 167 |
+
"size_bytes": 1225235921
|
| 168 |
+
},
|
| 169 |
+
"hardware": {
|
| 170 |
+
"release_path": "code/reports/evaluation-hardware.json",
|
| 171 |
+
"sha256": "cea05c0d6f208dbeae498be743d131f645cebe88a5054a8af5a884714582085b",
|
| 172 |
+
"size_bytes": 1606
|
| 173 |
+
},
|
| 174 |
+
"independent_audit": {
|
| 175 |
+
"release_path": "code/reports/final-audit.json",
|
| 176 |
+
"sha256": "4766d0224a8bddc8c6e6fe0e86e75937f65abdb261667d5d59bd3d482c8adb89",
|
| 177 |
+
"size_bytes": 133571
|
| 178 |
+
},
|
| 179 |
+
"preregistration": {
|
| 180 |
+
"release_path": "code/reports/study-preregistration.md",
|
| 181 |
+
"sha256": "bd6d34b4e71a2e38f4dcdc4a0b0a1aca7f7572e5d063af6b3e8491c6c6382d96",
|
| 182 |
+
"size_bytes": 4183
|
| 183 |
+
},
|
| 184 |
+
"reload": {
|
| 185 |
+
"release_path": "code/reports/selected-checkpoint-reload.json",
|
| 186 |
+
"sha256": "57489d34b22627c0fd0e3dc42f7f11231a901c059db7a4ea8cbab0aba2c37cdd",
|
| 187 |
+
"size_bytes": 630
|
| 188 |
+
},
|
| 189 |
+
"request": {
|
| 190 |
+
"release_path": "evidence/evaluation/request.json",
|
| 191 |
+
"sha256": "a507e3b947d603b02f56b6df12b160e9e182ee5706d2ac64d8e9f93358f79e62",
|
| 192 |
+
"size_bytes": 4699
|
| 193 |
+
},
|
| 194 |
+
"selection": {
|
| 195 |
+
"release_path": "evidence/selection/selection.json",
|
| 196 |
+
"sha256": "025b62587a26bd8cc78c80f362524b8c178190df5188b317299121afa6b1203b",
|
| 197 |
+
"size_bytes": 8037
|
| 198 |
+
},
|
| 199 |
+
"training": {
|
| 200 |
+
"release_path": "code/reports/study-training.json",
|
| 201 |
+
"sha256": "c272f9e1dedfedf285c0d7d1a46aa77a89d48019bf621e31a11921f38fc05600",
|
| 202 |
+
"size_bytes": 63791
|
| 203 |
+
},
|
| 204 |
+
"validation": {
|
| 205 |
+
"release_path": "code/reports/validation-summary.json",
|
| 206 |
+
"sha256": "2fd17daeeb12dfecf739c2dcd885e80ca3dc4547acaa3d22722ac9a4d747122a",
|
| 207 |
+
"size_bytes": 7990
|
| 208 |
+
}
|
| 209 |
+
},
|
| 210 |
+
"evidence_path_convention": "Paths are relative to the model release root; source-only copies retain compact reports under reports/. Publication is pending at report creation.",
|
| 211 |
+
"hardware": {
|
| 212 |
+
"cpu_model": "AMD Ryzen 9 9950X3D 16-Core Processor",
|
| 213 |
+
"evaluation_request_sha256": "a507e3b947d603b02f56b6df12b160e9e182ee5706d2ac64d8e9f93358f79e62",
|
| 214 |
+
"gpus": [
|
| 215 |
+
{
|
| 216 |
+
"configured_power_limit_watts": 575.0,
|
| 217 |
+
"driver_version": "610.57.04",
|
| 218 |
+
"name": "NVIDIA GeForce RTX 5090",
|
| 219 |
+
"reported_total_memory_mib": 32607
|
| 220 |
+
}
|
| 221 |
+
],
|
| 222 |
+
"host_reported_memory_kib": 129391464,
|
| 223 |
+
"kernel": "7.2.5-3-omarchy",
|
| 224 |
+
"logical_cpu_count": 32,
|
| 225 |
+
"observed_at_utc": "2026-10-05T21:20:14.982799+00:00",
|
| 226 |
+
"python_version": "3.13.16",
|
| 227 |
+
"schema_version": 1,
|
| 228 |
+
"scope": "Read-only hardware snapshot during final evaluation; no energy usage or continuous clock/temperature measurement",
|
| 229 |
+
"trained_runtime_config": {
|
| 230 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 231 |
+
"cpu_threads": 8,
|
| 232 |
+
"cuda_matmul_allow_tf32": false,
|
| 233 |
+
"cuda_version": "13.0",
|
| 234 |
+
"device": "cuda:0",
|
| 235 |
+
"dtype": "torch.bfloat16",
|
| 236 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 237 |
+
"evaluation_head": "fp32-gathered-rows",
|
| 238 |
+
"float32_matmul_precision": "highest",
|
| 239 |
+
"head_dtype": "float32",
|
| 240 |
+
"max_length": 4096,
|
| 241 |
+
"model_id": "Cloudflare/clef-flash",
|
| 242 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:checkpoint-sha256-b8133e9775a82c6b952fc1a1b1c805c5bc993fbcd919e81f663ad35e122ab7c7:stackcraft-clef-v1",
|
| 243 |
+
"source_sha256": "0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3",
|
| 244 |
+
"versions": {
|
| 245 |
+
"huggingface-hub": "1.33.0",
|
| 246 |
+
"peft": "0.21.2",
|
| 247 |
+
"safetensors": "0.8.0",
|
| 248 |
+
"torch": "2.14.1+cu130",
|
| 249 |
+
"transformers": "5.18.0"
|
| 250 |
+
}
|
| 251 |
+
}
|
| 252 |
+
},
|
| 253 |
+
"illustration": {
|
| 254 |
+
"players": {
|
| 255 |
+
"base": {
|
| 256 |
+
"evidence": {
|
| 257 |
+
"release_path": "evidence/evaluation/base/seed-30000.json",
|
| 258 |
+
"sha256": "429e52aa67bbdae6cd35f469af876b9b7872d61e4b862cb2ea47f40e7ef918d4",
|
| 259 |
+
"size_bytes": 325003
|
| 260 |
+
},
|
| 261 |
+
"final_board": {
|
| 262 |
+
"aggregate_height": 178,
|
| 263 |
+
"holes": 86,
|
| 264 |
+
"max_height": 20
|
| 265 |
+
},
|
| 266 |
+
"outcome": {
|
| 267 |
+
"cap_hit": false,
|
| 268 |
+
"lines": 0,
|
| 269 |
+
"pieces": 23,
|
| 270 |
+
"score": 0,
|
| 271 |
+
"status": "top_out",
|
| 272 |
+
"terminal": true
|
| 273 |
+
}
|
| 274 |
+
},
|
| 275 |
+
"base-fp32": {
|
| 276 |
+
"evidence": {
|
| 277 |
+
"release_path": "evidence/evaluation/base-fp32/seed-30000.json",
|
| 278 |
+
"sha256": "87744a7184c3ffbe1a4e7b8e5b526f628993de4135a551117de2c2283c105c34",
|
| 279 |
+
"size_bytes": 335159
|
| 280 |
+
},
|
| 281 |
+
"final_board": {
|
| 282 |
+
"aggregate_height": 183,
|
| 283 |
+
"holes": 79,
|
| 284 |
+
"max_height": 20
|
| 285 |
+
},
|
| 286 |
+
"outcome": {
|
| 287 |
+
"cap_hit": false,
|
| 288 |
+
"lines": 0,
|
| 289 |
+
"pieces": 26,
|
| 290 |
+
"score": 0,
|
| 291 |
+
"status": "top_out",
|
| 292 |
+
"terminal": true
|
| 293 |
+
}
|
| 294 |
+
},
|
| 295 |
+
"heuristic": {
|
| 296 |
+
"evidence": {
|
| 297 |
+
"release_path": "evidence/evaluation/heuristic/seed-30000.json",
|
| 298 |
+
"sha256": "5b7508b7a345998a7c677a5153779ca9d46bfd65ada64371c7117f54c6537c09",
|
| 299 |
+
"size_bytes": 2800853
|
| 300 |
+
},
|
| 301 |
+
"final_board": {
|
| 302 |
+
"aggregate_height": 21,
|
| 303 |
+
"holes": 1,
|
| 304 |
+
"max_height": 3
|
| 305 |
+
},
|
| 306 |
+
"outcome": {
|
| 307 |
+
"cap_hit": true,
|
| 308 |
+
"lines": 78,
|
| 309 |
+
"pieces": 200,
|
| 310 |
+
"score": 8800,
|
| 311 |
+
"status": "cap",
|
| 312 |
+
"terminal": false
|
| 313 |
+
}
|
| 314 |
+
},
|
| 315 |
+
"random": {
|
| 316 |
+
"evidence": {
|
| 317 |
+
"release_path": "evidence/evaluation/random/seed-30000.json",
|
| 318 |
+
"sha256": "f39dc524807bb0008a9bbe2cd004e152fdb939405d1e50879c81da2942c7a49e",
|
| 319 |
+
"size_bytes": 318599
|
| 320 |
+
},
|
| 321 |
+
"final_board": {
|
| 322 |
+
"aggregate_height": 184,
|
| 323 |
+
"holes": 88,
|
| 324 |
+
"max_height": 20
|
| 325 |
+
},
|
| 326 |
+
"outcome": {
|
| 327 |
+
"cap_hit": false,
|
| 328 |
+
"lines": 0,
|
| 329 |
+
"pieces": 24,
|
| 330 |
+
"score": 0,
|
| 331 |
+
"status": "top_out",
|
| 332 |
+
"terminal": true
|
| 333 |
+
}
|
| 334 |
+
},
|
| 335 |
+
"trained": {
|
| 336 |
+
"evidence": {
|
| 337 |
+
"release_path": "evidence/evaluation/trained/seed-30000.json",
|
| 338 |
+
"sha256": "d18bd7010da5c2d98ef02a73585a3cac0a47764a43a597c1d1b2d1280949890c",
|
| 339 |
+
"size_bytes": 1156045
|
| 340 |
+
},
|
| 341 |
+
"final_board": {
|
| 342 |
+
"aggregate_height": 196,
|
| 343 |
+
"holes": 28,
|
| 344 |
+
"max_height": 20
|
| 345 |
+
},
|
| 346 |
+
"outcome": {
|
| 347 |
+
"cap_hit": false,
|
| 348 |
+
"lines": 16,
|
| 349 |
+
"pieces": 82,
|
| 350 |
+
"score": 1600,
|
| 351 |
+
"status": "top_out",
|
| 352 |
+
"terminal": true
|
| 353 |
+
}
|
| 354 |
+
}
|
| 355 |
+
},
|
| 356 |
+
"scope": "Exploratory description of the preselected replay seed, not a causal explanation or extra experiment. Endpoints differ in turn and terminal status. Holes are empty cells below an occupied cell in their column.",
|
| 357 |
+
"seed": 30000
|
| 358 |
+
},
|
| 359 |
+
"independent_audit": {
|
| 360 |
+
"audit_script_sha256": "867eb41c8319c7c7b428a037b9c8a4f5a5e9c235a15631fae6716aed565626d1",
|
| 361 |
+
"elapsed_seconds": 63.74646008000127,
|
| 362 |
+
"episodes_verified": 1000,
|
| 363 |
+
"method": "Saved replay reconstruction; independent statistics and random.choice paired bootstrap. No new policy decisions/model loads.",
|
| 364 |
+
"report_sha256": "5585574b77e81c8d7dd44464c4dfb09f9511ccec0d9150c3ea44793760416857",
|
| 365 |
+
"request_sha256": "a507e3b947d603b02f56b6df12b160e9e182ee5706d2ac64d8e9f93358f79e62",
|
| 366 |
+
"selection_sha256": "025b62587a26bd8cc78c80f362524b8c178190df5188b317299121afa6b1203b",
|
| 367 |
+
"status": "passed"
|
| 368 |
+
},
|
| 369 |
+
"limitations": [
|
| 370 |
+
"One training seed; confidence intervals do not include training-seed variability.",
|
| 371 |
+
"827 synthetic training positions and 215 validation positions; teacher search is bounded and not guaranteed optimal.",
|
| 372 |
+
"Simplified deterministic turn-based rules, structured board input and one-piece preview, not real-time Tetris.",
|
| 373 |
+
"All heuristic episodes reach the cap; uncapped survival is unknown.",
|
| 374 |
+
"Teacher agreement is not a game-quality metric.",
|
| 375 |
+
"Different policy states and GPU/CPU execution make latency a workload measurement rather than a controlled same-input kernel benchmark.",
|
| 376 |
+
"No checkpoint, prompt, dataset or policy tuning after opening this final test. A future experiment needs new held-out seeds."
|
| 377 |
+
],
|
| 378 |
+
"players": {
|
| 379 |
+
"base": {
|
| 380 |
+
"cap_hit_rate": 0,
|
| 381 |
+
"cap_hits": 0,
|
| 382 |
+
"episodes": 200,
|
| 383 |
+
"error_rate": 0.0,
|
| 384 |
+
"failed_episodes": 0,
|
| 385 |
+
"failed_seeds": [],
|
| 386 |
+
"failure_adjusted": {
|
| 387 |
+
"lines": {
|
| 388 |
+
"count": 200,
|
| 389 |
+
"mean": 0.07,
|
| 390 |
+
"median": 0.0,
|
| 391 |
+
"p95": 1
|
| 392 |
+
},
|
| 393 |
+
"pieces": {
|
| 394 |
+
"count": 200,
|
| 395 |
+
"mean": 26.02,
|
| 396 |
+
"median": 26.0,
|
| 397 |
+
"p95": 30
|
| 398 |
+
},
|
| 399 |
+
"score": {
|
| 400 |
+
"count": 200,
|
| 401 |
+
"mean": 7,
|
| 402 |
+
"median": 0.0,
|
| 403 |
+
"p95": 100
|
| 404 |
+
}
|
| 405 |
+
},
|
| 406 |
+
"identity": {
|
| 407 |
+
"name": "base",
|
| 408 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:stackcraft-clef-v1",
|
| 409 |
+
"runtime_config": {
|
| 410 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 411 |
+
"cpu_threads": 8,
|
| 412 |
+
"cuda_matmul_allow_tf32": false,
|
| 413 |
+
"cuda_version": "13.0",
|
| 414 |
+
"device": "cuda:0",
|
| 415 |
+
"dtype": "torch.bfloat16",
|
| 416 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 417 |
+
"evaluation_head": "native",
|
| 418 |
+
"float32_matmul_precision": "highest",
|
| 419 |
+
"head_dtype": "bfloat16",
|
| 420 |
+
"max_length": 4096,
|
| 421 |
+
"model_id": "Cloudflare/clef-flash",
|
| 422 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:stackcraft-clef-v1",
|
| 423 |
+
"source_sha256": "0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3",
|
| 424 |
+
"versions": {
|
| 425 |
+
"huggingface-hub": "1.33.0",
|
| 426 |
+
"peft": "0.21.2",
|
| 427 |
+
"safetensors": "0.8.0",
|
| 428 |
+
"torch": "2.14.1+cu130",
|
| 429 |
+
"transformers": "5.18.0"
|
| 430 |
+
}
|
| 431 |
+
}
|
| 432 |
+
},
|
| 433 |
+
"input_tokens": {
|
| 434 |
+
"count": 5204,
|
| 435 |
+
"mean": 1354.2999615680246,
|
| 436 |
+
"median": 1286.0,
|
| 437 |
+
"p95": 2258
|
| 438 |
+
},
|
| 439 |
+
"invalid_decisions": 0,
|
| 440 |
+
"latency_seconds": {
|
| 441 |
+
"count": 5204,
|
| 442 |
+
"mean": 0.149109957484823,
|
| 443 |
+
"median": 0.1393812735186657,
|
| 444 |
+
"p95": 0.2449162660050206
|
| 445 |
+
},
|
| 446 |
+
"load_seconds": [
|
| 447 |
+
3.866232068015961
|
| 448 |
+
],
|
| 449 |
+
"observed_before_error": {
|
| 450 |
+
"lines": {
|
| 451 |
+
"count": 200,
|
| 452 |
+
"mean": 0.07,
|
| 453 |
+
"median": 0.0,
|
| 454 |
+
"p95": 1
|
| 455 |
+
},
|
| 456 |
+
"pieces": {
|
| 457 |
+
"count": 200,
|
| 458 |
+
"mean": 26.02,
|
| 459 |
+
"median": 26.0,
|
| 460 |
+
"p95": 30
|
| 461 |
+
},
|
| 462 |
+
"score": {
|
| 463 |
+
"count": 200,
|
| 464 |
+
"mean": 7,
|
| 465 |
+
"median": 0.0,
|
| 466 |
+
"p95": 100
|
| 467 |
+
}
|
| 468 |
+
}
|
| 469 |
+
},
|
| 470 |
+
"base-fp32": {
|
| 471 |
+
"cap_hit_rate": 0,
|
| 472 |
+
"cap_hits": 0,
|
| 473 |
+
"episodes": 200,
|
| 474 |
+
"error_rate": 0.0,
|
| 475 |
+
"failed_episodes": 0,
|
| 476 |
+
"failed_seeds": [],
|
| 477 |
+
"failure_adjusted": {
|
| 478 |
+
"lines": {
|
| 479 |
+
"count": 200,
|
| 480 |
+
"mean": 0.105,
|
| 481 |
+
"median": 0.0,
|
| 482 |
+
"p95": 1
|
| 483 |
+
},
|
| 484 |
+
"pieces": {
|
| 485 |
+
"count": 200,
|
| 486 |
+
"mean": 26,
|
| 487 |
+
"median": 26.0,
|
| 488 |
+
"p95": 31
|
| 489 |
+
},
|
| 490 |
+
"score": {
|
| 491 |
+
"count": 200,
|
| 492 |
+
"mean": 10.5,
|
| 493 |
+
"median": 0.0,
|
| 494 |
+
"p95": 100
|
| 495 |
+
}
|
| 496 |
+
},
|
| 497 |
+
"identity": {
|
| 498 |
+
"name": "base-fp32",
|
| 499 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:stackcraft-clef-v1:head-fp32-ablation",
|
| 500 |
+
"runtime_config": {
|
| 501 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 502 |
+
"cpu_threads": 8,
|
| 503 |
+
"cuda_matmul_allow_tf32": false,
|
| 504 |
+
"cuda_version": "13.0",
|
| 505 |
+
"device": "cuda:0",
|
| 506 |
+
"dtype": "torch.bfloat16",
|
| 507 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 508 |
+
"evaluation_head": "fp32-gathered-rows",
|
| 509 |
+
"float32_matmul_precision": "highest",
|
| 510 |
+
"head_dtype": "float32",
|
| 511 |
+
"max_length": 4096,
|
| 512 |
+
"model_id": "Cloudflare/clef-flash",
|
| 513 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:stackcraft-clef-v1:head-fp32-ablation",
|
| 514 |
+
"source_sha256": "0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3",
|
| 515 |
+
"versions": {
|
| 516 |
+
"huggingface-hub": "1.33.0",
|
| 517 |
+
"peft": "0.21.2",
|
| 518 |
+
"safetensors": "0.8.0",
|
| 519 |
+
"torch": "2.14.1+cu130",
|
| 520 |
+
"transformers": "5.18.0"
|
| 521 |
+
}
|
| 522 |
+
}
|
| 523 |
+
},
|
| 524 |
+
"input_tokens": {
|
| 525 |
+
"count": 5200,
|
| 526 |
+
"mean": 1349.7273076923077,
|
| 527 |
+
"median": 1286.0,
|
| 528 |
+
"p95": 2258
|
| 529 |
+
},
|
| 530 |
+
"invalid_decisions": 0,
|
| 531 |
+
"latency_seconds": {
|
| 532 |
+
"count": 5200,
|
| 533 |
+
"mean": 0.1501883656640264,
|
| 534 |
+
"median": 0.14096801349660382,
|
| 535 |
+
"p95": 0.24787959101377055
|
| 536 |
+
},
|
| 537 |
+
"load_seconds": [
|
| 538 |
+
2.1760330779943615
|
| 539 |
+
],
|
| 540 |
+
"observed_before_error": {
|
| 541 |
+
"lines": {
|
| 542 |
+
"count": 200,
|
| 543 |
+
"mean": 0.105,
|
| 544 |
+
"median": 0.0,
|
| 545 |
+
"p95": 1
|
| 546 |
+
},
|
| 547 |
+
"pieces": {
|
| 548 |
+
"count": 200,
|
| 549 |
+
"mean": 26,
|
| 550 |
+
"median": 26.0,
|
| 551 |
+
"p95": 31
|
| 552 |
+
},
|
| 553 |
+
"score": {
|
| 554 |
+
"count": 200,
|
| 555 |
+
"mean": 10.5,
|
| 556 |
+
"median": 0.0,
|
| 557 |
+
"p95": 100
|
| 558 |
+
}
|
| 559 |
+
}
|
| 560 |
+
},
|
| 561 |
+
"heuristic": {
|
| 562 |
+
"cap_hit_rate": 1,
|
| 563 |
+
"cap_hits": 200,
|
| 564 |
+
"episodes": 200,
|
| 565 |
+
"error_rate": 0.0,
|
| 566 |
+
"failed_episodes": 0,
|
| 567 |
+
"failed_seeds": [],
|
| 568 |
+
"failure_adjusted": {
|
| 569 |
+
"lines": {
|
| 570 |
+
"count": 200,
|
| 571 |
+
"mean": 76.53,
|
| 572 |
+
"median": 77.0,
|
| 573 |
+
"p95": 79
|
| 574 |
+
},
|
| 575 |
+
"pieces": {
|
| 576 |
+
"count": 200,
|
| 577 |
+
"mean": 200,
|
| 578 |
+
"median": 200.0,
|
| 579 |
+
"p95": 200
|
| 580 |
+
},
|
| 581 |
+
"score": {
|
| 582 |
+
"count": 200,
|
| 583 |
+
"mean": 8306.5,
|
| 584 |
+
"median": 8300.0,
|
| 585 |
+
"p95": 8800
|
| 586 |
+
}
|
| 587 |
+
},
|
| 588 |
+
"identity": {
|
| 589 |
+
"name": "heuristic",
|
| 590 |
+
"revision": "heuristic-v1-height1-holes4-bump1-lines8",
|
| 591 |
+
"runtime_config": {}
|
| 592 |
+
},
|
| 593 |
+
"input_tokens": {
|
| 594 |
+
"count": 0,
|
| 595 |
+
"mean": null,
|
| 596 |
+
"median": null,
|
| 597 |
+
"p95": null
|
| 598 |
+
},
|
| 599 |
+
"invalid_decisions": 0,
|
| 600 |
+
"latency_seconds": {
|
| 601 |
+
"count": 40000,
|
| 602 |
+
"mean": 0.00019850957853268482,
|
| 603 |
+
"median": 0.00013948898413218558,
|
| 604 |
+
"p95": 0.00028052800917066634
|
| 605 |
+
},
|
| 606 |
+
"load_seconds": [
|
| 607 |
+
2.1799933165311813e-06
|
| 608 |
+
],
|
| 609 |
+
"observed_before_error": {
|
| 610 |
+
"lines": {
|
| 611 |
+
"count": 200,
|
| 612 |
+
"mean": 76.53,
|
| 613 |
+
"median": 77.0,
|
| 614 |
+
"p95": 79
|
| 615 |
+
},
|
| 616 |
+
"pieces": {
|
| 617 |
+
"count": 200,
|
| 618 |
+
"mean": 200,
|
| 619 |
+
"median": 200.0,
|
| 620 |
+
"p95": 200
|
| 621 |
+
},
|
| 622 |
+
"score": {
|
| 623 |
+
"count": 200,
|
| 624 |
+
"mean": 8306.5,
|
| 625 |
+
"median": 8300.0,
|
| 626 |
+
"p95": 8800
|
| 627 |
+
}
|
| 628 |
+
}
|
| 629 |
+
},
|
| 630 |
+
"random": {
|
| 631 |
+
"cap_hit_rate": 0,
|
| 632 |
+
"cap_hits": 0,
|
| 633 |
+
"episodes": 200,
|
| 634 |
+
"error_rate": 0.0,
|
| 635 |
+
"failed_episodes": 0,
|
| 636 |
+
"failed_seeds": [],
|
| 637 |
+
"failure_adjusted": {
|
| 638 |
+
"lines": {
|
| 639 |
+
"count": 200,
|
| 640 |
+
"mean": 0.14,
|
| 641 |
+
"median": 0.0,
|
| 642 |
+
"p95": 1
|
| 643 |
+
},
|
| 644 |
+
"pieces": {
|
| 645 |
+
"count": 200,
|
| 646 |
+
"mean": 26.645,
|
| 647 |
+
"median": 27.0,
|
| 648 |
+
"p95": 31
|
| 649 |
+
},
|
| 650 |
+
"score": {
|
| 651 |
+
"count": 200,
|
| 652 |
+
"mean": 14,
|
| 653 |
+
"median": 0.0,
|
| 654 |
+
"p95": 100
|
| 655 |
+
}
|
| 656 |
+
},
|
| 657 |
+
"identity": {
|
| 658 |
+
"name": "random",
|
| 659 |
+
"revision": "random-v1-rng-0",
|
| 660 |
+
"runtime_config": {}
|
| 661 |
+
},
|
| 662 |
+
"input_tokens": {
|
| 663 |
+
"count": 0,
|
| 664 |
+
"mean": null,
|
| 665 |
+
"median": null,
|
| 666 |
+
"p95": null
|
| 667 |
+
},
|
| 668 |
+
"invalid_decisions": 0,
|
| 669 |
+
"latency_seconds": {
|
| 670 |
+
"count": 5329,
|
| 671 |
+
"mean": 1.4965126650845871e-06,
|
| 672 |
+
"median": 1.2499804142862558e-06,
|
| 673 |
+
"p95": 2.8790091164410114e-06
|
| 674 |
+
},
|
| 675 |
+
"load_seconds": [
|
| 676 |
+
3.4689990570768714e-05
|
| 677 |
+
],
|
| 678 |
+
"observed_before_error": {
|
| 679 |
+
"lines": {
|
| 680 |
+
"count": 200,
|
| 681 |
+
"mean": 0.14,
|
| 682 |
+
"median": 0.0,
|
| 683 |
+
"p95": 1
|
| 684 |
+
},
|
| 685 |
+
"pieces": {
|
| 686 |
+
"count": 200,
|
| 687 |
+
"mean": 26.645,
|
| 688 |
+
"median": 27.0,
|
| 689 |
+
"p95": 31
|
| 690 |
+
},
|
| 691 |
+
"score": {
|
| 692 |
+
"count": 200,
|
| 693 |
+
"mean": 14,
|
| 694 |
+
"median": 0.0,
|
| 695 |
+
"p95": 100
|
| 696 |
+
}
|
| 697 |
+
}
|
| 698 |
+
},
|
| 699 |
+
"trained": {
|
| 700 |
+
"cap_hit_rate": 0,
|
| 701 |
+
"cap_hits": 0,
|
| 702 |
+
"episodes": 200,
|
| 703 |
+
"error_rate": 0.0,
|
| 704 |
+
"failed_episodes": 0,
|
| 705 |
+
"failed_seeds": [],
|
| 706 |
+
"failure_adjusted": {
|
| 707 |
+
"lines": {
|
| 708 |
+
"count": 200,
|
| 709 |
+
"mean": 16.81,
|
| 710 |
+
"median": 16.0,
|
| 711 |
+
"p95": 29
|
| 712 |
+
},
|
| 713 |
+
"pieces": {
|
| 714 |
+
"count": 200,
|
| 715 |
+
"mean": 83.565,
|
| 716 |
+
"median": 82.0,
|
| 717 |
+
"p95": 114
|
| 718 |
+
},
|
| 719 |
+
"score": {
|
| 720 |
+
"count": 200,
|
| 721 |
+
"mean": 1745.5,
|
| 722 |
+
"median": 1650.0,
|
| 723 |
+
"p95": 3100
|
| 724 |
+
}
|
| 725 |
+
},
|
| 726 |
+
"identity": {
|
| 727 |
+
"name": "trained",
|
| 728 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:checkpoint-sha256-b8133e9775a82c6b952fc1a1b1c805c5bc993fbcd919e81f663ad35e122ab7c7:stackcraft-clef-v1",
|
| 729 |
+
"runtime_config": {
|
| 730 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 731 |
+
"cpu_threads": 8,
|
| 732 |
+
"cuda_matmul_allow_tf32": false,
|
| 733 |
+
"cuda_version": "13.0",
|
| 734 |
+
"device": "cuda:0",
|
| 735 |
+
"dtype": "torch.bfloat16",
|
| 736 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 737 |
+
"evaluation_head": "fp32-gathered-rows",
|
| 738 |
+
"float32_matmul_precision": "highest",
|
| 739 |
+
"head_dtype": "float32",
|
| 740 |
+
"max_length": 4096,
|
| 741 |
+
"model_id": "Cloudflare/clef-flash",
|
| 742 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:checkpoint-sha256-b8133e9775a82c6b952fc1a1b1c805c5bc993fbcd919e81f663ad35e122ab7c7:stackcraft-clef-v1",
|
| 743 |
+
"source_sha256": "0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3",
|
| 744 |
+
"versions": {
|
| 745 |
+
"huggingface-hub": "1.33.0",
|
| 746 |
+
"peft": "0.21.2",
|
| 747 |
+
"safetensors": "0.8.0",
|
| 748 |
+
"torch": "2.14.1+cu130",
|
| 749 |
+
"transformers": "5.18.0"
|
| 750 |
+
}
|
| 751 |
+
}
|
| 752 |
+
},
|
| 753 |
+
"input_tokens": {
|
| 754 |
+
"count": 16713,
|
| 755 |
+
"mean": 1532.1127864536588,
|
| 756 |
+
"median": 1304,
|
| 757 |
+
"p95": 2259
|
| 758 |
+
},
|
| 759 |
+
"invalid_decisions": 0,
|
| 760 |
+
"latency_seconds": {
|
| 761 |
+
"count": 16713,
|
| 762 |
+
"mean": 0.20681440101212237,
|
| 763 |
+
"median": 0.17184428698965348,
|
| 764 |
+
"p95": 0.3090000880183652
|
| 765 |
+
},
|
| 766 |
+
"load_seconds": [
|
| 767 |
+
2.5281899149995297
|
| 768 |
+
],
|
| 769 |
+
"observed_before_error": {
|
| 770 |
+
"lines": {
|
| 771 |
+
"count": 200,
|
| 772 |
+
"mean": 16.81,
|
| 773 |
+
"median": 16.0,
|
| 774 |
+
"p95": 29
|
| 775 |
+
},
|
| 776 |
+
"pieces": {
|
| 777 |
+
"count": 200,
|
| 778 |
+
"mean": 83.565,
|
| 779 |
+
"median": 82.0,
|
| 780 |
+
"p95": 114
|
| 781 |
+
},
|
| 782 |
+
"score": {
|
| 783 |
+
"count": 200,
|
| 784 |
+
"mean": 1745.5,
|
| 785 |
+
"median": 1650.0,
|
| 786 |
+
"p95": 3100
|
| 787 |
+
}
|
| 788 |
+
}
|
| 789 |
+
}
|
| 790 |
+
},
|
| 791 |
+
"protocol": {
|
| 792 |
+
"bootstrap_samples": 10000,
|
| 793 |
+
"bootstrap_seed": 2026,
|
| 794 |
+
"checkpoint_selection": "lowest finite validation NLL over all 215 positions; exact tie selects earlier of epoch-01 and epoch-02",
|
| 795 |
+
"ci_method": "paired episode percentile bootstrap",
|
| 796 |
+
"episodes_per_player": 200,
|
| 797 |
+
"failure_policy": "zero lines/score/pieces on errors; retain all pairs",
|
| 798 |
+
"final_test": true,
|
| 799 |
+
"max_error_rate": 0.0,
|
| 800 |
+
"max_pieces": 200,
|
| 801 |
+
"primary_metric": "lines",
|
| 802 |
+
"rules_version": "stackcraft-v1",
|
| 803 |
+
"secondary_metrics": [
|
| 804 |
+
"score",
|
| 805 |
+
"pieces"
|
| 806 |
+
],
|
| 807 |
+
"seeds": [
|
| 808 |
+
30000,
|
| 809 |
+
30001,
|
| 810 |
+
30002,
|
| 811 |
+
30003,
|
| 812 |
+
30004,
|
| 813 |
+
30005,
|
| 814 |
+
30006,
|
| 815 |
+
30007,
|
| 816 |
+
30008,
|
| 817 |
+
30009,
|
| 818 |
+
30010,
|
| 819 |
+
30011,
|
| 820 |
+
30012,
|
| 821 |
+
30013,
|
| 822 |
+
30014,
|
| 823 |
+
30015,
|
| 824 |
+
30016,
|
| 825 |
+
30017,
|
| 826 |
+
30018,
|
| 827 |
+
30019,
|
| 828 |
+
30020,
|
| 829 |
+
30021,
|
| 830 |
+
30022,
|
| 831 |
+
30023,
|
| 832 |
+
30024,
|
| 833 |
+
30025,
|
| 834 |
+
30026,
|
| 835 |
+
30027,
|
| 836 |
+
30028,
|
| 837 |
+
30029,
|
| 838 |
+
30030,
|
| 839 |
+
30031,
|
| 840 |
+
30032,
|
| 841 |
+
30033,
|
| 842 |
+
30034,
|
| 843 |
+
30035,
|
| 844 |
+
30036,
|
| 845 |
+
30037,
|
| 846 |
+
30038,
|
| 847 |
+
30039,
|
| 848 |
+
30040,
|
| 849 |
+
30041,
|
| 850 |
+
30042,
|
| 851 |
+
30043,
|
| 852 |
+
30044,
|
| 853 |
+
30045,
|
| 854 |
+
30046,
|
| 855 |
+
30047,
|
| 856 |
+
30048,
|
| 857 |
+
30049,
|
| 858 |
+
30050,
|
| 859 |
+
30051,
|
| 860 |
+
30052,
|
| 861 |
+
30053,
|
| 862 |
+
30054,
|
| 863 |
+
30055,
|
| 864 |
+
30056,
|
| 865 |
+
30057,
|
| 866 |
+
30058,
|
| 867 |
+
30059,
|
| 868 |
+
30060,
|
| 869 |
+
30061,
|
| 870 |
+
30062,
|
| 871 |
+
30063,
|
| 872 |
+
30064,
|
| 873 |
+
30065,
|
| 874 |
+
30066,
|
| 875 |
+
30067,
|
| 876 |
+
30068,
|
| 877 |
+
30069,
|
| 878 |
+
30070,
|
| 879 |
+
30071,
|
| 880 |
+
30072,
|
| 881 |
+
30073,
|
| 882 |
+
30074,
|
| 883 |
+
30075,
|
| 884 |
+
30076,
|
| 885 |
+
30077,
|
| 886 |
+
30078,
|
| 887 |
+
30079,
|
| 888 |
+
30080,
|
| 889 |
+
30081,
|
| 890 |
+
30082,
|
| 891 |
+
30083,
|
| 892 |
+
30084,
|
| 893 |
+
30085,
|
| 894 |
+
30086,
|
| 895 |
+
30087,
|
| 896 |
+
30088,
|
| 897 |
+
30089,
|
| 898 |
+
30090,
|
| 899 |
+
30091,
|
| 900 |
+
30092,
|
| 901 |
+
30093,
|
| 902 |
+
30094,
|
| 903 |
+
30095,
|
| 904 |
+
30096,
|
| 905 |
+
30097,
|
| 906 |
+
30098,
|
| 907 |
+
30099,
|
| 908 |
+
30100,
|
| 909 |
+
30101,
|
| 910 |
+
30102,
|
| 911 |
+
30103,
|
| 912 |
+
30104,
|
| 913 |
+
30105,
|
| 914 |
+
30106,
|
| 915 |
+
30107,
|
| 916 |
+
30108,
|
| 917 |
+
30109,
|
| 918 |
+
30110,
|
| 919 |
+
30111,
|
| 920 |
+
30112,
|
| 921 |
+
30113,
|
| 922 |
+
30114,
|
| 923 |
+
30115,
|
| 924 |
+
30116,
|
| 925 |
+
30117,
|
| 926 |
+
30118,
|
| 927 |
+
30119,
|
| 928 |
+
30120,
|
| 929 |
+
30121,
|
| 930 |
+
30122,
|
| 931 |
+
30123,
|
| 932 |
+
30124,
|
| 933 |
+
30125,
|
| 934 |
+
30126,
|
| 935 |
+
30127,
|
| 936 |
+
30128,
|
| 937 |
+
30129,
|
| 938 |
+
30130,
|
| 939 |
+
30131,
|
| 940 |
+
30132,
|
| 941 |
+
30133,
|
| 942 |
+
30134,
|
| 943 |
+
30135,
|
| 944 |
+
30136,
|
| 945 |
+
30137,
|
| 946 |
+
30138,
|
| 947 |
+
30139,
|
| 948 |
+
30140,
|
| 949 |
+
30141,
|
| 950 |
+
30142,
|
| 951 |
+
30143,
|
| 952 |
+
30144,
|
| 953 |
+
30145,
|
| 954 |
+
30146,
|
| 955 |
+
30147,
|
| 956 |
+
30148,
|
| 957 |
+
30149,
|
| 958 |
+
30150,
|
| 959 |
+
30151,
|
| 960 |
+
30152,
|
| 961 |
+
30153,
|
| 962 |
+
30154,
|
| 963 |
+
30155,
|
| 964 |
+
30156,
|
| 965 |
+
30157,
|
| 966 |
+
30158,
|
| 967 |
+
30159,
|
| 968 |
+
30160,
|
| 969 |
+
30161,
|
| 970 |
+
30162,
|
| 971 |
+
30163,
|
| 972 |
+
30164,
|
| 973 |
+
30165,
|
| 974 |
+
30166,
|
| 975 |
+
30167,
|
| 976 |
+
30168,
|
| 977 |
+
30169,
|
| 978 |
+
30170,
|
| 979 |
+
30171,
|
| 980 |
+
30172,
|
| 981 |
+
30173,
|
| 982 |
+
30174,
|
| 983 |
+
30175,
|
| 984 |
+
30176,
|
| 985 |
+
30177,
|
| 986 |
+
30178,
|
| 987 |
+
30179,
|
| 988 |
+
30180,
|
| 989 |
+
30181,
|
| 990 |
+
30182,
|
| 991 |
+
30183,
|
| 992 |
+
30184,
|
| 993 |
+
30185,
|
| 994 |
+
30186,
|
| 995 |
+
30187,
|
| 996 |
+
30188,
|
| 997 |
+
30189,
|
| 998 |
+
30190,
|
| 999 |
+
30191,
|
| 1000 |
+
30192,
|
| 1001 |
+
30193,
|
| 1002 |
+
30194,
|
| 1003 |
+
30195,
|
| 1004 |
+
30196,
|
| 1005 |
+
30197,
|
| 1006 |
+
30198,
|
| 1007 |
+
30199
|
| 1008 |
+
],
|
| 1009 |
+
"total_episodes": 1000,
|
| 1010 |
+
"training_seeds": [
|
| 1011 |
+
42
|
| 1012 |
+
]
|
| 1013 |
+
},
|
| 1014 |
+
"provenance": {
|
| 1015 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 1016 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 1017 |
+
"installed_versions": {
|
| 1018 |
+
"huggingface-hub": "1.33.0",
|
| 1019 |
+
"peft": "0.21.2",
|
| 1020 |
+
"safetensors": "0.8.0",
|
| 1021 |
+
"torch": "2.14.1+cu130",
|
| 1022 |
+
"transformers": "5.18.0"
|
| 1023 |
+
},
|
| 1024 |
+
"precision_policy": "native BF16 base; FP32 trained head; optional base-fp32 ablation",
|
| 1025 |
+
"rules_version": "stackcraft-v1",
|
| 1026 |
+
"source_commit": "e524f0c15c2dd60ebfdc1374d66836c79e1e6dd6",
|
| 1027 |
+
"source_dirty": false,
|
| 1028 |
+
"source_hashes": {
|
| 1029 |
+
"scripts/evaluate_clef.py": "70a47e9314ec9d7351a939bae32f07f14bd08f6106b60ce6155687c0540b92dd",
|
| 1030 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 1031 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 1032 |
+
"src/stackcraft/engine.py": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
|
| 1033 |
+
"src/stackcraft/evaluation.py": "df56f9d36655e79573a014a5c55c2571bc5e04e2c20f1b37e2d939b871e889e6",
|
| 1034 |
+
"src/stackcraft/pieces.py": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
|
| 1035 |
+
"src/stackcraft/players/__init__.py": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
|
| 1036 |
+
"src/stackcraft/provenance.py": "0728104f22361ad2a8b4471e6eef0467852d94f650d0259d7f5f674c0086f13b",
|
| 1037 |
+
"src/stackcraft/schema.py": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5",
|
| 1038 |
+
"src/stackcraft/tournament.py": "288ed447a32e0845f52097372a644fc211b1632790655f620801d3ce1639d559",
|
| 1039 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8",
|
| 1040 |
+
"uv.lock": "bc7fae476529578be003856b5f19e09c8cc96e0020ab999e20e1f8210696f9c8"
|
| 1041 |
+
},
|
| 1042 |
+
"source_identity_method": "exact-git-root"
|
| 1043 |
+
},
|
| 1044 |
+
"reload": {
|
| 1045 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 1046 |
+
"elapsed_seconds": 5.802506145002553,
|
| 1047 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 1048 |
+
"initial_free_vram": 33179500544,
|
| 1049 |
+
"max_absolute_probability_difference": 0.0,
|
| 1050 |
+
"reload": true,
|
| 1051 |
+
"row_ids": [
|
| 1052 |
+
"seed-10000-turn-0",
|
| 1053 |
+
"seed-10000-turn-1",
|
| 1054 |
+
"seed-10000-turn-2",
|
| 1055 |
+
"seed-10000-turn-3"
|
| 1056 |
+
],
|
| 1057 |
+
"source_commit": "7a21f6491bcaf2afacc9aa5b5e2733a35c41a7a8",
|
| 1058 |
+
"source_dirty": false,
|
| 1059 |
+
"source_identity_method": "exact-git-root",
|
| 1060 |
+
"status": "passed",
|
| 1061 |
+
"tolerance": 0.0001,
|
| 1062 |
+
"torch": "2.14.1+cu130",
|
| 1063 |
+
"total_vram": 33711521792
|
| 1064 |
+
},
|
| 1065 |
+
"runtime": {
|
| 1066 |
+
"decision_latency_scope": "All Player.choose calls, including encoding and probability transfer; no excluded warmup; cached local model initialization recorded separately. Policies visit different states.",
|
| 1067 |
+
"energy_measured": false,
|
| 1068 |
+
"final_evaluation_wrapper_seconds": 5103.112404823303,
|
| 1069 |
+
"monetary_cost_measured": false,
|
| 1070 |
+
"trained_to_heuristic_mean_decision_latency_ratio": 1041.8358778494417,
|
| 1071 |
+
"wrapper_scope": "Includes service stop/restore and health checks, evaluation, model loading and report writing; not pure inference time."
|
| 1072 |
+
},
|
| 1073 |
+
"schema_version": 1,
|
| 1074 |
+
"selection": {
|
| 1075 |
+
"checkpoint_sha256": {
|
| 1076 |
+
"adapter/README.md": "bf1fa63446e8ee38c5ac154ade9138a0e2354e3215201f59473102eeb5206fa0",
|
| 1077 |
+
"adapter/adapter_config.json": "bbbce7f19e186b4a79c39d03786bb190d6084e7990a601e8e28672cf1c969741",
|
| 1078 |
+
"adapter/adapter_model.safetensors": "3ed169fe68b20a370bb856f9d91842543374ff3c98fd3bbe139ef2d0b97877a3",
|
| 1079 |
+
"joint_head.safetensors": "4f21c0f315cf1bcf852a9887d158928855eaa9bdcab6b9770d518abf3727fa1b",
|
| 1080 |
+
"reference.json": "adacc63684d28598f8016602bab790cd3967317986b6934dd94a5e14fabe571f",
|
| 1081 |
+
"training_config.json": "3ead31d0a3ccb6a62914c3853ee26332f6412382c5196e40b461182e64d8b329"
|
| 1082 |
+
},
|
| 1083 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 1084 |
+
"decision_rule": "lowest finite mean target NLL over all 215 validation positions; exact tie chooses earlier epoch",
|
| 1085 |
+
"selected_key": "epoch-02",
|
| 1086 |
+
"selection_sha256": "025b62587a26bd8cc78c80f362524b8c178190df5188b317299121afa6b1203b",
|
| 1087 |
+
"validation": {
|
| 1088 |
+
"epoch-01": {
|
| 1089 |
+
"players": {
|
| 1090 |
+
"base": {
|
| 1091 |
+
"available_subset_mean_brier": 0.9231725575578064,
|
| 1092 |
+
"complete_probability_coverage": true,
|
| 1093 |
+
"correct": 21,
|
| 1094 |
+
"error_rate": 0.0,
|
| 1095 |
+
"failed_predictions": [],
|
| 1096 |
+
"finite_subset_mean_nll": 2.9527020509302693,
|
| 1097 |
+
"finite_subset_nll_positions": 215,
|
| 1098 |
+
"mean_brier": 0.9231725575578064,
|
| 1099 |
+
"mean_nll": 2.9527020509302693,
|
| 1100 |
+
"missing_probabilities": [],
|
| 1101 |
+
"nll_is_infinite": false,
|
| 1102 |
+
"positions": 215,
|
| 1103 |
+
"probability_positions": 215,
|
| 1104 |
+
"selection_performed": false,
|
| 1105 |
+
"teacher_agreement": 0.09767441860465116,
|
| 1106 |
+
"zero_target_probability_count": 0
|
| 1107 |
+
},
|
| 1108 |
+
"base-fp32": {
|
| 1109 |
+
"available_subset_mean_brier": 0.9231017245023939,
|
| 1110 |
+
"complete_probability_coverage": true,
|
| 1111 |
+
"correct": 22,
|
| 1112 |
+
"error_rate": 0.0,
|
| 1113 |
+
"failed_predictions": [],
|
| 1114 |
+
"finite_subset_mean_nll": 2.9523931327950934,
|
| 1115 |
+
"finite_subset_nll_positions": 215,
|
| 1116 |
+
"mean_brier": 0.9231017245023939,
|
| 1117 |
+
"mean_nll": 2.9523931327950934,
|
| 1118 |
+
"missing_probabilities": [],
|
| 1119 |
+
"nll_is_infinite": false,
|
| 1120 |
+
"positions": 215,
|
| 1121 |
+
"probability_positions": 215,
|
| 1122 |
+
"selection_performed": false,
|
| 1123 |
+
"teacher_agreement": 0.10232558139534884,
|
| 1124 |
+
"zero_target_probability_count": 0
|
| 1125 |
+
},
|
| 1126 |
+
"trained": {
|
| 1127 |
+
"available_subset_mean_brier": 0.7221895727255984,
|
| 1128 |
+
"complete_probability_coverage": true,
|
| 1129 |
+
"correct": 89,
|
| 1130 |
+
"error_rate": 0.0,
|
| 1131 |
+
"failed_predictions": [],
|
| 1132 |
+
"finite_subset_mean_nll": 1.8608697515370076,
|
| 1133 |
+
"finite_subset_nll_positions": 215,
|
| 1134 |
+
"mean_brier": 0.7221895727255984,
|
| 1135 |
+
"mean_nll": 1.8608697515370076,
|
| 1136 |
+
"missing_probabilities": [],
|
| 1137 |
+
"nll_is_infinite": false,
|
| 1138 |
+
"positions": 215,
|
| 1139 |
+
"probability_positions": 215,
|
| 1140 |
+
"selection_performed": false,
|
| 1141 |
+
"teacher_agreement": 0.413953488372093,
|
| 1142 |
+
"zero_target_probability_count": 0
|
| 1143 |
+
}
|
| 1144 |
+
},
|
| 1145 |
+
"provenance": {
|
| 1146 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 1147 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 1148 |
+
"installed_versions": {
|
| 1149 |
+
"huggingface-hub": "1.33.0",
|
| 1150 |
+
"peft": "0.21.2",
|
| 1151 |
+
"safetensors": "0.8.0",
|
| 1152 |
+
"torch": "2.14.1+cu130",
|
| 1153 |
+
"transformers": "5.18.0"
|
| 1154 |
+
},
|
| 1155 |
+
"precision_policy": "native BF16 base; FP32 trained head; optional base-fp32 ablation",
|
| 1156 |
+
"rules_version": "stackcraft-v1",
|
| 1157 |
+
"source_commit": "ada38885b55ae3e4b6705a18dec98d194d37447e",
|
| 1158 |
+
"source_dirty": false,
|
| 1159 |
+
"source_hashes": {
|
| 1160 |
+
"scripts/evaluate_clef.py": "70a47e9314ec9d7351a939bae32f07f14bd08f6106b60ce6155687c0540b92dd",
|
| 1161 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 1162 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 1163 |
+
"src/stackcraft/engine.py": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
|
| 1164 |
+
"src/stackcraft/evaluation.py": "df56f9d36655e79573a014a5c55c2571bc5e04e2c20f1b37e2d939b871e889e6",
|
| 1165 |
+
"src/stackcraft/pieces.py": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
|
| 1166 |
+
"src/stackcraft/players/__init__.py": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
|
| 1167 |
+
"src/stackcraft/provenance.py": "0728104f22361ad2a8b4471e6eef0467852d94f650d0259d7f5f674c0086f13b",
|
| 1168 |
+
"src/stackcraft/schema.py": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5",
|
| 1169 |
+
"src/stackcraft/tournament.py": "288ed447a32e0845f52097372a644fc211b1632790655f620801d3ce1639d559",
|
| 1170 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8",
|
| 1171 |
+
"uv.lock": "bc7fae476529578be003856b5f19e09c8cc96e0020ab999e20e1f8210696f9c8"
|
| 1172 |
+
},
|
| 1173 |
+
"source_identity_method": "exact-git-root"
|
| 1174 |
+
},
|
| 1175 |
+
"report_sha256": "ce401ef9fe1d25c11c7a0e3845def347967d8617223307ad823e23a90c3f0033"
|
| 1176 |
+
},
|
| 1177 |
+
"epoch-02": {
|
| 1178 |
+
"players": {
|
| 1179 |
+
"trained": {
|
| 1180 |
+
"available_subset_mean_brier": 0.7089798657211578,
|
| 1181 |
+
"complete_probability_coverage": true,
|
| 1182 |
+
"correct": 92,
|
| 1183 |
+
"error_rate": 0.0,
|
| 1184 |
+
"failed_predictions": [],
|
| 1185 |
+
"finite_subset_mean_nll": 1.7759231168523646,
|
| 1186 |
+
"finite_subset_nll_positions": 215,
|
| 1187 |
+
"mean_brier": 0.7089798657211578,
|
| 1188 |
+
"mean_nll": 1.7759231168523646,
|
| 1189 |
+
"missing_probabilities": [],
|
| 1190 |
+
"nll_is_infinite": false,
|
| 1191 |
+
"positions": 215,
|
| 1192 |
+
"probability_positions": 215,
|
| 1193 |
+
"selection_performed": false,
|
| 1194 |
+
"teacher_agreement": 0.42790697674418604,
|
| 1195 |
+
"zero_target_probability_count": 0
|
| 1196 |
+
}
|
| 1197 |
+
},
|
| 1198 |
+
"provenance": {
|
| 1199 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 1200 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 1201 |
+
"installed_versions": {
|
| 1202 |
+
"huggingface-hub": "1.33.0",
|
| 1203 |
+
"peft": "0.21.2",
|
| 1204 |
+
"safetensors": "0.8.0",
|
| 1205 |
+
"torch": "2.14.1+cu130",
|
| 1206 |
+
"transformers": "5.18.0"
|
| 1207 |
+
},
|
| 1208 |
+
"precision_policy": "native BF16 base; FP32 trained head; optional base-fp32 ablation",
|
| 1209 |
+
"rules_version": "stackcraft-v1",
|
| 1210 |
+
"source_commit": "7a21f6491bcaf2afacc9aa5b5e2733a35c41a7a8",
|
| 1211 |
+
"source_dirty": false,
|
| 1212 |
+
"source_hashes": {
|
| 1213 |
+
"scripts/evaluate_clef.py": "70a47e9314ec9d7351a939bae32f07f14bd08f6106b60ce6155687c0540b92dd",
|
| 1214 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 1215 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 1216 |
+
"src/stackcraft/engine.py": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
|
| 1217 |
+
"src/stackcraft/evaluation.py": "df56f9d36655e79573a014a5c55c2571bc5e04e2c20f1b37e2d939b871e889e6",
|
| 1218 |
+
"src/stackcraft/pieces.py": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
|
| 1219 |
+
"src/stackcraft/players/__init__.py": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
|
| 1220 |
+
"src/stackcraft/provenance.py": "0728104f22361ad2a8b4471e6eef0467852d94f650d0259d7f5f674c0086f13b",
|
| 1221 |
+
"src/stackcraft/schema.py": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5",
|
| 1222 |
+
"src/stackcraft/tournament.py": "288ed447a32e0845f52097372a644fc211b1632790655f620801d3ce1639d559",
|
| 1223 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8",
|
| 1224 |
+
"uv.lock": "bc7fae476529578be003856b5f19e09c8cc96e0020ab999e20e1f8210696f9c8"
|
| 1225 |
+
},
|
| 1226 |
+
"source_identity_method": "exact-git-root"
|
| 1227 |
+
},
|
| 1228 |
+
"report_sha256": "9482be5ac08b85a7bafc18d8f70a35ba277f476d9a96dd0684553e937abc4d2a"
|
| 1229 |
+
}
|
| 1230 |
+
}
|
| 1231 |
+
},
|
| 1232 |
+
"status": "final evaluation complete; publication pending",
|
| 1233 |
+
"training": {
|
| 1234 |
+
"config": {
|
| 1235 |
+
"batch_size": 1,
|
| 1236 |
+
"brier_weight": 0.1,
|
| 1237 |
+
"clip_gradient_norm": 1.0,
|
| 1238 |
+
"epochs": 2,
|
| 1239 |
+
"gradient_accumulation": 8,
|
| 1240 |
+
"label_smoothing": 0.05,
|
| 1241 |
+
"learning_rate": 1e-05,
|
| 1242 |
+
"max_length": 4096,
|
| 1243 |
+
"mode": "lora",
|
| 1244 |
+
"optimizer": "AdamW",
|
| 1245 |
+
"rank": 4,
|
| 1246 |
+
"seed": 42,
|
| 1247 |
+
"selection": "external validation only; this script does not choose a checkpoint",
|
| 1248 |
+
"weight_decay": 0.01
|
| 1249 |
+
},
|
| 1250 |
+
"dataset_counts": {
|
| 1251 |
+
"train": 827,
|
| 1252 |
+
"validation": 215
|
| 1253 |
+
},
|
| 1254 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 1255 |
+
"dataset_split_sha256": {
|
| 1256 |
+
"train": "edd682761db95a4f25bb30a284489c54d9336a36da0a9b19d2cda860b428baa8",
|
| 1257 |
+
"validation": "eff9cdc5932e935959ac4d26dce6470d335090f7428a91930001954266d133bc"
|
| 1258 |
+
},
|
| 1259 |
+
"elapsed_seconds": 1511.2463698700012,
|
| 1260 |
+
"epochs": [
|
| 1261 |
+
{
|
| 1262 |
+
"checkpoint": "runs/study-v1/epoch-01",
|
| 1263 |
+
"epoch": 1,
|
| 1264 |
+
"examples": 827,
|
| 1265 |
+
"mean_training_loss": 2.5779671531346287,
|
| 1266 |
+
"optimizer_steps": 104,
|
| 1267 |
+
"peak_allocated_bytes": 23230341120,
|
| 1268 |
+
"peak_reserved_bytes": 24178065408,
|
| 1269 |
+
"seconds": 742.4755487579969,
|
| 1270 |
+
"shuffle_order_sha256": "b6e0d7189b1d965472f1132dda2e9be3a35093dae838baf5d8e0c0d3471b6198"
|
| 1271 |
+
},
|
| 1272 |
+
{
|
| 1273 |
+
"checkpoint": "runs/study-v1/epoch-02",
|
| 1274 |
+
"epoch": 2,
|
| 1275 |
+
"examples": 827,
|
| 1276 |
+
"mean_training_loss": 1.9667175294878694,
|
| 1277 |
+
"optimizer_steps": 104,
|
| 1278 |
+
"peak_allocated_bytes": 23226543104,
|
| 1279 |
+
"peak_reserved_bytes": 24178065408,
|
| 1280 |
+
"seconds": 744.6554337799898,
|
| 1281 |
+
"shuffle_order_sha256": "b9d20efe77959592c99e9247a58d844be69df50db595bd87c6d7e595f5d2e0c4"
|
| 1282 |
+
}
|
| 1283 |
+
],
|
| 1284 |
+
"frozen_parameters_unchanged": true,
|
| 1285 |
+
"source_commit": "d9fcd05c02e9327d4d791f2338725092cede8bdd",
|
| 1286 |
+
"source_dirty": true,
|
| 1287 |
+
"source_hashes": {
|
| 1288 |
+
"scripts/train_clef.py": "e83586cf0fd0d7bb8c4aa15f9b858b9f9ffdb915622e355deb9513788d5054aa",
|
| 1289 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 1290 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 1291 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8"
|
| 1292 |
+
},
|
| 1293 |
+
"test_trajectories_used": false,
|
| 1294 |
+
"trainable_parameters": 132582404,
|
| 1295 |
+
"validation_used_for_training": false
|
| 1296 |
+
}
|
| 1297 |
+
}
|
code/reports/gpu-feasibility.json
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"runs/probe-v1/report.json": {
|
| 3 |
+
"sha256": "b66d2c01a43d08234229f3d7d3980a7d2d161fb21aa26965acb3bfe243351807",
|
| 4 |
+
"report": {
|
| 5 |
+
"status": "passed",
|
| 6 |
+
"reload": false,
|
| 7 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 8 |
+
"total_vram": 33711521792,
|
| 9 |
+
"initial_free_vram": 33179500544,
|
| 10 |
+
"torch": "2.14.1+cu130",
|
| 11 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 12 |
+
"source_commit": "e38ac536d0a6af444b48fddd6d8789ace089a51c",
|
| 13 |
+
"row_ids": [
|
| 14 |
+
"seed-10000-turn-0",
|
| 15 |
+
"seed-10000-turn-1",
|
| 16 |
+
"seed-10000-turn-2",
|
| 17 |
+
"seed-10000-turn-3"
|
| 18 |
+
],
|
| 19 |
+
"runtime_config": {
|
| 20 |
+
"model_id": "Cloudflare/clef-flash",
|
| 21 |
+
"revision": "Cloudflare/clef-flash@17f0b0ad64efb65d273590632833508766b2aae6:stackcraft-clef-v1",
|
| 22 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 23 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 24 |
+
"source_sha256": "0e304cf7c6500e8bb59bef7e2afd2c6373f82596dfb3b57d1aa93c175e2dc3a3",
|
| 25 |
+
"max_length": 4096,
|
| 26 |
+
"dtype": "torch.bfloat16",
|
| 27 |
+
"device": "cuda:0"
|
| 28 |
+
},
|
| 29 |
+
"load_and_native_seconds": 5.267827647010563,
|
| 30 |
+
"fp32_head_initial_max_probability_drift": 0.0007441192865371704,
|
| 31 |
+
"head_steps": [
|
| 32 |
+
{
|
| 33 |
+
"step": 1,
|
| 34 |
+
"row_id": "seed-10000-turn-0",
|
| 35 |
+
"tokens": 2248,
|
| 36 |
+
"loss": 3.5077385902404785,
|
| 37 |
+
"gradient_abs_sums": {
|
| 38 |
+
"head": 1406.742018819321,
|
| 39 |
+
"lora": 0.0
|
| 40 |
+
},
|
| 41 |
+
"seconds": 0.3278578449971974,
|
| 42 |
+
"peak_allocated_bytes": 21323760640,
|
| 43 |
+
"peak_reserved_bytes": 21369978880
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"step": 2,
|
| 47 |
+
"row_id": "seed-10000-turn-1",
|
| 48 |
+
"tokens": 2252,
|
| 49 |
+
"loss": 3.839761734008789,
|
| 50 |
+
"gradient_abs_sums": {
|
| 51 |
+
"head": 3064.3000336452387,
|
| 52 |
+
"lora": 0.0
|
| 53 |
+
},
|
| 54 |
+
"seconds": 0.258370580006158,
|
| 55 |
+
"peak_allocated_bytes": 21323760640,
|
| 56 |
+
"peak_reserved_bytes": 21541945344
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"step": 3,
|
| 60 |
+
"row_id": "seed-10000-turn-2",
|
| 61 |
+
"tokens": 1302,
|
| 62 |
+
"loss": 2.7408740520477295,
|
| 63 |
+
"gradient_abs_sums": {
|
| 64 |
+
"head": 2409.824099964928,
|
| 65 |
+
"lora": 0.0
|
| 66 |
+
},
|
| 67 |
+
"seconds": 0.1538237390050199,
|
| 68 |
+
"peak_allocated_bytes": 21323760640,
|
| 69 |
+
"peak_reserved_bytes": 21541945344
|
| 70 |
+
}
|
| 71 |
+
],
|
| 72 |
+
"head_frozen_parameters_unchanged": true,
|
| 73 |
+
"lora_steps": [
|
| 74 |
+
{
|
| 75 |
+
"step": 1,
|
| 76 |
+
"row_id": "seed-10000-turn-0",
|
| 77 |
+
"tokens": 2248,
|
| 78 |
+
"loss": 3.5077385902404785,
|
| 79 |
+
"gradient_abs_sums": {
|
| 80 |
+
"head": 1406.742226867238,
|
| 81 |
+
"lora": 4314.483247246593
|
| 82 |
+
},
|
| 83 |
+
"seconds": 1.3170126390177757,
|
| 84 |
+
"peak_allocated_bytes": 22128918016,
|
| 85 |
+
"peak_reserved_bytes": 22531801088
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"step": 2,
|
| 89 |
+
"row_id": "seed-10000-turn-1",
|
| 90 |
+
"tokens": 2252,
|
| 91 |
+
"loss": 3.845935106277466,
|
| 92 |
+
"gradient_abs_sums": {
|
| 93 |
+
"head": 3134.688414009288,
|
| 94 |
+
"lora": 2980.2572337791935
|
| 95 |
+
},
|
| 96 |
+
"seconds": 1.3217443070025183,
|
| 97 |
+
"peak_allocated_bytes": 23176526848,
|
| 98 |
+
"peak_reserved_bytes": 23483908096
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"step": 3,
|
| 102 |
+
"row_id": "seed-10000-turn-2",
|
| 103 |
+
"tokens": 1302,
|
| 104 |
+
"loss": 2.744056463241577,
|
| 105 |
+
"gradient_abs_sums": {
|
| 106 |
+
"head": 2369.929164870642,
|
| 107 |
+
"lora": 3249.260241425407
|
| 108 |
+
},
|
| 109 |
+
"seconds": 0.672728523000842,
|
| 110 |
+
"peak_allocated_bytes": 23176526848,
|
| 111 |
+
"peak_reserved_bytes": 23483908096
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"step": 4,
|
| 115 |
+
"row_id": "seed-10000-turn-3",
|
| 116 |
+
"tokens": 1304,
|
| 117 |
+
"loss": 2.775989294052124,
|
| 118 |
+
"gradient_abs_sums": {
|
| 119 |
+
"head": 2491.2841364948545,
|
| 120 |
+
"lora": 3332.906228476757
|
| 121 |
+
},
|
| 122 |
+
"seconds": 0.6700605849910062,
|
| 123 |
+
"peak_allocated_bytes": 23176526848,
|
| 124 |
+
"peak_reserved_bytes": 23483908096
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"step": 5,
|
| 128 |
+
"row_id": "seed-10000-turn-0",
|
| 129 |
+
"tokens": 2248,
|
| 130 |
+
"loss": 3.4597291946411133,
|
| 131 |
+
"gradient_abs_sums": {
|
| 132 |
+
"head": 1691.1293117979076,
|
| 133 |
+
"lora": 4719.155243932968
|
| 134 |
+
},
|
| 135 |
+
"seconds": 1.3178317880083341,
|
| 136 |
+
"peak_allocated_bytes": 23180389888,
|
| 137 |
+
"peak_reserved_bytes": 23595057152
|
| 138 |
+
}
|
| 139 |
+
],
|
| 140 |
+
"checkpoint": "runs/probe-v1/checkpoint",
|
| 141 |
+
"frozen_parameters_unchanged": true,
|
| 142 |
+
"elapsed_seconds": 51.810954032989684
|
| 143 |
+
}
|
| 144 |
+
},
|
| 145 |
+
"runs/probe-head-reload-v1/report.json": {
|
| 146 |
+
"sha256": "ed7afb912569ce4738d506962ca98c33cbbaee2d922ab4bff7b8adcbbae8c521",
|
| 147 |
+
"report": {
|
| 148 |
+
"status": "passed",
|
| 149 |
+
"reload": true,
|
| 150 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 151 |
+
"total_vram": 33711521792,
|
| 152 |
+
"initial_free_vram": 33179500544,
|
| 153 |
+
"torch": "2.14.1+cu130",
|
| 154 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 155 |
+
"source_commit": "e38ac536d0a6af444b48fddd6d8789ace089a51c",
|
| 156 |
+
"row_ids": [
|
| 157 |
+
"seed-10000-turn-0",
|
| 158 |
+
"seed-10000-turn-1",
|
| 159 |
+
"seed-10000-turn-2",
|
| 160 |
+
"seed-10000-turn-3"
|
| 161 |
+
],
|
| 162 |
+
"max_absolute_probability_difference": 0.0,
|
| 163 |
+
"tolerance": 0.0001,
|
| 164 |
+
"elapsed_seconds": 5.435347482009092
|
| 165 |
+
}
|
| 166 |
+
},
|
| 167 |
+
"runs/probe-reload-v1/report.json": {
|
| 168 |
+
"sha256": "174d5267b1825bbd66bcf4456fdc97b505e7a3565557462358ab99c66bd11539",
|
| 169 |
+
"report": {
|
| 170 |
+
"status": "failed",
|
| 171 |
+
"reload": true,
|
| 172 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 173 |
+
"total_vram": 33711521792,
|
| 174 |
+
"initial_free_vram": 33179500544,
|
| 175 |
+
"torch": "2.14.1+cu130",
|
| 176 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 177 |
+
"source_commit": "e38ac536d0a6af444b48fddd6d8789ace089a51c",
|
| 178 |
+
"row_ids": [
|
| 179 |
+
"seed-10000-turn-0",
|
| 180 |
+
"seed-10000-turn-1",
|
| 181 |
+
"seed-10000-turn-2",
|
| 182 |
+
"seed-10000-turn-3"
|
| 183 |
+
],
|
| 184 |
+
"error": "ValueError: saved adapter configuration differs from checkpoint metadata",
|
| 185 |
+
"elapsed_seconds": 4.1464992630062625
|
| 186 |
+
}
|
| 187 |
+
},
|
| 188 |
+
"runs/probe-reload-v2/report.json": {
|
| 189 |
+
"sha256": "1687e90ab5518785bc3c6a1701978828f410624bab6e297e5a4a2deb8bdcf2e3",
|
| 190 |
+
"report": {
|
| 191 |
+
"status": "passed",
|
| 192 |
+
"reload": true,
|
| 193 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 194 |
+
"total_vram": 33711521792,
|
| 195 |
+
"initial_free_vram": 33179500544,
|
| 196 |
+
"torch": "2.14.1+cu130",
|
| 197 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 198 |
+
"source_commit": "f7be7ceee9c38566dfebad0ebf5f1c04958faee3",
|
| 199 |
+
"row_ids": [
|
| 200 |
+
"seed-10000-turn-0",
|
| 201 |
+
"seed-10000-turn-1",
|
| 202 |
+
"seed-10000-turn-2",
|
| 203 |
+
"seed-10000-turn-3"
|
| 204 |
+
],
|
| 205 |
+
"max_absolute_probability_difference": 0.0,
|
| 206 |
+
"tolerance": 0.0001,
|
| 207 |
+
"elapsed_seconds": 5.648779143986758
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
}
|
code/reports/selected-checkpoint-reload.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"status": "passed",
|
| 3 |
+
"reload": true,
|
| 4 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 5 |
+
"total_vram": 33711521792,
|
| 6 |
+
"initial_free_vram": 33179500544,
|
| 7 |
+
"torch": "2.14.1+cu130",
|
| 8 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 9 |
+
"row_ids": [
|
| 10 |
+
"seed-10000-turn-0",
|
| 11 |
+
"seed-10000-turn-1",
|
| 12 |
+
"seed-10000-turn-2",
|
| 13 |
+
"seed-10000-turn-3"
|
| 14 |
+
],
|
| 15 |
+
"source_commit": "7a21f6491bcaf2afacc9aa5b5e2733a35c41a7a8",
|
| 16 |
+
"source_dirty": false,
|
| 17 |
+
"source_identity_method": "exact-git-root",
|
| 18 |
+
"max_absolute_probability_difference": 0.0,
|
| 19 |
+
"tolerance": 0.0001,
|
| 20 |
+
"elapsed_seconds": 5.802506145002553
|
| 21 |
+
}
|
code/reports/study-preregistration.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Archived training and final-test preregistration
|
| 2 |
+
|
| 3 |
+
The excerpt below is preserved verbatim from `record.md` at training launch commit
|
| 4 |
+
`d9fcd05c02e9327d4d791f2338725092cede8bdd`. The original complete record's SHA256 is
|
| 5 |
+
`bb18a6fc7c21d3d186afcf682e724015263340f3c269500951f8f159054453e1`. This archive lets readers inspect the
|
| 6 |
+
recorded decisions without access to the original Git repository. It documents
|
| 7 |
+
what was recorded; it is not a signed timestamp or independent registration service.
|
| 8 |
+
|
| 9 |
+
The final checkpoint selection and evaluation request provide separate hashes of
|
| 10 |
+
the actual artifacts used. This archive is historical: later outcomes belong in
|
| 11 |
+
the training, validation, reload and final evaluation reports.
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## 2026-10-05 — M4 training and M5 preregistration
|
| 16 |
+
|
| 17 |
+
Real-weight probe from e38ac53 passed3head-only and5rank4LoRA steps. Both trainable groups changed; full frozen parameter hashes were unchanged. All recorded losses and gradients finite, head and LoRA gradients nonzero. LoRA peak allocated23,180,389,888bytes/reserved23,595,057,152bytes; step time0.67–1.32s on1302–2252tokens. Wrapper restored GLM and verified its in-container health. Fresh-process parity is the remaining M4 gate; artifacts runs/probe-v1 and runs/probe-reload-v1.
|
| 18 |
+
|
| 19 |
+
Conditional on M4 reload passing, preregister M5: one seed42, BF16 native backbone, FP32 native head, rank4/alpha8/dropout0 text-only LoRA, no quantization, max4096tokens, AdamW lr1e-5/weight_decay0.01, batch1, gradient accumulation8 (last partial group normalized by actual size), norm clip1, CE label smoothing0.05 plus Brier sum weight0.1. Train exactly two epochs over the827 fixed training positions shuffled deterministically per epoch; save both epoch checkpoints. Bounded search is these two checkpoints only; exclude feasibility-probe checkpoints. Use all215 validation positions to select the epoch with lowest finite mean target NLL; exact tie chooses earlier epoch. Report teacher agreement and Brier as diagnostics, not selection overrides. If either candidate has inference errors or nonfinite NLL it is ineligible. If both fail, preserve evidence and reconsider before tests. Initial fulltraining timeout3600s; no test access during training/selection.
|
| 20 |
+
|
| 21 |
+
Freeze final evaluation at200paired seeds30000–30199 and200piececap, bootstrap10000replicates/seed2026, error threshold0. Primary comparison selectedtrained versus unchanged nativeBF16headClef. Include an unchangedFP32head ablation to separate precision change from learning, plus random and heuristic. Same rules, complete observation encoding, pinnedbackbone, legalchoices and preview. Three neural policies at200×200moves and144ms imply4.8h worst-case, but unchangedbase dies around28pieces in development; expect much less. Allow bounded per-policy execution with incremental artifacts; never drop failedseeds or quietly reducepool. Record exact checkpoint and validation evidence hashes before any testepisode. Publish negative outcomes honestly; no tuning after testresults.
|
| 22 |
+
|
| 23 |
+
Fresh LoRA reload v1 correctly rejected PEFT's shortened target list. Saved496adapter tensors independently map exactly248intended text layers; weights were sound. Loader now expands saved targets over every backbone module and requires exact target equality, rejecting accidental vision/MTP/output matches. A32-layer tiny-backbone regression reproduces PEFT shortening. New saves use explicit full targets and canonical pinned model ID. Original failed artifact and probe weights preserved; retry uses same checkpoint, not a new training configuration. Head-only fresh-process reload passed independently; GLM restored healthy on both success and rejectedreload.
|
| 24 |
+
|
| 25 |
+
M4 complete: head-only fresh reload maxabsoluteprobabilitydifference0.0 (tolerance1e-4); unchanged saved LoRA checkpoint retry after semantic-target validation also0.0. Both fresh processes loaded pinned weights; no retraining needed for reload fix. Compact reports/gpu-feasibility.json retains success and original rejectedreload with sourcehashes. Fulltraining preregistration above is now enabled; output runs/study-v1 with two epoch checkpoints,3600second wrapper timeout and restored GLM health.
|
code/reports/study-training.json
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"changed_trainable_parameter_names": [
|
| 3 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.out_proj.lora_A.default.weight",
|
| 4 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.out_proj.lora_B.default.weight",
|
| 5 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 6 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 7 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_z.lora_A.default.weight",
|
| 8 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_z.lora_B.default.weight",
|
| 9 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_b.lora_A.default.weight",
|
| 10 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_b.lora_B.default.weight",
|
| 11 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_a.lora_A.default.weight",
|
| 12 |
+
"language_model.base_model.model.model.language_model.layers.0.linear_attn.in_proj_a.lora_B.default.weight",
|
| 13 |
+
"language_model.base_model.model.model.language_model.layers.0.mlp.gate_proj.lora_A.default.weight",
|
| 14 |
+
"language_model.base_model.model.model.language_model.layers.0.mlp.gate_proj.lora_B.default.weight",
|
| 15 |
+
"language_model.base_model.model.model.language_model.layers.0.mlp.up_proj.lora_A.default.weight",
|
| 16 |
+
"language_model.base_model.model.model.language_model.layers.0.mlp.up_proj.lora_B.default.weight",
|
| 17 |
+
"language_model.base_model.model.model.language_model.layers.0.mlp.down_proj.lora_A.default.weight",
|
| 18 |
+
"language_model.base_model.model.model.language_model.layers.0.mlp.down_proj.lora_B.default.weight",
|
| 19 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.out_proj.lora_A.default.weight",
|
| 20 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.out_proj.lora_B.default.weight",
|
| 21 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 22 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 23 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_z.lora_A.default.weight",
|
| 24 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_z.lora_B.default.weight",
|
| 25 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_b.lora_A.default.weight",
|
| 26 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_b.lora_B.default.weight",
|
| 27 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_a.lora_A.default.weight",
|
| 28 |
+
"language_model.base_model.model.model.language_model.layers.1.linear_attn.in_proj_a.lora_B.default.weight",
|
| 29 |
+
"language_model.base_model.model.model.language_model.layers.1.mlp.gate_proj.lora_A.default.weight",
|
| 30 |
+
"language_model.base_model.model.model.language_model.layers.1.mlp.gate_proj.lora_B.default.weight",
|
| 31 |
+
"language_model.base_model.model.model.language_model.layers.1.mlp.up_proj.lora_A.default.weight",
|
| 32 |
+
"language_model.base_model.model.model.language_model.layers.1.mlp.up_proj.lora_B.default.weight",
|
| 33 |
+
"language_model.base_model.model.model.language_model.layers.1.mlp.down_proj.lora_A.default.weight",
|
| 34 |
+
"language_model.base_model.model.model.language_model.layers.1.mlp.down_proj.lora_B.default.weight",
|
| 35 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.out_proj.lora_A.default.weight",
|
| 36 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.out_proj.lora_B.default.weight",
|
| 37 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 38 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 39 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_z.lora_A.default.weight",
|
| 40 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_z.lora_B.default.weight",
|
| 41 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_b.lora_A.default.weight",
|
| 42 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_b.lora_B.default.weight",
|
| 43 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_a.lora_A.default.weight",
|
| 44 |
+
"language_model.base_model.model.model.language_model.layers.2.linear_attn.in_proj_a.lora_B.default.weight",
|
| 45 |
+
"language_model.base_model.model.model.language_model.layers.2.mlp.gate_proj.lora_A.default.weight",
|
| 46 |
+
"language_model.base_model.model.model.language_model.layers.2.mlp.gate_proj.lora_B.default.weight",
|
| 47 |
+
"language_model.base_model.model.model.language_model.layers.2.mlp.up_proj.lora_A.default.weight",
|
| 48 |
+
"language_model.base_model.model.model.language_model.layers.2.mlp.up_proj.lora_B.default.weight",
|
| 49 |
+
"language_model.base_model.model.model.language_model.layers.2.mlp.down_proj.lora_A.default.weight",
|
| 50 |
+
"language_model.base_model.model.model.language_model.layers.2.mlp.down_proj.lora_B.default.weight",
|
| 51 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.q_proj.lora_A.default.weight",
|
| 52 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.q_proj.lora_B.default.weight",
|
| 53 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.k_proj.lora_A.default.weight",
|
| 54 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.k_proj.lora_B.default.weight",
|
| 55 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.v_proj.lora_A.default.weight",
|
| 56 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.v_proj.lora_B.default.weight",
|
| 57 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.o_proj.lora_A.default.weight",
|
| 58 |
+
"language_model.base_model.model.model.language_model.layers.3.self_attn.o_proj.lora_B.default.weight",
|
| 59 |
+
"language_model.base_model.model.model.language_model.layers.3.mlp.gate_proj.lora_A.default.weight",
|
| 60 |
+
"language_model.base_model.model.model.language_model.layers.3.mlp.gate_proj.lora_B.default.weight",
|
| 61 |
+
"language_model.base_model.model.model.language_model.layers.3.mlp.up_proj.lora_A.default.weight",
|
| 62 |
+
"language_model.base_model.model.model.language_model.layers.3.mlp.up_proj.lora_B.default.weight",
|
| 63 |
+
"language_model.base_model.model.model.language_model.layers.3.mlp.down_proj.lora_A.default.weight",
|
| 64 |
+
"language_model.base_model.model.model.language_model.layers.3.mlp.down_proj.lora_B.default.weight",
|
| 65 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.out_proj.lora_A.default.weight",
|
| 66 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.out_proj.lora_B.default.weight",
|
| 67 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 68 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 69 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_z.lora_A.default.weight",
|
| 70 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_z.lora_B.default.weight",
|
| 71 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_b.lora_A.default.weight",
|
| 72 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_b.lora_B.default.weight",
|
| 73 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_a.lora_A.default.weight",
|
| 74 |
+
"language_model.base_model.model.model.language_model.layers.4.linear_attn.in_proj_a.lora_B.default.weight",
|
| 75 |
+
"language_model.base_model.model.model.language_model.layers.4.mlp.gate_proj.lora_A.default.weight",
|
| 76 |
+
"language_model.base_model.model.model.language_model.layers.4.mlp.gate_proj.lora_B.default.weight",
|
| 77 |
+
"language_model.base_model.model.model.language_model.layers.4.mlp.up_proj.lora_A.default.weight",
|
| 78 |
+
"language_model.base_model.model.model.language_model.layers.4.mlp.up_proj.lora_B.default.weight",
|
| 79 |
+
"language_model.base_model.model.model.language_model.layers.4.mlp.down_proj.lora_A.default.weight",
|
| 80 |
+
"language_model.base_model.model.model.language_model.layers.4.mlp.down_proj.lora_B.default.weight",
|
| 81 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.out_proj.lora_A.default.weight",
|
| 82 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.out_proj.lora_B.default.weight",
|
| 83 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 84 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 85 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_z.lora_A.default.weight",
|
| 86 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_z.lora_B.default.weight",
|
| 87 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_b.lora_A.default.weight",
|
| 88 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_b.lora_B.default.weight",
|
| 89 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_a.lora_A.default.weight",
|
| 90 |
+
"language_model.base_model.model.model.language_model.layers.5.linear_attn.in_proj_a.lora_B.default.weight",
|
| 91 |
+
"language_model.base_model.model.model.language_model.layers.5.mlp.gate_proj.lora_A.default.weight",
|
| 92 |
+
"language_model.base_model.model.model.language_model.layers.5.mlp.gate_proj.lora_B.default.weight",
|
| 93 |
+
"language_model.base_model.model.model.language_model.layers.5.mlp.up_proj.lora_A.default.weight",
|
| 94 |
+
"language_model.base_model.model.model.language_model.layers.5.mlp.up_proj.lora_B.default.weight",
|
| 95 |
+
"language_model.base_model.model.model.language_model.layers.5.mlp.down_proj.lora_A.default.weight",
|
| 96 |
+
"language_model.base_model.model.model.language_model.layers.5.mlp.down_proj.lora_B.default.weight",
|
| 97 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.out_proj.lora_A.default.weight",
|
| 98 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.out_proj.lora_B.default.weight",
|
| 99 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 100 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 101 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_z.lora_A.default.weight",
|
| 102 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_z.lora_B.default.weight",
|
| 103 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_b.lora_A.default.weight",
|
| 104 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_b.lora_B.default.weight",
|
| 105 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_a.lora_A.default.weight",
|
| 106 |
+
"language_model.base_model.model.model.language_model.layers.6.linear_attn.in_proj_a.lora_B.default.weight",
|
| 107 |
+
"language_model.base_model.model.model.language_model.layers.6.mlp.gate_proj.lora_A.default.weight",
|
| 108 |
+
"language_model.base_model.model.model.language_model.layers.6.mlp.gate_proj.lora_B.default.weight",
|
| 109 |
+
"language_model.base_model.model.model.language_model.layers.6.mlp.up_proj.lora_A.default.weight",
|
| 110 |
+
"language_model.base_model.model.model.language_model.layers.6.mlp.up_proj.lora_B.default.weight",
|
| 111 |
+
"language_model.base_model.model.model.language_model.layers.6.mlp.down_proj.lora_A.default.weight",
|
| 112 |
+
"language_model.base_model.model.model.language_model.layers.6.mlp.down_proj.lora_B.default.weight",
|
| 113 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.q_proj.lora_A.default.weight",
|
| 114 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.q_proj.lora_B.default.weight",
|
| 115 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.k_proj.lora_A.default.weight",
|
| 116 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.k_proj.lora_B.default.weight",
|
| 117 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.v_proj.lora_A.default.weight",
|
| 118 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.v_proj.lora_B.default.weight",
|
| 119 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.o_proj.lora_A.default.weight",
|
| 120 |
+
"language_model.base_model.model.model.language_model.layers.7.self_attn.o_proj.lora_B.default.weight",
|
| 121 |
+
"language_model.base_model.model.model.language_model.layers.7.mlp.gate_proj.lora_A.default.weight",
|
| 122 |
+
"language_model.base_model.model.model.language_model.layers.7.mlp.gate_proj.lora_B.default.weight",
|
| 123 |
+
"language_model.base_model.model.model.language_model.layers.7.mlp.up_proj.lora_A.default.weight",
|
| 124 |
+
"language_model.base_model.model.model.language_model.layers.7.mlp.up_proj.lora_B.default.weight",
|
| 125 |
+
"language_model.base_model.model.model.language_model.layers.7.mlp.down_proj.lora_A.default.weight",
|
| 126 |
+
"language_model.base_model.model.model.language_model.layers.7.mlp.down_proj.lora_B.default.weight",
|
| 127 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.out_proj.lora_A.default.weight",
|
| 128 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.out_proj.lora_B.default.weight",
|
| 129 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 130 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 131 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_z.lora_A.default.weight",
|
| 132 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_z.lora_B.default.weight",
|
| 133 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_b.lora_A.default.weight",
|
| 134 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_b.lora_B.default.weight",
|
| 135 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_a.lora_A.default.weight",
|
| 136 |
+
"language_model.base_model.model.model.language_model.layers.8.linear_attn.in_proj_a.lora_B.default.weight",
|
| 137 |
+
"language_model.base_model.model.model.language_model.layers.8.mlp.gate_proj.lora_A.default.weight",
|
| 138 |
+
"language_model.base_model.model.model.language_model.layers.8.mlp.gate_proj.lora_B.default.weight",
|
| 139 |
+
"language_model.base_model.model.model.language_model.layers.8.mlp.up_proj.lora_A.default.weight",
|
| 140 |
+
"language_model.base_model.model.model.language_model.layers.8.mlp.up_proj.lora_B.default.weight",
|
| 141 |
+
"language_model.base_model.model.model.language_model.layers.8.mlp.down_proj.lora_A.default.weight",
|
| 142 |
+
"language_model.base_model.model.model.language_model.layers.8.mlp.down_proj.lora_B.default.weight",
|
| 143 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.out_proj.lora_A.default.weight",
|
| 144 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.out_proj.lora_B.default.weight",
|
| 145 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 146 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 147 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_z.lora_A.default.weight",
|
| 148 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_z.lora_B.default.weight",
|
| 149 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_b.lora_A.default.weight",
|
| 150 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_b.lora_B.default.weight",
|
| 151 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_a.lora_A.default.weight",
|
| 152 |
+
"language_model.base_model.model.model.language_model.layers.9.linear_attn.in_proj_a.lora_B.default.weight",
|
| 153 |
+
"language_model.base_model.model.model.language_model.layers.9.mlp.gate_proj.lora_A.default.weight",
|
| 154 |
+
"language_model.base_model.model.model.language_model.layers.9.mlp.gate_proj.lora_B.default.weight",
|
| 155 |
+
"language_model.base_model.model.model.language_model.layers.9.mlp.up_proj.lora_A.default.weight",
|
| 156 |
+
"language_model.base_model.model.model.language_model.layers.9.mlp.up_proj.lora_B.default.weight",
|
| 157 |
+
"language_model.base_model.model.model.language_model.layers.9.mlp.down_proj.lora_A.default.weight",
|
| 158 |
+
"language_model.base_model.model.model.language_model.layers.9.mlp.down_proj.lora_B.default.weight",
|
| 159 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.out_proj.lora_A.default.weight",
|
| 160 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.out_proj.lora_B.default.weight",
|
| 161 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 162 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 163 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_z.lora_A.default.weight",
|
| 164 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_z.lora_B.default.weight",
|
| 165 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_b.lora_A.default.weight",
|
| 166 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_b.lora_B.default.weight",
|
| 167 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_a.lora_A.default.weight",
|
| 168 |
+
"language_model.base_model.model.model.language_model.layers.10.linear_attn.in_proj_a.lora_B.default.weight",
|
| 169 |
+
"language_model.base_model.model.model.language_model.layers.10.mlp.gate_proj.lora_A.default.weight",
|
| 170 |
+
"language_model.base_model.model.model.language_model.layers.10.mlp.gate_proj.lora_B.default.weight",
|
| 171 |
+
"language_model.base_model.model.model.language_model.layers.10.mlp.up_proj.lora_A.default.weight",
|
| 172 |
+
"language_model.base_model.model.model.language_model.layers.10.mlp.up_proj.lora_B.default.weight",
|
| 173 |
+
"language_model.base_model.model.model.language_model.layers.10.mlp.down_proj.lora_A.default.weight",
|
| 174 |
+
"language_model.base_model.model.model.language_model.layers.10.mlp.down_proj.lora_B.default.weight",
|
| 175 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.q_proj.lora_A.default.weight",
|
| 176 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.q_proj.lora_B.default.weight",
|
| 177 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.k_proj.lora_A.default.weight",
|
| 178 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.k_proj.lora_B.default.weight",
|
| 179 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.v_proj.lora_A.default.weight",
|
| 180 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.v_proj.lora_B.default.weight",
|
| 181 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.o_proj.lora_A.default.weight",
|
| 182 |
+
"language_model.base_model.model.model.language_model.layers.11.self_attn.o_proj.lora_B.default.weight",
|
| 183 |
+
"language_model.base_model.model.model.language_model.layers.11.mlp.gate_proj.lora_A.default.weight",
|
| 184 |
+
"language_model.base_model.model.model.language_model.layers.11.mlp.gate_proj.lora_B.default.weight",
|
| 185 |
+
"language_model.base_model.model.model.language_model.layers.11.mlp.up_proj.lora_A.default.weight",
|
| 186 |
+
"language_model.base_model.model.model.language_model.layers.11.mlp.up_proj.lora_B.default.weight",
|
| 187 |
+
"language_model.base_model.model.model.language_model.layers.11.mlp.down_proj.lora_A.default.weight",
|
| 188 |
+
"language_model.base_model.model.model.language_model.layers.11.mlp.down_proj.lora_B.default.weight",
|
| 189 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.out_proj.lora_A.default.weight",
|
| 190 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.out_proj.lora_B.default.weight",
|
| 191 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 192 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 193 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_z.lora_A.default.weight",
|
| 194 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_z.lora_B.default.weight",
|
| 195 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_b.lora_A.default.weight",
|
| 196 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_b.lora_B.default.weight",
|
| 197 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_a.lora_A.default.weight",
|
| 198 |
+
"language_model.base_model.model.model.language_model.layers.12.linear_attn.in_proj_a.lora_B.default.weight",
|
| 199 |
+
"language_model.base_model.model.model.language_model.layers.12.mlp.gate_proj.lora_A.default.weight",
|
| 200 |
+
"language_model.base_model.model.model.language_model.layers.12.mlp.gate_proj.lora_B.default.weight",
|
| 201 |
+
"language_model.base_model.model.model.language_model.layers.12.mlp.up_proj.lora_A.default.weight",
|
| 202 |
+
"language_model.base_model.model.model.language_model.layers.12.mlp.up_proj.lora_B.default.weight",
|
| 203 |
+
"language_model.base_model.model.model.language_model.layers.12.mlp.down_proj.lora_A.default.weight",
|
| 204 |
+
"language_model.base_model.model.model.language_model.layers.12.mlp.down_proj.lora_B.default.weight",
|
| 205 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.out_proj.lora_A.default.weight",
|
| 206 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.out_proj.lora_B.default.weight",
|
| 207 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 208 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 209 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_z.lora_A.default.weight",
|
| 210 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_z.lora_B.default.weight",
|
| 211 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_b.lora_A.default.weight",
|
| 212 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_b.lora_B.default.weight",
|
| 213 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_a.lora_A.default.weight",
|
| 214 |
+
"language_model.base_model.model.model.language_model.layers.13.linear_attn.in_proj_a.lora_B.default.weight",
|
| 215 |
+
"language_model.base_model.model.model.language_model.layers.13.mlp.gate_proj.lora_A.default.weight",
|
| 216 |
+
"language_model.base_model.model.model.language_model.layers.13.mlp.gate_proj.lora_B.default.weight",
|
| 217 |
+
"language_model.base_model.model.model.language_model.layers.13.mlp.up_proj.lora_A.default.weight",
|
| 218 |
+
"language_model.base_model.model.model.language_model.layers.13.mlp.up_proj.lora_B.default.weight",
|
| 219 |
+
"language_model.base_model.model.model.language_model.layers.13.mlp.down_proj.lora_A.default.weight",
|
| 220 |
+
"language_model.base_model.model.model.language_model.layers.13.mlp.down_proj.lora_B.default.weight",
|
| 221 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.out_proj.lora_A.default.weight",
|
| 222 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.out_proj.lora_B.default.weight",
|
| 223 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 224 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 225 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_z.lora_A.default.weight",
|
| 226 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_z.lora_B.default.weight",
|
| 227 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_b.lora_A.default.weight",
|
| 228 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_b.lora_B.default.weight",
|
| 229 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_a.lora_A.default.weight",
|
| 230 |
+
"language_model.base_model.model.model.language_model.layers.14.linear_attn.in_proj_a.lora_B.default.weight",
|
| 231 |
+
"language_model.base_model.model.model.language_model.layers.14.mlp.gate_proj.lora_A.default.weight",
|
| 232 |
+
"language_model.base_model.model.model.language_model.layers.14.mlp.gate_proj.lora_B.default.weight",
|
| 233 |
+
"language_model.base_model.model.model.language_model.layers.14.mlp.up_proj.lora_A.default.weight",
|
| 234 |
+
"language_model.base_model.model.model.language_model.layers.14.mlp.up_proj.lora_B.default.weight",
|
| 235 |
+
"language_model.base_model.model.model.language_model.layers.14.mlp.down_proj.lora_A.default.weight",
|
| 236 |
+
"language_model.base_model.model.model.language_model.layers.14.mlp.down_proj.lora_B.default.weight",
|
| 237 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.q_proj.lora_A.default.weight",
|
| 238 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.q_proj.lora_B.default.weight",
|
| 239 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.k_proj.lora_A.default.weight",
|
| 240 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.k_proj.lora_B.default.weight",
|
| 241 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.v_proj.lora_A.default.weight",
|
| 242 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.v_proj.lora_B.default.weight",
|
| 243 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.o_proj.lora_A.default.weight",
|
| 244 |
+
"language_model.base_model.model.model.language_model.layers.15.self_attn.o_proj.lora_B.default.weight",
|
| 245 |
+
"language_model.base_model.model.model.language_model.layers.15.mlp.gate_proj.lora_A.default.weight",
|
| 246 |
+
"language_model.base_model.model.model.language_model.layers.15.mlp.gate_proj.lora_B.default.weight",
|
| 247 |
+
"language_model.base_model.model.model.language_model.layers.15.mlp.up_proj.lora_A.default.weight",
|
| 248 |
+
"language_model.base_model.model.model.language_model.layers.15.mlp.up_proj.lora_B.default.weight",
|
| 249 |
+
"language_model.base_model.model.model.language_model.layers.15.mlp.down_proj.lora_A.default.weight",
|
| 250 |
+
"language_model.base_model.model.model.language_model.layers.15.mlp.down_proj.lora_B.default.weight",
|
| 251 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.out_proj.lora_A.default.weight",
|
| 252 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.out_proj.lora_B.default.weight",
|
| 253 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 254 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 255 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_z.lora_A.default.weight",
|
| 256 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_z.lora_B.default.weight",
|
| 257 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_b.lora_A.default.weight",
|
| 258 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_b.lora_B.default.weight",
|
| 259 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_a.lora_A.default.weight",
|
| 260 |
+
"language_model.base_model.model.model.language_model.layers.16.linear_attn.in_proj_a.lora_B.default.weight",
|
| 261 |
+
"language_model.base_model.model.model.language_model.layers.16.mlp.gate_proj.lora_A.default.weight",
|
| 262 |
+
"language_model.base_model.model.model.language_model.layers.16.mlp.gate_proj.lora_B.default.weight",
|
| 263 |
+
"language_model.base_model.model.model.language_model.layers.16.mlp.up_proj.lora_A.default.weight",
|
| 264 |
+
"language_model.base_model.model.model.language_model.layers.16.mlp.up_proj.lora_B.default.weight",
|
| 265 |
+
"language_model.base_model.model.model.language_model.layers.16.mlp.down_proj.lora_A.default.weight",
|
| 266 |
+
"language_model.base_model.model.model.language_model.layers.16.mlp.down_proj.lora_B.default.weight",
|
| 267 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.out_proj.lora_A.default.weight",
|
| 268 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.out_proj.lora_B.default.weight",
|
| 269 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 270 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 271 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_z.lora_A.default.weight",
|
| 272 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_z.lora_B.default.weight",
|
| 273 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_b.lora_A.default.weight",
|
| 274 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_b.lora_B.default.weight",
|
| 275 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_a.lora_A.default.weight",
|
| 276 |
+
"language_model.base_model.model.model.language_model.layers.17.linear_attn.in_proj_a.lora_B.default.weight",
|
| 277 |
+
"language_model.base_model.model.model.language_model.layers.17.mlp.gate_proj.lora_A.default.weight",
|
| 278 |
+
"language_model.base_model.model.model.language_model.layers.17.mlp.gate_proj.lora_B.default.weight",
|
| 279 |
+
"language_model.base_model.model.model.language_model.layers.17.mlp.up_proj.lora_A.default.weight",
|
| 280 |
+
"language_model.base_model.model.model.language_model.layers.17.mlp.up_proj.lora_B.default.weight",
|
| 281 |
+
"language_model.base_model.model.model.language_model.layers.17.mlp.down_proj.lora_A.default.weight",
|
| 282 |
+
"language_model.base_model.model.model.language_model.layers.17.mlp.down_proj.lora_B.default.weight",
|
| 283 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.out_proj.lora_A.default.weight",
|
| 284 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.out_proj.lora_B.default.weight",
|
| 285 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 286 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 287 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_z.lora_A.default.weight",
|
| 288 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_z.lora_B.default.weight",
|
| 289 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_b.lora_A.default.weight",
|
| 290 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_b.lora_B.default.weight",
|
| 291 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_a.lora_A.default.weight",
|
| 292 |
+
"language_model.base_model.model.model.language_model.layers.18.linear_attn.in_proj_a.lora_B.default.weight",
|
| 293 |
+
"language_model.base_model.model.model.language_model.layers.18.mlp.gate_proj.lora_A.default.weight",
|
| 294 |
+
"language_model.base_model.model.model.language_model.layers.18.mlp.gate_proj.lora_B.default.weight",
|
| 295 |
+
"language_model.base_model.model.model.language_model.layers.18.mlp.up_proj.lora_A.default.weight",
|
| 296 |
+
"language_model.base_model.model.model.language_model.layers.18.mlp.up_proj.lora_B.default.weight",
|
| 297 |
+
"language_model.base_model.model.model.language_model.layers.18.mlp.down_proj.lora_A.default.weight",
|
| 298 |
+
"language_model.base_model.model.model.language_model.layers.18.mlp.down_proj.lora_B.default.weight",
|
| 299 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.q_proj.lora_A.default.weight",
|
| 300 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.q_proj.lora_B.default.weight",
|
| 301 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.k_proj.lora_A.default.weight",
|
| 302 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.k_proj.lora_B.default.weight",
|
| 303 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.v_proj.lora_A.default.weight",
|
| 304 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.v_proj.lora_B.default.weight",
|
| 305 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.o_proj.lora_A.default.weight",
|
| 306 |
+
"language_model.base_model.model.model.language_model.layers.19.self_attn.o_proj.lora_B.default.weight",
|
| 307 |
+
"language_model.base_model.model.model.language_model.layers.19.mlp.gate_proj.lora_A.default.weight",
|
| 308 |
+
"language_model.base_model.model.model.language_model.layers.19.mlp.gate_proj.lora_B.default.weight",
|
| 309 |
+
"language_model.base_model.model.model.language_model.layers.19.mlp.up_proj.lora_A.default.weight",
|
| 310 |
+
"language_model.base_model.model.model.language_model.layers.19.mlp.up_proj.lora_B.default.weight",
|
| 311 |
+
"language_model.base_model.model.model.language_model.layers.19.mlp.down_proj.lora_A.default.weight",
|
| 312 |
+
"language_model.base_model.model.model.language_model.layers.19.mlp.down_proj.lora_B.default.weight",
|
| 313 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.out_proj.lora_A.default.weight",
|
| 314 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.out_proj.lora_B.default.weight",
|
| 315 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 316 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 317 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_z.lora_A.default.weight",
|
| 318 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_z.lora_B.default.weight",
|
| 319 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_b.lora_A.default.weight",
|
| 320 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_b.lora_B.default.weight",
|
| 321 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_a.lora_A.default.weight",
|
| 322 |
+
"language_model.base_model.model.model.language_model.layers.20.linear_attn.in_proj_a.lora_B.default.weight",
|
| 323 |
+
"language_model.base_model.model.model.language_model.layers.20.mlp.gate_proj.lora_A.default.weight",
|
| 324 |
+
"language_model.base_model.model.model.language_model.layers.20.mlp.gate_proj.lora_B.default.weight",
|
| 325 |
+
"language_model.base_model.model.model.language_model.layers.20.mlp.up_proj.lora_A.default.weight",
|
| 326 |
+
"language_model.base_model.model.model.language_model.layers.20.mlp.up_proj.lora_B.default.weight",
|
| 327 |
+
"language_model.base_model.model.model.language_model.layers.20.mlp.down_proj.lora_A.default.weight",
|
| 328 |
+
"language_model.base_model.model.model.language_model.layers.20.mlp.down_proj.lora_B.default.weight",
|
| 329 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.out_proj.lora_A.default.weight",
|
| 330 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.out_proj.lora_B.default.weight",
|
| 331 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 332 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 333 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_z.lora_A.default.weight",
|
| 334 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_z.lora_B.default.weight",
|
| 335 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_b.lora_A.default.weight",
|
| 336 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_b.lora_B.default.weight",
|
| 337 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_a.lora_A.default.weight",
|
| 338 |
+
"language_model.base_model.model.model.language_model.layers.21.linear_attn.in_proj_a.lora_B.default.weight",
|
| 339 |
+
"language_model.base_model.model.model.language_model.layers.21.mlp.gate_proj.lora_A.default.weight",
|
| 340 |
+
"language_model.base_model.model.model.language_model.layers.21.mlp.gate_proj.lora_B.default.weight",
|
| 341 |
+
"language_model.base_model.model.model.language_model.layers.21.mlp.up_proj.lora_A.default.weight",
|
| 342 |
+
"language_model.base_model.model.model.language_model.layers.21.mlp.up_proj.lora_B.default.weight",
|
| 343 |
+
"language_model.base_model.model.model.language_model.layers.21.mlp.down_proj.lora_A.default.weight",
|
| 344 |
+
"language_model.base_model.model.model.language_model.layers.21.mlp.down_proj.lora_B.default.weight",
|
| 345 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.out_proj.lora_A.default.weight",
|
| 346 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.out_proj.lora_B.default.weight",
|
| 347 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 348 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 349 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_z.lora_A.default.weight",
|
| 350 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_z.lora_B.default.weight",
|
| 351 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_b.lora_A.default.weight",
|
| 352 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_b.lora_B.default.weight",
|
| 353 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_a.lora_A.default.weight",
|
| 354 |
+
"language_model.base_model.model.model.language_model.layers.22.linear_attn.in_proj_a.lora_B.default.weight",
|
| 355 |
+
"language_model.base_model.model.model.language_model.layers.22.mlp.gate_proj.lora_A.default.weight",
|
| 356 |
+
"language_model.base_model.model.model.language_model.layers.22.mlp.gate_proj.lora_B.default.weight",
|
| 357 |
+
"language_model.base_model.model.model.language_model.layers.22.mlp.up_proj.lora_A.default.weight",
|
| 358 |
+
"language_model.base_model.model.model.language_model.layers.22.mlp.up_proj.lora_B.default.weight",
|
| 359 |
+
"language_model.base_model.model.model.language_model.layers.22.mlp.down_proj.lora_A.default.weight",
|
| 360 |
+
"language_model.base_model.model.model.language_model.layers.22.mlp.down_proj.lora_B.default.weight",
|
| 361 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.q_proj.lora_A.default.weight",
|
| 362 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.q_proj.lora_B.default.weight",
|
| 363 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.k_proj.lora_A.default.weight",
|
| 364 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.k_proj.lora_B.default.weight",
|
| 365 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.v_proj.lora_A.default.weight",
|
| 366 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.v_proj.lora_B.default.weight",
|
| 367 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.o_proj.lora_A.default.weight",
|
| 368 |
+
"language_model.base_model.model.model.language_model.layers.23.self_attn.o_proj.lora_B.default.weight",
|
| 369 |
+
"language_model.base_model.model.model.language_model.layers.23.mlp.gate_proj.lora_A.default.weight",
|
| 370 |
+
"language_model.base_model.model.model.language_model.layers.23.mlp.gate_proj.lora_B.default.weight",
|
| 371 |
+
"language_model.base_model.model.model.language_model.layers.23.mlp.up_proj.lora_A.default.weight",
|
| 372 |
+
"language_model.base_model.model.model.language_model.layers.23.mlp.up_proj.lora_B.default.weight",
|
| 373 |
+
"language_model.base_model.model.model.language_model.layers.23.mlp.down_proj.lora_A.default.weight",
|
| 374 |
+
"language_model.base_model.model.model.language_model.layers.23.mlp.down_proj.lora_B.default.weight",
|
| 375 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.out_proj.lora_A.default.weight",
|
| 376 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.out_proj.lora_B.default.weight",
|
| 377 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 378 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 379 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_z.lora_A.default.weight",
|
| 380 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_z.lora_B.default.weight",
|
| 381 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_b.lora_A.default.weight",
|
| 382 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_b.lora_B.default.weight",
|
| 383 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_a.lora_A.default.weight",
|
| 384 |
+
"language_model.base_model.model.model.language_model.layers.24.linear_attn.in_proj_a.lora_B.default.weight",
|
| 385 |
+
"language_model.base_model.model.model.language_model.layers.24.mlp.gate_proj.lora_A.default.weight",
|
| 386 |
+
"language_model.base_model.model.model.language_model.layers.24.mlp.gate_proj.lora_B.default.weight",
|
| 387 |
+
"language_model.base_model.model.model.language_model.layers.24.mlp.up_proj.lora_A.default.weight",
|
| 388 |
+
"language_model.base_model.model.model.language_model.layers.24.mlp.up_proj.lora_B.default.weight",
|
| 389 |
+
"language_model.base_model.model.model.language_model.layers.24.mlp.down_proj.lora_A.default.weight",
|
| 390 |
+
"language_model.base_model.model.model.language_model.layers.24.mlp.down_proj.lora_B.default.weight",
|
| 391 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.out_proj.lora_A.default.weight",
|
| 392 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.out_proj.lora_B.default.weight",
|
| 393 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 394 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 395 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_z.lora_A.default.weight",
|
| 396 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_z.lora_B.default.weight",
|
| 397 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_b.lora_A.default.weight",
|
| 398 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_b.lora_B.default.weight",
|
| 399 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_a.lora_A.default.weight",
|
| 400 |
+
"language_model.base_model.model.model.language_model.layers.25.linear_attn.in_proj_a.lora_B.default.weight",
|
| 401 |
+
"language_model.base_model.model.model.language_model.layers.25.mlp.gate_proj.lora_A.default.weight",
|
| 402 |
+
"language_model.base_model.model.model.language_model.layers.25.mlp.gate_proj.lora_B.default.weight",
|
| 403 |
+
"language_model.base_model.model.model.language_model.layers.25.mlp.up_proj.lora_A.default.weight",
|
| 404 |
+
"language_model.base_model.model.model.language_model.layers.25.mlp.up_proj.lora_B.default.weight",
|
| 405 |
+
"language_model.base_model.model.model.language_model.layers.25.mlp.down_proj.lora_A.default.weight",
|
| 406 |
+
"language_model.base_model.model.model.language_model.layers.25.mlp.down_proj.lora_B.default.weight",
|
| 407 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.out_proj.lora_A.default.weight",
|
| 408 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.out_proj.lora_B.default.weight",
|
| 409 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 410 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 411 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_z.lora_A.default.weight",
|
| 412 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_z.lora_B.default.weight",
|
| 413 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_b.lora_A.default.weight",
|
| 414 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_b.lora_B.default.weight",
|
| 415 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_a.lora_A.default.weight",
|
| 416 |
+
"language_model.base_model.model.model.language_model.layers.26.linear_attn.in_proj_a.lora_B.default.weight",
|
| 417 |
+
"language_model.base_model.model.model.language_model.layers.26.mlp.gate_proj.lora_A.default.weight",
|
| 418 |
+
"language_model.base_model.model.model.language_model.layers.26.mlp.gate_proj.lora_B.default.weight",
|
| 419 |
+
"language_model.base_model.model.model.language_model.layers.26.mlp.up_proj.lora_A.default.weight",
|
| 420 |
+
"language_model.base_model.model.model.language_model.layers.26.mlp.up_proj.lora_B.default.weight",
|
| 421 |
+
"language_model.base_model.model.model.language_model.layers.26.mlp.down_proj.lora_A.default.weight",
|
| 422 |
+
"language_model.base_model.model.model.language_model.layers.26.mlp.down_proj.lora_B.default.weight",
|
| 423 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.q_proj.lora_A.default.weight",
|
| 424 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.q_proj.lora_B.default.weight",
|
| 425 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.k_proj.lora_A.default.weight",
|
| 426 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.k_proj.lora_B.default.weight",
|
| 427 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.v_proj.lora_A.default.weight",
|
| 428 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.v_proj.lora_B.default.weight",
|
| 429 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.o_proj.lora_A.default.weight",
|
| 430 |
+
"language_model.base_model.model.model.language_model.layers.27.self_attn.o_proj.lora_B.default.weight",
|
| 431 |
+
"language_model.base_model.model.model.language_model.layers.27.mlp.gate_proj.lora_A.default.weight",
|
| 432 |
+
"language_model.base_model.model.model.language_model.layers.27.mlp.gate_proj.lora_B.default.weight",
|
| 433 |
+
"language_model.base_model.model.model.language_model.layers.27.mlp.up_proj.lora_A.default.weight",
|
| 434 |
+
"language_model.base_model.model.model.language_model.layers.27.mlp.up_proj.lora_B.default.weight",
|
| 435 |
+
"language_model.base_model.model.model.language_model.layers.27.mlp.down_proj.lora_A.default.weight",
|
| 436 |
+
"language_model.base_model.model.model.language_model.layers.27.mlp.down_proj.lora_B.default.weight",
|
| 437 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.out_proj.lora_A.default.weight",
|
| 438 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.out_proj.lora_B.default.weight",
|
| 439 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 440 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 441 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_z.lora_A.default.weight",
|
| 442 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_z.lora_B.default.weight",
|
| 443 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_b.lora_A.default.weight",
|
| 444 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_b.lora_B.default.weight",
|
| 445 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_a.lora_A.default.weight",
|
| 446 |
+
"language_model.base_model.model.model.language_model.layers.28.linear_attn.in_proj_a.lora_B.default.weight",
|
| 447 |
+
"language_model.base_model.model.model.language_model.layers.28.mlp.gate_proj.lora_A.default.weight",
|
| 448 |
+
"language_model.base_model.model.model.language_model.layers.28.mlp.gate_proj.lora_B.default.weight",
|
| 449 |
+
"language_model.base_model.model.model.language_model.layers.28.mlp.up_proj.lora_A.default.weight",
|
| 450 |
+
"language_model.base_model.model.model.language_model.layers.28.mlp.up_proj.lora_B.default.weight",
|
| 451 |
+
"language_model.base_model.model.model.language_model.layers.28.mlp.down_proj.lora_A.default.weight",
|
| 452 |
+
"language_model.base_model.model.model.language_model.layers.28.mlp.down_proj.lora_B.default.weight",
|
| 453 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.out_proj.lora_A.default.weight",
|
| 454 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.out_proj.lora_B.default.weight",
|
| 455 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 456 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 457 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_z.lora_A.default.weight",
|
| 458 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_z.lora_B.default.weight",
|
| 459 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_b.lora_A.default.weight",
|
| 460 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_b.lora_B.default.weight",
|
| 461 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_a.lora_A.default.weight",
|
| 462 |
+
"language_model.base_model.model.model.language_model.layers.29.linear_attn.in_proj_a.lora_B.default.weight",
|
| 463 |
+
"language_model.base_model.model.model.language_model.layers.29.mlp.gate_proj.lora_A.default.weight",
|
| 464 |
+
"language_model.base_model.model.model.language_model.layers.29.mlp.gate_proj.lora_B.default.weight",
|
| 465 |
+
"language_model.base_model.model.model.language_model.layers.29.mlp.up_proj.lora_A.default.weight",
|
| 466 |
+
"language_model.base_model.model.model.language_model.layers.29.mlp.up_proj.lora_B.default.weight",
|
| 467 |
+
"language_model.base_model.model.model.language_model.layers.29.mlp.down_proj.lora_A.default.weight",
|
| 468 |
+
"language_model.base_model.model.model.language_model.layers.29.mlp.down_proj.lora_B.default.weight",
|
| 469 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.out_proj.lora_A.default.weight",
|
| 470 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.out_proj.lora_B.default.weight",
|
| 471 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_qkv.lora_A.default.weight",
|
| 472 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_qkv.lora_B.default.weight",
|
| 473 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_z.lora_A.default.weight",
|
| 474 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_z.lora_B.default.weight",
|
| 475 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_b.lora_A.default.weight",
|
| 476 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_b.lora_B.default.weight",
|
| 477 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_a.lora_A.default.weight",
|
| 478 |
+
"language_model.base_model.model.model.language_model.layers.30.linear_attn.in_proj_a.lora_B.default.weight",
|
| 479 |
+
"language_model.base_model.model.model.language_model.layers.30.mlp.gate_proj.lora_A.default.weight",
|
| 480 |
+
"language_model.base_model.model.model.language_model.layers.30.mlp.gate_proj.lora_B.default.weight",
|
| 481 |
+
"language_model.base_model.model.model.language_model.layers.30.mlp.up_proj.lora_A.default.weight",
|
| 482 |
+
"language_model.base_model.model.model.language_model.layers.30.mlp.up_proj.lora_B.default.weight",
|
| 483 |
+
"language_model.base_model.model.model.language_model.layers.30.mlp.down_proj.lora_A.default.weight",
|
| 484 |
+
"language_model.base_model.model.model.language_model.layers.30.mlp.down_proj.lora_B.default.weight",
|
| 485 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.q_proj.lora_A.default.weight",
|
| 486 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.q_proj.lora_B.default.weight",
|
| 487 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.k_proj.lora_A.default.weight",
|
| 488 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.k_proj.lora_B.default.weight",
|
| 489 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.v_proj.lora_A.default.weight",
|
| 490 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.v_proj.lora_B.default.weight",
|
| 491 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.o_proj.lora_A.default.weight",
|
| 492 |
+
"language_model.base_model.model.model.language_model.layers.31.self_attn.o_proj.lora_B.default.weight",
|
| 493 |
+
"language_model.base_model.model.model.language_model.layers.31.mlp.gate_proj.lora_A.default.weight",
|
| 494 |
+
"language_model.base_model.model.model.language_model.layers.31.mlp.gate_proj.lora_B.default.weight",
|
| 495 |
+
"language_model.base_model.model.model.language_model.layers.31.mlp.up_proj.lora_A.default.weight",
|
| 496 |
+
"language_model.base_model.model.model.language_model.layers.31.mlp.up_proj.lora_B.default.weight",
|
| 497 |
+
"language_model.base_model.model.model.language_model.layers.31.mlp.down_proj.lora_A.default.weight",
|
| 498 |
+
"language_model.base_model.model.model.language_model.layers.31.mlp.down_proj.lora_B.default.weight",
|
| 499 |
+
"head.native_head.prior_logit_scale",
|
| 500 |
+
"head.native_head.joint_logit_scale",
|
| 501 |
+
"head.native_head.residual_gate",
|
| 502 |
+
"head.native_head.hidden_norm.weight",
|
| 503 |
+
"head.native_head.hidden_norm.bias",
|
| 504 |
+
"head.native_head.memory_projection.weight",
|
| 505 |
+
"head.native_head.question_projection.weight",
|
| 506 |
+
"head.native_head.option_question_projection.weight",
|
| 507 |
+
"head.native_head.global_projection.weight",
|
| 508 |
+
"head.native_head.option_context_projection.weight",
|
| 509 |
+
"head.native_head.option_lexical_projection.weight",
|
| 510 |
+
"head.native_head.type_embedding.weight",
|
| 511 |
+
"head.native_head.evidence_layers.0.query_norm.weight",
|
| 512 |
+
"head.native_head.evidence_layers.0.query_norm.bias",
|
| 513 |
+
"head.native_head.evidence_layers.0.memory_norm.weight",
|
| 514 |
+
"head.native_head.evidence_layers.0.memory_norm.bias",
|
| 515 |
+
"head.native_head.evidence_layers.0.attention.in_proj_weight",
|
| 516 |
+
"head.native_head.evidence_layers.0.attention.in_proj_bias",
|
| 517 |
+
"head.native_head.evidence_layers.0.attention.out_proj.weight",
|
| 518 |
+
"head.native_head.evidence_layers.0.attention.out_proj.bias",
|
| 519 |
+
"head.native_head.evidence_layers.0.feedforward_norm.weight",
|
| 520 |
+
"head.native_head.evidence_layers.0.feedforward_norm.bias",
|
| 521 |
+
"head.native_head.evidence_layers.0.feedforward.0.weight",
|
| 522 |
+
"head.native_head.evidence_layers.0.feedforward.0.bias",
|
| 523 |
+
"head.native_head.evidence_layers.0.feedforward.3.weight",
|
| 524 |
+
"head.native_head.evidence_layers.0.feedforward.3.bias",
|
| 525 |
+
"head.native_head.evidence_layers.1.query_norm.weight",
|
| 526 |
+
"head.native_head.evidence_layers.1.query_norm.bias",
|
| 527 |
+
"head.native_head.evidence_layers.1.memory_norm.weight",
|
| 528 |
+
"head.native_head.evidence_layers.1.memory_norm.bias",
|
| 529 |
+
"head.native_head.evidence_layers.1.attention.in_proj_weight",
|
| 530 |
+
"head.native_head.evidence_layers.1.attention.in_proj_bias",
|
| 531 |
+
"head.native_head.evidence_layers.1.attention.out_proj.weight",
|
| 532 |
+
"head.native_head.evidence_layers.1.attention.out_proj.bias",
|
| 533 |
+
"head.native_head.evidence_layers.1.feedforward_norm.weight",
|
| 534 |
+
"head.native_head.evidence_layers.1.feedforward_norm.bias",
|
| 535 |
+
"head.native_head.evidence_layers.1.feedforward.0.weight",
|
| 536 |
+
"head.native_head.evidence_layers.1.feedforward.0.bias",
|
| 537 |
+
"head.native_head.evidence_layers.1.feedforward.3.weight",
|
| 538 |
+
"head.native_head.evidence_layers.1.feedforward.3.bias",
|
| 539 |
+
"head.native_head.option_summary_norm.weight",
|
| 540 |
+
"head.native_head.option_summary_norm.bias",
|
| 541 |
+
"head.native_head.layers.0.self_attn.in_proj_weight",
|
| 542 |
+
"head.native_head.layers.0.self_attn.in_proj_bias",
|
| 543 |
+
"head.native_head.layers.0.self_attn.out_proj.weight",
|
| 544 |
+
"head.native_head.layers.0.self_attn.out_proj.bias",
|
| 545 |
+
"head.native_head.layers.0.multihead_attn.in_proj_weight",
|
| 546 |
+
"head.native_head.layers.0.multihead_attn.in_proj_bias",
|
| 547 |
+
"head.native_head.layers.0.multihead_attn.out_proj.weight",
|
| 548 |
+
"head.native_head.layers.0.multihead_attn.out_proj.bias",
|
| 549 |
+
"head.native_head.layers.0.linear1.weight",
|
| 550 |
+
"head.native_head.layers.0.linear1.bias",
|
| 551 |
+
"head.native_head.layers.0.linear2.weight",
|
| 552 |
+
"head.native_head.layers.0.linear2.bias",
|
| 553 |
+
"head.native_head.layers.0.norm1.weight",
|
| 554 |
+
"head.native_head.layers.0.norm1.bias",
|
| 555 |
+
"head.native_head.layers.0.norm2.weight",
|
| 556 |
+
"head.native_head.layers.0.norm2.bias",
|
| 557 |
+
"head.native_head.layers.0.norm3.weight",
|
| 558 |
+
"head.native_head.layers.0.norm3.bias",
|
| 559 |
+
"head.native_head.layers.1.self_attn.in_proj_weight",
|
| 560 |
+
"head.native_head.layers.1.self_attn.in_proj_bias",
|
| 561 |
+
"head.native_head.layers.1.self_attn.out_proj.weight",
|
| 562 |
+
"head.native_head.layers.1.self_attn.out_proj.bias",
|
| 563 |
+
"head.native_head.layers.1.multihead_attn.in_proj_weight",
|
| 564 |
+
"head.native_head.layers.1.multihead_attn.in_proj_bias",
|
| 565 |
+
"head.native_head.layers.1.multihead_attn.out_proj.weight",
|
| 566 |
+
"head.native_head.layers.1.multihead_attn.out_proj.bias",
|
| 567 |
+
"head.native_head.layers.1.linear1.weight",
|
| 568 |
+
"head.native_head.layers.1.linear1.bias",
|
| 569 |
+
"head.native_head.layers.1.linear2.weight",
|
| 570 |
+
"head.native_head.layers.1.linear2.bias",
|
| 571 |
+
"head.native_head.layers.1.norm1.weight",
|
| 572 |
+
"head.native_head.layers.1.norm1.bias",
|
| 573 |
+
"head.native_head.layers.1.norm2.weight",
|
| 574 |
+
"head.native_head.layers.1.norm2.bias",
|
| 575 |
+
"head.native_head.layers.1.norm3.weight",
|
| 576 |
+
"head.native_head.layers.1.norm3.bias",
|
| 577 |
+
"head.native_head.layers.2.self_attn.in_proj_weight",
|
| 578 |
+
"head.native_head.layers.2.self_attn.in_proj_bias",
|
| 579 |
+
"head.native_head.layers.2.self_attn.out_proj.weight",
|
| 580 |
+
"head.native_head.layers.2.self_attn.out_proj.bias",
|
| 581 |
+
"head.native_head.layers.2.multihead_attn.in_proj_weight",
|
| 582 |
+
"head.native_head.layers.2.multihead_attn.in_proj_bias",
|
| 583 |
+
"head.native_head.layers.2.multihead_attn.out_proj.weight",
|
| 584 |
+
"head.native_head.layers.2.multihead_attn.out_proj.bias",
|
| 585 |
+
"head.native_head.layers.2.linear1.weight",
|
| 586 |
+
"head.native_head.layers.2.linear1.bias",
|
| 587 |
+
"head.native_head.layers.2.linear2.weight",
|
| 588 |
+
"head.native_head.layers.2.linear2.bias",
|
| 589 |
+
"head.native_head.layers.2.norm1.weight",
|
| 590 |
+
"head.native_head.layers.2.norm1.bias",
|
| 591 |
+
"head.native_head.layers.2.norm2.weight",
|
| 592 |
+
"head.native_head.layers.2.norm2.bias",
|
| 593 |
+
"head.native_head.layers.2.norm3.weight",
|
| 594 |
+
"head.native_head.layers.2.norm3.bias",
|
| 595 |
+
"head.native_head.layers.3.self_attn.in_proj_weight",
|
| 596 |
+
"head.native_head.layers.3.self_attn.in_proj_bias",
|
| 597 |
+
"head.native_head.layers.3.self_attn.out_proj.weight",
|
| 598 |
+
"head.native_head.layers.3.self_attn.out_proj.bias",
|
| 599 |
+
"head.native_head.layers.3.multihead_attn.in_proj_weight",
|
| 600 |
+
"head.native_head.layers.3.multihead_attn.in_proj_bias",
|
| 601 |
+
"head.native_head.layers.3.multihead_attn.out_proj.weight",
|
| 602 |
+
"head.native_head.layers.3.multihead_attn.out_proj.bias",
|
| 603 |
+
"head.native_head.layers.3.linear1.weight",
|
| 604 |
+
"head.native_head.layers.3.linear1.bias",
|
| 605 |
+
"head.native_head.layers.3.linear2.weight",
|
| 606 |
+
"head.native_head.layers.3.linear2.bias",
|
| 607 |
+
"head.native_head.layers.3.norm1.weight",
|
| 608 |
+
"head.native_head.layers.3.norm1.bias",
|
| 609 |
+
"head.native_head.layers.3.norm2.weight",
|
| 610 |
+
"head.native_head.layers.3.norm2.bias",
|
| 611 |
+
"head.native_head.layers.3.norm3.weight",
|
| 612 |
+
"head.native_head.layers.3.norm3.bias",
|
| 613 |
+
"head.native_head.field_norm.weight",
|
| 614 |
+
"head.native_head.field_norm.bias",
|
| 615 |
+
"head.native_head.option_norm.weight",
|
| 616 |
+
"head.native_head.option_norm.bias",
|
| 617 |
+
"head.native_head.residual_scorer.0.weight",
|
| 618 |
+
"head.native_head.residual_scorer.0.bias",
|
| 619 |
+
"head.native_head.residual_scorer.3.weight",
|
| 620 |
+
"head.native_head.residual_scorer.3.bias"
|
| 621 |
+
],
|
| 622 |
+
"config": {
|
| 623 |
+
"batch_size": 1,
|
| 624 |
+
"brier_weight": 0.1,
|
| 625 |
+
"clip_gradient_norm": 1.0,
|
| 626 |
+
"epochs": 2,
|
| 627 |
+
"gradient_accumulation": 8,
|
| 628 |
+
"label_smoothing": 0.05,
|
| 629 |
+
"learning_rate": 1e-05,
|
| 630 |
+
"max_length": 4096,
|
| 631 |
+
"mode": "lora",
|
| 632 |
+
"optimizer": "AdamW",
|
| 633 |
+
"rank": 4,
|
| 634 |
+
"seed": 42,
|
| 635 |
+
"selection": "external validation only; this script does not choose a checkpoint",
|
| 636 |
+
"weight_decay": 0.01
|
| 637 |
+
},
|
| 638 |
+
"dataset_config_sha256": "65b72544c80cf19f4ac3b12ca109d0ebda18fb70eadb4b170a55b1e8bb3b4f6d",
|
| 639 |
+
"dataset_counts": {
|
| 640 |
+
"train": 827,
|
| 641 |
+
"validation": 215
|
| 642 |
+
},
|
| 643 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 644 |
+
"dataset_source_commit": "70d84bd8dc5d4a60f3b96455a57d9e6f9416d109",
|
| 645 |
+
"dataset_split_sha256": {
|
| 646 |
+
"train": "edd682761db95a4f25bb30a284489c54d9336a36da0a9b19d2cda860b428baa8",
|
| 647 |
+
"validation": "eff9cdc5932e935959ac4d26dce6470d335090f7428a91930001954266d133bc"
|
| 648 |
+
},
|
| 649 |
+
"elapsed_seconds": 1511.2463698700012,
|
| 650 |
+
"epochs": [
|
| 651 |
+
{
|
| 652 |
+
"checkpoint": "runs/study-v1/epoch-01",
|
| 653 |
+
"epoch": 1,
|
| 654 |
+
"examples": 827,
|
| 655 |
+
"mean_training_loss": 2.5779671531346287,
|
| 656 |
+
"optimizer_steps": 104,
|
| 657 |
+
"peak_allocated_bytes": 23230341120,
|
| 658 |
+
"peak_reserved_bytes": 24178065408,
|
| 659 |
+
"seconds": 742.4755487579969,
|
| 660 |
+
"shuffle_order_sha256": "b6e0d7189b1d965472f1132dda2e9be3a35093dae838baf5d8e0c0d3471b6198"
|
| 661 |
+
},
|
| 662 |
+
{
|
| 663 |
+
"checkpoint": "runs/study-v1/epoch-02",
|
| 664 |
+
"epoch": 2,
|
| 665 |
+
"examples": 827,
|
| 666 |
+
"mean_training_loss": 1.9667175294878694,
|
| 667 |
+
"optimizer_steps": 104,
|
| 668 |
+
"peak_allocated_bytes": 23226543104,
|
| 669 |
+
"peak_reserved_bytes": 24178065408,
|
| 670 |
+
"seconds": 744.6554337799898,
|
| 671 |
+
"shuffle_order_sha256": "b9d20efe77959592c99e9247a58d844be69df50db595bd87c6d7e595f5d2e0c4"
|
| 672 |
+
}
|
| 673 |
+
],
|
| 674 |
+
"frozen_parameters_unchanged": true,
|
| 675 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 676 |
+
"initial_free_vram": 33179500544,
|
| 677 |
+
"package_versions": {
|
| 678 |
+
"peft": "0.21.2",
|
| 679 |
+
"safetensors": "0.8.0",
|
| 680 |
+
"torch": "2.14.1+cu130",
|
| 681 |
+
"transformers": "5.18.0"
|
| 682 |
+
},
|
| 683 |
+
"source_commit": "d9fcd05c02e9327d4d791f2338725092cede8bdd",
|
| 684 |
+
"source_dirty": true,
|
| 685 |
+
"source_hashes": {
|
| 686 |
+
"scripts/train_clef.py": "e83586cf0fd0d7bb8c4aa15f9b858b9f9ffdb915622e355deb9513788d5054aa",
|
| 687 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 688 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 689 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8"
|
| 690 |
+
},
|
| 691 |
+
"status": "trained-awaiting-external-validation",
|
| 692 |
+
"test_trajectories_used": false,
|
| 693 |
+
"total_vram": 33711521792,
|
| 694 |
+
"trainable_parameters": 132582404,
|
| 695 |
+
"validation_used_for_training": false
|
| 696 |
+
}
|
code/reports/training-launch-source.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
code/reports/training-source-supplement.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"source_commit": "d9fcd05",
|
| 3 |
+
"source_hashes": {
|
| 4 |
+
"src/stackcraft/engine.py": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
|
| 5 |
+
"src/stackcraft/pieces.py": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
|
| 6 |
+
"src/stackcraft/schema.py": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5",
|
| 7 |
+
"src/stackcraft/players/__init__.py": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
|
| 8 |
+
"uv.lock": "bc7fae476529578be003856b5f19e09c8cc96e0020ab999e20e1f8210696f9c8"
|
| 9 |
+
},
|
| 10 |
+
"verified_equal_to_current_at_training_start": true
|
| 11 |
+
}
|
code/reports/validation-summary.json
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"selected_key": "epoch-02",
|
| 3 |
+
"decision_rule": "lowest finite mean target NLL over all 215 validation positions; exact tie chooses earlier epoch",
|
| 4 |
+
"selection_sha256": "025b62587a26bd8cc78c80f362524b8c178190df5188b317299121afa6b1203b",
|
| 5 |
+
"checkpoint_sha256": {
|
| 6 |
+
"adapter/README.md": "bf1fa63446e8ee38c5ac154ade9138a0e2354e3215201f59473102eeb5206fa0",
|
| 7 |
+
"adapter/adapter_config.json": "bbbce7f19e186b4a79c39d03786bb190d6084e7990a601e8e28672cf1c969741",
|
| 8 |
+
"adapter/adapter_model.safetensors": "3ed169fe68b20a370bb856f9d91842543374ff3c98fd3bbe139ef2d0b97877a3",
|
| 9 |
+
"joint_head.safetensors": "4f21c0f315cf1bcf852a9887d158928855eaa9bdcab6b9770d518abf3727fa1b",
|
| 10 |
+
"reference.json": "adacc63684d28598f8016602bab790cd3967317986b6934dd94a5e14fabe571f",
|
| 11 |
+
"training_config.json": "3ead31d0a3ccb6a62914c3853ee26332f6412382c5196e40b461182e64d8b329"
|
| 12 |
+
},
|
| 13 |
+
"dataset_manifest_sha256": "aeddcfc1ea5390f12122d2e786c1e2030dcc97d3790b6d7523432548198b1f94",
|
| 14 |
+
"validation": {
|
| 15 |
+
"epoch-01": {
|
| 16 |
+
"report_sha256": "ce401ef9fe1d25c11c7a0e3845def347967d8617223307ad823e23a90c3f0033",
|
| 17 |
+
"players": {
|
| 18 |
+
"base": {
|
| 19 |
+
"available_subset_mean_brier": 0.9231725575578064,
|
| 20 |
+
"complete_probability_coverage": true,
|
| 21 |
+
"correct": 21,
|
| 22 |
+
"error_rate": 0.0,
|
| 23 |
+
"failed_predictions": [],
|
| 24 |
+
"finite_subset_mean_nll": 2.9527020509302693,
|
| 25 |
+
"finite_subset_nll_positions": 215,
|
| 26 |
+
"mean_brier": 0.9231725575578064,
|
| 27 |
+
"mean_nll": 2.9527020509302693,
|
| 28 |
+
"missing_probabilities": [],
|
| 29 |
+
"nll_is_infinite": false,
|
| 30 |
+
"positions": 215,
|
| 31 |
+
"probability_positions": 215,
|
| 32 |
+
"selection_performed": false,
|
| 33 |
+
"teacher_agreement": 0.09767441860465116,
|
| 34 |
+
"zero_target_probability_count": 0
|
| 35 |
+
},
|
| 36 |
+
"base-fp32": {
|
| 37 |
+
"available_subset_mean_brier": 0.9231017245023939,
|
| 38 |
+
"complete_probability_coverage": true,
|
| 39 |
+
"correct": 22,
|
| 40 |
+
"error_rate": 0.0,
|
| 41 |
+
"failed_predictions": [],
|
| 42 |
+
"finite_subset_mean_nll": 2.9523931327950934,
|
| 43 |
+
"finite_subset_nll_positions": 215,
|
| 44 |
+
"mean_brier": 0.9231017245023939,
|
| 45 |
+
"mean_nll": 2.9523931327950934,
|
| 46 |
+
"missing_probabilities": [],
|
| 47 |
+
"nll_is_infinite": false,
|
| 48 |
+
"positions": 215,
|
| 49 |
+
"probability_positions": 215,
|
| 50 |
+
"selection_performed": false,
|
| 51 |
+
"teacher_agreement": 0.10232558139534884,
|
| 52 |
+
"zero_target_probability_count": 0
|
| 53 |
+
},
|
| 54 |
+
"trained": {
|
| 55 |
+
"available_subset_mean_brier": 0.7221895727255984,
|
| 56 |
+
"complete_probability_coverage": true,
|
| 57 |
+
"correct": 89,
|
| 58 |
+
"error_rate": 0.0,
|
| 59 |
+
"failed_predictions": [],
|
| 60 |
+
"finite_subset_mean_nll": 1.8608697515370076,
|
| 61 |
+
"finite_subset_nll_positions": 215,
|
| 62 |
+
"mean_brier": 0.7221895727255984,
|
| 63 |
+
"mean_nll": 1.8608697515370076,
|
| 64 |
+
"missing_probabilities": [],
|
| 65 |
+
"nll_is_infinite": false,
|
| 66 |
+
"positions": 215,
|
| 67 |
+
"probability_positions": 215,
|
| 68 |
+
"selection_performed": false,
|
| 69 |
+
"teacher_agreement": 0.413953488372093,
|
| 70 |
+
"zero_target_probability_count": 0
|
| 71 |
+
}
|
| 72 |
+
},
|
| 73 |
+
"provenance": {
|
| 74 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 75 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 76 |
+
"installed_versions": {
|
| 77 |
+
"huggingface-hub": "1.33.0",
|
| 78 |
+
"peft": "0.21.2",
|
| 79 |
+
"safetensors": "0.8.0",
|
| 80 |
+
"torch": "2.14.1+cu130",
|
| 81 |
+
"transformers": "5.18.0"
|
| 82 |
+
},
|
| 83 |
+
"precision_policy": "native BF16 base; FP32 trained head; optional base-fp32 ablation",
|
| 84 |
+
"rules_version": "stackcraft-v1",
|
| 85 |
+
"source_commit": "ada38885b55ae3e4b6705a18dec98d194d37447e",
|
| 86 |
+
"source_dirty": false,
|
| 87 |
+
"source_hashes": {
|
| 88 |
+
"scripts/evaluate_clef.py": "70a47e9314ec9d7351a939bae32f07f14bd08f6106b60ce6155687c0540b92dd",
|
| 89 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 90 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 91 |
+
"src/stackcraft/engine.py": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
|
| 92 |
+
"src/stackcraft/evaluation.py": "df56f9d36655e79573a014a5c55c2571bc5e04e2c20f1b37e2d939b871e889e6",
|
| 93 |
+
"src/stackcraft/pieces.py": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
|
| 94 |
+
"src/stackcraft/players/__init__.py": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
|
| 95 |
+
"src/stackcraft/provenance.py": "0728104f22361ad2a8b4471e6eef0467852d94f650d0259d7f5f674c0086f13b",
|
| 96 |
+
"src/stackcraft/schema.py": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5",
|
| 97 |
+
"src/stackcraft/tournament.py": "288ed447a32e0845f52097372a644fc211b1632790655f620801d3ce1639d559",
|
| 98 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8",
|
| 99 |
+
"uv.lock": "bc7fae476529578be003856b5f19e09c8cc96e0020ab999e20e1f8210696f9c8"
|
| 100 |
+
},
|
| 101 |
+
"source_identity_method": "exact-git-root"
|
| 102 |
+
}
|
| 103 |
+
},
|
| 104 |
+
"epoch-02": {
|
| 105 |
+
"report_sha256": "9482be5ac08b85a7bafc18d8f70a35ba277f476d9a96dd0684553e937abc4d2a",
|
| 106 |
+
"players": {
|
| 107 |
+
"trained": {
|
| 108 |
+
"available_subset_mean_brier": 0.7089798657211578,
|
| 109 |
+
"complete_probability_coverage": true,
|
| 110 |
+
"correct": 92,
|
| 111 |
+
"error_rate": 0.0,
|
| 112 |
+
"failed_predictions": [],
|
| 113 |
+
"finite_subset_mean_nll": 1.7759231168523646,
|
| 114 |
+
"finite_subset_nll_positions": 215,
|
| 115 |
+
"mean_brier": 0.7089798657211578,
|
| 116 |
+
"mean_nll": 1.7759231168523646,
|
| 117 |
+
"missing_probabilities": [],
|
| 118 |
+
"nll_is_infinite": false,
|
| 119 |
+
"positions": 215,
|
| 120 |
+
"probability_positions": 215,
|
| 121 |
+
"selection_performed": false,
|
| 122 |
+
"teacher_agreement": 0.42790697674418604,
|
| 123 |
+
"zero_target_probability_count": 0
|
| 124 |
+
}
|
| 125 |
+
},
|
| 126 |
+
"provenance": {
|
| 127 |
+
"base_revision": "17f0b0ad64efb65d273590632833508766b2aae6",
|
| 128 |
+
"encoding_version": "stackcraft-clef-v1",
|
| 129 |
+
"installed_versions": {
|
| 130 |
+
"huggingface-hub": "1.33.0",
|
| 131 |
+
"peft": "0.21.2",
|
| 132 |
+
"safetensors": "0.8.0",
|
| 133 |
+
"torch": "2.14.1+cu130",
|
| 134 |
+
"transformers": "5.18.0"
|
| 135 |
+
},
|
| 136 |
+
"precision_policy": "native BF16 base; FP32 trained head; optional base-fp32 ablation",
|
| 137 |
+
"rules_version": "stackcraft-v1",
|
| 138 |
+
"source_commit": "7a21f6491bcaf2afacc9aa5b5e2733a35c41a7a8",
|
| 139 |
+
"source_dirty": false,
|
| 140 |
+
"source_hashes": {
|
| 141 |
+
"scripts/evaluate_clef.py": "70a47e9314ec9d7351a939bae32f07f14bd08f6106b60ce6155687c0540b92dd",
|
| 142 |
+
"src/stackcraft/clef.py": "c505f35d27121ba9ec9db3c663927ac8b28304f4ed864765be61ce96c8c86e13",
|
| 143 |
+
"src/stackcraft/data.py": "74b68a1f391a3128d2e499c7e86b6c94a1f2a2bef33ffe75c8631207065bd991",
|
| 144 |
+
"src/stackcraft/engine.py": "0805be38ee968d569fa1c12230730f491490f7d27cfcc570c400ac3b4a546453",
|
| 145 |
+
"src/stackcraft/evaluation.py": "df56f9d36655e79573a014a5c55c2571bc5e04e2c20f1b37e2d939b871e889e6",
|
| 146 |
+
"src/stackcraft/pieces.py": "c5bee0f8767aeeab279cfbadb4c7b4c0bbf1f31c1d29b08dda197f0627654b29",
|
| 147 |
+
"src/stackcraft/players/__init__.py": "550bed9237303a845cdcc101542b0b688173a8f5436dd1d08c5f85a5a19fa2b2",
|
| 148 |
+
"src/stackcraft/provenance.py": "0728104f22361ad2a8b4471e6eef0467852d94f650d0259d7f5f674c0086f13b",
|
| 149 |
+
"src/stackcraft/schema.py": "35c70d96e8a49e434dc0a3607412a0ce58141306df59e09b1170511694cb6eb5",
|
| 150 |
+
"src/stackcraft/tournament.py": "288ed447a32e0845f52097372a644fc211b1632790655f620801d3ce1639d559",
|
| 151 |
+
"src/stackcraft/training.py": "f7e48b041b331cf2b68674ec2abb1059dd1da47cb261697b46260b4f0169dba8",
|
| 152 |
+
"uv.lock": "bc7fae476529578be003856b5f19e09c8cc96e0020ab999e20e1f8210696f9c8"
|
| 153 |
+
},
|
| 154 |
+
"source_identity_method": "exact-git-root"
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
}
|
| 158 |
+
}
|
code/scripts/benchmark_clef.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Actual pinned unchanged Clef development tournament; no held-out test seeds."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
import time
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
from stackcraft.clef import ClefPlayer
|
| 11 |
+
from stackcraft.engine import new_game
|
| 12 |
+
from stackcraft.players import HeuristicPlayer, RandomPlayer, observe
|
| 13 |
+
from stackcraft.tournament import run_tournament
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def main():
|
| 17 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 18 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 19 |
+
parser.add_argument("--episodes", type=int, default=2)
|
| 20 |
+
parser.add_argument("--max-pieces", type=int, default=30)
|
| 21 |
+
args = parser.parse_args()
|
| 22 |
+
if args.output.exists() or not 1 <= args.episodes <= 20:
|
| 23 |
+
parser.error("use a new output and1–20 development episodes")
|
| 24 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 25 |
+
torch.set_num_threads(8)
|
| 26 |
+
free, total = torch.cuda.mem_get_info()
|
| 27 |
+
if free < 25 * 1024**3:
|
| 28 |
+
raise RuntimeError(f"insufficient free GPU memory: {free}")
|
| 29 |
+
started = time.monotonic()
|
| 30 |
+
player = ClefPlayer.from_pretrained(trust_pinned_code=True)
|
| 31 |
+
warmup = player.choose(observe(new_game(42)))
|
| 32 |
+
print(
|
| 33 |
+
json.dumps({"warmup": warmup.action_id, "load_seconds": time.monotonic() - started}),
|
| 34 |
+
flush=True,
|
| 35 |
+
)
|
| 36 |
+
result = run_tournament(
|
| 37 |
+
{"random": RandomPlayer, "heuristic": HeuristicPlayer, "clef-base": lambda: player},
|
| 38 |
+
list(range(args.episodes)),
|
| 39 |
+
args.max_pieces,
|
| 40 |
+
)
|
| 41 |
+
result["development_only"] = True
|
| 42 |
+
result["gpu"] = torch.cuda.get_device_name()
|
| 43 |
+
result["peak_allocated_bytes"] = torch.cuda.max_memory_allocated()
|
| 44 |
+
result["peak_reserved_bytes"] = torch.cuda.max_memory_reserved()
|
| 45 |
+
result["elapsed_seconds"] = time.monotonic() - started
|
| 46 |
+
args.output.write_text(json.dumps(result, indent=2, allow_nan=False) + "\n")
|
| 47 |
+
print(json.dumps(result["summary"]), flush=True)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
if __name__ == "__main__":
|
| 51 |
+
main()
|
code/scripts/build_release.py
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Validate completed evidence and stage local release bundles; never upload anything."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
import hashlib
|
| 7 |
+
import importlib.util
|
| 8 |
+
import json
|
| 9 |
+
import shutil
|
| 10 |
+
import stat
|
| 11 |
+
import zipfile
|
| 12 |
+
from pathlib import Path, PurePosixPath
|
| 13 |
+
from types import ModuleType, SimpleNamespace
|
| 14 |
+
from typing import Any
|
| 15 |
+
|
| 16 |
+
from stackcraft.data import audit_dataset
|
| 17 |
+
from stackcraft.evaluation import FINAL_TEST_SEEDS, paired_report
|
| 18 |
+
from stackcraft.players import Decision, observe, validate_decision
|
| 19 |
+
from stackcraft.provenance import source_identity
|
| 20 |
+
from stackcraft.schema import GameState
|
| 21 |
+
|
| 22 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 23 |
+
PLAYERS = ("base", "base-fp32", "trained", "random", "heuristic")
|
| 24 |
+
COMPARISONS = {
|
| 25 |
+
"trained_vs_base": ("trained", "base"),
|
| 26 |
+
"trained_vs_heuristic": ("trained", "heuristic"),
|
| 27 |
+
"trained_vs_base_fp32": ("trained", "base-fp32"),
|
| 28 |
+
"base_fp32_vs_base": ("base-fp32", "base"),
|
| 29 |
+
}
|
| 30 |
+
CHECKPOINT_FILES = {
|
| 31 |
+
"training_config.json",
|
| 32 |
+
"joint_head.safetensors",
|
| 33 |
+
"reference.json",
|
| 34 |
+
"adapter/adapter_config.json",
|
| 35 |
+
"adapter/adapter_model.safetensors",
|
| 36 |
+
"adapter/README.md",
|
| 37 |
+
}
|
| 38 |
+
REPRO_SCRIPTS = (
|
| 39 |
+
"benchmark_clef.py",
|
| 40 |
+
"probe_clef_training.py",
|
| 41 |
+
"train_clef.py",
|
| 42 |
+
"evaluate_clef.py",
|
| 43 |
+
"select_checkpoint.py",
|
| 44 |
+
"export_demo.py",
|
| 45 |
+
"build_release.py",
|
| 46 |
+
"verify_release.py",
|
| 47 |
+
"prepare_checkpoint.py",
|
| 48 |
+
"gpu_session.py",
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def sha256(path: Path) -> str:
|
| 53 |
+
digest = hashlib.sha256()
|
| 54 |
+
with path.open("rb") as stream:
|
| 55 |
+
while chunk := stream.read(1_048_576):
|
| 56 |
+
digest.update(chunk)
|
| 57 |
+
return digest.hexdigest()
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def json_file(path: Path) -> Any:
|
| 61 |
+
return json.loads(path.read_text())
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def write_json(path: Path, value: Any) -> None:
|
| 65 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 66 |
+
path.write_text(json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + "\n")
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def load_helper(name: str) -> ModuleType:
|
| 70 |
+
spec = importlib.util.spec_from_file_location(
|
| 71 |
+
f"stackcraft_release_{name}", ROOT / "scripts" / name
|
| 72 |
+
)
|
| 73 |
+
if spec is None or spec.loader is None:
|
| 74 |
+
raise ValueError(f"cannot load reviewed release helper {name}")
|
| 75 |
+
module = importlib.util.module_from_spec(spec)
|
| 76 |
+
spec.loader.exec_module(module)
|
| 77 |
+
return module
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def copy_file(source: Path, destination: Path) -> None:
|
| 81 |
+
if source.is_symlink() or not source.is_file():
|
| 82 |
+
raise ValueError(f"release inputs must be regular files, not links: {source}")
|
| 83 |
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
| 84 |
+
shutil.copyfile(source, destination)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def safe_relative(path: str) -> Path:
|
| 88 |
+
relative = Path(path)
|
| 89 |
+
if relative.is_absolute() or ".." in relative.parts or not relative.parts:
|
| 90 |
+
raise ValueError("release evidence paths must stay within their evidence directory")
|
| 91 |
+
return relative
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def validate_reference(checkpoint: Path, dataset: Path, records: dict[str, Any]) -> None:
|
| 95 |
+
reference = json_file(checkpoint / "reference.json")
|
| 96 |
+
rows = records["train"][:4]
|
| 97 |
+
manifest_hash = sha256(dataset / "manifest.json")
|
| 98 |
+
if (
|
| 99 |
+
reference.get("row_ids") != [row["id"] for row in rows]
|
| 100 |
+
or reference.get("dataset_manifest_sha256") != manifest_hash
|
| 101 |
+
or reference.get("dataset_train_sha256") != sha256(dataset / "train.jsonl")
|
| 102 |
+
or reference.get("absolute_tolerance") != 1e-4
|
| 103 |
+
):
|
| 104 |
+
raise ValueError("checkpoint parity reference is not bound to the fixed training dataset")
|
| 105 |
+
metadata = json_file(checkpoint / "training_config.json")
|
| 106 |
+
if metadata.get("extra", {}).get("dataset_manifest_sha256") != manifest_hash:
|
| 107 |
+
raise ValueError("checkpoint was trained with a different dataset manifest")
|
| 108 |
+
config = metadata.get("extra", {}).get("config", {})
|
| 109 |
+
if reference.get("max_length") != config.get("max_length"):
|
| 110 |
+
raise ValueError("checkpoint reference context limit differs from training configuration")
|
| 111 |
+
probabilities = reference.get("probabilities")
|
| 112 |
+
if not isinstance(probabilities, list) or len(probabilities) != len(rows):
|
| 113 |
+
raise ValueError("checkpoint needs all four fixed-row probability references")
|
| 114 |
+
for row, distribution in zip(rows, probabilities, strict=True):
|
| 115 |
+
raw = row["observation"]
|
| 116 |
+
state = GameState(
|
| 117 |
+
tuple(tuple(r) for r in raw["board"]), 0, 0, raw["current"], raw["next_piece"]
|
| 118 |
+
)
|
| 119 |
+
observation = observe(state)
|
| 120 |
+
if not isinstance(distribution, dict) or not distribution:
|
| 121 |
+
raise ValueError("checkpoint reference must contain complete probabilities")
|
| 122 |
+
validate_decision(
|
| 123 |
+
Decision(max(distribution, key=distribution.__getitem__), distribution), observation
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def validate_evaluation(
|
| 128 |
+
evaluation: Path, selection_path: Path, checkpoint: Path, helpers: dict[str, ModuleType]
|
| 129 |
+
) -> tuple[dict[str, Any], dict[str, str], list[Path]]:
|
| 130 |
+
evaluator, exporter = helpers["evaluate"], helpers["export"]
|
| 131 |
+
hashes = evaluator.checkpoint_hashes(checkpoint)
|
| 132 |
+
if not set(hashes).issubset(CHECKPOINT_FILES):
|
| 133 |
+
raise ValueError("checkpoint contains unexpected files outside the release allowlist")
|
| 134 |
+
if not {"training_config.json", "joint_head.safetensors", "reference.json"}.issubset(hashes):
|
| 135 |
+
raise ValueError("checkpoint lacks trained head, metadata or reload reference")
|
| 136 |
+
metadata = json_file(checkpoint / "training_config.json")
|
| 137 |
+
if metadata.get("mode") == "lora" and not {
|
| 138 |
+
"adapter/adapter_config.json",
|
| 139 |
+
"adapter/adapter_model.safetensors",
|
| 140 |
+
"adapter/README.md",
|
| 141 |
+
}.issubset(hashes):
|
| 142 |
+
raise ValueError("LoRA checkpoint is missing adapter weights, configuration or attribution")
|
| 143 |
+
selection = json_file(selection_path)
|
| 144 |
+
report = json_file(evaluation / "report.json")
|
| 145 |
+
request = json_file(evaluation / "request.json")
|
| 146 |
+
if (
|
| 147 |
+
report.get("mode") != "tournament"
|
| 148 |
+
or report.get("final_test") is not True
|
| 149 |
+
or report.get("seeds") != list(FINAL_TEST_SEEDS)
|
| 150 |
+
or report.get("max_pieces") != 200
|
| 151 |
+
or set(report.get("players", {})) != set(PLAYERS)
|
| 152 |
+
or report.get("checkpoint_sha256") != hashes
|
| 153 |
+
or report.get("selection") != selection
|
| 154 |
+
):
|
| 155 |
+
raise ValueError("evaluation must be the complete frozen five-player, 200-seed final test")
|
| 156 |
+
if (
|
| 157 |
+
request.get("final_test") is not True
|
| 158 |
+
or request.get("mode") != "tournament"
|
| 159 |
+
or request.get("seeds") != list(FINAL_TEST_SEEDS)
|
| 160 |
+
or request.get("max_pieces") != 200
|
| 161 |
+
or set(request.get("players", [])) != set(PLAYERS)
|
| 162 |
+
or request.get("checkpoint_sha256") != hashes
|
| 163 |
+
or request.get("selection_sha256") != sha256(selection_path)
|
| 164 |
+
):
|
| 165 |
+
raise ValueError("evaluation request is not bound to the checkpoint and frozen selection")
|
| 166 |
+
args = SimpleNamespace(
|
| 167 |
+
seeds=tuple(FINAL_TEST_SEEDS),
|
| 168 |
+
final_test=True,
|
| 169 |
+
max_pieces=200,
|
| 170 |
+
players=request["players"],
|
| 171 |
+
selection_file=selection_path,
|
| 172 |
+
checkpoint=checkpoint,
|
| 173 |
+
max_length=request["max_length"],
|
| 174 |
+
)
|
| 175 |
+
evaluator.validate_selection(args, hashes)
|
| 176 |
+
episodes = report.get("episodes", [])
|
| 177 |
+
expected_pairs = {(player, seed) for player in PLAYERS for seed in FINAL_TEST_SEEDS}
|
| 178 |
+
by_pair = {(episode.get("player_id"), episode.get("seed")): episode for episode in episodes}
|
| 179 |
+
if len(episodes) != len(expected_pairs) or set(by_pair) != expected_pairs:
|
| 180 |
+
raise ValueError("final report must retain every player/seed pair exactly once")
|
| 181 |
+
paths = [Path("report.json"), Path("request.json")]
|
| 182 |
+
for player in PLAYERS:
|
| 183 |
+
metadata_path = Path(player) / "player.json"
|
| 184 |
+
metadata = json_file(evaluation / metadata_path)
|
| 185 |
+
if metadata != report["players"][player]:
|
| 186 |
+
raise ValueError("evaluation player metadata differs from its final report")
|
| 187 |
+
paths.append(metadata_path)
|
| 188 |
+
for seed in FINAL_TEST_SEEDS:
|
| 189 |
+
relative = Path(player) / f"seed-{seed}.json"
|
| 190 |
+
episode = json_file(evaluation / relative)
|
| 191 |
+
if episode != by_pair[(player, seed)]:
|
| 192 |
+
raise ValueError("episode file differs from final report")
|
| 193 |
+
if episode["player"]["revision"] != metadata["revision"] or episode["player"].get(
|
| 194 |
+
"runtime_config", {}
|
| 195 |
+
) != metadata.get("runtime_config", {}):
|
| 196 |
+
raise ValueError("episode player identity differs from recorded metadata")
|
| 197 |
+
# Recompute every board, visible observation, action and probability check.
|
| 198 |
+
exporter.validated_player(episode, 0)
|
| 199 |
+
paths.append(relative)
|
| 200 |
+
evaluator.validate_neural_runtimes(report["players"])
|
| 201 |
+
for key, (trained, base) in COMPARISONS.items():
|
| 202 |
+
computed = paired_report(episodes, trained_id=trained, base_id=base)
|
| 203 |
+
if computed != report.get(key):
|
| 204 |
+
raise ValueError(f"reported {key} does not match recomputed paired outcomes")
|
| 205 |
+
return report, hashes, paths
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def selection_evidence_files(selection_path: Path) -> list[Path]:
|
| 209 |
+
"""Preserve self-contained selection evidence with original relative links."""
|
| 210 |
+
selection = json_file(selection_path)
|
| 211 |
+
files = {Path(selection_path.name)}
|
| 212 |
+
pointers = [selection["validation_evidence"]]
|
| 213 |
+
for candidate in selection["candidates"]:
|
| 214 |
+
pointers.extend((candidate["validation_evidence"], candidate["checkpoint_metadata"]))
|
| 215 |
+
for pointer in pointers:
|
| 216 |
+
relative = safe_relative(pointer["path"])
|
| 217 |
+
source = selection_path.parent / relative
|
| 218 |
+
if sha256(source) != pointer["sha256"]:
|
| 219 |
+
raise ValueError("selection evidence changed after its hash was frozen")
|
| 220 |
+
files.add(relative)
|
| 221 |
+
value = json_file(source)
|
| 222 |
+
if value.get("mode") == "positions":
|
| 223 |
+
for player in value["players"].values():
|
| 224 |
+
predictions = relative.parent / safe_relative(player["predictions_file"])
|
| 225 |
+
if sha256(selection_path.parent / predictions) != player["predictions_sha256"]:
|
| 226 |
+
raise ValueError("validation predictions do not match their recorded hash")
|
| 227 |
+
files.add(predictions)
|
| 228 |
+
if (selection_path.parent / "selection-audit.json").is_file():
|
| 229 |
+
files.add(Path("selection-audit.json"))
|
| 230 |
+
return sorted(files)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def _evidence_name(name: str) -> None:
|
| 234 |
+
path = PurePosixPath(name)
|
| 235 |
+
if (
|
| 236 |
+
"\\" in name
|
| 237 |
+
or ":" in name
|
| 238 |
+
or "\x00" in name
|
| 239 |
+
or path.is_absolute()
|
| 240 |
+
or ".." in path.parts
|
| 241 |
+
or len(path.parts) < 2
|
| 242 |
+
or path.parts[0] != "evidence"
|
| 243 |
+
or path.as_posix() != name
|
| 244 |
+
):
|
| 245 |
+
raise ValueError("archive entries must be canonical files below evidence/")
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def verify_evidence_archive(archive: Path, expected: dict[str, dict[str, Any]]) -> None:
|
| 249 |
+
"""Read every entry back, checking exact names, lengths, CRC and raw SHA256."""
|
| 250 |
+
for name in expected:
|
| 251 |
+
_evidence_name(name)
|
| 252 |
+
with zipfile.ZipFile(archive) as source:
|
| 253 |
+
infos = source.infolist()
|
| 254 |
+
if len(infos) != len(expected) or {info.filename for info in infos} != set(expected):
|
| 255 |
+
raise ValueError("archive entries differ from the exact raw evidence inventory")
|
| 256 |
+
for info in infos:
|
| 257 |
+
_evidence_name(info.filename)
|
| 258 |
+
entry = expected[info.filename]
|
| 259 |
+
if (
|
| 260 |
+
info.is_dir()
|
| 261 |
+
or info.compress_type != zipfile.ZIP_DEFLATED
|
| 262 |
+
or info.flag_bits & 1
|
| 263 |
+
or stat.S_IFMT(info.external_attr >> 16) != stat.S_IFREG
|
| 264 |
+
or info.file_size != entry["bytes"]
|
| 265 |
+
):
|
| 266 |
+
raise ValueError("archive entry is not the declared regular evidence file")
|
| 267 |
+
digest = hashlib.sha256()
|
| 268 |
+
size = 0
|
| 269 |
+
with source.open(info) as stream:
|
| 270 |
+
while chunk := stream.read(1_048_576):
|
| 271 |
+
size += len(chunk)
|
| 272 |
+
digest.update(chunk)
|
| 273 |
+
if size != entry["bytes"] or digest.hexdigest() != entry["sha256"]:
|
| 274 |
+
raise ValueError("archive entry bytes differ from the raw evidence SHA256")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def pack_evidence(model: Path) -> dict[str, Any]:
|
| 278 |
+
"""Pack staged copies only; leave originals in place until the caller removes them."""
|
| 279 |
+
root = model / "evidence"
|
| 280 |
+
archive = model / "evidence.zip"
|
| 281 |
+
index = model / "evidence-files.json"
|
| 282 |
+
if archive.exists() or index.exists():
|
| 283 |
+
raise ValueError("evidence archive or inventory already exists")
|
| 284 |
+
paths = sorted(root.rglob("*"))
|
| 285 |
+
if root.is_symlink() or any(path.is_symlink() for path in paths):
|
| 286 |
+
raise ValueError("evidence archive inputs cannot contain symlinks")
|
| 287 |
+
files = [path for path in paths if path.is_file()]
|
| 288 |
+
if not files or any(not path.is_file() and not path.is_dir() for path in paths):
|
| 289 |
+
raise ValueError("evidence archive needs regular staged files")
|
| 290 |
+
expected = {}
|
| 291 |
+
with zipfile.ZipFile(archive, "x", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as target:
|
| 292 |
+
for path in files:
|
| 293 |
+
name = path.relative_to(model).as_posix()
|
| 294 |
+
_evidence_name(name)
|
| 295 |
+
info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
|
| 296 |
+
info.compress_type = zipfile.ZIP_DEFLATED
|
| 297 |
+
info.compress_level = 6
|
| 298 |
+
info.create_system = 3
|
| 299 |
+
info.external_attr = (stat.S_IFREG | 0o644) << 16
|
| 300 |
+
digest = hashlib.sha256()
|
| 301 |
+
size = 0
|
| 302 |
+
with path.open("rb") as source, target.open(info, "w", force_zip64=True) as stream:
|
| 303 |
+
while chunk := source.read(1_048_576):
|
| 304 |
+
digest.update(chunk)
|
| 305 |
+
size += len(chunk)
|
| 306 |
+
stream.write(chunk)
|
| 307 |
+
expected[name] = {"sha256": digest.hexdigest(), "bytes": size}
|
| 308 |
+
verify_evidence_archive(archive, expected)
|
| 309 |
+
# A concurrent edit must not be hidden by deleting a changed staged source.
|
| 310 |
+
for name, entry in expected.items():
|
| 311 |
+
path = model / name
|
| 312 |
+
if path.stat().st_size != entry["bytes"] or sha256(path) != entry["sha256"]:
|
| 313 |
+
raise ValueError("staged evidence changed while packing the archive")
|
| 314 |
+
manifest = {
|
| 315 |
+
"schema_version": 1,
|
| 316 |
+
"archive": "evidence.zip",
|
| 317 |
+
"compression": "ZIP_DEFLATED",
|
| 318 |
+
"compression_level": 6,
|
| 319 |
+
"archive_sha256": sha256(archive),
|
| 320 |
+
"archive_bytes": archive.stat().st_size,
|
| 321 |
+
"files": expected,
|
| 322 |
+
"extraction_root": "model repository root",
|
| 323 |
+
}
|
| 324 |
+
write_json(index, manifest)
|
| 325 |
+
return manifest
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def render_model_card(template: str, report: dict[str, Any], selection: dict[str, Any]) -> str:
|
| 329 |
+
summary = report["trained_vs_base"]
|
| 330 |
+
rows = [
|
| 331 |
+
"| Player | Lines mean (median) | Score mean (median) | "
|
| 332 |
+
"Placed mean (median) | Cap hits | Errors |",
|
| 333 |
+
"| --- | ---: | ---: | ---: | ---: | ---: |",
|
| 334 |
+
]
|
| 335 |
+
timing = [
|
| 336 |
+
"| Player | Decision mean ms | Median ms | p95 ms | Invalid decisions |",
|
| 337 |
+
"| --- | ---: | ---: | ---: | ---: |",
|
| 338 |
+
]
|
| 339 |
+
for name in PLAYERS:
|
| 340 |
+
player = summary["players"][name]
|
| 341 |
+
primary = player["failure_adjusted"]
|
| 342 |
+
outcomes = " | ".join(
|
| 343 |
+
f"{primary[metric]['mean']:.3f} ({primary[metric]['median']:.1f})"
|
| 344 |
+
for metric in ("lines", "score", "pieces")
|
| 345 |
+
)
|
| 346 |
+
rows.append(
|
| 347 |
+
f"| {name} | {outcomes} | {player['cap_hit_rate']:.1%} | {player['error_rate']:.1%} |"
|
| 348 |
+
)
|
| 349 |
+
latency = player["latency_seconds"]
|
| 350 |
+
values = " | ".join(
|
| 351 |
+
f"{latency[key] * 1000:.3f}" if latency[key] is not None else "unavailable"
|
| 352 |
+
for key in ("mean", "median", "p95")
|
| 353 |
+
)
|
| 354 |
+
timing.append(f"| {name} | {values} | {player['invalid_decisions']} |")
|
| 355 |
+
comparisons = [
|
| 356 |
+
"| Comparison (first minus second) | Mean lines difference | Paired 95% interval |",
|
| 357 |
+
"| --- | ---: | ---: |",
|
| 358 |
+
]
|
| 359 |
+
for key, (first, second) in COMPARISONS.items():
|
| 360 |
+
delta = report[key]["paired_trained_minus_base"]["lines"]
|
| 361 |
+
comparisons.append(
|
| 362 |
+
f"| {first} − {second} | {delta['mean_difference']:.3f} | "
|
| 363 |
+
f"[{delta['ci95_lower']:.3f}, {delta['ci95_upper']:.3f}] |"
|
| 364 |
+
)
|
| 365 |
+
results = (
|
| 366 |
+
"Verified final outcomes on all 200 paired seeds, with a 200-piece cap. "
|
| 367 |
+
"Lines, score and placed-piece summaries use the preregistered zero-on-error policy. "
|
| 368 |
+
"Cap hits indicate censored survival.\n\n"
|
| 369 |
+
+ "\n".join(rows)
|
| 370 |
+
+ "\n\nEach paired 95% bootstrap interval resamples complete episode differences. "
|
| 371 |
+
"Trained versus native base is the primary comparison; the heuristic and FP32-head "
|
| 372 |
+
"comparisons are separate checks. Intervals are not adjusted for multiple comparisons.\n\n"
|
| 373 |
+
+ "\n".join(comparisons)
|
| 374 |
+
+ "\n\nDecision latency includes recorded first-call effects, tokenization and policy "
|
| 375 |
+
"overhead, but excludes game rendering and replay playback.\n\n"
|
| 376 |
+
+ "\n".join(timing)
|
| 377 |
+
+ "\n\nThe full reports retain raw outcomes, all failed seeds and probability logs."
|
| 378 |
+
)
|
| 379 |
+
card = template.replace(
|
| 380 |
+
"**Release preparation: full-study results and selected checkpoint are pending.**",
|
| 381 |
+
f"Selected checkpoint: **{selection['selected_key']}**, "
|
| 382 |
+
"chosen on validation before final tests.",
|
| 383 |
+
).replace("**Insert verified held-out results here before publication.**", results)
|
| 384 |
+
if "pending" in card.lower() or "insert verified" in card.lower():
|
| 385 |
+
raise ValueError("model card still contains pending release text")
|
| 386 |
+
return card + (
|
| 387 |
+
"\n\n## Bundle layout\n\n"
|
| 388 |
+
"`checkpoint/` preserves the selected checkpoint bytes. `code/` contains the source, "
|
| 389 |
+
"locked environment, tests, scripts and milestone tutorials. `evidence.zip` losslessly "
|
| 390 |
+
"compresses the frozen selection, both validation candidates and complete final "
|
| 391 |
+
"evaluation. From the model repository root, run `python -m zipfile -e evidence.zip .` after download verification to restore "
|
| 392 |
+
"`evidence/` and all original relative paths. `evidence-files.json` records each raw "
|
| 393 |
+
"file's byte count and SHA256; extraction preserves the original evidence hashes. "
|
| 394 |
+
"From `code/`, run `uv sync --locked --extra ml`; load `../checkpoint` using the "
|
| 395 |
+
"native Stackcraft loader. No access to a private GitHub repository is required.\n"
|
| 396 |
+
)
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def code_files(root: Path, *, demo: bool) -> list[Path]:
|
| 400 |
+
"""Explicit file classes; no planning, environments, arbitrary data, runs or credentials."""
|
| 401 |
+
files = [
|
| 402 |
+
Path(name)
|
| 403 |
+
for name in (
|
| 404 |
+
"pyproject.toml",
|
| 405 |
+
"uv.lock",
|
| 406 |
+
".python-version",
|
| 407 |
+
"README.md",
|
| 408 |
+
"LICENSE",
|
| 409 |
+
"NOTICE",
|
| 410 |
+
"Dockerfile",
|
| 411 |
+
".dockerignore",
|
| 412 |
+
)
|
| 413 |
+
]
|
| 414 |
+
suffixes = {".py", ".html", ".js", ".css"}
|
| 415 |
+
for path in (root / "src/stackcraft").rglob("*"):
|
| 416 |
+
if path.is_file() and path.suffix in suffixes and "__pycache__" not in path.parts:
|
| 417 |
+
files.append(path.relative_to(root))
|
| 418 |
+
files.append(Path("src/stackcraft/web/baseline-demo.json"))
|
| 419 |
+
if not demo:
|
| 420 |
+
files.extend(Path("scripts") / name for name in REPRO_SCRIPTS)
|
| 421 |
+
files.extend((Path("release/model-card.md"), Path("release/dataset-card.md")))
|
| 422 |
+
for folder, suffix in (("tests", ".py"), ("docs", ".md")):
|
| 423 |
+
files.extend(path.relative_to(root) for path in (root / folder).rglob(f"*{suffix}"))
|
| 424 |
+
for suffix in (".json", ".md"):
|
| 425 |
+
files.extend(path.relative_to(root) for path in (root / "reports").glob(f"*{suffix}"))
|
| 426 |
+
for milestone, name in enumerate(
|
| 427 |
+
("foundation", "game", "baselines", "data", "gpu-feasibility", "evaluation", "release")
|
| 428 |
+
):
|
| 429 |
+
expected = Path("docs/tutorials") / f"{milestone:02d}-{name}.md"
|
| 430 |
+
if expected not in files:
|
| 431 |
+
raise ValueError(f"missing milestone tutorial: {expected}")
|
| 432 |
+
return sorted(set(files))
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
def bundled_source_readme(readme: str) -> str:
|
| 436 |
+
"""Replace only the known private planning link with the shipped release tutorial."""
|
| 437 |
+
return readme.replace(
|
| 438 |
+
"[the project plan](plan.md)",
|
| 439 |
+
"[the release tutorial](docs/tutorials/06-release.md)",
|
| 440 |
+
)
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def space_readme(report_url: str | None = None) -> str:
|
| 444 |
+
"""Describe the CPU demo without links to files excluded from its bundle."""
|
| 445 |
+
readme = """---
|
| 446 |
+
title: Stackcraft
|
| 447 |
+
sdk: docker
|
| 448 |
+
app_port: 7860
|
| 449 |
+
license: apache-2.0
|
| 450 |
+
---
|
| 451 |
+
|
| 452 |
+
# Stackcraft
|
| 453 |
+
|
| 454 |
+
Play a simplified falling-block game and watch a recorded comparison of an
|
| 455 |
+
unchanged Clef model, a trained model and a heuristic on the same piece sequence.
|
| 456 |
+
|
| 457 |
+
**Human play is live. Bot comparisons are recorded.** The comparison replays saved
|
| 458 |
+
placements and their decision probabilities; it does not call a model while you
|
| 459 |
+
watch. Playback speed changes the animation, not measured inference latency.
|
| 460 |
+
The fixed demonstration sequence is seed30000. One game illustrates behavior;
|
| 461 |
+
it does not establish which player performs better across the complete study.
|
| 462 |
+
|
| 463 |
+
Choose an orientation and column, then drop the current piece vertically onto a
|
| 464 |
+
10×20 board. One next piece is visible. Completed rows clear simultaneously.
|
| 465 |
+
There is no timed gravity, hold, wall kick, tuck or T-spin bonus. This is a small
|
| 466 |
+
placement game, not a competitive Tetris implementation. Probabilities express
|
| 467 |
+
model preferences over legal placements, not calibrated chances of winning.
|
| 468 |
+
|
| 469 |
+
The Docker Space runs on CPU and needs no model weights, GPU or model credentials.
|
| 470 |
+
Human sessions are kept in memory; download a replay before a restart to retain
|
| 471 |
+
your game. The service runs as UID1000 on port7860 by default.
|
| 472 |
+
|
| 473 |
+
To run the same image locally:
|
| 474 |
+
|
| 475 |
+
```bash
|
| 476 |
+
docker build -t stackcraft-demo .
|
| 477 |
+
docker run --rm -p 7860:7860 stackcraft-demo
|
| 478 |
+
```
|
| 479 |
+
|
| 480 |
+
Open http://localhost:7860. The [Dockerfile](Dockerfile) and
|
| 481 |
+
[game source](src/stackcraft/) are included here. The
|
| 482 |
+
[recorded comparison](src/stackcraft/web/baseline-demo.json) contains the displayed
|
| 483 |
+
actions, probabilities, outcomes and source report hashes.
|
| 484 |
+
|
| 485 |
+
Apache-2.0: see [LICENSE](LICENSE) and [NOTICE](NOTICE). Stackcraft is independent
|
| 486 |
+
of Cloudflare, Qwen and Tetris.
|
| 487 |
+
"""
|
| 488 |
+
if report_url:
|
| 489 |
+
readme += f"\n[Complete study report](<{report_url}>).\n"
|
| 490 |
+
return readme
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
def build_release(args: argparse.Namespace) -> dict[str, Any]:
|
| 494 |
+
if args.output.exists():
|
| 495 |
+
raise ValueError("release output already exists; use a new directory")
|
| 496 |
+
helpers = {"evaluate": load_helper("evaluate_clef.py"), "export": load_helper("export_demo.py")}
|
| 497 |
+
manifest = json_file(args.dataset / "manifest.json")
|
| 498 |
+
records = {
|
| 499 |
+
split: [
|
| 500 |
+
json.loads(line) for line in (args.dataset / f"{split}.jsonl").read_text().splitlines()
|
| 501 |
+
]
|
| 502 |
+
for split in ("train", "validation")
|
| 503 |
+
}
|
| 504 |
+
audit_dataset(records, manifest)
|
| 505 |
+
for split in ("train", "validation"):
|
| 506 |
+
if sha256(args.dataset / f"{split}.jsonl") != manifest["splits"][split]["sha256"]:
|
| 507 |
+
raise ValueError("dataset file bytes differ from the manifest SHA256")
|
| 508 |
+
if {split: len(rows) for split, rows in records.items()} != {"train": 827, "validation": 215}:
|
| 509 |
+
raise ValueError("release dataset differs from the fixed 827/215-position study")
|
| 510 |
+
validate_reference(args.checkpoint, args.dataset, records)
|
| 511 |
+
report, checkpoint_hashes, evaluation_paths = validate_evaluation(
|
| 512 |
+
args.evaluation, args.selection, args.checkpoint, helpers
|
| 513 |
+
)
|
| 514 |
+
selection = json_file(args.selection)
|
| 515 |
+
evidence_paths = selection_evidence_files(args.selection)
|
| 516 |
+
expected_demo = helpers["export"].export_manifest(
|
| 517 |
+
[args.evaluation / "report.json"],
|
| 518 |
+
["base", "trained", "heuristic"],
|
| 519 |
+
report_url=args.report_url,
|
| 520 |
+
)
|
| 521 |
+
if json_file(ROOT / "src/stackcraft/web/baseline-demo.json") != expected_demo:
|
| 522 |
+
raise ValueError(
|
| 523 |
+
"web demo must first be exported from this exact final report at fixed seed 30000"
|
| 524 |
+
)
|
| 525 |
+
card = render_model_card((ROOT / "release/model-card.md").read_text(), report, selection)
|
| 526 |
+
dataset_card = (ROOT / "release/dataset-card.md").read_text()
|
| 527 |
+
if "pending" in dataset_card.lower():
|
| 528 |
+
raise ValueError("dataset card still contains pending release text")
|
| 529 |
+
code_paths = code_files(ROOT, demo=False)
|
| 530 |
+
demo_paths = code_files(ROOT, demo=True)
|
| 531 |
+
identity = source_identity(ROOT)
|
| 532 |
+
args.output.mkdir(parents=True, exist_ok=False)
|
| 533 |
+
for relative in checkpoint_hashes:
|
| 534 |
+
copy_file(args.checkpoint / relative, args.output / "model/checkpoint" / relative)
|
| 535 |
+
for relative in code_paths:
|
| 536 |
+
copy_file(ROOT / relative, args.output / "model/code" / relative)
|
| 537 |
+
source_readme = args.output / "model/code/README.md"
|
| 538 |
+
source_readme.write_text(bundled_source_readme(source_readme.read_text()))
|
| 539 |
+
for relative in demo_paths:
|
| 540 |
+
copy_file(ROOT / relative, args.output / "demo" / relative)
|
| 541 |
+
for relative in evaluation_paths:
|
| 542 |
+
copy_file(args.evaluation / relative, args.output / "model/evidence/evaluation" / relative)
|
| 543 |
+
for relative in evidence_paths:
|
| 544 |
+
copy_file(
|
| 545 |
+
args.selection.parent / relative, args.output / "model/evidence/selection" / relative
|
| 546 |
+
)
|
| 547 |
+
pack_evidence(args.output / "model")
|
| 548 |
+
# Only remove the copies created in this new staging directory, after read-back
|
| 549 |
+
# verification. Original run/selection artifacts and checkpoints are untouched.
|
| 550 |
+
shutil.rmtree(args.output / "model/evidence")
|
| 551 |
+
for name in ("train.jsonl", "validation.jsonl", "manifest.json"):
|
| 552 |
+
copy_file(args.dataset / name, args.output / "dataset" / name)
|
| 553 |
+
for bundle in ("model", "dataset"):
|
| 554 |
+
for name in ("LICENSE", "NOTICE"):
|
| 555 |
+
copy_file(ROOT / name, args.output / bundle / name)
|
| 556 |
+
(args.output / "model/README.md").write_text(card)
|
| 557 |
+
(args.output / "dataset/README.md").write_text(dataset_card)
|
| 558 |
+
(args.output / "demo/README.md").write_text(space_readme(args.report_url))
|
| 559 |
+
copied_hashes = {
|
| 560 |
+
relative: sha256(args.output / "model/checkpoint" / relative)
|
| 561 |
+
for relative in checkpoint_hashes
|
| 562 |
+
}
|
| 563 |
+
if copied_hashes != checkpoint_hashes:
|
| 564 |
+
raise ValueError("checkpoint bytes changed during staging")
|
| 565 |
+
code = args.output / "model/code"
|
| 566 |
+
write_json(
|
| 567 |
+
code / "source-manifest.json",
|
| 568 |
+
{
|
| 569 |
+
"schema_version": 1,
|
| 570 |
+
"source_commit": identity["source_commit"],
|
| 571 |
+
"source_dirty": identity["source_dirty"],
|
| 572 |
+
"source_hashes": {
|
| 573 |
+
str(path.relative_to(code)): sha256(path)
|
| 574 |
+
for path in sorted(code.rglob("*"))
|
| 575 |
+
if path.is_file()
|
| 576 |
+
},
|
| 577 |
+
},
|
| 578 |
+
)
|
| 579 |
+
# Each independently uploaded repository carries its own byte manifest.
|
| 580 |
+
# The root manifest additionally covers these manifests and all three bundles.
|
| 581 |
+
for bundle in ("model", "dataset", "demo"):
|
| 582 |
+
directory = args.output / bundle
|
| 583 |
+
write_json(
|
| 584 |
+
directory / "release-files.json",
|
| 585 |
+
{
|
| 586 |
+
"schema_version": 1,
|
| 587 |
+
"files": {
|
| 588 |
+
str(path.relative_to(directory)): {
|
| 589 |
+
"sha256": sha256(path),
|
| 590 |
+
"bytes": path.stat().st_size,
|
| 591 |
+
}
|
| 592 |
+
for path in sorted(directory.rglob("*"))
|
| 593 |
+
if path.is_file()
|
| 594 |
+
},
|
| 595 |
+
"manifest_excludes_itself": True,
|
| 596 |
+
"checkpoint_sha256": checkpoint_hashes if bundle == "model" else None,
|
| 597 |
+
},
|
| 598 |
+
)
|
| 599 |
+
files = {
|
| 600 |
+
str(path.relative_to(args.output)): {"sha256": sha256(path), "bytes": path.stat().st_size}
|
| 601 |
+
for path in sorted(args.output.rglob("*"))
|
| 602 |
+
if path.is_file()
|
| 603 |
+
}
|
| 604 |
+
result = {
|
| 605 |
+
"schema_version": 1,
|
| 606 |
+
"kind": "local-release-bundle",
|
| 607 |
+
"published": False,
|
| 608 |
+
"checkpoint_sha256": checkpoint_hashes,
|
| 609 |
+
"selection_sha256": sha256(args.selection),
|
| 610 |
+
"evaluation_report_sha256": sha256(args.evaluation / "report.json"),
|
| 611 |
+
"dataset_manifest_sha256": sha256(args.dataset / "manifest.json"),
|
| 612 |
+
**identity,
|
| 613 |
+
"files": files,
|
| 614 |
+
"manifest_excludes_itself": True,
|
| 615 |
+
}
|
| 616 |
+
write_json(args.output / "release-manifest.json", result)
|
| 617 |
+
return result
|
| 618 |
+
|
| 619 |
+
|
| 620 |
+
def main(argv: list[str] | None = None) -> int:
|
| 621 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 622 |
+
parser.add_argument("--checkpoint", required=True, type=Path)
|
| 623 |
+
parser.add_argument("--selection", required=True, type=Path)
|
| 624 |
+
parser.add_argument("--evaluation", required=True, type=Path)
|
| 625 |
+
parser.add_argument("--dataset", type=Path, default=Path("data/study-v1"))
|
| 626 |
+
parser.add_argument("--output", required=True, type=Path)
|
| 627 |
+
parser.add_argument(
|
| 628 |
+
"--report-url", help="Optional approved public report URL in the demo manifest"
|
| 629 |
+
)
|
| 630 |
+
args = parser.parse_args(argv)
|
| 631 |
+
try:
|
| 632 |
+
manifest = build_release(args)
|
| 633 |
+
except (OSError, ValueError, KeyError, TypeError) as error:
|
| 634 |
+
parser.error(str(error))
|
| 635 |
+
print(f"Prepared {len(manifest['files'])} files locally in {args.output}; nothing published.")
|
| 636 |
+
return 0
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
if __name__ == "__main__":
|
| 640 |
+
raise SystemExit(main())
|
code/scripts/evaluate_clef.py
ADDED
|
@@ -0,0 +1,616 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluate fixed Clef checkpoints without selecting from test results.
|
| 2 |
+
|
| 3 |
+
Examples (real GPU loads; run only after memory admission):
|
| 4 |
+
uv run --locked --extra ml python scripts/evaluate_clef.py positions \
|
| 5 |
+
--dataset data/study-v1 --checkpoint checkpoints/selected --output runs/validation
|
| 6 |
+
uv run --locked --extra ml python scripts/evaluate_clef.py tournament \
|
| 7 |
+
--checkpoint checkpoints/selected --seeds 0,1 --max-pieces 30 --output runs/dev-game
|
| 8 |
+
|
| 9 |
+
Final evaluation additionally requires --final-test --selection-file frozen.json.
|
| 10 |
+
The final selection schema is documented by validate_selection below.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import gc
|
| 17 |
+
import hashlib
|
| 18 |
+
import importlib.metadata
|
| 19 |
+
import json
|
| 20 |
+
import math
|
| 21 |
+
import time
|
| 22 |
+
from dataclasses import asdict
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
from typing import Any
|
| 25 |
+
|
| 26 |
+
from stackcraft.clef import ENCODING_VERSION, MODEL_ID, MODEL_REVISION, ClefPlayer
|
| 27 |
+
from stackcraft.data import audit_dataset, canonical_json
|
| 28 |
+
from stackcraft.evaluation import FINAL_TEST_SEEDS, paired_report, summarize_positions
|
| 29 |
+
from stackcraft.players import (
|
| 30 |
+
Decision,
|
| 31 |
+
HeuristicPlayer,
|
| 32 |
+
Player,
|
| 33 |
+
RandomPlayer,
|
| 34 |
+
observe,
|
| 35 |
+
validate_decision,
|
| 36 |
+
)
|
| 37 |
+
from stackcraft.provenance import source_identity
|
| 38 |
+
from stackcraft.replay import replay_states
|
| 39 |
+
from stackcraft.schema import RULES_VERSION, GameState
|
| 40 |
+
from stackcraft.tournament import run_episode
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def file_hash(path: Path) -> str:
|
| 44 |
+
digest = hashlib.sha256()
|
| 45 |
+
with path.open("rb") as stream:
|
| 46 |
+
while chunk := stream.read(1_048_576):
|
| 47 |
+
digest.update(chunk)
|
| 48 |
+
return digest.hexdigest()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def checkpoint_hashes(path: Path) -> dict[str, str]:
|
| 52 |
+
if (
|
| 53 |
+
not (path / "training_config.json").is_file()
|
| 54 |
+
or not (path / "joint_head.safetensors").is_file()
|
| 55 |
+
):
|
| 56 |
+
raise ValueError("checkpoint must contain training_config.json and joint_head.safetensors")
|
| 57 |
+
return {
|
| 58 |
+
str(file.relative_to(path)): file_hash(file)
|
| 59 |
+
for file in sorted(path.rglob("*"))
|
| 60 |
+
if file.is_file()
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def parse_seeds(value: str) -> tuple[int, ...]:
|
| 65 |
+
"""Comma-separated integers or a Python-style start:stop range."""
|
| 66 |
+
try:
|
| 67 |
+
if ":" in value:
|
| 68 |
+
start, stop = (int(part) for part in value.split(":"))
|
| 69 |
+
seeds = tuple(range(start, stop))
|
| 70 |
+
else:
|
| 71 |
+
seeds = tuple(int(part) for part in value.split(","))
|
| 72 |
+
except ValueError as error:
|
| 73 |
+
raise argparse.ArgumentTypeError(
|
| 74 |
+
"seeds must be comma-separated integers or start:stop"
|
| 75 |
+
) from error
|
| 76 |
+
if not seeds or len(seeds) != len(set(seeds)):
|
| 77 |
+
raise argparse.ArgumentTypeError("seeds must be nonempty and unique")
|
| 78 |
+
return seeds
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def validation_ineligibility(report: dict[str, Any], metadata: dict[str, Any]) -> list[str]:
|
| 82 |
+
"""The preregistered rule: all 215 rows, no errors, complete finite target NLL."""
|
| 83 |
+
reasons = []
|
| 84 |
+
metrics = report.get("players", {}).get("trained", {}).get("metrics", {})
|
| 85 |
+
if report.get("mode") != "positions" or report.get("split") != "validation":
|
| 86 |
+
reasons.append("not validation position evidence")
|
| 87 |
+
if report.get("positions") != 215 or metrics.get("positions") != 215:
|
| 88 |
+
reasons.append("not all 215 validation positions")
|
| 89 |
+
if metrics.get("error_rate") != 0 or metrics.get("failed_predictions") != []:
|
| 90 |
+
reasons.append("validation inference errors")
|
| 91 |
+
if (
|
| 92 |
+
metrics.get("complete_probability_coverage") is not True
|
| 93 |
+
or metrics.get("probability_positions") != 215
|
| 94 |
+
or metrics.get("missing_probabilities") != []
|
| 95 |
+
):
|
| 96 |
+
reasons.append("incomplete validation probability coverage")
|
| 97 |
+
nll = metrics.get("mean_nll")
|
| 98 |
+
if (
|
| 99 |
+
not isinstance(nll, (float, int))
|
| 100 |
+
or isinstance(nll, bool)
|
| 101 |
+
or not math.isfinite(nll)
|
| 102 |
+
or metrics.get("nll_is_infinite") is not False
|
| 103 |
+
):
|
| 104 |
+
reasons.append("nonfinite validation mean target NLL")
|
| 105 |
+
expected_manifest = metadata.get("extra", {}).get("dataset_manifest_sha256")
|
| 106 |
+
if not expected_manifest or report.get("dataset_manifest_sha256") != expected_manifest:
|
| 107 |
+
reasons.append("checkpoint/evidence dataset manifest mismatch")
|
| 108 |
+
return reasons
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def validate_selection(args: argparse.Namespace, hashes: dict[str, str]) -> dict[str, Any] | None:
|
| 112 |
+
"""Require a concrete frozen protocol before any reserved test seed can run.
|
| 113 |
+
|
| 114 |
+
JSON fields: schema_version=1, selection_split='validation', checkpoint_sha256
|
| 115 |
+
(full relative file/hash mapping), validation_evidence={path,sha256},
|
| 116 |
+
test_seeds (all 200), max_pieces, encoding_version, max_length,
|
| 117 |
+
players, head_dtypes, bootstrap_samples=10000, bootstrap_seed=2026,
|
| 118 |
+
max_error_rate=0.0, selected_key, decision_rule. Evidence paths are relative
|
| 119 |
+
to the selection file. Native base head remains BF16; base-fp32 is an ablation.
|
| 120 |
+
"""
|
| 121 |
+
overlap = set(args.seeds) & set(FINAL_TEST_SEEDS)
|
| 122 |
+
if not args.final_test:
|
| 123 |
+
if overlap:
|
| 124 |
+
raise ValueError(
|
| 125 |
+
"reserved test seeds require explicit --final-test and frozen selection"
|
| 126 |
+
)
|
| 127 |
+
if args.selection_file is not None:
|
| 128 |
+
raise ValueError("--selection-file is reserved for --final-test")
|
| 129 |
+
return None
|
| 130 |
+
if tuple(args.seeds) != FINAL_TEST_SEEDS:
|
| 131 |
+
raise ValueError("final evaluation must use exactly --seeds 30000:30200")
|
| 132 |
+
if args.max_pieces != 200:
|
| 133 |
+
raise ValueError("final evaluation is frozen at a 200-piece cap")
|
| 134 |
+
if set(args.players) != {"base", "base-fp32", "trained", "random", "heuristic"}:
|
| 135 |
+
raise ValueError("final evaluation requires all five preregistered players")
|
| 136 |
+
if args.selection_file is None:
|
| 137 |
+
raise ValueError("--final-test requires --selection-file")
|
| 138 |
+
selection = json.loads(args.selection_file.read_text())
|
| 139 |
+
required = {
|
| 140 |
+
"schema_version": 1,
|
| 141 |
+
"selection_split": "validation",
|
| 142 |
+
"checkpoint_sha256": hashes,
|
| 143 |
+
"test_seeds": list(FINAL_TEST_SEEDS),
|
| 144 |
+
"test_seeds_sha256": hashlib.sha256(
|
| 145 |
+
canonical_json(list(FINAL_TEST_SEEDS)).encode()
|
| 146 |
+
).hexdigest(),
|
| 147 |
+
"max_pieces": args.max_pieces,
|
| 148 |
+
"encoding_version": ENCODING_VERSION,
|
| 149 |
+
"max_length": args.max_length,
|
| 150 |
+
"players": args.players,
|
| 151 |
+
"head_dtypes": {
|
| 152 |
+
player: "bfloat16" if player == "base" else "float32"
|
| 153 |
+
for player in args.players
|
| 154 |
+
if player in ("base", "base-fp32", "trained")
|
| 155 |
+
},
|
| 156 |
+
"bootstrap_samples": 10000,
|
| 157 |
+
"bootstrap_seed": 2026,
|
| 158 |
+
"max_error_rate": 0.0,
|
| 159 |
+
}
|
| 160 |
+
for key, value in required.items():
|
| 161 |
+
if type(selection.get(key)) is not type(value) or selection[key] != value:
|
| 162 |
+
raise ValueError(f"frozen selection {key} differs from requested evaluation")
|
| 163 |
+
if any(
|
| 164 |
+
not isinstance(selection.get(key), str) or not selection[key]
|
| 165 |
+
for key in ("selected_key", "decision_rule")
|
| 166 |
+
):
|
| 167 |
+
raise ValueError("frozen selection needs selected_key and decision_rule")
|
| 168 |
+
evidence = selection.get("validation_evidence", {})
|
| 169 |
+
if not isinstance(evidence.get("path"), str) or not evidence["path"]:
|
| 170 |
+
raise ValueError("frozen selection must identify validation evidence")
|
| 171 |
+
path = args.selection_file.parent / evidence["path"]
|
| 172 |
+
if not path.is_file() or file_hash(path) != evidence.get("sha256"):
|
| 173 |
+
raise ValueError("validation evidence file/hash mismatch")
|
| 174 |
+
validation = json.loads(path.read_text())
|
| 175 |
+
if validation.get("mode") != "positions" or validation.get("split") != "validation":
|
| 176 |
+
raise ValueError("selection evidence must be a validation-position evaluation")
|
| 177 |
+
if validation.get("checkpoint_sha256") != hashes:
|
| 178 |
+
raise ValueError("validation evidence evaluated a different checkpoint")
|
| 179 |
+
if "trained" not in validation.get("players", {}):
|
| 180 |
+
raise ValueError("validation evidence must include the trained checkpoint")
|
| 181 |
+
if selection.get("validation_metrics") != validation["players"]["trained"].get("metrics"):
|
| 182 |
+
raise ValueError("frozen validation metrics differ from evidence")
|
| 183 |
+
metadata = json.loads((args.checkpoint / "training_config.json").read_text())
|
| 184 |
+
if reasons := validation_ineligibility(validation, metadata):
|
| 185 |
+
raise ValueError("selected checkpoint is ineligible: " + "; ".join(reasons))
|
| 186 |
+
candidates = selection.get("candidates", [])
|
| 187 |
+
if len(candidates) != 2 or [candidate.get("epoch") for candidate in candidates] != [1, 2]:
|
| 188 |
+
raise ValueError("selection must preserve both preregistered epoch candidates")
|
| 189 |
+
eligible = []
|
| 190 |
+
for candidate in candidates:
|
| 191 |
+
candidate_evidence = args.selection_file.parent / candidate["validation_evidence"]["path"]
|
| 192 |
+
candidate_metadata = args.selection_file.parent / candidate["checkpoint_metadata"]["path"]
|
| 193 |
+
if (
|
| 194 |
+
file_hash(candidate_evidence) != candidate["validation_evidence"]["sha256"]
|
| 195 |
+
or file_hash(candidate_metadata)
|
| 196 |
+
!= candidate["checkpoint_sha256"]["training_config.json"]
|
| 197 |
+
):
|
| 198 |
+
raise ValueError("candidate evidence/metadata hash mismatch")
|
| 199 |
+
report = json.loads(candidate_evidence.read_text())
|
| 200 |
+
config = json.loads(candidate_metadata.read_text())
|
| 201 |
+
if (
|
| 202 |
+
report.get("checkpoint_sha256") != candidate["checkpoint_sha256"]
|
| 203 |
+
or config.get("extra", {}).get("epoch") != candidate["epoch"]
|
| 204 |
+
):
|
| 205 |
+
raise ValueError("candidate report/checkpoint identity mismatch")
|
| 206 |
+
reasons = validation_ineligibility(report, config)
|
| 207 |
+
if candidate.get("ineligibility_reasons") != reasons:
|
| 208 |
+
raise ValueError("candidate eligibility differs from preserved evidence")
|
| 209 |
+
if not reasons:
|
| 210 |
+
eligible.append(
|
| 211 |
+
(report["players"]["trained"]["metrics"]["mean_nll"], candidate["epoch"], candidate)
|
| 212 |
+
)
|
| 213 |
+
if not eligible:
|
| 214 |
+
raise ValueError("no eligible validation checkpoint")
|
| 215 |
+
winner = min(eligible, key=lambda item: (item[0], item[1]))[2]
|
| 216 |
+
if winner["key"] != selection["selected_key"] or winner["checkpoint_sha256"] != hashes:
|
| 217 |
+
raise ValueError("selection violates lowest validation NLL with earlier-epoch tie rule")
|
| 218 |
+
return selection
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def _write(path: Path, value: Any) -> None:
|
| 222 |
+
temporary = path.with_suffix(path.suffix + ".tmp")
|
| 223 |
+
temporary.write_text(json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + "\n")
|
| 224 |
+
temporary.replace(path)
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def _load_player(name: str, args: argparse.Namespace, hashes: dict[str, str]) -> Player:
|
| 228 |
+
if name == "random":
|
| 229 |
+
return RandomPlayer()
|
| 230 |
+
if name == "heuristic":
|
| 231 |
+
return HeuristicPlayer()
|
| 232 |
+
import torch
|
| 233 |
+
|
| 234 |
+
from stackcraft.training import FP32DecisionHead, load_checkpoint
|
| 235 |
+
|
| 236 |
+
torch.set_num_threads(8)
|
| 237 |
+
torch.backends.cuda.matmul.allow_tf32 = False
|
| 238 |
+
|
| 239 |
+
if args.device.startswith("cuda"):
|
| 240 |
+
free, _ = torch.cuda.mem_get_info(args.device)
|
| 241 |
+
if free < args.min_free_gib * 1024**3:
|
| 242 |
+
raise RuntimeError(f"GPU has {free / 1024**3:.2f} GiB free; need {args.min_free_gib}")
|
| 243 |
+
player = ClefPlayer.from_pretrained(
|
| 244 |
+
trust_pinned_code=True,
|
| 245 |
+
local_files_only=True,
|
| 246 |
+
device=args.device,
|
| 247 |
+
max_length=args.max_length,
|
| 248 |
+
)
|
| 249 |
+
if name == "trained":
|
| 250 |
+
load_checkpoint(player.model, args.checkpoint, trainable=False)
|
| 251 |
+
identity = hashlib.sha256(canonical_json(hashes).encode()).hexdigest()
|
| 252 |
+
player.revision = (
|
| 253 |
+
f"{MODEL_ID}@{MODEL_REVISION}:checkpoint-sha256-{identity}:{ENCODING_VERSION}"
|
| 254 |
+
)
|
| 255 |
+
elif name == "base-fp32":
|
| 256 |
+
# Explicit precision ablation; the primary base keeps the native BF16 head.
|
| 257 |
+
player.model.head = FP32DecisionHead(player.model.head)
|
| 258 |
+
player.revision += ":head-fp32-ablation"
|
| 259 |
+
player.model.eval()
|
| 260 |
+
player.name = name
|
| 261 |
+
player.runtime_config.update(
|
| 262 |
+
{
|
| 263 |
+
"revision": player.revision,
|
| 264 |
+
"dtype": str(next(player.model.parameters()).dtype),
|
| 265 |
+
"device": str(next(player.model.parameters()).device),
|
| 266 |
+
"head_dtype": "bfloat16" if name == "base" else "float32",
|
| 267 |
+
"evaluation_head": "native" if name == "base" else "fp32-gathered-rows",
|
| 268 |
+
"versions": installed_versions(),
|
| 269 |
+
"cuda_version": torch.version.cuda,
|
| 270 |
+
"cpu_threads": torch.get_num_threads(),
|
| 271 |
+
"float32_matmul_precision": torch.get_float32_matmul_precision(),
|
| 272 |
+
"cuda_matmul_allow_tf32": torch.backends.cuda.matmul.allow_tf32,
|
| 273 |
+
}
|
| 274 |
+
)
|
| 275 |
+
return player
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
def installed_versions() -> dict[str, str | None]:
|
| 279 |
+
versions = {}
|
| 280 |
+
for package in ("torch", "transformers", "peft", "safetensors", "huggingface-hub"):
|
| 281 |
+
try:
|
| 282 |
+
versions[package] = importlib.metadata.version(package)
|
| 283 |
+
except importlib.metadata.PackageNotFoundError:
|
| 284 |
+
versions[package] = None
|
| 285 |
+
return versions
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def validate_neural_runtimes(identities: dict[str, Any]) -> None:
|
| 289 |
+
"""Head precision may differ explicitly; shared backbone/input settings may not."""
|
| 290 |
+
common = (
|
| 291 |
+
"model_id",
|
| 292 |
+
"base_revision",
|
| 293 |
+
"encoding_version",
|
| 294 |
+
"source_sha256",
|
| 295 |
+
"max_length",
|
| 296 |
+
"dtype",
|
| 297 |
+
"device",
|
| 298 |
+
"versions",
|
| 299 |
+
"cuda_version",
|
| 300 |
+
"cpu_threads",
|
| 301 |
+
"float32_matmul_precision",
|
| 302 |
+
"cuda_matmul_allow_tf32",
|
| 303 |
+
)
|
| 304 |
+
selected = [
|
| 305 |
+
value["runtime_config"]
|
| 306 |
+
for key, value in identities.items()
|
| 307 |
+
if key in ("base", "base-fp32", "trained")
|
| 308 |
+
]
|
| 309 |
+
if selected and any(
|
| 310 |
+
{k: runtime.get(k) for k in common} != {k: selected[0].get(k) for k in common}
|
| 311 |
+
for runtime in selected[1:]
|
| 312 |
+
):
|
| 313 |
+
raise ValueError("neural players differ in shared backbone, encoding, precision or device")
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def _release() -> None:
|
| 317 |
+
gc.collect()
|
| 318 |
+
import sys
|
| 319 |
+
|
| 320 |
+
if "torch" in sys.modules:
|
| 321 |
+
torch = sys.modules["torch"]
|
| 322 |
+
if torch.cuda.is_available():
|
| 323 |
+
torch.cuda.empty_cache()
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def _provenance() -> dict[str, Any]:
|
| 327 |
+
root = Path(__file__).resolve().parents[1]
|
| 328 |
+
identity = source_identity(root)
|
| 329 |
+
paths = [Path(__file__).resolve(), root / "uv.lock"] + [
|
| 330 |
+
root / "src/stackcraft" / name
|
| 331 |
+
for name in (
|
| 332 |
+
"clef.py",
|
| 333 |
+
"training.py",
|
| 334 |
+
"evaluation.py",
|
| 335 |
+
"tournament.py",
|
| 336 |
+
"engine.py",
|
| 337 |
+
"pieces.py",
|
| 338 |
+
"players/__init__.py",
|
| 339 |
+
"schema.py",
|
| 340 |
+
"data.py",
|
| 341 |
+
"provenance.py",
|
| 342 |
+
)
|
| 343 |
+
]
|
| 344 |
+
return {
|
| 345 |
+
**identity,
|
| 346 |
+
"source_commit": identity["source_commit"]
|
| 347 |
+
+ ("+working-tree" if identity["source_dirty"] else ""),
|
| 348 |
+
"source_hashes": {str(path.relative_to(root)): file_hash(path) for path in paths},
|
| 349 |
+
"rules_version": RULES_VERSION,
|
| 350 |
+
"base_revision": MODEL_REVISION,
|
| 351 |
+
"encoding_version": ENCODING_VERSION,
|
| 352 |
+
"precision_policy": "native BF16 base; FP32 trained head; optional base-fp32 ablation",
|
| 353 |
+
"installed_versions": installed_versions(),
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def evaluate_positions(args: argparse.Namespace, hashes: dict[str, str]) -> dict[str, Any]:
|
| 358 |
+
manifest = json.loads((args.dataset / "manifest.json").read_text())
|
| 359 |
+
splits = {
|
| 360 |
+
split: [
|
| 361 |
+
json.loads(line) for line in (args.dataset / f"{split}.jsonl").read_text().splitlines()
|
| 362 |
+
]
|
| 363 |
+
for split in ("train", "validation")
|
| 364 |
+
}
|
| 365 |
+
audit_dataset(splits, manifest)
|
| 366 |
+
records = splits["validation"]
|
| 367 |
+
if not records:
|
| 368 |
+
raise ValueError("validation split is empty")
|
| 369 |
+
result: dict[str, Any] = {
|
| 370 |
+
"mode": "positions",
|
| 371 |
+
"split": "validation",
|
| 372 |
+
"positions": len(records),
|
| 373 |
+
"dataset_manifest_sha256": file_hash(args.dataset / "manifest.json"),
|
| 374 |
+
"checkpoint_sha256": hashes,
|
| 375 |
+
"provenance": _provenance(),
|
| 376 |
+
"players": {},
|
| 377 |
+
}
|
| 378 |
+
for name in args.players:
|
| 379 |
+
loading = time.perf_counter()
|
| 380 |
+
player = _load_player(name, args, hashes)
|
| 381 |
+
load_seconds = time.perf_counter() - loading
|
| 382 |
+
predictions: dict[str, Decision | None] = {}
|
| 383 |
+
with (args.output / f"{name}-positions.jsonl").open("x") as stream:
|
| 384 |
+
for row in records:
|
| 385 |
+
raw = row["observation"]
|
| 386 |
+
state = GameState(
|
| 387 |
+
tuple(tuple(r) for r in raw["board"]), 0, 0, raw["current"], raw["next_piece"]
|
| 388 |
+
)
|
| 389 |
+
observation = observe(state)
|
| 390 |
+
event: dict[str, Any] = {"id": row["id"], "target_action_id": row["action_id"]}
|
| 391 |
+
started = time.perf_counter()
|
| 392 |
+
try:
|
| 393 |
+
decision = player.choose(observation)
|
| 394 |
+
validate_decision(decision, observation)
|
| 395 |
+
except Exception as error:
|
| 396 |
+
predictions[row["id"]] = None
|
| 397 |
+
event["error"] = {"type": type(error).__name__, "message": str(error)}
|
| 398 |
+
else:
|
| 399 |
+
predictions[row["id"]] = decision
|
| 400 |
+
event["decision"] = asdict(decision)
|
| 401 |
+
event["input_tokens"] = getattr(player, "last_input_tokens", None)
|
| 402 |
+
event["decision_seconds"] = time.perf_counter() - started
|
| 403 |
+
stream.write(canonical_json(event) + "\n")
|
| 404 |
+
stream.flush()
|
| 405 |
+
result["players"][name] = {
|
| 406 |
+
"revision": player.revision,
|
| 407 |
+
"runtime_config": getattr(player, "runtime_config", {}),
|
| 408 |
+
"load_seconds": load_seconds,
|
| 409 |
+
"metrics": summarize_positions(records, predictions),
|
| 410 |
+
"predictions_file": f"{name}-positions.jsonl",
|
| 411 |
+
"predictions_sha256": file_hash(args.output / f"{name}-positions.jsonl"),
|
| 412 |
+
}
|
| 413 |
+
validate_neural_runtimes(result["players"])
|
| 414 |
+
_write(args.output / "report.json", result)
|
| 415 |
+
del player
|
| 416 |
+
_release()
|
| 417 |
+
return result
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def evaluate_tournament(
|
| 421 |
+
args: argparse.Namespace, hashes: dict[str, str], selection: Any
|
| 422 |
+
) -> dict[str, Any]:
|
| 423 |
+
episodes = []
|
| 424 |
+
result: dict[str, Any] = {
|
| 425 |
+
"mode": "tournament",
|
| 426 |
+
"final_test": args.final_test,
|
| 427 |
+
"seeds": list(args.seeds),
|
| 428 |
+
"max_pieces": args.max_pieces,
|
| 429 |
+
"checkpoint_sha256": hashes,
|
| 430 |
+
"selection": selection,
|
| 431 |
+
"provenance": _provenance(),
|
| 432 |
+
"players": {},
|
| 433 |
+
"episodes": episodes,
|
| 434 |
+
}
|
| 435 |
+
for name in args.players:
|
| 436 |
+
directory = args.output / name
|
| 437 |
+
directory.mkdir(exist_ok=True)
|
| 438 |
+
saved = {}
|
| 439 |
+
for seed in args.seeds:
|
| 440 |
+
path = directory / f"seed-{seed}.json"
|
| 441 |
+
if path.exists():
|
| 442 |
+
episode = json.loads(path.read_text())
|
| 443 |
+
if (
|
| 444 |
+
episode.get("player_id") != name
|
| 445 |
+
or episode.get("seed") != seed
|
| 446 |
+
or episode.get("max_pieces") != args.max_pieces
|
| 447 |
+
):
|
| 448 |
+
raise ValueError("saved episode identity differs from frozen request")
|
| 449 |
+
final = replay_states(episode["replay"])[-1]
|
| 450 |
+
if (
|
| 451 |
+
any(
|
| 452 |
+
episode["outcome"][key] != getattr(final, key)
|
| 453 |
+
for key in ("score", "lines", "terminal")
|
| 454 |
+
)
|
| 455 |
+
or episode["outcome"]["pieces"] != final.piece_index
|
| 456 |
+
):
|
| 457 |
+
raise ValueError("saved episode outcome differs from replay")
|
| 458 |
+
saved[seed] = episode
|
| 459 |
+
metadata_path = directory / "player.json"
|
| 460 |
+
if len(saved) == len(args.seeds):
|
| 461 |
+
if not metadata_path.exists():
|
| 462 |
+
raise ValueError("completed player episodes lack player metadata")
|
| 463 |
+
metadata = json.loads(metadata_path.read_text())
|
| 464 |
+
if any(
|
| 465 |
+
episode["player"]["revision"] != metadata["revision"]
|
| 466 |
+
or episode["player"].get("runtime_config", {}) != metadata["runtime_config"]
|
| 467 |
+
for episode in saved.values()
|
| 468 |
+
):
|
| 469 |
+
raise ValueError("completed episode identity differs from saved player metadata")
|
| 470 |
+
result["players"][name] = metadata
|
| 471 |
+
episodes.extend(saved[seed] for seed in args.seeds)
|
| 472 |
+
continue
|
| 473 |
+
loading = time.perf_counter()
|
| 474 |
+
player = _load_player(name, args, hashes)
|
| 475 |
+
load_seconds = time.perf_counter() - loading
|
| 476 |
+
identity = {
|
| 477 |
+
"revision": player.revision,
|
| 478 |
+
"runtime_config": getattr(player, "runtime_config", {}),
|
| 479 |
+
}
|
| 480 |
+
if any(
|
| 481 |
+
episode["player"]["revision"] != identity["revision"]
|
| 482 |
+
or episode["player"].get("runtime_config", {}) != identity["runtime_config"]
|
| 483 |
+
for episode in saved.values()
|
| 484 |
+
):
|
| 485 |
+
raise ValueError("loaded player differs from saved episode identity")
|
| 486 |
+
if metadata_path.exists():
|
| 487 |
+
metadata = json.loads(metadata_path.read_text())
|
| 488 |
+
if any(metadata.get(key) != value for key, value in identity.items()):
|
| 489 |
+
raise ValueError("loaded player differs from saved player metadata")
|
| 490 |
+
metadata["load_seconds"].append(load_seconds)
|
| 491 |
+
else:
|
| 492 |
+
metadata = {**identity, "load_seconds": [load_seconds]}
|
| 493 |
+
result["players"][name] = metadata
|
| 494 |
+
validate_neural_runtimes(result["players"])
|
| 495 |
+
_write(metadata_path, metadata)
|
| 496 |
+
# No excluded model calls. First-call effects remain in recorded latency.
|
| 497 |
+
for seed in args.seeds:
|
| 498 |
+
if seed in saved:
|
| 499 |
+
episodes.append(saved[seed])
|
| 500 |
+
continue
|
| 501 |
+
if name == "random":
|
| 502 |
+
player = RandomPlayer() # episode-independent behavior RNG
|
| 503 |
+
episode = run_episode(player, seed, args.max_pieces)
|
| 504 |
+
episode["player_id"] = name
|
| 505 |
+
_write(directory / f"seed-{seed}.json", episode)
|
| 506 |
+
episodes.append(episode)
|
| 507 |
+
_write(
|
| 508 |
+
args.output / "progress.json",
|
| 509 |
+
{"player": name, "seed": seed, "episodes": len(episodes)},
|
| 510 |
+
)
|
| 511 |
+
del player
|
| 512 |
+
_release()
|
| 513 |
+
validate_neural_runtimes(result["players"])
|
| 514 |
+
if "trained" in args.players and "base" in args.players:
|
| 515 |
+
result["trained_vs_base"] = paired_report(episodes, trained_id="trained", base_id="base")
|
| 516 |
+
if "trained" in args.players and "heuristic" in args.players:
|
| 517 |
+
result["trained_vs_heuristic"] = paired_report(
|
| 518 |
+
episodes, trained_id="trained", base_id="heuristic"
|
| 519 |
+
)
|
| 520 |
+
if "trained" in args.players and "base-fp32" in args.players:
|
| 521 |
+
result["trained_vs_base_fp32"] = paired_report(
|
| 522 |
+
episodes, trained_id="trained", base_id="base-fp32"
|
| 523 |
+
)
|
| 524 |
+
if "base-fp32" in args.players and "base" in args.players:
|
| 525 |
+
result["base_fp32_vs_base"] = paired_report(
|
| 526 |
+
episodes, trained_id="base-fp32", base_id="base"
|
| 527 |
+
)
|
| 528 |
+
_write(args.output / "report.json", result)
|
| 529 |
+
return result
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
def parser() -> argparse.ArgumentParser:
|
| 533 |
+
root = argparse.ArgumentParser(description=__doc__)
|
| 534 |
+
sub = root.add_subparsers(dest="mode", required=True)
|
| 535 |
+
for name in ("positions", "tournament"):
|
| 536 |
+
command = sub.add_parser(name)
|
| 537 |
+
command.add_argument("--checkpoint", type=Path)
|
| 538 |
+
command.add_argument("--output", type=Path, required=True)
|
| 539 |
+
command.add_argument(
|
| 540 |
+
"--players",
|
| 541 |
+
nargs="+",
|
| 542 |
+
choices=("base", "base-fp32", "trained", "random", "heuristic"),
|
| 543 |
+
default=["base", "base-fp32", "trained"]
|
| 544 |
+
if name == "positions"
|
| 545 |
+
else ["base", "base-fp32", "trained", "random", "heuristic"],
|
| 546 |
+
)
|
| 547 |
+
command.add_argument("--device", default="cuda")
|
| 548 |
+
command.add_argument("--min-free-gib", type=float, default=22.0)
|
| 549 |
+
command.add_argument("--max-length", type=int, default=4096)
|
| 550 |
+
if name == "positions":
|
| 551 |
+
command.add_argument("--dataset", type=Path, required=True)
|
| 552 |
+
else:
|
| 553 |
+
command.add_argument(
|
| 554 |
+
"--seeds",
|
| 555 |
+
type=parse_seeds,
|
| 556 |
+
required=True,
|
| 557 |
+
help="comma integers or start:stop (stop exclusive)",
|
| 558 |
+
)
|
| 559 |
+
command.add_argument("--max-pieces", type=int, required=True)
|
| 560 |
+
command.add_argument("--final-test", action="store_true")
|
| 561 |
+
command.add_argument("--selection-file", type=Path)
|
| 562 |
+
command.add_argument(
|
| 563 |
+
"--resume", action="store_true", help="resume matching atomic episode artifacts"
|
| 564 |
+
)
|
| 565 |
+
return root
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
def main(argv: list[str] | None = None) -> int:
|
| 569 |
+
cli = parser()
|
| 570 |
+
args = cli.parse_args(argv)
|
| 571 |
+
if len(set(args.players)) != len(args.players):
|
| 572 |
+
cli.error("players must be unique")
|
| 573 |
+
if args.max_length < 1 or not 0 < args.min_free_gib <= 1000:
|
| 574 |
+
cli.error("max-length and min-free-gib must be positive finite values")
|
| 575 |
+
if "trained" in args.players and args.checkpoint is None:
|
| 576 |
+
cli.error("trained evaluation requires --checkpoint")
|
| 577 |
+
if args.mode == "tournament" and args.max_pieces < 1:
|
| 578 |
+
cli.error("max-pieces must be positive")
|
| 579 |
+
try:
|
| 580 |
+
hashes = checkpoint_hashes(args.checkpoint) if args.checkpoint is not None else {}
|
| 581 |
+
selection = validate_selection(args, hashes) if args.mode == "tournament" else None
|
| 582 |
+
request = {
|
| 583 |
+
key: str(value) if isinstance(value, Path) else value
|
| 584 |
+
for key, value in vars(args).items()
|
| 585 |
+
if key != "resume"
|
| 586 |
+
}
|
| 587 |
+
provenance = _provenance()
|
| 588 |
+
request.update(
|
| 589 |
+
checkpoint_sha256=hashes,
|
| 590 |
+
source_hashes=provenance["source_hashes"],
|
| 591 |
+
installed_versions=provenance.get("installed_versions"),
|
| 592 |
+
)
|
| 593 |
+
request["selection_sha256"] = (
|
| 594 |
+
file_hash(args.selection_file)
|
| 595 |
+
if args.mode == "tournament" and args.selection_file
|
| 596 |
+
else None
|
| 597 |
+
)
|
| 598 |
+
request = json.loads(canonical_json(request))
|
| 599 |
+
if getattr(args, "resume", False):
|
| 600 |
+
if json.loads((args.output / "request.json").read_text()) != request:
|
| 601 |
+
raise ValueError("resume request/source/checkpoint differs from original run")
|
| 602 |
+
else:
|
| 603 |
+
args.output.mkdir(parents=True, exist_ok=False)
|
| 604 |
+
_write(args.output / "request.json", request)
|
| 605 |
+
if args.mode == "positions":
|
| 606 |
+
result = evaluate_positions(args, hashes)
|
| 607 |
+
else:
|
| 608 |
+
result = evaluate_tournament(args, hashes, selection)
|
| 609 |
+
except (ValueError, OSError) as error:
|
| 610 |
+
cli.error(str(error))
|
| 611 |
+
print(json.dumps({"mode": result["mode"], "output": str(args.output), "status": "complete"}))
|
| 612 |
+
return 0
|
| 613 |
+
|
| 614 |
+
|
| 615 |
+
if __name__ == "__main__":
|
| 616 |
+
raise SystemExit(main())
|
code/scripts/export_demo.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Export a fixed-seed, validated recorded comparison without loading any model."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
import hashlib
|
| 7 |
+
import json
|
| 8 |
+
import math
|
| 9 |
+
from dataclasses import asdict
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Any
|
| 12 |
+
from urllib.parse import urlsplit
|
| 13 |
+
|
| 14 |
+
from stackcraft.players import Decision, observe, validate_decision
|
| 15 |
+
from stackcraft.replay import replay_states
|
| 16 |
+
from stackcraft.schema import RULES_VERSION
|
| 17 |
+
|
| 18 |
+
DISPLAY_NAMES = {
|
| 19 |
+
"base": "Clef-flash base",
|
| 20 |
+
"base-fp32": "Base · FP32 head",
|
| 21 |
+
"trained": "Stackcraft fine-tune",
|
| 22 |
+
"heuristic": "Heuristic",
|
| 23 |
+
"random": "Random",
|
| 24 |
+
"clef-base": "Clef-flash base",
|
| 25 |
+
}
|
| 26 |
+
DEMO_SEED = 30000
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _json_value(value: Any) -> Any:
|
| 30 |
+
return json.loads(json.dumps(value, sort_keys=True, allow_nan=False))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def validated_player(episode: dict[str, Any], source_index: int) -> dict[str, Any]:
|
| 34 |
+
"""Re-simulate moves and check probabilities against their exact observations."""
|
| 35 |
+
if (
|
| 36 |
+
type(episode.get("schema_version")) is not int
|
| 37 |
+
or episode["schema_version"] != 1
|
| 38 |
+
or episode.get("rules_version") != RULES_VERSION
|
| 39 |
+
):
|
| 40 |
+
raise ValueError("episode uses an incompatible schema or rules version")
|
| 41 |
+
cap = episode.get("max_pieces")
|
| 42 |
+
if type(cap) is not int or not 1 <= cap <= 2000:
|
| 43 |
+
raise ValueError("demo episode cap must be between 1 and 2000")
|
| 44 |
+
replay = episode["replay"]
|
| 45 |
+
if replay.get("seed") != episode.get("seed"):
|
| 46 |
+
raise ValueError("episode seed differs from replay seed")
|
| 47 |
+
states = replay_states(replay)
|
| 48 |
+
final = states[-1]
|
| 49 |
+
if final.piece_index > cap:
|
| 50 |
+
raise ValueError("episode exceeds its declared cap")
|
| 51 |
+
outcome = episode["outcome"]
|
| 52 |
+
expected = {
|
| 53 |
+
"score": final.score,
|
| 54 |
+
"lines": final.lines,
|
| 55 |
+
"pieces": final.piece_index,
|
| 56 |
+
"terminal": final.terminal,
|
| 57 |
+
}
|
| 58 |
+
if any(type(outcome.get(k)) is not type(v) or outcome[k] != v for k, v in expected.items()):
|
| 59 |
+
raise ValueError("episode outcome differs from replay")
|
| 60 |
+
errors = episode.get("errors")
|
| 61 |
+
if not isinstance(errors, list):
|
| 62 |
+
raise ValueError("episode errors must be an explicit list")
|
| 63 |
+
status = "error" if errors else "top_out" if final.terminal else "cap"
|
| 64 |
+
if outcome.get("status") != status or outcome.get("cap_hit") is not (
|
| 65 |
+
not errors and not final.terminal and final.piece_index == cap
|
| 66 |
+
):
|
| 67 |
+
raise ValueError("episode stop reason or cap flag is inconsistent")
|
| 68 |
+
if not errors and not final.terminal and final.piece_index != cap:
|
| 69 |
+
raise ValueError("successful recording ended before its declared cap")
|
| 70 |
+
events = episode["decisions"]
|
| 71 |
+
if not isinstance(events, list) or len(events) != final.piece_index + bool(errors):
|
| 72 |
+
raise ValueError("decision count differs from replay and error outcome")
|
| 73 |
+
compact = []
|
| 74 |
+
for index, event in enumerate(events):
|
| 75 |
+
if type(event.get("turn")) is not int or event["turn"] != index:
|
| 76 |
+
raise ValueError("recorded decision indices must be consecutive")
|
| 77 |
+
observation = observe(states[index])
|
| 78 |
+
if _json_value(event.get("observation")) != _json_value(asdict(observation)):
|
| 79 |
+
raise ValueError("recorded decision observation differs from replay")
|
| 80 |
+
seconds = event.get("decision_seconds")
|
| 81 |
+
if type(seconds) not in (float, int) or not math.isfinite(seconds) or seconds < 0:
|
| 82 |
+
raise ValueError("recorded decision time must be finite and nonnegative")
|
| 83 |
+
item = {"turn": index, "decision_seconds": seconds}
|
| 84 |
+
if index == final.piece_index:
|
| 85 |
+
if event.get("error") not in errors or "action_id" in event:
|
| 86 |
+
raise ValueError("terminal error event differs from episode errors")
|
| 87 |
+
item["error"] = event["error"]
|
| 88 |
+
else:
|
| 89 |
+
action = event.get("action_id")
|
| 90 |
+
if action != replay["actions"][index]:
|
| 91 |
+
raise ValueError("recorded decision differs from replay action")
|
| 92 |
+
distribution = event.get("probabilities")
|
| 93 |
+
validate_decision(Decision(action, distribution), observation)
|
| 94 |
+
item.update(action_id=action, probabilities=distribution)
|
| 95 |
+
if "input_tokens" in event:
|
| 96 |
+
if type(event["input_tokens"]) is not int or event["input_tokens"] < 1:
|
| 97 |
+
raise ValueError("input token counts must be positive integers")
|
| 98 |
+
item["input_tokens"] = event["input_tokens"]
|
| 99 |
+
compact.append(item)
|
| 100 |
+
player = episode["player"]
|
| 101 |
+
player_id = episode["player_id"]
|
| 102 |
+
if not isinstance(player.get("revision"), str) or not player["revision"]:
|
| 103 |
+
raise ValueError("player revision must identify the actual recorded player")
|
| 104 |
+
return {
|
| 105 |
+
"id": player_id,
|
| 106 |
+
"name": DISPLAY_NAMES.get(player_id, player.get("name", player_id)),
|
| 107 |
+
"revision": player["revision"],
|
| 108 |
+
"runtime_config": player.get("runtime_config", {}),
|
| 109 |
+
"replay": replay,
|
| 110 |
+
"outcome": outcome,
|
| 111 |
+
"errors": errors,
|
| 112 |
+
"decisions": compact,
|
| 113 |
+
"source_index": source_index,
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def export_manifest(
|
| 118 |
+
inputs: list[Path], players: list[str], seed: int = DEMO_SEED, *, report_url: str | None = None
|
| 119 |
+
) -> dict[str, Any]:
|
| 120 |
+
if report_url is not None:
|
| 121 |
+
parsed = urlsplit(report_url)
|
| 122 |
+
if (
|
| 123 |
+
parsed.scheme != "https"
|
| 124 |
+
or not parsed.hostname
|
| 125 |
+
or parsed.username is not None
|
| 126 |
+
or parsed.password is not None
|
| 127 |
+
or any(char.isspace() or ord(char) < 32 for char in report_url)
|
| 128 |
+
or "\\" in report_url
|
| 129 |
+
):
|
| 130 |
+
raise ValueError("report URL must be an absolute HTTPS URL without credentials")
|
| 131 |
+
_ = parsed.port # Also reject malformed or out-of-range explicit ports.
|
| 132 |
+
if seed != DEMO_SEED or type(seed) is not int:
|
| 133 |
+
raise ValueError("the release demo seed is fixed before results at 30000")
|
| 134 |
+
if len(players) != len(set(players)) or not 2 <= len(players) <= 4:
|
| 135 |
+
raise ValueError("choose two to four distinct players")
|
| 136 |
+
sources = []
|
| 137 |
+
selected = {}
|
| 138 |
+
caps = set()
|
| 139 |
+
for path in inputs:
|
| 140 |
+
raw = path.read_bytes()
|
| 141 |
+
source = json.loads(raw)
|
| 142 |
+
if not isinstance(source, dict):
|
| 143 |
+
raise ValueError("source artifact must be a JSON object")
|
| 144 |
+
episodes = source.get("episodes", [source] if "replay" in source else None)
|
| 145 |
+
if not isinstance(episodes, list):
|
| 146 |
+
raise ValueError(f"{path.name} is not a tournament or episode artifact")
|
| 147 |
+
source_index = len(sources)
|
| 148 |
+
provenance = {
|
| 149 |
+
key: source[key]
|
| 150 |
+
for key in ("provenance", "checkpoint_sha256", "final_test", "development_only")
|
| 151 |
+
if key in source
|
| 152 |
+
}
|
| 153 |
+
sources.append({"file": path.name, "sha256": hashlib.sha256(raw).hexdigest(), **provenance})
|
| 154 |
+
for episode in episodes:
|
| 155 |
+
if not isinstance(episode, dict):
|
| 156 |
+
raise ValueError("episodes must be JSON objects")
|
| 157 |
+
identity = episode.get("player_id")
|
| 158 |
+
if episode.get("seed") != seed or identity not in players:
|
| 159 |
+
continue
|
| 160 |
+
if identity in selected:
|
| 161 |
+
raise ValueError(f"duplicate recording for {identity} at seed {seed}")
|
| 162 |
+
selected[identity] = validated_player(episode, source_index)
|
| 163 |
+
caps.add(episode["max_pieces"])
|
| 164 |
+
missing = set(players) - set(selected)
|
| 165 |
+
if missing:
|
| 166 |
+
raise ValueError(f"missing fixed-seed recordings for: {', '.join(sorted(missing))}")
|
| 167 |
+
if len(caps) != 1:
|
| 168 |
+
raise ValueError("all recorded players must have the same episode cap")
|
| 169 |
+
return {
|
| 170 |
+
"schema_version": 1,
|
| 171 |
+
"rules_version": RULES_VERSION,
|
| 172 |
+
"kind": "recorded-evaluation",
|
| 173 |
+
"label": "Recorded player comparison",
|
| 174 |
+
"seed": seed,
|
| 175 |
+
"max_pieces": caps.pop(),
|
| 176 |
+
"selection": {"seed": seed, "method": "fixed-before-results"},
|
| 177 |
+
"disclaimer": (
|
| 178 |
+
"Recorded decisions, no live model inference. Seed 30000 was fixed before results. "
|
| 179 |
+
"One replay illustrates behavior; it is not the aggregate evaluation. "
|
| 180 |
+
"Playback speed does not represent decision latency."
|
| 181 |
+
),
|
| 182 |
+
"sources": sources,
|
| 183 |
+
**({"report_url": report_url} if report_url is not None else {}),
|
| 184 |
+
"players": [selected[name] for name in players],
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def main(argv: list[str] | None = None) -> int:
|
| 189 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 190 |
+
parser.add_argument("--input", action="append", type=Path, required=True)
|
| 191 |
+
parser.add_argument("--players", nargs="+", default=["base", "trained", "heuristic"])
|
| 192 |
+
parser.add_argument("--seed", type=int, default=DEMO_SEED)
|
| 193 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 194 |
+
parser.add_argument(
|
| 195 |
+
"--report-url", help="Confirmed public HTTPS location of full study results"
|
| 196 |
+
)
|
| 197 |
+
args = parser.parse_args(argv)
|
| 198 |
+
if args.output.exists():
|
| 199 |
+
parser.error("output exists; choose a new path to preserve earlier evidence")
|
| 200 |
+
try:
|
| 201 |
+
manifest = export_manifest(args.input, args.players, args.seed, report_url=args.report_url)
|
| 202 |
+
serialized = json.dumps(manifest, indent=2, allow_nan=False) + "\n"
|
| 203 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 204 |
+
with args.output.open("x") as stream:
|
| 205 |
+
stream.write(serialized)
|
| 206 |
+
except (OSError, ValueError, KeyError, TypeError) as error:
|
| 207 |
+
parser.error(str(error))
|
| 208 |
+
print(f"Exported recorded seed {args.seed} to {args.output}")
|
| 209 |
+
return 0
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
if __name__ == "__main__":
|
| 213 |
+
raise SystemExit(main())
|
code/scripts/gpu_session.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run a bounded Stackcraft job while temporarily borrowing the GLM GPU.
|
| 2 |
+
|
| 3 |
+
Use only under the user's recorded authorization to stop and restore GLM.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import contextlib
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
import signal
|
| 11 |
+
import subprocess
|
| 12 |
+
import time
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
CONTAINER = "omarchy-local-ai-glm-4.7-flash.llamacpp.q5xl.32k.rtx-5090-engine"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 20 |
+
parser.add_argument("--timeout", type=int, default=1800)
|
| 21 |
+
parser.add_argument("--record", type=Path, required=True)
|
| 22 |
+
parser.add_argument("command", nargs=argparse.REMAINDER)
|
| 23 |
+
args = parser.parse_args()
|
| 24 |
+
command = args.command[1:] if args.command[:1] == ["--"] else args.command
|
| 25 |
+
if not command or args.record.exists():
|
| 26 |
+
parser.error("supply a command and a new record path")
|
| 27 |
+
state = subprocess.check_output(
|
| 28 |
+
["docker", "inspect", "--format", "{{.State.Running}}", CONTAINER], text=True
|
| 29 |
+
).strip()
|
| 30 |
+
if state != "true":
|
| 31 |
+
parser.error("GLM is not currently running; do not alter an unexpected service state")
|
| 32 |
+
args.record.parent.mkdir(parents=True, exist_ok=True)
|
| 33 |
+
report = {"container": CONTAINER, "command": command, "original_running": True}
|
| 34 |
+
child = None
|
| 35 |
+
stopped = False
|
| 36 |
+
|
| 37 |
+
def interrupt(signum, frame):
|
| 38 |
+
raise KeyboardInterrupt(f"received signal{signum}")
|
| 39 |
+
|
| 40 |
+
signal.signal(signal.SIGTERM, interrupt)
|
| 41 |
+
signal.signal(signal.SIGINT, interrupt)
|
| 42 |
+
try:
|
| 43 |
+
# Set before stop so a partial/interrupted Docker call still attempts restoration.
|
| 44 |
+
stopped = True
|
| 45 |
+
subprocess.run(["docker", "stop", "--timeout", "30", CONTAINER], check=True, timeout=45)
|
| 46 |
+
report["stopped_at"] = time.time()
|
| 47 |
+
args.record.write_text(json.dumps(report, indent=2) + "\n")
|
| 48 |
+
child = subprocess.Popen(command, start_new_session=True)
|
| 49 |
+
report["job_pid"] = child.pid
|
| 50 |
+
args.record.write_text(json.dumps(report, indent=2) + "\n")
|
| 51 |
+
code = child.wait(timeout=args.timeout)
|
| 52 |
+
report["exit_code"] = code
|
| 53 |
+
if code:
|
| 54 |
+
raise RuntimeError(f"Stackcraft GPU job exited with code{code}")
|
| 55 |
+
finally:
|
| 56 |
+
# A second ordinary interrupt must not interrupt service restoration.
|
| 57 |
+
signal.signal(signal.SIGTERM, signal.SIG_IGN)
|
| 58 |
+
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
| 59 |
+
try:
|
| 60 |
+
try:
|
| 61 |
+
if child is not None and child.poll() is None:
|
| 62 |
+
with contextlib.suppress(ProcessLookupError):
|
| 63 |
+
os.killpg(child.pid, signal.SIGTERM)
|
| 64 |
+
try:
|
| 65 |
+
child.wait(timeout=10)
|
| 66 |
+
except subprocess.TimeoutExpired:
|
| 67 |
+
with contextlib.suppress(ProcessLookupError):
|
| 68 |
+
os.killpg(child.pid, signal.SIGKILL)
|
| 69 |
+
child.wait(timeout=10)
|
| 70 |
+
finally:
|
| 71 |
+
if stopped:
|
| 72 |
+
subprocess.run(["docker", "start", CONTAINER], check=True, timeout=60)
|
| 73 |
+
restored = subprocess.check_output(
|
| 74 |
+
["docker", "inspect", "--format", "{{.State.Running}}", CONTAINER],
|
| 75 |
+
text=True,
|
| 76 |
+
).strip()
|
| 77 |
+
report["restored_running"] = restored == "true"
|
| 78 |
+
if not report["restored_running"]:
|
| 79 |
+
raise RuntimeError("GLM did not restart")
|
| 80 |
+
# Probe inside the container: host port8080 belongs to another service.
|
| 81 |
+
deadline = time.monotonic() + 120
|
| 82 |
+
while True:
|
| 83 |
+
healthy = False
|
| 84 |
+
try:
|
| 85 |
+
health = subprocess.run(
|
| 86 |
+
[
|
| 87 |
+
"docker",
|
| 88 |
+
"exec",
|
| 89 |
+
CONTAINER,
|
| 90 |
+
"curl",
|
| 91 |
+
"-fsS",
|
| 92 |
+
"--max-time",
|
| 93 |
+
"5",
|
| 94 |
+
"http://localhost:8080/health",
|
| 95 |
+
],
|
| 96 |
+
capture_output=True,
|
| 97 |
+
text=True,
|
| 98 |
+
timeout=10,
|
| 99 |
+
)
|
| 100 |
+
healthy = health.returncode == 0 and json.loads(health.stdout) == {
|
| 101 |
+
"status": "ok"
|
| 102 |
+
}
|
| 103 |
+
except (subprocess.TimeoutExpired, json.JSONDecodeError):
|
| 104 |
+
pass
|
| 105 |
+
if healthy:
|
| 106 |
+
report["restored_healthy"] = True
|
| 107 |
+
break
|
| 108 |
+
if time.monotonic() >= deadline:
|
| 109 |
+
raise RuntimeError(
|
| 110 |
+
"GLM restarted but health did not recover within120s"
|
| 111 |
+
)
|
| 112 |
+
time.sleep(2)
|
| 113 |
+
report["restored_at"] = time.time()
|
| 114 |
+
except BaseException as error:
|
| 115 |
+
report["restoration_error"] = f"{type(error).__name__}: {error}"
|
| 116 |
+
raise
|
| 117 |
+
finally:
|
| 118 |
+
args.record.write_text(json.dumps(report, indent=2) + "\n")
|
| 119 |
+
print(json.dumps(report), flush=True)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
if __name__ == "__main__":
|
| 123 |
+
main()
|
code/scripts/prepare_checkpoint.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Copy an immutable trained checkpoint and replace only its generated adapter card."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
import hashlib
|
| 7 |
+
import json
|
| 8 |
+
import shutil
|
| 9 |
+
import stat
|
| 10 |
+
from datetime import UTC, datetime
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Any
|
| 13 |
+
|
| 14 |
+
from stackcraft.clef import MODEL_ID, MODEL_REVISION
|
| 15 |
+
|
| 16 |
+
CARD_PATH = "adapter/README.md"
|
| 17 |
+
REQUIRED_FILES = {
|
| 18 |
+
"training_config.json",
|
| 19 |
+
"joint_head.safetensors",
|
| 20 |
+
"adapter/adapter_config.json",
|
| 21 |
+
"adapter/adapter_model.safetensors",
|
| 22 |
+
"reference.json",
|
| 23 |
+
CARD_PATH,
|
| 24 |
+
}
|
| 25 |
+
ADAPTER_CARD = f"""---
|
| 26 |
+
base_model: {MODEL_ID}
|
| 27 |
+
library_name: peft
|
| 28 |
+
tags:
|
| 29 |
+
- lora
|
| 30 |
+
- stackcraft
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
# Stackcraft LoRA adapter component
|
| 34 |
+
|
| 35 |
+
This Stackcraft adapter adapts [{MODEL_ID}](https://huggingface.co/{MODEL_ID}),
|
| 36 |
+
released by Cloudflare and pinned at revision `{MODEL_REVISION}`.
|
| 37 |
+
The original model card and license are provided in that upstream repository.
|
| 38 |
+
|
| 39 |
+
This directory is only one component of the Stackcraft checkpoint. Load the
|
| 40 |
+
parent checkpoint's `joint_head.safetensors` and this adapter together through
|
| 41 |
+
Stackcraft's `load_checkpoint` function. The native decision head contains learned
|
| 42 |
+
parameters; loading only the LoRA adapter does not reproduce the trained model.
|
| 43 |
+
Use the exact pinned base and the parent `training_config.json` contract.
|
| 44 |
+
|
| 45 |
+
This component card does not claim improved performance or publication status.
|
| 46 |
+
See the release's full model card and evaluation report for measured results,
|
| 47 |
+
limitations, training provenance and reproduction commands. A prepared candidate
|
| 48 |
+
has not necessarily been selected by validation.
|
| 49 |
+
"""
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _reject_symlink_components(path: Path) -> None:
|
| 53 |
+
for component in (path, *path.parents):
|
| 54 |
+
if component.is_symlink():
|
| 55 |
+
raise ValueError(f"symlinks are not accepted: {component}")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def file_hash(path: Path) -> str:
|
| 59 |
+
digest = hashlib.sha256()
|
| 60 |
+
with path.open("rb") as stream:
|
| 61 |
+
while chunk := stream.read(1_048_576):
|
| 62 |
+
digest.update(chunk)
|
| 63 |
+
return digest.hexdigest()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def inventory(directory: Path) -> dict[str, str]:
|
| 67 |
+
"""Reject symlinks and special files; hash every regular file by relative path."""
|
| 68 |
+
result = {}
|
| 69 |
+
for path in sorted(directory.rglob("*")):
|
| 70 |
+
mode = path.lstat().st_mode
|
| 71 |
+
if stat.S_ISLNK(mode) or not (stat.S_ISREG(mode) or stat.S_ISDIR(mode)):
|
| 72 |
+
raise ValueError(f"checkpoint contains a symlink or special file: {path}")
|
| 73 |
+
if stat.S_ISREG(mode):
|
| 74 |
+
result[path.relative_to(directory).as_posix()] = file_hash(path)
|
| 75 |
+
return result
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def prepare(source: Path, output: Path) -> dict[str, Any]:
|
| 79 |
+
_reject_symlink_components(source.absolute())
|
| 80 |
+
_reject_symlink_components(output.absolute())
|
| 81 |
+
source = source.resolve()
|
| 82 |
+
output = output.resolve()
|
| 83 |
+
provenance = output.parent / f"{output.name}-preparation.json"
|
| 84 |
+
_reject_symlink_components(provenance)
|
| 85 |
+
if not source.is_dir():
|
| 86 |
+
raise ValueError("source must be an existing checkpoint directory")
|
| 87 |
+
if output.is_relative_to(source) or source.is_relative_to(output):
|
| 88 |
+
raise ValueError("source and output directories must be separate, not nested")
|
| 89 |
+
if output.exists() or provenance.exists():
|
| 90 |
+
raise FileExistsError("output checkpoint or preparation record already exists")
|
| 91 |
+
before = inventory(source)
|
| 92 |
+
if not REQUIRED_FILES.issubset(before):
|
| 93 |
+
missing = ", ".join(sorted(REQUIRED_FILES - before.keys()))
|
| 94 |
+
raise ValueError(f"checkpoint is incomplete; missing: {missing}")
|
| 95 |
+
if any((source / name).stat().st_size == 0 for name in REQUIRED_FILES):
|
| 96 |
+
raise ValueError("required checkpoint files must not be empty")
|
| 97 |
+
metadata = json.loads((source / "training_config.json").read_text())
|
| 98 |
+
if (
|
| 99 |
+
not isinstance(metadata, dict)
|
| 100 |
+
or metadata.get("base_model") != MODEL_ID
|
| 101 |
+
or metadata.get("base_revision") != MODEL_REVISION
|
| 102 |
+
or metadata.get("mode") != "lora"
|
| 103 |
+
):
|
| 104 |
+
raise ValueError("checkpoint does not identify the pinned Stackcraft LoRA base")
|
| 105 |
+
for name in ("adapter/adapter_config.json", "reference.json"):
|
| 106 |
+
if not isinstance(json.loads((source / name).read_text()), dict):
|
| 107 |
+
raise ValueError(f"{name} must be a JSON object")
|
| 108 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 109 |
+
output.mkdir(exist_ok=False)
|
| 110 |
+
try:
|
| 111 |
+
for name in before:
|
| 112 |
+
destination = output / name
|
| 113 |
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
| 114 |
+
shutil.copy2(source / name, destination, follow_symlinks=False)
|
| 115 |
+
# Recheck copied entries before writing the card, including a link introduced
|
| 116 |
+
# by a source changing during copy. Never follow a copied symlink to write.
|
| 117 |
+
inventory(output)
|
| 118 |
+
(output / CARD_PATH).unlink()
|
| 119 |
+
with (output / CARD_PATH).open("x") as stream:
|
| 120 |
+
stream.write(ADAPTER_CARD)
|
| 121 |
+
after = inventory(output)
|
| 122 |
+
if before.keys() != after.keys():
|
| 123 |
+
raise RuntimeError("prepared checkpoint changed the file inventory")
|
| 124 |
+
changed = [name for name in before if before[name] != after[name]]
|
| 125 |
+
if any(name != CARD_PATH for name in changed):
|
| 126 |
+
raise RuntimeError("preparation altered a file other than adapter/README.md")
|
| 127 |
+
if inventory(source) != before:
|
| 128 |
+
raise RuntimeError("source checkpoint changed during preparation")
|
| 129 |
+
report = {
|
| 130 |
+
"schema_version": 1,
|
| 131 |
+
"operation": "replace-generated-adapter-documentation-only",
|
| 132 |
+
"created_at": datetime.now(UTC).isoformat(),
|
| 133 |
+
"source": str(source),
|
| 134 |
+
"output": str(output),
|
| 135 |
+
"source_sha256": before,
|
| 136 |
+
"output_sha256": after,
|
| 137 |
+
"changed_files": changed,
|
| 138 |
+
"unchanged_non_documentation": True,
|
| 139 |
+
"source_unchanged": True,
|
| 140 |
+
"base_model": MODEL_ID,
|
| 141 |
+
"base_revision": MODEL_REVISION,
|
| 142 |
+
"preparation_script_sha256": file_hash(Path(__file__)),
|
| 143 |
+
}
|
| 144 |
+
with provenance.open("x") as stream:
|
| 145 |
+
json.dump(report, stream, indent=2, sort_keys=True, allow_nan=False)
|
| 146 |
+
stream.write("\n")
|
| 147 |
+
except BaseException:
|
| 148 |
+
# This call created the output exclusively; leave the raw source untouched.
|
| 149 |
+
shutil.rmtree(output)
|
| 150 |
+
raise
|
| 151 |
+
return report
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def main(argv: list[str] | None = None) -> int:
|
| 155 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 156 |
+
parser.add_argument("--source", type=Path, required=True)
|
| 157 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 158 |
+
args = parser.parse_args(argv)
|
| 159 |
+
try:
|
| 160 |
+
report = prepare(args.source, args.output)
|
| 161 |
+
except (OSError, ValueError, RuntimeError) as error:
|
| 162 |
+
parser.error(str(error))
|
| 163 |
+
print(json.dumps({"output": report["output"], "changed_files": report["changed_files"]}))
|
| 164 |
+
return 0
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
if __name__ == "__main__":
|
| 168 |
+
raise SystemExit(main())
|