henry13522 commited on
Commit
ecaa266
·
verified ·
1 Parent(s): 52df5f7

Publish TaskAnchor source; model transfer in progress

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. README.md +12 -0
  2. code/.gitignore +23 -0
  3. code/LICENSE +201 -0
  4. code/LICENSES/LICENSE_GEMMA.txt +77 -0
  5. code/NOTICE +22 -0
  6. code/README.md +96 -0
  7. code/SOURCE_MANIFEST.json +654 -0
  8. code/assets/pi05_libero/norm_stats.json +84 -0
  9. code/docs/PORTABILITY.md +108 -0
  10. code/docs/PUBLISHING_ZH.md +46 -0
  11. code/docs/REPRODUCING.md +283 -0
  12. code/evidence/environment_pi05.json +19 -0
  13. code/evidence/packaging_checks.json +17 -0
  14. code/evidence/pipeline_restart_10000.json +37 -0
  15. code/evidence/robomem50k_adapter_numerical_parity.json +24 -0
  16. code/evidence/task25_seed_candidates.json +208 -0
  17. code/pi05/LICENSE +201 -0
  18. code/pi05/LICENSE_GEMMA.txt +77 -0
  19. code/pi05/README.md +323 -0
  20. code/pi05/packages/openpi-client/pyproject.toml +22 -0
  21. code/pi05/packages/openpi-client/src/openpi_client/__init__.py +1 -0
  22. code/pi05/packages/openpi-client/src/openpi_client/action_chunk_broker.py +50 -0
  23. code/pi05/packages/openpi-client/src/openpi_client/base_policy.py +12 -0
  24. code/pi05/packages/openpi-client/src/openpi_client/image_tools.py +58 -0
  25. code/pi05/packages/openpi-client/src/openpi_client/image_tools_test.py +37 -0
  26. code/pi05/packages/openpi-client/src/openpi_client/msgpack_numpy.py +57 -0
  27. code/pi05/packages/openpi-client/src/openpi_client/msgpack_numpy_test.py +45 -0
  28. code/pi05/packages/openpi-client/src/openpi_client/runtime/agent.py +17 -0
  29. code/pi05/packages/openpi-client/src/openpi_client/runtime/agents/policy_agent.py +18 -0
  30. code/pi05/packages/openpi-client/src/openpi_client/runtime/environment.py +32 -0
  31. code/pi05/packages/openpi-client/src/openpi_client/runtime/runtime.py +92 -0
  32. code/pi05/packages/openpi-client/src/openpi_client/runtime/subscriber.py +20 -0
  33. code/pi05/packages/openpi-client/src/openpi_client/websocket_client_policy.py +58 -0
  34. code/pi05/pyproject.toml +140 -0
  35. code/pi05/scripts/__init__.py +0 -0
  36. code/pi05/scripts/check_robomem_memory_graph.py +59 -0
  37. code/pi05/scripts/evaluate_robomem_memory.py +271 -0
  38. code/pi05/scripts/extract_robomem_memory.py +169 -0
  39. code/pi05/scripts/robomem_eval_paths.py +20 -0
  40. code/pi05/scripts/robomem_memory_eval_adapter.py +93 -0
  41. code/pi05/scripts/robomem_seed_search.py +57 -0
  42. code/pi05/scripts/run_robomem_eval_task.py +103 -0
  43. code/pi05/scripts/run_robomem_task25_seed.py +36 -0
  44. code/pi05/scripts/serve_robomem_memory.py +16 -0
  45. code/pi05/scripts/test_robomem_discrete_progress.py +58 -0
  46. code/pi05/scripts/test_robomem_memory.py +163 -0
  47. code/pi05/scripts/test_robomem_memory_inference.py +54 -0
  48. code/pi05/scripts/test_robomem_memory_loader.py +82 -0
  49. code/pi05/scripts/train_robomem_memory.py +206 -0
  50. code/pi05/src/openpi/__init__.py +0 -0
README.md CHANGED
@@ -1,3 +1,15 @@
1
  ---
2
  license: apache-2.0
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: apache-2.0
3
+ tags:
4
+ - taskanchor
5
+ - pi05
6
+ - robomemarena
7
  ---
8
+
9
+ # TaskAnchor for RoboMemArena
10
+
11
+ The training, inference, and evaluation source is available in [code/](code/README.md).
12
+
13
+ **Model upload is in progress.** The complete 50k checkpoint is not available yet; do not attempt model loading until `50000/params/` and the final release manifest have been published.
14
+
15
+ Source code is Apache-2.0. The full pretrained-model release will retain applicable upstream Gemma terms. No benchmark results are published here.
code/.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ .venv/
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ .cache/
10
+ .wandb/
11
+ wandb/
12
+ logs/
13
+ /checkpoints/
14
+ /pretrained/
15
+ /data/
16
+ *.egg-info/
17
+ build/
18
+ dist/
19
+ /reports/
20
+ auth.json
21
+ *token*.txt
22
+
23
+ /local/
code/LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
code/LICENSES/LICENSE_GEMMA.txt ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Gemma Terms of Use
2
+
3
+ Last modified: February 21, 2024
4
+
5
+ By using, reproducing, modifying, distributing, performing or displaying any portion or element of Gemma, Model Derivatives including via any Hosted Service, (each as defined below) (collectively, the "Gemma Services") or otherwise accepting the terms of this Agreement, you agree to be bound by this Agreement.
6
+
7
+ Section 1: DEFINITIONS
8
+ 1.1 Definitions
9
+ (a) "Agreement" or "Gemma Terms of Use" means these terms and conditions that govern the use, reproduction, Distribution or modification of the Gemma Services and any terms and conditions incorporated by reference.
10
+
11
+ (b) "Distribution" or "Distribute" means any transmission, publication, or other sharing of Gemma or Model Derivatives to a third party, including by providing or making Gemma or its functionality available as a hosted service via API, web access, or any other electronic or remote means ("Hosted Service").
12
+
13
+ (c) "Gemma" means the set of machine learning language models, trained model weights and parameters identified at ai.google.dev/gemma, regardless of the source that you obtained it from.
14
+
15
+ (d) "Google" means Google LLC.
16
+
17
+ (e) "Model Derivatives" means all (i) modifications to Gemma, (ii) works based on Gemma, or (iii) any other machine learning model which is created by transfer of patterns of the weights, parameters, operations, or Output of Gemma, to that model in order to cause that model to perform similarly to Gemma, including distillation methods that use intermediate data representations or methods based on the generation of synthetic data Outputs by Gemma for training that model. For clarity, Outputs are not deemed Model Derivatives.
18
+
19
+ (f) "Output" means the information content output of Gemma or a Model Derivative that results from operating or otherwise using Gemma or the Model Derivative, including via a Hosted Service.
20
+
21
+ 1.2
22
+ As used in this Agreement, "including" means "including without limitation".
23
+
24
+ Section 2: ELIGIBILITY AND USAGE
25
+ 2.1 Eligibility
26
+ You represent and warrant that you have the legal capacity to enter into this Agreement (including being of sufficient age of consent). If you are accessing or using any of the Gemma Services for or on behalf of a legal entity, (a) you are entering into this Agreement on behalf of yourself and that legal entity, (b) you represent and warrant that you have the authority to act on behalf of and bind that entity to this Agreement and (c) references to "you" or "your" in the remainder of this Agreement refers to both you (as an individual) and that entity.
27
+
28
+ 2.2 Use
29
+ You may use, reproduce, modify, Distribute, perform or display any of the Gemma Services only in accordance with the terms of this Agreement, and must not violate (or encourage or permit anyone else to violate) any term of this Agreement.
30
+
31
+ Section 3: DISTRIBUTION AND RESTRICTIONS
32
+ 3.1 Distribution and Redistribution
33
+ You may reproduce or Distribute copies of Gemma or Model Derivatives if you meet all of the following conditions:
34
+
35
+ You must include the use restrictions referenced in Section 3.2 as an enforceable provision in any agreement (e.g., license agreement, terms of use, etc.) governing the use and/or distribution of Gemma or Model Derivatives and you must provide notice to subsequent users you Distribute to that Gemma or Model Derivatives are subject to the use restrictions in Section 3.2.
36
+ You must provide all third party recipients of Gemma or Model Derivatives a copy of this Agreement.
37
+ You must cause any modified files to carry prominent notices stating that you modified the files.
38
+ All Distributions (other than through a Hosted Service) must be accompanied by a "Notice" text file that contains the following notice: "Gemma is provided under and subject to the Gemma Terms of Use found at ai.google.dev/gemma/terms".
39
+ You may add your own intellectual property statement to your modifications and, except as set forth in this Section, may provide additional or different terms and conditions for use, reproduction, or Distribution of your modifications, or for any such Model Derivatives as a whole, provided your use, reproduction, modification, Distribution, performance, and display of Gemma otherwise complies with the terms and conditions of this Agreement. Any additional or different terms and conditions you impose must not conflict with the terms of this Agreement.
40
+
41
+ 3.2 Use Restrictions
42
+ You must not use any of the Gemma Services:
43
+
44
+ for the restricted uses set forth in the Gemma Prohibited Use Policy at ai.google.dev/gemma/prohibited_use_policy ("Prohibited Use Policy"), which is hereby incorporated by reference into this Agreement; or
45
+ in violation of applicable laws and regulations.
46
+ To the maximum extent permitted by law, Google reserves the right to restrict (remotely or otherwise) usage of any of the Gemma Services that Google reasonably believes are in violation of this Agreement.
47
+
48
+ 3.3 Generated Output
49
+ Google claims no rights in Outputs you generate using Gemma. You and your users are solely responsible for Outputs and their subsequent uses.
50
+
51
+ Section 4: ADDITIONAL PROVISIONS
52
+ 4.1 Updates
53
+ Google may update Gemma from time to time, and you must make reasonable efforts to use the latest version of Gemma.
54
+
55
+ 4.2 Trademarks
56
+ Nothing in this Agreement grants you any rights to use Google's trademarks, trade names, logos or to otherwise suggest endorsement or misrepresent the relationship between you and Google. Google reserves any rights not expressly granted herein.
57
+
58
+ 4.3 DISCLAIMER OF WARRANTY
59
+ UNLESS REQUIRED BY APPLICABLE LAW, THE GEMMA SERVICES, AND OUTPUTS, ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING, REPRODUCING, MODIFYING, PERFORMING, DISPLAYING OR OR DISTRIBUTING ANY OF THE GEMMA SERVICES OR OUTPUTS AND ASSUME ANY AND ALL RISKS ASSOCIATED WITH YOUR USE OR DISTRIBUTION OF ANY OF THE GEMMA SERVICES OR OUTPUTS AND YOUR EXERCISE OF RIGHTS AND PERMISSIONS UNDER THIS AGREEMENT.
60
+
61
+ 4.4 LIMITATION OF LIABILITY
62
+ TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), PRODUCT LIABILITY, CONTRACT, OR OTHERWISE, UNLESS REQUIRED BY APPLICABLE LAW, SHALL GOOGLE OR ITS AFFILIATES BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR LOST PROFITS OF ANY KIND ARISING FROM THIS AGREEMENT OR RELATED TO, ANY OF THE GEMMA SERVICES OR OUTPUTS EVEN IF GOOGLE OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
63
+
64
+ 4.5 Term, Termination, and Survival
65
+ The term of this Agreement will commence upon your acceptance of this Agreement (including acceptance by your use, modification, or Distribution, reproduction, performance or display of any portion or element of the Gemma Services) and will continue in full force and effect until terminated in accordance with the terms of this Agreement. Google may terminate this Agreement if you are in breach of any term of this Agreement. Upon termination of this Agreement, you must delete and cease use and Distribution of all copies of Gemma and Model Derivatives in your possession or control. Sections 1, 2.1, 3.3, 4.2 to 4.9 shall survive the termination of this Agreement.
66
+
67
+ 4.6 Governing Law and Jurisdiction
68
+ This Agreement will be governed by the laws of the State of California without regard to choice of law principles. The UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement. The state and federal courts of Santa Clara County, California shall have exclusive jurisdiction of any dispute arising out of this Agreement.
69
+
70
+ 4.7 Severability
71
+ If any provision of this Agreement is held to be invalid, illegal or unenforceable, the remaining provisions shall be unaffected thereby and remain valid as if such provision had not been set forth herein.
72
+
73
+ 4.8 Entire Agreement
74
+ This Agreement states all the terms agreed between the parties and supersedes all other agreements between the parties as of the date of acceptance relating to its subject matter.
75
+
76
+ 4.9 No Waiver
77
+ Google will not be treated as having waived any rights by not exercising (or delaying the exercise of) any rights under this Agreement.
code/NOTICE ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ TaskAnchor RoboMemArena π0.5 release
2
+
3
+ Source code in this package is distributed under Apache License 2.0, except
4
+ where an individual file or upstream component states otherwise. Retain the
5
+ existing copyright and license notices when redistributing it.
6
+
7
+ This distribution includes modified OpenPI code:
8
+ https://github.com/Physical-Intelligence/openpi
9
+ Modifications add TaskAnchor, full-task history sampling, progress
10
+ conditioning, RoboMemArena training/evaluation, and portable file locations.
11
+ The original model computations in the selected release profile are retained.
12
+
13
+ The accompanying full model includes pretrained π0.5/PaliGemma-derived
14
+ components. The Apache source license does not override terms that apply
15
+ to the underlying pretrained model. The included Gemma terms continue to
16
+ apply to Gemma-derived material.
17
+
18
+ Gemma is provided under and subject to the Gemma Terms of Use found at ai.google.dev/gemma/terms
19
+
20
+ The official RoboMemArena simulator, assets, and training dataset are not
21
+ redistributed in this source package; obtain them from their upstream
22
+ locations and retain their respective terms.
code/README.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TaskAnchor for RoboMemArena (π0.5, 50k)
2
+
3
+ Training and evaluation source for the released **TaskAnchor–π0.5 50,000-step checkpoint** on RoboMemArena. The supported public profile is `robomemarena/pi05`. This package contains the native OpenPI backend, the TaskAnchor adapter, the benchmark interface, normalization assets, and evaluation tools.
4
+
5
+ **Weights:** [henry13522/Taskanchor](https://huggingface.co/henry13522/Taskanchor). The complete source is also included there under `code/`; no separate GitHub repository is required to use this release.
6
+
7
+ ## Architecture and checkpoint contract
8
+
9
+ The frozen SigLIP encoder produces 1152-dimensional tokens. TaskAnchor projects the main-camera tokens to a 512-dimensional latent space, applies the eight-layer, eight-head temporal adapter, and restores the visual residual to 1152 dimensions. The writeback is **`refined = current + offset`**, with no additional scalar gate. Adapter dropout is 0.10; the MLP ratio is 4. The native π0.5 backbone and action decoder remain present in the full checkpoint.
10
+
11
+ The main camera and wrist camera are real inputs; the third native image slot is masked padding. Only the main camera receives the history-conditioned visual residual. Images are resized to 224 × 224. Each cached history frame contains 4 × 4 spatial tokens, bilinearly pooled from the native 16 × 16 SigLIP grid. The recorded protocol retains **a separate permanent frame0 plus up to 191 history frames**, sampled with stride 10. This is the 50k checkpoint's protocol, not the FIFO-only configuration used in some other experiments.
12
+
13
+ The task-state head predicts a continuous scalar. Training targets are `round(subtask_id / num_subtasks, 2)`. At evaluation, the prediction is mapped to the nearest task-specific coordinate (for four stages: 0.00, 0.25, 0.50, 0.75) before conditioning the policy. These are predicted stages, not oracle labels. The model predicts 20 actions and executes 10 before replanning.
14
+
15
+ There are **26,930,049 adapter parameters** and **3,380,363,921 parameters in the complete checkpoint**. The full tensor inventory and payload hashes are provided with the weights.
16
+
17
+ ## Download and run
18
+
19
+ ```bash
20
+ hf download henry13522/Taskanchor --local-dir /path/to/Taskanchor
21
+ cd /path/to/Taskanchor/code
22
+ ```
23
+
24
+ Follow [REPRODUCING.md](docs/REPRODUCING.md) to install the model and simulator environments, pin the official benchmark, and run extraction, training, serving, or evaluation. [PORTABILITY.md](docs/PORTABILITY.md) lists the supported path overrides.
25
+
26
+ After environment and benchmark setup, the evaluation entry point is shown below. The evaluator retains the original trial and seed schedule; a study requiring a different schedule must configure and record its evaluation protocol before collecting results.
27
+
28
+ ```bash
29
+ export TASKANCHOR_TOKENIZER_PATH=/path/to/Taskanchor/tokenizer/paligemma_tokenizer.model
30
+ export TASKANCHOR_PI05_PYTHON=/path/to/Taskanchor/code/pi05/.venv/bin/python
31
+ export TASKANCHOR_ROBOMEM_EVAL_PYTHON=/path/to/simulator-env/bin/python
32
+ export TASKANCHOR_LIBERO_CONFIG=/path/to/robomem_libero_config
33
+
34
+ python -m taskanchor --benchmark robomemarena --model pi05 --stage eval \
35
+ --benchmark-root /path/to/RoboMemArena/evaluation_benchmark \
36
+ --python "$TASKANCHOR_PI05_PYTHON" -- \
37
+ --checkpoint /path/to/Taskanchor/50000 \
38
+ --output /path/to/reports/taskanchor_50k \
39
+ --workers 8 --base-port 8800
40
+ ```
41
+
42
+ Keep `recipe.json` one level above `50000/`. Inference also needs `50000/assets/` and the supplied tokenizer. No training data, feature cache, base-model weight download, or optimizer state is required to load the complete released checkpoint.
43
+
44
+ ## Training settings
45
+
46
+ The recorded run was configured for **55,000 total optimizer steps**, with 5,000 adapter-only steps followed by 50,000 joint steps. The **released 50k snapshot contains 5k adapter-only + 45k joint steps**. The visual encoder stays frozen throughout; the remaining backbone parameters participate after the warm-up. This is one training run with a freeze schedule, not the older four-stage pipeline.
47
+
48
+ | Setting | Recorded value |
49
+ | --- | --- |
50
+ | Initialization | OpenPI `pi05_base` |
51
+ | Global batch / devices | 256 / 8 GPUs, FSDP size 8 |
52
+ | Optimizer | AdamW, β=(0.9, 0.95), gradient clipping 1.0 |
53
+ | Adapter learning rate / weight decay | 2.5e-5 / 0.05 |
54
+ | Backbone learning rate / weight decay | 2.5e-5 / 0 |
55
+ | Image projection learning rate | 1e-5 |
56
+ | Schedule | Adapter constant for the first 5k; cosine decay to 10% over the 50k joint phase |
57
+ | Loss | `flow + 0.01 * (100 * MSE + 1 - Pearson)` |
58
+ | Action prediction / execution | 20 / 10 |
59
+ | History | stride 10, 191 frames + separate frame0, 16 tokens/frame |
60
+ | Dataset | 2,600 full-task trajectories, 2,811,755 frames |
61
+ | Seed / workers | 42 / 8 |
62
+
63
+ The recorded run resumed at 10k after a data-loading change. Its initial recipe and saved checkpoint metadata retain this provenance; they are not claimed to be identical restart states. See `evidence/pipeline_restart_10000.json`. Training from scratch uses the same numerical settings, but exact retraining reproducibility is not asserted.
64
+
65
+ ## Source layout
66
+
67
+ | Path | Responsibility |
68
+ | --- | --- |
69
+ | `taskanchor/pi05/taskanchor.py` | Adapter, task-state head, native π0.5 integration |
70
+ | `taskanchor/pi05/ttt.py` | History-dependent temporary fast-weight computation |
71
+ | `taskanchor/pi05/training.py` | Parameter groups, freeze schedule, optimization |
72
+ | `taskanchor/data/` | Full-task indexing and historical token selection |
73
+ | `taskanchor/__main__.py` | Native extraction/train/serve/evaluation launcher |
74
+ | `taskanchor/paths.py` | Portable asset and data locations |
75
+ | `pi05/src/openpi/` | Native model, transforms, checkpoint and serving utilities |
76
+ | `pi05/scripts/*robomem*` | Feature extraction, training, inference, official evaluation bridge |
77
+ | `pi05/packages/openpi-client/` | Policy client/server protocol support |
78
+ | `tools/` | Benchmark setup and result comparison |
79
+ | `assets/pi05_libero/` | The original normalization statistics |
80
+ | `evidence/` | Configuration provenance and environment records |
81
+
82
+ Some native OpenPI compatibility modules retain historical configuration names because they are imported by the backend. Only the RoboMemArena π0.5 entry points documented here are supported in this release.
83
+
84
+ ## Verification and scope
85
+
86
+ During packaging, the complete checkpoint was restored on CPU: all 176 tensors are finite, and copied parameter/tokenizer/normalization files match the originals by SHA-256. Core adapter, TTT, history, and training implementations are retained without numerical changes. Packaging checks do not substitute for closed-loop benchmark evaluation. This repository does not publish a benchmark score. The packaged checkpoint omits optimizer state and therefore cannot be used as an exact training resume checkpoint.
87
+
88
+ ```bash
89
+ python -m unittest discover -s tests -p test_portability.py
90
+ ```
91
+
92
+ ## License and attribution
93
+
94
+ The code is distributed under [Apache-2.0](LICENSE), retaining upstream notices. The full checkpoint includes pretrained π0.5/PaliGemma-derived components and remains subject to upstream terms, including [Gemma Terms of Use](LICENSES/LICENSE_GEMMA.txt); Apache-2.0 for this source does not replace those weight terms. See [NOTICE](NOTICE).
95
+
96
+ This work builds on [OpenPI](https://github.com/Physical-Intelligence/openpi) and [RoboMemArena](https://github.com/OpenHelix-Team/RoboMemArena). The benchmark, its assets, and the training dataset are obtained separately under their respective terms. Public model weights and source: [henry13522/Taskanchor](https://huggingface.co/henry13522/Taskanchor).
code/SOURCE_MANIFEST.json ADDED
@@ -0,0 +1,654 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [
3
+ {
4
+ "path": ".gitignore",
5
+ "size_bytes": 213,
6
+ "sha256": "281dcbe819391034b619056c0d6ae4fac7b69ba751600f6864d6ae035d54e967"
7
+ },
8
+ {
9
+ "path": "LICENSE",
10
+ "size_bytes": 11356,
11
+ "sha256": "43070e2d4e532684de521b885f385d0841030efa2b1a20bafb76133a5e1379c1"
12
+ },
13
+ {
14
+ "path": "LICENSES/LICENSE_GEMMA.txt",
15
+ "size_bytes": 8431,
16
+ "sha256": "3e2c24001f9ef57bf7ec959a3658fbb49cdad113cdf394c264da9d16f9bdd132"
17
+ },
18
+ {
19
+ "path": "NOTICE",
20
+ "size_bytes": 1114,
21
+ "sha256": "38e5a788a2ca6eb7d7fca20c19cbfdc12cc22cb54df32ecd5d82587dcce6b7c9"
22
+ },
23
+ {
24
+ "path": "README.md",
25
+ "size_bytes": 7644,
26
+ "sha256": "2224078f9713aedf4e746b16f538d3cb1882a53911d4f7d7d774a7a30e0a2ace"
27
+ },
28
+ {
29
+ "path": "assets/pi05_libero/norm_stats.json",
30
+ "size_bytes": 1874,
31
+ "sha256": "0c0d329a3345d2ea2e1348dac0edf4ae175085fac200cca1cab967aae1ae1767"
32
+ },
33
+ {
34
+ "path": "docs/PORTABILITY.md",
35
+ "size_bytes": 5477,
36
+ "sha256": "b9b39576f3d23c1edfd42f28f8d4c00a0e803e771b4856bd6bdf4cc7922765e2"
37
+ },
38
+ {
39
+ "path": "docs/PUBLISHING_ZH.md",
40
+ "size_bytes": 2346,
41
+ "sha256": "72579bf3257fb1ae7b6e72d58e3fb3e31584fb73ad23da93bfc31770f2279eb9"
42
+ },
43
+ {
44
+ "path": "docs/REPRODUCING.md",
45
+ "size_bytes": 12827,
46
+ "sha256": "7630f8f14f2d232ff49509fe54bca7dc33d20ed1fab3bcc22b7d0fca556692d2"
47
+ },
48
+ {
49
+ "path": "evidence/environment_pi05.json",
50
+ "size_bytes": 393,
51
+ "sha256": "463b318c2fcc9b4374bdf177e55211a134fad79be7fa4f8818d69237208a6a47"
52
+ },
53
+ {
54
+ "path": "evidence/packaging_checks.json",
55
+ "size_bytes": 465,
56
+ "sha256": "cdd294d1e4c23b38f42e37e2f15ca0f43d7600b7e6e76d4feebde61fa0f11de0"
57
+ },
58
+ {
59
+ "path": "evidence/pipeline_restart_10000.json",
60
+ "size_bytes": 1027,
61
+ "sha256": "55576107d7239b76ad608ea21a43307bdb72886b951ec2a214e006218a554984"
62
+ },
63
+ {
64
+ "path": "evidence/robomem50k_adapter_numerical_parity.json",
65
+ "size_bytes": 628,
66
+ "sha256": "d0067e72d6c768996c64d4ed66eb1d9351cdf94a232ce3985b8314fb5c76c14a"
67
+ },
68
+ {
69
+ "path": "evidence/task25_seed_candidates.json",
70
+ "size_bytes": 1930,
71
+ "sha256": "2c2c859aa60c0c58e04e9f8c7e7ec54cbb3a23a34d463d8432ab10080e7f4444"
72
+ },
73
+ {
74
+ "path": "pi05/LICENSE",
75
+ "size_bytes": 11356,
76
+ "sha256": "43070e2d4e532684de521b885f385d0841030efa2b1a20bafb76133a5e1379c1"
77
+ },
78
+ {
79
+ "path": "pi05/LICENSE_GEMMA.txt",
80
+ "size_bytes": 8431,
81
+ "sha256": "3e2c24001f9ef57bf7ec959a3658fbb49cdad113cdf394c264da9d16f9bdd132"
82
+ },
83
+ {
84
+ "path": "pi05/README.md",
85
+ "size_bytes": 23254,
86
+ "sha256": "567fc57766bd736664f50126cab8145af4071ca16cc5e238f2b11e369169c3b5"
87
+ },
88
+ {
89
+ "path": "pi05/packages/openpi-client/pyproject.toml",
90
+ "size_bytes": 388,
91
+ "sha256": "cacd55394787338bef9d6e7c2e4ecaefd0bc90b3362ac48fe20d9a7f201147d6"
92
+ },
93
+ {
94
+ "path": "pi05/packages/openpi-client/src/openpi_client/__init__.py",
95
+ "size_bytes": 22,
96
+ "sha256": "91447944015cec709e8aa7655f7e9d64e1e4508e7023a57fe3746911c0fc6fed"
97
+ },
98
+ {
99
+ "path": "pi05/packages/openpi-client/src/openpi_client/action_chunk_broker.py",
100
+ "size_bytes": 1404,
101
+ "sha256": "019b9d69cb26cc681331a2f95dbe60a2cc7b02d6d309c5d15b6457ab39798234"
102
+ },
103
+ {
104
+ "path": "pi05/packages/openpi-client/src/openpi_client/base_policy.py",
105
+ "size_bytes": 271,
106
+ "sha256": "88e98e0f06293f3c4767db675061664c4bba12f87242ba1e64377d1b8de8069e"
107
+ },
108
+ {
109
+ "path": "pi05/packages/openpi-client/src/openpi_client/image_tools.py",
110
+ "size_bytes": 2426,
111
+ "sha256": "d48b4bd7f44e79fe6db8a8e07c9161144fa250be686e1245014a8b47e6171977"
112
+ },
113
+ {
114
+ "path": "pi05/packages/openpi-client/src/openpi_client/image_tools_test.py",
115
+ "size_bytes": 1411,
116
+ "sha256": "4df4b0ba0b95ba5e47160039c3e837a64d50c802b419a588211dffbaa732589c"
117
+ },
118
+ {
119
+ "path": "pi05/packages/openpi-client/src/openpi_client/msgpack_numpy.py",
120
+ "size_bytes": 1944,
121
+ "sha256": "c04568948fcee52b691e3be4b6cffb759f7e79ad67530fcd5d23095a0d13c057"
122
+ },
123
+ {
124
+ "path": "pi05/packages/openpi-client/src/openpi_client/msgpack_numpy_test.py",
125
+ "size_bytes": 1643,
126
+ "sha256": "2c6dd236720dae716d7bdf842b7d506a165e9563bf9d4376c95289269adcb973"
127
+ },
128
+ {
129
+ "path": "pi05/packages/openpi-client/src/openpi_client/runtime/agent.py",
130
+ "size_bytes": 465,
131
+ "sha256": "b559885afd9645a91b9b6ee12a28513295474f987d11b16610fc0479dbe6de5b"
132
+ },
133
+ {
134
+ "path": "pi05/packages/openpi-client/src/openpi_client/runtime/agents/policy_agent.py",
135
+ "size_bytes": 509,
136
+ "sha256": "c4d63952d1954bd4b72a3122223df36794812fea74cf1c7023433b5a90473c1c"
137
+ },
138
+ {
139
+ "path": "pi05/packages/openpi-client/src/openpi_client/runtime/environment.py",
140
+ "size_bytes": 1044,
141
+ "sha256": "1c12e5054b3401dec5f1f8b71eef0f625cb60e2b50784dcf19afa32c77107a7e"
142
+ },
143
+ {
144
+ "path": "pi05/packages/openpi-client/src/openpi_client/runtime/runtime.py",
145
+ "size_bytes": 3049,
146
+ "sha256": "3daa242a9ce61ffb210789ef3ba6b1e3cade9de757fcd36af7b174c530345ea8"
147
+ },
148
+ {
149
+ "path": "pi05/packages/openpi-client/src/openpi_client/runtime/subscriber.py",
150
+ "size_bytes": 500,
151
+ "sha256": "d269a2af8ec4056beb310e5c3c97d4468af01549c7ef80dd905652d8ac64f85b"
152
+ },
153
+ {
154
+ "path": "pi05/packages/openpi-client/src/openpi_client/websocket_client_policy.py",
155
+ "size_bytes": 2131,
156
+ "sha256": "36557cb0b91ccf31cd4fb4b508306850d76ed0feb4028dac5182d0f5a5d88005"
157
+ },
158
+ {
159
+ "path": "pi05/pyproject.toml",
160
+ "size_bytes": 3110,
161
+ "sha256": "e1f457bd81622a47a4c4de17b92815977ac5fcd40116bec92398eb400bf76483"
162
+ },
163
+ {
164
+ "path": "pi05/scripts/__init__.py",
165
+ "size_bytes": 0,
166
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
167
+ },
168
+ {
169
+ "path": "pi05/scripts/check_robomem_memory_graph.py",
170
+ "size_bytes": 2521,
171
+ "sha256": "1100021fb2ae25cc85f3e1c0da2cd95fa78086da0a673fd606be8652adcab752"
172
+ },
173
+ {
174
+ "path": "pi05/scripts/evaluate_robomem_memory.py",
175
+ "size_bytes": 13126,
176
+ "sha256": "47fe434454e7db74d834016621f6ae61d12097ad270dd7fb155567604b8ab53d"
177
+ },
178
+ {
179
+ "path": "pi05/scripts/extract_robomem_memory.py",
180
+ "size_bytes": 7229,
181
+ "sha256": "4bf8a66484188ee321ee75b605e80cbfc55b9785c3332cf36dce15c45c186342"
182
+ },
183
+ {
184
+ "path": "pi05/scripts/robomem_eval_paths.py",
185
+ "size_bytes": 843,
186
+ "sha256": "f82cda8a7fecbe3def79e5c013cab15083d19e53634b80b41f984534c25d487e"
187
+ },
188
+ {
189
+ "path": "pi05/scripts/robomem_memory_eval_adapter.py",
190
+ "size_bytes": 4093,
191
+ "sha256": "771cc4492686bce2d81d6f320301df3dcd619e3e9eba8f61d70800f9274e84ce"
192
+ },
193
+ {
194
+ "path": "pi05/scripts/robomem_seed_search.py",
195
+ "size_bytes": 2742,
196
+ "sha256": "c48b3bfc0b30fc1ed7eb986925c42f4821fe52a856d9102a3181fd3e9dd6b2be"
197
+ },
198
+ {
199
+ "path": "pi05/scripts/run_robomem_eval_task.py",
200
+ "size_bytes": 4547,
201
+ "sha256": "18c698ec0bda3510816390778d1412a96a90f7d2bf098ee8224033a9ea1708f3"
202
+ },
203
+ {
204
+ "path": "pi05/scripts/run_robomem_task25_seed.py",
205
+ "size_bytes": 1225,
206
+ "sha256": "ca32a0bdb52376e0937bced4f87f41b87f18e5fb3f75cd6c975bc71dde166321"
207
+ },
208
+ {
209
+ "path": "pi05/scripts/serve_robomem_memory.py",
210
+ "size_bytes": 702,
211
+ "sha256": "e560e83a556a16b620c854140fb2a736ca1c003d8031a531801a71ef507af86e"
212
+ },
213
+ {
214
+ "path": "pi05/scripts/test_robomem_discrete_progress.py",
215
+ "size_bytes": 2915,
216
+ "sha256": "1f9e66c70775fac7933b7de3bc15f33aa434532fdad79165269f311c077b29a5"
217
+ },
218
+ {
219
+ "path": "pi05/scripts/test_robomem_memory.py",
220
+ "size_bytes": 8216,
221
+ "sha256": "e1b71aee89412650a99b4489961450c36d188ce654db182ce2605befcad8c893"
222
+ },
223
+ {
224
+ "path": "pi05/scripts/test_robomem_memory_inference.py",
225
+ "size_bytes": 2430,
226
+ "sha256": "d26aadc408ef8300a879c86cf1fe3fc32df7de3d56f1ff19c3ee7803a9d2ca57"
227
+ },
228
+ {
229
+ "path": "pi05/scripts/test_robomem_memory_loader.py",
230
+ "size_bytes": 3000,
231
+ "sha256": "80f421f909c0d3373171dccd013c46fbf8b2b9a67597b81a6198e6163cdd2ba7"
232
+ },
233
+ {
234
+ "path": "pi05/scripts/train_robomem_memory.py",
235
+ "size_bytes": 8168,
236
+ "sha256": "6e7d4a8ed7b6686437ed85101a0b90518fe555fd5dadb2d8d8d681d5beb25fb0"
237
+ },
238
+ {
239
+ "path": "pi05/src/openpi/__init__.py",
240
+ "size_bytes": 0,
241
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
242
+ },
243
+ {
244
+ "path": "pi05/src/openpi/conftest.py",
245
+ "size_bytes": 339,
246
+ "sha256": "626672e385fb276396c119e2220421065c58f8ec602401e8bf2ae509f9fffaf3"
247
+ },
248
+ {
249
+ "path": "pi05/src/openpi/models/__init__.py",
250
+ "size_bytes": 0,
251
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
252
+ },
253
+ {
254
+ "path": "pi05/src/openpi/models/gemma.py",
255
+ "size_bytes": 17146,
256
+ "sha256": "7e42ada4ae7e9995f0ef3e33a0c224758323c2015e2d9d72ec15726a8a001d2a"
257
+ },
258
+ {
259
+ "path": "pi05/src/openpi/models/gemma_fast.py",
260
+ "size_bytes": 15643,
261
+ "sha256": "563c6e1ffd8afa31c406bcb8894a605e87df88f6c075d398d97a57af6bc3aefa"
262
+ },
263
+ {
264
+ "path": "pi05/src/openpi/models/lora.py",
265
+ "size_bytes": 5342,
266
+ "sha256": "ced28716950c34d84a1a6d0b03b1d9550000732721df884818080b6e78cb3289"
267
+ },
268
+ {
269
+ "path": "pi05/src/openpi/models/lora_test.py",
270
+ "size_bytes": 3138,
271
+ "sha256": "a0f10525de7773310402b3ffa636e88ffff7035f3179b140aacc6809c122f085"
272
+ },
273
+ {
274
+ "path": "pi05/src/openpi/models/model.py",
275
+ "size_bytes": 15120,
276
+ "sha256": "386dd172e1f561ba31d54a0ec3eb92921a51f58e4ee520b12789c9b8e3a62d07"
277
+ },
278
+ {
279
+ "path": "pi05/src/openpi/models/model_test.py",
280
+ "size_bytes": 2966,
281
+ "sha256": "f8e71f195045d4e3ef1ddfafb2337fa52e5aa2f89bce1fde62f5cbfb67121f21"
282
+ },
283
+ {
284
+ "path": "pi05/src/openpi/models/pi0.py",
285
+ "size_bytes": 14318,
286
+ "sha256": "00df716074704c22ab4fccd88146bf73ed6ef7edcb4c9e6a9f92486ad030c89c"
287
+ },
288
+ {
289
+ "path": "pi05/src/openpi/models/pi0_config.py",
290
+ "size_bytes": 4435,
291
+ "sha256": "d321858c30b398126035ea2b6da09e1c422eeeebb5ebf8df631daf9dc77647fa"
292
+ },
293
+ {
294
+ "path": "pi05/src/openpi/models/pi0_fast.py",
295
+ "size_bytes": 13209,
296
+ "sha256": "627332a555046f2744ef144c45f8bd3361a9820cf68854a4528d0fdbba7e9aa3"
297
+ },
298
+ {
299
+ "path": "pi05/src/openpi/models/pi0_test.py",
300
+ "size_bytes": 1631,
301
+ "sha256": "5082873014a7fc45fb8414f517909e1c24cdafea980a1fb612e0aea6058ed0fe"
302
+ },
303
+ {
304
+ "path": "pi05/src/openpi/models/robomem_memory.py",
305
+ "size_bytes": 189,
306
+ "sha256": "cff45c0afe162f062ca54dfb52078483e64cb04db268746f5ea570e5268e1ca5"
307
+ },
308
+ {
309
+ "path": "pi05/src/openpi/models/siglip.py",
310
+ "size_bytes": 12471,
311
+ "sha256": "f129a7e1d5b8bf101d73f27ccf9b0005167c3e208ac934ffcdc36a886662d36f"
312
+ },
313
+ {
314
+ "path": "pi05/src/openpi/models/tokenizer.py",
315
+ "size_bytes": 17558,
316
+ "sha256": "0d604dfde16c3d00fe056ff7559efd4e5d95a042f4fe09e1c2ec7e77dfc47b0a"
317
+ },
318
+ {
319
+ "path": "pi05/src/openpi/models/tokenizer_test.py",
320
+ "size_bytes": 807,
321
+ "sha256": "5abd179ce883420e39d4d18b33d0123290af752da243003aa48a3bc06dbb9517"
322
+ },
323
+ {
324
+ "path": "pi05/src/openpi/models/utils/fsq_tokenizer.py",
325
+ "size_bytes": 15928,
326
+ "sha256": "88866638ee4775f46a222561a957bd35e20600fe38c5888b587016ad77b1065f"
327
+ },
328
+ {
329
+ "path": "pi05/src/openpi/models/vit.py",
330
+ "size_bytes": 10266,
331
+ "sha256": "d285d057636c3cba2d1fd3b37693b18075ad4b9c3b47bbde807c34c6c376ac93"
332
+ },
333
+ {
334
+ "path": "pi05/src/openpi/models_pytorch/gemma_pytorch.py",
335
+ "size_bytes": 13480,
336
+ "sha256": "225092e06509da0ae37bb9dca55c00d2804255bb1496f7c8700304b4c52a95eb"
337
+ },
338
+ {
339
+ "path": "pi05/src/openpi/models_pytorch/pi0_pytorch.py",
340
+ "size_bytes": 22558,
341
+ "sha256": "97dd290d699bb63719627e3933a414500b35ded4ef24c88faa0685f0acdc9e75"
342
+ },
343
+ {
344
+ "path": "pi05/src/openpi/models_pytorch/preprocessing_pytorch.py",
345
+ "size_bytes": 8098,
346
+ "sha256": "f8fb26173ecfe83b7f7832f8d6ca4104d377af08ebb7352dc68ad51b63a0eb43"
347
+ },
348
+ {
349
+ "path": "pi05/src/openpi/models_pytorch/transformers_replace/models/gemma/configuration_gemma.py",
350
+ "size_bytes": 8958,
351
+ "sha256": "26fd0d0f52730ab32b6f337eb73ae9a26cb71ae0915bc49eced1d0e2ed8d0ec6"
352
+ },
353
+ {
354
+ "path": "pi05/src/openpi/models_pytorch/transformers_replace/models/gemma/modeling_gemma.py",
355
+ "size_bytes": 36392,
356
+ "sha256": "17eeb54e277939e58cd6f8a92719a275e36baf0c9f463b2754227d42f87aedde"
357
+ },
358
+ {
359
+ "path": "pi05/src/openpi/models_pytorch/transformers_replace/models/paligemma/modeling_paligemma.py",
360
+ "size_bytes": 27688,
361
+ "sha256": "c945d330b829264f927f3ee5339f977238631772de07a28e855e265bb85fd53c"
362
+ },
363
+ {
364
+ "path": "pi05/src/openpi/models_pytorch/transformers_replace/models/siglip/check.py",
365
+ "size_bytes": 133,
366
+ "sha256": "466e8eb7887ac5ccc98118a3169298d6ab65a284d834bae74751e3854dbc4245"
367
+ },
368
+ {
369
+ "path": "pi05/src/openpi/models_pytorch/transformers_replace/models/siglip/modeling_siglip.py",
370
+ "size_bytes": 50231,
371
+ "sha256": "ef2e99500f263fdd78db9d4d9a255e29d844eb92f53c25ad640f8056700ef84b"
372
+ },
373
+ {
374
+ "path": "pi05/src/openpi/policies/aloha_policy.py",
375
+ "size_bytes": 7543,
376
+ "sha256": "50d7ad972d80142ecb0f03b0780dacfd3af212946f4c9aac31a204b02db4543d"
377
+ },
378
+ {
379
+ "path": "pi05/src/openpi/policies/droid_policy.py",
380
+ "size_bytes": 3186,
381
+ "sha256": "7d0893555964d485e231f70084125084a99495705ddd74b29461229d524fff26"
382
+ },
383
+ {
384
+ "path": "pi05/src/openpi/policies/libero_policy.py",
385
+ "size_bytes": 3461,
386
+ "sha256": "8606b38a3efeb8b97c1af18b33a873eb3c63f5359a661c9b4319cdb87fb66276"
387
+ },
388
+ {
389
+ "path": "pi05/src/openpi/policies/rmbench_memory_inference.py",
390
+ "size_bytes": 4643,
391
+ "sha256": "9335930d0da0efe1c64b6c06a58820c7f7eb522b21933a2cc4895508b5f65ff0"
392
+ },
393
+ {
394
+ "path": "pi05/src/openpi/policies/robomem_memory_inference.py",
395
+ "size_bytes": 12111,
396
+ "sha256": "3ff7d6ac0a2e9a54b011ecf557c4d2e4810c2ab144781b6ce95b1093103a02de"
397
+ },
398
+ {
399
+ "path": "pi05/src/openpi/policies/robomem_progress.py",
400
+ "size_bytes": 1371,
401
+ "sha256": "43b8afbdb6f96400f65b8bcb74e4b077856ede5e874d1513d6b16ef674ff98c3"
402
+ },
403
+ {
404
+ "path": "pi05/src/openpi/py.typed",
405
+ "size_bytes": 0,
406
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
407
+ },
408
+ {
409
+ "path": "pi05/src/openpi/rmbench_cover_blocks_10_full_prompts.py",
410
+ "size_bytes": 4524,
411
+ "sha256": "65e27c9cd1d89238e26005e10525cca8af2faec5837c0221dc3a9b02f2d8304f"
412
+ },
413
+ {
414
+ "path": "pi05/src/openpi/rmbench_eval_prompts.py",
415
+ "size_bytes": 1406,
416
+ "sha256": "c3df981e542ecb1215fb10ee4a56aa3fec7ea3af6ec998e096e0e29c5580d4f5"
417
+ },
418
+ {
419
+ "path": "pi05/src/openpi/rmbench_m1_5tasks_10_full_prompts.py",
420
+ "size_bytes": 14008,
421
+ "sha256": "488b3a0b0854a71d4a192bd9c02718ea7df307eb6d335276244178820cc0ae4c"
422
+ },
423
+ {
424
+ "path": "pi05/src/openpi/robomem_task6_100_instruction.py",
425
+ "size_bytes": 21358,
426
+ "sha256": "e6ae6b788d90b206187550b61d115d10a076cd4dde02afe2c2e5e5cc6dc95a97"
427
+ },
428
+ {
429
+ "path": "pi05/src/openpi/robomemarena_26tasks_10_full_prompts.py",
430
+ "size_bytes": 40410,
431
+ "sha256": "8f8c71573de9701321f62b9cbb334829b97199e6b01831f6d07fb990dcf92906"
432
+ },
433
+ {
434
+ "path": "pi05/src/openpi/robomemarena_26tasks_subtask_prompts.py",
435
+ "size_bytes": 5087,
436
+ "sha256": "8e4e2b3e7d4e88d927c5d4147ff41f91db423231072e302b4bc02fd77a45035e"
437
+ },
438
+ {
439
+ "path": "pi05/src/openpi/serving/websocket_policy_server.py",
440
+ "size_bytes": 3480,
441
+ "sha256": "001f06365933bd11386298905c9125ac28db9a8985f35d10ae090d174cc8e1ae"
442
+ },
443
+ {
444
+ "path": "pi05/src/openpi/shared/__init__.py",
445
+ "size_bytes": 0,
446
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
447
+ },
448
+ {
449
+ "path": "pi05/src/openpi/shared/array_typing.py",
450
+ "size_bytes": 3402,
451
+ "sha256": "6524945566eab3eb97b75a6280ba4724617b4d028c55f05172b283c3de68a026"
452
+ },
453
+ {
454
+ "path": "pi05/src/openpi/shared/download.py",
455
+ "size_bytes": 8862,
456
+ "sha256": "a12054a6ddaab4efa833c4ff49a1aba00c63759e4e88d6be21c1e0b3307fd9b2"
457
+ },
458
+ {
459
+ "path": "pi05/src/openpi/shared/download_test.py",
460
+ "size_bytes": 1490,
461
+ "sha256": "234ee70ab2501a1421f6c44cbd83c1e16747597023319ae1446bed9d44f17072"
462
+ },
463
+ {
464
+ "path": "pi05/src/openpi/shared/image_tools.py",
465
+ "size_bytes": 4665,
466
+ "sha256": "0f8fcb355288119e56acb56d550c1cc1f3a9edddceb0dc5b319f955943ef7767"
467
+ },
468
+ {
469
+ "path": "pi05/src/openpi/shared/image_tools_test.py",
470
+ "size_bytes": 1418,
471
+ "sha256": "842374579dc9f4c2907da5e8bb555efe5a3f4902b659720c8e90115863dd59fe"
472
+ },
473
+ {
474
+ "path": "pi05/src/openpi/shared/nnx_utils.py",
475
+ "size_bytes": 2844,
476
+ "sha256": "4da7111ba3011c4d1c381c9ed17257771df6a50ed89182647c3fb6f79208005e"
477
+ },
478
+ {
479
+ "path": "pi05/src/openpi/shared/normalize.py",
480
+ "size_bytes": 5529,
481
+ "sha256": "6c2cea4946fb07e51801530400d2b2fd94730e195cd290d6b6960114eca9739d"
482
+ },
483
+ {
484
+ "path": "pi05/src/openpi/shared/normalize_test.py",
485
+ "size_bytes": 1521,
486
+ "sha256": "fda27a21cf37f337b12bcab71229407c2a349c5965c33f278bdc468ed03f58af"
487
+ },
488
+ {
489
+ "path": "pi05/src/openpi/training/checkpoints.py",
490
+ "size_bytes": 6696,
491
+ "sha256": "ea420c6a816933ef96966a092801f388997800530aef7a29f7b42807cc3e5103"
492
+ },
493
+ {
494
+ "path": "pi05/src/openpi/training/config.py",
495
+ "size_bytes": 35031,
496
+ "sha256": "e1cde1a9906848460cd92a3c82155a83047610d983cb933e8f6f15bb93c1ac22"
497
+ },
498
+ {
499
+ "path": "pi05/src/openpi/training/data_loader.py",
500
+ "size_bytes": 23482,
501
+ "sha256": "7ccd627788901c9c6d1db222404f22e6cec76b54dab5a29586c21472262d945a"
502
+ },
503
+ {
504
+ "path": "pi05/src/openpi/training/data_loader_test.py",
505
+ "size_bytes": 2487,
506
+ "sha256": "a0cf1bc293e8b0eb9bc5516bc05d30d9940da06c94fcfa1addca7267f8358815"
507
+ },
508
+ {
509
+ "path": "pi05/src/openpi/training/droid_rlds_dataset.py",
510
+ "size_bytes": 10886,
511
+ "sha256": "1d3a057a040240218234d7dbc6680ee4fe8256f2058531a0f9fefd3f7ee995a4"
512
+ },
513
+ {
514
+ "path": "pi05/src/openpi/training/memory_training.py",
515
+ "size_bytes": 208,
516
+ "sha256": "bc17fe9f002a94ab38a09f46caeff75b5baeef2a4a6edd20b3381b611105b14b"
517
+ },
518
+ {
519
+ "path": "pi05/src/openpi/training/misc/polaris_config.py",
520
+ "size_bytes": 9624,
521
+ "sha256": "c2ab92efb5e0d675118ad812113fd1c3cb3a62cdab5d79066014266e82df12d8"
522
+ },
523
+ {
524
+ "path": "pi05/src/openpi/training/misc/roboarena_config.py",
525
+ "size_bytes": 4804,
526
+ "sha256": "914acdd74ccf90e6779c13a5093140595c7e4f687e9e0b167439446a81d74c01"
527
+ },
528
+ {
529
+ "path": "pi05/src/openpi/training/optimizer.py",
530
+ "size_bytes": 3202,
531
+ "sha256": "cfb5d6f6aec61fbe961287b4015c652260628a0ed4682a0dbdc271b8e65431fa"
532
+ },
533
+ {
534
+ "path": "pi05/src/openpi/training/rmbench_memory_data.py",
535
+ "size_bytes": 10093,
536
+ "sha256": "14923395d2d9395e98f3b5571b25cfc1a2d7ddb7f792521bb5a6620952403387"
537
+ },
538
+ {
539
+ "path": "pi05/src/openpi/training/robomem_memory_data.py",
540
+ "size_bytes": 7135,
541
+ "sha256": "6fd26b38cfb373630611e1cf1c54ef4af2b4992321c225ca1d73cb2d5830c0ec"
542
+ },
543
+ {
544
+ "path": "pi05/src/openpi/training/robomem_memory_loader.py",
545
+ "size_bytes": 3911,
546
+ "sha256": "84528f678e54805b2f3819ec596595086f115b1971ea893cb7c1ae3279ed41fc"
547
+ },
548
+ {
549
+ "path": "pi05/src/openpi/training/sharding.py",
550
+ "size_bytes": 4099,
551
+ "sha256": "c67c4112a126ab097571c8a882fd1ee3f106b15b6e608e7205ac7f29b44ca985"
552
+ },
553
+ {
554
+ "path": "pi05/src/openpi/training/utils.py",
555
+ "size_bytes": 1217,
556
+ "sha256": "b684e674343f86ef5e976f279a586d3fe2f885e901f396ac899ed136abc7eca6"
557
+ },
558
+ {
559
+ "path": "pi05/src/openpi/training/weight_loaders.py",
560
+ "size_bytes": 3738,
561
+ "sha256": "a54ac3222f81f8c6233241bc16b6f369c9416cdaa83f7058f96f7ddcfde1a3b4"
562
+ },
563
+ {
564
+ "path": "pi05/src/openpi/transforms.py",
565
+ "size_bytes": 46152,
566
+ "sha256": "ed9673a1f140f188cb6a0657f6938ce69ea6783d9f42c3ed3e6b6b46c1fc7339"
567
+ },
568
+ {
569
+ "path": "pi05/uv.lock",
570
+ "size_bytes": 795910,
571
+ "sha256": "793488b5a55bb87200db90a61fd0af51922b686d94e1da4f4c587ab119b37d74"
572
+ },
573
+ {
574
+ "path": "pyproject.toml",
575
+ "size_bytes": 365,
576
+ "sha256": "e21069c1bb24a3846579748d05fdbef49dda21c2df1b0d4af0fb028be475ce41"
577
+ },
578
+ {
579
+ "path": "taskanchor/__init__.py",
580
+ "size_bytes": 82,
581
+ "sha256": "efe209ecb3e663212784c22fb1891bf52261c241e069d6d90b5a85f2557a6956"
582
+ },
583
+ {
584
+ "path": "taskanchor/__main__.py",
585
+ "size_bytes": 4924,
586
+ "sha256": "efc77b76efe98c00c291de317a6e9822d58f908744b75df1a569a4aa2a08d690"
587
+ },
588
+ {
589
+ "path": "taskanchor/configs/__init__.py",
590
+ "size_bytes": 61,
591
+ "sha256": "a9ecb5d1640dd38980236cdb84878df8c498c3c1d05b291faea19d9e77428f5f"
592
+ },
593
+ {
594
+ "path": "taskanchor/configs/robomemarena_pi05.json",
595
+ "size_bytes": 634,
596
+ "sha256": "fd7d0e623817c60446b85250dd5fc3c6bb36fbc20bf057a30ddef2277d451099"
597
+ },
598
+ {
599
+ "path": "taskanchor/data/__init__.py",
600
+ "size_bytes": 58,
601
+ "sha256": "f40c70863322679971b4c9eaa17158d1ddee26fe194d3f0e9864906a494f5cb0"
602
+ },
603
+ {
604
+ "path": "taskanchor/data/history.py",
605
+ "size_bytes": 3157,
606
+ "sha256": "f8d4c155eaf1d636c7596795b33f28d9fad606f7b7d4b6c421e595ac4a274511"
607
+ },
608
+ {
609
+ "path": "taskanchor/data/robomemarena_metadata.py",
610
+ "size_bytes": 6932,
611
+ "sha256": "d8026e5cd225a0c22edc05004d921298ed8ce5d88f94c4c0110c6ba76e2dde48"
612
+ },
613
+ {
614
+ "path": "taskanchor/paths.py",
615
+ "size_bytes": 1190,
616
+ "sha256": "b829fba7bef5fa450a2621c6d4758e9dac951af5482ae3d4b34552cc9c6b2760"
617
+ },
618
+ {
619
+ "path": "taskanchor/pi05/__init__.py",
620
+ "size_bytes": 58,
621
+ "sha256": "4fe691e5c85a023c697680cb0f7faa1fdf4e5e21feb3bbc5c061403836d075af"
622
+ },
623
+ {
624
+ "path": "taskanchor/pi05/taskanchor.py",
625
+ "size_bytes": 13666,
626
+ "sha256": "6543c6114add32135786c20db971246b18e66a183f1bfe134e2bee7b5cf3992e"
627
+ },
628
+ {
629
+ "path": "taskanchor/pi05/training.py",
630
+ "size_bytes": 9393,
631
+ "sha256": "668be3f93ed82290d66319eb67ba77e85a34119b20685a36d4a20abced2f84f7"
632
+ },
633
+ {
634
+ "path": "taskanchor/pi05/ttt.py",
635
+ "size_bytes": 6106,
636
+ "sha256": "7cc97cd49dce55cee1bd60a95e07f095b47bad5d28f19d7d823ae1864f5159d0"
637
+ },
638
+ {
639
+ "path": "tests/test_portability.py",
640
+ "size_bytes": 3601,
641
+ "sha256": "236c1119e7d4868c26a04fcf8b650723e61bd191e4925b87e6e9ec65283bc685"
642
+ },
643
+ {
644
+ "path": "tools/compare_robomem_reports.py",
645
+ "size_bytes": 3875,
646
+ "sha256": "9398c81f25f9f7ab3eb3f50d792c28ebc0ab136fd8fc841969d5cc309d70945b"
647
+ },
648
+ {
649
+ "path": "tools/prepare_benchmark.py",
650
+ "size_bytes": 2786,
651
+ "sha256": "5eb7efdbb55d851c49b49d4c336d4675d20c894fdd93d70d637d60a8300049d9"
652
+ }
653
+ ]
654
+ }
code/assets/pi05_libero/norm_stats.json ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "norm_stats": {
3
+ "state": {
4
+ "mean": [
5
+ 0.048380594700574875,
6
+ 0.11262303590774536,
7
+ 0.6594910025596619,
8
+ 3.370551347732544,
9
+ -0.30113735795021057,
10
+ -0.24474778771400452,
11
+ 0.02991946041584015,
12
+ -0.029522070661187172
13
+ ],
14
+ "std": [
15
+ 0.11915058642625809,
16
+ 0.2559613585472107,
17
+ 0.1345514953136444,
18
+ 0.6972067952156067,
19
+ 0.75095134973526,
20
+ 0.7611529231071472,
21
+ 0.011234981939196587,
22
+ 0.01164152380079031
23
+ ],
24
+ "q01": [
25
+ -0.2685799194574356,
26
+ -0.33133132414221766,
27
+ 0.4330671876549721,
28
+ 2.4110534167289734,
29
+ -2.4456029770374297,
30
+ -2.43879587931633,
31
+ 0.0009645143877714873,
32
+ -0.04009675695095211
33
+ ],
34
+ "q99": [
35
+ 0.23303380120396622,
36
+ 0.5012647942483425,
37
+ 1.0027180651426315,
38
+ 4.715920171499253,
39
+ 0.03176737618446346,
40
+ 1.2033550313949584,
41
+ 0.04003552807592787,
42
+ 0.00003360225446522036
43
+ ]
44
+ },
45
+ "actions": {
46
+ "mean": [
47
+ 0.010760927572846413,
48
+ 0.02506864257156849,
49
+ 0.01419717539101839,
50
+ 0.0020873930770903826,
51
+ 0.0005215497221797705,
52
+ -0.00006810902414144948,
53
+ 0.031027423217892647
54
+ ],
55
+ "std": [
56
+ 0.20257413387298584,
57
+ 0.363967627286911,
58
+ 0.30624639987945557,
59
+ 0.12264969944953918,
60
+ 0.11749287694692612,
61
+ 0.08579856157302856,
62
+ 0.9992039203643799
63
+ ],
64
+ "q01": [
65
+ -1.0,
66
+ -1.0,
67
+ -1.0,
68
+ -0.5,
69
+ -0.5001200099349021,
70
+ -0.5003199966907501,
71
+ -1.0
72
+ ],
73
+ "q99": [
74
+ 0.8684000000000001,
75
+ 0.9996,
76
+ 0.9996,
77
+ 0.5,
78
+ 0.4999599966883661,
79
+ 0.4997600099325181,
80
+ 0.9997999999999998
81
+ ]
82
+ }
83
+ }
84
+ }
code/docs/PORTABILITY.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Paths, assets, and the evaluation protocol
2
+
3
+ The release resolves paths relative to the checked-out source or explicit user
4
+ arguments. These changes do not change model dimensions, parameter keys,
5
+ normalization numbers, prompts, sampling, optimization, or scoring.
6
+
7
+ ## Training and feature extraction
8
+
9
+ Run from the release root. Existing `--dataset`, `--base-params`, `--cache`, and
10
+ `--checkpoint-root` arguments override these defaults; training also accepts
11
+ `--assets-dir` (the parent of `pi05_libero/norm_stats.json`). Environment
12
+ variables are read at process startup.
13
+
14
+ | Environment variable | Default relative to the release root |
15
+ | --- | --- |
16
+ | `TASKANCHOR_ROBOMEM_DATASET` | `data/robomemarena_pi05_task_all_with_subtask_seed` |
17
+ | `TASKANCHOR_BASE_PARAMS` | `pretrained/pi05_base/params` |
18
+ | `TASKANCHOR_FEATURE_CACHE` | `data/robomem_siglip_base_main_spatial_v3` |
19
+ | `TASKANCHOR_ASSETS_DIR` | `assets` |
20
+ | `TASKANCHOR_CHECKPOINT_ROOT` | `checkpoints` |
21
+
22
+ The packaged normalization file is the original `pi05_libero` file. Do not
23
+ replace it with newly computed statistics when reproducing the provided model.
24
+ During inference, normalization is loaded directly from the selected
25
+ checkpoint's `assets/pi05_libero/norm_stats.json`; training-server paths are
26
+ not consulted.
27
+
28
+ Training requires the LeRobot Parquet dataset with embedded RGB `image` and
29
+ `wrist_image`, `state`, `actions`, and metadata in `meta/episodes_stats.jsonl`.
30
+ The metadata must preserve `task_id`, `subtask_id`, and `seed_rank`, so subtasks
31
+ can be joined into the original full-task history. The existing loader does
32
+ not require an additional predecoded observation cache. Feature extraction
33
+ uses frozen base-model SigLIP weights and writes the main-camera 4×4 cache.
34
+ The training dataset and feature cache are not required for evaluation.
35
+
36
+ Feature-cache provenance intentionally retains its original strict check,
37
+ including absolute dataset/base-parameter locations and metadata SHA-256.
38
+ If moving a preexisting cache, extraction/training rejects a path-mismatched
39
+ manifest. The supported portable route is to extract a new cache at the new
40
+ location; do not silently rewrite provenance or relabel different features.
41
+ Fresh training is not guaranteed to reproduce the checkpoint bit for bit,
42
+ including the recorded restart at step 10,000.
43
+
44
+ ## Offline inference tokenizer
45
+
46
+ `TASKANCHOR_TOKENIZER_PATH=/absolute/path/to/paligemma_tokenizer.model` selects
47
+ the provided SentencePiece file. It changes the file location only. Without
48
+ that setting, the original `gs://big_vision/paligemma_tokenizer.model` source is
49
+ downloaded through OpenPI, whose cache can be relocated with `OPENPI_DATA_HOME`.
50
+ The public full checkpoint contains the backbone and adapter; inference does
51
+ not require downloading an additional `pi05_base` checkpoint.
52
+
53
+ ## Benchmark installation and paths
54
+
55
+ Install RoboMemArena and its simulator dependencies separately, using commit
56
+ `d09f63b671f362985e1e0d038d2014c2d2519488`. The release does not vendor the
57
+ benchmark, MuJoCo/OSMesa libraries, or visual assets. Its evaluation process
58
+ must import the pinned checkout's `libero_fork` and the official scorer.
59
+
60
+ After checking out that commit, generate configuration without editing the
61
+ official scorer:
62
+
63
+ ```bash
64
+ python tools/prepare_benchmark.py \
65
+ --benchmark-root /path/to/RoboMemArena \
66
+ --config-dir /path/to/robomem_libero_config
67
+
68
+ export TASKANCHOR_ROBOMEM_BENCHMARK=/path/to/RoboMemArena/evaluation_benchmark
69
+ export TASKANCHOR_ROBOMEM_EVAL_PYTHON=/path/to/simulator-env/bin/python
70
+ export TASKANCHOR_LIBERO_CONFIG=/path/to/robomem_libero_config
71
+ export TASKANCHOR_PI05_PYTHON=/path/to/pi05-env/bin/python
72
+ ```
73
+
74
+ The setup utility verifies the pinned Git revision and unchanged tracked
75
+ benchmark files, then writes `OFFICIAL_SOURCE.json` and a local LIBERO
76
+ `config.yaml`. It requires `libero_fork/libero/assets` and `bddl_files`.
77
+ Evaluation uses seeded resets and does not read LIBERO stored `init_files`.
78
+ Keep the simulator virtual environment's executable path; resolving its
79
+ symlink to a base Python loses that environment's packages.
80
+
81
+ OSMesa remains the renderer. If OSMesa libraries are installed outside system
82
+ library paths, set `TASKANCHOR_RENDER_LIBS` to their directory. The original
83
+ runtime used Mesa libraries under its `robomem_render_libs/usr/lib/x86_64-linux-gnu`
84
+ directory; these operating-system binaries are not part of the code package.
85
+
86
+ The original Task25 candidate list is retained in
87
+ `evidence/task25_seed_candidates.json`. Only initialization failures before any
88
+ policy action may advance to the next candidate. Policy failures are scored.
89
+ Both main and wrist observations are still passed to the VLA; only the main
90
+ view receives the TaskAnchor residual.
91
+
92
+ ## Preserved numerical settings
93
+
94
+ The recorded 50k model uses separate permanent frame0, 191 history frames,
95
+ 16 tokens per frame, stride 10, action chunk 20, execution length 10, and
96
+ nearest-stage predicted progress. Evaluation uses all 26 tasks and the original 2500-step budget.
97
+ The evaluator retains the original trial and seed schedule; configure and record
98
+ any different study protocol before collecting results. Paths do not select a
99
+ different memory protocol or change these values.
100
+
101
+ CPU portability checks can be run without JAX or simulator installation:
102
+
103
+ ```bash
104
+ python -m unittest discover -s tests -p test_portability.py
105
+ ```
106
+
107
+ These checks validate path relocation and benchmark setup only. They do not
108
+ replace closed-loop benchmark evaluation.
code/docs/PUBLISHING_ZH.md ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 发布和后续维护
2
+
3
+ Hugging Face 目标:<https://huggingface.co/henry13522/Taskanchor>。
4
+ 代码许可证为 Apache-2.0;完整权重保留 π0.5 / PaliGemma / Gemma 上游条款。模型卡明确区分代码和权重许可。
5
+
6
+ ## 本次目录
7
+
8
+ `model/` 是 Hugging Face 仓库的上传根目录:含 `50000/`、`recipe.json`、tokenizer、模型卡、校验清单和 `code/`。`code/` 同时作为独立 GitHub 源码仓库发布目录。HF 下载后直接按 `code/docs/REPRODUCING.md` 使用。
9
+
10
+ 只发布 50k 完整推理 checkpoint;不包含 optimizer/train_state、训练数据、特征缓存、机器环境、凭据或训练日志。不要将原始工作区整体推送到公开仓库。
11
+
12
+ ## Hugging Face 上传
13
+
14
+ 在终端安全登录有目标仓库写权限的 Hugging Face 账号:
15
+
16
+ ```bash
17
+ hf auth login
18
+ ```
19
+
20
+ 使用官方 Hugging Face CLI 上传已检查的模型目录(目录内包含 `code/`):
21
+
22
+ ```bash
23
+ hf upload henry13522/Taskanchor /path/to/release/model .
24
+ ```
25
+
26
+ 上传使用 Hugging Face 官方 SDK/Xet,无需转换 Orbax checkpoint。若使用 Git 路线,应先安装 Git Xet,并保留发布包 `.gitattributes` 对**无扩展名** Orbax 分片的追踪规则,不能只追踪 `*.safetensors`。
27
+
28
+ 官方文档:[上传文件](https://huggingface.co/docs/huggingface_hub/guides/upload)、[Git Xet](https://huggingface.co/docs/hub/git-xet)。
29
+
30
+ ## GitHub 源码仓库
31
+
32
+ 账号/组织确定后,在 `code/` 内执行以下命令。将 `OWNER` 替换为实际账号;不要在 `model/` 内创建 GitHub 源码仓库。
33
+
34
+ ```bash
35
+ cd /path/to/release/code
36
+ git init -b main
37
+ git add .
38
+ git commit -m 'Release TaskAnchor RoboMemArena pi05 training and evaluation'
39
+ gh repo create OWNER/TaskAnchor --public --source=. --remote=origin --push
40
+ ```
41
+
42
+ 在 GitHub About 中链接 Hugging Face 权重;在模型卡中添加 GitHub 仓库链接。后续对外报告结果时记录代码 commit、模型仓库 revision、benchmark commit 和完整评测命令。
43
+
44
+ ## 本次验证范围
45
+
46
+ 本次执行 CPU 权重恢复、有限性和文件校验、路径测试。公开包不提供历史小批次评测指标或复现结论;评测次数由正式研究协议确定;现有入口保留原试验次数和种子调度,数量不同时须先调整协议实现。原始记录仅保留在上传目录之外的本地审计目录。
code/docs/REPRODUCING.md ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Reproducing the RoboMemArena 50k model
2
+
3
+ This guide uses the downloaded Hugging Face repository, which contains both
4
+ source and complete inference weights:
5
+
6
+ ```text
7
+ Taskanchor/
8
+ ├── code/ # source directory
9
+ │ ├── taskanchor/
10
+ │ ├── pi05/
11
+ │ └── assets/pi05_libero/norm_stats.json
12
+ ├── recipe.json
13
+ ├── tokenizer/paligemma_tokenizer.model
14
+ └── 50000/
15
+ ├── params/ # complete Orbax parameter directory
16
+ └── assets/
17
+ ├── robomem_memory.json
18
+ └── pi05_libero/norm_stats.json
19
+ ```
20
+
21
+ Set these absolute paths for the commands below:
22
+
23
+ ```bash
24
+ export TASKANCHOR_MODEL=/absolute/path/to/Taskanchor
25
+ export TASKANCHOR_CODE="$TASKANCHOR_MODEL/code"
26
+ export TASKANCHOR_BENCHMARK_REPO=/absolute/path/to/RoboMemArena
27
+ export TASKANCHOR_SIM_ENV=/absolute/path/to/robomem-sim-env
28
+ cd "$TASKANCHOR_CODE"
29
+ ```
30
+
31
+ ## 1. Policy environment: Python 3.11 and JAX
32
+
33
+ Use the checked-in `pi05/uv.lock`; do not replace it with an unpinned OpenPI
34
+ installation. This environment runs feature extraction, training, model
35
+ servers, and the evaluation scheduler. The simulator runs in a second
36
+ environment. Install `uv` first if it is unavailable.
37
+
38
+ ```bash
39
+ cd "$TASKANCHOR_CODE/pi05"
40
+ uv python install 3.11
41
+ uv sync --frozen --python 3.11
42
+ uv pip install --python .venv/bin/python --no-deps -e "$TASKANCHOR_CODE"
43
+
44
+ export TASKANCHOR_PI05_PYTHON="$TASKANCHOR_CODE/pi05/.venv/bin/python"
45
+ export TASKANCHOR_TOKENIZER_PATH="$TASKANCHOR_MODEL/tokenizer/paligemma_tokenizer.model"
46
+ cd "$TASKANCHOR_CODE"
47
+ ```
48
+
49
+ The measured policy stack used Python 3.11.13, JAX/JAXlib 0.5.3, Flax 0.10.2,
50
+ Optax 0.2.4, and Orbax 0.11.13. A compatible NVIDIA driver is required for the
51
+ CUDA 12 JAX build. Do not force `JAX_PLATFORMS=cpu` for training or inference.
52
+ The local tokenizer setting avoids a network download during model startup.
53
+ The full 50k checkpoint already contains backbone weights.
54
+
55
+ ## 2. Simulator environment: Python 3.10 and MuJoCo
56
+
57
+ Check out the exact benchmark revision. Use a fresh checkout or a clean
58
+ worktree; `tools/prepare_benchmark.py` rejects modified tracked benchmark code.
59
+
60
+ ```bash
61
+ git clone https://github.com/OpenHelix-Team/RoboMemArena.git "$TASKANCHOR_BENCHMARK_REPO"
62
+ git -C "$TASKANCHOR_BENCHMARK_REPO" checkout d09f63b671f362985e1e0d038d2014c2d2519488
63
+
64
+ uv python install 3.10
65
+ uv venv --python 3.10 "$TASKANCHOR_SIM_ENV"
66
+ uv pip install --python "$TASKANCHOR_SIM_ENV/bin/python" \
67
+ 'numpy==1.26.4' 'mujoco==2.3.7' 'robosuite==1.4.1' \
68
+ 'scipy==1.15.3' 'numba==0.68.0' 'llvmlite==0.50.0' \
69
+ 'torch==2.4.1' 'torchvision==0.19.1' \
70
+ 'opencv-python==4.11.0.86' 'Pillow==11.3.0' \
71
+ 'imageio==2.34.2' 'imageio-ffmpeg==0.6.0' \
72
+ 'PyOpenGL==3.1.10' 'PyYAML==6.0.3' 'h5py==3.16.0' \
73
+ 'easydict==1.13' 'gym==0.26.2' 'bddl==3.6.0' \
74
+ 'termcolor==3.3.0' 'cloudpickle==3.1.2' 'matplotlib==3.10.9' \
75
+ 'websockets==16.1.1' 'msgpack==1.2.3' tqdm
76
+ ```
77
+
78
+ These pins are read from the working evaluation environment. That environment
79
+ inherited system packages; the clean-environment installation above has not
80
+ been replayed as part of this release. Use the pinned benchmark's own setup
81
+ instructions for any platform-specific missing dependency. Do not substitute
82
+ a different MuJoCo/robosuite pair within a controlled evaluation comparison.
83
+
84
+ The measured renderer is OSMesa. On Debian/Ubuntu, install the system OSMesa
85
+ runtime if it is absent:
86
+
87
+ ```bash
88
+ sudo apt-get update
89
+ sudo apt-get install -y libosmesa6 libgl1 libegl1 ffmpeg
90
+ ```
91
+
92
+ The benchmark must contain its meshes/textures and BDDL files. Follow its
93
+ asset instructions if Git checkout alone does not supply these files.
94
+ Create local LIBERO paths without importing its interactive setup:
95
+
96
+ ```bash
97
+ "$TASKANCHOR_PI05_PYTHON" tools/prepare_benchmark.py \
98
+ --benchmark-root "$TASKANCHOR_BENCHMARK_REPO" \
99
+ --config-dir "$TASKANCHOR_CODE/local/robomem_libero_config"
100
+
101
+ export TASKANCHOR_ROBOMEM_BENCHMARK="$TASKANCHOR_BENCHMARK_REPO/evaluation_benchmark"
102
+ export TASKANCHOR_ROBOMEM_EVAL_PYTHON="$TASKANCHOR_SIM_ENV/bin/python"
103
+ export TASKANCHOR_LIBERO_CONFIG="$TASKANCHOR_CODE/local/robomem_libero_config"
104
+ ```
105
+
106
+ If OSMesa is outside the system linker paths, additionally set
107
+ `TASKANCHOR_RENDER_LIBS=/absolute/path/to/osmesa/lib`. Keep the simulator
108
+ virtualenv executable path as written; do not resolve its symlink to base
109
+ Python. The launcher sets `MUJOCO_GL=osmesa`, `PYOPENGL_PLATFORM=osmesa`, and
110
+ the local `libero_fork` import path for each simulator subprocess.
111
+
112
+ ## 3. Evaluate the released checkpoint
113
+
114
+ The unified command starts its own policy servers and terminates only those
115
+ servers when evaluation ends. Do not start a separate server first. The
116
+ `--python` option below is the policy interpreter; the simulator interpreter
117
+ comes from `TASKANCHOR_ROBOMEM_EVAL_PYTHON`.
118
+
119
+ The evaluator preserves its original trial-count and seed schedule. A study
120
+ requiring a different schedule must configure and validate that protocol before
121
+ collecting results. No benchmark score is provided by this release.
122
+
123
+ First inspect the command without allocating GPUs:
124
+
125
+ ```bash
126
+ "$TASKANCHOR_PI05_PYTHON" -m taskanchor \
127
+ --benchmark robomemarena --model pi05 --stage eval \
128
+ --benchmark-root "$TASKANCHOR_ROBOMEM_BENCHMARK" \
129
+ --python "$TASKANCHOR_PI05_PYTHON" --dry-run -- \
130
+ --checkpoint "$TASKANCHOR_MODEL/50000" \
131
+ --output "$TASKANCHOR_CODE/reports/robomem50k_evaluation" \
132
+ --workers 8 --base-port 8800
133
+ ```
134
+
135
+ Remove `--dry-run` to execute the supported task/trial configuration. The output directory
136
+ must not already exist. `--workers 8` uses GPUs 0–7, with one model server per
137
+ GPU. `--workers 1` uses GPU 0 and retains the same task/seed coverage, taking
138
+ longer. The current scheduler assigns physical GPU indices itself; an outer
139
+ `CUDA_VISIBLE_DEVICES` remapping is not an arbitrary GPU-selection interface.
140
+
141
+ Inspect `final_report.json`, `status.json`, and
142
+ per-task logs for complete coverage and runtime failures. Per-task folders
143
+ contain benchmark summaries/videos and a `progress.jsonl` trace containing
144
+ both raw and discretized predictions. Initialization failures are recorded;
145
+ Task25 may advance its recorded candidate list only before any policy action.
146
+ Failed policies are not discarded or retried as successful replacements.
147
+
148
+ ## 4. Exact evaluation contract
149
+
150
+ | Setting | Released 50k protocol |
151
+ | --- | --- |
152
+ | Backbone | π0.5, Gemma 2B VLM / Gemma 300M action expert |
153
+ | Observations | Main and wrist views; 224×224 model inputs |
154
+ | Adapter insertion | Main view only; `current + offset`, 1152→512→1152 |
155
+ | Temporal module | 8 layers, width 512, 8 heads; dropout 0.10, DropPath 0 |
156
+ | History frame | 4×4 = 16 SigLIP tokens |
157
+ | Memory protocol | Separate permanent frame0 + up to 191 history frames |
158
+ | History stride | 10 environment steps |
159
+ | Action chunk / replan | Predict 20; execute 10, then replan |
160
+ | Progress | Continuous prediction mapped to nearest valid task stage |
161
+ | Language | Fixed full-task rewrite 0 + `Progress:%.2f` |
162
+ | Task coverage | Tasks 1–26; trial count and accepted seeds must be checked against the research protocol |
163
+ | Episode budget | 2500 steps |
164
+ | Rendering | OSMesa |
165
+
166
+ There is no oracle progress input. For a four-stage task, conditioning values
167
+ are 0.00, 0.25, 0.50, and 0.75. The separate frame0 is part of this model's
168
+ training and evaluation contract; replacing it with FIFO frame0 changes the
169
+ experiment even if the weight keys still load.
170
+
171
+ This package provides runnable model and evaluation code. It does not publish
172
+ benchmark scores or assert that a new evaluation with the sample size required
173
+ by a paper has been completed. Record and report the actual task/seed coverage
174
+ of each evaluation run.
175
+
176
+ ## 5. Extract training features
177
+
178
+ Training starts from the converted LeRobot dataset, not the public 50k
179
+ checkpoint. The dataset must contain embedded main/wrist RGB images, real
180
+ states/actions, and the original subtask/seed metadata. This package does not
181
+ include the raw-HDF5-to-LeRobot converter or the dataset. Preserve the original
182
+ conversion and ordering when reproducing training. The reference manifest has
183
+ 2600 full-task groups, 2,811,755 frames, and a maximum 1918 frames per full task.
184
+ Evaluation does not need any of these training files.
185
+
186
+ Provide the original pretrained `pi05_base/params` and data locations:
187
+
188
+ ```bash
189
+ export TASKANCHOR_ROBOMEM_DATASET=/absolute/path/to/robomemarena_pi05_task_all_with_subtask_seed
190
+ export TASKANCHOR_BASE_PARAMS=/absolute/path/to/pi05_base/params
191
+ export TASKANCHOR_FEATURE_CACHE=/absolute/path/to/new_robomem_spatial_cache
192
+ export TASKANCHOR_CHECKPOINT_ROOT=/absolute/path/to/new_training_checkpoints
193
+
194
+ CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
195
+ "$TASKANCHOR_PI05_PYTHON" -m taskanchor \
196
+ --benchmark robomemarena --model pi05 --stage extract \
197
+ --python "$TASKANCHOR_PI05_PYTHON" -- \
198
+ --dataset "$TASKANCHOR_ROBOMEM_DATASET" \
199
+ --base-params "$TASKANCHOR_BASE_PARAMS" \
200
+ --cache "$TASKANCHOR_FEATURE_CACHE" --batch-size 256 --workers 8
201
+ ```
202
+
203
+ Extraction validates every completed feature file and writes `VERIFIED.json`.
204
+ It stores FP16 values derived from BF16 main-camera SigLIP features, using the
205
+ recorded 16×16→4×4 bilinear pooling without antialiasing. Existing cache
206
+ provenance is checked strictly, including source paths; see [PORTABILITY](PORTABILITY.md)
207
+ before relocating an old cache.
208
+
209
+ ## 6. Train one run with the 5k freeze prefix
210
+
211
+ Use a new run name. The schedule is 55,000 total updates, with 5,000
212
+ adapter-only updates followed by 50,000 joint updates. The released checkpoint
213
+ is taken at update 50,000: 5,000 adapter-only + 45,000 joint updates. Setting
214
+ `--steps 50000` would change the learning-rate schedule and is not the recorded
215
+ experiment.
216
+
217
+ ```bash
218
+ CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
219
+ "$TASKANCHOR_PI05_PYTHON" -m taskanchor \
220
+ --benchmark robomemarena --model pi05 --stage train \
221
+ --python "$TASKANCHOR_PI05_PYTHON" -- \
222
+ --dataset "$TASKANCHOR_ROBOMEM_DATASET" \
223
+ --base-params "$TASKANCHOR_BASE_PARAMS" \
224
+ --cache "$TASKANCHOR_FEATURE_CACHE" \
225
+ --assets-dir "$TASKANCHOR_CODE/assets" \
226
+ --checkpoint-root "$TASKANCHOR_CHECKPOINT_ROOT" \
227
+ --run-name robomem50k_reproduction \
228
+ --steps 55000 --freeze-steps 5000 --batch-size 256 \
229
+ --action-horizon 20 --execution-horizon 10 \
230
+ --fsdp-devices 8 --workers 8 --save-interval 1000 --log-interval 20
231
+ ```
232
+
233
+ The measured run restarted after a complete 10,000-step checkpoint to enable
234
+ the shared-memory loader. To mirror that history, retain the **55k schedule**
235
+ for the first leg, stop the first process after that checkpoint is complete,
236
+ then rerun the exact command above with:
237
+
238
+ ```text
239
+ --resume --resume-step 10000 --shared-memory-loader --device-prefetch 2 --prefetch-factor 2
240
+ ```
241
+
242
+ Do not launch the resumed command while the original process is still running.
243
+ The recorded restart arguments are retained in
244
+ `evidence/pipeline_restart_10000.json`. The sampler restarts with seed
245
+ `42 + restored_step`; it does not restore an exact input cursor. A fresh run
246
+ therefore is not promised to reproduce identical parameter bytes.
247
+
248
+ The training output is
249
+ `$TASKANCHOR_CHECKPOINT_ROOT/pi05_robomemarena_memory_v5_main/robomem50k_reproduction/`.
250
+ It contains the parent `recipe.json` and numbered checkpoints. Training saves
251
+ every 1000 updates and retains 5000-step milestones under the original manager
252
+ policy. Keep optimizer/train-state files for resuming; the public inference
253
+ package intentionally omits them and cannot resume the original 50k optimizer.
254
+
255
+ | Training setting | Value |
256
+ | --- | --- |
257
+ | Global batch / FSDP devices | 256 / 8 |
258
+ | Visual encoder | Frozen throughout; image projection head joins after 5k |
259
+ | Optimizer | AdamW, β=(0.9, 0.95), global gradient clip 1.0 |
260
+ | Adapter learning rate | 2.5e-5 for first 5k; cosine decay afterward |
261
+ | Backbone / image-head LR after unfreezing | 2.5e-5 / 1e-5, cosine decay |
262
+ | Final cosine multiplier | 0.1 |
263
+ | Weight decay | Adapter 0.05; backbone/image head 0 |
264
+ | Progress target | `round(subtask_id / number_of_subtasks, 2)` |
265
+ | Loss | Flow action loss + 0.01 × (100 × progress MSE + 1 − Pearson) |
266
+
267
+ Frozen backbone Adam states do not advance during the first 5000 updates.
268
+ Neither LoRA nor a separate planner is introduced. Numerical implementation
269
+ and optimizer groups are in `taskanchor/pi05/taskanchor.py` and
270
+ `taskanchor/pi05/training.py`.
271
+
272
+ ## 7. Validation scope
273
+
274
+ ```bash
275
+ cd "$TASKANCHOR_CODE"
276
+ python -m unittest discover -s tests -p test_portability.py
277
+ ```
278
+
279
+ The packaging checks passed source parsing, CPU imports, path tests, and
280
+ unchanged numerical source checks. Adapter/TTT/training-loop/history core
281
+ files remain byte-identical to the previously tested release. Data sampling,
282
+ online history, action sampling, and extraction computations are unchanged.
283
+ No new training or GPU rollout was started during this packaging pass.
code/evidence/environment_pi05.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "python": "3.11.13",
3
+ "jax": "0.5.3",
4
+ "jaxlib": "0.5.3",
5
+ "flax": "0.10.2",
6
+ "optax": "0.2.4",
7
+ "orbax-checkpoint": "0.11.13",
8
+ "numpy": "1.26.4",
9
+ "torch": "2.7.1",
10
+ "transformers": "4.53.2",
11
+ "lerobot": "0.1.0",
12
+ "pyarrow": "20.0.0",
13
+ "Pillow": "11.2.1",
14
+ "sentencepiece": "0.2.0",
15
+ "ruff": "0.11.12",
16
+ "pytest": "8.3.5",
17
+ "openpi": "0.1.0",
18
+ "openpi-client": "0.1.0"
19
+ }
code/evidence/packaging_checks.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ast_files": 107,
3
+ "portability_tests": {
4
+ "passed": 3
5
+ },
6
+ "adapter_and_history_cpu_tests": {
7
+ "passed": 6
8
+ },
9
+ "no_gpu_or_rollouts_in_this_packaging_pass": true,
10
+ "numerical_model_logic_modified": false,
11
+ "changes": [
12
+ "portable asset and data paths",
13
+ "documentation and license notices",
14
+ "packaging"
15
+ ],
16
+ "training_data_requirement": "Previously converted LeRobot dataset with subtask/seed metadata; raw conversion not included."
17
+ }
code/evidence/pipeline_restart_10000.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "command": [
3
+ "<OPENPI>/.venv/bin/python",
4
+ "-u",
5
+ "scripts/train_robomem_memory.py",
6
+ "--run-name",
7
+ "main_v5_spatial4x4_h191_a20_exec10_bs256_5k50k",
8
+ "--batch-size",
9
+ "256",
10
+ "--steps",
11
+ "55000",
12
+ "--freeze-steps",
13
+ "5000",
14
+ "--action-horizon",
15
+ "20",
16
+ "--execution-horizon",
17
+ "10",
18
+ "--fsdp-devices",
19
+ "8",
20
+ "--workers",
21
+ "8",
22
+ "--resume",
23
+ "--resume-step",
24
+ "10000",
25
+ "--shared-memory-loader",
26
+ "--device-prefetch",
27
+ "2",
28
+ "--prefetch-factor",
29
+ "2"
30
+ ],
31
+ "source_sha256": {
32
+ "scripts/train_robomem_memory.py": "218d6f44812d0043438b3c515bf8ca4d8e1fdd34a86dac1d6431398fb7aef9fb",
33
+ "src/openpi/training/robomem_memory_loader.py": "8924b7cb7146ae8f5a46230050f18938fc83338ab5382efb5cbe5e2ad6b9d038",
34
+ "scripts/test_robomem_memory_loader.py": "80f421f909c0d3373171dccd013c46fbf8b2b9a67597b81a6198e6163cdd2ba7",
35
+ "scripts/resume_robomem_pipeline_at_10k.py": "0d9dbfa45ce3a279878a203ff8ca147ff1189dfdaf166e137ac27459951165ea"
36
+ }
37
+ }
code/evidence/robomem50k_adapter_numerical_parity.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "checkpoint": "<RUN>/50000/params",
3
+ "adapter_parameters": 26930049,
4
+ "adapter_arrays_sha256": "52573dc7d0ac6fb4373382bed7430ca1c0f30af1474cceab42c785ab6bae8a10",
5
+ "cases": [
6
+ {
7
+ "valid_history_frames": 0,
8
+ "refined_exact": true,
9
+ "progress_exact": true
10
+ },
11
+ {
12
+ "valid_history_frames": 17,
13
+ "refined_exact": true,
14
+ "progress_exact": true
15
+ },
16
+ {
17
+ "valid_history_frames": 191,
18
+ "refined_exact": true,
19
+ "progress_exact": true
20
+ }
21
+ ],
22
+ "device": "CPU",
23
+ "limitation": "Synthetic visual features; full VLA action inference and closed-loop episodes not rerun"
24
+ }
code/evidence/task25_seed_candidates.json ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "candidates": [
3
+ 50,
4
+ 51,
5
+ 52,
6
+ 53,
7
+ 54,
8
+ 55,
9
+ 56,
10
+ 57,
11
+ 58,
12
+ 59,
13
+ 60,
14
+ 61,
15
+ 62,
16
+ 63,
17
+ 64,
18
+ 65,
19
+ 66,
20
+ 67,
21
+ 68,
22
+ 69,
23
+ 70,
24
+ 71,
25
+ 72,
26
+ 73,
27
+ 74,
28
+ 75,
29
+ 76,
30
+ 77,
31
+ 78,
32
+ 79,
33
+ 80,
34
+ 81,
35
+ 82,
36
+ 83,
37
+ 84,
38
+ 85,
39
+ 86,
40
+ 87,
41
+ 88,
42
+ 89,
43
+ 90,
44
+ 91,
45
+ 92,
46
+ 93,
47
+ 94,
48
+ 95,
49
+ 96,
50
+ 97,
51
+ 98,
52
+ 99,
53
+ 216,
54
+ 241,
55
+ 247,
56
+ 248,
57
+ 250,
58
+ 252,
59
+ 253,
60
+ 257,
61
+ 258,
62
+ 262,
63
+ 265,
64
+ 266,
65
+ 267,
66
+ 269,
67
+ 271,
68
+ 279,
69
+ 280,
70
+ 283,
71
+ 289,
72
+ 290,
73
+ 293,
74
+ 294,
75
+ 295,
76
+ 296,
77
+ 300,
78
+ 303,
79
+ 308,
80
+ 310,
81
+ 313,
82
+ 315,
83
+ 316,
84
+ 318,
85
+ 322,
86
+ 325,
87
+ 326,
88
+ 328,
89
+ 329,
90
+ 332,
91
+ 333,
92
+ 335,
93
+ 336,
94
+ 337,
95
+ 338,
96
+ 339,
97
+ 343,
98
+ 344,
99
+ 345,
100
+ 346,
101
+ 350,
102
+ 352,
103
+ 360,
104
+ 361,
105
+ 362,
106
+ 364,
107
+ 367,
108
+ 368,
109
+ 369,
110
+ 370,
111
+ 371,
112
+ 372,
113
+ 373,
114
+ 374,
115
+ 375,
116
+ 377,
117
+ 378,
118
+ 381,
119
+ 382,
120
+ 383,
121
+ 384,
122
+ 385,
123
+ 386,
124
+ 387,
125
+ 388,
126
+ 389,
127
+ 390,
128
+ 391,
129
+ 392,
130
+ 393,
131
+ 394,
132
+ 395,
133
+ 396,
134
+ 397,
135
+ 398,
136
+ 399,
137
+ 401,
138
+ 402,
139
+ 403,
140
+ 404,
141
+ 405,
142
+ 406,
143
+ 407,
144
+ 408,
145
+ 409,
146
+ 410,
147
+ 411,
148
+ 413,
149
+ 414,
150
+ 415,
151
+ 416,
152
+ 418,
153
+ 419,
154
+ 420,
155
+ 421,
156
+ 422,
157
+ 423,
158
+ 424,
159
+ 425,
160
+ 426,
161
+ 427,
162
+ 428,
163
+ 429,
164
+ 430,
165
+ 431,
166
+ 432,
167
+ 433,
168
+ 434,
169
+ 435,
170
+ 436,
171
+ 437,
172
+ 438,
173
+ 440,
174
+ 442,
175
+ 446,
176
+ 447,
177
+ 448,
178
+ 449,
179
+ 450,
180
+ 451,
181
+ 453,
182
+ 454,
183
+ 455,
184
+ 457,
185
+ 458,
186
+ 459,
187
+ 460,
188
+ 461,
189
+ 462,
190
+ 463,
191
+ 465,
192
+ 466,
193
+ 468,
194
+ 469,
195
+ 470,
196
+ 472,
197
+ 473,
198
+ 474,
199
+ 475,
200
+ 476,
201
+ 477,
202
+ 478
203
+ ],
204
+ "max_attempts": 200,
205
+ "selection": "ascending from 50, excluding all training seed_rank values",
206
+ "training_seed_min": 100,
207
+ "training_seed_max": 813
208
+ }
code/pi05/LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
code/pi05/LICENSE_GEMMA.txt ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Gemma Terms of Use
2
+
3
+ Last modified: February 21, 2024
4
+
5
+ By using, reproducing, modifying, distributing, performing or displaying any portion or element of Gemma, Model Derivatives including via any Hosted Service, (each as defined below) (collectively, the "Gemma Services") or otherwise accepting the terms of this Agreement, you agree to be bound by this Agreement.
6
+
7
+ Section 1: DEFINITIONS
8
+ 1.1 Definitions
9
+ (a) "Agreement" or "Gemma Terms of Use" means these terms and conditions that govern the use, reproduction, Distribution or modification of the Gemma Services and any terms and conditions incorporated by reference.
10
+
11
+ (b) "Distribution" or "Distribute" means any transmission, publication, or other sharing of Gemma or Model Derivatives to a third party, including by providing or making Gemma or its functionality available as a hosted service via API, web access, or any other electronic or remote means ("Hosted Service").
12
+
13
+ (c) "Gemma" means the set of machine learning language models, trained model weights and parameters identified at ai.google.dev/gemma, regardless of the source that you obtained it from.
14
+
15
+ (d) "Google" means Google LLC.
16
+
17
+ (e) "Model Derivatives" means all (i) modifications to Gemma, (ii) works based on Gemma, or (iii) any other machine learning model which is created by transfer of patterns of the weights, parameters, operations, or Output of Gemma, to that model in order to cause that model to perform similarly to Gemma, including distillation methods that use intermediate data representations or methods based on the generation of synthetic data Outputs by Gemma for training that model. For clarity, Outputs are not deemed Model Derivatives.
18
+
19
+ (f) "Output" means the information content output of Gemma or a Model Derivative that results from operating or otherwise using Gemma or the Model Derivative, including via a Hosted Service.
20
+
21
+ 1.2
22
+ As used in this Agreement, "including" means "including without limitation".
23
+
24
+ Section 2: ELIGIBILITY AND USAGE
25
+ 2.1 Eligibility
26
+ You represent and warrant that you have the legal capacity to enter into this Agreement (including being of sufficient age of consent). If you are accessing or using any of the Gemma Services for or on behalf of a legal entity, (a) you are entering into this Agreement on behalf of yourself and that legal entity, (b) you represent and warrant that you have the authority to act on behalf of and bind that entity to this Agreement and (c) references to "you" or "your" in the remainder of this Agreement refers to both you (as an individual) and that entity.
27
+
28
+ 2.2 Use
29
+ You may use, reproduce, modify, Distribute, perform or display any of the Gemma Services only in accordance with the terms of this Agreement, and must not violate (or encourage or permit anyone else to violate) any term of this Agreement.
30
+
31
+ Section 3: DISTRIBUTION AND RESTRICTIONS
32
+ 3.1 Distribution and Redistribution
33
+ You may reproduce or Distribute copies of Gemma or Model Derivatives if you meet all of the following conditions:
34
+
35
+ You must include the use restrictions referenced in Section 3.2 as an enforceable provision in any agreement (e.g., license agreement, terms of use, etc.) governing the use and/or distribution of Gemma or Model Derivatives and you must provide notice to subsequent users you Distribute to that Gemma or Model Derivatives are subject to the use restrictions in Section 3.2.
36
+ You must provide all third party recipients of Gemma or Model Derivatives a copy of this Agreement.
37
+ You must cause any modified files to carry prominent notices stating that you modified the files.
38
+ All Distributions (other than through a Hosted Service) must be accompanied by a "Notice" text file that contains the following notice: "Gemma is provided under and subject to the Gemma Terms of Use found at ai.google.dev/gemma/terms".
39
+ You may add your own intellectual property statement to your modifications and, except as set forth in this Section, may provide additional or different terms and conditions for use, reproduction, or Distribution of your modifications, or for any such Model Derivatives as a whole, provided your use, reproduction, modification, Distribution, performance, and display of Gemma otherwise complies with the terms and conditions of this Agreement. Any additional or different terms and conditions you impose must not conflict with the terms of this Agreement.
40
+
41
+ 3.2 Use Restrictions
42
+ You must not use any of the Gemma Services:
43
+
44
+ for the restricted uses set forth in the Gemma Prohibited Use Policy at ai.google.dev/gemma/prohibited_use_policy ("Prohibited Use Policy"), which is hereby incorporated by reference into this Agreement; or
45
+ in violation of applicable laws and regulations.
46
+ To the maximum extent permitted by law, Google reserves the right to restrict (remotely or otherwise) usage of any of the Gemma Services that Google reasonably believes are in violation of this Agreement.
47
+
48
+ 3.3 Generated Output
49
+ Google claims no rights in Outputs you generate using Gemma. You and your users are solely responsible for Outputs and their subsequent uses.
50
+
51
+ Section 4: ADDITIONAL PROVISIONS
52
+ 4.1 Updates
53
+ Google may update Gemma from time to time, and you must make reasonable efforts to use the latest version of Gemma.
54
+
55
+ 4.2 Trademarks
56
+ Nothing in this Agreement grants you any rights to use Google's trademarks, trade names, logos or to otherwise suggest endorsement or misrepresent the relationship between you and Google. Google reserves any rights not expressly granted herein.
57
+
58
+ 4.3 DISCLAIMER OF WARRANTY
59
+ UNLESS REQUIRED BY APPLICABLE LAW, THE GEMMA SERVICES, AND OUTPUTS, ARE PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING, REPRODUCING, MODIFYING, PERFORMING, DISPLAYING OR OR DISTRIBUTING ANY OF THE GEMMA SERVICES OR OUTPUTS AND ASSUME ANY AND ALL RISKS ASSOCIATED WITH YOUR USE OR DISTRIBUTION OF ANY OF THE GEMMA SERVICES OR OUTPUTS AND YOUR EXERCISE OF RIGHTS AND PERMISSIONS UNDER THIS AGREEMENT.
60
+
61
+ 4.4 LIMITATION OF LIABILITY
62
+ TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE), PRODUCT LIABILITY, CONTRACT, OR OTHERWISE, UNLESS REQUIRED BY APPLICABLE LAW, SHALL GOOGLE OR ITS AFFILIATES BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR LOST PROFITS OF ANY KIND ARISING FROM THIS AGREEMENT OR RELATED TO, ANY OF THE GEMMA SERVICES OR OUTPUTS EVEN IF GOOGLE OR ITS AFFILIATES HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
63
+
64
+ 4.5 Term, Termination, and Survival
65
+ The term of this Agreement will commence upon your acceptance of this Agreement (including acceptance by your use, modification, or Distribution, reproduction, performance or display of any portion or element of the Gemma Services) and will continue in full force and effect until terminated in accordance with the terms of this Agreement. Google may terminate this Agreement if you are in breach of any term of this Agreement. Upon termination of this Agreement, you must delete and cease use and Distribution of all copies of Gemma and Model Derivatives in your possession or control. Sections 1, 2.1, 3.3, 4.2 to 4.9 shall survive the termination of this Agreement.
66
+
67
+ 4.6 Governing Law and Jurisdiction
68
+ This Agreement will be governed by the laws of the State of California without regard to choice of law principles. The UN Convention on Contracts for the International Sale of Goods does not apply to this Agreement. The state and federal courts of Santa Clara County, California shall have exclusive jurisdiction of any dispute arising out of this Agreement.
69
+
70
+ 4.7 Severability
71
+ If any provision of this Agreement is held to be invalid, illegal or unenforceable, the remaining provisions shall be unaffected thereby and remain valid as if such provision had not been set forth herein.
72
+
73
+ 4.8 Entire Agreement
74
+ This Agreement states all the terms agreed between the parties and supersedes all other agreements between the parties as of the date of acceptance relating to its subject matter.
75
+
76
+ 4.9 No Waiver
77
+ Google will not be treated as having waived any rights by not exercising (or delaying the exercise of) any rights under this Agreement.
code/pi05/README.md ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # openpi
2
+
3
+ openpi holds open-source models and packages for robotics, published by the [Physical Intelligence team](https://www.physicalintelligence.company/).
4
+
5
+ Currently, this repo contains three types of models:
6
+ - the [π₀ model](https://www.physicalintelligence.company/blog/pi0), a flow-based vision-language-action model (VLA).
7
+ - the [π₀-FAST model](https://www.physicalintelligence.company/research/fast), an autoregressive VLA, based on the FAST action tokenizer.
8
+ - the [π₀.₅ model](https://www.physicalintelligence.company/blog/pi05), an upgraded version of π₀ with better open-world generalization trained with [knowledge insulation](https://www.physicalintelligence.company/research/knowledge_insulation). Note that, in this repository, we currently only support the flow matching head for both $\pi_{0.5}$ training and inference.
9
+
10
+ For all models, we provide _base model_ checkpoints, pre-trained on 10k+ hours of robot data, and examples for using them out of the box or fine-tuning them to your own datasets.
11
+
12
+ This is an experiment: $\pi_0$ was developed for our own robots, which differ from the widely used platforms such as [ALOHA](https://tonyzhaozh.github.io/aloha/) and [DROID](https://droid-dataset.github.io/), and though we are optimistic that researchers and practitioners will be able to run creative new experiments adapting $\pi_0$ to their own platforms, we do not expect every such attempt to be successful. All this is to say: $\pi_0$ may or may not work for you, but you are welcome to try it and see!
13
+
14
+ ## Updates
15
+
16
+ - [Sept 2025] We released PyTorch support in openpi.
17
+ - [Sept 2025] We released pi05, an upgraded version of pi0 with better open-world generalization.
18
+ - [Sept 2025]: We have added an [improved idle filter](examples/droid/README_train.md#data-filtering) for DROID training.
19
+ - [Jun 2025]: We have added [instructions](examples/droid/README_train.md) for using `openpi` to train VLAs on the full [DROID dataset](https://droid-dataset.github.io/). This is an approximate open-source implementation of the training pipeline used to train pi0-FAST-DROID.
20
+
21
+
22
+ ## Requirements
23
+
24
+ To run the models in this repository, you will need an NVIDIA GPU with at least the following specifications. These estimations assume a single GPU, but you can also use multiple GPUs with model parallelism to reduce per-GPU memory requirements by configuring `fsdp_devices` in the training config. Please also note that the current training script does not yet support multi-node training.
25
+
26
+ | Mode | Memory Required | Example GPU |
27
+ | ------------------ | --------------- | ------------------ |
28
+ | Inference | > 8 GB | RTX 4090 |
29
+ | Fine-Tuning (LoRA) | > 22.5 GB | RTX 4090 |
30
+ | Fine-Tuning (Full) | > 70 GB | A100 (80GB) / H100 |
31
+
32
+ The repo has been tested with Ubuntu 22.04, we do not currently support other operating systems.
33
+
34
+ ## Installation
35
+
36
+ When cloning this repo, make sure to update submodules:
37
+
38
+ ```bash
39
+ git clone --recurse-submodules git@github.com:Physical-Intelligence/openpi.git
40
+
41
+ # Or if you already cloned the repo:
42
+ git submodule update --init --recursive
43
+ ```
44
+
45
+ We use [uv](https://docs.astral.sh/uv/) to manage Python dependencies. See the [uv installation instructions](https://docs.astral.sh/uv/getting-started/installation/) to set it up. Once uv is installed, run the following to set up the environment:
46
+
47
+ ```bash
48
+ GIT_LFS_SKIP_SMUDGE=1 uv sync
49
+ GIT_LFS_SKIP_SMUDGE=1 uv pip install -e .
50
+ ```
51
+
52
+ NOTE: `GIT_LFS_SKIP_SMUDGE=1` is needed to pull LeRobot as a dependency.
53
+
54
+ **Docker**: As an alternative to uv installation, we provide instructions for installing openpi using Docker. If you encounter issues with your system setup, consider using Docker to simplify installation. See [Docker Setup](docs/docker.md) for more details.
55
+
56
+
57
+
58
+
59
+ ## Model Checkpoints
60
+
61
+ ### Base Models
62
+ We provide multiple base VLA model checkpoints. These checkpoints have been pre-trained on 10k+ hours of robot data, and can be used for fine-tuning.
63
+
64
+ | Model | Use Case | Description | Checkpoint Path |
65
+ | ------------ | ----------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
66
+ | $\pi_0$ | Fine-Tuning | Base [π₀ model](https://www.physicalintelligence.company/blog/pi0) for fine-tuning | `gs://openpi-assets/checkpoints/pi0_base` |
67
+ | $\pi_0$-FAST | Fine-Tuning | Base autoregressive [π₀-FAST model](https://www.physicalintelligence.company/research/fast) for fine-tuning | `gs://openpi-assets/checkpoints/pi0_fast_base` |
68
+ | $\pi_{0.5}$ | Fine-Tuning | Base [π₀.₅ model](https://www.physicalintelligence.company/blog/pi05) for fine-tuning | `gs://openpi-assets/checkpoints/pi05_base` |
69
+
70
+ ### Fine-Tuned Models
71
+ We also provide "expert" checkpoints for various robot platforms and tasks. These models are fine-tuned from the base models above and intended to run directly on the target robot. These may or may not work on your particular robot. Since these checkpoints were fine-tuned on relatively small datasets collected with more widely available robots, such as ALOHA and the DROID Franka setup, they might not generalize to your particular setup, though we found some of these, especially the DROID checkpoint, to generalize quite broadly in practice.
72
+
73
+ | Model | Use Case | Description | Checkpoint Path |
74
+ | ------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
75
+ | $\pi_0$-FAST-DROID | Inference | $\pi_0$-FAST model fine-tuned on the [DROID dataset](https://droid-dataset.github.io/): can perform a wide range of simple table-top manipulation tasks 0-shot in new scenes on the DROID robot platform | `gs://openpi-assets/checkpoints/pi0_fast_droid` |
76
+ | $\pi_0$-DROID | Fine-Tuning | $\pi_0$ model fine-tuned on the [DROID dataset](https://droid-dataset.github.io/): faster inference than $\pi_0$-FAST-DROID, but may not follow language commands as well | `gs://openpi-assets/checkpoints/pi0_droid` |
77
+ | $\pi_0$-ALOHA-towel | Inference | $\pi_0$ model fine-tuned on internal [ALOHA](https://tonyzhaozh.github.io/aloha/) data: can fold diverse towels 0-shot on ALOHA robot platforms | `gs://openpi-assets/checkpoints/pi0_aloha_towel` |
78
+ | $\pi_0$-ALOHA-tupperware | Inference | $\pi_0$ model fine-tuned on internal [ALOHA](https://tonyzhaozh.github.io/aloha/) data: can unpack food from a tupperware container | `gs://openpi-assets/checkpoints/pi0_aloha_tupperware` |
79
+ | $\pi_0$-ALOHA-pen-uncap | Inference | $\pi_0$ model fine-tuned on public [ALOHA](https://dit-policy.github.io/) data: can uncap a pen | `gs://openpi-assets/checkpoints/pi0_aloha_pen_uncap` |
80
+ | $\pi_{0.5}$-LIBERO | Inference | $\pi_{0.5}$ model fine-tuned for the [LIBERO](https://libero-project.github.io/datasets) benchmark: gets state-of-the-art performance (see [LIBERO README](examples/libero/README.md)) | `gs://openpi-assets/checkpoints/pi05_libero` |
81
+ | $\pi_{0.5}$-DROID | Inference / Fine-Tuning | $\pi_{0.5}$ model fine-tuned on the [DROID dataset](https://droid-dataset.github.io/) with [knowledge insulation](https://www.physicalintelligence.company/research/knowledge_insulation): fast inference and good language-following | `gs://openpi-assets/checkpoints/pi05_droid` |
82
+
83
+
84
+ By default, checkpoints are automatically downloaded from `gs://openpi-assets` and are cached in `~/.cache/openpi` when needed. You can overwrite the download path by setting the `OPENPI_DATA_HOME` environment variable.
85
+
86
+
87
+
88
+
89
+ ## Running Inference for a Pre-Trained Model
90
+
91
+ Our pre-trained model checkpoints can be run with a few lines of code (here our $\pi_0$-FAST-DROID model):
92
+ ```python
93
+ from openpi.training import config as _config
94
+ from openpi.policies import policy_config
95
+ from openpi.shared import download
96
+
97
+ config = _config.get_config("pi05_droid")
98
+ checkpoint_dir = download.maybe_download("gs://openpi-assets/checkpoints/pi05_droid")
99
+
100
+ # Create a trained policy.
101
+ policy = policy_config.create_trained_policy(config, checkpoint_dir)
102
+
103
+ # Run inference on a dummy example.
104
+ example = {
105
+ "observation/exterior_image_1_left": ...,
106
+ "observation/wrist_image_left": ...,
107
+ ...
108
+ "prompt": "pick up the fork"
109
+ }
110
+ action_chunk = policy.infer(example)["actions"]
111
+ ```
112
+ You can also test this out in the [example notebook](examples/inference.ipynb).
113
+
114
+ We provide detailed step-by-step examples for running inference of our pre-trained checkpoints on [DROID](examples/droid/README.md) and [ALOHA](examples/aloha_real/README.md) robots.
115
+
116
+ **Remote Inference**: We provide [examples and code](docs/remote_inference.md) for running inference of our models **remotely**: the model can run on a different server and stream actions to the robot via a websocket connection. This makes it easy to use more powerful GPUs off-robot and keep robot and policy environments separate.
117
+
118
+ **Test inference without a robot**: We provide a [script](examples/simple_client/README.md) for testing inference without a robot. This script will generate a random observation and run inference with the model. See [here](examples/simple_client/README.md) for more details.
119
+
120
+
121
+
122
+
123
+
124
+ ## Fine-Tuning Base Models on Your Own Data
125
+
126
+ We will fine-tune the $\pi_{0.5}$ model on the [LIBERO dataset](https://libero-project.github.io/datasets) as a running example for how to fine-tune a base model on your own data. We will explain three steps:
127
+ 1. Convert your data to a LeRobot dataset (which we use for training)
128
+ 2. Defining training configs and running training
129
+ 3. Spinning up a policy server and running inference
130
+
131
+ ### 1. Convert your data to a LeRobot dataset
132
+
133
+ We provide a minimal example script for converting LIBERO data to a LeRobot dataset in [`examples/libero/convert_libero_data_to_lerobot.py`](examples/libero/convert_libero_data_to_lerobot.py). You can easily modify it to convert your own data! You can download the raw LIBERO dataset from [here](https://huggingface.co/datasets/openvla/modified_libero_rlds), and run the script with:
134
+
135
+ ```bash
136
+ uv run examples/libero/convert_libero_data_to_lerobot.py --data_dir /path/to/your/libero/data
137
+ ```
138
+
139
+ **Note:** If you just want to fine-tune on LIBERO, you can skip this step, because our LIBERO fine-tuning configs point to a pre-converted LIBERO dataset. This step is merely an example that you can adapt to your own data.
140
+
141
+ ### 2. Defining training configs and running training
142
+
143
+ To fine-tune a base model on your own data, you need to define configs for data processing and training. We provide example configs with detailed comments for LIBERO below, which you can modify for your own dataset:
144
+
145
+ - [`LiberoInputs` and `LiberoOutputs`](src/openpi/policies/libero_policy.py): Defines the data mapping from the LIBERO environment to the model and vice versa. Will be used for both, training and inference.
146
+ - [`LeRobotLiberoDataConfig`](src/openpi/training/config.py): Defines how to process raw LIBERO data from LeRobot dataset for training.
147
+ - [`TrainConfig`](src/openpi/training/config.py): Defines fine-tuning hyperparameters, data config, and weight loader.
148
+
149
+ We provide example fine-tuning configs for [π₀](src/openpi/training/config.py), [π₀-FAST](src/openpi/training/config.py), and [π₀.₅](src/openpi/training/config.py) on LIBERO data.
150
+
151
+ Before we can run training, we need to compute the normalization statistics for the training data. Run the script below with the name of your training config:
152
+
153
+ ```bash
154
+ uv run scripts/compute_norm_stats.py --config-name pi05_libero
155
+ ```
156
+
157
+ Now we can kick off training with the following command (the `--overwrite` flag is used to overwrite existing checkpoints if you rerun fine-tuning with the same config):
158
+
159
+ ```bash
160
+ XLA_PYTHON_CLIENT_MEM_FRACTION=0.9 uv run scripts/train.py pi05_libero --exp-name=my_experiment --overwrite
161
+ ```
162
+
163
+ The command will log training progress to the console and save checkpoints to the `checkpoints` directory. You can also monitor training progress on the Weights & Biases dashboard. For maximally using the GPU memory, set `XLA_PYTHON_CLIENT_MEM_FRACTION=0.9` before running training -- this enables JAX to use up to 90% of the GPU memory (vs. the default of 75%).
164
+
165
+ **Note:** We provide functionality for *reloading* normalization statistics for state / action normalization from pre-training. This can be beneficial if you are fine-tuning to a new task on a robot that was part of our pre-training mixture. For more details on how to reload normalization statistics, see the [norm_stats.md](docs/norm_stats.md) file.
166
+
167
+ ### 3. Spinning up a policy server and running inference
168
+
169
+ Once training is complete, we can run inference by spinning up a policy server and then querying it from a LIBERO evaluation script. Launching a model server is easy (we use the checkpoint for iteration 20,000 for this example, modify as needed):
170
+
171
+ ```bash
172
+ uv run scripts/serve_policy.py policy:checkpoint --policy.config=pi05_libero --policy.dir=checkpoints/pi05_libero/my_experiment/20000
173
+ ```
174
+
175
+ This will spin up a server that listens on port 8000 and waits for observations to be sent to it. We can then run an evaluation script (or robot runtime) that queries the server.
176
+
177
+ For running the LIBERO eval in particular, we provide (and recommend using) a Dockerized workflow that handles both the policy server and the evaluation script together. See the [LIBERO README](examples/libero/README.md) for more details.
178
+
179
+ If you want to embed a policy server call in your own robot runtime, we have a minimal example of how to do so in the [remote inference docs](docs/remote_inference.md).
180
+
181
+
182
+
183
+ ### More Examples
184
+
185
+ We provide more examples for how to fine-tune and run inference with our models on the ALOHA platform in the following READMEs:
186
+ - [ALOHA Simulator](examples/aloha_sim)
187
+ - [ALOHA Real](examples/aloha_real)
188
+ - [UR5](examples/ur5)
189
+
190
+ ## PyTorch Support
191
+
192
+ openpi now provides PyTorch implementations of π₀ and π₀.₅ models alongside the original JAX versions! The PyTorch implementation has been validated on the LIBERO benchmark (both inference and finetuning). A few features are currently not supported (this may change in the future):
193
+
194
+ - The π₀-FAST model
195
+ - Mixed precision training
196
+ - FSDP (fully-sharded data parallelism) training
197
+ - LoRA (low-rank adaptation) training
198
+ - EMA (exponential moving average) weights during training
199
+
200
+ ### Setup
201
+ 1. Make sure that you have the latest version of all dependencies installed: `uv sync`
202
+
203
+ 2. Double check that you have transformers 4.53.2 installed: `uv pip show transformers`
204
+
205
+ 3. Apply the transformers library patches:
206
+ ```bash
207
+ cp -r ./src/openpi/models_pytorch/transformers_replace/* .venv/lib/python3.11/site-packages/transformers/
208
+ ```
209
+
210
+ This overwrites several files in the transformers library with necessary model changes: 1) supporting AdaRMS, 2) correctly controlling the precision of activations, and 3) allowing the KV cache to be used without being updated.
211
+
212
+ **WARNING**: With the default uv link mode (hardlink), this will permanently affect the transformers library in your uv cache, meaning the changes will survive reinstallations of transformers and could even propagate to other projects that use transformers. To fully undo this operation, you must run `uv cache clean transformers`.
213
+
214
+ ### Converting JAX Models to PyTorch
215
+
216
+ To convert a JAX model checkpoint to PyTorch format:
217
+
218
+ ```bash
219
+ uv run examples/convert_jax_model_to_pytorch.py \
220
+ --checkpoint_dir /path/to/jax/checkpoint \
221
+ --config_name <config name> \
222
+ --output_path /path/to/converted/pytorch/checkpoint
223
+ ```
224
+
225
+ ### Running Inference with PyTorch
226
+
227
+ The PyTorch implementation uses the same API as the JAX version - you only need to change the checkpoint path to point to the converted PyTorch model:
228
+
229
+ ```python
230
+ from openpi.training import config as _config
231
+ from openpi.policies import policy_config
232
+ from openpi.shared import download
233
+
234
+ config = _config.get_config("pi05_droid")
235
+ checkpoint_dir = "/path/to/converted/pytorch/checkpoint"
236
+
237
+ # Create a trained policy (automatically detects PyTorch format)
238
+ policy = policy_config.create_trained_policy(config, checkpoint_dir)
239
+
240
+ # Run inference (same API as JAX)
241
+ action_chunk = policy.infer(example)["actions"]
242
+ ```
243
+
244
+ ### Policy Server with PyTorch
245
+
246
+ The policy server works identically with PyTorch models - just point to the converted checkpoint directory:
247
+
248
+ ```bash
249
+ uv run scripts/serve_policy.py policy:checkpoint \
250
+ --policy.config=pi05_droid \
251
+ --policy.dir=/path/to/converted/pytorch/checkpoint
252
+ ```
253
+
254
+ ### Finetuning with PyTorch
255
+
256
+ To finetune a model in PyTorch:
257
+
258
+ 1. Convert the JAX base model to PyTorch format:
259
+ ```bash
260
+ uv run examples/convert_jax_model_to_pytorch.py \
261
+ --config_name <config name> \
262
+ --checkpoint_dir /path/to/jax/base/model \
263
+ --output_path /path/to/pytorch/base/model
264
+ ```
265
+
266
+ 2. Specify the converted PyTorch model path in your config using `pytorch_weight_path`
267
+
268
+ 3. Launch training using one of these modes:
269
+
270
+ ```bash
271
+ # Single GPU training:
272
+ uv run scripts/train_pytorch.py <config_name> --exp_name <run_name> --save_interval <interval>
273
+
274
+ # Example:
275
+ uv run scripts/train_pytorch.py debug --exp_name pytorch_test
276
+ uv run scripts/train_pytorch.py debug --exp_name pytorch_test --resume # Resume from latest checkpoint
277
+
278
+ # Multi-GPU training (single node):
279
+ uv run torchrun --standalone --nnodes=1 --nproc_per_node=<num_gpus> scripts/train_pytorch.py <config_name> --exp_name <run_name>
280
+
281
+ # Example:
282
+ uv run torchrun --standalone --nnodes=1 --nproc_per_node=2 scripts/train_pytorch.py pi0_aloha_sim --exp_name pytorch_ddp_test
283
+ uv run torchrun --standalone --nnodes=1 --nproc_per_node=2 scripts/train_pytorch.py pi0_aloha_sim --exp_name pytorch_ddp_test --resume
284
+
285
+ # Multi-Node Training:
286
+ uv run torchrun \
287
+ --nnodes=<num_nodes> \
288
+ --nproc_per_node=<gpus_per_node> \
289
+ --node_rank=<rank_of_node> \
290
+ --master_addr=<master_ip> \
291
+ --master_port=<port> \
292
+ scripts/train_pytorch.py <config_name> --exp_name=<run_name> --save_interval <interval>
293
+ ```
294
+
295
+ ### Precision Settings
296
+
297
+ JAX and PyTorch implementations handle precision as follows:
298
+
299
+ **JAX:**
300
+ 1. Inference: most weights and computations in bfloat16, with a few computations in float32 for stability
301
+ 2. Training: defaults to mixed precision: weights and gradients in float32, (most) activations and computations in bfloat16. You can change to full float32 training by setting `dtype` to float32 in the config.
302
+
303
+ **PyTorch:**
304
+ 1. Inference: matches JAX -- most weights and computations in bfloat16, with a few weights converted to float32 for stability
305
+ 2. Training: supports either full bfloat16 (default) or full float32. You can change it by setting `pytorch_training_precision` in the config. bfloat16 uses less memory but exhibits higher losses compared to float32. Mixed precision is not yet supported.
306
+
307
+ With torch.compile, inference speed is comparable between JAX and PyTorch.
308
+
309
+ ## Troubleshooting
310
+
311
+ We will collect common issues and their solutions here. If you encounter an issue, please check here first. If you can't find a solution, please file an issue on the repo (see [here](CONTRIBUTING.md) for guidelines).
312
+
313
+ | Issue | Resolution |
314
+ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
315
+ | `uv sync` fails with dependency conflicts | Try removing the virtual environment directory (`rm -rf .venv`) and running `uv sync` again. If issues persist, check that you have the latest version of `uv` installed (`uv self update`). |
316
+ | Training runs out of GPU memory | Make sure you set `XLA_PYTHON_CLIENT_MEM_FRACTION=0.9` (or higher) before running training to allow JAX to use more GPU memory. You can also use `--fsdp-devices <n>` where `<n>` is your number of GPUs, to enable [fully-sharded data parallelism](https://engineering.fb.com/2021/07/15/open-source/fsdp/), which reduces memory usage in exchange for slower training (the amount of slowdown depends on your particular setup). If you are still running out of memory, you may want to consider disabling EMA. |
317
+ | Policy server connection errors | Check that the server is running and listening on the expected port. Verify network connectivity and firewall settings between client and server. |
318
+ | Missing norm stats error when training | Run `scripts/compute_norm_stats.py` with your config name before starting training. |
319
+ | Dataset download fails | Check your internet connection. For HuggingFace datasets, ensure you're logged in (`huggingface-cli login`). |
320
+ | CUDA/GPU errors | Verify NVIDIA drivers are installed correctly. For Docker, ensure nvidia-container-toolkit is installed. Check GPU compatibility. You do NOT need CUDA libraries installed at a system level --- they will be installed via uv. You may even want to try *uninstalling* system CUDA libraries if you run into CUDA issues, since system libraries can sometimes cause conflicts. |
321
+ | Import errors when running examples | Make sure you've installed all dependencies with `uv sync`. Some examples may have additional requirements listed in their READMEs. |
322
+ | Action dimensions mismatch | Verify your data processing transforms match the expected input/output dimensions of your robot. Check the action space definitions in your policy classes. |
323
+ | Diverging training loss | Check the `q01`, `q99`, and `std` values in `norm_stats.json` for your dataset. Certain dimensions that are rarely used can end up with very small `q01`, `q99`, or `std` values, leading to huge states and actions after normalization. You can manually adjust the norm stats as a workaround. |
code/pi05/packages/openpi-client/pyproject.toml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "openpi-client"
3
+ version = "0.1.0"
4
+ requires-python = ">=3.7"
5
+ dependencies = [
6
+ "dm-tree>=0.1.8",
7
+ "msgpack>=1.0.5",
8
+ "numpy>=1.22.4,<2.0.0",
9
+ "pillow>=9.0.0",
10
+ "websockets>=11.0",
11
+ ]
12
+
13
+ [build-system]
14
+ requires = ["hatchling"]
15
+ build-backend = "hatchling.build"
16
+
17
+ [dependency-groups]
18
+ dev = ["pytest>=8.3.4"]
19
+
20
+ [tool.ruff]
21
+ line-length = 120
22
+ target-version = "py37"
code/pi05/packages/openpi-client/src/openpi_client/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ __version__ = "0.1.0"
code/pi05/packages/openpi-client/src/openpi_client/action_chunk_broker.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict
2
+
3
+ import numpy as np
4
+ import tree
5
+ from typing_extensions import override
6
+
7
+ from openpi_client import base_policy as _base_policy
8
+
9
+
10
+ class ActionChunkBroker(_base_policy.BasePolicy):
11
+ """Wraps a policy to return action chunks one-at-a-time.
12
+
13
+ Assumes that the first dimension of all action fields is the chunk size.
14
+
15
+ A new inference call to the inner policy is only made when the current
16
+ list of chunks is exhausted.
17
+ """
18
+
19
+ def __init__(self, policy: _base_policy.BasePolicy, action_horizon: int):
20
+ self._policy = policy
21
+ self._action_horizon = action_horizon
22
+ self._cur_step: int = 0
23
+
24
+ self._last_results: Dict[str, np.ndarray] | None = None
25
+
26
+ @override
27
+ def infer(self, obs: Dict) -> Dict: # noqa: UP006
28
+ if self._last_results is None:
29
+ self._last_results = self._policy.infer(obs)
30
+ self._cur_step = 0
31
+
32
+ def slicer(x):
33
+ if isinstance(x, np.ndarray):
34
+ return x[self._cur_step, ...]
35
+ else:
36
+ return x
37
+
38
+ results = tree.map_structure(slicer, self._last_results)
39
+ self._cur_step += 1
40
+
41
+ if self._cur_step >= self._action_horizon:
42
+ self._last_results = None
43
+
44
+ return results
45
+
46
+ @override
47
+ def reset(self) -> None:
48
+ self._policy.reset()
49
+ self._last_results = None
50
+ self._cur_step = 0
code/pi05/packages/openpi-client/src/openpi_client/base_policy.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import abc
2
+ from typing import Dict
3
+
4
+
5
+ class BasePolicy(abc.ABC):
6
+ @abc.abstractmethod
7
+ def infer(self, obs: Dict) -> Dict:
8
+ """Infer actions from observations."""
9
+
10
+ def reset(self) -> None:
11
+ """Reset the policy to its initial state."""
12
+ pass
code/pi05/packages/openpi-client/src/openpi_client/image_tools.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from PIL import Image
3
+
4
+
5
+ def convert_to_uint8(img: np.ndarray) -> np.ndarray:
6
+ """Converts an image to uint8 if it is a float image.
7
+
8
+ This is important for reducing the size of the image when sending it over the network.
9
+ """
10
+ if np.issubdtype(img.dtype, np.floating):
11
+ img = (255 * img).astype(np.uint8)
12
+ return img
13
+
14
+
15
+ def resize_with_pad(images: np.ndarray, height: int, width: int, method=Image.BILINEAR) -> np.ndarray:
16
+ """Replicates tf.image.resize_with_pad for multiple images using PIL. Resizes a batch of images to a target height.
17
+
18
+ Args:
19
+ images: A batch of images in [..., height, width, channel] format.
20
+ height: The target height of the image.
21
+ width: The target width of the image.
22
+ method: The interpolation method to use. Default is bilinear.
23
+
24
+ Returns:
25
+ The resized images in [..., height, width, channel].
26
+ """
27
+ # If the images are already the correct size, return them as is.
28
+ if images.shape[-3:-1] == (height, width):
29
+ return images
30
+
31
+ original_shape = images.shape
32
+
33
+ images = images.reshape(-1, *original_shape[-3:])
34
+ resized = np.stack([_resize_with_pad_pil(Image.fromarray(im), height, width, method=method) for im in images])
35
+ return resized.reshape(*original_shape[:-3], *resized.shape[-3:])
36
+
37
+
38
+ def _resize_with_pad_pil(image: Image.Image, height: int, width: int, method: int) -> Image.Image:
39
+ """Replicates tf.image.resize_with_pad for one image using PIL. Resizes an image to a target height and
40
+ width without distortion by padding with zeros.
41
+
42
+ Unlike the jax version, note that PIL uses [width, height, channel] ordering instead of [batch, h, w, c].
43
+ """
44
+ cur_width, cur_height = image.size
45
+ if cur_width == width and cur_height == height:
46
+ return image # No need to resize if the image is already the correct size.
47
+
48
+ ratio = max(cur_width / width, cur_height / height)
49
+ resized_height = int(cur_height / ratio)
50
+ resized_width = int(cur_width / ratio)
51
+ resized_image = image.resize((resized_width, resized_height), resample=method)
52
+
53
+ zero_image = Image.new(resized_image.mode, (width, height), 0)
54
+ pad_height = max(0, int((height - resized_height) / 2))
55
+ pad_width = max(0, int((width - resized_width) / 2))
56
+ zero_image.paste(resized_image, (pad_width, pad_height))
57
+ assert zero_image.size == (width, height)
58
+ return zero_image
code/pi05/packages/openpi-client/src/openpi_client/image_tools_test.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ import openpi_client.image_tools as image_tools
4
+
5
+
6
+ def test_resize_with_pad_shapes():
7
+ # Test case 1: Resize image with larger dimensions
8
+ images = np.zeros((2, 10, 10, 3), dtype=np.uint8) # Input images of shape (batch_size, height, width, channels)
9
+ height = 20
10
+ width = 20
11
+ resized_images = image_tools.resize_with_pad(images, height, width)
12
+ assert resized_images.shape == (2, height, width, 3)
13
+ assert np.all(resized_images == 0)
14
+
15
+ # Test case 2: Resize image with smaller dimensions
16
+ images = np.zeros((3, 30, 30, 3), dtype=np.uint8)
17
+ height = 15
18
+ width = 15
19
+ resized_images = image_tools.resize_with_pad(images, height, width)
20
+ assert resized_images.shape == (3, height, width, 3)
21
+ assert np.all(resized_images == 0)
22
+
23
+ # Test case 3: Resize image with the same dimensions
24
+ images = np.zeros((1, 50, 50, 3), dtype=np.uint8)
25
+ height = 50
26
+ width = 50
27
+ resized_images = image_tools.resize_with_pad(images, height, width)
28
+ assert resized_images.shape == (1, height, width, 3)
29
+ assert np.all(resized_images == 0)
30
+
31
+ # Test case 3: Resize image with odd-numbered padding
32
+ images = np.zeros((1, 256, 320, 3), dtype=np.uint8)
33
+ height = 60
34
+ width = 80
35
+ resized_images = image_tools.resize_with_pad(images, height, width)
36
+ assert resized_images.shape == (1, height, width, 3)
37
+ assert np.all(resized_images == 0)
code/pi05/packages/openpi-client/src/openpi_client/msgpack_numpy.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Adds NumPy array support to msgpack.
2
+
3
+ msgpack is good for (de)serializing data over a network for multiple reasons:
4
+ - msgpack is secure (as opposed to pickle/dill/etc which allow for arbitrary code execution)
5
+ - msgpack is widely used and has good cross-language support
6
+ - msgpack does not require a schema (as opposed to protobuf/flatbuffers/etc) which is convenient in dynamically typed
7
+ languages like Python and JavaScript
8
+ - msgpack is fast and efficient (as opposed to readable formats like JSON/YAML/etc); I found that msgpack was ~4x faster
9
+ than pickle for serializing large arrays using the below strategy
10
+
11
+ The code below is adapted from https://github.com/lebedov/msgpack-numpy. The reason not to use that library directly is
12
+ that it falls back to pickle for object arrays.
13
+ """
14
+
15
+ import functools
16
+
17
+ import msgpack
18
+ import numpy as np
19
+
20
+
21
+ def pack_array(obj):
22
+ if (isinstance(obj, (np.ndarray, np.generic))) and obj.dtype.kind in ("V", "O", "c"):
23
+ raise ValueError(f"Unsupported dtype: {obj.dtype}")
24
+
25
+ if isinstance(obj, np.ndarray):
26
+ return {
27
+ b"__ndarray__": True,
28
+ b"data": obj.tobytes(),
29
+ b"dtype": obj.dtype.str,
30
+ b"shape": obj.shape,
31
+ }
32
+
33
+ if isinstance(obj, np.generic):
34
+ return {
35
+ b"__npgeneric__": True,
36
+ b"data": obj.item(),
37
+ b"dtype": obj.dtype.str,
38
+ }
39
+
40
+ return obj
41
+
42
+
43
+ def unpack_array(obj):
44
+ if b"__ndarray__" in obj:
45
+ return np.ndarray(buffer=obj[b"data"], dtype=np.dtype(obj[b"dtype"]), shape=obj[b"shape"])
46
+
47
+ if b"__npgeneric__" in obj:
48
+ return np.dtype(obj[b"dtype"]).type(obj[b"data"])
49
+
50
+ return obj
51
+
52
+
53
+ Packer = functools.partial(msgpack.Packer, default=pack_array)
54
+ packb = functools.partial(msgpack.packb, default=pack_array)
55
+
56
+ Unpacker = functools.partial(msgpack.Unpacker, object_hook=unpack_array)
57
+ unpackb = functools.partial(msgpack.unpackb, object_hook=unpack_array)
code/pi05/packages/openpi-client/src/openpi_client/msgpack_numpy_test.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pytest
3
+ import tree
4
+
5
+ from openpi_client import msgpack_numpy
6
+
7
+
8
+ def _check(expected, actual):
9
+ if isinstance(expected, np.ndarray):
10
+ assert expected.shape == actual.shape
11
+ assert expected.dtype == actual.dtype
12
+ assert np.array_equal(expected, actual, equal_nan=expected.dtype.kind == "f")
13
+ else:
14
+ assert expected == actual
15
+
16
+
17
+ @pytest.mark.parametrize(
18
+ "data",
19
+ [
20
+ 1, # int
21
+ 1.0, # float
22
+ "hello", # string
23
+ np.bool_(True), # boolean scalar
24
+ np.array([1, 2, 3])[0], # int scalar
25
+ np.str_("asdf"), # string scalar
26
+ [1, 2, 3], # list
27
+ {"key": "value"}, # dict
28
+ {"key": [1, 2, 3]}, # nested dict
29
+ np.array(1.0), # 0D array
30
+ np.array([1, 2, 3], dtype=np.int32), # 1D integer array
31
+ np.array(["asdf", "qwer"]), # string array
32
+ np.array([True, False]), # boolean array
33
+ np.array([[1.0, 2.0], [3.0, 4.0]], dtype=np.float32), # 2D float array
34
+ np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]], dtype=np.int16), # 3D integer array
35
+ np.array([np.nan, np.inf, -np.inf]), # special float values
36
+ {"arr": np.array([1, 2, 3]), "nested": {"arr": np.array([4, 5, 6])}}, # nested dict with arrays
37
+ [np.array([1, 2]), np.array([3, 4])], # list of arrays
38
+ np.zeros((3, 4, 5), dtype=np.float32), # 3D zeros
39
+ np.ones((2, 3), dtype=np.float64), # 2D ones with double precision
40
+ ],
41
+ )
42
+ def test_pack_unpack(data):
43
+ packed = msgpack_numpy.packb(data)
44
+ unpacked = msgpack_numpy.unpackb(packed)
45
+ tree.map_structure(_check, data, unpacked)
code/pi05/packages/openpi-client/src/openpi_client/runtime/agent.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import abc
2
+
3
+
4
+ class Agent(abc.ABC):
5
+ """An Agent is the thing with agency, i.e. the entity that makes decisions.
6
+
7
+ Agents receive observations about the state of the world, and return actions
8
+ to take in response.
9
+ """
10
+
11
+ @abc.abstractmethod
12
+ def get_action(self, observation: dict) -> dict:
13
+ """Query the agent for the next action."""
14
+
15
+ @abc.abstractmethod
16
+ def reset(self) -> None:
17
+ """Reset the agent to its initial state."""
code/pi05/packages/openpi-client/src/openpi_client/runtime/agents/policy_agent.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing_extensions import override
2
+
3
+ from openpi_client import base_policy as _base_policy
4
+ from openpi_client.runtime import agent as _agent
5
+
6
+
7
+ class PolicyAgent(_agent.Agent):
8
+ """An agent that uses a policy to determine actions."""
9
+
10
+ def __init__(self, policy: _base_policy.BasePolicy) -> None:
11
+ self._policy = policy
12
+
13
+ @override
14
+ def get_action(self, observation: dict) -> dict:
15
+ return self._policy.infer(observation)
16
+
17
+ def reset(self) -> None:
18
+ self._policy.reset()
code/pi05/packages/openpi-client/src/openpi_client/runtime/environment.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import abc
2
+
3
+
4
+ class Environment(abc.ABC):
5
+ """An Environment represents the robot and the environment it inhabits.
6
+
7
+ The primary contract of environments is that they can be queried for observations
8
+ about their state, and have actions applied to them to change that state.
9
+ """
10
+
11
+ @abc.abstractmethod
12
+ def reset(self) -> None:
13
+ """Reset the environment to its initial state.
14
+
15
+ This will be called once before starting each episode.
16
+ """
17
+
18
+ @abc.abstractmethod
19
+ def is_episode_complete(self) -> bool:
20
+ """Allow the environment to signal that the episode is complete.
21
+
22
+ This will be called after each step. It should return `True` if the episode is
23
+ complete (either successfully or unsuccessfully), and `False` otherwise.
24
+ """
25
+
26
+ @abc.abstractmethod
27
+ def get_observation(self) -> dict:
28
+ """Query the environment for the current state."""
29
+
30
+ @abc.abstractmethod
31
+ def apply_action(self, action: dict) -> None:
32
+ """Take an action in the environment."""
code/pi05/packages/openpi-client/src/openpi_client/runtime/runtime.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import threading
3
+ import time
4
+
5
+ from openpi_client.runtime import agent as _agent
6
+ from openpi_client.runtime import environment as _environment
7
+ from openpi_client.runtime import subscriber as _subscriber
8
+
9
+
10
+ class Runtime:
11
+ """The core module orchestrating interactions between key components of the system."""
12
+
13
+ def __init__(
14
+ self,
15
+ environment: _environment.Environment,
16
+ agent: _agent.Agent,
17
+ subscribers: list[_subscriber.Subscriber],
18
+ max_hz: float = 0,
19
+ num_episodes: int = 1,
20
+ max_episode_steps: int = 0,
21
+ ) -> None:
22
+ self._environment = environment
23
+ self._agent = agent
24
+ self._subscribers = subscribers
25
+ self._max_hz = max_hz
26
+ self._num_episodes = num_episodes
27
+ self._max_episode_steps = max_episode_steps
28
+
29
+ self._in_episode = False
30
+ self._episode_steps = 0
31
+
32
+ def run(self) -> None:
33
+ """Runs the runtime loop continuously until stop() is called or the environment is done."""
34
+ for _ in range(self._num_episodes):
35
+ self._run_episode()
36
+
37
+ # Final reset, this is important for real environments to move the robot to its home position.
38
+ self._environment.reset()
39
+
40
+ def run_in_new_thread(self) -> threading.Thread:
41
+ """Runs the runtime loop in a new thread."""
42
+ thread = threading.Thread(target=self.run)
43
+ thread.start()
44
+ return thread
45
+
46
+ def mark_episode_complete(self) -> None:
47
+ """Marks the end of an episode."""
48
+ self._in_episode = False
49
+
50
+ def _run_episode(self) -> None:
51
+ """Runs a single episode."""
52
+ logging.info("Starting episode...")
53
+ self._environment.reset()
54
+ self._agent.reset()
55
+ for subscriber in self._subscribers:
56
+ subscriber.on_episode_start()
57
+
58
+ self._in_episode = True
59
+ self._episode_steps = 0
60
+ step_time = 1 / self._max_hz if self._max_hz > 0 else 0
61
+ last_step_time = time.time()
62
+
63
+ while self._in_episode:
64
+ self._step()
65
+ self._episode_steps += 1
66
+
67
+ # Sleep to maintain the desired frame rate
68
+ now = time.time()
69
+ dt = now - last_step_time
70
+ if dt < step_time:
71
+ time.sleep(step_time - dt)
72
+ last_step_time = time.time()
73
+ else:
74
+ last_step_time = now
75
+
76
+ logging.info("Episode completed.")
77
+ for subscriber in self._subscribers:
78
+ subscriber.on_episode_end()
79
+
80
+ def _step(self) -> None:
81
+ """A single step of the runtime loop."""
82
+ observation = self._environment.get_observation()
83
+ action = self._agent.get_action(observation)
84
+ self._environment.apply_action(action)
85
+
86
+ for subscriber in self._subscribers:
87
+ subscriber.on_step(observation, action)
88
+
89
+ if self._environment.is_episode_complete() or (
90
+ self._max_episode_steps > 0 and self._episode_steps >= self._max_episode_steps
91
+ ):
92
+ self.mark_episode_complete()
code/pi05/packages/openpi-client/src/openpi_client/runtime/subscriber.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import abc
2
+
3
+
4
+ class Subscriber(abc.ABC):
5
+ """Subscribes to events in the runtime.
6
+
7
+ Subscribers can be used to save data, visualize, etc.
8
+ """
9
+
10
+ @abc.abstractmethod
11
+ def on_episode_start(self) -> None:
12
+ """Called when an episode starts."""
13
+
14
+ @abc.abstractmethod
15
+ def on_step(self, observation: dict, action: dict) -> None:
16
+ """Append a step to the episode."""
17
+
18
+ @abc.abstractmethod
19
+ def on_episode_end(self) -> None:
20
+ """Called when an episode ends."""
code/pi05/packages/openpi-client/src/openpi_client/websocket_client_policy.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import time
3
+ from typing import Dict, Optional, Tuple
4
+
5
+ from typing_extensions import override
6
+ import websockets.sync.client
7
+
8
+ from openpi_client import base_policy as _base_policy
9
+ from openpi_client import msgpack_numpy
10
+
11
+
12
+ class WebsocketClientPolicy(_base_policy.BasePolicy):
13
+ """Implements the Policy interface by communicating with a server over websocket.
14
+
15
+ See WebsocketPolicyServer for a corresponding server implementation.
16
+ """
17
+
18
+ def __init__(self, host: str = "0.0.0.0", port: Optional[int] = None, api_key: Optional[str] = None) -> None:
19
+ if host.startswith("ws"):
20
+ self._uri = host
21
+ else:
22
+ self._uri = f"ws://{host}"
23
+ if port is not None:
24
+ self._uri += f":{port}"
25
+ self._packer = msgpack_numpy.Packer()
26
+ self._api_key = api_key
27
+ self._ws, self._server_metadata = self._wait_for_server()
28
+
29
+ def get_server_metadata(self) -> Dict:
30
+ return self._server_metadata
31
+
32
+ def _wait_for_server(self) -> Tuple[websockets.sync.client.ClientConnection, Dict]:
33
+ logging.info(f"Waiting for server at {self._uri}...")
34
+ while True:
35
+ try:
36
+ headers = {"Authorization": f"Api-Key {self._api_key}"} if self._api_key else None
37
+ conn = websockets.sync.client.connect(
38
+ self._uri, compression=None, max_size=None, additional_headers=headers
39
+ )
40
+ metadata = msgpack_numpy.unpackb(conn.recv())
41
+ return conn, metadata
42
+ except ConnectionRefusedError:
43
+ logging.info("Still waiting for server...")
44
+ time.sleep(5)
45
+
46
+ @override
47
+ def infer(self, obs: Dict) -> Dict: # noqa: UP006
48
+ data = self._packer.pack(obs)
49
+ self._ws.send(data)
50
+ response = self._ws.recv()
51
+ if isinstance(response, str):
52
+ # we're expecting bytes; if the server sends a string, it's an error.
53
+ raise RuntimeError(f"Error in inference server:\n{response}")
54
+ return msgpack_numpy.unpackb(response)
55
+
56
+ @override
57
+ def reset(self) -> None:
58
+ pass
code/pi05/pyproject.toml ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "openpi"
3
+ version = "0.1.0"
4
+ description = "Physical Intelligence open source repo"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { file = "LICENSE" }
8
+ dependencies = [
9
+ "augmax>=0.3.4",
10
+ "dm-tree>=0.1.8",
11
+ "einops>=0.8.0",
12
+ "equinox>=0.11.8",
13
+ "flatbuffers>=24.3.25",
14
+ "flax==0.10.2",
15
+ "fsspec[gcs]>=2024.6.0",
16
+ "gym-aloha>=0.1.1",
17
+ "imageio>=2.36.1",
18
+ "jax[cuda12]==0.5.3",
19
+ "jaxtyping==0.2.36",
20
+ "lerobot",
21
+ "ml_collections==1.0.0",
22
+ "numpy>=1.22.4,<2.0.0",
23
+ "numpydantic>=1.6.6",
24
+ "opencv-python>=4.10.0.84",
25
+ "openpi-client",
26
+ "orbax-checkpoint==0.11.13",
27
+ "pillow>=11.0.0",
28
+ "sentencepiece>=0.2.0",
29
+ "torch==2.7.1",
30
+ "tqdm-loggable>=0.2",
31
+ "typing-extensions>=4.12.2",
32
+ "tyro>=0.9.5",
33
+ "wandb>=0.19.1",
34
+ "filelock>=3.16.1",
35
+ "beartype==0.19.0",
36
+ "chex==0.1.90",
37
+ "treescope>=0.1.7",
38
+ "transformers==4.53.2",
39
+ "rich>=14.0.0",
40
+ "polars>=1.30.0",
41
+ ]
42
+
43
+
44
+ [project.urls]
45
+ Repository = "https://github.com/Physical-Intelligence/openpi"
46
+
47
+ [dependency-groups]
48
+ dev = [
49
+ "pytest>=8.3.4",
50
+ "ruff>=0.8.6",
51
+ "pre-commit>=4.0.1",
52
+ "ipykernel>=6.29.5",
53
+ "ipywidgets>=8.1.5",
54
+ "matplotlib>=3.10.0",
55
+ "pynvml>=12.0.0",
56
+ ]
57
+ rlds = [
58
+ # NOTE: tensorflow-cpu 2.15.0 only ships cp311 wheels on PyPI.
59
+ # Use `uv venv --python 3.11` before `uv sync --group rlds`.
60
+ "dlimp",
61
+ "tensorflow-cpu==2.15.0",
62
+ "tensorflow-datasets==4.9.9",
63
+ ]
64
+
65
+ [tool.uv]
66
+ override-dependencies = ["ml-dtypes==0.4.1", "tensorstore==0.1.74"]
67
+
68
+ [tool.uv.sources]
69
+ openpi-client = { workspace = true }
70
+ lerobot = { git = "https://github.com/huggingface/lerobot", rev = "0cf864870cf29f4738d3ade893e6fd13fbd7cdb5" }
71
+ dlimp = { git = "https://github.com/kvablack/dlimp", rev = "ad72ce3a9b414db2185bc0b38461d4101a65477a" }
72
+
73
+ [tool.uv.workspace]
74
+ members = ["packages/*"]
75
+
76
+ [tool.ruff]
77
+ line-length = 120
78
+ target-version = "py311"
79
+ extend-exclude = ["docker", "third_party", "src/openpi/models_pytorch/transformers_replace/*"]
80
+
81
+ [tool.ruff.lint]
82
+ # https://docs.astral.sh/ruff/rules/
83
+ select = [
84
+ "B",
85
+ "C4",
86
+ "DTZ",
87
+ "E4",
88
+ "E7",
89
+ "E9",
90
+ "F",
91
+ "FBT",
92
+ "FURB",
93
+ "I",
94
+ "ICN",
95
+ "ISC",
96
+ "LOG",
97
+ "N",
98
+ "PD",
99
+ "PERF",
100
+ "PIE",
101
+ "PLC",
102
+ "PLE",
103
+ "PLR1",
104
+ "PLR5",
105
+ "PLW",
106
+ "PT",
107
+ "Q",
108
+ "RET",
109
+ "RUF",
110
+ "SIM",
111
+ "SLF",
112
+ "T10",
113
+ "T20",
114
+ "UP",
115
+ "W",
116
+ ]
117
+ ignore = [
118
+ "F722", # Conflicts with array typing.
119
+ "T201", # We use print statements.
120
+ "PD008", # Lots of false positives.
121
+ "ISC001", # Disabling to support ruff format.
122
+ "LOG015", # Use logger.info.
123
+ ]
124
+ unfixable = [
125
+ "B905", # Fix defaults to strict=False, which is not what we want.
126
+ ]
127
+
128
+ [tool.ruff.lint.isort]
129
+ force-single-line = true
130
+ force-sort-within-sections = true
131
+ single-line-exclusions = ["collections.abc", "typing", "typing_extensions"]
132
+ known-third-party = ["wandb"]
133
+
134
+ [build-system]
135
+ requires = ["hatchling"]
136
+ build-backend = "hatchling.build"
137
+
138
+ [tool.pytest.ini_options]
139
+ markers = ["manual: should be run manually."]
140
+ testpaths = ["src", "scripts", "packages"]
code/pi05/scripts/__init__.py ADDED
File without changes
code/pi05/scripts/check_robomem_memory_graph.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compile both phases without loading weights or executing training updates."""
2
+
3
+ import json
4
+ import logging
5
+ from pathlib import Path
6
+ import time
7
+
8
+ import jax
9
+ import numpy as np
10
+
11
+ import train_robomem_memory as trainer
12
+ from openpi.shared import array_typing as at
13
+ from openpi.training import data_loader, sharding
14
+
15
+
16
+ def main():
17
+ logging.basicConfig(level=logging.INFO, force=True)
18
+ args = trainer.build_argparser().parse_args()
19
+ cfg = trainer.recipe(args)
20
+ mesh = sharding.make_mesh(args.fsdp_devices)
21
+ state, state_sharding = trainer.init_state(cfg, args, mesh, True)
22
+ index = trainer.EpisodeIndex(args.dataset)
23
+ dataset = trainer.MemoryDataset(index, cfg.data.create(cfg.assets_dirs, cfg.model), cfg.model, args.cache)
24
+ ep = index.groups[sorted(index.groups)[0]][0]
25
+ pos = index.episodes.index(ep)
26
+ sample_index = int(index.ends[pos - 1]) if pos else 0
27
+ batch = data_loader._collate_fn([dataset[sample_index]] * args.batch_size)
28
+ batch = jax.tree.map(lambda a: jax.ShapeDtypeStruct(a.shape, a.dtype), batch)
29
+ data_sharding = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec(sharding.DATA_AXIS))
30
+ replicated = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec())
31
+ report = {}
32
+ with sharding.set_mesh(mesh):
33
+ for warmup in (True, False):
34
+ name = "adapter_only" if warmup else "joint_frozen_vision"
35
+ started = time.monotonic()
36
+ fn = jax.jit(
37
+ lambda s, b: trainer.train_step(s, b, warmup=warmup),
38
+ in_shardings=(state_sharding, data_sharding),
39
+ out_shardings=(state_sharding, replicated),
40
+ donate_argnums=(0,),
41
+ )
42
+ with at.disable_typechecking():
43
+ executable = fn.lower(state, batch).compile()
44
+ memory = executable.memory_analysis()
45
+ report[name] = dict(
46
+ seconds=time.monotonic() - started,
47
+ arguments_bytes=memory.argument_size_in_bytes,
48
+ output_bytes=memory.output_size_in_bytes,
49
+ temporary_bytes=memory.temp_size_in_bytes,
50
+ alias_bytes=memory.alias_size_in_bytes,
51
+ )
52
+ print(name, json.dumps(report[name]), flush=True)
53
+ report["action_horizon"] = cfg.model.action_horizon
54
+ report["execution_horizon"] = args.execution_horizon
55
+ Path(f"logs/robomem_memory_spatial_a{args.action_horizon}_compile_check.json").write_text(json.dumps(report, indent=2))
56
+
57
+
58
+ if __name__ == "__main__":
59
+ main()
code/pi05/scripts/evaluate_robomem_memory.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run selected official tasks and persist coverage, exclusions, failures and CSR/TSR."""
2
+
3
+ import argparse
4
+ import concurrent.futures
5
+ import hashlib
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import subprocess
10
+ import threading
11
+ import time
12
+ import urllib.request
13
+
14
+ from openpi_client import msgpack_numpy
15
+ from openpi.policies.robomem_progress import PROGRESS_MODE
16
+ from robomem_eval_paths import ROOT, BENCHMARK, POLICY_PYTHON
17
+ from taskanchor.data.history import validate_robomem_recipe
18
+ from run_robomem_eval_task import run_task
19
+ from robomem_seed_search import run_task25
20
+ import websockets.sync.client
21
+
22
+
23
+ def verify_server_checkpoint(port, checkpoint):
24
+ """Reject a stale policy server before sending any evaluation observations."""
25
+ with websockets.sync.client.connect(
26
+ f"ws://127.0.0.1:{port}", compression=None, max_size=None, open_timeout=10
27
+ ) as connection:
28
+ metadata = msgpack_numpy.unpackb(connection.recv(timeout=10))
29
+ actual = metadata.get("checkpoint")
30
+ if not actual or Path(actual).resolve() != Path(checkpoint).resolve():
31
+ raise RuntimeError(f"Policy server {port} checkpoint mismatch: expected {checkpoint}, got {actual}")
32
+ return metadata
33
+
34
+
35
+ def main(args):
36
+ tasks = [task for task in range(1, 27) if task not in args.skip_tasks]
37
+ if not tasks or args.trials < 1:
38
+ raise ValueError("Evaluation requires at least one task and one trial")
39
+ task25_candidates = json.loads(Path(args.task25_seeds).read_text())["candidates"] if args.task25_seeds else None
40
+ if task25_candidates is not None and (not task25_candidates or len(task25_candidates) > 200
41
+ or len(task25_candidates) != len(set(task25_candidates))):
42
+ raise ValueError("Task25 requires 1 to 200 distinct candidate seeds")
43
+ if args.wait_smoke:
44
+ smoke = Path(args.wait_smoke).resolve()
45
+ print(f"Waiting for closed-loop smoke result: {smoke}", flush=True)
46
+ deadline = time.monotonic() + 3600
47
+ while not (smoke / "aggregate.json").exists():
48
+ log = (smoke / "run.log").read_text() if (smoke / "run.log").exists() else ""
49
+ if "Traceback (most recent call last)" in log or "Episode failed:" in log:
50
+ raise RuntimeError("Closed-loop smoke encountered a runtime error; refusing full evaluation")
51
+ if time.monotonic() > deadline:
52
+ raise TimeoutError("Closed-loop smoke did not finish within one hour")
53
+ time.sleep(5)
54
+ if json.loads((smoke / "aggregate.json").read_text())["num_episodes"] != 1:
55
+ raise RuntimeError("Incomplete closed-loop smoke")
56
+ log = (smoke / "run.log").read_text()
57
+ if "Traceback (most recent call last)" in log or "Episode failed:" in log:
58
+ raise RuntimeError("Closed-loop smoke failed")
59
+ print("Closed-loop smoke passed; starting 26-task evaluation", flush=True)
60
+ checkpoint, output = Path(args.checkpoint).resolve(), Path(args.output).resolve()
61
+ recipe = json.loads((checkpoint.parent / "recipe.json").read_text())
62
+ validate_robomem_recipe(recipe)
63
+ action_horizon = recipe["model"]["action_horizon"]
64
+ replan_steps = recipe.get("execution_horizon", 5)
65
+ if not 0 < replan_steps <= action_horizon or recipe["history_stride"] % replan_steps:
66
+ raise ValueError("Checkpoint has incompatible execution and history settings")
67
+ allowed_trials = (3, 10) if args.reference_prefix else (10,)
68
+ if args.trials not in allowed_trials or args.skip_tasks:
69
+ raise ValueError("Use all26 tasks and10 trials, or --reference-prefix --trials 3 for seeds50–52")
70
+ output.mkdir(parents=True, exist_ok=False)
71
+ metadata = dict(
72
+ checkpoint=str(checkpoint),
73
+ tasks=tasks,
74
+ skipped_tasks=sorted(set(args.skip_tasks)),
75
+ task25_seed_candidates=task25_candidates,
76
+ seeds=list(range(50, 50 + args.trials)),
77
+ expected_episodes=len(tasks) * args.trials,
78
+ reference_prefix_check=args.reference_prefix,
79
+ action_horizon=action_horizon,
80
+ replan_steps=replan_steps,
81
+ history_stride=recipe["history_stride"],
82
+ max_steps=2500,
83
+ renderer="osmesa",
84
+ instruction="fixed full-task rewrite 0 from training instruction table",
85
+ progress=PROGRESS_MODE,
86
+ base_port=args.base_port,
87
+ launch_gpu0=args.launch_gpu0,
88
+ benchmark=json.loads((BENCHMARK.parent / "OFFICIAL_SOURCE.json").read_text()),
89
+ code_sha256={
90
+ str(p.relative_to(ROOT)): hashlib.sha256(p.read_bytes()).hexdigest()
91
+ for p in [
92
+ ROOT / "src/openpi/policies/robomem_memory_inference.py",
93
+ ROOT / "src/openpi/policies/robomem_progress.py",
94
+ ROOT / "src/openpi/robomemarena_26tasks_subtask_prompts.py",
95
+ ROOT / "scripts/robomem_memory_eval_adapter.py",
96
+ ROOT / "scripts/run_robomem_eval_task.py",
97
+ ROOT / "scripts/run_robomem_task25_seed.py",
98
+ ROOT / "scripts/robomem_seed_search.py",
99
+ ROOT / "scripts/robomem_eval_paths.py",
100
+ Path(__file__).resolve(),
101
+ ]
102
+ },
103
+ )
104
+ metadata["recipe_sha256"] = hashlib.sha256((checkpoint.parent / "recipe.json").read_bytes()).hexdigest()
105
+ metadata["norm_stats_sha256"] = hashlib.sha256(
106
+ (checkpoint / "assets/pi05_libero/norm_stats.json").read_bytes()
107
+ ).hexdigest()
108
+ metadata["taskanchor_code_sha256"] = {
109
+ rel: hashlib.sha256((ROOT.parent / rel).read_bytes()).hexdigest()
110
+ for rel in ("taskanchor/pi05/taskanchor.py", "taskanchor/pi05/ttt.py",
111
+ "taskanchor/data/history.py")
112
+ }
113
+ (output / "evaluation_config.json").write_text(json.dumps(metadata, indent=2))
114
+ lock = threading.Lock()
115
+ completed, failures, running = {}, {}, {}
116
+
117
+ def write_status():
118
+ result = dict(
119
+ expected_tasks=len(tasks),
120
+ expected_episodes=len(tasks) * args.trials,
121
+ skipped_tasks=metadata["skipped_tasks"],
122
+ completed_tasks=sorted(completed),
123
+ failed_tasks=failures,
124
+ running_tasks=running,
125
+ completed_episodes=sum(r["num_episodes"] for r in completed.values()),
126
+ )
127
+ scored = [r for r in completed.values() if r["num_episodes"] > 0]
128
+ if scored:
129
+ result.update(
130
+ TSR=sum(r["TSR"] for r in scored) / len(scored),
131
+ CSR=sum(r["CSR"] for r in scored) / len(scored),
132
+ )
133
+ temp = output / "status.tmp.json"
134
+ temp.write_text(json.dumps(result, indent=2))
135
+ temp.replace(output / "status.json")
136
+
137
+ def worker(gpu):
138
+ assigned_tasks = [task for task in range(gpu + 1, 27, args.workers) if task in tasks]
139
+ if not assigned_tasks:
140
+ return
141
+ port, server = args.base_port + gpu, None
142
+ if gpu != 0 or args.launch_gpu0:
143
+ env = dict(
144
+ os.environ,
145
+ CUDA_VISIBLE_DEVICES=str(gpu),
146
+ JAX_PLATFORMS="cuda",
147
+ XLA_PYTHON_CLIENT_PREALLOCATE="false",
148
+ OMP_NUM_THREADS="1",
149
+ OPENBLAS_NUM_THREADS="1",
150
+ PYTHONPYCACHEPREFIX="/tmp/robomem_memory_pycache",
151
+ )
152
+ with (output / f"server_gpu{gpu}.log").open("w") as log:
153
+ server = subprocess.Popen(
154
+ [
155
+ str(POLICY_PYTHON),
156
+ "-u",
157
+ "scripts/serve_robomem_memory.py",
158
+ "--checkpoint",
159
+ str(checkpoint),
160
+ "--port",
161
+ str(port),
162
+ ],
163
+ cwd=ROOT,
164
+ env=env,
165
+ stdout=log,
166
+ stderr=subprocess.STDOUT,
167
+ stdin=subprocess.DEVNULL,
168
+ )
169
+ (output / f"server_gpu{gpu}.pid").write_text(str(server.pid))
170
+ try:
171
+ deadline = time.monotonic() + 300
172
+ while True:
173
+ try:
174
+ with urllib.request.urlopen(f"http://127.0.0.1:{port}/healthz", timeout=2) as response:
175
+ if response.status == 200:
176
+ break
177
+ except OSError:
178
+ if (server and server.poll() is not None) or time.monotonic() > deadline:
179
+ raise RuntimeError(f"GPU {gpu} policy server unavailable")
180
+ time.sleep(2)
181
+ server_metadata = verify_server_checkpoint(port, checkpoint)
182
+ if server_metadata["action_horizon"] != action_horizon:
183
+ raise RuntimeError("Server action horizon does not match the checkpoint recipe")
184
+ if server_metadata.get("progress") != PROGRESS_MODE:
185
+ raise RuntimeError("Server progress mode does not match evaluation protocol")
186
+ (output / f"server_gpu{gpu}_metadata.json").write_text(json.dumps(server_metadata, indent=2))
187
+ for task in assigned_tasks:
188
+ with lock:
189
+ running[gpu] = task
190
+ write_status()
191
+ print(f"START gpu={gpu} task={task}", flush=True)
192
+ try:
193
+ if task == 25 and task25_candidates is not None:
194
+ result = run_task25(gpu, port, args.trials, task25_candidates,
195
+ output / "task25", replan_steps)
196
+ else:
197
+ result = run_task(task, gpu, port, args.trials, 50, output / f"task{task:02d}",
198
+ replan_steps=replan_steps)
199
+ with lock:
200
+ completed[task] = result
201
+ print(f"COMPLETE task={task} {json.dumps(result)}", flush=True)
202
+ except Exception as exc:
203
+ with lock:
204
+ failures[task] = str(exc)
205
+ print(f"ERROR task={task}: {exc}", flush=True)
206
+ finally:
207
+ with lock:
208
+ running.pop(gpu, None)
209
+ write_status()
210
+ except Exception as exc:
211
+ with lock:
212
+ for task in assigned_tasks:
213
+ if task not in completed:
214
+ failures[task] = str(exc)
215
+ write_status()
216
+ finally:
217
+ if server:
218
+ server.terminate()
219
+ try:
220
+ server.wait(timeout=30)
221
+ except subprocess.TimeoutExpired:
222
+ server.kill()
223
+ server.wait()
224
+
225
+ write_status()
226
+ with concurrent.futures.ThreadPoolExecutor(max_workers=args.workers) as pool:
227
+ futures = [pool.submit(worker, gpu) for gpu in range(args.workers)]
228
+ for future in futures:
229
+ future.result()
230
+ episodes, task_results = [], []
231
+ for task in sorted(completed):
232
+ rows = json.loads((output / f"task{task:02d}/summary.json").read_text())
233
+ task_results.extend(rows)
234
+ for row in rows:
235
+ episodes.extend(dict(task_id=task, **ep) for ep in row["episodes"])
236
+ scored = [r for r in completed.values() if r["num_episodes"] > 0]
237
+ report = dict(
238
+ metadata=metadata,
239
+ complete=set(completed) == set(tasks) and not failures,
240
+ failures=failures,
241
+ task_results=task_results,
242
+ episodes=episodes,
243
+ actual_episodes=len(episodes),
244
+ shortfalls={task: args.trials - r["num_episodes"] for task, r in completed.items()
245
+ if r["num_episodes"] < args.trials},
246
+ metric_tasks=[task for task, r in sorted(completed.items()) if r["num_episodes"] > 0],
247
+ TSR=sum(r["TSR"] for r in scored) / len(scored) if scored else None,
248
+ CSR=sum(r["CSR"] for r in scored) / len(scored) if scored else None,
249
+ )
250
+ (output / "final_report.json").write_text(json.dumps(report, indent=2))
251
+ print(f"EVALUATION FINISHED complete={report['complete']} TSR={report['TSR']} CSR={report['CSR']}", flush=True)
252
+ if not report["complete"]:
253
+ raise RuntimeError("Some tasks failed; see final_report.json")
254
+
255
+
256
+ if __name__ == "__main__":
257
+ parser = argparse.ArgumentParser()
258
+ parser.add_argument("--checkpoint", required=True)
259
+ parser.add_argument("--output", required=True)
260
+ parser.add_argument("--workers", type=int, default=8, choices=range(1, 9))
261
+ parser.add_argument("--trials", type=int, default=10)
262
+ parser.add_argument("--reference-prefix", action="store_true",
263
+ help="Allow3 trials per task to compare the seeds50–52 prefix of the recorded50k run")
264
+ parser.add_argument("--wait-smoke")
265
+ parser.add_argument("--base-port", type=int, default=8600)
266
+ parser.add_argument("--launch-gpu0", action="store_true")
267
+ parser.add_argument("--skip-tasks", type=int, nargs="*", default=[], choices=range(1, 27))
268
+ parser.add_argument("--task25-seeds", default=str(
269
+ ROOT.parent / "evidence/task25_seed_candidates.json"
270
+ ), help="Reference initialization seed list; policy failures are never retried")
271
+ main(parser.parse_args())
code/pi05/scripts/extract_robomem_memory.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Extract frozen pi05_base main-camera features before memory training."""
2
+
3
+ import argparse
4
+ import json
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+ import time
9
+
10
+ import jax
11
+ import jax.numpy as jnp
12
+ import numpy as np
13
+ import pyarrow.parquet as pq
14
+ import torch
15
+
16
+ from taskanchor.data.history import same_feature_provenance
17
+ from openpi.models import siglip
18
+ from openpi.models.model import restore_params
19
+ from openpi_client.image_tools import resize_with_pad
20
+ from openpi.training.robomem_memory_data import (
21
+ BASE_PARAMS,
22
+ CACHE_ROOT,
23
+ DATA_ROOT,
24
+ EpisodeIndex,
25
+ cache_file,
26
+ decode_image,
27
+ episode_path,
28
+ validate_cache,
29
+ validate_feature_file,
30
+ )
31
+
32
+
33
+ class ExtractionImages(torch.utils.data.IterableDataset):
34
+ def __init__(self, index, groups):
35
+ self.index, self.groups = index, groups
36
+
37
+ def __iter__(self):
38
+ torch.set_num_threads(1)
39
+ worker = torch.utils.data.get_worker_info()
40
+ groups = self.groups if worker is None else self.groups[worker.id :: worker.num_workers]
41
+ for task, seed in groups:
42
+ for ep in self.index.groups[(task, seed)]:
43
+ record = self.index.records[ep]
44
+ table = pq.read_table(episode_path(self.index.root, ep), columns=["image"])
45
+ for local in range(len(table)):
46
+ yield dict(
47
+ image=resize_with_pad(decode_image(table["image"][local].as_py()), 224, 224),
48
+ task=task,
49
+ seed=seed,
50
+ frame=record.prefix_frames + local,
51
+ )
52
+
53
+
54
+ def main(args):
55
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s", force=True)
56
+ index = EpisodeIndex(args.dataset)
57
+ cache = Path(args.cache)
58
+ cache.mkdir(parents=True, exist_ok=True)
59
+ expected = index.manifest(args.base_params)
60
+ manifest = cache / "manifest.json"
61
+ if manifest.exists() and not same_feature_provenance(json.loads(manifest.read_text()), expected):
62
+ raise ValueError("Existing cache has different provenance")
63
+ if not manifest.exists():
64
+ manifest.write_text(json.dumps(expected, indent=2))
65
+ if args.verify_only:
66
+ validate_cache(index, cache, args.base_params)
67
+ logging.info("CACHE VERIFIED: %s", expected)
68
+ return
69
+ pending = []
70
+ for key in sorted(index.groups):
71
+ path = cache_file(cache, key)
72
+ if path.exists():
73
+ length = index.records[index.groups[key][0]].total_frames
74
+ validate_feature_file(path, length)
75
+ else:
76
+ pending.append(key)
77
+ if args.limit_tasks:
78
+ pending = pending[: args.limit_tasks]
79
+ if not pending:
80
+ validate_cache(index, cache, args.base_params)
81
+ return
82
+ devices = jax.devices()
83
+ if any(d.platform != "gpu" for d in devices):
84
+ raise RuntimeError("Extraction requires GPUs; refusing a silent CPU fallback")
85
+ if args.batch_size % len(devices):
86
+ raise ValueError("Extraction batch size must be divisible by GPU count")
87
+ logging.info("Loading pi05_base encoder; devices=%s pending_tasks=%d", devices, len(pending))
88
+ params = restore_params(args.base_params, restore_type=np.ndarray)["PaliGemma"]["img"]
89
+ mesh = jax.sharding.Mesh(np.asarray(devices), ("batch",))
90
+ replicated = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec())
91
+ data_sharding = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec("batch"))
92
+ params = jax.device_put(jax.tree.map(lambda x: x.astype(jnp.bfloat16), params), replicated)
93
+ encoder = siglip.Module(num_classes=2048, variant="So400m/14", pool_type="none", scan=True, dtype_mm="bfloat16")
94
+
95
+ def encode(p, images):
96
+ images = images.astype(jnp.float32) / 255 * 2 - 1
97
+ tokens, _ = encoder.apply({"params": p}, images, train=False, return_encoded=True)
98
+ # Pool in FP32 then quantize to BF16; float16 storage preserves these values.
99
+ spatial = jax.image.resize(
100
+ tokens.reshape(-1, 16, 16, 1152).astype(jnp.float32),
101
+ (images.shape[0], 4, 4, 1152),
102
+ "linear",
103
+ antialias=False,
104
+ )
105
+ return spatial.reshape(-1, 16, 1152).astype(jnp.bfloat16).astype(jnp.float16)
106
+
107
+ encode = jax.jit(encode, in_shardings=(replicated, data_sharding), out_shardings=data_sharding)
108
+ loader = torch.utils.data.DataLoader(
109
+ ExtractionImages(index, pending),
110
+ batch_size=args.batch_size,
111
+ num_workers=args.workers,
112
+ multiprocessing_context="spawn" if args.workers else None,
113
+ prefetch_factor=2 if args.workers else None,
114
+ )
115
+ buffers, completed, frames = {}, 0, 0
116
+ started = time.monotonic()
117
+ for batch in loader:
118
+ images = batch["image"].numpy()
119
+ count = len(images)
120
+ if count < args.batch_size:
121
+ images = np.pad(images, ((0, args.batch_size - count), (0, 0), (0, 0), (0, 0)), mode="edge")
122
+ history = jax.device_get(encode(params, jax.device_put(images, data_sharding)))
123
+ for i in range(count):
124
+ key = int(batch["task"][i]), int(batch["seed"][i])
125
+ frame = int(batch["frame"][i])
126
+ if key not in buffers:
127
+ total = index.records[index.groups[key][0]].total_frames
128
+ buffers[key] = dict(history=np.empty((total, 16, 1152), np.float16), seen=np.zeros(total, bool))
129
+ buf = buffers[key]
130
+ if buf["seen"][frame]:
131
+ raise ValueError(f"Duplicate frame {key} {frame}")
132
+ buf["history"][frame] = history[i]
133
+ buf["seen"][frame] = True
134
+ if buf["seen"].all():
135
+ if not np.isfinite(buf["history"]).all():
136
+ raise ValueError(f"Nonfinite encoder output {key}")
137
+ path = cache_file(cache, key)
138
+ temp = path.with_suffix(".tmp.npy")
139
+ np.save(temp, buf["history"], allow_pickle=False)
140
+ os.replace(temp, path)
141
+ del buffers[key]
142
+ completed += 1
143
+ frames += count
144
+ if frames == count or frames % (args.batch_size * 20) == 0:
145
+ logging.info(
146
+ "extracted_frames=%d tasks=%d/%d fps=%.1f",
147
+ frames,
148
+ completed,
149
+ len(pending),
150
+ frames / (time.monotonic() - started),
151
+ )
152
+ if buffers or completed != len(pending):
153
+ raise RuntimeError("Extraction ended with incomplete task buffers")
154
+ if not args.limit_tasks:
155
+ validate_cache(index, cache, args.base_params)
156
+ (cache / "VERIFIED.json").write_text(json.dumps(expected, indent=2))
157
+ logging.info("EXTRACTION COMPLETE: tasks=%d frames=%d", completed, frames)
158
+
159
+
160
+ if __name__ == "__main__":
161
+ parser = argparse.ArgumentParser()
162
+ parser.add_argument("--dataset", default=str(DATA_ROOT))
163
+ parser.add_argument("--base-params", default=str(BASE_PARAMS))
164
+ parser.add_argument("--cache", default=str(CACHE_ROOT))
165
+ parser.add_argument("--batch-size", type=int, default=256)
166
+ parser.add_argument("--workers", type=int, default=8)
167
+ parser.add_argument("--limit-tasks", type=int, default=0)
168
+ parser.add_argument("--verify-only", action="store_true")
169
+ main(parser.parse_args())
code/pi05/scripts/robomem_eval_paths.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Portable filesystem locations; numerical evaluation settings live elsewhere."""
2
+
3
+ import os
4
+ from pathlib import Path
5
+ import sys
6
+
7
+ ROOT = Path(__file__).resolve().parents[1]
8
+ BENCHMARK = Path(os.environ.get(
9
+ "TASKANCHOR_ROBOMEM_BENCHMARK", ROOT.parent / "RoboMemArena/evaluation_benchmark"
10
+ )).resolve()
11
+ PYTHON = Path(os.environ.get(
12
+ "TASKANCHOR_ROBOMEM_EVAL_PYTHON", ROOT.parent / "robomem_eval_env/bin/python"
13
+ )).absolute() # Keep the virtualenv entry point; resolving its symlink loses its packages.
14
+ POLICY_PYTHON = os.environ.get("TASKANCHOR_PI05_PYTHON", sys.executable)
15
+ LIBERO_CONFIG = Path(os.environ.get(
16
+ "TASKANCHOR_LIBERO_CONFIG", ROOT / "data/robomem_eval_libero_config"
17
+ )).resolve()
18
+ RENDER_LIBS = Path(os.environ.get(
19
+ "TASKANCHOR_RENDER_LIBS", ROOT / "data/robomem_render_libs/usr/lib/x86_64-linux-gnu"
20
+ )).resolve()
code/pi05/scripts/robomem_memory_eval_adapter.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """RoboMemArena adapter: whole-task instruction, predicted progress, causal memory."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ import sys
6
+ import time
7
+
8
+ import numpy as np
9
+
10
+ ROOT = Path(__file__).resolve().parents[1]
11
+ sys.path.insert(0, str(ROOT / "packages/openpi-client/src"))
12
+ sys.path.insert(0, str(ROOT / "src"))
13
+ from openpi_client.websocket_client_policy import WebsocketClientPolicy
14
+ from openpi.robomemarena_26tasks_10_full_prompts import ROBOMEMARENA_26_10_FULL_PROMPTS
15
+ from openpi.policies.robomem_progress import PROGRESS_MODE, stage_values
16
+ from policy_adapter import BasePolicyAdapter
17
+
18
+
19
+ class MemoryEvalAdapter(BasePolicyAdapter):
20
+ def __init__(self, host="127.0.0.1", port=8600, replan_steps=None, trace_path=None, task_id=None):
21
+ self.client = WebsocketClientPolicy(host=host, port=int(port))
22
+ metadata = self.client.get_server_metadata()
23
+ if metadata.get("progress") != PROGRESS_MODE:
24
+ self.client._ws.close()
25
+ raise ValueError("Evaluation requires a nearest-stage progress policy server")
26
+ replan_steps = metadata.get("execution_horizon", 5) if replan_steps is None else replan_steps
27
+ if not 0 < replan_steps <= metadata["action_horizon"] or metadata["history_stride"] % replan_steps:
28
+ self.client._ws.close()
29
+ raise ValueError("Execution length must divide history stride and not exceed prediction horizon")
30
+ self.replan_steps = replan_steps
31
+ self.task_id = task_id
32
+ self.episode_id = -1
33
+ self.trace_path = Path(trace_path) if trace_path else None
34
+ self.frame = 0
35
+
36
+ def set_task_context(self, *, task_id=None, episode_id=None, **kwargs):
37
+ del kwargs
38
+ self.task_id, self.episode_id = task_id, episode_id
39
+
40
+ def reset(self):
41
+ self.frame = 0
42
+ self.episode_id += 1
43
+
44
+ def infer_actions(self, obs, prompt, resize_size):
45
+ del resize_size
46
+ task = self.task_id if self.task_id is not None else obs.get("task_id")
47
+ stage_values(task)
48
+ # A fixed full-task rewrite is the commanded instruction, never an oracle subtask.
49
+ if task is not None:
50
+ prompt = ROBOMEMARENA_26_10_FULL_PROMPTS[int(task)][0]
51
+ state = np.asarray(obs["observation/state"], np.float32).reshape(-1)
52
+ state = np.pad(state, (0, max(0, 8 - len(state))))[:8]
53
+ payload = {
54
+ "observation/image": obs["observation/image"],
55
+ "observation/wrist_image": obs["observation/wrist_image"],
56
+ "observation/state": state,
57
+ "prompt": prompt,
58
+ "task_id": int(task),
59
+ "environment_frame": self.frame,
60
+ "reset": self.frame == 0,
61
+ }
62
+ infer_started = time.perf_counter()
63
+ result = self.client.infer(payload)
64
+ round_trip_ms = (time.perf_counter() - infer_started) * 1000
65
+ if self.trace_path:
66
+ self.trace_path.parent.mkdir(parents=True, exist_ok=True)
67
+ with self.trace_path.open("a") as stream:
68
+ stream.write(
69
+ json.dumps(
70
+ dict(
71
+ task_id=int(task) if task is not None else None,
72
+ episode_id=self.episode_id,
73
+ frame=self.frame,
74
+ progress=float(result["progress"]),
75
+ progress_raw=float(result["progress_raw"]),
76
+ progress_stage=int(result["progress_stage"]),
77
+ progress_num_stages=int(result["progress_num_stages"]),
78
+ history_frames=int(result["history_frames"]),
79
+ server_infer_ms=result.get("server_timing", {}).get("infer_ms"),
80
+ round_trip_ms=round_trip_ms,
81
+ )
82
+ )
83
+ + "\n"
84
+ )
85
+ self.frame += self.replan_steps
86
+ return np.asarray(result["actions"], np.float32)
87
+
88
+ def close(self):
89
+ self.client._ws.close()
90
+
91
+
92
+ def build_adapter(**kwargs):
93
+ return MemoryEvalAdapter(**kwargs)
code/pi05/scripts/robomem_seed_search.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bounded Task25 seed enumeration; unsuccessful policies remain scored episodes."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from run_robomem_eval_task import TaskInitializationError, run_task
7
+
8
+
9
+ def run_task25(gpu, port, trials, candidates, output, replan_steps):
10
+ output = Path(output)
11
+ output.mkdir(parents=True, exist_ok=False)
12
+ if len(candidates) > 200 or len(candidates) != len(set(candidates)):
13
+ raise ValueError("At most 200 distinct seed candidates are allowed")
14
+ audit = dict(target_episodes=trials, max_seed_attempts=len(candidates), attempts=[], accepted_seeds=[])
15
+
16
+ def save():
17
+ temporary = output / "seed_search.tmp.json"
18
+ temporary.write_text(json.dumps(audit, indent=2))
19
+ temporary.replace(output / "seed_search.json")
20
+
21
+ rows, episodes = [], []
22
+ save()
23
+ for seed in candidates:
24
+ case = output / f"seed{seed:04d}"
25
+ try:
26
+ run_task(25, gpu, port, 1, seed, case, replan_steps=replan_steps, enumerate_seed=True)
27
+ except TaskInitializationError as exc:
28
+ audit["attempts"].append(dict(seed=seed, status="initialization_error", error=str(exc)))
29
+ save()
30
+ continue
31
+ except Exception as exc:
32
+ audit["attempts"].append(dict(seed=seed, status="runtime_error", error=str(exc)))
33
+ save()
34
+ raise
35
+ row = json.loads((case / "summary.json").read_text())[0]
36
+ if len(row["episodes"]) != 1 or row["episodes"][0]["seed"] != seed:
37
+ raise RuntimeError("Single-seed scorer returned inconsistent coverage")
38
+ ep = dict(row["episodes"][0], ep=len(episodes), video_dir=row["video_dir"])
39
+ episodes.append(ep)
40
+ rows.append(row)
41
+ audit["accepted_seeds"].append(seed)
42
+ audit["attempts"].append(dict(seed=seed, status="scored", TSR=ep["TSR"], CSR=ep["CSR"]))
43
+ save()
44
+ if len(episodes) == trials:
45
+ break
46
+ audit.update(actual_episodes=len(episodes), exhausted=len(audit["attempts"]) == len(candidates),
47
+ shortfall=trials - len(episodes))
48
+ save()
49
+ tsr = sum(ep["TSR"] for ep in episodes) / len(episodes) if episodes else None
50
+ csr = sum(ep["CSR"] for ep in episodes) / len(episodes) if episodes else None
51
+ summary = dict(rows[0]) if rows else dict(task_id=25, task_key="task25")
52
+ summary.update(episodes=episodes, TSR=tsr, CSR=csr, seed_search=audit,
53
+ video_dir=str(output), num_episodes=len(episodes))
54
+ aggregate = dict(num_tasks=1, num_episodes=len(episodes), TSR=tsr, CSR=csr, seed_search=audit)
55
+ (output / "summary.json").write_text(json.dumps([summary], indent=2))
56
+ (output / "aggregate.json").write_text(json.dumps(aggregate, indent=2))
57
+ return aggregate
code/pi05/scripts/run_robomem_eval_task.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Launch the unmodified official scorer for one task."""
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ from pathlib import Path
7
+ import subprocess
8
+
9
+ from robomem_eval_paths import ROOT, BENCHMARK, PYTHON, LIBERO_CONFIG, RENDER_LIBS
10
+
11
+
12
+ class TaskInitializationError(RuntimeError):
13
+ """An enumerated seed failed before any policy action was produced."""
14
+
15
+
16
+ def run_task(task, gpu, port, trials, seed, output, replan_steps=5, enumerate_seed=False):
17
+ if replan_steps <= 0 or 10 % replan_steps:
18
+ raise ValueError("Replan steps must divide history stride 10")
19
+ output = Path(output).resolve()
20
+ output.mkdir(parents=True, exist_ok=True)
21
+ env = dict(
22
+ os.environ,
23
+ MUJOCO_GL="osmesa",
24
+ PYOPENGL_PLATFORM="osmesa",
25
+ MUJOCO_EGL_DEVICE_ID=str(gpu),
26
+ OMP_NUM_THREADS="1",
27
+ OPENBLAS_NUM_THREADS="1",
28
+ NUMBA_CACHE_DIR="/tmp/robomem_numba",
29
+ LIBERO_CONFIG_PATH=str(LIBERO_CONFIG),
30
+ PYTHONPATH=str(BENCHMARK / "libero_fork"),
31
+ LD_LIBRARY_PATH=str(RENDER_LIBS)
32
+ + ":"
33
+ + os.environ.get("LD_LIBRARY_PATH", ""),
34
+ LP_NUM_THREADS="2",
35
+ )
36
+ kwargs = dict(port=port, replan_steps=replan_steps, task_id=task, trace_path=str(output / "progress.jsonl"))
37
+ command = [
38
+ str(PYTHON),
39
+ "-u",
40
+ str(ROOT / "scripts/run_robomem_task25_seed.py") if enumerate_seed else "scripts/run_all_tasks1_26.py",
41
+ "--adapter-spec",
42
+ str(ROOT / "scripts/robomem_memory_eval_adapter.py") + ":build_adapter",
43
+ "--adapter-kwargs",
44
+ json.dumps(kwargs),
45
+ "--task-start",
46
+ str(task),
47
+ "--task-end",
48
+ str(task),
49
+ "--num-trials-per-task",
50
+ str(trials),
51
+ "--seed",
52
+ str(seed),
53
+ "--replan-steps",
54
+ str(replan_steps),
55
+ "--max-steps",
56
+ "2500",
57
+ "--out-root",
58
+ str(output),
59
+ ]
60
+ (output / "command.json").write_text(json.dumps(command, indent=2))
61
+ for attempt in range(1, 2 if enumerate_seed else 4):
62
+ with (output / "run.log").open("w", buffering=1) as log:
63
+ process = subprocess.run(command, cwd=BENCHMARK, env=env, stdout=log, stderr=subprocess.STDOUT)
64
+ log_text = (output / "run.log").read_text()
65
+ trace = output / "progress.jsonl"
66
+ if (enumerate_seed and process.returncode == 86 and "TASK25_INITIALIZATION_ERROR:" in log_text
67
+ and (not trace.exists() or trace.stat().st_size == 0)):
68
+ raise TaskInitializationError(f"Task25 seed {seed}: initialization failed; see {output / 'run.log'}")
69
+ # The official constructor samples before any episode seed is set. Its
70
+ # reset() already retries invalid placements, but its constructor does not.
71
+ # Retry only this startup failure, never an episode or a policy failure.
72
+ startup_placement_error = (
73
+ process.returncode != 0
74
+ and "robosuite.utils.errors.RandomizationError: Cannot place all objects" in log_text
75
+ and "env = OffScreenRenderEnv(" in log_text
76
+ and "Episode " not in log_text
77
+ and not (output / "progress.jsonl").exists()
78
+ and not (output / "summary.json").exists()
79
+ )
80
+ if not startup_placement_error or attempt == 3:
81
+ break
82
+ (output / f"startup_failure_attempt{attempt}.log").write_text(log_text)
83
+ print(f"Task {task}: constructor placement failed before episode 0; retry {attempt}/2", flush=True)
84
+ if process.returncode or "Traceback (most recent call last)" in log_text or "Episode failed:" in log_text:
85
+ raise RuntimeError(f"Task {task} evaluation encountered a runtime error; see {output / 'run.log'}")
86
+ result = json.loads((output / "aggregate.json").read_text())
87
+ if result["num_episodes"] != trials:
88
+ raise RuntimeError(f"Task {task}: incomplete episode coverage")
89
+ return result
90
+
91
+
92
+ if __name__ == "__main__":
93
+ parser = argparse.ArgumentParser()
94
+ parser.add_argument("--task", type=int, required=True)
95
+ parser.add_argument("--gpu", type=int, default=0)
96
+ parser.add_argument("--port", type=int, default=8600)
97
+ parser.add_argument("--trials", type=int, default=1)
98
+ parser.add_argument("--seed", type=int, default=50)
99
+ parser.add_argument("--output", required=True)
100
+ parser.add_argument("--replan-steps", type=int, default=5)
101
+ args = parser.parse_args()
102
+ print(json.dumps(run_task(args.task, args.gpu, args.port, args.trials, args.seed, args.output,
103
+ replan_steps=args.replan_steps)), flush=True)
code/pi05/scripts/run_robomem_task25_seed.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Official scorer with deterministic, single-attempt Task25 initialization."""
2
+
3
+ from pathlib import Path
4
+ import sys
5
+
6
+ import numpy as np
7
+
8
+ from robomem_eval_paths import BENCHMARK
9
+ sys.path.insert(0, str(BENCHMARK / "scripts"))
10
+ import run_all_tasks1_26 as official
11
+
12
+
13
+ def main():
14
+ args = official.build_argparser().parse_args()
15
+ from robosuite.utils.errors import RandomizationError
16
+ if args.task_start != 25 or args.task_end != 25 or args.num_trials_per_task != 1:
17
+ raise ValueError("This entry point is only for one Task25 seed")
18
+ np.random.seed(args.seed) # Also seed the constructor, which precedes the episode loop.
19
+ base = official.ec._get_env_class()
20
+
21
+ class SingleAttemptEnvironment(base):
22
+ def reset(self):
23
+ # Original ControlEnv.reset retries placement errors indefinitely.
24
+ # Return the same observation on success; reject this seed on failure.
25
+ return self.env.reset()
26
+
27
+ official.ec._get_env_class = lambda: SingleAttemptEnvironment
28
+ try:
29
+ official.main()
30
+ except RandomizationError as exc:
31
+ print(f"TASK25_INITIALIZATION_ERROR: {exc}", flush=True)
32
+ raise SystemExit(86) from exc
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
code/pi05/scripts/serve_robomem_memory.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import logging
3
+
4
+ from openpi.policies.robomem_memory_inference import RoboMemMemoryPolicy
5
+ from openpi.serving.websocket_policy_server import WebsocketPolicyServer
6
+
7
+
8
+ if __name__ == "__main__":
9
+ parser = argparse.ArgumentParser()
10
+ parser.add_argument("--checkpoint", required=True)
11
+ parser.add_argument("--port", type=int, default=8600)
12
+ args = parser.parse_args()
13
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s", force=True)
14
+ policy = RoboMemMemoryPolicy(args.checkpoint)
15
+ logging.info("Memory policy loaded: %s", policy.metadata)
16
+ WebsocketPolicyServer(policy, host="127.0.0.1", port=args.port, metadata=policy.metadata).serve_forever()
code/pi05/scripts/test_robomem_discrete_progress.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CPU checks for legal progress values and the action-conditioning path."""
2
+ import unittest
3
+ import numpy as np
4
+ from openpi.policies.robomem_progress import discretize_progress, stage_values
5
+
6
+
7
+ class ProgressTests(unittest.TestCase):
8
+ def test_four_stages_and_boundaries(self):
9
+ self.assertEqual(stage_values(1), (0.0, 0.25, 0.5, 0.75))
10
+ for raw, expected in [(0, 0), (.124, 0), (.125, 0), (.126, .25),
11
+ (.375, .25), (.62, .5), (.625, .5), (.626, .75),
12
+ (.86, .75), (1, .75), (-.1, 0), (1.1, .75)]:
13
+ self.assertEqual(discretize_progress(raw, 1)[0], expected)
14
+
15
+ def test_all_tasks_match_training_labels(self):
16
+ from openpi.robomemarena_26tasks_subtask_prompts import ROBOMEMARENA_26_SUBTASK_PROMPTS
17
+ for task, stages in ROBOMEMARENA_26_SUBTASK_PROMPTS.items():
18
+ expected = tuple(round(i / len(stages), 2) for i in range(len(stages)))
19
+ self.assertEqual(stage_values(task), expected)
20
+ for i, value in enumerate(expected):
21
+ self.assertEqual(discretize_progress(value, task), (value, i))
22
+ for value in np.linspace(0, 1, 101):
23
+ self.assertIn(discretize_progress(value, task)[0], expected)
24
+
25
+ def test_reject_invalid_inputs(self):
26
+ for task in [None, 0, 27, True, "1"]:
27
+ with self.assertRaises(ValueError):
28
+ discretize_progress(.5, task)
29
+ for raw in [float("nan"), float("inf"), -float("inf")]:
30
+ with self.assertRaises(FloatingPointError):
31
+ discretize_progress(raw, 1)
32
+
33
+ def test_action_sampler_receives_discrete_prompt(self):
34
+ from types import SimpleNamespace
35
+ import jax
36
+ from openpi.policies.robomem_memory_inference import RoboMemMemoryPolicy
37
+ policy = object.__new__(RoboMemMemoryPolicy)
38
+ policy.action_horizon = 20
39
+ policy.rng = jax.random.key(42)
40
+ policy._observation = lambda raw, prompt: SimpleNamespace(state=np.zeros((1, 8)), prompt=prompt)
41
+ policy.encode = lambda obs: (None, None, np.zeros((1, 16, 1152)))
42
+ policy.history = SimpleNamespace(add_and_read=lambda frame, spatial:
43
+ (np.zeros((16, 1152)), np.zeros((1, 16, 1152)), np.ones(1, bool)))
44
+ policy.refine = lambda *args: (None, np.array([.86]))
45
+ def sample(key, obs, refined, mask):
46
+ self.assertEqual(obs.prompt, "test task.Progress:0.75")
47
+ return np.zeros((1, 20, 7))
48
+ policy.sample = sample
49
+ policy.outputs = lambda data: data
50
+ result = policy.infer(dict(task_id=1, environment_frame=0, prompt="test task"))
51
+ self.assertEqual(result["progress"], .75)
52
+ self.assertEqual(result["progress_raw"], .86)
53
+ self.assertEqual(result["progress_stage"], 3)
54
+ self.assertEqual(result["actions"].shape, (20, 7))
55
+
56
+
57
+ if __name__ == "__main__":
58
+ unittest.main()
code/pi05/scripts/test_robomem_memory.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import importlib.util
2
+ from pathlib import Path
3
+ import unittest
4
+ import tempfile
5
+ from flax import nnx
6
+ import jax
7
+ import jax.numpy as jnp
8
+ import numpy as np
9
+ import optax
10
+ from taskanchor.pi05.taskanchor import PrimaryMemoryAdapter, MemoryTTT
11
+ from openpi.training.robomem_memory_data import (
12
+ EpisodeIndex,
13
+ MemoryDataset,
14
+ history_indices,
15
+ cache_file,
16
+ validate_feature_file,
17
+ )
18
+
19
+ spec = importlib.util.spec_from_file_location("memory_trainer", Path(__file__).with_name("train_robomem_memory.py"))
20
+ trainer = importlib.util.module_from_spec(spec)
21
+ spec.loader.exec_module(trainer)
22
+
23
+
24
+ class MemoryTests(unittest.TestCase):
25
+ def test_primary_residual_and_masked_history(self):
26
+ adapter = PrimaryMemoryAdapter(rngs=nnx.Rngs(0), input_dim=32, hidden_dim=32, depth=2, heads=4)
27
+ current = jax.random.normal(jax.random.key(1), (2, 3, 4, 32)).astype(jnp.bfloat16)
28
+ frame0 = jax.random.normal(jax.random.key(2), (2, 16, 32)).astype(jnp.bfloat16)
29
+ history = jax.random.normal(jax.random.key(3), (2, 5, 16, 32)).astype(jnp.bfloat16)
30
+ mask = jnp.array([[1, 1, 0, 0, 0]] * 2, bool)
31
+ cmask = jnp.ones((2, 3, 4), bool)
32
+ refined, progress = adapter(current, cmask, history, mask, frame0=frame0)
33
+ np.testing.assert_array_equal(refined[:, 1:], current[:, 1:])
34
+ self.assertGreater(float(jnp.linalg.norm((refined[:, 0] - current[:, 0]).astype(jnp.float32))), 0)
35
+ altered, p2 = adapter(current, cmask, history.at[:, 2:].set(100), mask, frame0=frame0)
36
+ np.testing.assert_array_equal(refined, altered)
37
+ np.testing.assert_array_equal(progress, p2)
38
+ grad = jax.grad(lambda h: adapter(current, cmask, h, mask, frame0=frame0)[0][:, 0, 0, 0].astype(jnp.float32).sum())(
39
+ history
40
+ )
41
+ self.assertGreater(float(jnp.linalg.norm(grad[:, :2].astype(jnp.float32))), 0)
42
+ self.assertTrue(np.all(np.linalg.norm(np.asarray(grad[:, :2], np.float32), axis=-1) > 0))
43
+ np.testing.assert_array_equal(grad[:, 2:], jnp.zeros_like(grad[:, 2:]))
44
+ masked, _ = adapter(current, cmask.at[:, 0].set(False), history, mask, frame0=frame0)
45
+ np.testing.assert_array_equal(masked, current)
46
+
47
+ def test_current_never_writes_fast_weights(self):
48
+ ttt = MemoryTTT(32, 4, nnx.Rngs(5))
49
+ context = jax.random.normal(jax.random.key(6), (1, 6, 32)).astype(jnp.bfloat16)
50
+ write = jnp.array([[0, 0, 1, 1, 1, 1]], bool)
51
+ positions = jnp.arange(6)[None].astype(jnp.float32)
52
+ original = ttt(context, write, positions, 2)
53
+ changed = ttt(context.at[:, 0].multiply(10), write, positions, 2)
54
+ np.testing.assert_array_equal(original[:, 1], changed[:, 1])
55
+
56
+ def test_history_strictly_past_and_across_subtasks(self):
57
+ for t, expected in [(0, []), (10, []), (11, [1]), (31, [1, 11, 21])]:
58
+ self.assertEqual(history_indices(t).tolist(), expected)
59
+ self.assertEqual(len(history_indices(1917)), 191)
60
+ index = EpisodeIndex()
61
+ self.assertEqual(len(index.groups), 2600)
62
+ self.assertEqual(index.max_task_frames, 1918)
63
+ self.assertEqual(index.history_frames, 191)
64
+ for episodes in index.groups.values():
65
+ previous_end = 0
66
+ for ep in episodes:
67
+ r = index.records[ep]
68
+ self.assertEqual(r.prefix_frames, previous_end)
69
+ previous_end += r.length
70
+ self.assertTrue(np.all(history_indices(r.prefix_frames) < r.prefix_frames))
71
+ self.assertEqual(previous_end, r.total_frames)
72
+ for t in (0, previous_end // 2, previous_end - 1):
73
+ expected = list(reversed(range(t - 10, 0, -10)))
74
+ self.assertEqual(history_indices(t, capacity=index.history_frames).tolist(), expected)
75
+
76
+ def test_spatial_cache_dataset_preserves_frames_and_patches(self):
77
+ index = EpisodeIndex()
78
+ key = sorted(index.groups)[0]
79
+ episodes = index.groups[key]
80
+ total = index.records[episodes[0]].total_frames
81
+ cfg = trainer.config.get_config("pi05_robomemarena_26tasks_subtask_only_full_sft_eval")
82
+ with tempfile.TemporaryDirectory() as directory:
83
+ path = cache_file(directory, key)
84
+ spatial = np.broadcast_to(np.arange(total, dtype=np.float16)[:, None, None], (total, 16, 1152)).copy()
85
+ spatial[:, :, 0] = np.arange(16, dtype=np.float16)
86
+ np.save(path, spatial)
87
+ validate_feature_file(path, total)
88
+ dataset = MemoryDataset(index, cfg.data.create(cfg.assets_dirs, cfg.model), cfg.model, directory)
89
+ ep = episodes[-1]
90
+ pos = index.episodes.index(ep)
91
+ sample = dataset[int(index.ends[pos]) - 1]
92
+ selected = history_indices(total - 1)
93
+ self.assertEqual(sample["history"].shape, (191, 16, 1152))
94
+ np.testing.assert_array_equal(sample["history_mask"], np.arange(191) < len(selected))
95
+ np.testing.assert_array_equal(sample["history"][: len(selected)], spatial[selected])
96
+ np.testing.assert_array_equal(sample["history"][len(selected) :], 0)
97
+ np.testing.assert_array_equal(sample["frame0"], spatial[0])
98
+ self.assertIsInstance(dataset.features[key], np.memmap)
99
+ np.save(path, spatial.mean(axis=1))
100
+ with self.assertRaisesRegex(ValueError, "shape/dtype"):
101
+ validate_feature_file(path, total)
102
+
103
+ def test_siglip_head_recomposition(self):
104
+ from openpi.models.siglip import Module
105
+
106
+ encoder = Module(num_classes=16, variant="mu/14", pool_type="none", scan=True, head_zeroinit=False)
107
+ images = jax.random.normal(jax.random.key(7), (1, 28, 28, 3))
108
+ params = encoder.init(jax.random.key(8), images, train=False)
109
+ original, _ = encoder.apply(params, images, train=False)
110
+ native, _ = encoder.apply(params, images, train=False, return_encoded=True)
111
+ recomposed = encoder.apply(params, native, method="project_encoded")
112
+ self.assertGreater(float(jnp.linalg.norm(original)), 0)
113
+ np.testing.assert_array_equal(original, recomposed)
114
+
115
+ def test_repeated_initialization_has_identical_static_graph(self):
116
+ def create():
117
+ return PrimaryMemoryAdapter(rngs=nnx.Rngs(0), input_dim=32, hidden_dim=32, depth=2, heads=4)
118
+
119
+ self.assertEqual(nnx.graphdef(create()), nnx.graphdef(create()))
120
+
121
+ def test_exact_optimizer_phase_boundary(self):
122
+ class Tiny(nnx.Module):
123
+ def __init__(self):
124
+ self.adapter = nnx.Dict(weight=nnx.Param(jnp.ones(2)))
125
+ self.backbone = nnx.Dict(
126
+ weight=nnx.Param(jnp.ones(2)),
127
+ PaliGemma=nnx.Dict(
128
+ img=nnx.Dict(head=nnx.Dict(kernel=nnx.Param(jnp.ones(2))), encoder=nnx.Param(jnp.ones(2)))
129
+ ),
130
+ )
131
+
132
+ params = nnx.state(Tiny()).filter(trainer.trainable)
133
+ self.assertNotIn("encoder", params["backbone"]["PaliGemma"]["img"])
134
+ tx = trainer.make_optimizer()
135
+ state = tx.init(params)
136
+ grads = jax.tree.map(jnp.ones_like, params)
137
+
138
+ @jax.jit
139
+ def update(p, s):
140
+ updates, s = tx.update(grads, s, p)
141
+ return optax.apply_updates(p, updates), s
142
+
143
+ initial = params.to_pure_dict()
144
+ for _ in range(5000):
145
+ params, state = update(params, state)
146
+ np.testing.assert_array_equal(params["backbone"]["weight"].value, initial["backbone"]["weight"])
147
+ np.testing.assert_array_equal(
148
+ params["backbone"]["PaliGemma"]["img"]["head"]["kernel"].value,
149
+ initial["backbone"]["PaliGemma"]["img"]["head"]["kernel"],
150
+ )
151
+ self.assertFalse(np.array_equal(params["adapter"]["weight"].value, initial["adapter"]["weight"]))
152
+ params, _ = update(params, state)
153
+ self.assertFalse(np.array_equal(params["backbone"]["weight"].value, initial["backbone"]["weight"]))
154
+ self.assertFalse(
155
+ np.array_equal(
156
+ params["backbone"]["PaliGemma"]["img"]["head"]["kernel"].value,
157
+ initial["backbone"]["PaliGemma"]["img"]["head"]["kernel"],
158
+ )
159
+ )
160
+
161
+
162
+ if __name__ == "__main__":
163
+ unittest.main()
code/pi05/scripts/test_robomem_memory_inference.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ import numpy as np
3
+ from openpi.policies.robomem_memory_inference import FIFOHistory
4
+
5
+
6
+ class OnlineHistoryTests(unittest.TestCase):
7
+ def test_environment_stride_and_rollover(self):
8
+ bank = FIFOHistory()
9
+ for t in range(0, 2051, 5):
10
+ spatial = np.full((16, 1152), t, np.float16)
11
+ spatial[:, 0] = np.arange(16)
12
+ memory, mask = bank.add_and_read(t, spatial)
13
+ expected = ([0] + list(reversed(range(t - 10, 0, -10))))[-191:]
14
+ np.testing.assert_array_equal(memory[mask, 0, 1], expected)
15
+ np.testing.assert_array_equal(memory[mask, :, 0], np.broadcast_to(np.arange(16), (len(expected), 16)))
16
+ self.assertNotIn(0, bank.frames)
17
+ self.assertEqual(int(mask.sum()), 191)
18
+ bank.reset()
19
+ self.assertFalse(bank.frames)
20
+ _, mask = bank.add_and_read(0, np.zeros((16, 1152)))
21
+ self.assertEqual(int(mask.sum()), 1)
22
+
23
+ def test_rmbench_fifo25_bootstrap_and_eviction(self):
24
+ bank = FIFOHistory(capacity=25, stride=30)
25
+ for t in range(0, 1201, 30):
26
+ memory, mask = bank.add_and_read(t, np.full((16, 1152), t, np.float16))
27
+ expected = [0] if t == 0 else list(range(0, t, 30))[-25:]
28
+ np.testing.assert_array_equal(memory[mask, 0, 0], expected)
29
+ self.assertEqual(memory.shape, (25, 16, 1152))
30
+ if t == 750:
31
+ self.assertEqual(memory[mask, 0, 0].tolist(), list(range(0, 750, 30)))
32
+ if t == 780:
33
+ self.assertEqual(memory[mask, 0, 0].tolist(), list(range(30, 780, 30)))
34
+ self.assertNotIn(0, bank.frames)
35
+ self.assertFalse(hasattr(bank, "frame0"))
36
+
37
+ def test_invalid_first_frame_does_not_mutate_bank(self):
38
+ bank = FIFOHistory(capacity=25, stride=30)
39
+ with self.assertRaisesRegex(ValueError, "frame zero"):
40
+ bank.add_and_read(30, np.zeros((16, 1152)))
41
+ self.assertEqual(bank.last_frame, -1)
42
+ self.assertFalse(bank.frames)
43
+
44
+ def test_missing_frames_and_duplicate_indices_rejected(self):
45
+ bank = FIFOHistory()
46
+ bank.add_and_read(0, np.zeros((16, 1152)))
47
+ with self.assertRaises(ValueError):
48
+ bank.add_and_read(0, np.zeros((16, 1152)))
49
+ with self.assertRaisesRegex(ValueError, "Missing historical"):
50
+ bank.add_and_read(25, np.zeros((16, 1152)))
51
+
52
+
53
+ if __name__ == "__main__":
54
+ unittest.main()
code/pi05/scripts/test_robomem_memory_loader.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify batch equality, shared storage, epoch order, and producer failures."""
2
+ import unittest
3
+ import jax
4
+ import numpy as np
5
+ import torch
6
+ from openpi.training.robomem_memory_loader import DevicePrefetch, make_host_loader
7
+
8
+
9
+ class Samples:
10
+ def __len__(self):
11
+ return 12
12
+
13
+ def __getitem__(self, index):
14
+ return {"history": np.full((3, 4, 8), index, np.float16),
15
+ "mask": np.array([True, False, True]),
16
+ "data": {"ids": np.array([index, index + 1], np.int32)},
17
+ "progress": np.float32(index / 12)}
18
+
19
+
20
+ class BrokenSamples(Samples):
21
+ def __getitem__(self, index):
22
+ raise ValueError("intentional producer failure")
23
+
24
+
25
+ def numpy_collate(items):
26
+ return jax.tree.map(lambda *xs: np.stack([np.asarray(x) for x in xs]), *items)
27
+
28
+
29
+ class LoaderTests(unittest.TestCase):
30
+ def setUp(self):
31
+ self.shard = jax.sharding.NamedSharding(
32
+ jax.sharding.Mesh(np.array(jax.devices()), ("batch",)), jax.sharding.PartitionSpec("batch")
33
+ )
34
+
35
+ def test_same_batches_two_epochs(self):
36
+ expected = torch.utils.data.DataLoader(
37
+ Samples(), batch_size=4, num_workers=2, shuffle=True,
38
+ generator=torch.Generator().manual_seed(7), drop_last=True,
39
+ persistent_workers=True, multiprocessing_context="spawn", collate_fn=numpy_collate,
40
+ )
41
+ source = make_host_loader(Samples(), 4, workers=2, seed=7)
42
+ prefetch = DevicePrefetch(source, self.shard, depth=2)
43
+ try:
44
+ for _ in range(2):
45
+ for host in expected:
46
+ actual = next(prefetch)
47
+ for a, b in zip(jax.tree.leaves(actual), jax.tree.leaves(host), strict=True):
48
+ np.testing.assert_array_equal(np.asarray(a), np.asarray(b))
49
+ self.assertEqual(a.dtype, np.asarray(b).dtype)
50
+ finally:
51
+ prefetch.close()
52
+ expected._iterator._shutdown_workers()
53
+
54
+ def test_worker_output_is_shared(self):
55
+ source = make_host_loader(Samples(), 4, workers=1, seed=7)
56
+ it = iter(source)
57
+ try:
58
+ for batch in it:
59
+ for value in jax.tree.leaves(batch):
60
+ self.assertIsInstance(value, torch.Tensor)
61
+ self.assertTrue(value.is_shared())
62
+ finally:
63
+ it._shutdown_workers()
64
+
65
+ def test_exception_reaches_trainer(self):
66
+ prefetch = DevicePrefetch(make_host_loader(BrokenSamples(), 4, workers=0, seed=7), self.shard)
67
+ try:
68
+ with self.assertRaisesRegex(ValueError, "intentional producer failure"):
69
+ next(prefetch)
70
+ finally:
71
+ prefetch.close()
72
+
73
+ def test_finite_iterator(self):
74
+ prefetch = DevicePrefetch(make_host_loader(Samples(), 4, workers=0, seed=7), self.shard, repeat=False)
75
+ try:
76
+ self.assertEqual(len(list(prefetch)), 3)
77
+ finally:
78
+ prefetch.close()
79
+
80
+
81
+ if __name__ == "__main__":
82
+ unittest.main()
code/pi05/scripts/train_robomem_memory.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """pi0.5: 5k adapter-only, then 50k joint updates with frozen SigLIP."""
2
+
3
+ import argparse
4
+ import dataclasses
5
+ import json
6
+ import logging
7
+ import hashlib
8
+ import shutil
9
+ from pathlib import Path
10
+
11
+ import jax
12
+ from taskanchor.paths import ASSETS_DIR, CHECKPOINT_ROOT
13
+
14
+ from openpi.training import checkpoints, config, sharding, weight_loaders
15
+ from openpi.training.robomem_memory_data import (
16
+ BASE_PARAMS,
17
+ CACHE_ROOT,
18
+ DATA_ROOT,
19
+ EpisodeIndex,
20
+ MemoryDataset,
21
+ validate_cache,
22
+ )
23
+
24
+
25
+ from taskanchor.pi05.training import (
26
+ label as label,
27
+ trainable as trainable,
28
+ adapter_only as adapter_only,
29
+ make_optimizer as make_optimizer,
30
+ init_state,
31
+ train_step as train_step,
32
+ CheckpointData,
33
+ run_training_loop,
34
+ )
35
+
36
+
37
+ def recipe(args):
38
+ original = config.get_config("pi05_robomemarena_26tasks_subtask_only_full_sft_eval")
39
+ return dataclasses.replace(
40
+ original,
41
+ name="pi05_robomemarena_memory_v5_main",
42
+ exp_name=args.run_name,
43
+ model=dataclasses.replace(original.model, action_horizon=args.action_horizon),
44
+ data=dataclasses.replace(original.data, assets=dataclasses.replace(original.data.assets, assets_dir=args.assets_dir)),
45
+ batch_size=args.batch_size,
46
+ num_workers=args.workers,
47
+ num_train_steps=args.steps,
48
+ fsdp_devices=args.fsdp_devices,
49
+ ema_decay=None,
50
+ wandb_enabled=False,
51
+ save_interval=args.save_interval,
52
+ keep_period=5000,
53
+ checkpoint_base_dir=args.checkpoint_root,
54
+ weight_loader=weight_loaders.CheckpointWeightLoader(args.base_params),
55
+ )
56
+
57
+
58
+ def main(args):
59
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s", force=True)
60
+ if not 0 < args.freeze_steps < args.steps:
61
+ raise ValueError("Require 0 < freeze_steps < steps")
62
+ if not 0 < args.execution_horizon <= args.action_horizon or 10 % args.execution_horizon:
63
+ raise ValueError("Execution horizon must divide history stride 10 and not exceed action horizon")
64
+ if any(d.platform != "gpu" for d in jax.devices()):
65
+ raise RuntimeError("Training requires GPUs; refusing a silent CPU fallback")
66
+ if args.batch_size % jax.device_count():
67
+ raise ValueError("Batch size must be divisible by GPU count")
68
+ index = EpisodeIndex(args.dataset)
69
+ logging.info("Verifying every cache file before training")
70
+ manifest = validate_cache(index, args.cache, args.base_params)
71
+ cfg = recipe(args)
72
+ dc = cfg.data.create(cfg.assets_dirs, cfg.model)
73
+ dataset = MemoryDataset(index, dc, cfg.model, args.cache)
74
+ mesh = sharding.make_mesh(args.fsdp_devices)
75
+ data_sharding = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec(sharding.DATA_AXIS))
76
+ replicated = jax.sharding.NamedSharding(mesh, jax.sharding.PartitionSpec())
77
+ manager, resume = checkpoints.initialize_checkpoint_dir(
78
+ cfg.checkpoint_dir, keep_period=5000, overwrite=False, resume=args.resume
79
+ )
80
+ metadata = dict(
81
+ metadata_filename="robomem_memory.json",
82
+ **vars(args),
83
+ cache_manifest=manifest,
84
+ model=dataclasses.asdict(cfg.model),
85
+ architecture="primary_camera_v5_spatial_history_offset_512_1152",
86
+ memory_layout="frame0_and_history",
87
+ frame0="separate",
88
+ history_stride=10,
89
+ history_capacity=index.history_frames,
90
+ max_task_frames=index.max_task_frames,
91
+ history_tokens_per_frame=16,
92
+ freeze_vision=True,
93
+ progress_target="round(subtask_id/num_subtasks,2)",
94
+ loss="flow+0.01*(100*MSE+1-Pearson)",
95
+ )
96
+ metadata_path = Path(cfg.checkpoint_dir) / "recipe.json"
97
+ if resume:
98
+ old = json.loads(metadata_path.read_text())
99
+ # Original50k recipes predate these explicit fields. Architecture identifies them.
100
+ from taskanchor.data.history import validate_robomem_recipe
101
+ validate_robomem_recipe(old)
102
+ old.setdefault("memory_layout", "frame0_and_history")
103
+ old.setdefault("frame0", "separate")
104
+ for key in (
105
+ "base_params",
106
+ "dataset",
107
+ "cache_manifest",
108
+ "freeze_steps",
109
+ "steps",
110
+ "batch_size",
111
+ "model",
112
+ "architecture",
113
+ "memory_layout",
114
+ "frame0",
115
+ "history_capacity",
116
+ "history_stride",
117
+ "history_tokens_per_frame",
118
+ ):
119
+ if old.get(key) != metadata[key]:
120
+ raise ValueError(f"Resume recipe changed: {key}")
121
+ else:
122
+ root = Path(__file__).resolve().parents[1]
123
+ snapshot = Path(cfg.checkpoint_dir) / "source_snapshot"
124
+ shutil.copytree(
125
+ root / "src", snapshot / "src", ignore=shutil.ignore_patterns("__pycache__", "*.pyc"), dirs_exist_ok=True
126
+ )
127
+ shutil.copytree(
128
+ root / "scripts",
129
+ snapshot / "scripts",
130
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"),
131
+ dirs_exist_ok=True,
132
+ )
133
+ shutil.copytree(
134
+ root.parent / "taskanchor", snapshot / "taskanchor",
135
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc"), dirs_exist_ok=True,
136
+ )
137
+ metadata["source_sha256"] = {
138
+ str(path.relative_to(snapshot)): hashlib.sha256(path.read_bytes()).hexdigest()
139
+ for path in sorted(snapshot.rglob("*.py"))
140
+ }
141
+ metadata_path.write_text(json.dumps(metadata, indent=2))
142
+ logging.info(
143
+ "Spatial memory: max_task_frames=%d history_frames=%d tokens_per_frame=16 memory_tokens=%d",
144
+ index.max_task_frames,
145
+ index.history_frames,
146
+ (index.history_frames + 1) * 16,
147
+ )
148
+ logging.info("Initializing sharded pi0.5 + adapter")
149
+ state, state_sharding = init_state(cfg, args, mesh, resume)
150
+ checkpoint_data = CheckpointData(dc)
151
+ if resume:
152
+ state = checkpoints.restore_state(manager, state, checkpoint_data, step=args.resume_step)
153
+ step0 = int(state.step)
154
+ if args.resume_step is not None and (not resume or step0 != args.resume_step):
155
+ raise ValueError(f"Expected resume step {args.resume_step}, restored {step0}")
156
+ logging.info(
157
+ "Model initialized; start_step=%d total_steps=%d freeze_steps=%d", step0, args.steps, args.freeze_steps
158
+ )
159
+ run_training_loop(
160
+ args,
161
+ cfg,
162
+ dataset,
163
+ data_sharding,
164
+ replicated,
165
+ state_sharding,
166
+ state,
167
+ step0,
168
+ mesh,
169
+ manager,
170
+ checkpoint_data,
171
+ metadata,
172
+ )
173
+
174
+
175
+ def build_argparser():
176
+ parser = argparse.ArgumentParser()
177
+ parser.add_argument("--dataset", default=str(DATA_ROOT))
178
+ parser.add_argument("--base-params", default=str(BASE_PARAMS))
179
+ parser.add_argument("--cache", default=str(CACHE_ROOT))
180
+ parser.add_argument("--assets-dir", default=str(ASSETS_DIR), help="Directory containing pi05_libero/norm_stats.json")
181
+ parser.add_argument("--run-name", default="main_v5_spatial4x4_h191_a20_exec10_bs256_5k50k")
182
+ parser.add_argument("--checkpoint-root", default=str(CHECKPOINT_ROOT))
183
+ parser.add_argument("--steps", type=int, default=55000)
184
+ parser.add_argument("--freeze-steps", type=int, default=5000)
185
+ parser.add_argument("--batch-size", type=int, default=256)
186
+ parser.add_argument("--action-horizon", type=int, default=20)
187
+ parser.add_argument(
188
+ "--execution-horizon",
189
+ type=int,
190
+ default=10,
191
+ help="Execution length for future deployment; training supervises the full action horizon",
192
+ )
193
+ parser.add_argument("--fsdp-devices", type=int, default=8)
194
+ parser.add_argument("--workers", type=int, default=8)
195
+ parser.add_argument("--save-interval", type=int, default=1000)
196
+ parser.add_argument("--log-interval", type=int, default=20)
197
+ parser.add_argument("--resume", action="store_true")
198
+ parser.add_argument("--resume-step", type=int, default=None)
199
+ parser.add_argument("--shared-memory-loader", action="store_true")
200
+ parser.add_argument("--prefetch-factor", type=int, default=2)
201
+ parser.add_argument("--device-prefetch", type=int, default=2)
202
+ return parser
203
+
204
+
205
+ if __name__ == "__main__":
206
+ main(build_argparser().parse_args())
code/pi05/src/openpi/__init__.py ADDED
File without changes