Instructions to use ar-mine/cosmos-robometer-private-code with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ar-mine/cosmos-robometer-private-code with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("ar-mine/cosmos-robometer-private-code", device_map="auto") - PEFT
How to use ar-mine/cosmos-robometer-private-code with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
Phase 2.1A Robometer-Low training kit v2
Browse files- .gitignore +14 -0
- README.md +67 -0
- TRAINING_MANUAL.md +369 -0
- configs/phase21a_robometer_low_full_smoke_v1.yaml +58 -0
- configs/phase21a_robometer_low_lora_train_v2.yaml +88 -0
- configs/phase21a_robometer_low_smoke_v1.yaml +83 -0
- environment/phase21a_robometer/.python-version +1 -0
- environment/phase21a_robometer/README.md +36 -0
- environment/phase21a_robometer/doctor.py +78 -0
- environment/phase21a_robometer/pyproject.toml +40 -0
- environment/phase21a_robometer/system-manifest.json +37 -0
- environment/phase21a_robometer/uv.lock +814 -0
- manifests/phase21a_training_environment_v1.json +42 -0
- manifests/phase2_remediation_pairs_v1_summary.json +26 -0
- provenance/source_revisions.yaml +17 -0
- reports/PHASE21A_ROBOMETER_LOW_SMOKE_V1.md +64 -0
- reports/PHASE21A_STEP_BUDGET_V2.md +50 -0
- reports/PHASE21A_TRAINING_MANUAL.md +369 -0
- reports/phase21a_robometer_low_smoke_v1.json +89 -0
- scripts/build_phase21a_hf_dataset_bundle.py +119 -0
- scripts/run_phase21a_robometer_low_lora_seeds.py +71 -0
- scripts/smoke_phase21a_robometer_low.py +526 -0
- scripts/smoke_phase21a_robometer_low_full.py +371 -0
- scripts/train_phase21a_robometer_low_lora.py +519 -0
- src/__init__.py +1 -0
- src/data_contract.py +467 -0
- src/reward_heads.py +173 -0
- tests/conftest.py +9 -0
- tests/test_data_contract.py +184 -0
.gitignore
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.venv/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*.pt
|
| 5 |
+
*.pth
|
| 6 |
+
*.safetensors
|
| 7 |
+
*.npz
|
| 8 |
+
hf_cache/
|
| 9 |
+
runs/
|
| 10 |
+
outputs/
|
| 11 |
+
wandb/
|
| 12 |
+
*.log
|
| 13 |
+
.env
|
| 14 |
+
*token*
|
README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: transformers
|
| 3 |
+
tags:
|
| 4 |
+
- robotics
|
| 5 |
+
- reward-modeling
|
| 6 |
+
- qwen3-vl
|
| 7 |
+
- peft
|
| 8 |
+
- private-training-code
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# Cosmos–Robometer Phase 2.1A training code
|
| 12 |
+
|
| 13 |
+
Private, portable training kit for the approved `Public-Proxy stage2_core_v1`
|
| 14 |
+
experiments:
|
| 15 |
+
|
| 16 |
+
- `Robometer-Low-LoRA`: implemented formal trainer with frozen ID-dev evaluation,
|
| 17 |
+
early stopping, and a 3,000-step hard cap.
|
| 18 |
+
- `Robometer-Low`: full language-decoder smoke test. Formal training remains
|
| 19 |
+
intentionally blocked until suitable multi-GPU/server memory is available.
|
| 20 |
+
|
| 21 |
+
No dataset files, model weights, checkpoints, Hugging Face cache, local virtual
|
| 22 |
+
environment, credentials, OOD evaluation data, or machine-specific run logs are
|
| 23 |
+
included.
|
| 24 |
+
|
| 25 |
+
## Recreate the environment
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
uv python install 3.10.12
|
| 29 |
+
uv sync --project environment/phase21a_robometer --frozen
|
| 30 |
+
uv run --project environment/phase21a_robometer \
|
| 31 |
+
python environment/phase21a_robometer/doctor.py
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
The environment is locked for PyTorch 2.8.0/CUDA 12.8. The reference smoke
|
| 35 |
+
hardware was one RTX 5090 with 32,607 MiB VRAM.
|
| 36 |
+
|
| 37 |
+
## External inputs
|
| 38 |
+
|
| 39 |
+
- Base model: `unsloth/Qwen3-VL-4B-Instruct` at revision
|
| 40 |
+
`252d592b59b0233b226875a44ac135cfa1d3f755`.
|
| 41 |
+
- Dataset route: `Public-Proxy stage2_core_v1` derived from
|
| 42 |
+
`robometer/processed_datasets` at revision
|
| 43 |
+
`1a8eb2d279c70964fadeaf8019a77c490c3abe02`.
|
| 44 |
+
- Frozen Phase 2.1A subset: 2,689 pair rows referencing 4,323 trajectory files.
|
| 45 |
+
|
| 46 |
+
The planned dataset repository is
|
| 47 |
+
`ar-mine/cosmos-robometer-public-dataset`.
|
| 48 |
+
Until it is available, the subset can be reconstructed from the pinned official
|
| 49 |
+
Robometer processed-data repository and the frozen dataset manifest.
|
| 50 |
+
|
| 51 |
+
## Training
|
| 52 |
+
|
| 53 |
+
See [TRAINING_MANUAL.md](TRAINING_MANUAL.md). The frozen LoRA configuration is
|
| 54 |
+
`configs/phase21a_robometer_low_lora_train_v2.yaml`.
|
| 55 |
+
|
| 56 |
+
## Current support boundary
|
| 57 |
+
|
| 58 |
+
The LoRA trainer is runnable and validated at micro-batch 32, gradient
|
| 59 |
+
accumulation 2, effective batch 64. The full-tune file is a capacity smoke test,
|
| 60 |
+
not a formal distributed trainer. Paged 8-bit AdamW is not permitted for the
|
| 61 |
+
formal `Robometer-Low` comparison.
|
| 62 |
+
|
| 63 |
+
## Redistribution
|
| 64 |
+
|
| 65 |
+
This private repository contains experiment code and metadata only. It does not
|
| 66 |
+
grant redistribution rights for upstream datasets or model weights. Review the
|
| 67 |
+
terms of each upstream source before making any derived dataset public.
|
TRAINING_MANUAL.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 2.1A 训练手册
|
| 2 |
+
|
| 3 |
+
日期:2026-08-27
|
| 4 |
+
对象:`Robometer-Low-LoRA` 与 `Robometer-Low`
|
| 5 |
+
数据:`Public-Proxy stage2_core_v1` 的冻结 5% pair manifest
|
| 6 |
+
|
| 7 |
+
## 1. 名称和不可变边界
|
| 8 |
+
|
| 9 |
+
- `Robometer-Low-LoRA`:Qwen3-VL-4B 视觉塔和语言基座冻结;训练 attention LoRA、五个新增
|
| 10 |
+
token rows 和 progress/success/preference 三头。
|
| 11 |
+
- `Robometer-Low`:视觉塔冻结;语言 decoder 全量微调;训练三个 heads;`use_peft=false`。
|
| 12 |
+
- `Low` 只表示使用非完整版 Public-Proxy 数据,不表示量化或 LoRA。
|
| 13 |
+
- 两者使用同一 2,021-pair train split、同一 A/B 联合输入、同一 8+8 帧、同一三损失合同。
|
| 14 |
+
- Robometer-Low 正式训练只允许标准 `torch.optim.AdamW`;禁止 paged AdamW 8-bit。
|
| 15 |
+
- OOD benchmark 不进入训练 loop、checkpoint selection 或超参数选择。
|
| 16 |
+
|
| 17 |
+
冻结数据统计:2,689 rows,其中 train/dev/confirmation-dev/excluded 分别为
|
| 18 |
+
2,021/108/538/22;manifest SHA256 为
|
| 19 |
+
`c77642e3d34177d1bfbcebe8c97cdf0af952c83d95a677cf42d95426b6d49204`。
|
| 20 |
+
|
| 21 |
+
## 2. 当前代码成熟度
|
| 22 |
+
|
| 23 |
+
| 分支 | 状态 | 可以做什么 |
|
| 24 |
+
|---|---|---|
|
| 25 |
+
| Robometer-Low-LoRA v1 | 已按用户要求停止 | 日志到 step 246;最新完整 checkpoint 为 step 4 |
|
| 26 |
+
| Robometer-Low-LoRA v2 | 已配置,未正式启动 | 3,000-step cap、在线 ID-dev、早停和最佳 checkpoint 恢复 |
|
| 27 |
+
| Robometer-Low | 等待服务器资源和正式 runner | 单卡容量 smoke;不能把当前 smoke 当正式训练入口 |
|
| 28 |
+
|
| 29 |
+
LoRA v1 已停止且不再续训。v2 使用全新 output root;禁止拿 v1 的 step-4 checkpoint 恢复到 v2,
|
| 30 |
+
因为总预算、scheduler 和选择协议均已改变。
|
| 31 |
+
|
| 32 |
+
## 3. 参考环境
|
| 33 |
+
|
| 34 |
+
已验证环境:
|
| 35 |
+
|
| 36 |
+
| 项目 | 版本 |
|
| 37 |
+
|---|---|
|
| 38 |
+
| OS | Ubuntu 22.04.5 LTS |
|
| 39 |
+
| Python | 3.10.12 |
|
| 40 |
+
| NVIDIA driver | 580.173.02 |
|
| 41 |
+
| PyTorch / CUDA runtime | 2.8.0+cu128 / 12.8 |
|
| 42 |
+
| torchvision | 0.23.0+cu128 |
|
| 43 |
+
| transformers | 4.57.6 |
|
| 44 |
+
| PEFT | 0.19.1 |
|
| 45 |
+
| accelerate | 1.14.0 |
|
| 46 |
+
| flash-attn | 2.8.3.post1 |
|
| 47 |
+
| qwen-vl-utils | 0.0.14 |
|
| 48 |
+
| NumPy / Pillow | 1.26.4 / 11.3.0 |
|
| 49 |
+
|
| 50 |
+
可部署环境固定在 `environment/phase21a_robometer/pyproject.toml` 和 `uv.lock`,不复制 `.venv`:
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
cd /srv/phase21a-kit
|
| 54 |
+
uv python install 3.10.12
|
| 55 |
+
uv sync --project environment/phase21a_robometer --frozen
|
| 56 |
+
uv run --project environment/phase21a_robometer \
|
| 57 |
+
python environment/phase21a_robometer/doctor.py
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
如果上传包内保存了相同 `pyproject.toml` 和 `uv.lock`,仍要在目标 CUDA/driver 上重新创建环境。
|
| 61 |
+
不要为上传工具升级训练 venv;`hf` CLI 可使用单独小环境。
|
| 62 |
+
|
| 63 |
+
## 4. 下载并固定输入
|
| 64 |
+
|
| 65 |
+
以下使用示例变量;按服务器目录修改:
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
PHASE21A_KIT=/srv/phase21a-kit
|
| 69 |
+
PHASE21A_DATA=/srv/phase21a-data
|
| 70 |
+
PHASE21A_MODEL=/srv/models/qwen3-vl-4b-instruct-252d592
|
| 71 |
+
PHASE21A_RUNS=/srv/phase21a-runs
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
下载 training kit 和 dataset:
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
hf download ar-mine/cosmos-robometer-private-code \
|
| 78 |
+
--revision <KIT_COMMIT> \
|
| 79 |
+
--local-dir "$PHASE21A_KIT"
|
| 80 |
+
|
| 81 |
+
hf download ar-mine/cosmos-robometer-public-dataset \
|
| 82 |
+
--repo-type dataset \
|
| 83 |
+
--revision <DATA_COMMIT> \
|
| 84 |
+
--local-dir "$PHASE21A_DATA"
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
下载精确基础模型;不要使用浮动 `main`:
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
hf download unsloth/Qwen3-VL-4B-Instruct \
|
| 91 |
+
--revision 252d592b59b0233b226875a44ac135cfa1d3f755 \
|
| 92 |
+
--local-dir "$PHASE21A_MODEL"
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## 5. 开跑前共同门禁
|
| 96 |
+
|
| 97 |
+
### 5.1 文件完整性
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
cd "$PHASE21A_DATA"
|
| 101 |
+
sha256sum --check SHA256SUMS
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
再运行 dataset bundle 工具的 dry-run,把 `--output-dir` 指向任意不会执行复制的路径:
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
cd "$PHASE21A_KIT"
|
| 108 |
+
uv run --project environment/phase21a_robometer python scripts/build_phase21a_hf_dataset_bundle.py \
|
| 109 |
+
--manifest "$PHASE21A_DATA/manifests/phase2_remediation_pairs_v1.jsonl" \
|
| 110 |
+
--processed-root "$PHASE21A_DATA/processed_datasets" \
|
| 111 |
+
--output-dir /tmp/phase21a-dry-run
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
必须得到:2,689 rows、4,323 unique frame files、0 missing、7.908 GiB。
|
| 115 |
+
|
| 116 |
+
### 5.2 GPU/软件门禁
|
| 117 |
+
|
| 118 |
+
```bash
|
| 119 |
+
nvidia-smi
|
| 120 |
+
uv run --project environment/phase21a_robometer \
|
| 121 |
+
python environment/phase21a_robometer/doctor.py
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
必须确认:BF16 可用、FlashAttention 2 可加载、模型 snapshot 离线可读、目标输出盘有足够空间。
|
| 125 |
+
|
| 126 |
+
### 5.3 数据和评测隔离
|
| 127 |
+
|
| 128 |
+
- formal trainer 只读取 `split=train` 且 `sample_type=preference`,精确为 2,021 pairs。
|
| 129 |
+
- dev/confirmation-dev 只能用于 ID 报告或预注册 checkpoint selection。
|
| 130 |
+
- `excluded_original_dev_component` 不得回流训练。
|
| 131 |
+
- RBM-EVAL-OOD 不下载到训练节点也可以;不得据其结果调参。
|
| 132 |
+
|
| 133 |
+
## 6. Robometer-Low-LoRA
|
| 134 |
+
|
| 135 |
+
### 6.1 冻结配置
|
| 136 |
+
|
| 137 |
+
| 参数 | 值 |
|
| 138 |
+
|---|---:|
|
| 139 |
+
| base | `unsloth/Qwen3-VL-4B-Instruct@252d592...` |
|
| 140 |
+
| frames per pair | A 8 + B 8 |
|
| 141 |
+
| canvas | 256×256 letterbox |
|
| 142 |
+
| LoRA | r=8, alpha=16, dropout=0.05 |
|
| 143 |
+
| LoRA targets | q/k/v/o projections |
|
| 144 |
+
| trainable | LoRA + 5 special-token rows + 3 heads |
|
| 145 |
+
| trainable params | 15,768,332 |
|
| 146 |
+
| micro batch | 32 pairs |
|
| 147 |
+
| gradient accumulation | 2 |
|
| 148 |
+
| effective batch | 64 pairs |
|
| 149 |
+
| precision | BF16 |
|
| 150 |
+
| attention | FlashAttention 2 |
|
| 151 |
+
| activation checkpointing | enabled |
|
| 152 |
+
| optimizer | standard AdamW |
|
| 153 |
+
| LoRA LR / heads LR | 2e-6 / 1e-4 |
|
| 154 |
+
| weight decay | 0.01 |
|
| 155 |
+
| scheduler | cosine, 5% warmup |
|
| 156 |
+
| grad clip | 1.0 |
|
| 157 |
+
| steps | 3,000 hard cap per seed;ID-dev early stop |
|
| 158 |
+
| seeds | 7, 17, 27 |
|
| 159 |
+
| checkpoint/eval | every 250 steps;min 1,000;patience 4;min delta 0.005 |
|
| 160 |
+
|
| 161 |
+
新增 token 的顺序必须保持:
|
| 162 |
+
|
| 163 |
+
```text
|
| 164 |
+
<|split_token|>, <|reward_token|>, <|pref_token|>, <|sim_token|>, <|prog_token|>
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
在当前 base revision 上对应 ID 为 151669–151673。
|
| 168 |
+
|
| 169 |
+
### 6.2 单步 smoke
|
| 170 |
+
|
| 171 |
+
在新服务器上必须先跑最长 prompt pool 的真实一步:
|
| 172 |
+
|
| 173 |
+
```bash
|
| 174 |
+
cd "$PHASE21A_KIT"
|
| 175 |
+
uv run --project environment/phase21a_robometer python scripts/smoke_phase21a_robometer_low.py \
|
| 176 |
+
--snapshot "$PHASE21A_MODEL" \
|
| 177 |
+
--manifest "$PHASE21A_DATA/manifests/phase2_remediation_pairs_v1.jsonl" \
|
| 178 |
+
--processed-root "$PHASE21A_DATA/processed_datasets" \
|
| 179 |
+
--micro-batch-pairs 32 \
|
| 180 |
+
--gradient-accumulation 2 \
|
| 181 |
+
--optimizer-steps 1 \
|
| 182 |
+
--pair-pool-size 8 \
|
| 183 |
+
--seed 7 \
|
| 184 |
+
--lora-lr 2e-6 \
|
| 185 |
+
--head-lr 1e-4 \
|
| 186 |
+
--output /tmp/phase21a_lora_smoke.json
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
通过条件:
|
| 190 |
+
|
| 191 |
+
- `status=pass`;
|
| 192 |
+
- effective batch 精确 64;
|
| 193 |
+
- sequence max 不超过已知最长 1,319 tokens;
|
| 194 |
+
- 三个 loss 有限;
|
| 195 |
+
- LoRA、token rows、三个 heads 的梯度都有限且非零;
|
| 196 |
+
- 峰值 reserved 不超过本机显存安全线。RTX 5090 参考值为 28.150 GiB,但其他 GPU 不应机械套用。
|
| 197 |
+
|
| 198 |
+
### 6.3 正式启动
|
| 199 |
+
|
| 200 |
+
portable runner 已支持显式模型、manifest 和数据路径:
|
| 201 |
+
|
| 202 |
+
```bash
|
| 203 |
+
cd "$PHASE21A_KIT"
|
| 204 |
+
uv run --project environment/phase21a_robometer python scripts/run_phase21a_robometer_low_lora_seeds.py \
|
| 205 |
+
--snapshot "$PHASE21A_MODEL" \
|
| 206 |
+
--manifest "$PHASE21A_DATA/manifests/phase2_remediation_pairs_v1.jsonl" \
|
| 207 |
+
--processed-root "$PHASE21A_DATA/processed_datasets" \
|
| 208 |
+
--output-root "$PHASE21A_RUNS/robometer-low-lora-v2" \
|
| 209 |
+
--seeds 7 17 27 \
|
| 210 |
+
--optimizer-steps 3000 \
|
| 211 |
+
--micro-batch-pairs 32 \
|
| 212 |
+
--gradient-accumulation 2 \
|
| 213 |
+
--lora-lr 2e-6 \
|
| 214 |
+
--head-lr 1e-4 \
|
| 215 |
+
--checkpoint-every 250 \
|
| 216 |
+
--eval-every 250 \
|
| 217 |
+
--early-stop-min-steps 1000 \
|
| 218 |
+
--early-stop-patience-evals 4 \
|
| 219 |
+
--early-stop-min-delta 0.005
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
生产服务器上应由 Slurm、systemd 或其他作业调度器托管;不要依赖普通 SSH shell 的 `&`。每次只运行
|
| 223 |
+
一个 runner,避免两个进程写同一 output root。
|
| 224 |
+
|
| 225 |
+
### 6.4 恢复
|
| 226 |
+
|
| 227 |
+
runner 会检测每个 seed 的:
|
| 228 |
+
|
| 229 |
+
- 有 `summary.json`:跳过已完成 seed;
|
| 230 |
+
- 有 `latest_training_state.pt`:自动加 `--resume`;
|
| 231 |
+
- 两者都没有:从 step 0 开始。
|
| 232 |
+
|
| 233 |
+
完整 checkpoint 包含 adapter、heads、AdamW、scheduler、NumPy/PyTorch/CUDA RNG、数据 permutation
|
| 234 |
+
和 cursor。恢复前不得修改 manifest;trainer 会核对 manifest SHA256 和 seed。
|
| 235 |
+
|
| 236 |
+
### 6.5 监控
|
| 237 |
+
|
| 238 |
+
```bash
|
| 239 |
+
tail -f "$PHASE21A_RUNS/robometer-low-lora-v2/seed_7/progress.jsonl"
|
| 240 |
+
nvidia-smi
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
每条 progress record 应包含三个 loss、全局和分组梯度、LR、序列长度、step time 和显存。立即停止的
|
| 244 |
+
异常包括:NaN/Inf、任一必要梯度组为零、manifest hash 改变、frame file 缺失或 OOM。
|
| 245 |
+
|
| 246 |
+
RTX 5090 当前参考吞吐约 23–24 秒/step、2.7 pairs/s;硬件和温控会改变 ETA。
|
| 247 |
+
|
| 248 |
+
### 6.6 完成产物
|
| 249 |
+
|
| 250 |
+
每个 seed 完成后应出现:
|
| 251 |
+
|
| 252 |
+
```text
|
| 253 |
+
seed_<n>/
|
| 254 |
+
├── adapter/
|
| 255 |
+
├── reward_heads.pt
|
| 256 |
+
├── best_model_state.pt
|
| 257 |
+
├── latest_training_state.pt
|
| 258 |
+
├── progress.jsonl
|
| 259 |
+
├── id_dev_metrics.jsonl
|
| 260 |
+
└── summary.json
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
发布前另外保存 processor/tokenizer 和特殊 token map;对外权重优先使用 safetensors。v2 的
|
| 264 |
+
`adapter/` 和 `reward_heads.pt` 会恢复为 ID-dev 最佳 step,而不是机械使用最后一步。
|
| 265 |
+
|
| 266 |
+
## 7. Robometer-Low 全量 decoder 训练
|
| 267 |
+
|
| 268 |
+
### 7.1 当前禁止直接正式启动
|
| 269 |
+
|
| 270 |
+
单张 RTX 5090 上,标准 AdamW 即使 `micro_batch_pairs=1` 也在第一次 optimizer-state 初始化时 OOM。
|
| 271 |
+
PagedAdamW8bit 虽能跑通容量 smoke,但用户已明确禁止其用于正式 Robometer-Low。
|
| 272 |
+
|
| 273 |
+
当前 `smoke_phase21a_robometer_low_full.py` 只是容量和梯度审计工具,不具备:
|
| 274 |
+
|
| 275 |
+
- 多 GPU/FSDP;
|
| 276 |
+
- 正式 3,000-step cap 和 ID-dev 早停的可恢复状态;
|
| 277 |
+
- distributed RNG/data cursor 恢复;
|
| 278 |
+
- sharded optimizer checkpoint;
|
| 279 |
+
- 三 seed runner。
|
| 280 |
+
|
| 281 |
+
因此在正式 full runner 实现并通过下述门禁前,Robometer-Low 状态保持
|
| 282 |
+
`waiting_for_server_resources`,不能把 smoke 循环延长后冒充正式训练。
|
| 283 |
+
|
| 284 |
+
### 7.2 目标正式配置
|
| 285 |
+
|
| 286 |
+
| 参数 | 目标值 |
|
| 287 |
+
|---|---:|
|
| 288 |
+
| vision encoder | frozen |
|
| 289 |
+
| language decoder | full trainable,`use_peft=false` |
|
| 290 |
+
| heads | full trainable |
|
| 291 |
+
| language trainable params | 约 4.0218B |
|
| 292 |
+
| heads trainable params | 9.857M |
|
| 293 |
+
| optimizer | standard torch AdamW,禁止 bitsandbytes/paged |
|
| 294 |
+
| LR | 2e-5 |
|
| 295 |
+
| weight decay | 0.01 |
|
| 296 |
+
| precision/attention | BF16 / FlashAttention 2 |
|
| 297 |
+
| activation checkpointing | enabled |
|
| 298 |
+
| scheduler | cosine,5% warmup |
|
| 299 |
+
| grad clip | 10.0(与当前 full smoke/官方设置一致) |
|
| 300 |
+
| global effective batch | 至少且优先精确为 64 pairs |
|
| 301 |
+
| steps/seeds | 3,000 hard cap + 与 LoRA 相同 ID-dev 早停;7/17/27 |
|
| 302 |
+
|
| 303 |
+
分布式 global batch 计算公式:
|
| 304 |
+
|
| 305 |
+
```text
|
| 306 |
+
global_effective_batch = micro_batch_pairs_per_gpu × world_size × gradient_accumulation
|
| 307 |
+
```
|
| 308 |
+
|
| 309 |
+
例如 4 GPU、每卡 micro batch 8、accumulation 2 得到 64;这只是算术示例,必须以目标硬件 capacity
|
| 310 |
+
smoke 为准。
|
| 311 |
+
|
| 312 |
+
### 7.3 正式 runner 的实现门禁
|
| 313 |
+
|
| 314 |
+
正式代码必须满足:
|
| 315 |
+
|
| 316 |
+
1. 复用 LoRA v1 的 portable manifest loader、联合 A/B prompt、五 token 顺序和三 heads;
|
| 317 |
+
2. 只冻结视觉塔,语言 decoder 全量 `requires_grad=True`;
|
| 318 |
+
3. optimizer 类型审计精确为 `torch.optim.AdamW`,代码路径不导入 bitsandbytes;
|
| 319 |
+
4. FSDP `FULL_SHARD` 包裹 `Qwen3VLTextDecoderLayer`,并验证无未分片的巨型语言层;
|
| 320 |
+
5. global effective batch 精确记录并达到 64;
|
| 321 |
+
6. 每 rank 数据不重复,分布式 sampler 的 epoch/order/cursor 可恢复;
|
| 322 |
+
7. checkpoint 包含模型、heads、optimizer、scheduler、scaler(如有)、全部 RNG 和 sampler state;
|
| 323 |
+
8. 中间 checkpoint 优先 sharded state,最终再受控合并,避免 rank 0 保存时 OOM;
|
| 324 |
+
9. rank 0 写日志,所有 rank 对 NaN/OOM/数据异常做同步失败;
|
| 325 |
+
10. OOD 不进入训练或选择。
|
| 326 |
+
|
| 327 |
+
上游 Robometer 的 `robometer/configs/distributed/fsdp.yaml` 可作为 FSDP 配置参考,但官方
|
| 328 |
+
`train.py` 不能直接替代本实验 runner:其默认 PEFT 为 r=32 且覆盖 MLP projections,数据采样和当前
|
| 329 |
+
冻结 manifest 也不同。直接运行官方命令会形成另一个 protocol。
|
| 330 |
+
|
| 331 |
+
### 7.4 服务器验收顺序
|
| 332 |
+
|
| 333 |
+
1. 在隔离进程中加载模型,确认视觉 trainable=0、语言和 heads 数量正确;
|
| 334 |
+
2. 用最长 prompt 跑一次 FSDP forward/backward/标准 AdamW step;
|
| 335 |
+
3. 尝试相邻 micro batches,选择峰值显存不超过 90% 的最大安全值;
|
| 336 |
+
4. 通过 accumulation 调整 global effective batch 到 64;
|
| 337 |
+
5. 跑 100-step stability smoke,要求 0 NaN/Inf、0 OOM、0 empty mask;
|
| 338 |
+
6. 在 step 50 保存,重启后恢复到 51,并与不中断参考运行核对 loss/LR/data cursor;
|
| 339 |
+
7. 固定代码 commit、FSDP config hash、world size 和环境后,才允许三 seed 正式启动。
|
| 340 |
+
|
| 341 |
+
### 7.5 多 GPU checkpoint 和发布
|
| 342 |
+
|
| 343 |
+
- 训练 checkpoint 保留 sharded optimizer state,供相同或兼容 world size 恢复。
|
| 344 |
+
- 发布 checkpoint 只在训练结束后合并;合并节点要有足够 CPU RAM 和磁盘临时空间。
|
| 345 |
+
- 模型卡必须注明 base revision、数据 commit、world size、global batch、FSDP strategy 和标准 AdamW。
|
| 346 |
+
- 任何使用 8-bit/paged optimizer 的结果必须另命名为实验性分支,不能称为 Robometer-Low。
|
| 347 |
+
|
| 348 |
+
## 8. 最终开跑检查表
|
| 349 |
+
|
| 350 |
+
### LoRA
|
| 351 |
+
|
| 352 |
+
- [ ] kit/data/model 三个 commit/revision 已固定
|
| 353 |
+
- [ ] SHA256 全通过,4,323/4,323 frame files 可解析
|
| 354 |
+
- [ ] 单步 smoke 通过且显存安全
|
| 355 |
+
- [ ] output root 全新或只有可信完整 checkpoint
|
| 356 |
+
- [ ] 作业调度器托管,只有一个 runner
|
| 357 |
+
- [ ] seeds 7/17/27、3,000-step cap、ID-dev 早停、effective batch 64
|
| 358 |
+
- [ ] OOD 未接入训练 loop
|
| 359 |
+
|
| 360 |
+
### Full
|
| 361 |
+
|
| 362 |
+
- [ ] 服务器资源已分配
|
| 363 |
+
- [ ] 自定义正式 FSDP runner 已实现和 code review
|
| 364 |
+
- [ ] standard AdamW 类型审计通过,代码不走 bitsandbytes
|
| 365 |
+
- [ ] 100-step 和断点恢复一致性门禁通过
|
| 366 |
+
- [ ] global effective batch=64
|
| 367 |
+
- [ ] vision frozen、language full trainable
|
| 368 |
+
- [ ] 中间 sharded checkpoint 与最终合并流程均验证
|
| 369 |
+
- [ ] 完成上述全部条件后才从 `waiting_for_server_resources` 改为 `running`
|
configs/phase21a_robometer_low_full_smoke_v1.yaml
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
protocol_id: phase21a_robometer_low_full_smoke_v1
|
| 2 |
+
status: smoke_only
|
| 3 |
+
formal_training_allowed: false
|
| 4 |
+
cosmos3_edge_modification_allowed: false
|
| 5 |
+
|
| 6 |
+
naming:
|
| 7 |
+
robometer_low: full_language_decoder_tuning_on_non_full_public_proxy_data
|
| 8 |
+
robometer_low_lora: attention_lora_on_non_full_public_proxy_data
|
| 9 |
+
low_meaning: dataset_scale_not_parameter_tuning_method
|
| 10 |
+
|
| 11 |
+
data:
|
| 12 |
+
route: Public-Proxy stage2_core_v1
|
| 13 |
+
manifest: manifests/phase2_remediation_pairs_v1.jsonl
|
| 14 |
+
split: train
|
| 15 |
+
frames_per_trajectory: 8
|
| 16 |
+
joint_frames_per_pair: 16
|
| 17 |
+
canvas: [256, 256]
|
| 18 |
+
smoke_pair_pool: 8
|
| 19 |
+
smoke_pair_pool_selection: longest_prompt_characters_in_train_split
|
| 20 |
+
|
| 21 |
+
architecture:
|
| 22 |
+
base: Qwen3-VL-4B-Instruct
|
| 23 |
+
vision_frozen: true
|
| 24 |
+
language_decoder_full_tune: true
|
| 25 |
+
peft_or_lora: false
|
| 26 |
+
new_special_token_rows_trainable: true
|
| 27 |
+
heads_trainable: [progress, success, preference]
|
| 28 |
+
joint_trajectory_a_b_forward: true
|
| 29 |
+
per_frame_progress_token: <|prog_token|>
|
| 30 |
+
pair_preference_token: <|pref_token|>
|
| 31 |
+
progress_bins: 10
|
| 32 |
+
|
| 33 |
+
optimization:
|
| 34 |
+
precision: BF16
|
| 35 |
+
attention: flash_attention_2
|
| 36 |
+
gradient_checkpointing: true
|
| 37 |
+
learning_rate: 2.0e-5
|
| 38 |
+
weight_decay: 0.01
|
| 39 |
+
gradient_clip: 10.0
|
| 40 |
+
minimum_effective_batch_pairs: 64
|
| 41 |
+
optimizer_trials:
|
| 42 |
+
- name: adamw_torch
|
| 43 |
+
official_default: true
|
| 44 |
+
foreach: false
|
| 45 |
+
- name: paged_adamw_8bit
|
| 46 |
+
official_default: false
|
| 47 |
+
purpose: single_gpu_full_parameter_fallback_if_adamw_state_oom
|
| 48 |
+
|
| 49 |
+
capacity_selection:
|
| 50 |
+
each_candidate_in_fresh_process: true
|
| 51 |
+
memory_headroom_rule: peak_reserved_fraction_lte_0.90
|
| 52 |
+
final_gradient_accumulation: ceil(64 / selected_micro_batch_pairs)
|
| 53 |
+
final_validation_optimizer_steps: 1
|
| 54 |
+
|
| 55 |
+
outputs:
|
| 56 |
+
root: runs/phase21a_robometer_low_full_smoke_v1
|
| 57 |
+
report_json: reports/phase21a_robometer_low_full_smoke_v1.json
|
| 58 |
+
report_markdown: reports/PHASE21A_ROBOMETER_LOW_FULL_SMOKE_V1.md
|
configs/phase21a_robometer_low_lora_train_v2.yaml
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
protocol_id: phase21a_robometer_low_lora_train_v2
|
| 2 |
+
status: configured_not_started
|
| 3 |
+
model_name: Robometer-Low-LoRA
|
| 4 |
+
low_meaning: non_full_public_proxy_dataset
|
| 5 |
+
|
| 6 |
+
data:
|
| 7 |
+
route: Public-Proxy stage2_core_v1
|
| 8 |
+
manifest: manifests/phase2_remediation_pairs_v1.jsonl
|
| 9 |
+
train_pairs: 2021
|
| 10 |
+
id_dev_pairs: 108
|
| 11 |
+
confirmation_dev_pairs: 538
|
| 12 |
+
frames_per_trajectory: 8
|
| 13 |
+
joint_frames_per_pair: 16
|
| 14 |
+
canvas: [256, 256]
|
| 15 |
+
|
| 16 |
+
architecture:
|
| 17 |
+
base: Qwen3-VL-4B-Instruct
|
| 18 |
+
base_revision: 252d592b59b0233b226875a44ac135cfa1d3f755
|
| 19 |
+
vision_frozen: true
|
| 20 |
+
language_base_frozen: true
|
| 21 |
+
language_lora:
|
| 22 |
+
rank: 8
|
| 23 |
+
alpha: 16
|
| 24 |
+
dropout: 0.05
|
| 25 |
+
targets: [q_proj, k_proj, v_proj, o_proj]
|
| 26 |
+
special_token_rows_trainable: true
|
| 27 |
+
heads_trainable: [progress, success, preference]
|
| 28 |
+
|
| 29 |
+
training:
|
| 30 |
+
seeds: [7, 17, 27]
|
| 31 |
+
optimizer_steps_cap_per_seed: 3000
|
| 32 |
+
pair_presentations_at_cap: 192000
|
| 33 |
+
nominal_epochs_at_cap: 95.0025
|
| 34 |
+
micro_batch_pairs: 32
|
| 35 |
+
gradient_accumulation: 2
|
| 36 |
+
effective_batch_pairs: 64
|
| 37 |
+
precision: BF16
|
| 38 |
+
gradient_checkpointing: true
|
| 39 |
+
attention: flash_attention_2
|
| 40 |
+
optimizer: AdamW
|
| 41 |
+
lora_lr: 2.0e-6
|
| 42 |
+
head_lr: 1.0e-4
|
| 43 |
+
weight_decay: 0.01
|
| 44 |
+
scheduler: cosine
|
| 45 |
+
warmup_fraction: 0.05
|
| 46 |
+
warmup_steps_at_cap: 150
|
| 47 |
+
gradient_clip: 1.0
|
| 48 |
+
checkpoint_every_steps: 250
|
| 49 |
+
|
| 50 |
+
model_selection:
|
| 51 |
+
split: dev
|
| 52 |
+
uses_confirmation_dev: false
|
| 53 |
+
uses_ood: false
|
| 54 |
+
evaluate_every_steps: 250
|
| 55 |
+
evaluate_both_pair_orientations: true
|
| 56 |
+
eval_micro_batch_pairs: 32
|
| 57 |
+
metric: summed_mean_id_dev_loss
|
| 58 |
+
mode: min
|
| 59 |
+
minimum_training_steps: 1000
|
| 60 |
+
patience_evaluations: 4
|
| 61 |
+
minimum_delta: 0.005
|
| 62 |
+
restore_best_checkpoint: true
|
| 63 |
+
|
| 64 |
+
budget_basis:
|
| 65 |
+
official_rbm_1m_steps: 15000
|
| 66 |
+
official_scale_interpretation: approximately_960000_presentations_at_effective_batch_64
|
| 67 |
+
low_dataset_interpretation_of_15000: approximately_475_nominal_epochs
|
| 68 |
+
v2_cap_interpretation: approximately_95_nominal_epochs
|
| 69 |
+
phase20a_reference_steps: 2000
|
| 70 |
+
decision: use_3000_as_a_hard_cap_and_select_or_stop_on_id_dev
|
| 71 |
+
|
| 72 |
+
robometer_low_full:
|
| 73 |
+
status: waiting_for_server_resources
|
| 74 |
+
optimizer_steps_cap_per_seed: 3000
|
| 75 |
+
model_selection: same_as_lora_for_matched_comparison
|
| 76 |
+
required_optimizer: official_adamw
|
| 77 |
+
paged_adamw_8bit_formal_training_allowed: false
|
| 78 |
+
|
| 79 |
+
environment:
|
| 80 |
+
project: environment/phase21a_robometer
|
| 81 |
+
lockfile: environment/phase21a_robometer/uv.lock
|
| 82 |
+
|
| 83 |
+
outputs:
|
| 84 |
+
root: runs/phase21a_robometer_low_lora_train_v2
|
| 85 |
+
progress_log_name: progress.jsonl
|
| 86 |
+
id_dev_log_name: id_dev_metrics.jsonl
|
| 87 |
+
checkpoint_name: latest_training_state.pt
|
| 88 |
+
best_model_name: best_model_state.pt
|
configs/phase21a_robometer_low_smoke_v1.yaml
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
protocol_id: phase21a_robometer_low_lora_smoke_v1
|
| 2 |
+
status: smoke_only
|
| 3 |
+
formal_training_allowed: false
|
| 4 |
+
cosmos3_edge_modification_allowed: false
|
| 5 |
+
|
| 6 |
+
scope:
|
| 7 |
+
phase: Phase 2.1A
|
| 8 |
+
model: Robometer-Low-LoRA
|
| 9 |
+
purpose: select_a_stable_gpu_saturating_micro_batch_before_formal_training
|
| 10 |
+
|
| 11 |
+
data:
|
| 12 |
+
route: Public-Proxy stage2_core_v1
|
| 13 |
+
manifest: manifests/phase2_remediation_pairs_v1.jsonl
|
| 14 |
+
split: train
|
| 15 |
+
real_pairs: true
|
| 16 |
+
frames_per_trajectory: 8
|
| 17 |
+
trajectories_per_pair: 2
|
| 18 |
+
canvas: [256, 256]
|
| 19 |
+
smoke_pair_pool: 8
|
| 20 |
+
smoke_pair_pool_selection: longest_prompt_characters_in_train_split
|
| 21 |
+
|
| 22 |
+
architecture:
|
| 23 |
+
base: Qwen3-VL-4B-Instruct
|
| 24 |
+
robometer_contract:
|
| 25 |
+
joint_trajectory_a_b_forward: true
|
| 26 |
+
per_frame_progress_token: <|prog_token|>
|
| 27 |
+
pair_preference_token: <|pref_token|>
|
| 28 |
+
split_token: <|split_token|>
|
| 29 |
+
progress_head: discrete_10_bin
|
| 30 |
+
success_head: binary
|
| 31 |
+
preference_head: direct_binary
|
| 32 |
+
head_topology: linear_half_layernorm_gelu_dropout_linear
|
| 33 |
+
preference_order_randomization: deterministic_alternation_for_smoke
|
| 34 |
+
low_resource_delta:
|
| 35 |
+
vision_frozen: true
|
| 36 |
+
language_lora:
|
| 37 |
+
rank: 8
|
| 38 |
+
alpha: 16
|
| 39 |
+
dropout: 0.05
|
| 40 |
+
targets: [q_proj, k_proj, v_proj, o_proj]
|
| 41 |
+
newly_added_special_token_rows_trainable: true
|
| 42 |
+
heads_trainable: [progress, success, preference]
|
| 43 |
+
full_language_model_training: false
|
| 44 |
+
|
| 45 |
+
optimization:
|
| 46 |
+
precision: BF16
|
| 47 |
+
attention: flash_attention_2
|
| 48 |
+
gradient_checkpointing: true
|
| 49 |
+
optimizer: AdamW
|
| 50 |
+
head_lr: 1.0e-4
|
| 51 |
+
lora_and_token_lr: 2.0e-6
|
| 52 |
+
weight_decay: 0.01
|
| 53 |
+
gradient_clip: 1.0
|
| 54 |
+
minimum_effective_batch_pairs: 64
|
| 55 |
+
|
| 56 |
+
capacity_selection:
|
| 57 |
+
candidates_micro_batch_pairs_tested: [1, 2, 3, 4, 6, 8, 16, 24, 32, 34, 35]
|
| 58 |
+
adaptive_expansion_reason: micro_batch_8_used_only_41_56_percent_peak_reserved
|
| 59 |
+
each_candidate_in_fresh_process: true
|
| 60 |
+
capacity_probe_optimizer_steps: 1
|
| 61 |
+
stable_requires:
|
| 62 |
+
- finite_total_and_component_losses
|
| 63 |
+
- finite_trainable_gradients
|
| 64 |
+
- all_three_heads_receive_gradients
|
| 65 |
+
- lora_receives_gradients
|
| 66 |
+
- special_token_rows_receive_gradients
|
| 67 |
+
- no_oom
|
| 68 |
+
memory_headroom_rule: peak_reserved_fraction_lte_0.90
|
| 69 |
+
selected_micro_batch_pairs: 32
|
| 70 |
+
selected_gradient_accumulation: 2
|
| 71 |
+
selected_effective_batch_pairs: 64
|
| 72 |
+
selection_override: user_confirmed_2026_08_27
|
| 73 |
+
rejected_adjacent_setting:
|
| 74 |
+
micro_batch_pairs: 33
|
| 75 |
+
gradient_accumulation: 2
|
| 76 |
+
reason: user_selected_micro_batch_32_for_exact_effective_batch_64
|
| 77 |
+
final_validation_optimizer_steps: 1
|
| 78 |
+
|
| 79 |
+
outputs:
|
| 80 |
+
filename_compatibility_note: legacy_paths_retain_robometer_low_prefix
|
| 81 |
+
root: runs/phase21a_robometer_low_smoke_v1
|
| 82 |
+
report_json: reports/phase21a_robometer_low_smoke_v1.json
|
| 83 |
+
report_markdown: reports/PHASE21A_ROBOMETER_LOW_SMOKE_V1.md
|
environment/phase21a_robometer/.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.10.12
|
environment/phase21a_robometer/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 2.1A uv environment
|
| 2 |
+
|
| 3 |
+
This directory is the deployable Python environment definition for
|
| 4 |
+
`Robometer-Low-LoRA` and `Robometer-Low`. Do not copy a local `.venv` between
|
| 5 |
+
machines.
|
| 6 |
+
|
| 7 |
+
## Host requirements
|
| 8 |
+
|
| 9 |
+
- Linux x86_64
|
| 10 |
+
- NVIDIA GPU with BF16 support
|
| 11 |
+
- NVIDIA driver compatible with CUDA 12.8
|
| 12 |
+
- `uv` 0.11.3 or a compatible newer release
|
| 13 |
+
- A CUDA development toolkit is needed only if no compatible prebuilt
|
| 14 |
+
FlashAttention wheel can be selected and a source build is required.
|
| 15 |
+
|
| 16 |
+
## Rebuild
|
| 17 |
+
|
| 18 |
+
From the experiment root:
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
uv python install 3.10.12
|
| 22 |
+
uv sync --project environment/phase21a_robometer --frozen
|
| 23 |
+
uv run --project environment/phase21a_robometer python environment/phase21a_robometer/doctor.py
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
The second `uv sync --frozen --dry-run` must report no changes. Run the formal
|
| 27 |
+
training smoke test before starting a seed.
|
| 28 |
+
|
| 29 |
+
The formal trainer sets `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True` before
|
| 30 |
+
initializing CUDA. Keep this setting for the validated micro-batch of 32 pairs;
|
| 31 |
+
removing it can cause allocator-fragmentation OOM near the RTX 5090 capacity
|
| 32 |
+
limit even when several GiB are reserved but unused.
|
| 33 |
+
|
| 34 |
+
The base model, public-proxy dataset, and run output are external artifacts and
|
| 35 |
+
must not be placed inside this environment directory. Pin their Hugging Face
|
| 36 |
+
revisions in the run manifest.
|
environment/phase21a_robometer/doctor.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Fail fast when a Phase 2.1A deployment cannot run the frozen trainer."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import importlib.metadata as metadata
|
| 7 |
+
import json
|
| 8 |
+
import platform
|
| 9 |
+
import sys
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
EXPECTED = {
|
| 15 |
+
"accelerate": "1.14.0",
|
| 16 |
+
"bitsandbytes": "0.49.2",
|
| 17 |
+
"flash-attn": "2.8.3.post1",
|
| 18 |
+
"numpy": "1.26.4",
|
| 19 |
+
"peft": "0.19.1",
|
| 20 |
+
"torch": "2.8.0",
|
| 21 |
+
"torchvision": "0.23.0",
|
| 22 |
+
"transformers": "4.57.6",
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def normalized(version: str) -> str:
|
| 27 |
+
return version.split("+", maxsplit=1)[0]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def main() -> None:
|
| 31 |
+
actual = {name: metadata.version(name) for name in EXPECTED}
|
| 32 |
+
mismatches = {
|
| 33 |
+
name: {"expected": expected, "actual": actual[name]}
|
| 34 |
+
for name, expected in EXPECTED.items()
|
| 35 |
+
if normalized(actual[name]) != expected
|
| 36 |
+
}
|
| 37 |
+
if mismatches:
|
| 38 |
+
raise RuntimeError(f"package version mismatch: {mismatches}")
|
| 39 |
+
if sys.version_info[:2] != (3, 10):
|
| 40 |
+
raise RuntimeError(f"Python 3.10 is required, found {platform.python_version()}")
|
| 41 |
+
if not torch.cuda.is_available():
|
| 42 |
+
raise RuntimeError("CUDA is unavailable")
|
| 43 |
+
if torch.version.cuda != "12.8":
|
| 44 |
+
raise RuntimeError(f"PyTorch CUDA 12.8 is required, found {torch.version.cuda}")
|
| 45 |
+
if not torch.cuda.is_bf16_supported():
|
| 46 |
+
raise RuntimeError("GPU does not support BF16")
|
| 47 |
+
|
| 48 |
+
import flash_attn
|
| 49 |
+
from flash_attn import flash_attn_func
|
| 50 |
+
|
| 51 |
+
device = torch.device("cuda:0")
|
| 52 |
+
query = torch.randn(1, 16, 2, 64, device=device, dtype=torch.bfloat16)
|
| 53 |
+
key = torch.randn_like(query)
|
| 54 |
+
value = torch.randn_like(query)
|
| 55 |
+
output = flash_attn_func(query, key, value, causal=True)
|
| 56 |
+
if output.shape != query.shape or not torch.isfinite(output).all():
|
| 57 |
+
raise RuntimeError("FlashAttention CUDA probe failed")
|
| 58 |
+
|
| 59 |
+
print(
|
| 60 |
+
json.dumps(
|
| 61 |
+
{
|
| 62 |
+
"status": "pass",
|
| 63 |
+
"python": platform.python_version(),
|
| 64 |
+
"packages": actual,
|
| 65 |
+
"torch_cuda": torch.version.cuda,
|
| 66 |
+
"gpu": torch.cuda.get_device_name(0),
|
| 67 |
+
"compute_capability": torch.cuda.get_device_capability(0),
|
| 68 |
+
"bf16_supported": True,
|
| 69 |
+
"flash_attn": flash_attn.__version__,
|
| 70 |
+
},
|
| 71 |
+
indent=2,
|
| 72 |
+
sort_keys=True,
|
| 73 |
+
)
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
if __name__ == "__main__":
|
| 78 |
+
main()
|
environment/phase21a_robometer/pyproject.toml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "cosmos-robometer-phase21a"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Reproducible runtime for Phase 2.1A Robometer-Low and Robometer-Low-LoRA"
|
| 5 |
+
requires-python = "==3.10.*"
|
| 6 |
+
dependencies = [
|
| 7 |
+
"accelerate==1.14.0",
|
| 8 |
+
"bitsandbytes==0.49.2",
|
| 9 |
+
"flash-attn==2.8.3.post1",
|
| 10 |
+
"hf-xet==1.5.1",
|
| 11 |
+
"huggingface-hub==0.36.2",
|
| 12 |
+
"numpy==1.26.4",
|
| 13 |
+
"peft==0.19.1",
|
| 14 |
+
"pillow==11.3.0",
|
| 15 |
+
"pyyaml==6.0.3",
|
| 16 |
+
"qwen-vl-utils==0.0.14",
|
| 17 |
+
"safetensors==0.8.0",
|
| 18 |
+
"torch==2.8.0",
|
| 19 |
+
"torchvision==0.23.0",
|
| 20 |
+
"transformers==4.57.6",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
[tool.uv]
|
| 24 |
+
package = false
|
| 25 |
+
|
| 26 |
+
# flash-attn must build or select its wheel against the exact PyTorch runtime.
|
| 27 |
+
[tool.uv.extra-build-dependencies]
|
| 28 |
+
flash-attn = [{ requirement = "torch", match-runtime = true }]
|
| 29 |
+
|
| 30 |
+
[tool.uv.extra-build-variables]
|
| 31 |
+
flash-attn = { FLASH_ATTENTION_SKIP_CUDA_BUILD = "TRUE" }
|
| 32 |
+
|
| 33 |
+
[tool.uv.sources]
|
| 34 |
+
torch = { index = "pytorch-cu128", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }
|
| 35 |
+
torchvision = { index = "pytorch-cu128", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }
|
| 36 |
+
|
| 37 |
+
[[tool.uv.index]]
|
| 38 |
+
name = "pytorch-cu128"
|
| 39 |
+
url = "https://download.pytorch.org/whl/cu128"
|
| 40 |
+
explicit = true
|
environment/phase21a_robometer/system-manifest.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"environment_id": "phase21a_robometer_uv_v1",
|
| 3 |
+
"generated_at": "2026-08-27T19:48:21+08:00",
|
| 4 |
+
"lock_sha256": "69d9354b3197aa9e7d3b649c8cab76beb50f685bee96e28e7ad55e72421a3b8d",
|
| 5 |
+
"uv_version_used_to_lock": "0.11.3",
|
| 6 |
+
"python": "3.10.12",
|
| 7 |
+
"platform": "linux_x86_64",
|
| 8 |
+
"pytorch": "2.8.0+cu128",
|
| 9 |
+
"torch_cuda": "12.8",
|
| 10 |
+
"flash_attn": "2.8.3.post1",
|
| 11 |
+
"reference_host": {
|
| 12 |
+
"os": "Ubuntu 22.04",
|
| 13 |
+
"gpu": "NVIDIA GeForce RTX 5090",
|
| 14 |
+
"gpu_memory_mib": 32607,
|
| 15 |
+
"compute_capability": "12.0",
|
| 16 |
+
"nvidia_driver": "580.173.02"
|
| 17 |
+
},
|
| 18 |
+
"target_requirements": {
|
| 19 |
+
"bf16": true,
|
| 20 |
+
"nvidia_driver_compatible_with_cuda_12_8": true,
|
| 21 |
+
"flash_attention_cuda_probe_required": true
|
| 22 |
+
},
|
| 23 |
+
"video_decode_dependency": "not_installed_multi_image_training_only",
|
| 24 |
+
"base_model": {
|
| 25 |
+
"repo_id": "unsloth/Qwen3-VL-4B-Instruct",
|
| 26 |
+
"revision": "252d592b59b0233b226875a44ac135cfa1d3f755"
|
| 27 |
+
},
|
| 28 |
+
"robometer_upstream_revision": "a669dffc241d7d76bec12f36efd4084d914d017c",
|
| 29 |
+
"verification": {
|
| 30 |
+
"clean_uv_sync": "pass",
|
| 31 |
+
"second_frozen_sync_no_changes": "pass",
|
| 32 |
+
"uv_pip_check": "pass",
|
| 33 |
+
"bf16_flash_attention_doctor": "pass",
|
| 34 |
+
"phase21a_v2_one_step_and_full_id_dev_smoke": "pass",
|
| 35 |
+
"smoke_peak_reserved_gib": 27.193359375
|
| 36 |
+
}
|
| 37 |
+
}
|
environment/phase21a_robometer/uv.lock
ADDED
|
@@ -0,0 +1,814 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
revision = 3
|
| 3 |
+
requires-python = "==3.10.*"
|
| 4 |
+
resolution-markers = [
|
| 5 |
+
"sys_platform == 'linux' or sys_platform == 'win32'",
|
| 6 |
+
"sys_platform != 'linux' and sys_platform != 'win32'",
|
| 7 |
+
]
|
| 8 |
+
|
| 9 |
+
[[package]]
|
| 10 |
+
name = "accelerate"
|
| 11 |
+
version = "1.14.0"
|
| 12 |
+
source = { registry = "https://pypi.org/simple" }
|
| 13 |
+
dependencies = [
|
| 14 |
+
{ name = "huggingface-hub" },
|
| 15 |
+
{ name = "numpy" },
|
| 16 |
+
{ name = "packaging" },
|
| 17 |
+
{ name = "psutil" },
|
| 18 |
+
{ name = "pyyaml" },
|
| 19 |
+
{ name = "safetensors" },
|
| 20 |
+
{ name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 21 |
+
{ name = "torch", version = "2.8.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 22 |
+
]
|
| 23 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8d/75/94cd5d389649578aca399e5aa822637eec18319a1dadc400ffe2f9a7493f/accelerate-1.14.0.tar.gz", hash = "sha256:41b9c4377a54e0b460a959b0defa1b736e4ca0a2373252d9a539964c2afe3c8d", size = 412167, upload-time = "2026-06-11T13:45:52.326Z" }
|
| 24 |
+
wheels = [
|
| 25 |
+
{ url = "https://files.pythonhosted.org/packages/a8/db/253133d7e7cb40d3af384bb2f5c0b4a2b7fdcffbc95c688cc67a20a3c103/accelerate-1.14.0-py3-none-any.whl", hash = "sha256:e94390c2863b873be18f623f9df48a0d8fe5eff13ea7f1a00092b0a7904888c6", size = 389246, upload-time = "2026-06-11T13:45:50.477Z" },
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
[[package]]
|
| 29 |
+
name = "av"
|
| 30 |
+
version = "17.1.0"
|
| 31 |
+
source = { registry = "https://pypi.org/simple" }
|
| 32 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5e/e3/477fa20578c284abeda08d91b63ee9abaebc93445d8feeb989d3d444bae1/av-17.1.0.tar.gz", hash = "sha256:7f1e71ff621b66253333926f948e00faae11d855b2442133c65128bca64cdeb3", size = 4288546, upload-time = "2026-06-07T05:52:55.999Z" }
|
| 33 |
+
wheels = [
|
| 34 |
+
{ url = "https://files.pythonhosted.org/packages/ca/92/c9d0cea4f6f8f93f5b15a39f99d2d593f922484f22a2d98a8d482283e15b/av-17.1.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:19c84fd72af5ef81a20f18fbc6f9aedff9e1455e53a7062c1d4c95926d73da4e", size = 22622703, upload-time = "2026-06-07T05:51:40.405Z" },
|
| 35 |
+
{ url = "https://files.pythonhosted.org/packages/dc/57/74399770aa103ee4b5ff6da1781440c91a41901d89abb2433fe88773246e/av-17.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19264c9bb4bee404accc7ce9ec461f2044b7f577a70234d29aafde31ed17de46", size = 18273538, upload-time = "2026-06-07T05:51:43.078Z" },
|
| 36 |
+
{ url = "https://files.pythonhosted.org/packages/eb/17/27c85b12e9ffa8f3f6854358b3eabcd91f3c29c7dac36843fa1376e833f4/av-17.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:22dff0ae582d10ef08c75c2150a4fd27cfc26653b54930c7c27b9f7b3aa20723", size = 34519101, upload-time = "2026-06-07T05:51:45.305Z" },
|
| 37 |
+
{ url = "https://files.pythonhosted.org/packages/04/a4/542d4bfd9f4aec5f3265985b9dbc6b259d45c2e668f9714e5f4e05b71e64/av-17.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:90c49bc9608377d01e82e747377505419a229464873341db18202d5dddecce5a", size = 36647600, upload-time = "2026-06-07T05:51:48.57Z" },
|
| 38 |
+
{ url = "https://files.pythonhosted.org/packages/63/1e/63bd5c59580f38109fa4c452b29b715a20c9a5eb3a078b3c447484593c40/av-17.1.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cc5a5247622cb77e24c342364eb68f88c1442ddfaab60c1f1f483359d3cc7879", size = 25786289, upload-time = "2026-06-07T05:51:51.674Z" },
|
| 39 |
+
{ url = "https://files.pythonhosted.org/packages/70/30/78155cef0c9f8bc13f044130192c58bf962f2c9066982ff3593afe8d27f1/av-17.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff457ed419348e5b8e8c811d341389b052c5e4d5839da3794d019b125b9fe830", size = 35599848, upload-time = "2026-06-07T05:51:54.207Z" },
|
| 40 |
+
{ url = "https://files.pythonhosted.org/packages/76/cb/ae1d7a735a5ad9dc502dba864c51d605cbe932a769218352fd570254c38e/av-17.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1370b11a697eb3f2555906f8ab3519b0cfe48425d7830a3996ad42e6bffafda5", size = 26776479, upload-time = "2026-06-07T05:51:56.788Z" },
|
| 41 |
+
{ url = "https://files.pythonhosted.org/packages/fb/40/128429b9eb0c4a2beb122ed8d04b189515df68967987c2654a2e262a5c43/av-17.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3dcd41e53f53f9a3260751d9c3c11d34e93d70d61e506c81f13dbc1e3606e07b", size = 37763744, upload-time = "2026-06-07T05:51:59.222Z" },
|
| 42 |
+
{ url = "https://files.pythonhosted.org/packages/01/6a/5980e7bbeeadfd7a9db8e38e9f1140a3e0c392fccc31bd7b1e4a75cf5a96/av-17.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:3453b06075c7bb973fdb6de52563f7692ff05cbc64c0bb45f4fd6e8709131f2f", size = 28126516, upload-time = "2026-06-07T05:52:01.658Z" },
|
| 43 |
+
{ url = "https://files.pythonhosted.org/packages/ec/87/8036b5c781bc3639ea04ef42d4e26da253bd4bd4311d8705b6a1c8824047/av-17.1.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ad7b4aa011093324b7118245f50ac6db244cfe9900d4072508a5245a2b0d3f41", size = 22460847, upload-time = "2026-06-07T05:52:04.261Z" },
|
| 44 |
+
{ url = "https://files.pythonhosted.org/packages/6d/af/dfdf6fc7b17814b50d0aa9e7a7e37b87be91be3890f44b0d525433cd1fd1/av-17.1.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:43ebbe977f19a7f2d2bd1a4e119675a0b15e05852cf7309846b6ab922ba7ffe9", size = 18159115, upload-time = "2026-06-07T05:52:06.64Z" },
|
| 45 |
+
{ url = "https://files.pythonhosted.org/packages/ad/13/64f6c466471cea225b8b2f4cdc51a571f8a286984b55a08d169b932fda5d/av-17.1.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6a20658ec7d96a70e14b1196eff00b7cdd8831ac3b99868e16b8ba8b24090847", size = 33224427, upload-time = "2026-06-07T05:52:09.165Z" },
|
| 46 |
+
{ url = "https://files.pythonhosted.org/packages/77/43/96b35170bf2e64e00a41748c6400ff73232dc0fc62ded283679fb07c7fe0/av-17.1.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f9a65d1f48b818323fb411e80358f89d77dec340b01d27c6b2dfbb9cbf4b779f", size = 35370183, upload-time = "2026-06-07T05:52:11.959Z" },
|
| 47 |
+
{ url = "https://files.pythonhosted.org/packages/2e/b3/8e8b4b6498731bfbd88e8399a756543f8088f1bd33d08eab678b5aebe728/av-17.1.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:58f7593726437cda5bd19793027e027768450b5c4a594777bf487798a33db702", size = 24459265, upload-time = "2026-06-07T05:52:14.66Z" },
|
| 48 |
+
{ url = "https://files.pythonhosted.org/packages/14/ac/ceb84b7553db21f1143d817245c560d9267168e1e58b1a8eeae2b62c4d04/av-17.1.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:bbab058bd965309f39962e53caac8126987c68c0be094fc4f9427e5615b0218f", size = 34283709, upload-time = "2026-06-07T05:52:17.389Z" },
|
| 49 |
+
{ url = "https://files.pythonhosted.org/packages/59/f9/4115fd84148c9a1cf365096694be6ac882fd3cd3cdb7a2f35e71fecf1631/av-17.1.0-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:9514cfda85180554c430695282faf4be3ffdf95775d8519733821244eecb58e0", size = 25397573, upload-time = "2026-06-07T05:52:20.012Z" },
|
| 50 |
+
{ url = "https://files.pythonhosted.org/packages/e2/ac/92e52d5ed0e0b84d9d93e52b4338c2713d8a44082b8696e6516fdae7c4e4/av-17.1.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e1c90f85cd7431ede95b11e8e711571a896ebea433f298849c2c0f1594c8d86e", size = 36451495, upload-time = "2026-06-07T05:52:22.581Z" },
|
| 51 |
+
{ url = "https://files.pythonhosted.org/packages/6b/f2/53a7cd34adb6a971d7e6d99663e74db286966c9db8afdca17472fdf0f98e/av-17.1.0-cp311-abi3-win_amd64.whl", hash = "sha256:5df5c1172ef1cf65a1529d612f7da7798ce2cf82c1ff7212466b538a6cc7214c", size = 28036393, upload-time = "2026-06-07T05:52:25.657Z" },
|
| 52 |
+
{ url = "https://files.pythonhosted.org/packages/66/47/cd9ae0edf2206351c1251bb94b5ec58728e42c5f6ee16c03c412f3a1bb3e/av-17.1.0-cp311-abi3-win_arm64.whl", hash = "sha256:ee98534242a74da847af78624779ac5a3177dc7c69f956a4da9e6f0fdb37d7f6", size = 21174601, upload-time = "2026-06-07T05:52:28.077Z" },
|
| 53 |
+
]
|
| 54 |
+
|
| 55 |
+
[[package]]
|
| 56 |
+
name = "bitsandbytes"
|
| 57 |
+
version = "0.49.2"
|
| 58 |
+
source = { registry = "https://pypi.org/simple" }
|
| 59 |
+
dependencies = [
|
| 60 |
+
{ name = "numpy" },
|
| 61 |
+
{ name = "packaging" },
|
| 62 |
+
{ name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 63 |
+
{ name = "torch", version = "2.8.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 64 |
+
]
|
| 65 |
+
wheels = [
|
| 66 |
+
{ url = "https://files.pythonhosted.org/packages/d8/7d/f1fe0992334b18cd8494f89aeec1dcc674635584fcd9f115784fea3a1d05/bitsandbytes-0.49.2-py3-none-macosx_14_0_arm64.whl", hash = "sha256:87be5975edeac5396d699ecbc39dfc47cf2c026daaf2d5852a94368611a6823f", size = 131940, upload-time = "2026-02-16T21:26:04.572Z" },
|
| 67 |
+
{ url = "https://files.pythonhosted.org/packages/29/71/acff7af06c818664aa87ff73e17a52c7788ad746b72aea09d3cb8e424348/bitsandbytes-0.49.2-py3-none-manylinux_2_24_aarch64.whl", hash = "sha256:2fc0830c5f7169be36e60e11f2be067c8f812dfcb829801a8703735842450750", size = 31442815, upload-time = "2026-02-16T21:26:06.783Z" },
|
| 68 |
+
{ url = "https://files.pythonhosted.org/packages/19/57/3443d6f183436fbdaf5000aac332c4d5ddb056665d459244a5608e98ae92/bitsandbytes-0.49.2-py3-none-manylinux_2_24_x86_64.whl", hash = "sha256:54b771f06e1a3c73af5c7f16ccf0fc23a846052813d4b008d10cb6e017dd1c8c", size = 60651714, upload-time = "2026-02-16T21:26:11.579Z" },
|
| 69 |
+
{ url = "https://files.pythonhosted.org/packages/b6/d4/501655842ad6771fb077f576d78cbedb5445d15b1c3c91343ed58ca46f0e/bitsandbytes-0.49.2-py3-none-win_amd64.whl", hash = "sha256:2e0ddd09cd778155388023cbe81f00afbb7c000c214caef3ce83386e7144df7d", size = 55372289, upload-time = "2026-02-16T21:26:16.267Z" },
|
| 70 |
+
]
|
| 71 |
+
|
| 72 |
+
[[package]]
|
| 73 |
+
name = "certifi"
|
| 74 |
+
version = "2026.7.22"
|
| 75 |
+
source = { registry = "https://pypi.org/simple" }
|
| 76 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
|
| 77 |
+
wheels = [
|
| 78 |
+
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
[[package]]
|
| 82 |
+
name = "charset-normalizer"
|
| 83 |
+
version = "3.5.1"
|
| 84 |
+
source = { registry = "https://pypi.org/simple" }
|
| 85 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" }
|
| 86 |
+
wheels = [
|
| 87 |
+
{ url = "https://files.pythonhosted.org/packages/71/aa/554e2614f38fc34c58ff1d0911ae8535ad2516440d5482d76fe59f1088b0/charset_normalizer-3.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa", size = 369072, upload-time = "2026-08-15T08:16:22.964Z" },
|
| 88 |
+
{ url = "https://files.pythonhosted.org/packages/03/6d/439231dfc3ccfa6f8c06477b7da2219cbd41a2de3d49084df8ec7b5100f2/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda", size = 251142, upload-time = "2026-08-15T08:16:24.81Z" },
|
| 89 |
+
{ url = "https://files.pythonhosted.org/packages/55/53/7d819bd23a00ef45039146fa2cce1daa2f0771e758c5653ee1f6edac91ed/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45", size = 240714, upload-time = "2026-08-15T08:16:26.392Z" },
|
| 90 |
+
{ url = "https://files.pythonhosted.org/packages/b2/2c/45847198c16f4b38090cc7423b2b6a9008e438704d8ab413211832498d31/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab", size = 279637, upload-time = "2026-08-15T08:16:27.961Z" },
|
| 91 |
+
{ url = "https://files.pythonhosted.org/packages/69/2b/d8be3523ddf9f0b0f3e56d1359034aa10653a4d11564c697f802b4775766/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a", size = 276543, upload-time = "2026-08-15T08:16:29.399Z" },
|
| 92 |
+
{ url = "https://files.pythonhosted.org/packages/32/cd/4f564b8f132de25db594efc706897069f016790cea63a5669c9df2675f64/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3", size = 261644, upload-time = "2026-08-15T08:16:30.722Z" },
|
| 93 |
+
{ url = "https://files.pythonhosted.org/packages/f5/e3/38b975422534a608f98c360e79c2f07c763d66dd4272300d45fb1fee54b0/charset_normalizer-3.5.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb", size = 259609, upload-time = "2026-08-15T08:16:32.248Z" },
|
| 94 |
+
{ url = "https://files.pythonhosted.org/packages/87/bd/fbc24d825c66f1c74f6ccdea3742c3d8354a4888e86d1315a197fee69061/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f", size = 252457, upload-time = "2026-08-15T08:16:33.849Z" },
|
| 95 |
+
{ url = "https://files.pythonhosted.org/packages/b9/2d/918d0e98a0e679469ed05bb2d90c2088b4d315bb612969d8499f76fb5210/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea", size = 242240, upload-time = "2026-08-15T08:16:35.396Z" },
|
| 96 |
+
{ url = "https://files.pythonhosted.org/packages/20/c8/c36f6e0b2dfec351bd38cbc05362697e58bcd073d7dbd95154290c9714ce/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f", size = 280308, upload-time = "2026-08-15T08:16:36.825Z" },
|
| 97 |
+
{ url = "https://files.pythonhosted.org/packages/ca/7b/311b3e02e8c4092400c449c850a760d8c45d900983c83a70cc07208c551d/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182", size = 258679, upload-time = "2026-08-15T08:16:38.22Z" },
|
| 98 |
+
{ url = "https://files.pythonhosted.org/packages/b9/90/082cc45599c392f28c036a497f49e0634041a785fc3849c80ccf396d096f/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa", size = 277221, upload-time = "2026-08-15T08:16:39.62Z" },
|
| 99 |
+
{ url = "https://files.pythonhosted.org/packages/58/ad/b9aecf38d805cbcf84fa94f14c5d972a16561e20296a11dc799a5dcf3763/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818", size = 263799, upload-time = "2026-08-15T08:16:40.885Z" },
|
| 100 |
+
{ url = "https://files.pythonhosted.org/packages/b7/23/b38a20598d5a825f85d9d7636860e56ff0db1479f86497a6e485aa9326f7/charset_normalizer-3.5.1-cp310-cp310-win32.whl", hash = "sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20", size = 182037, upload-time = "2026-08-15T08:16:42.198Z" },
|
| 101 |
+
{ url = "https://files.pythonhosted.org/packages/d2/21/83fffb77864408b8bf0fe1ca603926401d6f8775a8e150b39aacc9958f8a/charset_normalizer-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d", size = 206030, upload-time = "2026-08-15T08:16:43.787Z" },
|
| 102 |
+
{ url = "https://files.pythonhosted.org/packages/86/2e/b93135b5034b1157fb29554b0d06d4844ce62282f0e0a14036f93d7ee2e7/charset_normalizer-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5", size = 185092, upload-time = "2026-08-15T08:16:45.177Z" },
|
| 103 |
+
{ url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" },
|
| 104 |
+
{ url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" },
|
| 105 |
+
{ url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" },
|
| 106 |
+
{ url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" },
|
| 107 |
+
{ url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" },
|
| 108 |
+
{ url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" },
|
| 109 |
+
{ url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" },
|
| 110 |
+
{ url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" },
|
| 111 |
+
{ url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" },
|
| 112 |
+
{ url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" },
|
| 113 |
+
{ url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" },
|
| 114 |
+
{ url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" },
|
| 115 |
+
{ url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" },
|
| 116 |
+
{ url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" },
|
| 117 |
+
{ url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" },
|
| 118 |
+
{ url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" },
|
| 119 |
+
{ url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" },
|
| 120 |
+
]
|
| 121 |
+
|
| 122 |
+
[[package]]
|
| 123 |
+
name = "colorama"
|
| 124 |
+
version = "0.4.6"
|
| 125 |
+
source = { registry = "https://pypi.org/simple" }
|
| 126 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
| 127 |
+
wheels = [
|
| 128 |
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
| 129 |
+
]
|
| 130 |
+
|
| 131 |
+
[[package]]
|
| 132 |
+
name = "cosmos-robometer-phase21a"
|
| 133 |
+
version = "0.1.0"
|
| 134 |
+
source = { virtual = "." }
|
| 135 |
+
dependencies = [
|
| 136 |
+
{ name = "accelerate" },
|
| 137 |
+
{ name = "bitsandbytes" },
|
| 138 |
+
{ name = "flash-attn" },
|
| 139 |
+
{ name = "hf-xet" },
|
| 140 |
+
{ name = "huggingface-hub" },
|
| 141 |
+
{ name = "numpy" },
|
| 142 |
+
{ name = "peft" },
|
| 143 |
+
{ name = "pillow" },
|
| 144 |
+
{ name = "pyyaml" },
|
| 145 |
+
{ name = "qwen-vl-utils" },
|
| 146 |
+
{ name = "safetensors" },
|
| 147 |
+
{ name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 148 |
+
{ name = "torch", version = "2.8.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 149 |
+
{ name = "torchvision", version = "0.23.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 150 |
+
{ name = "torchvision", version = "0.23.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 151 |
+
{ name = "transformers" },
|
| 152 |
+
]
|
| 153 |
+
|
| 154 |
+
[package.metadata]
|
| 155 |
+
requires-dist = [
|
| 156 |
+
{ name = "accelerate", specifier = "==1.14.0" },
|
| 157 |
+
{ name = "bitsandbytes", specifier = "==0.49.2" },
|
| 158 |
+
{ name = "flash-attn", specifier = "==2.8.3.post1" },
|
| 159 |
+
{ name = "hf-xet", specifier = "==1.5.1" },
|
| 160 |
+
{ name = "huggingface-hub", specifier = "==0.36.2" },
|
| 161 |
+
{ name = "numpy", specifier = "==1.26.4" },
|
| 162 |
+
{ name = "peft", specifier = "==0.19.1" },
|
| 163 |
+
{ name = "pillow", specifier = "==11.3.0" },
|
| 164 |
+
{ name = "pyyaml", specifier = "==6.0.3" },
|
| 165 |
+
{ name = "qwen-vl-utils", specifier = "==0.0.14" },
|
| 166 |
+
{ name = "safetensors", specifier = "==0.8.0" },
|
| 167 |
+
{ name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32'", specifier = "==2.8.0" },
|
| 168 |
+
{ name = "torch", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = "==2.8.0", index = "https://download.pytorch.org/whl/cu128" },
|
| 169 |
+
{ name = "torchvision", marker = "sys_platform != 'linux' and sys_platform != 'win32'", specifier = "==0.23.0" },
|
| 170 |
+
{ name = "torchvision", marker = "sys_platform == 'linux' or sys_platform == 'win32'", specifier = "==0.23.0", index = "https://download.pytorch.org/whl/cu128" },
|
| 171 |
+
{ name = "transformers", specifier = "==4.57.6" },
|
| 172 |
+
]
|
| 173 |
+
|
| 174 |
+
[[package]]
|
| 175 |
+
name = "einops"
|
| 176 |
+
version = "0.8.2"
|
| 177 |
+
source = { registry = "https://pypi.org/simple" }
|
| 178 |
+
sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" }
|
| 179 |
+
wheels = [
|
| 180 |
+
{ url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" },
|
| 181 |
+
]
|
| 182 |
+
|
| 183 |
+
[[package]]
|
| 184 |
+
name = "filelock"
|
| 185 |
+
version = "3.32.4"
|
| 186 |
+
source = { registry = "https://pypi.org/simple" }
|
| 187 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6d/30/03b03951873a1a0ffc7e8ca0e10c15597b59e8d0e39260704cd2ea087bc4/filelock-3.32.4.tar.gz", hash = "sha256:2bde2e4cf732e0153406d8a7bc80620ecf5e621fe0d25e41143c4e3b4733ff30", size = 222126, upload-time = "2026-08-23T17:37:55.363Z" }
|
| 188 |
+
wheels = [
|
| 189 |
+
{ url = "https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl", hash = "sha256:22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd", size = 99864, upload-time = "2026-08-23T17:37:53.913Z" },
|
| 190 |
+
]
|
| 191 |
+
|
| 192 |
+
[[package]]
|
| 193 |
+
name = "flash-attn"
|
| 194 |
+
version = "2.8.3.post1"
|
| 195 |
+
source = { registry = "https://pypi.org/simple" }
|
| 196 |
+
dependencies = [
|
| 197 |
+
{ name = "einops" },
|
| 198 |
+
{ name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 199 |
+
{ name = "torch", version = "2.8.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 200 |
+
]
|
| 201 |
+
sdist = { url = "https://files.pythonhosted.org/packages/01/7a/92a46e7cd6bbb4d7b2855a457c3b855df54a97af5656d98fc92e58e61065/flash_attn-2.8.3.post1.tar.gz", hash = "sha256:55d5103ed846da8b56e0797acf4bde07dee4b1c7e8907fcfc6699c203030c348", size = 8451657, upload-time = "2026-06-11T04:35:21.318Z" }
|
| 202 |
+
|
| 203 |
+
[[package]]
|
| 204 |
+
name = "fsspec"
|
| 205 |
+
version = "2026.7.0"
|
| 206 |
+
source = { registry = "https://pypi.org/simple" }
|
| 207 |
+
sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" }
|
| 208 |
+
wheels = [
|
| 209 |
+
{ url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" },
|
| 210 |
+
]
|
| 211 |
+
|
| 212 |
+
[[package]]
|
| 213 |
+
name = "hf-xet"
|
| 214 |
+
version = "1.5.1"
|
| 215 |
+
source = { registry = "https://pypi.org/simple" }
|
| 216 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4b/2d/57fd21d84d93efb4bd0b962383790e19dd1bc053501b4264c97903b4e83e/hf_xet-1.5.1.tar.gz", hash = "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", size = 876636, upload-time = "2026-06-08T23:02:53.897Z" }
|
| 217 |
+
wheels = [
|
| 218 |
+
{ url = "https://files.pythonhosted.org/packages/7a/d8/5e54cf37434759d1f4f2ba9b66077ff9d4c4e1f37b6bd7975da5c40d94ab/hf_xet-1.5.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", size = 4077794, upload-time = "2026-06-08T23:02:40.656Z" },
|
| 219 |
+
{ url = "https://files.pythonhosted.org/packages/35/94/4b2ecfbad8f8b04701a23aefb62f540b9137d058b7e1dbef16a32676f0e9/hf_xet-1.5.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", size = 3845354, upload-time = "2026-06-08T23:02:42.702Z" },
|
| 220 |
+
{ url = "https://files.pythonhosted.org/packages/de/cc/f99f4bc7295023d7bd9ebbfd51f75cc530ca262c1227666268b8208f4b77/hf_xet-1.5.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", size = 4514864, upload-time = "2026-06-08T23:02:44.497Z" },
|
| 221 |
+
{ url = "https://files.pythonhosted.org/packages/cd/6e/21f7e5a2381278bd3b7b7a5a4d90038518bb6308a0c1daf5d9f8268bb178/hf_xet-1.5.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", size = 4303784, upload-time = "2026-06-08T23:02:46.203Z" },
|
| 222 |
+
{ url = "https://files.pythonhosted.org/packages/35/0e/f992bb6927ac1cb30ef74e62268f551f338bc32b2191f7c96a44c6f7283e/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", size = 4500703, upload-time = "2026-06-08T23:02:47.628Z" },
|
| 223 |
+
{ url = "https://files.pythonhosted.org/packages/fb/d1/90a498d05447980b977b1669246eeeeae4cfb0ea3e7a286eaba627f91bf9/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", size = 4719498, upload-time = "2026-06-08T23:02:49.268Z" },
|
| 224 |
+
{ url = "https://files.pythonhosted.org/packages/6d/b6/20f99cfe97cc663a711f7b33cc21d4793e51968e9a26125b4afcd77315ba/hf_xet-1.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5", size = 4026419, upload-time = "2026-06-08T23:02:50.829Z" },
|
| 225 |
+
{ url = "https://files.pythonhosted.org/packages/f9/fa/77453694888f03e5a8c8852d1514a0894d8e81c622d39edbaf308ea0dcf4/hf_xet-1.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", size = 3855178, upload-time = "2026-06-08T23:02:52.452Z" },
|
| 226 |
+
]
|
| 227 |
+
|
| 228 |
+
[[package]]
|
| 229 |
+
name = "huggingface-hub"
|
| 230 |
+
version = "0.36.2"
|
| 231 |
+
source = { registry = "https://pypi.org/simple" }
|
| 232 |
+
dependencies = [
|
| 233 |
+
{ name = "filelock" },
|
| 234 |
+
{ name = "fsspec" },
|
| 235 |
+
{ name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
|
| 236 |
+
{ name = "packaging" },
|
| 237 |
+
{ name = "pyyaml" },
|
| 238 |
+
{ name = "requests" },
|
| 239 |
+
{ name = "tqdm" },
|
| 240 |
+
{ name = "typing-extensions" },
|
| 241 |
+
]
|
| 242 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" }
|
| 243 |
+
wheels = [
|
| 244 |
+
{ url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" },
|
| 245 |
+
]
|
| 246 |
+
|
| 247 |
+
[[package]]
|
| 248 |
+
name = "idna"
|
| 249 |
+
version = "3.19"
|
| 250 |
+
source = { registry = "https://pypi.org/simple" }
|
| 251 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
|
| 252 |
+
wheels = [
|
| 253 |
+
{ url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
|
| 254 |
+
]
|
| 255 |
+
|
| 256 |
+
[[package]]
|
| 257 |
+
name = "jinja2"
|
| 258 |
+
version = "3.1.6"
|
| 259 |
+
source = { registry = "https://pypi.org/simple" }
|
| 260 |
+
dependencies = [
|
| 261 |
+
{ name = "markupsafe" },
|
| 262 |
+
]
|
| 263 |
+
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
| 264 |
+
wheels = [
|
| 265 |
+
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
| 266 |
+
]
|
| 267 |
+
|
| 268 |
+
[[package]]
|
| 269 |
+
name = "markupsafe"
|
| 270 |
+
version = "3.0.3"
|
| 271 |
+
source = { registry = "https://pypi.org/simple" }
|
| 272 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
|
| 273 |
+
wheels = [
|
| 274 |
+
{ url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" },
|
| 275 |
+
{ url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" },
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" },
|
| 277 |
+
{ url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" },
|
| 278 |
+
{ url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" },
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" },
|
| 280 |
+
{ url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" },
|
| 281 |
+
{ url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" },
|
| 282 |
+
{ url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" },
|
| 283 |
+
{ url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" },
|
| 284 |
+
{ url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" },
|
| 285 |
+
]
|
| 286 |
+
|
| 287 |
+
[[package]]
|
| 288 |
+
name = "mpmath"
|
| 289 |
+
version = "1.3.0"
|
| 290 |
+
source = { registry = "https://pypi.org/simple" }
|
| 291 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
|
| 292 |
+
wheels = [
|
| 293 |
+
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
|
| 294 |
+
]
|
| 295 |
+
|
| 296 |
+
[[package]]
|
| 297 |
+
name = "networkx"
|
| 298 |
+
version = "3.4.2"
|
| 299 |
+
source = { registry = "https://pypi.org/simple" }
|
| 300 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" }
|
| 301 |
+
wheels = [
|
| 302 |
+
{ url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" },
|
| 303 |
+
]
|
| 304 |
+
|
| 305 |
+
[[package]]
|
| 306 |
+
name = "numpy"
|
| 307 |
+
version = "1.26.4"
|
| 308 |
+
source = { registry = "https://pypi.org/simple" }
|
| 309 |
+
sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" }
|
| 310 |
+
wheels = [
|
| 311 |
+
{ url = "https://files.pythonhosted.org/packages/a7/94/ace0fdea5241a27d13543ee117cbc65868e82213fb31a8eb7fe9ff23f313/numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", size = 20631468, upload-time = "2024-02-05T23:48:01.194Z" },
|
| 312 |
+
{ url = "https://files.pythonhosted.org/packages/20/f7/b24208eba89f9d1b58c1668bc6c8c4fd472b20c45573cb767f59d49fb0f6/numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", size = 13966411, upload-time = "2024-02-05T23:48:29.038Z" },
|
| 313 |
+
{ url = "https://files.pythonhosted.org/packages/fc/a5/4beee6488160798683eed5bdb7eead455892c3b4e1f78d79d8d3f3b084ac/numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", size = 14219016, upload-time = "2024-02-05T23:48:54.098Z" },
|
| 314 |
+
{ url = "https://files.pythonhosted.org/packages/4b/d7/ecf66c1cd12dc28b4040b15ab4d17b773b87fa9d29ca16125de01adb36cd/numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f", size = 18240889, upload-time = "2024-02-05T23:49:25.361Z" },
|
| 315 |
+
{ url = "https://files.pythonhosted.org/packages/24/03/6f229fe3187546435c4f6f89f6d26c129d4f5bed40552899fcf1f0bf9e50/numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", size = 13876746, upload-time = "2024-02-05T23:49:51.983Z" },
|
| 316 |
+
{ url = "https://files.pythonhosted.org/packages/39/fe/39ada9b094f01f5a35486577c848fe274e374bbf8d8f472e1423a0bbd26d/numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", size = 18078620, upload-time = "2024-02-05T23:50:22.515Z" },
|
| 317 |
+
{ url = "https://files.pythonhosted.org/packages/d5/ef/6ad11d51197aad206a9ad2286dc1aac6a378059e06e8cf22cd08ed4f20dc/numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", size = 5972659, upload-time = "2024-02-05T23:50:35.834Z" },
|
| 318 |
+
{ url = "https://files.pythonhosted.org/packages/19/77/538f202862b9183f54108557bfda67e17603fc560c384559e769321c9d92/numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", size = 15808905, upload-time = "2024-02-05T23:51:03.701Z" },
|
| 319 |
+
]
|
| 320 |
+
|
| 321 |
+
[[package]]
|
| 322 |
+
name = "nvidia-cublas-cu12"
|
| 323 |
+
version = "12.8.4.1"
|
| 324 |
+
source = { registry = "https://pypi.org/simple" }
|
| 325 |
+
wheels = [
|
| 326 |
+
{ url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" },
|
| 327 |
+
]
|
| 328 |
+
|
| 329 |
+
[[package]]
|
| 330 |
+
name = "nvidia-cuda-cupti-cu12"
|
| 331 |
+
version = "12.8.90"
|
| 332 |
+
source = { registry = "https://pypi.org/simple" }
|
| 333 |
+
wheels = [
|
| 334 |
+
{ url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" },
|
| 335 |
+
]
|
| 336 |
+
|
| 337 |
+
[[package]]
|
| 338 |
+
name = "nvidia-cuda-nvrtc-cu12"
|
| 339 |
+
version = "12.8.93"
|
| 340 |
+
source = { registry = "https://pypi.org/simple" }
|
| 341 |
+
wheels = [
|
| 342 |
+
{ url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" },
|
| 343 |
+
]
|
| 344 |
+
|
| 345 |
+
[[package]]
|
| 346 |
+
name = "nvidia-cuda-runtime-cu12"
|
| 347 |
+
version = "12.8.90"
|
| 348 |
+
source = { registry = "https://pypi.org/simple" }
|
| 349 |
+
wheels = [
|
| 350 |
+
{ url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" },
|
| 351 |
+
]
|
| 352 |
+
|
| 353 |
+
[[package]]
|
| 354 |
+
name = "nvidia-cudnn-cu12"
|
| 355 |
+
version = "9.10.2.21"
|
| 356 |
+
source = { registry = "https://pypi.org/simple" }
|
| 357 |
+
dependencies = [
|
| 358 |
+
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 359 |
+
]
|
| 360 |
+
wheels = [
|
| 361 |
+
{ url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" },
|
| 362 |
+
]
|
| 363 |
+
|
| 364 |
+
[[package]]
|
| 365 |
+
name = "nvidia-cufft-cu12"
|
| 366 |
+
version = "11.3.3.83"
|
| 367 |
+
source = { registry = "https://pypi.org/simple" }
|
| 368 |
+
dependencies = [
|
| 369 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 370 |
+
]
|
| 371 |
+
wheels = [
|
| 372 |
+
{ url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" },
|
| 373 |
+
]
|
| 374 |
+
|
| 375 |
+
[[package]]
|
| 376 |
+
name = "nvidia-cufile-cu12"
|
| 377 |
+
version = "1.13.1.3"
|
| 378 |
+
source = { registry = "https://pypi.org/simple" }
|
| 379 |
+
wheels = [
|
| 380 |
+
{ url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" },
|
| 381 |
+
]
|
| 382 |
+
|
| 383 |
+
[[package]]
|
| 384 |
+
name = "nvidia-curand-cu12"
|
| 385 |
+
version = "10.3.9.90"
|
| 386 |
+
source = { registry = "https://pypi.org/simple" }
|
| 387 |
+
wheels = [
|
| 388 |
+
{ url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" },
|
| 389 |
+
]
|
| 390 |
+
|
| 391 |
+
[[package]]
|
| 392 |
+
name = "nvidia-cusolver-cu12"
|
| 393 |
+
version = "11.7.3.90"
|
| 394 |
+
source = { registry = "https://pypi.org/simple" }
|
| 395 |
+
dependencies = [
|
| 396 |
+
{ name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 397 |
+
{ name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 398 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 399 |
+
]
|
| 400 |
+
wheels = [
|
| 401 |
+
{ url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" },
|
| 402 |
+
]
|
| 403 |
+
|
| 404 |
+
[[package]]
|
| 405 |
+
name = "nvidia-cusparse-cu12"
|
| 406 |
+
version = "12.5.8.93"
|
| 407 |
+
source = { registry = "https://pypi.org/simple" }
|
| 408 |
+
dependencies = [
|
| 409 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 410 |
+
]
|
| 411 |
+
wheels = [
|
| 412 |
+
{ url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" },
|
| 413 |
+
]
|
| 414 |
+
|
| 415 |
+
[[package]]
|
| 416 |
+
name = "nvidia-cusparselt-cu12"
|
| 417 |
+
version = "0.7.1"
|
| 418 |
+
source = { registry = "https://pypi.org/simple" }
|
| 419 |
+
wheels = [
|
| 420 |
+
{ url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" },
|
| 421 |
+
]
|
| 422 |
+
|
| 423 |
+
[[package]]
|
| 424 |
+
name = "nvidia-nccl-cu12"
|
| 425 |
+
version = "2.27.3"
|
| 426 |
+
source = { registry = "https://pypi.org/simple" }
|
| 427 |
+
wheels = [
|
| 428 |
+
{ url = "https://files.pythonhosted.org/packages/5c/5b/4e4fff7bad39adf89f735f2bc87248c81db71205b62bcc0d5ca5b606b3c3/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039", size = 322364134, upload-time = "2025-06-03T21:58:04.013Z" },
|
| 429 |
+
]
|
| 430 |
+
|
| 431 |
+
[[package]]
|
| 432 |
+
name = "nvidia-nvjitlink-cu12"
|
| 433 |
+
version = "12.8.93"
|
| 434 |
+
source = { registry = "https://pypi.org/simple" }
|
| 435 |
+
wheels = [
|
| 436 |
+
{ url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" },
|
| 437 |
+
]
|
| 438 |
+
|
| 439 |
+
[[package]]
|
| 440 |
+
name = "nvidia-nvtx-cu12"
|
| 441 |
+
version = "12.8.90"
|
| 442 |
+
source = { registry = "https://pypi.org/simple" }
|
| 443 |
+
wheels = [
|
| 444 |
+
{ url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" },
|
| 445 |
+
]
|
| 446 |
+
|
| 447 |
+
[[package]]
|
| 448 |
+
name = "packaging"
|
| 449 |
+
version = "26.3"
|
| 450 |
+
source = { registry = "https://pypi.org/simple" }
|
| 451 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
|
| 452 |
+
wheels = [
|
| 453 |
+
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
|
| 454 |
+
]
|
| 455 |
+
|
| 456 |
+
[[package]]
|
| 457 |
+
name = "peft"
|
| 458 |
+
version = "0.19.1"
|
| 459 |
+
source = { registry = "https://pypi.org/simple" }
|
| 460 |
+
dependencies = [
|
| 461 |
+
{ name = "accelerate" },
|
| 462 |
+
{ name = "huggingface-hub" },
|
| 463 |
+
{ name = "numpy" },
|
| 464 |
+
{ name = "packaging" },
|
| 465 |
+
{ name = "psutil" },
|
| 466 |
+
{ name = "pyyaml" },
|
| 467 |
+
{ name = "safetensors" },
|
| 468 |
+
{ name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 469 |
+
{ name = "torch", version = "2.8.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 470 |
+
{ name = "tqdm" },
|
| 471 |
+
{ name = "transformers" },
|
| 472 |
+
]
|
| 473 |
+
sdist = { url = "https://files.pythonhosted.org/packages/86/cf/037f1e3d5186496c05513a6754639e2dab3038a05f384284d49a9bd06a2d/peft-0.19.1.tar.gz", hash = "sha256:0d97542fe96dcdaa20d3b81c06f26f988618f416a73544ab23c3618ccb674a40", size = 763738, upload-time = "2026-04-16T15:46:45.105Z" }
|
| 474 |
+
wheels = [
|
| 475 |
+
{ url = "https://files.pythonhosted.org/packages/e8/b6/f54d676ed93cc2dd2234c3b172ea9c8c3d7d29361e66b1b23dec57a67465/peft-0.19.1-py3-none-any.whl", hash = "sha256:2113f72a81621b5913ef28f9022204c742df111890c5f49d812716a4a301e356", size = 680692, upload-time = "2026-04-16T15:46:42.886Z" },
|
| 476 |
+
]
|
| 477 |
+
|
| 478 |
+
[[package]]
|
| 479 |
+
name = "pillow"
|
| 480 |
+
version = "11.3.0"
|
| 481 |
+
source = { registry = "https://pypi.org/simple" }
|
| 482 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" }
|
| 483 |
+
wheels = [
|
| 484 |
+
{ url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" },
|
| 485 |
+
{ url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" },
|
| 486 |
+
{ url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" },
|
| 487 |
+
{ url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" },
|
| 488 |
+
{ url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" },
|
| 489 |
+
{ url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" },
|
| 490 |
+
{ url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" },
|
| 491 |
+
{ url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" },
|
| 492 |
+
{ url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" },
|
| 493 |
+
{ url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" },
|
| 494 |
+
{ url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" },
|
| 495 |
+
{ url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" },
|
| 496 |
+
{ url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" },
|
| 497 |
+
{ url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" },
|
| 498 |
+
{ url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" },
|
| 499 |
+
{ url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" },
|
| 500 |
+
{ url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" },
|
| 501 |
+
{ url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" },
|
| 502 |
+
]
|
| 503 |
+
|
| 504 |
+
[[package]]
|
| 505 |
+
name = "psutil"
|
| 506 |
+
version = "7.2.2"
|
| 507 |
+
source = { registry = "https://pypi.org/simple" }
|
| 508 |
+
sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
|
| 509 |
+
wheels = [
|
| 510 |
+
{ url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
|
| 511 |
+
{ url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
|
| 512 |
+
{ url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
|
| 513 |
+
{ url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
|
| 514 |
+
{ url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
|
| 515 |
+
{ url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
|
| 516 |
+
{ url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
|
| 517 |
+
{ url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
|
| 518 |
+
]
|
| 519 |
+
|
| 520 |
+
[[package]]
|
| 521 |
+
name = "pyyaml"
|
| 522 |
+
version = "6.0.3"
|
| 523 |
+
source = { registry = "https://pypi.org/simple" }
|
| 524 |
+
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
|
| 525 |
+
wheels = [
|
| 526 |
+
{ url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
|
| 527 |
+
{ url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
|
| 528 |
+
{ url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
|
| 529 |
+
{ url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
|
| 530 |
+
{ url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
|
| 531 |
+
{ url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
|
| 532 |
+
{ url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
|
| 533 |
+
{ url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
|
| 534 |
+
{ url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
|
| 535 |
+
]
|
| 536 |
+
|
| 537 |
+
[[package]]
|
| 538 |
+
name = "qwen-vl-utils"
|
| 539 |
+
version = "0.0.14"
|
| 540 |
+
source = { registry = "https://pypi.org/simple" }
|
| 541 |
+
dependencies = [
|
| 542 |
+
{ name = "av" },
|
| 543 |
+
{ name = "packaging" },
|
| 544 |
+
{ name = "pillow" },
|
| 545 |
+
{ name = "requests" },
|
| 546 |
+
]
|
| 547 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b6/b1/ad4fc2260a3badd278b38d642f3b987412f1f6682f0ef2b31b0572d5caa8/qwen_vl_utils-0.0.14.tar.gz", hash = "sha256:9c7cad5ae803b3a10f8bb7194deb12aeacdd032f92f4224e880c73587a7346ad", size = 8453, upload-time = "2025-09-23T09:38:57.532Z" }
|
| 548 |
+
wheels = [
|
| 549 |
+
{ url = "https://files.pythonhosted.org/packages/c4/43/80f67e0336cb2fc725f8e06f7fe35c1d0fe946f4d2b8b2175e797e07349e/qwen_vl_utils-0.0.14-py3-none-any.whl", hash = "sha256:5e28657bfd031e56bd447c5901b58ddfc3835285ed100f4c56580e0ade054e96", size = 8120, upload-time = "2025-09-23T09:38:56.297Z" },
|
| 550 |
+
]
|
| 551 |
+
|
| 552 |
+
[[package]]
|
| 553 |
+
name = "regex"
|
| 554 |
+
version = "2026.7.19"
|
| 555 |
+
source = { registry = "https://pypi.org/simple" }
|
| 556 |
+
sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" }
|
| 557 |
+
wheels = [
|
| 558 |
+
{ url = "https://files.pythonhosted.org/packages/24/13/bbf7d9d1887fe4a3693527c6caa232c197ea9da91f1212e9672eff60329d/regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b", size = 494009, upload-time = "2026-07-19T00:16:13.602Z" },
|
| 559 |
+
{ url = "https://files.pythonhosted.org/packages/a3/19/783688e75a2bec15d50aec0d5e7e317d363808bc82a6eb6750b897bfcd7b/regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52", size = 295287, upload-time = "2026-07-19T00:16:15.702Z" },
|
| 560 |
+
{ url = "https://files.pythonhosted.org/packages/16/01/cefe4f051302ca298d3f3e79ed6dbd933ac84485b9515acdd6a52d70cef7/regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665", size = 290633, upload-time = "2026-07-19T00:16:17.182Z" },
|
| 561 |
+
{ url = "https://files.pythonhosted.org/packages/ae/1e/1045ca2cabb12e8ec41ad0d138e9f3ff1eb079d30a6f51ccf7d709b44aad/regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0", size = 785300, upload-time = "2026-07-19T00:16:18.505Z" },
|
| 562 |
+
{ url = "https://files.pythonhosted.org/packages/d5/4a/20e5bca184e90bf1bd187efdb53363f4a7b7b34f01d54ced5740caf104bd/regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951", size = 854079, upload-time = "2026-07-19T00:16:19.909Z" },
|
| 563 |
+
{ url = "https://files.pythonhosted.org/packages/09/9b/5a2e59678be3b24aa6a42b2c6d66a48daa212593e9f4096fe7ba577fa9b1/regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3", size = 899496, upload-time = "2026-07-19T00:16:21.453Z" },
|
| 564 |
+
{ url = "https://files.pythonhosted.org/packages/48/9a/7317f14ed8ed9fd998d1978b4802b07bc4d79216353c435dbcc1ddd1301f/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c", size = 793541, upload-time = "2026-07-19T00:16:22.991Z" },
|
| 565 |
+
{ url = "https://files.pythonhosted.org/packages/6f/53/833c2db3e274d3c191f4c42fe5bfa358e4c8b617d5d7312d31334965fc46/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6", size = 785515, upload-time = "2026-07-19T00:16:24.654Z" },
|
| 566 |
+
{ url = "https://files.pythonhosted.org/packages/8d/b9/efb2f9fa151d71db09d4015e1fb92fee47416f01c12164836bbd23e2f3c2/regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175", size = 769556, upload-time = "2026-07-19T00:16:26.207Z" },
|
| 567 |
+
{ url = "https://files.pythonhosted.org/packages/81/4d/45610c263f8eadb84e4a1fabd904d81d5176226faa9104ef498bf8a8b285/regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6", size = 774130, upload-time = "2026-07-19T00:16:27.786Z" },
|
| 568 |
+
{ url = "https://files.pythonhosted.org/packages/40/95/1b40d87c7a9e5480bec7a87bce9fd67fc3f14b5f106c8ee66d660249072f/regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095", size = 848694, upload-time = "2026-07-19T00:16:29.412Z" },
|
| 569 |
+
{ url = "https://files.pythonhosted.org/packages/17/8b/bb45968addd5b394ef9cd9184bd9c65ade1a819dbb2b92b71ad52a0c7907/regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0", size = 758505, upload-time = "2026-07-19T00:16:31.006Z" },
|
| 570 |
+
{ url = "https://files.pythonhosted.org/packages/bf/6f/33386c672fbf43e21602135a0f29a97ee251a483f007fe51d10e9b2dbc93/regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a", size = 836985, upload-time = "2026-07-19T00:16:32.459Z" },
|
| 571 |
+
{ url = "https://files.pythonhosted.org/packages/22/f1/9112b86e9bb075619862e8e42b604794389f1958faa68fb69bde505dd90e/regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902", size = 782610, upload-time = "2026-07-19T00:16:33.857Z" },
|
| 572 |
+
{ url = "https://files.pythonhosted.org/packages/3a/f9/13d460d8a385ca0b0be9e6be80a90968b9293b3e30895543ad2d1d1653e4/regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e", size = 266772, upload-time = "2026-07-19T00:16:35.194Z" },
|
| 573 |
+
{ url = "https://files.pythonhosted.org/packages/9f/90/29addd7a03e1aea402c1f31467e25c80caabc8c3735b88a23cf73b0aa9c2/regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db", size = 277967, upload-time = "2026-07-19T00:16:36.847Z" },
|
| 574 |
+
{ url = "https://files.pythonhosted.org/packages/8c/ba/ecfce06fe66c122bc6f77ae284887a9282e4411fe1e6268c5266611ca054/regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6", size = 276963, upload-time = "2026-07-19T00:16:38.315Z" },
|
| 575 |
+
]
|
| 576 |
+
|
| 577 |
+
[[package]]
|
| 578 |
+
name = "requests"
|
| 579 |
+
version = "2.34.2"
|
| 580 |
+
source = { registry = "https://pypi.org/simple" }
|
| 581 |
+
dependencies = [
|
| 582 |
+
{ name = "certifi" },
|
| 583 |
+
{ name = "charset-normalizer" },
|
| 584 |
+
{ name = "idna" },
|
| 585 |
+
{ name = "urllib3" },
|
| 586 |
+
]
|
| 587 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
|
| 588 |
+
wheels = [
|
| 589 |
+
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
| 590 |
+
]
|
| 591 |
+
|
| 592 |
+
[[package]]
|
| 593 |
+
name = "safetensors"
|
| 594 |
+
version = "0.8.0"
|
| 595 |
+
source = { registry = "https://pypi.org/simple" }
|
| 596 |
+
sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" }
|
| 597 |
+
wheels = [
|
| 598 |
+
{ url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" },
|
| 599 |
+
{ url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" },
|
| 600 |
+
{ url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" },
|
| 601 |
+
{ url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" },
|
| 602 |
+
{ url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" },
|
| 603 |
+
{ url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" },
|
| 604 |
+
{ url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" },
|
| 605 |
+
{ url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" },
|
| 606 |
+
{ url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" },
|
| 607 |
+
{ url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" },
|
| 608 |
+
{ url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" },
|
| 609 |
+
{ url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" },
|
| 610 |
+
{ url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" },
|
| 611 |
+
{ url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" },
|
| 612 |
+
{ url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" },
|
| 613 |
+
{ url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" },
|
| 614 |
+
]
|
| 615 |
+
|
| 616 |
+
[[package]]
|
| 617 |
+
name = "setuptools"
|
| 618 |
+
version = "84.0.0"
|
| 619 |
+
source = { registry = "https://pypi.org/simple" }
|
| 620 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" }
|
| 621 |
+
wheels = [
|
| 622 |
+
{ url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" },
|
| 623 |
+
]
|
| 624 |
+
|
| 625 |
+
[[package]]
|
| 626 |
+
name = "sympy"
|
| 627 |
+
version = "1.14.0"
|
| 628 |
+
source = { registry = "https://pypi.org/simple" }
|
| 629 |
+
dependencies = [
|
| 630 |
+
{ name = "mpmath" },
|
| 631 |
+
]
|
| 632 |
+
sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
|
| 633 |
+
wheels = [
|
| 634 |
+
{ url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
|
| 635 |
+
]
|
| 636 |
+
|
| 637 |
+
[[package]]
|
| 638 |
+
name = "tokenizers"
|
| 639 |
+
version = "0.22.2"
|
| 640 |
+
source = { registry = "https://pypi.org/simple" }
|
| 641 |
+
dependencies = [
|
| 642 |
+
{ name = "huggingface-hub" },
|
| 643 |
+
]
|
| 644 |
+
sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" }
|
| 645 |
+
wheels = [
|
| 646 |
+
{ url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" },
|
| 647 |
+
{ url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" },
|
| 648 |
+
{ url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" },
|
| 649 |
+
{ url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" },
|
| 650 |
+
{ url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" },
|
| 651 |
+
{ url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" },
|
| 652 |
+
{ url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" },
|
| 653 |
+
{ url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" },
|
| 654 |
+
{ url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" },
|
| 655 |
+
{ url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" },
|
| 656 |
+
{ url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" },
|
| 657 |
+
{ url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" },
|
| 658 |
+
{ url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" },
|
| 659 |
+
{ url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" },
|
| 660 |
+
{ url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" },
|
| 661 |
+
{ url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" },
|
| 662 |
+
{ url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" },
|
| 663 |
+
{ url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" },
|
| 664 |
+
{ url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" },
|
| 665 |
+
]
|
| 666 |
+
|
| 667 |
+
[[package]]
|
| 668 |
+
name = "torch"
|
| 669 |
+
version = "2.8.0"
|
| 670 |
+
source = { registry = "https://pypi.org/simple" }
|
| 671 |
+
resolution-markers = [
|
| 672 |
+
"sys_platform != 'linux' and sys_platform != 'win32'",
|
| 673 |
+
]
|
| 674 |
+
dependencies = [
|
| 675 |
+
{ name = "filelock", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 676 |
+
{ name = "fsspec", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 677 |
+
{ name = "jinja2", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 678 |
+
{ name = "networkx", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 679 |
+
{ name = "sympy", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 680 |
+
{ name = "typing-extensions", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 681 |
+
]
|
| 682 |
+
wheels = [
|
| 683 |
+
{ url = "https://files.pythonhosted.org/packages/ef/d6/e6d4c57e61c2b2175d3aafbfb779926a2cfd7c32eeda7c543925dceec923/torch-2.8.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:a3f16a58a9a800f589b26d47ee15aca3acf065546137fc2af039876135f4c760", size = 73611154, upload-time = "2025-08-06T14:53:10.919Z" },
|
| 684 |
+
]
|
| 685 |
+
|
| 686 |
+
[[package]]
|
| 687 |
+
name = "torch"
|
| 688 |
+
version = "2.8.0+cu128"
|
| 689 |
+
source = { registry = "https://download.pytorch.org/whl/cu128" }
|
| 690 |
+
resolution-markers = [
|
| 691 |
+
"sys_platform == 'linux' or sys_platform == 'win32'",
|
| 692 |
+
]
|
| 693 |
+
dependencies = [
|
| 694 |
+
{ name = "filelock", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 695 |
+
{ name = "fsspec", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 696 |
+
{ name = "jinja2", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 697 |
+
{ name = "networkx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 698 |
+
{ name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 699 |
+
{ name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 700 |
+
{ name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 701 |
+
{ name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 702 |
+
{ name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 703 |
+
{ name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 704 |
+
{ name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 705 |
+
{ name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 706 |
+
{ name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 707 |
+
{ name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 708 |
+
{ name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 709 |
+
{ name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 710 |
+
{ name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 711 |
+
{ name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 712 |
+
{ name = "sympy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 713 |
+
{ name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" },
|
| 714 |
+
{ name = "typing-extensions", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 715 |
+
]
|
| 716 |
+
wheels = [
|
| 717 |
+
{ url = "https://download-r2.pytorch.org/whl/cu128/torch-2.8.0%2Bcu128-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0c96999d15cf1f13dd7c913e0b21a9a355538e6cfc10861a17158320292f5954", upload-time = "2025-10-01T23:47:19Z" },
|
| 718 |
+
{ url = "https://download-r2.pytorch.org/whl/cu128/torch-2.8.0%2Bcu128-cp310-cp310-win_amd64.whl", hash = "sha256:43938e9a174c90e5eb9e906532b2f1e21532bbfa5a61b65193b4f54714d34f9e", upload-time = "2025-10-01T23:47:42Z" },
|
| 719 |
+
]
|
| 720 |
+
|
| 721 |
+
[[package]]
|
| 722 |
+
name = "torchvision"
|
| 723 |
+
version = "0.23.0"
|
| 724 |
+
source = { registry = "https://pypi.org/simple" }
|
| 725 |
+
resolution-markers = [
|
| 726 |
+
"sys_platform != 'linux' and sys_platform != 'win32'",
|
| 727 |
+
]
|
| 728 |
+
dependencies = [
|
| 729 |
+
{ name = "numpy", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 730 |
+
{ name = "pillow", marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 731 |
+
{ name = "torch", version = "2.8.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" },
|
| 732 |
+
]
|
| 733 |
+
wheels = [
|
| 734 |
+
{ url = "https://files.pythonhosted.org/packages/4d/49/5ad5c3ff4920be0adee9eb4339b4fb3b023a0fc55b9ed8dbc73df92946b8/torchvision-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7266871daca00ad46d1c073e55d972179d12a58fa5c9adec9a3db9bbed71284a", size = 1856885, upload-time = "2025-08-06T14:57:55.024Z" },
|
| 735 |
+
]
|
| 736 |
+
|
| 737 |
+
[[package]]
|
| 738 |
+
name = "torchvision"
|
| 739 |
+
version = "0.23.0+cu128"
|
| 740 |
+
source = { registry = "https://download.pytorch.org/whl/cu128" }
|
| 741 |
+
resolution-markers = [
|
| 742 |
+
"sys_platform == 'linux' or sys_platform == 'win32'",
|
| 743 |
+
]
|
| 744 |
+
dependencies = [
|
| 745 |
+
{ name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 746 |
+
{ name = "pillow", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 747 |
+
{ name = "torch", version = "2.8.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 748 |
+
]
|
| 749 |
+
wheels = [
|
| 750 |
+
{ url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.23.0%2Bcu128-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:460bc8d70f63bdb433a7351decc2c1ae1903f7f378e4a7614fc8e8c97a5c36aa", upload-time = "2025-08-05T20:11:51Z" },
|
| 751 |
+
{ url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.23.0%2Bcu128-cp310-cp310-win_amd64.whl", hash = "sha256:8ec6f2281ef5d52471b01b99eb04243d0c2cccb1972ba43217085025fe5a6c3f", upload-time = "2025-08-05T20:11:51Z" },
|
| 752 |
+
]
|
| 753 |
+
|
| 754 |
+
[[package]]
|
| 755 |
+
name = "tqdm"
|
| 756 |
+
version = "4.70.0"
|
| 757 |
+
source = { registry = "https://pypi.org/simple" }
|
| 758 |
+
dependencies = [
|
| 759 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 760 |
+
]
|
| 761 |
+
sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" }
|
| 762 |
+
wheels = [
|
| 763 |
+
{ url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" },
|
| 764 |
+
]
|
| 765 |
+
|
| 766 |
+
[[package]]
|
| 767 |
+
name = "transformers"
|
| 768 |
+
version = "4.57.6"
|
| 769 |
+
source = { registry = "https://pypi.org/simple" }
|
| 770 |
+
dependencies = [
|
| 771 |
+
{ name = "filelock" },
|
| 772 |
+
{ name = "huggingface-hub" },
|
| 773 |
+
{ name = "numpy" },
|
| 774 |
+
{ name = "packaging" },
|
| 775 |
+
{ name = "pyyaml" },
|
| 776 |
+
{ name = "regex" },
|
| 777 |
+
{ name = "requests" },
|
| 778 |
+
{ name = "safetensors" },
|
| 779 |
+
{ name = "tokenizers" },
|
| 780 |
+
{ name = "tqdm" },
|
| 781 |
+
]
|
| 782 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" }
|
| 783 |
+
wheels = [
|
| 784 |
+
{ url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498, upload-time = "2026-01-16T10:38:31.289Z" },
|
| 785 |
+
]
|
| 786 |
+
|
| 787 |
+
[[package]]
|
| 788 |
+
name = "triton"
|
| 789 |
+
version = "3.4.0"
|
| 790 |
+
source = { registry = "https://pypi.org/simple" }
|
| 791 |
+
dependencies = [
|
| 792 |
+
{ name = "setuptools", marker = "sys_platform == 'linux' or sys_platform == 'win32'" },
|
| 793 |
+
]
|
| 794 |
+
wheels = [
|
| 795 |
+
{ url = "https://files.pythonhosted.org/packages/62/ee/0ee5f64a87eeda19bbad9bc54ae5ca5b98186ed00055281fd40fb4beb10e/triton-3.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ff2785de9bc02f500e085420273bb5cc9c9bb767584a4aa28d6e360cec70128", size = 155430069, upload-time = "2025-07-30T19:58:21.715Z" },
|
| 796 |
+
]
|
| 797 |
+
|
| 798 |
+
[[package]]
|
| 799 |
+
name = "typing-extensions"
|
| 800 |
+
version = "4.16.0"
|
| 801 |
+
source = { registry = "https://pypi.org/simple" }
|
| 802 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
|
| 803 |
+
wheels = [
|
| 804 |
+
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
| 805 |
+
]
|
| 806 |
+
|
| 807 |
+
[[package]]
|
| 808 |
+
name = "urllib3"
|
| 809 |
+
version = "2.7.0"
|
| 810 |
+
source = { registry = "https://pypi.org/simple" }
|
| 811 |
+
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
| 812 |
+
wheels = [
|
| 813 |
+
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
| 814 |
+
]
|
manifests/phase21a_training_environment_v1.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": "phase21a_training_environment_v1",
|
| 3 |
+
"captured_at": "2026-08-27",
|
| 4 |
+
"operating_system": "Ubuntu 22.04.5 LTS",
|
| 5 |
+
"python": "3.10.12",
|
| 6 |
+
"gpu_reference": {
|
| 7 |
+
"name": "NVIDIA GeForce RTX 5090",
|
| 8 |
+
"memory_mib": 32607,
|
| 9 |
+
"compute_capability": "12.0",
|
| 10 |
+
"driver": "580.173.02"
|
| 11 |
+
},
|
| 12 |
+
"cuda_runtime_from_torch": "12.8",
|
| 13 |
+
"packages": {
|
| 14 |
+
"torch": "2.8.0+cu128",
|
| 15 |
+
"torchvision": "0.23.0+cu128",
|
| 16 |
+
"transformers": "4.57.6",
|
| 17 |
+
"peft": "0.19.1",
|
| 18 |
+
"accelerate": "1.14.0",
|
| 19 |
+
"flash-attn": "2.8.3.post1",
|
| 20 |
+
"qwen-vl-utils": "0.0.14",
|
| 21 |
+
"numpy": "1.26.4",
|
| 22 |
+
"pillow": "11.3.0",
|
| 23 |
+
"safetensors": "0.8.0",
|
| 24 |
+
"huggingface-hub": "0.36.2",
|
| 25 |
+
"packaging": "26.2"
|
| 26 |
+
},
|
| 27 |
+
"base_model": {
|
| 28 |
+
"repo_id": "unsloth/Qwen3-VL-4B-Instruct",
|
| 29 |
+
"revision": "252d592b59b0233b226875a44ac135cfa1d3f755",
|
| 30 |
+
"license_reported_by_hub": "apache-2.0"
|
| 31 |
+
},
|
| 32 |
+
"upstream_robometer": {
|
| 33 |
+
"repository": "https://github.com/robometer/robometer",
|
| 34 |
+
"revision": "a669dffc241d7d76bec12f36efd4084d914d017c",
|
| 35 |
+
"environment_source": ["pyproject.toml", "uv.lock"]
|
| 36 |
+
},
|
| 37 |
+
"notes": [
|
| 38 |
+
"Do not upload or copy the local .venv; recreate it from a frozen lock or container.",
|
| 39 |
+
"Use a separate upload-only environment if upgrading huggingface-hub.",
|
| 40 |
+
"bitsandbytes is not required or allowed for formal Robometer-Low training."
|
| 41 |
+
]
|
| 42 |
+
}
|
manifests/phase2_remediation_pairs_v1_summary.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"status": "pass",
|
| 3 |
+
"role": "phase2_five_percent_id_only_remediation",
|
| 4 |
+
"seed": 1701,
|
| 5 |
+
"requested_confirmation_fraction_of_full_stream": 0.2,
|
| 6 |
+
"pair_count": 2689,
|
| 7 |
+
"original_stream_pair_count": 538,
|
| 8 |
+
"train_pair_count": 2021,
|
| 9 |
+
"original_dev_pair_count": 108,
|
| 10 |
+
"confirmation_dev_pair_count": 538,
|
| 11 |
+
"excluded_original_dev_component_pair_count": 22,
|
| 12 |
+
"component_count": 2393,
|
| 13 |
+
"eligible_confirmation_component_count": 1874,
|
| 14 |
+
"selected_confirmation_component_count": 495,
|
| 15 |
+
"original_dev_preserved_verbatim": true,
|
| 16 |
+
"five_percent_is_exact_one_percent_prefix_extension": true,
|
| 17 |
+
"confirmation_ids_seen_in_original_stream": 0,
|
| 18 |
+
"active_split_trajectory_id_overlaps": {
|
| 19 |
+
"train_dev": 0,
|
| 20 |
+
"train_confirmation_dev": 0,
|
| 21 |
+
"dev_confirmation_dev": 0
|
| 22 |
+
},
|
| 23 |
+
"active_unique_feature_sides": 5181,
|
| 24 |
+
"manifest": "manifests/phase2_remediation_pairs_v1.jsonl",
|
| 25 |
+
"manifest_sha256": "c77642e3d34177d1bfbcebe8c97cdf0af952c83d95a677cf42d95426b6d49204"
|
| 26 |
+
}
|
provenance/source_revisions.yaml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
schema_version: phase21a_source_revisions_v1
|
| 2 |
+
package_date: 2026-08-27
|
| 3 |
+
workspace_base_revision: 2053d617d3a3dab8d008395948337d20e8947f57
|
| 4 |
+
upstream_robometer:
|
| 5 |
+
repository: https://github.com/robometer/robometer
|
| 6 |
+
revision: a669dffc241d7d76bec12f36efd4084d914d017c
|
| 7 |
+
base_model:
|
| 8 |
+
repo_id: unsloth/Qwen3-VL-4B-Instruct
|
| 9 |
+
revision: 252d592b59b0233b226875a44ac135cfa1d3f755
|
| 10 |
+
processed_dataset:
|
| 11 |
+
repo_id: robometer/processed_datasets
|
| 12 |
+
revision: 1a8eb2d279c70964fadeaf8019a77c490c3abe02
|
| 13 |
+
local_profile: stage2_core_v1
|
| 14 |
+
exact_rbm1m_claim_allowed: false
|
| 15 |
+
protocol:
|
| 16 |
+
config: configs/phase21a_robometer_low_lora_train_v2.yaml
|
| 17 |
+
manifest_sha256: c77642e3d34177d1bfbcebe8c97cdf0af952c83d95a677cf42d95426b6d49204
|
reports/PHASE21A_ROBOMETER_LOW_SMOKE_V1.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 2.1A Robometer-Low-LoRA smoke test
|
| 2 |
+
|
| 3 |
+
## 结论
|
| 4 |
+
|
| 5 |
+
Robometer-Low-LoRA 的真实数据 smoke 已通过。正式训练容量配置为:
|
| 6 |
+
|
| 7 |
+
```text
|
| 8 |
+
micro_batch_pairs = 32
|
| 9 |
+
gradient_accumulation = 2
|
| 10 |
+
effective_batch_pairs = 64
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
该配置在 RTX 5090 31.335 GiB 上、使用训练集中最长指令的真实 pair,完整执行了两个累计
|
| 14 |
+
micro-step 和一次 optimizer update;峰值 allocated/reserved 为 26.114/28.150 GiB(reserved
|
| 15 |
+
89.84%),吞吐 2.497 pair/s。总 loss、三个
|
| 16 |
+
component loss 和全部可训练梯度有限;LoRA、新增特殊 token 行、preference/progress/success 三个
|
| 17 |
+
head 的梯度范数均非零。
|
| 18 |
+
|
| 19 |
+
本轮没有启动正式训练,也没有修改 Cosmos3-Edge。
|
| 20 |
+
|
| 21 |
+
## 本轮 Robometer-Low-LoRA 定义
|
| 22 |
+
|
| 23 |
+
保留真实 Robometer-on-Qwen 的结构操作:A/B 两条轨迹在同一次前向中联合输入;每帧后放置
|
| 24 |
+
`<|prog_token|>`;两轨迹之间放置 `<|split_token|>`;末尾用 `<|pref_token|>` 做直接二分类;
|
| 25 |
+
progress、success、preference head 均使用 `Linear(D,D/2) → LayerNorm → GELU → Dropout → Linear`。
|
| 26 |
+
每个 pair 是 8+8=16 张真实 256×256 letterbox 图像。最终边界复验特意选取 train split 中任务
|
| 27 |
+
文本最长的 8 个 pair(88–158 字符),自然序列长度为 1,302–1,319 token。
|
| 28 |
+
|
| 29 |
+
“LoRA”表示参数高效训练:冻结视觉塔与语言基座,仅在语言注意力
|
| 30 |
+
q/k/v/o 投影上训练 LoRA(r=8, alpha=16),同时训练五个新增特殊 token 的 embedding 行和三个
|
| 31 |
+
预测头。总可训练参数 15,768,332,其中 LoRA+token rows 5,911,040,heads 9,857,292。
|
| 32 |
+
|
| 33 |
+
命名中的 “Low” 只表示使用非完整版 Public-Proxy 数据;全量 decoder 版本命名为
|
| 34 |
+
Robometer-Low,当前版本明确命名为 Robometer-Low-LoRA。
|
| 35 |
+
|
| 36 |
+
## 容量搜索
|
| 37 |
+
|
| 38 |
+
每个容量 probe 均在独立进程加载同一 Qwen 快照,并执行联合 16 帧的真实前向、三损失反向与
|
| 39 |
+
optimizer update。显存安全规则预先固定为 peak reserved 不超过物理显存的 90%。
|
| 40 |
+
|
| 41 |
+
| micro batch | grad accum | effective batch | peak reserved GiB | reserved % | pair/s | 判定 |
|
| 42 |
+
|---:|---:|---:|---:|---:|---:|---|
|
| 43 |
+
| 2 | 1 | 2 | 10.172 | 32.46 | 1.464 | pass |
|
| 44 |
+
| 4 | 1 | 4 | 10.719 | 34.21 | 1.883 | pass |
|
| 45 |
+
| 8 | 1 | 8 | 13.023 | 41.56 | 2.099 | pass |
|
| 46 |
+
| 16 | 1 | 16 | 17.670 | 56.39 | 2.270 | pass |
|
| 47 |
+
| 24 | 1 | 24 | 22.889 | 73.05 | 2.426 | pass |
|
| 48 |
+
| 32 | 1 | 32 | 26.877 | 85.77 | 2.459 | pass |
|
| 49 |
+
| 34 | 1 | 34 | 28.006 | 89.38 | 2.451 | single-step pass |
|
| 50 |
+
| 35 | 1 | 35 | 28.658 | 91.46 | 2.477 | 越过安全线 |
|
| 51 |
+
| 34 | 2 | 68 | 28.518 | 91.01 | 1.773 | 最长指令复验通过但越线 |
|
| 52 |
+
| 33 | 2 | 66 | 27.982 | 89.30 | 2.446 | pass,后由用户改为 32 |
|
| 53 |
+
| **32** | **2** | **64** | **28.150** | **89.84** | **2.497** | **最长指令复验,用户确认** |
|
| 54 |
+
|
| 55 |
+
micro-batch 1 的首次运行包含 CUDA kernel/allocator warm-up,11.34 s 的耗时不用于吞吐比较;
|
| 56 |
+
micro-batch 3、6 及全部原始点仍保存在 machine-readable 报告和 run JSON 中。表中 1–35 的
|
| 57 |
+
单步搜索使用前缀 smoke pool;最终 33×2 与相邻 34×2 使用全量 train split 的最长指令 pool,
|
| 58 |
+
最终 32×2 使用相同的最长指令 pool,是当前权威配置。
|
| 59 |
+
|
| 60 |
+
## 边界与下一步
|
| 61 |
+
|
| 62 |
+
这只证明实现可训练、三条 loss/gradient 路径接通以及单卡容量满足要求,不证明模型收敛或指标
|
| 63 |
+
提升。正式 Phase 2.1A、学习率/步数、checkpoint 选择和 Cosmos3-Edge 对应改造仍未开始,需用户
|
| 64 |
+
确认后另行执行。
|
reports/PHASE21A_STEP_BUDGET_V2.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 2.1A step budget v2
|
| 2 |
+
|
| 3 |
+
Date: 2026-08-27
|
| 4 |
+
|
| 5 |
+
## Decision
|
| 6 |
+
|
| 7 |
+
Replace the inherited 15,000-step fixed run with a 3,000-step hard cap and
|
| 8 |
+
pre-registered ID-dev early stopping. Apply the same cap and selection rule to
|
| 9 |
+
Robometer-Low-LoRA and the future full-decoder Robometer-Low comparison.
|
| 10 |
+
|
| 11 |
+
## Scale calculation
|
| 12 |
+
|
| 13 |
+
- Frozen train set: 2,021 preference pairs.
|
| 14 |
+
- Effective batch: 64 pairs per optimizer step.
|
| 15 |
+
- 15,000 steps: 960,000 pair presentations, approximately 475 nominal passes.
|
| 16 |
+
- 3,000-step cap: 192,000 pair presentations, approximately 95 nominal passes.
|
| 17 |
+
- Minimum 1,000 steps: 64,000 pair presentations, approximately 32 nominal passes.
|
| 18 |
+
|
| 19 |
+
The official 15,000-step value was copied from the million-scale RBM-1M recipe;
|
| 20 |
+
it was not estimated from this 2,021-pair Low split.
|
| 21 |
+
|
| 22 |
+
## Evidence from the stopped v1 run
|
| 23 |
+
|
| 24 |
+
The user-stopped seed-7 run logged through step 246. Mean total train loss fell
|
| 25 |
+
from 4.573 over steps 1-25 to 3.218 over steps 201-225; the last-100-step linear
|
| 26 |
+
trend was still negative by about 0.455 per 100 steps. It had not converged.
|
| 27 |
+
However, the 15,000-step scheduler used 750 warmup steps, so step 246 had reached
|
| 28 |
+
only about 33% of the target learning rate. This curve cannot be extrapolated to
|
| 29 |
+
justify 15,000 steps.
|
| 30 |
+
|
| 31 |
+
## Selection protocol
|
| 32 |
+
|
| 33 |
+
- Scheduler: cosine with 5% warmup; 150 warmup steps at the cap.
|
| 34 |
+
- Evaluate every 250 steps on the original 108-pair `dev` split.
|
| 35 |
+
- Evaluate both A/B orientations for each pair.
|
| 36 |
+
- Select by the sum of mean preference, progress, and success ID-dev losses.
|
| 37 |
+
- Minimum training: 1,000 steps.
|
| 38 |
+
- Early stop after four consecutive evaluations without an improvement of at
|
| 39 |
+
least 0.005 ID-dev loss.
|
| 40 |
+
- Restore the best ID-dev checkpoint for export.
|
| 41 |
+
- Do not use `confirmation_dev` or RBM-EVAL-OOD for selection.
|
| 42 |
+
|
| 43 |
+
The cap is a safety bound, not a claim that every seed needs exactly 3,000
|
| 44 |
+
steps. The selected step is an experimental result recorded per seed.
|
| 45 |
+
|
| 46 |
+
## Restart boundary
|
| 47 |
+
|
| 48 |
+
The v1 run is not resumed into v2. Its latest complete checkpoint is step 4,
|
| 49 |
+
and v2 changes both the scheduler horizon and checkpoint-selection protocol.
|
| 50 |
+
All v2 seeds must start from the same pinned base model in a new output root.
|
reports/PHASE21A_TRAINING_MANUAL.md
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 2.1A 训练手册
|
| 2 |
+
|
| 3 |
+
日期:2026-08-27
|
| 4 |
+
对象:`Robometer-Low-LoRA` 与 `Robometer-Low`
|
| 5 |
+
数据:`Public-Proxy stage2_core_v1` 的冻结 5% pair manifest
|
| 6 |
+
|
| 7 |
+
## 1. 名称和不可变边界
|
| 8 |
+
|
| 9 |
+
- `Robometer-Low-LoRA`:Qwen3-VL-4B 视觉塔和语言基座冻结;训练 attention LoRA、五个新增
|
| 10 |
+
token rows 和 progress/success/preference 三头。
|
| 11 |
+
- `Robometer-Low`:视觉塔冻结;语言 decoder 全量微调;训练三个 heads;`use_peft=false`。
|
| 12 |
+
- `Low` 只表示使用非完整版 Public-Proxy 数据,不表示量化或 LoRA。
|
| 13 |
+
- 两者使用同一 2,021-pair train split、同一 A/B 联合输入、同一 8+8 帧、同一三损失合同。
|
| 14 |
+
- Robometer-Low 正式训练只允许标准 `torch.optim.AdamW`;禁止 paged AdamW 8-bit。
|
| 15 |
+
- OOD benchmark 不进入训练 loop、checkpoint selection 或超参数选择。
|
| 16 |
+
|
| 17 |
+
冻结数据统计:2,689 rows,其中 train/dev/confirmation-dev/excluded 分别为
|
| 18 |
+
2,021/108/538/22;manifest SHA256 为
|
| 19 |
+
`c77642e3d34177d1bfbcebe8c97cdf0af952c83d95a677cf42d95426b6d49204`。
|
| 20 |
+
|
| 21 |
+
## 2. 当前代码成熟度
|
| 22 |
+
|
| 23 |
+
| 分支 | 状态 | 可以做什么 |
|
| 24 |
+
|---|---|---|
|
| 25 |
+
| Robometer-Low-LoRA v1 | 已按用户要求停止 | 日志到 step 246;最新完整 checkpoint 为 step 4 |
|
| 26 |
+
| Robometer-Low-LoRA v2 | 已配置,未正式启动 | 3,000-step cap、在线 ID-dev、早停和最佳 checkpoint 恢复 |
|
| 27 |
+
| Robometer-Low | 等待服务器资源和正式 runner | 单卡容量 smoke;不能把当前 smoke 当正式训练入口 |
|
| 28 |
+
|
| 29 |
+
LoRA v1 已停止且不再续训。v2 使用全新 output root;禁止拿 v1 的 step-4 checkpoint 恢复到 v2,
|
| 30 |
+
因为总预算、scheduler 和选择协议均已改变。
|
| 31 |
+
|
| 32 |
+
## 3. 参考环境
|
| 33 |
+
|
| 34 |
+
已验证环境:
|
| 35 |
+
|
| 36 |
+
| 项目 | 版本 |
|
| 37 |
+
|---|---|
|
| 38 |
+
| OS | Ubuntu 22.04.5 LTS |
|
| 39 |
+
| Python | 3.10.12 |
|
| 40 |
+
| NVIDIA driver | 580.173.02 |
|
| 41 |
+
| PyTorch / CUDA runtime | 2.8.0+cu128 / 12.8 |
|
| 42 |
+
| torchvision | 0.23.0+cu128 |
|
| 43 |
+
| transformers | 4.57.6 |
|
| 44 |
+
| PEFT | 0.19.1 |
|
| 45 |
+
| accelerate | 1.14.0 |
|
| 46 |
+
| flash-attn | 2.8.3.post1 |
|
| 47 |
+
| qwen-vl-utils | 0.0.14 |
|
| 48 |
+
| NumPy / Pillow | 1.26.4 / 11.3.0 |
|
| 49 |
+
|
| 50 |
+
可部署环境固定在 `environment/phase21a_robometer/pyproject.toml` 和 `uv.lock`,不复制 `.venv`:
|
| 51 |
+
|
| 52 |
+
```bash
|
| 53 |
+
cd /srv/phase21a-kit
|
| 54 |
+
uv python install 3.10.12
|
| 55 |
+
uv sync --project environment/phase21a_robometer --frozen
|
| 56 |
+
uv run --project environment/phase21a_robometer \
|
| 57 |
+
python environment/phase21a_robometer/doctor.py
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
如果上传包内保存了相同 `pyproject.toml` 和 `uv.lock`,仍要在目标 CUDA/driver 上重新创建环境。
|
| 61 |
+
不要为上传工具升级训练 venv;`hf` CLI 可使用单独小环境。
|
| 62 |
+
|
| 63 |
+
## 4. 下载并固定输入
|
| 64 |
+
|
| 65 |
+
以下使用示例变量;按服务器目录修改:
|
| 66 |
+
|
| 67 |
+
```bash
|
| 68 |
+
PHASE21A_KIT=/srv/phase21a-kit
|
| 69 |
+
PHASE21A_DATA=/srv/phase21a-data
|
| 70 |
+
PHASE21A_MODEL=/srv/models/qwen3-vl-4b-instruct-252d592
|
| 71 |
+
PHASE21A_RUNS=/srv/phase21a-runs
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
下载 training kit 和 dataset:
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
hf download ar-mine/cosmos-robometer-private-code \
|
| 78 |
+
--revision <KIT_COMMIT> \
|
| 79 |
+
--local-dir "$PHASE21A_KIT"
|
| 80 |
+
|
| 81 |
+
hf download ar-mine/cosmos-robometer-public-dataset \
|
| 82 |
+
--repo-type dataset \
|
| 83 |
+
--revision <DATA_COMMIT> \
|
| 84 |
+
--local-dir "$PHASE21A_DATA"
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
下载精确基础模型;不要使用浮动 `main`:
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
hf download unsloth/Qwen3-VL-4B-Instruct \
|
| 91 |
+
--revision 252d592b59b0233b226875a44ac135cfa1d3f755 \
|
| 92 |
+
--local-dir "$PHASE21A_MODEL"
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
## 5. 开跑前共同门禁
|
| 96 |
+
|
| 97 |
+
### 5.1 文件完整性
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
cd "$PHASE21A_DATA"
|
| 101 |
+
sha256sum --check SHA256SUMS
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
再运行 dataset bundle 工具的 dry-run,把 `--output-dir` 指向任意不会执行复制的路径:
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
cd "$PHASE21A_KIT"
|
| 108 |
+
uv run --project environment/phase21a_robometer python scripts/build_phase21a_hf_dataset_bundle.py \
|
| 109 |
+
--manifest "$PHASE21A_DATA/manifests/phase2_remediation_pairs_v1.jsonl" \
|
| 110 |
+
--processed-root "$PHASE21A_DATA/processed_datasets" \
|
| 111 |
+
--output-dir /tmp/phase21a-dry-run
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
必须得到:2,689 rows、4,323 unique frame files、0 missing、7.908 GiB。
|
| 115 |
+
|
| 116 |
+
### 5.2 GPU/软件门禁
|
| 117 |
+
|
| 118 |
+
```bash
|
| 119 |
+
nvidia-smi
|
| 120 |
+
uv run --project environment/phase21a_robometer \
|
| 121 |
+
python environment/phase21a_robometer/doctor.py
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
必须确认:BF16 可用、FlashAttention 2 可加载、模型 snapshot 离线可读、目标输出盘有足够空间。
|
| 125 |
+
|
| 126 |
+
### 5.3 数据和评测隔离
|
| 127 |
+
|
| 128 |
+
- formal trainer 只读取 `split=train` 且 `sample_type=preference`,精确为 2,021 pairs。
|
| 129 |
+
- dev/confirmation-dev 只能用于 ID 报告或预注册 checkpoint selection。
|
| 130 |
+
- `excluded_original_dev_component` 不得回流训练。
|
| 131 |
+
- RBM-EVAL-OOD 不下载到训练节点也可以;不得据其结果调参。
|
| 132 |
+
|
| 133 |
+
## 6. Robometer-Low-LoRA
|
| 134 |
+
|
| 135 |
+
### 6.1 冻结配置
|
| 136 |
+
|
| 137 |
+
| 参数 | 值 |
|
| 138 |
+
|---|---:|
|
| 139 |
+
| base | `unsloth/Qwen3-VL-4B-Instruct@252d592...` |
|
| 140 |
+
| frames per pair | A 8 + B 8 |
|
| 141 |
+
| canvas | 256×256 letterbox |
|
| 142 |
+
| LoRA | r=8, alpha=16, dropout=0.05 |
|
| 143 |
+
| LoRA targets | q/k/v/o projections |
|
| 144 |
+
| trainable | LoRA + 5 special-token rows + 3 heads |
|
| 145 |
+
| trainable params | 15,768,332 |
|
| 146 |
+
| micro batch | 32 pairs |
|
| 147 |
+
| gradient accumulation | 2 |
|
| 148 |
+
| effective batch | 64 pairs |
|
| 149 |
+
| precision | BF16 |
|
| 150 |
+
| attention | FlashAttention 2 |
|
| 151 |
+
| activation checkpointing | enabled |
|
| 152 |
+
| optimizer | standard AdamW |
|
| 153 |
+
| LoRA LR / heads LR | 2e-6 / 1e-4 |
|
| 154 |
+
| weight decay | 0.01 |
|
| 155 |
+
| scheduler | cosine, 5% warmup |
|
| 156 |
+
| grad clip | 1.0 |
|
| 157 |
+
| steps | 3,000 hard cap per seed;ID-dev early stop |
|
| 158 |
+
| seeds | 7, 17, 27 |
|
| 159 |
+
| checkpoint/eval | every 250 steps;min 1,000;patience 4;min delta 0.005 |
|
| 160 |
+
|
| 161 |
+
新增 token 的顺序必须保持:
|
| 162 |
+
|
| 163 |
+
```text
|
| 164 |
+
<|split_token|>, <|reward_token|>, <|pref_token|>, <|sim_token|>, <|prog_token|>
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
在当前 base revision 上对应 ID 为 151669–151673。
|
| 168 |
+
|
| 169 |
+
### 6.2 单步 smoke
|
| 170 |
+
|
| 171 |
+
在新服务器上必须先跑最长 prompt pool 的真实一步:
|
| 172 |
+
|
| 173 |
+
```bash
|
| 174 |
+
cd "$PHASE21A_KIT"
|
| 175 |
+
uv run --project environment/phase21a_robometer python scripts/smoke_phase21a_robometer_low.py \
|
| 176 |
+
--snapshot "$PHASE21A_MODEL" \
|
| 177 |
+
--manifest "$PHASE21A_DATA/manifests/phase2_remediation_pairs_v1.jsonl" \
|
| 178 |
+
--processed-root "$PHASE21A_DATA/processed_datasets" \
|
| 179 |
+
--micro-batch-pairs 32 \
|
| 180 |
+
--gradient-accumulation 2 \
|
| 181 |
+
--optimizer-steps 1 \
|
| 182 |
+
--pair-pool-size 8 \
|
| 183 |
+
--seed 7 \
|
| 184 |
+
--lora-lr 2e-6 \
|
| 185 |
+
--head-lr 1e-4 \
|
| 186 |
+
--output /tmp/phase21a_lora_smoke.json
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
通过条件:
|
| 190 |
+
|
| 191 |
+
- `status=pass`;
|
| 192 |
+
- effective batch 精确 64;
|
| 193 |
+
- sequence max 不超过已知最长 1,319 tokens;
|
| 194 |
+
- 三个 loss 有限;
|
| 195 |
+
- LoRA、token rows、三个 heads 的梯度都有限且非零;
|
| 196 |
+
- 峰值 reserved 不超过本机显存安全线。RTX 5090 参考值为 28.150 GiB,但其他 GPU 不应机械套用。
|
| 197 |
+
|
| 198 |
+
### 6.3 正式启动
|
| 199 |
+
|
| 200 |
+
portable runner 已支持显式模型、manifest 和数据路径:
|
| 201 |
+
|
| 202 |
+
```bash
|
| 203 |
+
cd "$PHASE21A_KIT"
|
| 204 |
+
uv run --project environment/phase21a_robometer python scripts/run_phase21a_robometer_low_lora_seeds.py \
|
| 205 |
+
--snapshot "$PHASE21A_MODEL" \
|
| 206 |
+
--manifest "$PHASE21A_DATA/manifests/phase2_remediation_pairs_v1.jsonl" \
|
| 207 |
+
--processed-root "$PHASE21A_DATA/processed_datasets" \
|
| 208 |
+
--output-root "$PHASE21A_RUNS/robometer-low-lora-v2" \
|
| 209 |
+
--seeds 7 17 27 \
|
| 210 |
+
--optimizer-steps 3000 \
|
| 211 |
+
--micro-batch-pairs 32 \
|
| 212 |
+
--gradient-accumulation 2 \
|
| 213 |
+
--lora-lr 2e-6 \
|
| 214 |
+
--head-lr 1e-4 \
|
| 215 |
+
--checkpoint-every 250 \
|
| 216 |
+
--eval-every 250 \
|
| 217 |
+
--early-stop-min-steps 1000 \
|
| 218 |
+
--early-stop-patience-evals 4 \
|
| 219 |
+
--early-stop-min-delta 0.005
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
生产服务器上应由 Slurm、systemd 或其他作业调度器托管;不要依赖普通 SSH shell 的 `&`。每次只运行
|
| 223 |
+
一个 runner,避免两个进程写同一 output root。
|
| 224 |
+
|
| 225 |
+
### 6.4 恢复
|
| 226 |
+
|
| 227 |
+
runner 会检测每个 seed 的:
|
| 228 |
+
|
| 229 |
+
- 有 `summary.json`:跳过已完成 seed;
|
| 230 |
+
- 有 `latest_training_state.pt`:自动加 `--resume`;
|
| 231 |
+
- 两者都没有:从 step 0 开始。
|
| 232 |
+
|
| 233 |
+
完整 checkpoint 包含 adapter、heads、AdamW、scheduler、NumPy/PyTorch/CUDA RNG、数据 permutation
|
| 234 |
+
和 cursor。恢复前不得修改 manifest;trainer 会核对 manifest SHA256 和 seed。
|
| 235 |
+
|
| 236 |
+
### 6.5 监控
|
| 237 |
+
|
| 238 |
+
```bash
|
| 239 |
+
tail -f "$PHASE21A_RUNS/robometer-low-lora-v2/seed_7/progress.jsonl"
|
| 240 |
+
nvidia-smi
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
每条 progress record 应包含三个 loss、全局和分组梯度、LR、序列长度、step time 和显存。立即停止的
|
| 244 |
+
异常包括:NaN/Inf、任一必要梯度组为零、manifest hash 改变、frame file 缺失或 OOM。
|
| 245 |
+
|
| 246 |
+
RTX 5090 当前参考吞吐约 23–24 秒/step、2.7 pairs/s;硬件和温控会改变 ETA。
|
| 247 |
+
|
| 248 |
+
### 6.6 完成产物
|
| 249 |
+
|
| 250 |
+
每个 seed 完成后应出现:
|
| 251 |
+
|
| 252 |
+
```text
|
| 253 |
+
seed_<n>/
|
| 254 |
+
├── adapter/
|
| 255 |
+
├── reward_heads.pt
|
| 256 |
+
├── best_model_state.pt
|
| 257 |
+
├── latest_training_state.pt
|
| 258 |
+
├── progress.jsonl
|
| 259 |
+
├── id_dev_metrics.jsonl
|
| 260 |
+
└── summary.json
|
| 261 |
+
```
|
| 262 |
+
|
| 263 |
+
发布前另外保存 processor/tokenizer 和特殊 token map;对外权重优先使用 safetensors。v2 的
|
| 264 |
+
`adapter/` 和 `reward_heads.pt` 会恢复为 ID-dev 最佳 step,而不是机械使用最后一步。
|
| 265 |
+
|
| 266 |
+
## 7. Robometer-Low 全量 decoder 训练
|
| 267 |
+
|
| 268 |
+
### 7.1 当前禁止直接正式启动
|
| 269 |
+
|
| 270 |
+
单张 RTX 5090 上,标准 AdamW 即使 `micro_batch_pairs=1` 也在第一次 optimizer-state 初始化时 OOM。
|
| 271 |
+
PagedAdamW8bit 虽能跑通容量 smoke,但用户已明确禁止其用于正式 Robometer-Low。
|
| 272 |
+
|
| 273 |
+
当前 `smoke_phase21a_robometer_low_full.py` 只是容量和梯度审计工具,不具备:
|
| 274 |
+
|
| 275 |
+
- 多 GPU/FSDP;
|
| 276 |
+
- 正式 3,000-step cap 和 ID-dev 早停的可恢复状态;
|
| 277 |
+
- distributed RNG/data cursor 恢复;
|
| 278 |
+
- sharded optimizer checkpoint;
|
| 279 |
+
- 三 seed runner。
|
| 280 |
+
|
| 281 |
+
因此在正式 full runner 实现并通过下述门禁前,Robometer-Low 状态保持
|
| 282 |
+
`waiting_for_server_resources`,不能把 smoke 循环延长后冒充正式训练。
|
| 283 |
+
|
| 284 |
+
### 7.2 目标正式配置
|
| 285 |
+
|
| 286 |
+
| 参数 | 目标值 |
|
| 287 |
+
|---|---:|
|
| 288 |
+
| vision encoder | frozen |
|
| 289 |
+
| language decoder | full trainable,`use_peft=false` |
|
| 290 |
+
| heads | full trainable |
|
| 291 |
+
| language trainable params | 约 4.0218B |
|
| 292 |
+
| heads trainable params | 9.857M |
|
| 293 |
+
| optimizer | standard torch AdamW,禁止 bitsandbytes/paged |
|
| 294 |
+
| LR | 2e-5 |
|
| 295 |
+
| weight decay | 0.01 |
|
| 296 |
+
| precision/attention | BF16 / FlashAttention 2 |
|
| 297 |
+
| activation checkpointing | enabled |
|
| 298 |
+
| scheduler | cosine,5% warmup |
|
| 299 |
+
| grad clip | 10.0(与当前 full smoke/官方设置一致) |
|
| 300 |
+
| global effective batch | 至少且优先精确为 64 pairs |
|
| 301 |
+
| steps/seeds | 3,000 hard cap + 与 LoRA 相同 ID-dev 早停;7/17/27 |
|
| 302 |
+
|
| 303 |
+
分布式 global batch 计算公式:
|
| 304 |
+
|
| 305 |
+
```text
|
| 306 |
+
global_effective_batch = micro_batch_pairs_per_gpu × world_size × gradient_accumulation
|
| 307 |
+
```
|
| 308 |
+
|
| 309 |
+
例如 4 GPU、每卡 micro batch 8、accumulation 2 得到 64;这只是算术示例,必须以目标硬件 capacity
|
| 310 |
+
smoke 为准。
|
| 311 |
+
|
| 312 |
+
### 7.3 正式 runner 的实现门禁
|
| 313 |
+
|
| 314 |
+
正式代码必须满足:
|
| 315 |
+
|
| 316 |
+
1. 复用 LoRA v1 的 portable manifest loader、联合 A/B prompt、五 token 顺序和三 heads;
|
| 317 |
+
2. 只冻结视觉塔,语言 decoder 全量 `requires_grad=True`;
|
| 318 |
+
3. optimizer 类型审计精确为 `torch.optim.AdamW`,代码路径不导入 bitsandbytes;
|
| 319 |
+
4. FSDP `FULL_SHARD` 包裹 `Qwen3VLTextDecoderLayer`,并验证无未分片的巨型语言层;
|
| 320 |
+
5. global effective batch 精确记录并达到 64;
|
| 321 |
+
6. 每 rank 数据不重复,分布式 sampler 的 epoch/order/cursor 可恢复;
|
| 322 |
+
7. checkpoint 包含模型、heads、optimizer、scheduler、scaler(如有)、全部 RNG 和 sampler state;
|
| 323 |
+
8. 中间 checkpoint 优先 sharded state,最终再受控合并,避免 rank 0 保存时 OOM;
|
| 324 |
+
9. rank 0 写日志,所有 rank 对 NaN/OOM/数据异常做同步失败;
|
| 325 |
+
10. OOD 不进入训练或选择。
|
| 326 |
+
|
| 327 |
+
上游 Robometer 的 `robometer/configs/distributed/fsdp.yaml` 可作为 FSDP 配置参考,但官方
|
| 328 |
+
`train.py` 不能直接替代本实验 runner:其默认 PEFT 为 r=32 且覆盖 MLP projections,数据采样和当前
|
| 329 |
+
冻结 manifest 也不同。直接运行官方命令会形成另一个 protocol。
|
| 330 |
+
|
| 331 |
+
### 7.4 服务器验收顺序
|
| 332 |
+
|
| 333 |
+
1. 在隔离进程中加载模型,确认视觉 trainable=0、语言和 heads 数量正确;
|
| 334 |
+
2. 用最长 prompt 跑一次 FSDP forward/backward/标准 AdamW step;
|
| 335 |
+
3. 尝试相邻 micro batches,选择峰值显存不超过 90% 的最大安全值;
|
| 336 |
+
4. 通过 accumulation 调整 global effective batch 到 64;
|
| 337 |
+
5. 跑 100-step stability smoke,要求 0 NaN/Inf、0 OOM、0 empty mask;
|
| 338 |
+
6. 在 step 50 保存,重启后恢复到 51,并与不中断参考运行核对 loss/LR/data cursor;
|
| 339 |
+
7. 固定代码 commit、FSDP config hash、world size 和环境后,才允许三 seed 正式启动。
|
| 340 |
+
|
| 341 |
+
### 7.5 多 GPU checkpoint 和发布
|
| 342 |
+
|
| 343 |
+
- 训练 checkpoint 保留 sharded optimizer state,供相同或兼容 world size 恢复。
|
| 344 |
+
- 发布 checkpoint 只在训练结束后合并;合并节点要有足够 CPU RAM 和磁盘临时空间。
|
| 345 |
+
- 模型卡必须注明 base revision、数据 commit、world size、global batch、FSDP strategy 和标准 AdamW。
|
| 346 |
+
- 任何使用 8-bit/paged optimizer 的结果必须另命名为实验性分支,不能称为 Robometer-Low。
|
| 347 |
+
|
| 348 |
+
## 8. 最终开跑检查表
|
| 349 |
+
|
| 350 |
+
### LoRA
|
| 351 |
+
|
| 352 |
+
- [ ] kit/data/model 三个 commit/revision 已固定
|
| 353 |
+
- [ ] SHA256 全通过,4,323/4,323 frame files 可解析
|
| 354 |
+
- [ ] 单步 smoke 通过且显存安全
|
| 355 |
+
- [ ] output root 全新或只有可信完整 checkpoint
|
| 356 |
+
- [ ] 作业调度器托管,只有一个 runner
|
| 357 |
+
- [ ] seeds 7/17/27、3,000-step cap、ID-dev 早停、effective batch 64
|
| 358 |
+
- [ ] OOD 未接入训练 loop
|
| 359 |
+
|
| 360 |
+
### Full
|
| 361 |
+
|
| 362 |
+
- [ ] 服务器资源已分配
|
| 363 |
+
- [ ] 自定义正式 FSDP runner 已实现和 code review
|
| 364 |
+
- [ ] standard AdamW 类型审计通过,代码不走 bitsandbytes
|
| 365 |
+
- [ ] 100-step 和断点恢复一致性门禁通过
|
| 366 |
+
- [ ] global effective batch=64
|
| 367 |
+
- [ ] vision frozen、language full trainable
|
| 368 |
+
- [ ] 中间 sharded checkpoint 与最终合并流程均验证
|
| 369 |
+
- [ ] 完成上述全部条件后才从 `waiting_for_server_resources` 改为 `running`
|
reports/phase21a_robometer_low_smoke_v1.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"status": "pass_awaiting_user_confirmation_before_formal_work",
|
| 3 |
+
"protocol_id": "phase21a_robometer_low_lora_smoke_v1",
|
| 4 |
+
"phase": "Phase 2.1A",
|
| 5 |
+
"model": "Robometer-Low-LoRA",
|
| 6 |
+
"completed_at_local": "2026-08-27T16:46:00+08:00",
|
| 7 |
+
"scope": {
|
| 8 |
+
"smoke_only": true,
|
| 9 |
+
"formal_training_started": false,
|
| 10 |
+
"cosmos3_edge_modified": false
|
| 11 |
+
},
|
| 12 |
+
"contract": {
|
| 13 |
+
"data_route": "Public-Proxy stage2_core_v1",
|
| 14 |
+
"real_pair_pool_size": 8,
|
| 15 |
+
"final_pair_pool_selection": "longest_prompt_characters_in_train_split",
|
| 16 |
+
"final_pair_pool_prompt_character_range": [88, 158],
|
| 17 |
+
"frames_per_trajectory": 8,
|
| 18 |
+
"frames_per_joint_pair_forward": 16,
|
| 19 |
+
"canvas": [256, 256],
|
| 20 |
+
"preference": "direct BCE from <|pref_token|>",
|
| 21 |
+
"progress": "10-bin soft cross entropy from per-frame <|prog_token|>",
|
| 22 |
+
"success": "balanced BCE from per-frame <|prog_token|>",
|
| 23 |
+
"vision_frozen": true,
|
| 24 |
+
"language": "LoRA r8 alpha16 dropout0.05 on q/k/v/o",
|
| 25 |
+
"new_special_token_rows_trainable": true,
|
| 26 |
+
"trainable_heads": ["preference", "progress", "success"],
|
| 27 |
+
"precision": "BF16",
|
| 28 |
+
"gradient_checkpointing": true,
|
| 29 |
+
"attention": "flash_attention_2"
|
| 30 |
+
},
|
| 31 |
+
"official_robometer_training_reference": {
|
| 32 |
+
"train_vision_encoder": false,
|
| 33 |
+
"train_language_model": true,
|
| 34 |
+
"use_peft": false,
|
| 35 |
+
"interpretation": "official default fully fine-tunes the language decoder; this result is explicitly named Robometer-Low-LoRA"
|
| 36 |
+
},
|
| 37 |
+
"parameter_audit": {
|
| 38 |
+
"base_total": 4443056128,
|
| 39 |
+
"backbone_trainable_lora_and_token_rows": 5911040,
|
| 40 |
+
"heads_trainable": 9857292,
|
| 41 |
+
"all_trainable": 15768332
|
| 42 |
+
},
|
| 43 |
+
"gpu": {
|
| 44 |
+
"name": "NVIDIA GeForce RTX 5090",
|
| 45 |
+
"total_gib": 31.33453369140625,
|
| 46 |
+
"peak_reserved_limit_fraction": 0.90
|
| 47 |
+
},
|
| 48 |
+
"capacity_probes": [
|
| 49 |
+
{"micro_batch_pairs": 1, "peak_reserved_gib": 9.44921875, "peak_reserved_fraction": 0.3015592586460454, "pairs_per_second": 0.08816775947063833, "status": "pass_compile_warmup_timing_outlier"},
|
| 50 |
+
{"micro_batch_pairs": 2, "peak_reserved_gib": 10.171875, "peak_reserved_fraction": 0.32462187247387436, "pairs_per_second": 1.4644908823074159, "status": "pass"},
|
| 51 |
+
{"micro_batch_pairs": 3, "peak_reserved_gib": 10.171875, "peak_reserved_fraction": 0.32462187247387436, "pairs_per_second": 1.8326653558369463, "status": "pass"},
|
| 52 |
+
{"micro_batch_pairs": 4, "peak_reserved_gib": 10.71875, "peak_reserved_fraction": 0.3420746613165558, "pairs_per_second": 1.8833336172113138, "status": "pass"},
|
| 53 |
+
{"micro_batch_pairs": 6, "peak_reserved_gib": 11.853515625, "peak_reserved_fraction": 0.37828919816511974, "pairs_per_second": 2.0176592085188503, "status": "pass"},
|
| 54 |
+
{"micro_batch_pairs": 8, "peak_reserved_gib": 13.0234375, "peak_reserved_fraction": 0.4156257000107132, "pairs_per_second": 2.0987864768533617, "status": "pass"},
|
| 55 |
+
{"micro_batch_pairs": 16, "peak_reserved_gib": 17.669921875, "peak_reserved_fraction": 0.5639120737847814, "pairs_per_second": 2.2696475376426384, "status": "pass"},
|
| 56 |
+
{"micro_batch_pairs": 24, "peak_reserved_gib": 22.888671875, "peak_reserved_fraction": 0.7304615444549413, "pairs_per_second": 2.4263013301323024, "status": "pass"},
|
| 57 |
+
{"micro_batch_pairs": 32, "peak_reserved_gib": 26.876953125, "peak_reserved_fraction": 0.8577422402290679, "pairs_per_second": 2.4586052936303937, "status": "pass"},
|
| 58 |
+
{"micro_batch_pairs": 34, "peak_reserved_gib": 28.005859375, "peak_reserved_fraction": 0.8937697829114603, "pairs_per_second": 2.4506286870197433, "status": "pass_single_microstep"},
|
| 59 |
+
{"micro_batch_pairs": 35, "peak_reserved_gib": 28.658203125, "peak_reserved_fraction": 0.9145884667452302, "pairs_per_second": 2.4765409697399408, "status": "pass_but_rejected_over_memory_limit"}
|
| 60 |
+
],
|
| 61 |
+
"accumulation_boundary_checks": [
|
| 62 |
+
{"micro_batch_pairs": 34, "gradient_accumulation": 2, "effective_batch_pairs": 68, "pair_pool_selection": "longest_prompt_characters_in_train_split", "sequence_tokens_max": 1319, "peak_reserved_gib": 28.517578125, "peak_reserved_fraction": 0.9101006067571121, "pairs_per_second": 1.7730906691413035, "status": "pass_but_rejected_over_memory_limit"},
|
| 63 |
+
{"micro_batch_pairs": 33, "gradient_accumulation": 2, "effective_batch_pairs": 66, "pair_pool_selection": "longest_prompt_characters_in_train_split", "sequence_tokens_max": 1319, "peak_allocated_gib": 26.66872262954712, "peak_reserved_gib": 27.982421875, "peak_reserved_fraction": 0.8930218062467739, "pairs_per_second": 2.445795899300712, "seconds_per_microstep": 13.492540407576598, "status": "pass_superseded_by_user_batch_selection"},
|
| 64 |
+
{"micro_batch_pairs": 32, "gradient_accumulation": 2, "effective_batch_pairs": 64, "pair_pool_selection": "longest_prompt_characters_in_train_split", "sequence_tokens_max": 1319, "peak_allocated_gib": 26.11378002166748, "peak_reserved_gib": 28.150390625, "peak_reserved_fraction": 0.898382305677026, "pairs_per_second": 2.496645440085846, "seconds_per_microstep": 12.817198424017988, "status": "selected_pass_user_confirmed"}
|
| 65 |
+
],
|
| 66 |
+
"selected": {
|
| 67 |
+
"micro_batch_pairs": 32,
|
| 68 |
+
"gradient_accumulation": 2,
|
| 69 |
+
"effective_batch_pairs": 64,
|
| 70 |
+
"selection_reason": "user_confirmed_exact_effective_batch_64",
|
| 71 |
+
"optimizer_steps_validated": 1,
|
| 72 |
+
"microsteps_validated": 2,
|
| 73 |
+
"pair_pool_selection": "longest_prompt_characters_in_train_split",
|
| 74 |
+
"sequence_tokens_max": 1319,
|
| 75 |
+
"peak_allocated_gib": 26.11378002166748,
|
| 76 |
+
"peak_reserved_gib": 28.150390625,
|
| 77 |
+
"peak_reserved_fraction": 0.898382305677026,
|
| 78 |
+
"pairs_per_second": 2.496645440085846,
|
| 79 |
+
"loss_all_finite": true,
|
| 80 |
+
"gradient_all_finite": true,
|
| 81 |
+
"all_required_gradient_groups_nonzero": true
|
| 82 |
+
},
|
| 83 |
+
"artifacts": {
|
| 84 |
+
"selected_raw": "runs/phase21a_robometer_low_smoke_v1/worstcase_mb32_ga2_eff64.json",
|
| 85 |
+
"rejected_adjacent_raw": "runs/phase21a_robometer_low_smoke_v1/worstcase_mb34_ga2_eff68.json",
|
| 86 |
+
"script_sha256": "4d1ddd53cb26b1c9115808ff65ae21ecf8de36ca322bc0a2449ce4a9fd00fe89",
|
| 87 |
+
"config_sha256_before_final_result_annotation": "02ef3a8ff8dc35766ecd4c0800866522218f6ae0768a898b381b9ef6a18d7422"
|
| 88 |
+
}
|
| 89 |
+
}
|
scripts/build_phase21a_hf_dataset_bundle.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Materialize the minimal portable Phase 2.1A dataset bundle.
|
| 3 |
+
|
| 4 |
+
The source view may contain symlinks into cold storage. This script copies only
|
| 5 |
+
frame archives referenced by the frozen manifest and preserves their relative
|
| 6 |
+
paths so ``--processed-root <bundle>/processed_datasets`` remains valid.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import hashlib
|
| 13 |
+
import json
|
| 14 |
+
import shutil
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def sha256(path: Path) -> str:
|
| 19 |
+
digest = hashlib.sha256()
|
| 20 |
+
with path.open("rb") as stream:
|
| 21 |
+
for block in iter(lambda: stream.read(8 * 1024 * 1024), b""):
|
| 22 |
+
digest.update(block)
|
| 23 |
+
return digest.hexdigest()
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def main() -> None:
|
| 27 |
+
parser = argparse.ArgumentParser()
|
| 28 |
+
parser.add_argument("--manifest", type=Path, required=True)
|
| 29 |
+
parser.add_argument("--processed-root", type=Path, required=True)
|
| 30 |
+
parser.add_argument("--output-dir", type=Path, required=True)
|
| 31 |
+
parser.add_argument(
|
| 32 |
+
"--execute",
|
| 33 |
+
action="store_true",
|
| 34 |
+
help="Copy files and write inventory; without this flag only print the plan.",
|
| 35 |
+
)
|
| 36 |
+
args = parser.parse_args()
|
| 37 |
+
|
| 38 |
+
manifest = args.manifest.resolve()
|
| 39 |
+
# Preserve the lexical parent because ``processed_root`` is commonly a
|
| 40 |
+
# symlink named ``processed_datasets`` and manifest paths include that name.
|
| 41 |
+
processed_root = args.processed_root.absolute()
|
| 42 |
+
source_root = processed_root.parent
|
| 43 |
+
output = args.output_dir.resolve()
|
| 44 |
+
resolved_processed_root = processed_root.resolve()
|
| 45 |
+
if output == resolved_processed_root or resolved_processed_root in output.parents:
|
| 46 |
+
raise ValueError("output directory must not be inside the source dataset view")
|
| 47 |
+
|
| 48 |
+
rows = [json.loads(line) for line in manifest.read_text(encoding="utf-8").splitlines() if line]
|
| 49 |
+
references: set[str] = set()
|
| 50 |
+
split_counts: dict[str, int] = {}
|
| 51 |
+
for row in rows:
|
| 52 |
+
split = str(row["split"])
|
| 53 |
+
split_counts[split] = split_counts.get(split, 0) + 1
|
| 54 |
+
for role in ("chosen", "rejected"):
|
| 55 |
+
value = str(row[role]["source_frame_file"])
|
| 56 |
+
relative = Path(value)
|
| 57 |
+
if relative.is_absolute() or ".." in relative.parts:
|
| 58 |
+
raise ValueError(f"non-portable frame path: {value}")
|
| 59 |
+
if relative.parts and relative.parts[0] == ".":
|
| 60 |
+
relative = Path(*relative.parts[1:])
|
| 61 |
+
if not relative.parts or relative.parts[0] != "processed_datasets":
|
| 62 |
+
raise ValueError(f"frame path is outside processed_datasets: {value}")
|
| 63 |
+
references.add(relative.as_posix())
|
| 64 |
+
|
| 65 |
+
sources: list[tuple[str, Path, int]] = []
|
| 66 |
+
for relative in sorted(references):
|
| 67 |
+
relative_path = Path(relative)
|
| 68 |
+
source = resolved_processed_root / Path(*relative_path.parts[1:])
|
| 69 |
+
if not source.is_file():
|
| 70 |
+
raise FileNotFoundError(source)
|
| 71 |
+
sources.append((relative, source, source.stat().st_size))
|
| 72 |
+
|
| 73 |
+
plan = {
|
| 74 |
+
"status": "ready" if args.execute else "dry_run",
|
| 75 |
+
"manifest": str(manifest),
|
| 76 |
+
"manifest_sha256": sha256(manifest),
|
| 77 |
+
"pair_rows": len(rows),
|
| 78 |
+
"split_counts": split_counts,
|
| 79 |
+
"unique_frame_files": len(sources),
|
| 80 |
+
"frame_bytes": sum(size for _, _, size in sources),
|
| 81 |
+
"frame_gib": sum(size for _, _, size in sources) / 2**30,
|
| 82 |
+
"output_dir": str(output),
|
| 83 |
+
}
|
| 84 |
+
if not args.execute:
|
| 85 |
+
print(json.dumps(plan, indent=2, sort_keys=True))
|
| 86 |
+
return
|
| 87 |
+
|
| 88 |
+
manifest_target = output / "manifests" / manifest.name
|
| 89 |
+
manifest_target.parent.mkdir(parents=True, exist_ok=True)
|
| 90 |
+
shutil.copy2(manifest, manifest_target)
|
| 91 |
+
summary_source = manifest.with_name(f"{manifest.stem}_summary.json")
|
| 92 |
+
if summary_source.is_file():
|
| 93 |
+
shutil.copy2(summary_source, manifest_target.parent / summary_source.name)
|
| 94 |
+
|
| 95 |
+
inventory = []
|
| 96 |
+
checksum_lines = []
|
| 97 |
+
for index, (relative, source, size) in enumerate(sources, start=1):
|
| 98 |
+
target = output / relative
|
| 99 |
+
target.parent.mkdir(parents=True, exist_ok=True)
|
| 100 |
+
if not target.is_file() or target.stat().st_size != size:
|
| 101 |
+
shutil.copy2(source, target)
|
| 102 |
+
checksum = sha256(target)
|
| 103 |
+
inventory.append({"path": relative, "bytes": size, "sha256": checksum})
|
| 104 |
+
checksum_lines.append(f"{checksum} {relative}\n")
|
| 105 |
+
if index % 100 == 0:
|
| 106 |
+
print(f"materialized {index}/{len(sources)}", flush=True)
|
| 107 |
+
|
| 108 |
+
inventory_payload = dict(plan)
|
| 109 |
+
inventory_payload["status"] = "complete"
|
| 110 |
+
inventory_payload["files"] = inventory
|
| 111 |
+
(output / "phase21a_data_inventory.json").write_text(
|
| 112 |
+
json.dumps(inventory_payload, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
| 113 |
+
)
|
| 114 |
+
(output / "SHA256SUMS").write_text("".join(checksum_lines), encoding="utf-8")
|
| 115 |
+
print(json.dumps({key: value for key, value in inventory_payload.items() if key != "files"}, indent=2))
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
if __name__ == "__main__":
|
| 119 |
+
main()
|
scripts/run_phase21a_robometer_low_lora_seeds.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Run the three Phase 2.1A v2 Robometer-Low-LoRA seeds serially with resume."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import subprocess
|
| 8 |
+
import sys
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 13 |
+
TRAINER = ROOT / "scripts" / "train_phase21a_robometer_low_lora.py"
|
| 14 |
+
def main() -> None:
|
| 15 |
+
parser = argparse.ArgumentParser()
|
| 16 |
+
parser.add_argument("--output-root", type=Path, required=True)
|
| 17 |
+
parser.add_argument("--snapshot", type=Path, required=True)
|
| 18 |
+
parser.add_argument(
|
| 19 |
+
"--manifest",
|
| 20 |
+
type=Path,
|
| 21 |
+
default=ROOT / "manifests" / "phase2_remediation_pairs_v1.jsonl",
|
| 22 |
+
)
|
| 23 |
+
parser.add_argument(
|
| 24 |
+
"--processed-root",
|
| 25 |
+
type=Path,
|
| 26 |
+
default=ROOT / "artifacts" / "data_views" / "phase1_official" / "processed_datasets",
|
| 27 |
+
)
|
| 28 |
+
parser.add_argument("--seeds", type=int, nargs="+", default=[7, 17, 27])
|
| 29 |
+
parser.add_argument("--optimizer-steps", type=int, default=3000)
|
| 30 |
+
parser.add_argument("--micro-batch-pairs", type=int, default=32)
|
| 31 |
+
parser.add_argument("--gradient-accumulation", type=int, default=2)
|
| 32 |
+
parser.add_argument("--lora-lr", type=float, default=2.0e-6)
|
| 33 |
+
parser.add_argument("--head-lr", type=float, default=1.0e-4)
|
| 34 |
+
parser.add_argument("--checkpoint-every", type=int, default=250)
|
| 35 |
+
parser.add_argument("--eval-every", type=int, default=250)
|
| 36 |
+
parser.add_argument("--eval-micro-batch-pairs", type=int, default=32)
|
| 37 |
+
parser.add_argument("--early-stop-min-steps", type=int, default=1000)
|
| 38 |
+
parser.add_argument("--early-stop-patience-evals", type=int, default=4)
|
| 39 |
+
parser.add_argument("--early-stop-min-delta", type=float, default=5.0e-3)
|
| 40 |
+
args = parser.parse_args()
|
| 41 |
+
for seed in args.seeds:
|
| 42 |
+
output = args.output_root / f"seed_{seed}"
|
| 43 |
+
if (output / "summary.json").is_file():
|
| 44 |
+
continue
|
| 45 |
+
command = [
|
| 46 |
+
sys.executable,
|
| 47 |
+
str(TRAINER),
|
| 48 |
+
"--snapshot", str(args.snapshot),
|
| 49 |
+
"--manifest", str(args.manifest),
|
| 50 |
+
"--processed-root", str(args.processed_root),
|
| 51 |
+
"--seed", str(seed),
|
| 52 |
+
"--optimizer-steps", str(args.optimizer_steps),
|
| 53 |
+
"--micro-batch-pairs", str(args.micro_batch_pairs),
|
| 54 |
+
"--gradient-accumulation", str(args.gradient_accumulation),
|
| 55 |
+
"--lora-lr", str(args.lora_lr),
|
| 56 |
+
"--head-lr", str(args.head_lr),
|
| 57 |
+
"--checkpoint-every", str(args.checkpoint_every),
|
| 58 |
+
"--eval-every", str(args.eval_every),
|
| 59 |
+
"--eval-micro-batch-pairs", str(args.eval_micro_batch_pairs),
|
| 60 |
+
"--early-stop-min-steps", str(args.early_stop_min_steps),
|
| 61 |
+
"--early-stop-patience-evals", str(args.early_stop_patience_evals),
|
| 62 |
+
"--early-stop-min-delta", str(args.early_stop_min_delta),
|
| 63 |
+
"--output-dir", str(output),
|
| 64 |
+
]
|
| 65 |
+
if (output / "latest_training_state.pt").is_file():
|
| 66 |
+
command.append("--resume")
|
| 67 |
+
subprocess.run(command, check=True)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
if __name__ == "__main__":
|
| 71 |
+
main()
|
scripts/smoke_phase21a_robometer_low.py
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""One isolated Robometer-Low smoke run on real Route-A preference pairs."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import math
|
| 9 |
+
import os
|
| 10 |
+
import random
|
| 11 |
+
import sys
|
| 12 |
+
import time
|
| 13 |
+
from functools import lru_cache
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
import torch
|
| 18 |
+
import torch.nn as nn
|
| 19 |
+
import torch.nn.functional as F
|
| 20 |
+
from PIL import Image
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
|
| 24 |
+
if str(EXPERIMENT_ROOT) not in sys.path:
|
| 25 |
+
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
| 26 |
+
|
| 27 |
+
from src.data_contract import letterbox_rgb # noqa: E402
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
SPECIAL_TOKENS = (
|
| 31 |
+
"<|split_token|>",
|
| 32 |
+
"<|reward_token|>",
|
| 33 |
+
"<|pref_token|>",
|
| 34 |
+
"<|sim_token|>",
|
| 35 |
+
"<|prog_token|>",
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def parse_args() -> argparse.Namespace:
|
| 40 |
+
parser = argparse.ArgumentParser()
|
| 41 |
+
parser.add_argument("--snapshot", type=Path, required=True)
|
| 42 |
+
parser.add_argument("--manifest", type=Path, required=True)
|
| 43 |
+
parser.add_argument("--processed-root", type=Path, required=True)
|
| 44 |
+
parser.add_argument("--micro-batch-pairs", type=int, required=True)
|
| 45 |
+
parser.add_argument("--gradient-accumulation", type=int, required=True)
|
| 46 |
+
parser.add_argument("--optimizer-steps", type=int, default=1)
|
| 47 |
+
parser.add_argument("--pair-pool-size", type=int, default=8)
|
| 48 |
+
parser.add_argument("--seed", type=int, default=7)
|
| 49 |
+
parser.add_argument("--head-lr", type=float, default=1.0e-4)
|
| 50 |
+
parser.add_argument("--lora-lr", type=float, default=2.0e-6)
|
| 51 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 52 |
+
return parser.parse_args()
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class RobometerHeads(nn.Module):
|
| 56 |
+
"""Exact Robometer prediction-head topology, with 10-bin progress."""
|
| 57 |
+
|
| 58 |
+
def __init__(self, hidden_size: int, dropout: float = 0.1) -> None:
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.progress_head = self._head(hidden_size, 10, dropout)
|
| 61 |
+
self.success_head = self._head(hidden_size, 1, dropout)
|
| 62 |
+
self.preference_head = self._head(hidden_size, 1, dropout)
|
| 63 |
+
|
| 64 |
+
@staticmethod
|
| 65 |
+
def _head(hidden_size: int, output_size: int, dropout: float) -> nn.Sequential:
|
| 66 |
+
return nn.Sequential(
|
| 67 |
+
nn.Linear(hidden_size, hidden_size // 2),
|
| 68 |
+
nn.LayerNorm(hidden_size // 2),
|
| 69 |
+
nn.GELU(),
|
| 70 |
+
nn.Dropout(dropout),
|
| 71 |
+
nn.Linear(hidden_size // 2, output_size),
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def read_train_rows(path: Path, count: int) -> list[dict]:
|
| 76 |
+
candidates = []
|
| 77 |
+
with path.open("r", encoding="utf-8") as stream:
|
| 78 |
+
for line in stream:
|
| 79 |
+
row = json.loads(line)
|
| 80 |
+
if row["split"] == "train" and row["sample_type"] == "preference":
|
| 81 |
+
candidates.append(row)
|
| 82 |
+
# Capacity must cover the worst text-padding case in the formal manifest.
|
| 83 |
+
rows = sorted(candidates, key=lambda row: len(row["prompt_task"]), reverse=True)[:count]
|
| 84 |
+
if len(rows) < count:
|
| 85 |
+
raise RuntimeError(f"requested {count} smoke rows, found {len(rows)}")
|
| 86 |
+
if not any(row["chosen"]["progress_supervised"] for row in rows):
|
| 87 |
+
raise RuntimeError("smoke pool has no progress-supervised chosen trajectory")
|
| 88 |
+
if not any(any(row["chosen"]["success_label"]) for row in rows):
|
| 89 |
+
raise RuntimeError("smoke pool has no positive success label")
|
| 90 |
+
return rows
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def resolve_frame_file(value: str, processed_root: Path) -> Path:
|
| 94 |
+
path = Path(value)
|
| 95 |
+
if path.is_absolute():
|
| 96 |
+
return path
|
| 97 |
+
resolved = (processed_root.parent / path).resolve()
|
| 98 |
+
if not resolved.is_file():
|
| 99 |
+
raise FileNotFoundError(resolved)
|
| 100 |
+
return resolved
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
@lru_cache(maxsize=64)
|
| 104 |
+
def load_trajectory_cached(
|
| 105 |
+
frame_path_string: str, selected_indices: tuple[int, ...]
|
| 106 |
+
) -> tuple[Image.Image, ...]:
|
| 107 |
+
with np.load(frame_path_string) as archive:
|
| 108 |
+
frames = archive["frames"][np.asarray(selected_indices, dtype=np.int64)]
|
| 109 |
+
return tuple(Image.fromarray(letterbox_rgb(frame, 256)) for frame in frames)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def load_images(record: dict, processed_root: Path) -> tuple[Image.Image, ...]:
|
| 113 |
+
frame_path = resolve_frame_file(record["source_frame_file"], processed_root)
|
| 114 |
+
return load_trajectory_cached(
|
| 115 |
+
str(frame_path), tuple(int(index) for index in record["selected_frame_indices"])
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def build_conversation(
|
| 120 |
+
row: dict, processed_root: Path, chosen_is_a: bool
|
| 121 |
+
) -> tuple[list[dict], dict, float]:
|
| 122 |
+
record_a = row["chosen"] if chosen_is_a else row["rejected"]
|
| 123 |
+
record_b = row["rejected"] if chosen_is_a else row["chosen"]
|
| 124 |
+
prompt = (
|
| 125 |
+
f"Given these two trajectories for the task '{row['prompt_task']}', evaluate "
|
| 126 |
+
"which one makes more progress towards the task. Return A for the first "
|
| 127 |
+
"trajectory and B for the second trajectory. Also predict the task progress "
|
| 128 |
+
"at each frame of the first trajectory, how far along the robot is towards "
|
| 129 |
+
"completing the task, a float between 0 and 1, where 0 is the starting state "
|
| 130 |
+
"and 1 is when the task is completed. If the robot is not performing the "
|
| 131 |
+
"same task, predict 0 progress."
|
| 132 |
+
)
|
| 133 |
+
content: list[dict] = [
|
| 134 |
+
{"type": "text", "text": prompt},
|
| 135 |
+
{"type": "text", "text": "This is Trajectory A. "},
|
| 136 |
+
]
|
| 137 |
+
for image in load_images(record_a, processed_root):
|
| 138 |
+
content.append({"type": "image", "image": image})
|
| 139 |
+
content.append({"type": "text", "text": "<|prog_token|>"})
|
| 140 |
+
content.extend(
|
| 141 |
+
[
|
| 142 |
+
{"type": "text", "text": "<|split_token|>"},
|
| 143 |
+
{"type": "text", "text": "This is Trajectory B. "},
|
| 144 |
+
]
|
| 145 |
+
)
|
| 146 |
+
for image in load_images(record_b, processed_root):
|
| 147 |
+
content.append({"type": "image", "image": image})
|
| 148 |
+
content.append({"type": "text", "text": "<|prog_token|>"})
|
| 149 |
+
content.extend(
|
| 150 |
+
[
|
| 151 |
+
{
|
| 152 |
+
"type": "text",
|
| 153 |
+
"text": "Now predict the preference between the two trajectories.",
|
| 154 |
+
},
|
| 155 |
+
{"type": "text", "text": "<|pref_token|>"},
|
| 156 |
+
]
|
| 157 |
+
)
|
| 158 |
+
return [{"role": "user", "content": content}], record_a, float(chosen_is_a)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def process_batch(processor, conversations: list[list[dict]]) -> dict[str, torch.Tensor]:
|
| 162 |
+
from qwen_vl_utils import process_vision_info
|
| 163 |
+
|
| 164 |
+
texts = [
|
| 165 |
+
processor.apply_chat_template(
|
| 166 |
+
message,
|
| 167 |
+
tokenize=False,
|
| 168 |
+
add_generation_prompt=False,
|
| 169 |
+
add_vision_id=True,
|
| 170 |
+
enable_thinking=False,
|
| 171 |
+
fps=1,
|
| 172 |
+
)
|
| 173 |
+
for message in conversations
|
| 174 |
+
]
|
| 175 |
+
image_inputs, video_inputs, video_kwargs = process_vision_info(
|
| 176 |
+
conversations,
|
| 177 |
+
return_video_kwargs=True,
|
| 178 |
+
return_video_metadata=True,
|
| 179 |
+
image_patch_size=processor.image_processor.patch_size,
|
| 180 |
+
)
|
| 181 |
+
if video_inputs:
|
| 182 |
+
raise RuntimeError("Robometer-Low smoke requires multi-image mode, not video mode")
|
| 183 |
+
kwargs = dict(
|
| 184 |
+
text=texts,
|
| 185 |
+
images=image_inputs,
|
| 186 |
+
padding=True,
|
| 187 |
+
truncation=False,
|
| 188 |
+
return_tensors="pt",
|
| 189 |
+
do_resize=False,
|
| 190 |
+
)
|
| 191 |
+
if video_kwargs:
|
| 192 |
+
kwargs.update(video_kwargs)
|
| 193 |
+
return processor(**kwargs)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def make_targets(records_a: list[dict], device: torch.device) -> dict[str, torch.Tensor]:
|
| 197 |
+
progress = torch.as_tensor(
|
| 198 |
+
np.asarray([record["target_progress"] for record in records_a]),
|
| 199 |
+
dtype=torch.float32,
|
| 200 |
+
device=device,
|
| 201 |
+
)
|
| 202 |
+
progress_mask = torch.as_tensor(
|
| 203 |
+
np.asarray(
|
| 204 |
+
[
|
| 205 |
+
np.asarray(record["frame_valid_mask"], dtype=bool)
|
| 206 |
+
& (np.asarray(record["predict_last_frame_mask"]) > 0)
|
| 207 |
+
& bool(record["progress_supervised"])
|
| 208 |
+
for record in records_a
|
| 209 |
+
]
|
| 210 |
+
),
|
| 211 |
+
dtype=torch.bool,
|
| 212 |
+
device=device,
|
| 213 |
+
)
|
| 214 |
+
success = torch.as_tensor(
|
| 215 |
+
np.asarray([record["success_label"] for record in records_a]),
|
| 216 |
+
dtype=torch.float32,
|
| 217 |
+
device=device,
|
| 218 |
+
)
|
| 219 |
+
success_mask = torch.as_tensor(
|
| 220 |
+
np.asarray([record["frame_valid_mask"] for record in records_a]),
|
| 221 |
+
dtype=torch.bool,
|
| 222 |
+
device=device,
|
| 223 |
+
)
|
| 224 |
+
return {
|
| 225 |
+
"progress": progress,
|
| 226 |
+
"progress_mask": progress_mask,
|
| 227 |
+
"success": success,
|
| 228 |
+
"success_mask": success_mask,
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def finite_grad_audit(named_parameters) -> tuple[bool, float, int]:
|
| 233 |
+
squared_norm = 0.0
|
| 234 |
+
tensor_count = 0
|
| 235 |
+
for _name, parameter in named_parameters:
|
| 236 |
+
if not parameter.requires_grad or parameter.grad is None:
|
| 237 |
+
continue
|
| 238 |
+
tensor_count += 1
|
| 239 |
+
gradient = parameter.grad.detach().float()
|
| 240 |
+
if not torch.isfinite(gradient).all():
|
| 241 |
+
return False, math.nan, tensor_count
|
| 242 |
+
squared_norm += float(torch.sum(gradient * gradient))
|
| 243 |
+
return True, math.sqrt(squared_norm), tensor_count
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def group_grad_norm(named_parameters, marker: str) -> float:
|
| 247 |
+
squared_norm = 0.0
|
| 248 |
+
found = 0
|
| 249 |
+
for name, parameter in named_parameters:
|
| 250 |
+
if marker not in name or parameter.grad is None:
|
| 251 |
+
continue
|
| 252 |
+
squared_norm += float(torch.sum(parameter.grad.detach().float() ** 2))
|
| 253 |
+
found += 1
|
| 254 |
+
return math.sqrt(squared_norm) if found else 0.0
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def main() -> None:
|
| 258 |
+
args = parse_args()
|
| 259 |
+
if args.micro_batch_pairs <= 0 or args.gradient_accumulation <= 0:
|
| 260 |
+
raise ValueError("batch and accumulation must be positive")
|
| 261 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 262 |
+
random.seed(args.seed)
|
| 263 |
+
np.random.seed(args.seed)
|
| 264 |
+
torch.manual_seed(args.seed)
|
| 265 |
+
torch.cuda.manual_seed_all(args.seed)
|
| 266 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 267 |
+
|
| 268 |
+
from peft import LoraConfig, get_peft_model
|
| 269 |
+
from transformers import AutoModelForImageTextToText, AutoProcessor
|
| 270 |
+
|
| 271 |
+
started = time.perf_counter()
|
| 272 |
+
processor = AutoProcessor.from_pretrained(
|
| 273 |
+
args.snapshot, local_files_only=True, padding_side="right"
|
| 274 |
+
)
|
| 275 |
+
original_vocab = len(processor.tokenizer)
|
| 276 |
+
for token in SPECIAL_TOKENS:
|
| 277 |
+
if token not in processor.tokenizer.get_vocab():
|
| 278 |
+
processor.tokenizer.add_special_tokens(
|
| 279 |
+
{"additional_special_tokens": [token]}
|
| 280 |
+
)
|
| 281 |
+
special_token_ids = {
|
| 282 |
+
token: int(processor.tokenizer.convert_tokens_to_ids(token))
|
| 283 |
+
for token in SPECIAL_TOKENS
|
| 284 |
+
}
|
| 285 |
+
model = AutoModelForImageTextToText.from_pretrained(
|
| 286 |
+
args.snapshot,
|
| 287 |
+
local_files_only=True,
|
| 288 |
+
dtype=torch.bfloat16,
|
| 289 |
+
attn_implementation="flash_attention_2",
|
| 290 |
+
device_map={"": 0},
|
| 291 |
+
)
|
| 292 |
+
model.resize_token_embeddings(len(processor.tokenizer))
|
| 293 |
+
for parameter in model.parameters():
|
| 294 |
+
parameter.requires_grad_(False)
|
| 295 |
+
|
| 296 |
+
language_model = model.model.language_model
|
| 297 |
+
peft_config = LoraConfig(
|
| 298 |
+
r=8,
|
| 299 |
+
lora_alpha=16,
|
| 300 |
+
lora_dropout=0.05,
|
| 301 |
+
bias="none",
|
| 302 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
|
| 303 |
+
trainable_token_indices=list(special_token_ids.values()),
|
| 304 |
+
)
|
| 305 |
+
model.model.language_model = get_peft_model(language_model, peft_config)
|
| 306 |
+
model.config.use_cache = False
|
| 307 |
+
model.gradient_checkpointing_enable(
|
| 308 |
+
gradient_checkpointing_kwargs={"use_reentrant": False}
|
| 309 |
+
)
|
| 310 |
+
model.enable_input_require_grads()
|
| 311 |
+
|
| 312 |
+
hidden_size = int(model.config.text_config.hidden_size)
|
| 313 |
+
heads = RobometerHeads(hidden_size).to(device="cuda", dtype=torch.bfloat16)
|
| 314 |
+
backbone_trainable = [
|
| 315 |
+
parameter for parameter in model.parameters() if parameter.requires_grad
|
| 316 |
+
]
|
| 317 |
+
head_parameters = list(heads.parameters())
|
| 318 |
+
optimizer = torch.optim.AdamW(
|
| 319 |
+
[
|
| 320 |
+
{"params": backbone_trainable, "lr": args.lora_lr},
|
| 321 |
+
{"params": head_parameters, "lr": args.head_lr},
|
| 322 |
+
],
|
| 323 |
+
weight_decay=0.01,
|
| 324 |
+
)
|
| 325 |
+
rows = read_train_rows(args.manifest, args.pair_pool_size)
|
| 326 |
+
total_parameters = sum(parameter.numel() for parameter in model.parameters())
|
| 327 |
+
trainable_backbone_parameters = sum(p.numel() for p in backbone_trainable)
|
| 328 |
+
trainable_head_parameters = sum(p.numel() for p in head_parameters)
|
| 329 |
+
|
| 330 |
+
model.train()
|
| 331 |
+
heads.train()
|
| 332 |
+
torch.cuda.empty_cache()
|
| 333 |
+
torch.cuda.reset_peak_memory_stats()
|
| 334 |
+
load_seconds = time.perf_counter() - started
|
| 335 |
+
run_started = time.perf_counter()
|
| 336 |
+
component_history = []
|
| 337 |
+
sequence_lengths = []
|
| 338 |
+
optimizer.zero_grad(set_to_none=True)
|
| 339 |
+
named_trainable = list(model.named_parameters()) + [
|
| 340 |
+
(f"heads.{name}", parameter) for name, parameter in heads.named_parameters()
|
| 341 |
+
]
|
| 342 |
+
|
| 343 |
+
for optimizer_step in range(args.optimizer_steps):
|
| 344 |
+
for accumulation_index in range(args.gradient_accumulation):
|
| 345 |
+
global_microstep = (
|
| 346 |
+
optimizer_step * args.gradient_accumulation + accumulation_index
|
| 347 |
+
)
|
| 348 |
+
batch_rows = [
|
| 349 |
+
rows[(global_microstep * args.micro_batch_pairs + offset) % len(rows)]
|
| 350 |
+
for offset in range(args.micro_batch_pairs)
|
| 351 |
+
]
|
| 352 |
+
conversations = []
|
| 353 |
+
records_a = []
|
| 354 |
+
preference_labels = []
|
| 355 |
+
for offset, row in enumerate(batch_rows):
|
| 356 |
+
chosen_is_a = ((global_microstep * args.micro_batch_pairs + offset) % 2) == 0
|
| 357 |
+
conversation, record_a, label = build_conversation(
|
| 358 |
+
row, args.processed_root, chosen_is_a
|
| 359 |
+
)
|
| 360 |
+
conversations.append(conversation)
|
| 361 |
+
records_a.append(record_a)
|
| 362 |
+
preference_labels.append(label)
|
| 363 |
+
inputs = process_batch(processor, conversations)
|
| 364 |
+
sequence_lengths.extend(inputs["attention_mask"].sum(1).tolist())
|
| 365 |
+
inputs = {key: value.to("cuda") for key, value in inputs.items()}
|
| 366 |
+
targets = make_targets(records_a, torch.device("cuda"))
|
| 367 |
+
preference_target = torch.tensor(
|
| 368 |
+
preference_labels, dtype=torch.float32, device="cuda"
|
| 369 |
+
)
|
| 370 |
+
output = model.model(**inputs, output_hidden_states=False, use_cache=False)
|
| 371 |
+
hidden = output.last_hidden_state
|
| 372 |
+
prog_mask = inputs["input_ids"].eq(special_token_ids["<|prog_token|>"])
|
| 373 |
+
pref_mask = inputs["input_ids"].eq(special_token_ids["<|pref_token|>"])
|
| 374 |
+
prog_counts = prog_mask.sum(1)
|
| 375 |
+
pref_counts = pref_mask.sum(1)
|
| 376 |
+
if not torch.equal(
|
| 377 |
+
prog_counts, torch.full_like(prog_counts, 16)
|
| 378 |
+
) or not torch.equal(pref_counts, torch.ones_like(pref_counts)):
|
| 379 |
+
raise RuntimeError(
|
| 380 |
+
f"token extraction mismatch: prog={prog_counts.tolist()} "
|
| 381 |
+
f"pref={pref_counts.tolist()}"
|
| 382 |
+
)
|
| 383 |
+
prog_hidden = hidden[prog_mask].reshape(args.micro_batch_pairs, 16, hidden_size)
|
| 384 |
+
pref_hidden = hidden[pref_mask].reshape(args.micro_batch_pairs, hidden_size)
|
| 385 |
+
progress_logits = heads.progress_head(prog_hidden[:, :8])
|
| 386 |
+
success_logits = heads.success_head(prog_hidden[:, :8]).squeeze(-1)
|
| 387 |
+
preference_logits = heads.preference_head(pref_hidden).squeeze(-1)
|
| 388 |
+
|
| 389 |
+
if targets["progress_mask"].any():
|
| 390 |
+
per_frame_progress = -(
|
| 391 |
+
targets["progress"] * F.log_softmax(progress_logits.float(), dim=-1)
|
| 392 |
+
).sum(-1)
|
| 393 |
+
progress_loss = per_frame_progress[targets["progress_mask"]].mean()
|
| 394 |
+
else:
|
| 395 |
+
progress_loss = progress_logits.float().sum() * 0.0
|
| 396 |
+
valid_success = targets["success"][targets["success_mask"]]
|
| 397 |
+
valid_success_logits = success_logits.float()[targets["success_mask"]]
|
| 398 |
+
positives = valid_success.sum()
|
| 399 |
+
negatives = valid_success.numel() - positives
|
| 400 |
+
pos_weight = (negatives / positives.clamp_min(1.0)).clamp_min(1.0)
|
| 401 |
+
success_loss = F.binary_cross_entropy_with_logits(
|
| 402 |
+
valid_success_logits, valid_success, pos_weight=pos_weight
|
| 403 |
+
)
|
| 404 |
+
preference_loss = F.binary_cross_entropy_with_logits(
|
| 405 |
+
preference_logits.float(), preference_target
|
| 406 |
+
)
|
| 407 |
+
total_loss = progress_loss + success_loss + preference_loss
|
| 408 |
+
if not torch.isfinite(total_loss):
|
| 409 |
+
raise FloatingPointError(f"non-finite loss: {float(total_loss)}")
|
| 410 |
+
(total_loss / args.gradient_accumulation).backward()
|
| 411 |
+
component_history.append(
|
| 412 |
+
{
|
| 413 |
+
"total": float(total_loss.detach()),
|
| 414 |
+
"preference": float(preference_loss.detach()),
|
| 415 |
+
"progress": float(progress_loss.detach()),
|
| 416 |
+
"success": float(success_loss.detach()),
|
| 417 |
+
"progress_supervised_frames": int(targets["progress_mask"].sum()),
|
| 418 |
+
"success_supervised_frames": int(targets["success_mask"].sum()),
|
| 419 |
+
}
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
finite_gradients, preclip_grad_norm, gradient_tensor_count = finite_grad_audit(
|
| 423 |
+
named_trainable
|
| 424 |
+
)
|
| 425 |
+
if not finite_gradients:
|
| 426 |
+
raise FloatingPointError("non-finite trainable gradient")
|
| 427 |
+
grad_groups = {
|
| 428 |
+
"lora": group_grad_norm(named_trainable, "lora_"),
|
| 429 |
+
"token_rows": group_grad_norm(named_trainable, "trainable_tokens"),
|
| 430 |
+
"progress_head": group_grad_norm(named_trainable, "heads.progress_head"),
|
| 431 |
+
"success_head": group_grad_norm(named_trainable, "heads.success_head"),
|
| 432 |
+
"preference_head": group_grad_norm(named_trainable, "heads.preference_head"),
|
| 433 |
+
}
|
| 434 |
+
missing_grad_groups = [name for name, value in grad_groups.items() if not value > 0]
|
| 435 |
+
if missing_grad_groups:
|
| 436 |
+
raise RuntimeError(f"missing gradient groups: {missing_grad_groups}")
|
| 437 |
+
torch.nn.utils.clip_grad_norm_(backbone_trainable + head_parameters, 1.0)
|
| 438 |
+
optimizer.step()
|
| 439 |
+
optimizer.zero_grad(set_to_none=True)
|
| 440 |
+
|
| 441 |
+
torch.cuda.synchronize()
|
| 442 |
+
run_seconds = time.perf_counter() - run_started
|
| 443 |
+
device = torch.cuda.get_device_properties(0)
|
| 444 |
+
peak_allocated = torch.cuda.max_memory_allocated()
|
| 445 |
+
peak_reserved = torch.cuda.max_memory_reserved()
|
| 446 |
+
effective_batch = args.micro_batch_pairs * args.gradient_accumulation
|
| 447 |
+
payload = {
|
| 448 |
+
"status": "pass",
|
| 449 |
+
"protocol_id": "phase21a_robometer_low_smoke_v1",
|
| 450 |
+
"scope": {
|
| 451 |
+
"smoke_only": True,
|
| 452 |
+
"formal_training_started": False,
|
| 453 |
+
"cosmos3_edge_modified": False,
|
| 454 |
+
},
|
| 455 |
+
"micro_batch_pairs": args.micro_batch_pairs,
|
| 456 |
+
"gradient_accumulation": args.gradient_accumulation,
|
| 457 |
+
"effective_batch_pairs": effective_batch,
|
| 458 |
+
"optimizer_steps": args.optimizer_steps,
|
| 459 |
+
"microsteps": args.optimizer_steps * args.gradient_accumulation,
|
| 460 |
+
"real_pair_pool_size": len(rows),
|
| 461 |
+
"pair_pool_selection": "longest_prompt_characters_in_train_split",
|
| 462 |
+
"pair_pool_prompt_character_range": [
|
| 463 |
+
min(len(row["prompt_task"]) for row in rows),
|
| 464 |
+
max(len(row["prompt_task"]) for row in rows),
|
| 465 |
+
],
|
| 466 |
+
"frames_per_pair": 16,
|
| 467 |
+
"precision": "BF16",
|
| 468 |
+
"gradient_checkpointing": True,
|
| 469 |
+
"architecture": {
|
| 470 |
+
"joint_a_b_forward": True,
|
| 471 |
+
"direct_preference_token_head": True,
|
| 472 |
+
"per_frame_progress_tokens": True,
|
| 473 |
+
"progress_bins": 10,
|
| 474 |
+
"lora_rank": 8,
|
| 475 |
+
"lora_alpha": 16,
|
| 476 |
+
"lora_targets": ["q_proj", "k_proj", "v_proj", "o_proj"],
|
| 477 |
+
"special_tokens": special_token_ids,
|
| 478 |
+
"original_vocab_size": original_vocab,
|
| 479 |
+
"resized_vocab_size": len(processor.tokenizer),
|
| 480 |
+
},
|
| 481 |
+
"parameters": {
|
| 482 |
+
"base_total": total_parameters,
|
| 483 |
+
"backbone_trainable_lora_and_token_rows": trainable_backbone_parameters,
|
| 484 |
+
"heads_trainable": trainable_head_parameters,
|
| 485 |
+
"all_trainable": trainable_backbone_parameters + trainable_head_parameters,
|
| 486 |
+
},
|
| 487 |
+
"loss_last": component_history[-1],
|
| 488 |
+
"loss_all_finite": all(
|
| 489 |
+
math.isfinite(value)
|
| 490 |
+
for item in component_history
|
| 491 |
+
for key, value in item.items()
|
| 492 |
+
if key in {"total", "preference", "progress", "success"}
|
| 493 |
+
),
|
| 494 |
+
"gradient_audit": {
|
| 495 |
+
"finite": finite_gradients,
|
| 496 |
+
"preclip_global_norm": preclip_grad_norm,
|
| 497 |
+
"tensor_count": gradient_tensor_count,
|
| 498 |
+
"group_norms": grad_groups,
|
| 499 |
+
},
|
| 500 |
+
"sequence_tokens": {
|
| 501 |
+
"min": int(min(sequence_lengths)),
|
| 502 |
+
"max": int(max(sequence_lengths)),
|
| 503 |
+
"mean": float(np.mean(sequence_lengths)),
|
| 504 |
+
},
|
| 505 |
+
"timing": {
|
| 506 |
+
"model_load_seconds": load_seconds,
|
| 507 |
+
"run_seconds": run_seconds,
|
| 508 |
+
"seconds_per_microstep": run_seconds
|
| 509 |
+
/ (args.optimizer_steps * args.gradient_accumulation),
|
| 510 |
+
"pairs_per_second": effective_batch * args.optimizer_steps / run_seconds,
|
| 511 |
+
},
|
| 512 |
+
"gpu": {
|
| 513 |
+
"name": device.name,
|
| 514 |
+
"total_gib": device.total_memory / 2**30,
|
| 515 |
+
"peak_allocated_gib": peak_allocated / 2**30,
|
| 516 |
+
"peak_reserved_gib": peak_reserved / 2**30,
|
| 517 |
+
"peak_reserved_fraction": peak_reserved / device.total_memory,
|
| 518 |
+
},
|
| 519 |
+
}
|
| 520 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 521 |
+
args.output.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
| 522 |
+
print(json.dumps(payload, indent=2), flush=True)
|
| 523 |
+
|
| 524 |
+
|
| 525 |
+
if __name__ == "__main__":
|
| 526 |
+
main()
|
scripts/smoke_phase21a_robometer_low_full.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Single-GPU full-language-decoder Robometer-Low smoke on real pairs."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import json
|
| 8 |
+
import math
|
| 9 |
+
import os
|
| 10 |
+
import random
|
| 11 |
+
import sys
|
| 12 |
+
import time
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn.functional as F
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
|
| 21 |
+
if str(EXPERIMENT_ROOT) not in sys.path:
|
| 22 |
+
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
| 23 |
+
|
| 24 |
+
from scripts.smoke_phase21a_robometer_low import ( # noqa: E402
|
| 25 |
+
SPECIAL_TOKENS,
|
| 26 |
+
RobometerHeads,
|
| 27 |
+
build_conversation,
|
| 28 |
+
finite_grad_audit,
|
| 29 |
+
group_grad_norm,
|
| 30 |
+
make_targets,
|
| 31 |
+
process_batch,
|
| 32 |
+
read_train_rows,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def parse_args() -> argparse.Namespace:
|
| 37 |
+
parser = argparse.ArgumentParser()
|
| 38 |
+
parser.add_argument("--snapshot", type=Path, required=True)
|
| 39 |
+
parser.add_argument("--manifest", type=Path, required=True)
|
| 40 |
+
parser.add_argument("--processed-root", type=Path, required=True)
|
| 41 |
+
parser.add_argument("--micro-batch-pairs", type=int, required=True)
|
| 42 |
+
parser.add_argument("--gradient-accumulation", type=int, required=True)
|
| 43 |
+
parser.add_argument("--optimizer-steps", type=int, default=1)
|
| 44 |
+
parser.add_argument("--pair-pool-size", type=int, default=8)
|
| 45 |
+
parser.add_argument("--optimizer", choices=("adamw_torch", "paged_adamw_8bit"), required=True)
|
| 46 |
+
parser.add_argument("--learning-rate", type=float, default=2.0e-5)
|
| 47 |
+
parser.add_argument("--seed", type=int, default=7)
|
| 48 |
+
parser.add_argument("--require-all-gradient-groups", action="store_true")
|
| 49 |
+
parser.add_argument("--output", type=Path, required=True)
|
| 50 |
+
return parser.parse_args()
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def tensor_bytes(value) -> tuple[int, int]:
|
| 54 |
+
"""Return CUDA and CPU tensor bytes recursively for optimizer state."""
|
| 55 |
+
cuda_bytes = 0
|
| 56 |
+
cpu_bytes = 0
|
| 57 |
+
if torch.is_tensor(value):
|
| 58 |
+
size = value.numel() * value.element_size()
|
| 59 |
+
if value.device.type == "cuda":
|
| 60 |
+
cuda_bytes += size
|
| 61 |
+
elif value.device.type == "cpu":
|
| 62 |
+
cpu_bytes += size
|
| 63 |
+
elif isinstance(value, dict):
|
| 64 |
+
for item in value.values():
|
| 65 |
+
cuda, cpu = tensor_bytes(item)
|
| 66 |
+
cuda_bytes += cuda
|
| 67 |
+
cpu_bytes += cpu
|
| 68 |
+
elif isinstance(value, (list, tuple)):
|
| 69 |
+
for item in value:
|
| 70 |
+
cuda, cpu = tensor_bytes(item)
|
| 71 |
+
cuda_bytes += cuda
|
| 72 |
+
cpu_bytes += cpu
|
| 73 |
+
return cuda_bytes, cpu_bytes
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def make_optimizer(name: str, parameters: list[torch.nn.Parameter], learning_rate: float):
|
| 77 |
+
if name == "adamw_torch":
|
| 78 |
+
return torch.optim.AdamW(
|
| 79 |
+
parameters,
|
| 80 |
+
lr=learning_rate,
|
| 81 |
+
weight_decay=0.01,
|
| 82 |
+
foreach=False,
|
| 83 |
+
)
|
| 84 |
+
import bitsandbytes as bnb
|
| 85 |
+
|
| 86 |
+
return bnb.optim.PagedAdamW8bit(
|
| 87 |
+
parameters,
|
| 88 |
+
lr=learning_rate,
|
| 89 |
+
weight_decay=0.01,
|
| 90 |
+
min_8bit_size=4096,
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def main() -> None:
|
| 95 |
+
args = parse_args()
|
| 96 |
+
if args.micro_batch_pairs <= 0 or args.gradient_accumulation <= 0:
|
| 97 |
+
raise ValueError("batch and accumulation must be positive")
|
| 98 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 99 |
+
random.seed(args.seed)
|
| 100 |
+
np.random.seed(args.seed)
|
| 101 |
+
torch.manual_seed(args.seed)
|
| 102 |
+
torch.cuda.manual_seed_all(args.seed)
|
| 103 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 104 |
+
|
| 105 |
+
from transformers import AutoModelForImageTextToText, AutoProcessor
|
| 106 |
+
|
| 107 |
+
started = time.perf_counter()
|
| 108 |
+
processor = AutoProcessor.from_pretrained(
|
| 109 |
+
args.snapshot, local_files_only=True, padding_side="right"
|
| 110 |
+
)
|
| 111 |
+
original_vocab = len(processor.tokenizer)
|
| 112 |
+
for token in SPECIAL_TOKENS:
|
| 113 |
+
if token not in processor.tokenizer.get_vocab():
|
| 114 |
+
processor.tokenizer.add_special_tokens({"additional_special_tokens": [token]})
|
| 115 |
+
special_token_ids = {
|
| 116 |
+
token: int(processor.tokenizer.convert_tokens_to_ids(token))
|
| 117 |
+
for token in SPECIAL_TOKENS
|
| 118 |
+
}
|
| 119 |
+
model = AutoModelForImageTextToText.from_pretrained(
|
| 120 |
+
args.snapshot,
|
| 121 |
+
local_files_only=True,
|
| 122 |
+
dtype=torch.bfloat16,
|
| 123 |
+
attn_implementation="flash_attention_2",
|
| 124 |
+
device_map={"": 0},
|
| 125 |
+
)
|
| 126 |
+
model.resize_token_embeddings(len(processor.tokenizer))
|
| 127 |
+
for parameter in model.parameters():
|
| 128 |
+
parameter.requires_grad_(False)
|
| 129 |
+
for parameter in model.model.language_model.parameters():
|
| 130 |
+
parameter.requires_grad_(True)
|
| 131 |
+
model.config.use_cache = False
|
| 132 |
+
model.gradient_checkpointing_enable(
|
| 133 |
+
gradient_checkpointing_kwargs={"use_reentrant": False}
|
| 134 |
+
)
|
| 135 |
+
model.enable_input_require_grads()
|
| 136 |
+
|
| 137 |
+
hidden_size = int(model.config.text_config.hidden_size)
|
| 138 |
+
heads = RobometerHeads(hidden_size).to(device="cuda", dtype=torch.bfloat16)
|
| 139 |
+
language_parameters = [
|
| 140 |
+
parameter for parameter in model.parameters() if parameter.requires_grad
|
| 141 |
+
]
|
| 142 |
+
head_parameters = list(heads.parameters())
|
| 143 |
+
trainable_parameters = language_parameters + head_parameters
|
| 144 |
+
vision_trainable = sum(
|
| 145 |
+
parameter.numel()
|
| 146 |
+
for name, parameter in model.named_parameters()
|
| 147 |
+
if parameter.requires_grad and ".visual." in name
|
| 148 |
+
)
|
| 149 |
+
if vision_trainable:
|
| 150 |
+
raise RuntimeError(f"vision boundary violation: {vision_trainable} trainable parameters")
|
| 151 |
+
optimizer = make_optimizer(args.optimizer, trainable_parameters, args.learning_rate)
|
| 152 |
+
rows = read_train_rows(args.manifest, args.pair_pool_size)
|
| 153 |
+
|
| 154 |
+
model.train()
|
| 155 |
+
heads.train()
|
| 156 |
+
torch.cuda.empty_cache()
|
| 157 |
+
torch.cuda.reset_peak_memory_stats()
|
| 158 |
+
load_seconds = time.perf_counter() - started
|
| 159 |
+
run_started = time.perf_counter()
|
| 160 |
+
component_history = []
|
| 161 |
+
sequence_lengths = []
|
| 162 |
+
optimizer.zero_grad(set_to_none=True)
|
| 163 |
+
named_trainable = list(model.named_parameters()) + [
|
| 164 |
+
(f"heads.{name}", parameter) for name, parameter in heads.named_parameters()
|
| 165 |
+
]
|
| 166 |
+
last_gradient_audit = None
|
| 167 |
+
|
| 168 |
+
for optimizer_step in range(args.optimizer_steps):
|
| 169 |
+
for accumulation_index in range(args.gradient_accumulation):
|
| 170 |
+
global_microstep = optimizer_step * args.gradient_accumulation + accumulation_index
|
| 171 |
+
batch_rows = [
|
| 172 |
+
rows[(global_microstep * args.micro_batch_pairs + offset) % len(rows)]
|
| 173 |
+
for offset in range(args.micro_batch_pairs)
|
| 174 |
+
]
|
| 175 |
+
conversations = []
|
| 176 |
+
records_a = []
|
| 177 |
+
preference_labels = []
|
| 178 |
+
for offset, row in enumerate(batch_rows):
|
| 179 |
+
chosen_is_a = ((global_microstep * args.micro_batch_pairs + offset) % 2) == 0
|
| 180 |
+
conversation, record_a, label = build_conversation(
|
| 181 |
+
row, args.processed_root, chosen_is_a
|
| 182 |
+
)
|
| 183 |
+
conversations.append(conversation)
|
| 184 |
+
records_a.append(record_a)
|
| 185 |
+
preference_labels.append(label)
|
| 186 |
+
inputs = process_batch(processor, conversations)
|
| 187 |
+
sequence_lengths.extend(inputs["attention_mask"].sum(1).tolist())
|
| 188 |
+
inputs = {key: value.to("cuda") for key, value in inputs.items()}
|
| 189 |
+
targets = make_targets(records_a, torch.device("cuda"))
|
| 190 |
+
preference_target = torch.tensor(
|
| 191 |
+
preference_labels, dtype=torch.float32, device="cuda"
|
| 192 |
+
)
|
| 193 |
+
output = model.model(**inputs, output_hidden_states=False, use_cache=False)
|
| 194 |
+
hidden = output.last_hidden_state
|
| 195 |
+
prog_mask = inputs["input_ids"].eq(special_token_ids["<|prog_token|>"])
|
| 196 |
+
pref_mask = inputs["input_ids"].eq(special_token_ids["<|pref_token|>"])
|
| 197 |
+
prog_counts = prog_mask.sum(1)
|
| 198 |
+
pref_counts = pref_mask.sum(1)
|
| 199 |
+
if not torch.equal(prog_counts, torch.full_like(prog_counts, 16)):
|
| 200 |
+
raise RuntimeError(f"progress token counts: {prog_counts.tolist()}")
|
| 201 |
+
if not torch.equal(pref_counts, torch.ones_like(pref_counts)):
|
| 202 |
+
raise RuntimeError(f"preference token counts: {pref_counts.tolist()}")
|
| 203 |
+
prog_hidden = hidden[prog_mask].reshape(args.micro_batch_pairs, 16, hidden_size)
|
| 204 |
+
pref_hidden = hidden[pref_mask].reshape(args.micro_batch_pairs, hidden_size)
|
| 205 |
+
progress_logits = heads.progress_head(prog_hidden[:, :8])
|
| 206 |
+
success_logits = heads.success_head(prog_hidden[:, :8]).squeeze(-1)
|
| 207 |
+
preference_logits = heads.preference_head(pref_hidden).squeeze(-1)
|
| 208 |
+
|
| 209 |
+
if targets["progress_mask"].any():
|
| 210 |
+
per_frame_progress = -(
|
| 211 |
+
targets["progress"] * F.log_softmax(progress_logits.float(), dim=-1)
|
| 212 |
+
).sum(-1)
|
| 213 |
+
progress_loss = per_frame_progress[targets["progress_mask"]].mean()
|
| 214 |
+
else:
|
| 215 |
+
progress_loss = progress_logits.float().sum() * 0.0
|
| 216 |
+
valid_success = targets["success"][targets["success_mask"]]
|
| 217 |
+
valid_success_logits = success_logits.float()[targets["success_mask"]]
|
| 218 |
+
positives = valid_success.sum()
|
| 219 |
+
negatives = valid_success.numel() - positives
|
| 220 |
+
pos_weight = (negatives / positives.clamp_min(1.0)).clamp_min(1.0)
|
| 221 |
+
success_loss = F.binary_cross_entropy_with_logits(
|
| 222 |
+
valid_success_logits, valid_success, pos_weight=pos_weight
|
| 223 |
+
)
|
| 224 |
+
preference_loss = F.binary_cross_entropy_with_logits(
|
| 225 |
+
preference_logits.float(), preference_target
|
| 226 |
+
)
|
| 227 |
+
total_loss = progress_loss + success_loss + preference_loss
|
| 228 |
+
if not torch.isfinite(total_loss):
|
| 229 |
+
raise FloatingPointError(f"non-finite loss: {float(total_loss)}")
|
| 230 |
+
(total_loss / args.gradient_accumulation).backward()
|
| 231 |
+
component_history.append(
|
| 232 |
+
{
|
| 233 |
+
"total": float(total_loss.detach()),
|
| 234 |
+
"preference": float(preference_loss.detach()),
|
| 235 |
+
"progress": float(progress_loss.detach()),
|
| 236 |
+
"success": float(success_loss.detach()),
|
| 237 |
+
"progress_supervised_frames": int(targets["progress_mask"].sum()),
|
| 238 |
+
"success_supervised_frames": int(targets["success_mask"].sum()),
|
| 239 |
+
}
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
finite_gradients, preclip_grad_norm, gradient_tensor_count = finite_grad_audit(
|
| 243 |
+
named_trainable
|
| 244 |
+
)
|
| 245 |
+
if not finite_gradients:
|
| 246 |
+
raise FloatingPointError("non-finite trainable gradient")
|
| 247 |
+
embedding_gradient = model.get_input_embeddings().weight.grad
|
| 248 |
+
if embedding_gradient is None:
|
| 249 |
+
special_token_grad_norm = 0.0
|
| 250 |
+
else:
|
| 251 |
+
special_token_grad_norm = float(
|
| 252 |
+
torch.linalg.vector_norm(
|
| 253 |
+
embedding_gradient[list(special_token_ids.values())].float()
|
| 254 |
+
)
|
| 255 |
+
)
|
| 256 |
+
grad_groups = {
|
| 257 |
+
"language_decoder": group_grad_norm(named_trainable, "language_model"),
|
| 258 |
+
"special_token_rows": special_token_grad_norm,
|
| 259 |
+
"progress_head": group_grad_norm(named_trainable, "heads.progress_head"),
|
| 260 |
+
"success_head": group_grad_norm(named_trainable, "heads.success_head"),
|
| 261 |
+
"preference_head": group_grad_norm(named_trainable, "heads.preference_head"),
|
| 262 |
+
}
|
| 263 |
+
missing_grad_groups = [name for name, value in grad_groups.items() if not value > 0]
|
| 264 |
+
if args.require_all_gradient_groups and missing_grad_groups:
|
| 265 |
+
raise RuntimeError(f"missing gradient groups: {missing_grad_groups}")
|
| 266 |
+
last_gradient_audit = {
|
| 267 |
+
"finite": finite_gradients,
|
| 268 |
+
"preclip_global_norm": preclip_grad_norm,
|
| 269 |
+
"tensor_count": gradient_tensor_count,
|
| 270 |
+
"group_norms": grad_groups,
|
| 271 |
+
"missing_groups": missing_grad_groups,
|
| 272 |
+
}
|
| 273 |
+
torch.nn.utils.clip_grad_norm_(trainable_parameters, 10.0)
|
| 274 |
+
optimizer.step()
|
| 275 |
+
optimizer.zero_grad(set_to_none=True)
|
| 276 |
+
|
| 277 |
+
torch.cuda.synchronize()
|
| 278 |
+
run_seconds = time.perf_counter() - run_started
|
| 279 |
+
device = torch.cuda.get_device_properties(0)
|
| 280 |
+
peak_allocated = torch.cuda.max_memory_allocated()
|
| 281 |
+
peak_reserved = torch.cuda.max_memory_reserved()
|
| 282 |
+
optimizer_cuda_bytes, optimizer_cpu_bytes = tensor_bytes(optimizer.state)
|
| 283 |
+
effective_batch = args.micro_batch_pairs * args.gradient_accumulation
|
| 284 |
+
language_trainable_count = sum(parameter.numel() for parameter in language_parameters)
|
| 285 |
+
head_trainable_count = sum(parameter.numel() for parameter in head_parameters)
|
| 286 |
+
payload = {
|
| 287 |
+
"status": "pass",
|
| 288 |
+
"protocol_id": "phase21a_robometer_low_full_smoke_v1",
|
| 289 |
+
"scope": {
|
| 290 |
+
"smoke_only": True,
|
| 291 |
+
"formal_training_started": False,
|
| 292 |
+
"cosmos3_edge_modified": False,
|
| 293 |
+
},
|
| 294 |
+
"micro_batch_pairs": args.micro_batch_pairs,
|
| 295 |
+
"gradient_accumulation": args.gradient_accumulation,
|
| 296 |
+
"effective_batch_pairs": effective_batch,
|
| 297 |
+
"optimizer_steps": args.optimizer_steps,
|
| 298 |
+
"microsteps": args.optimizer_steps * args.gradient_accumulation,
|
| 299 |
+
"optimizer": args.optimizer,
|
| 300 |
+
"optimizer_official_default": args.optimizer == "adamw_torch",
|
| 301 |
+
"real_pair_pool_size": len(rows),
|
| 302 |
+
"pair_pool_selection": "longest_prompt_characters_in_train_split",
|
| 303 |
+
"pair_pool_prompt_character_range": [
|
| 304 |
+
min(len(row["prompt_task"]) for row in rows),
|
| 305 |
+
max(len(row["prompt_task"]) for row in rows),
|
| 306 |
+
],
|
| 307 |
+
"frames_per_pair": 16,
|
| 308 |
+
"precision": "BF16",
|
| 309 |
+
"gradient_checkpointing": True,
|
| 310 |
+
"architecture": {
|
| 311 |
+
"joint_a_b_forward": True,
|
| 312 |
+
"vision_frozen": True,
|
| 313 |
+
"language_decoder_full_tune": True,
|
| 314 |
+
"peft_or_lora": False,
|
| 315 |
+
"direct_preference_token_head": True,
|
| 316 |
+
"per_frame_progress_tokens": True,
|
| 317 |
+
"progress_bins": 10,
|
| 318 |
+
"special_tokens": special_token_ids,
|
| 319 |
+
"original_vocab_size": original_vocab,
|
| 320 |
+
"resized_vocab_size": len(processor.tokenizer),
|
| 321 |
+
},
|
| 322 |
+
"parameters": {
|
| 323 |
+
"model_total": sum(parameter.numel() for parameter in model.parameters()),
|
| 324 |
+
"language_decoder_trainable": language_trainable_count,
|
| 325 |
+
"heads_trainable": head_trainable_count,
|
| 326 |
+
"all_trainable": language_trainable_count + head_trainable_count,
|
| 327 |
+
"vision_trainable": vision_trainable,
|
| 328 |
+
"trainable_fraction_of_model_and_heads": (
|
| 329 |
+
(language_trainable_count + head_trainable_count)
|
| 330 |
+
/ (sum(parameter.numel() for parameter in model.parameters()) + head_trainable_count)
|
| 331 |
+
),
|
| 332 |
+
},
|
| 333 |
+
"optimizer_state": {
|
| 334 |
+
"cuda_gib": optimizer_cuda_bytes / 2**30,
|
| 335 |
+
"cpu_gib": optimizer_cpu_bytes / 2**30,
|
| 336 |
+
},
|
| 337 |
+
"loss_last": component_history[-1],
|
| 338 |
+
"loss_all_finite": all(
|
| 339 |
+
math.isfinite(value)
|
| 340 |
+
for item in component_history
|
| 341 |
+
for key, value in item.items()
|
| 342 |
+
if key in {"total", "preference", "progress", "success"}
|
| 343 |
+
),
|
| 344 |
+
"gradient_audit": last_gradient_audit,
|
| 345 |
+
"sequence_tokens": {
|
| 346 |
+
"min": int(min(sequence_lengths)),
|
| 347 |
+
"max": int(max(sequence_lengths)),
|
| 348 |
+
"mean": float(np.mean(sequence_lengths)),
|
| 349 |
+
},
|
| 350 |
+
"timing": {
|
| 351 |
+
"model_load_seconds": load_seconds,
|
| 352 |
+
"run_seconds": run_seconds,
|
| 353 |
+
"seconds_per_microstep": run_seconds
|
| 354 |
+
/ (args.optimizer_steps * args.gradient_accumulation),
|
| 355 |
+
"pairs_per_second": effective_batch * args.optimizer_steps / run_seconds,
|
| 356 |
+
},
|
| 357 |
+
"gpu": {
|
| 358 |
+
"name": device.name,
|
| 359 |
+
"total_gib": device.total_memory / 2**30,
|
| 360 |
+
"peak_allocated_gib": peak_allocated / 2**30,
|
| 361 |
+
"peak_reserved_gib": peak_reserved / 2**30,
|
| 362 |
+
"peak_reserved_fraction": peak_reserved / device.total_memory,
|
| 363 |
+
},
|
| 364 |
+
}
|
| 365 |
+
args.output.parent.mkdir(parents=True, exist_ok=True)
|
| 366 |
+
args.output.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
|
| 367 |
+
print(json.dumps(payload, indent=2), flush=True)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
if __name__ == "__main__":
|
| 371 |
+
main()
|
scripts/train_phase21a_robometer_low_lora.py
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Resumable formal Robometer-Low-LoRA training on joint preference pairs."""
|
| 3 |
+
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import hashlib
|
| 8 |
+
import json
|
| 9 |
+
import math
|
| 10 |
+
import os
|
| 11 |
+
import random
|
| 12 |
+
import sys
|
| 13 |
+
import time
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
# Avoid large reserved-but-unallocated CUDA segments on near-capacity batches.
|
| 17 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 18 |
+
|
| 19 |
+
import numpy as np
|
| 20 |
+
import torch
|
| 21 |
+
import torch.nn.functional as F
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
|
| 25 |
+
if str(EXPERIMENT_ROOT) not in sys.path:
|
| 26 |
+
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
| 27 |
+
|
| 28 |
+
from scripts.smoke_phase21a_robometer_low import ( # noqa: E402
|
| 29 |
+
SPECIAL_TOKENS,
|
| 30 |
+
RobometerHeads,
|
| 31 |
+
build_conversation,
|
| 32 |
+
finite_grad_audit,
|
| 33 |
+
group_grad_norm,
|
| 34 |
+
make_targets,
|
| 35 |
+
process_batch,
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def parse_args() -> argparse.Namespace:
|
| 40 |
+
parser = argparse.ArgumentParser()
|
| 41 |
+
parser.add_argument("--snapshot", type=Path, required=True)
|
| 42 |
+
parser.add_argument("--manifest", type=Path, required=True)
|
| 43 |
+
parser.add_argument("--processed-root", type=Path, required=True)
|
| 44 |
+
parser.add_argument("--seed", type=int, required=True)
|
| 45 |
+
parser.add_argument("--optimizer-steps", type=int, default=3000)
|
| 46 |
+
parser.add_argument("--micro-batch-pairs", type=int, default=32)
|
| 47 |
+
parser.add_argument("--gradient-accumulation", type=int, default=2)
|
| 48 |
+
parser.add_argument("--lora-lr", type=float, default=2.0e-6)
|
| 49 |
+
parser.add_argument("--head-lr", type=float, default=1.0e-4)
|
| 50 |
+
parser.add_argument("--checkpoint-every", type=int, default=250)
|
| 51 |
+
parser.add_argument("--eval-every", type=int, default=250)
|
| 52 |
+
parser.add_argument("--eval-micro-batch-pairs", type=int, default=32)
|
| 53 |
+
parser.add_argument("--early-stop-min-steps", type=int, default=1000)
|
| 54 |
+
parser.add_argument("--early-stop-patience-evals", type=int, default=4)
|
| 55 |
+
parser.add_argument("--early-stop-min-delta", type=float, default=5.0e-3)
|
| 56 |
+
parser.add_argument("--output-dir", type=Path, required=True)
|
| 57 |
+
parser.add_argument("--resume", action="store_true")
|
| 58 |
+
return parser.parse_args()
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def read_rows(path: Path) -> list[dict]:
|
| 62 |
+
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
| 63 |
+
train = [row for row in rows if row["split"] == "train" and row["sample_type"] == "preference"]
|
| 64 |
+
if len(train) != 2021:
|
| 65 |
+
raise RuntimeError(f"expected 2021 train preference pairs, found {len(train)}")
|
| 66 |
+
return train
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def read_dev_rows(path: Path) -> list[dict]:
|
| 70 |
+
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
| 71 |
+
dev = [row for row in rows if row["split"] == "dev" and row["sample_type"] == "preference"]
|
| 72 |
+
if len(dev) != 108:
|
| 73 |
+
raise RuntimeError(f"expected 108 ID-dev preference pairs, found {len(dev)}")
|
| 74 |
+
return dev
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def global_success_pos_weight(rows: list[dict]) -> float:
|
| 78 |
+
positive = 0.0
|
| 79 |
+
total = 0
|
| 80 |
+
for row in rows:
|
| 81 |
+
for role in ("chosen", "rejected"):
|
| 82 |
+
record = row[role]
|
| 83 |
+
valid = np.asarray(record["frame_valid_mask"], dtype=bool)
|
| 84 |
+
labels = np.asarray(record["success_label"], dtype=np.float32)
|
| 85 |
+
positive += float(labels[valid].sum())
|
| 86 |
+
total += int(valid.sum())
|
| 87 |
+
if not 0 < positive < total:
|
| 88 |
+
raise RuntimeError("success labels require both classes")
|
| 89 |
+
return (total - positive) / positive
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def atomic_torch_save(payload: dict, path: Path) -> None:
|
| 93 |
+
temporary = path.with_suffix(".tmp")
|
| 94 |
+
torch.save(payload, temporary)
|
| 95 |
+
temporary.replace(path)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def reconcile_progress_log(path: Path, completed_steps: int) -> None:
|
| 99 |
+
"""Drop records newer than the checkpoint before a resumed run appends."""
|
| 100 |
+
if not path.is_file():
|
| 101 |
+
return
|
| 102 |
+
records = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
| 103 |
+
retained = [record for record in records if int(record["step"]) <= completed_steps]
|
| 104 |
+
temporary = path.with_suffix(".tmp")
|
| 105 |
+
payload = "".join(json.dumps(record, sort_keys=True) + "\n" for record in retained)
|
| 106 |
+
temporary.write_text(payload, encoding="utf-8")
|
| 107 |
+
temporary.replace(path)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def cpu_state_dict(state: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
|
| 111 |
+
return {name: tensor.detach().cpu().clone() for name, tensor in state.items()}
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@torch.inference_mode()
|
| 115 |
+
def evaluate_dev(
|
| 116 |
+
*, model, processor, heads, rows: list[dict], processed_root: Path,
|
| 117 |
+
token_ids: dict[str, int], hidden_size: int, pos_weight: torch.Tensor,
|
| 118 |
+
micro_batch_pairs: int,
|
| 119 |
+
) -> dict:
|
| 120 |
+
"""Evaluate both A/B orientations on the frozen ID-dev split only."""
|
| 121 |
+
model.eval()
|
| 122 |
+
heads.eval()
|
| 123 |
+
presentations = [(row, chosen_is_a) for row in rows for chosen_is_a in (True, False)]
|
| 124 |
+
sums = {"preference": 0.0, "progress": 0.0, "success": 0.0}
|
| 125 |
+
counts = {"preference": 0, "progress": 0, "success": 0}
|
| 126 |
+
preference_correct = 0
|
| 127 |
+
for start in range(0, len(presentations), micro_batch_pairs):
|
| 128 |
+
batch = presentations[start : start + micro_batch_pairs]
|
| 129 |
+
conversations, records_a, preference_labels = [], [], []
|
| 130 |
+
for row, chosen_is_a in batch:
|
| 131 |
+
conversation, record_a, label = build_conversation(row, processed_root, chosen_is_a)
|
| 132 |
+
conversations.append(conversation)
|
| 133 |
+
records_a.append(record_a)
|
| 134 |
+
preference_labels.append(label)
|
| 135 |
+
inputs = process_batch(processor, conversations)
|
| 136 |
+
inputs = {key: value.to("cuda") for key, value in inputs.items()}
|
| 137 |
+
targets = make_targets(records_a, torch.device("cuda"))
|
| 138 |
+
preference_target = torch.tensor(preference_labels, dtype=torch.float32, device="cuda")
|
| 139 |
+
output = model.model(**inputs, output_hidden_states=False, use_cache=False)
|
| 140 |
+
hidden = output.last_hidden_state
|
| 141 |
+
prog_mask = inputs["input_ids"].eq(token_ids["<|prog_token|>"])
|
| 142 |
+
pref_mask = inputs["input_ids"].eq(token_ids["<|pref_token|>"])
|
| 143 |
+
batch_size = len(batch)
|
| 144 |
+
if not torch.equal(prog_mask.sum(1), torch.full_like(prog_mask.sum(1), 16)):
|
| 145 |
+
raise RuntimeError("ID-dev progress token extraction mismatch")
|
| 146 |
+
if not torch.equal(pref_mask.sum(1), torch.ones_like(pref_mask.sum(1))):
|
| 147 |
+
raise RuntimeError("ID-dev preference token extraction mismatch")
|
| 148 |
+
prog_hidden = hidden[prog_mask].reshape(batch_size, 16, hidden_size)
|
| 149 |
+
pref_hidden = hidden[pref_mask].reshape(batch_size, hidden_size)
|
| 150 |
+
progress_logits = heads.progress_head(prog_hidden[:, :8])
|
| 151 |
+
success_logits = heads.success_head(prog_hidden[:, :8]).squeeze(-1)
|
| 152 |
+
preference_logits = heads.preference_head(pref_hidden).squeeze(-1)
|
| 153 |
+
progress_frames = -(
|
| 154 |
+
targets["progress"] * F.log_softmax(progress_logits.float(), dim=-1)
|
| 155 |
+
).sum(-1)
|
| 156 |
+
progress_values = progress_frames[targets["progress_mask"]]
|
| 157 |
+
success_sum = F.binary_cross_entropy_with_logits(
|
| 158 |
+
success_logits.float()[targets["success_mask"]],
|
| 159 |
+
targets["success"][targets["success_mask"]],
|
| 160 |
+
pos_weight=pos_weight,
|
| 161 |
+
reduction="sum",
|
| 162 |
+
)
|
| 163 |
+
preference_sum = F.binary_cross_entropy_with_logits(
|
| 164 |
+
preference_logits.float(), preference_target, reduction="sum"
|
| 165 |
+
)
|
| 166 |
+
sums["progress"] += float(progress_values.sum())
|
| 167 |
+
sums["success"] += float(success_sum)
|
| 168 |
+
sums["preference"] += float(preference_sum)
|
| 169 |
+
counts["progress"] += int(progress_values.numel())
|
| 170 |
+
counts["success"] += int(targets["success_mask"].sum())
|
| 171 |
+
counts["preference"] += batch_size
|
| 172 |
+
preference_correct += int(
|
| 173 |
+
((preference_logits > 0) == (preference_target > 0.5)).sum()
|
| 174 |
+
)
|
| 175 |
+
components = {name: sums[name] / counts[name] for name in sums}
|
| 176 |
+
model.train()
|
| 177 |
+
heads.train()
|
| 178 |
+
return {
|
| 179 |
+
"loss": sum(components.values()),
|
| 180 |
+
"loss_components": components,
|
| 181 |
+
"preference_accuracy": preference_correct / counts["preference"],
|
| 182 |
+
"pair_count": len(rows),
|
| 183 |
+
"orientation_presentations": len(presentations),
|
| 184 |
+
"supervision_counts": counts,
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def main() -> None:
|
| 189 |
+
args = parse_args()
|
| 190 |
+
if args.micro_batch_pairs * args.gradient_accumulation != 64:
|
| 191 |
+
raise ValueError("formal protocol requires exact effective batch 64")
|
| 192 |
+
if min(args.optimizer_steps, args.checkpoint_every, args.eval_every) <= 0:
|
| 193 |
+
raise ValueError("steps, checkpoint interval, and eval interval must be positive")
|
| 194 |
+
if args.eval_micro_batch_pairs <= 0 or args.early_stop_patience_evals <= 0:
|
| 195 |
+
raise ValueError("eval batch and early-stop patience must be positive")
|
| 196 |
+
if not 0 < args.early_stop_min_steps <= args.optimizer_steps:
|
| 197 |
+
raise ValueError("early-stop minimum must be in (0, optimizer_steps]")
|
| 198 |
+
if args.checkpoint_every != args.eval_every:
|
| 199 |
+
raise ValueError("v2 requires checkpoint_every == eval_every")
|
| 200 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 201 |
+
random.seed(args.seed)
|
| 202 |
+
np.random.seed(args.seed)
|
| 203 |
+
torch.manual_seed(args.seed)
|
| 204 |
+
torch.cuda.manual_seed_all(args.seed)
|
| 205 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 206 |
+
|
| 207 |
+
from peft import LoraConfig, get_peft_model, get_peft_model_state_dict, set_peft_model_state_dict
|
| 208 |
+
from transformers import AutoModelForImageTextToText, AutoProcessor
|
| 209 |
+
|
| 210 |
+
args.output_dir.mkdir(parents=True, exist_ok=True)
|
| 211 |
+
progress_path = args.output_dir / "progress.jsonl"
|
| 212 |
+
checkpoint_path = args.output_dir / "latest_training_state.pt"
|
| 213 |
+
best_model_path = args.output_dir / "best_model_state.pt"
|
| 214 |
+
eval_path = args.output_dir / "id_dev_metrics.jsonl"
|
| 215 |
+
manifest_sha256 = hashlib.sha256(args.manifest.read_bytes()).hexdigest()
|
| 216 |
+
rows = read_rows(args.manifest)
|
| 217 |
+
dev_rows = read_dev_rows(args.manifest)
|
| 218 |
+
processor = AutoProcessor.from_pretrained(args.snapshot, local_files_only=True, padding_side="right")
|
| 219 |
+
for token in SPECIAL_TOKENS:
|
| 220 |
+
if token not in processor.tokenizer.get_vocab():
|
| 221 |
+
processor.tokenizer.add_special_tokens({"additional_special_tokens": [token]})
|
| 222 |
+
token_ids = {token: int(processor.tokenizer.convert_tokens_to_ids(token)) for token in SPECIAL_TOKENS}
|
| 223 |
+
model = AutoModelForImageTextToText.from_pretrained(
|
| 224 |
+
args.snapshot,
|
| 225 |
+
local_files_only=True,
|
| 226 |
+
dtype=torch.bfloat16,
|
| 227 |
+
attn_implementation="flash_attention_2",
|
| 228 |
+
device_map={"": 0},
|
| 229 |
+
)
|
| 230 |
+
model.resize_token_embeddings(len(processor.tokenizer))
|
| 231 |
+
for parameter in model.parameters():
|
| 232 |
+
parameter.requires_grad_(False)
|
| 233 |
+
model.model.language_model = get_peft_model(
|
| 234 |
+
model.model.language_model,
|
| 235 |
+
LoraConfig(
|
| 236 |
+
r=8,
|
| 237 |
+
lora_alpha=16,
|
| 238 |
+
lora_dropout=0.05,
|
| 239 |
+
bias="none",
|
| 240 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
|
| 241 |
+
trainable_token_indices=list(token_ids.values()),
|
| 242 |
+
),
|
| 243 |
+
)
|
| 244 |
+
model.config.use_cache = False
|
| 245 |
+
model.gradient_checkpointing_enable(gradient_checkpointing_kwargs={"use_reentrant": False})
|
| 246 |
+
model.enable_input_require_grads()
|
| 247 |
+
hidden_size = int(model.config.text_config.hidden_size)
|
| 248 |
+
heads = RobometerHeads(hidden_size).to(device="cuda", dtype=torch.bfloat16)
|
| 249 |
+
backbone_parameters = [parameter for parameter in model.parameters() if parameter.requires_grad]
|
| 250 |
+
head_parameters = list(heads.parameters())
|
| 251 |
+
optimizer = torch.optim.AdamW(
|
| 252 |
+
[
|
| 253 |
+
{"params": backbone_parameters, "lr": args.lora_lr},
|
| 254 |
+
{"params": head_parameters, "lr": args.head_lr},
|
| 255 |
+
],
|
| 256 |
+
weight_decay=0.01,
|
| 257 |
+
)
|
| 258 |
+
warmup_steps = max(1, round(args.optimizer_steps * 0.05))
|
| 259 |
+
|
| 260 |
+
def lr_lambda(step: int) -> float:
|
| 261 |
+
if step < warmup_steps:
|
| 262 |
+
return (step + 1) / warmup_steps
|
| 263 |
+
progress = (step - warmup_steps) / max(1, args.optimizer_steps - warmup_steps)
|
| 264 |
+
return 0.5 * (1.0 + math.cos(math.pi * progress))
|
| 265 |
+
|
| 266 |
+
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)
|
| 267 |
+
generator = np.random.default_rng(args.seed)
|
| 268 |
+
order = generator.permutation(len(rows)).tolist()
|
| 269 |
+
cursor = 0
|
| 270 |
+
start_step = 0
|
| 271 |
+
step_seconds: list[float] = []
|
| 272 |
+
eval_history: list[dict] = []
|
| 273 |
+
best_eval_loss = math.inf
|
| 274 |
+
best_step = 0
|
| 275 |
+
bad_eval_count = 0
|
| 276 |
+
pos_weight = torch.tensor(global_success_pos_weight(rows), dtype=torch.float32, device="cuda")
|
| 277 |
+
|
| 278 |
+
if args.resume:
|
| 279 |
+
if not checkpoint_path.is_file():
|
| 280 |
+
raise FileNotFoundError(checkpoint_path)
|
| 281 |
+
checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
|
| 282 |
+
if checkpoint.get("protocol_id") != "phase21a_robometer_low_lora_train_v2":
|
| 283 |
+
raise RuntimeError("refusing to resume a checkpoint from a different protocol")
|
| 284 |
+
if checkpoint["manifest_sha256"] != manifest_sha256 or checkpoint["seed"] != args.seed:
|
| 285 |
+
raise RuntimeError("resume manifest or seed mismatch")
|
| 286 |
+
set_peft_model_state_dict(model.model.language_model, checkpoint["adapter_state"])
|
| 287 |
+
heads.load_state_dict(checkpoint["heads_state"])
|
| 288 |
+
optimizer.load_state_dict(checkpoint["optimizer_state"])
|
| 289 |
+
scheduler.load_state_dict(checkpoint["scheduler_state"])
|
| 290 |
+
generator.bit_generator.state = checkpoint["numpy_generator_state"]
|
| 291 |
+
order = checkpoint["order"]
|
| 292 |
+
cursor = checkpoint["cursor"]
|
| 293 |
+
start_step = checkpoint["completed_steps"]
|
| 294 |
+
step_seconds = checkpoint["step_seconds"]
|
| 295 |
+
eval_history = checkpoint.get("eval_history", [])
|
| 296 |
+
best_eval_loss = float(checkpoint.get("best_eval_loss", math.inf))
|
| 297 |
+
best_step = int(checkpoint.get("best_step", 0))
|
| 298 |
+
bad_eval_count = int(checkpoint.get("bad_eval_count", 0))
|
| 299 |
+
torch.set_rng_state(checkpoint["torch_rng_state"])
|
| 300 |
+
torch.cuda.set_rng_state_all(checkpoint["cuda_rng_state_all"])
|
| 301 |
+
reconcile_progress_log(progress_path, start_step)
|
| 302 |
+
reconcile_progress_log(eval_path, start_step)
|
| 303 |
+
if start_step >= args.optimizer_steps:
|
| 304 |
+
print(json.dumps({"status": "already_complete", "completed_steps": start_step}), flush=True)
|
| 305 |
+
return
|
| 306 |
+
|
| 307 |
+
model.train()
|
| 308 |
+
heads.train()
|
| 309 |
+
torch.cuda.empty_cache()
|
| 310 |
+
torch.cuda.reset_peak_memory_stats()
|
| 311 |
+
named_trainable = list(model.named_parameters()) + [
|
| 312 |
+
(f"heads.{name}", parameter) for name, parameter in heads.named_parameters()
|
| 313 |
+
]
|
| 314 |
+
completed = start_step
|
| 315 |
+
stop_reason = "max_steps"
|
| 316 |
+
for step in range(start_step, args.optimizer_steps):
|
| 317 |
+
if cursor + 64 > len(order):
|
| 318 |
+
order = generator.permutation(len(rows)).tolist()
|
| 319 |
+
cursor = 0
|
| 320 |
+
selected_indices = order[cursor : cursor + 64]
|
| 321 |
+
cursor += 64
|
| 322 |
+
selected_rows = [rows[index] for index in selected_indices]
|
| 323 |
+
optimizer.zero_grad(set_to_none=True)
|
| 324 |
+
components = {"preference": 0.0, "progress": 0.0, "success": 0.0}
|
| 325 |
+
sequence_min = 10**9
|
| 326 |
+
sequence_max = 0
|
| 327 |
+
torch.cuda.synchronize()
|
| 328 |
+
step_started = time.perf_counter()
|
| 329 |
+
for microstep in range(args.gradient_accumulation):
|
| 330 |
+
batch_rows = selected_rows[
|
| 331 |
+
microstep * args.micro_batch_pairs : (microstep + 1) * args.micro_batch_pairs
|
| 332 |
+
]
|
| 333 |
+
conversations = []
|
| 334 |
+
records_a = []
|
| 335 |
+
preference_labels = []
|
| 336 |
+
for offset, row in enumerate(batch_rows):
|
| 337 |
+
global_presentation = step * 64 + microstep * args.micro_batch_pairs + offset
|
| 338 |
+
chosen_is_a = global_presentation % 2 == 0
|
| 339 |
+
conversation, record_a, label = build_conversation(row, args.processed_root, chosen_is_a)
|
| 340 |
+
conversations.append(conversation)
|
| 341 |
+
records_a.append(record_a)
|
| 342 |
+
preference_labels.append(label)
|
| 343 |
+
inputs = process_batch(processor, conversations)
|
| 344 |
+
lengths = inputs["attention_mask"].sum(1)
|
| 345 |
+
sequence_min = min(sequence_min, int(lengths.min()))
|
| 346 |
+
sequence_max = max(sequence_max, int(lengths.max()))
|
| 347 |
+
inputs = {key: value.to("cuda") for key, value in inputs.items()}
|
| 348 |
+
targets = make_targets(records_a, torch.device("cuda"))
|
| 349 |
+
preference_target = torch.tensor(preference_labels, dtype=torch.float32, device="cuda")
|
| 350 |
+
output = model.model(**inputs, output_hidden_states=False, use_cache=False)
|
| 351 |
+
hidden = output.last_hidden_state
|
| 352 |
+
prog_mask = inputs["input_ids"].eq(token_ids["<|prog_token|>"])
|
| 353 |
+
pref_mask = inputs["input_ids"].eq(token_ids["<|pref_token|>"])
|
| 354 |
+
if not torch.equal(prog_mask.sum(1), torch.full_like(prog_mask.sum(1), 16)):
|
| 355 |
+
raise RuntimeError("progress token extraction mismatch")
|
| 356 |
+
if not torch.equal(pref_mask.sum(1), torch.ones_like(pref_mask.sum(1))):
|
| 357 |
+
raise RuntimeError("preference token extraction mismatch")
|
| 358 |
+
prog_hidden = hidden[prog_mask].reshape(args.micro_batch_pairs, 16, hidden_size)
|
| 359 |
+
pref_hidden = hidden[pref_mask].reshape(args.micro_batch_pairs, hidden_size)
|
| 360 |
+
progress_logits = heads.progress_head(prog_hidden[:, :8])
|
| 361 |
+
success_logits = heads.success_head(prog_hidden[:, :8]).squeeze(-1)
|
| 362 |
+
preference_logits = heads.preference_head(pref_hidden).squeeze(-1)
|
| 363 |
+
if targets["progress_mask"].any():
|
| 364 |
+
progress_frames = -(
|
| 365 |
+
targets["progress"] * F.log_softmax(progress_logits.float(), dim=-1)
|
| 366 |
+
).sum(-1)
|
| 367 |
+
progress_loss = progress_frames[targets["progress_mask"]].mean()
|
| 368 |
+
else:
|
| 369 |
+
raise RuntimeError("formal micro-batch has empty progress supervision")
|
| 370 |
+
success_loss = F.binary_cross_entropy_with_logits(
|
| 371 |
+
success_logits.float()[targets["success_mask"]],
|
| 372 |
+
targets["success"][targets["success_mask"]],
|
| 373 |
+
pos_weight=pos_weight,
|
| 374 |
+
)
|
| 375 |
+
preference_loss = F.binary_cross_entropy_with_logits(
|
| 376 |
+
preference_logits.float(), preference_target
|
| 377 |
+
)
|
| 378 |
+
total_loss = preference_loss + progress_loss + success_loss
|
| 379 |
+
if not torch.isfinite(total_loss):
|
| 380 |
+
raise FloatingPointError(f"non-finite loss at step {step + 1}")
|
| 381 |
+
(total_loss / args.gradient_accumulation).backward()
|
| 382 |
+
components["preference"] += float(preference_loss.detach()) / args.gradient_accumulation
|
| 383 |
+
components["progress"] += float(progress_loss.detach()) / args.gradient_accumulation
|
| 384 |
+
components["success"] += float(success_loss.detach()) / args.gradient_accumulation
|
| 385 |
+
finite, grad_norm, grad_tensors = finite_grad_audit(named_trainable)
|
| 386 |
+
if not finite:
|
| 387 |
+
raise FloatingPointError(f"non-finite gradient at step {step + 1}")
|
| 388 |
+
group_norms = {
|
| 389 |
+
"lora": group_grad_norm(named_trainable, "lora_"),
|
| 390 |
+
"token_rows": group_grad_norm(named_trainable, "trainable_tokens"),
|
| 391 |
+
"progress_head": group_grad_norm(named_trainable, "heads.progress_head"),
|
| 392 |
+
"success_head": group_grad_norm(named_trainable, "heads.success_head"),
|
| 393 |
+
"preference_head": group_grad_norm(named_trainable, "heads.preference_head"),
|
| 394 |
+
}
|
| 395 |
+
if any(value <= 0 for value in group_norms.values()):
|
| 396 |
+
raise RuntimeError(f"missing gradient group: {group_norms}")
|
| 397 |
+
torch.nn.utils.clip_grad_norm_(backbone_parameters + head_parameters, 1.0)
|
| 398 |
+
optimizer.step()
|
| 399 |
+
scheduler.step()
|
| 400 |
+
torch.cuda.synchronize()
|
| 401 |
+
elapsed = time.perf_counter() - step_started
|
| 402 |
+
step_seconds.append(elapsed)
|
| 403 |
+
record = {
|
| 404 |
+
"step": step + 1,
|
| 405 |
+
"loss": sum(components.values()),
|
| 406 |
+
"loss_components": components,
|
| 407 |
+
"gradient_norm_preclip": grad_norm,
|
| 408 |
+
"gradient_tensor_count": grad_tensors,
|
| 409 |
+
"gradient_group_norms": group_norms,
|
| 410 |
+
"step_seconds": elapsed,
|
| 411 |
+
"lora_lr": optimizer.param_groups[0]["lr"],
|
| 412 |
+
"head_lr": optimizer.param_groups[1]["lr"],
|
| 413 |
+
"sequence_tokens_min": sequence_min,
|
| 414 |
+
"sequence_tokens_max": sequence_max,
|
| 415 |
+
"peak_allocated_gib": torch.cuda.max_memory_allocated() / 2**30,
|
| 416 |
+
"peak_reserved_gib": torch.cuda.max_memory_reserved() / 2**30,
|
| 417 |
+
}
|
| 418 |
+
with progress_path.open("a", encoding="utf-8") as stream:
|
| 419 |
+
stream.write(json.dumps(record, sort_keys=True) + "\n")
|
| 420 |
+
print(json.dumps(record, sort_keys=True), flush=True)
|
| 421 |
+
completed = step + 1
|
| 422 |
+
should_evaluate = completed % args.eval_every == 0 or completed == args.optimizer_steps
|
| 423 |
+
if should_evaluate:
|
| 424 |
+
metrics = evaluate_dev(
|
| 425 |
+
model=model,
|
| 426 |
+
processor=processor,
|
| 427 |
+
heads=heads,
|
| 428 |
+
rows=dev_rows,
|
| 429 |
+
processed_root=args.processed_root,
|
| 430 |
+
token_ids=token_ids,
|
| 431 |
+
hidden_size=hidden_size,
|
| 432 |
+
pos_weight=pos_weight,
|
| 433 |
+
micro_batch_pairs=args.eval_micro_batch_pairs,
|
| 434 |
+
)
|
| 435 |
+
metrics["step"] = completed
|
| 436 |
+
eval_history.append(metrics)
|
| 437 |
+
with eval_path.open("a", encoding="utf-8") as stream:
|
| 438 |
+
stream.write(json.dumps(metrics, sort_keys=True) + "\n")
|
| 439 |
+
print(json.dumps({"id_dev": metrics}, sort_keys=True), flush=True)
|
| 440 |
+
if metrics["loss"] < best_eval_loss - args.early_stop_min_delta:
|
| 441 |
+
best_eval_loss = float(metrics["loss"])
|
| 442 |
+
best_step = completed
|
| 443 |
+
bad_eval_count = 0
|
| 444 |
+
atomic_torch_save(
|
| 445 |
+
{
|
| 446 |
+
"protocol_id": "phase21a_robometer_low_lora_train_v2",
|
| 447 |
+
"selected_step": best_step,
|
| 448 |
+
"id_dev_metrics": metrics,
|
| 449 |
+
"adapter_state": cpu_state_dict(
|
| 450 |
+
get_peft_model_state_dict(model.model.language_model)
|
| 451 |
+
),
|
| 452 |
+
"heads_state": cpu_state_dict(heads.state_dict()),
|
| 453 |
+
},
|
| 454 |
+
best_model_path,
|
| 455 |
+
)
|
| 456 |
+
else:
|
| 457 |
+
bad_eval_count += 1
|
| 458 |
+
|
| 459 |
+
should_checkpoint = completed % args.checkpoint_every == 0 or completed == args.optimizer_steps
|
| 460 |
+
if should_checkpoint:
|
| 461 |
+
atomic_torch_save(
|
| 462 |
+
{
|
| 463 |
+
"protocol_id": "phase21a_robometer_low_lora_train_v2",
|
| 464 |
+
"completed_steps": completed,
|
| 465 |
+
"target_steps": args.optimizer_steps,
|
| 466 |
+
"seed": args.seed,
|
| 467 |
+
"manifest_sha256": manifest_sha256,
|
| 468 |
+
"adapter_state": get_peft_model_state_dict(model.model.language_model),
|
| 469 |
+
"heads_state": heads.state_dict(),
|
| 470 |
+
"optimizer_state": optimizer.state_dict(),
|
| 471 |
+
"scheduler_state": scheduler.state_dict(),
|
| 472 |
+
"numpy_generator_state": generator.bit_generator.state,
|
| 473 |
+
"order": order,
|
| 474 |
+
"cursor": cursor,
|
| 475 |
+
"step_seconds": step_seconds,
|
| 476 |
+
"eval_history": eval_history,
|
| 477 |
+
"best_eval_loss": best_eval_loss,
|
| 478 |
+
"best_step": best_step,
|
| 479 |
+
"bad_eval_count": bad_eval_count,
|
| 480 |
+
"torch_rng_state": torch.get_rng_state(),
|
| 481 |
+
"cuda_rng_state_all": torch.cuda.get_rng_state_all(),
|
| 482 |
+
},
|
| 483 |
+
checkpoint_path,
|
| 484 |
+
)
|
| 485 |
+
if (
|
| 486 |
+
should_evaluate
|
| 487 |
+
and completed >= args.early_stop_min_steps
|
| 488 |
+
and bad_eval_count >= args.early_stop_patience_evals
|
| 489 |
+
):
|
| 490 |
+
stop_reason = "id_dev_early_stop"
|
| 491 |
+
break
|
| 492 |
+
if not best_model_path.is_file():
|
| 493 |
+
raise RuntimeError("training completed without an ID-dev model selection checkpoint")
|
| 494 |
+
best_model = torch.load(best_model_path, map_location="cpu", weights_only=False)
|
| 495 |
+
set_peft_model_state_dict(model.model.language_model, best_model["adapter_state"])
|
| 496 |
+
heads.load_state_dict(best_model["heads_state"])
|
| 497 |
+
adapter_dir = args.output_dir / "adapter"
|
| 498 |
+
model.model.language_model.save_pretrained(adapter_dir)
|
| 499 |
+
torch.save(heads.state_dict(), args.output_dir / "reward_heads.pt")
|
| 500 |
+
summary = {
|
| 501 |
+
"status": "training_complete",
|
| 502 |
+
"protocol_id": "phase21a_robometer_low_lora_train_v2",
|
| 503 |
+
"seed": args.seed,
|
| 504 |
+
"optimizer_steps_cap": args.optimizer_steps,
|
| 505 |
+
"completed_steps": completed,
|
| 506 |
+
"selected_step": best_step,
|
| 507 |
+
"best_id_dev_loss": best_eval_loss,
|
| 508 |
+
"stop_reason": stop_reason,
|
| 509 |
+
"effective_batch_pairs": 64,
|
| 510 |
+
"mean_step_seconds": float(np.mean(step_seconds)),
|
| 511 |
+
"total_training_hours": float(np.sum(step_seconds) / 3600),
|
| 512 |
+
"final_ood_used": False,
|
| 513 |
+
}
|
| 514 |
+
(args.output_dir / "summary.json").write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
|
| 515 |
+
print(json.dumps(summary, indent=2), flush=True)
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
if __name__ == "__main__":
|
| 519 |
+
main()
|
src/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Experiment-local implementation modules."""
|
src/data_contract.py
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Backbone-neutral trajectory contract for Route A.
|
| 2 |
+
|
| 3 |
+
The module deliberately stops before model-specific normalization. Every backbone
|
| 4 |
+
therefore receives the same selected RGB frames, labels and masks.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import hashlib
|
| 10 |
+
import json
|
| 11 |
+
from dataclasses import dataclass, replace
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import Any, Iterable, Literal, Sequence
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
import torch
|
| 17 |
+
from PIL import Image
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
TemporalStrategy = Literal["forward", "reverse", "rewind", "different_task"]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def deterministic_frame_indices(
|
| 24 |
+
frame_count: int, target_frames: int = 8
|
| 25 |
+
) -> tuple[np.ndarray, np.ndarray]:
|
| 26 |
+
"""Return official-style rint(linspace) indices and a real-frame mask."""
|
| 27 |
+
|
| 28 |
+
if frame_count <= 0 or target_frames <= 0:
|
| 29 |
+
raise ValueError("frame_count and target_frames must be positive")
|
| 30 |
+
if frame_count >= target_frames:
|
| 31 |
+
indices = np.rint(
|
| 32 |
+
np.linspace(0, frame_count - 1, target_frames)
|
| 33 |
+
).astype(np.int64)
|
| 34 |
+
indices[0] = 0
|
| 35 |
+
indices[-1] = frame_count - 1
|
| 36 |
+
return indices, np.ones(target_frames, dtype=np.bool_)
|
| 37 |
+
|
| 38 |
+
real = np.arange(frame_count, dtype=np.int64)
|
| 39 |
+
padding = np.full(target_frames - frame_count, frame_count - 1, dtype=np.int64)
|
| 40 |
+
valid = np.concatenate(
|
| 41 |
+
[
|
| 42 |
+
np.ones(frame_count, dtype=np.bool_),
|
| 43 |
+
np.zeros(target_frames - frame_count, dtype=np.bool_),
|
| 44 |
+
]
|
| 45 |
+
)
|
| 46 |
+
return np.concatenate([real, padding]), valid
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def temporal_permutation(length: int, strategy: TemporalStrategy) -> np.ndarray:
|
| 50 |
+
"""Return a permutation shared by frames, labels, indices and masks."""
|
| 51 |
+
|
| 52 |
+
if length <= 0:
|
| 53 |
+
raise ValueError("length must be positive")
|
| 54 |
+
if strategy in ("forward", "different_task"):
|
| 55 |
+
return np.arange(length, dtype=np.int64)
|
| 56 |
+
if strategy == "reverse":
|
| 57 |
+
return np.arange(length - 1, -1, -1, dtype=np.int64)
|
| 58 |
+
if strategy == "rewind":
|
| 59 |
+
split = length // 2
|
| 60 |
+
return np.concatenate(
|
| 61 |
+
[
|
| 62 |
+
np.arange(split, dtype=np.int64),
|
| 63 |
+
np.arange(length - 1, split - 1, -1, dtype=np.int64),
|
| 64 |
+
]
|
| 65 |
+
)
|
| 66 |
+
raise ValueError(f"unsupported temporal strategy: {strategy}")
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def letterbox_rgb(frame: np.ndarray, canvas_size: int = 256) -> np.ndarray:
|
| 70 |
+
"""Deterministically resize an RGB uint8 frame onto a square black canvas."""
|
| 71 |
+
|
| 72 |
+
if (
|
| 73 |
+
frame.ndim != 3
|
| 74 |
+
or frame.shape[-1] != 3
|
| 75 |
+
or frame.dtype != np.uint8
|
| 76 |
+
or canvas_size <= 0
|
| 77 |
+
):
|
| 78 |
+
raise ValueError("frame must be uint8 [H,W,3] and canvas_size must be positive")
|
| 79 |
+
height, width = frame.shape[:2]
|
| 80 |
+
if height <= 0 or width <= 0:
|
| 81 |
+
raise ValueError("frame dimensions must be positive")
|
| 82 |
+
scale = min(canvas_size / width, canvas_size / height)
|
| 83 |
+
resized_width = max(1, min(canvas_size, round(width * scale)))
|
| 84 |
+
resized_height = max(1, min(canvas_size, round(height * scale)))
|
| 85 |
+
resized = Image.fromarray(frame, mode="RGB").resize(
|
| 86 |
+
(resized_width, resized_height), Image.Resampling.BILINEAR
|
| 87 |
+
)
|
| 88 |
+
canvas = Image.new("RGB", (canvas_size, canvas_size), (0, 0, 0))
|
| 89 |
+
offset = (
|
| 90 |
+
(canvas_size - resized_width) // 2,
|
| 91 |
+
(canvas_size - resized_height) // 2,
|
| 92 |
+
)
|
| 93 |
+
canvas.paste(resized, offset)
|
| 94 |
+
return np.asarray(canvas, dtype=np.uint8)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def _c51_targets(values: np.ndarray, bins: int) -> np.ndarray:
|
| 98 |
+
if bins < 2:
|
| 99 |
+
raise ValueError("progress bins must be at least 2")
|
| 100 |
+
positions = np.clip(values, 0.0, 1.0) * (bins - 1)
|
| 101 |
+
lower = np.floor(positions).astype(np.int64)
|
| 102 |
+
upper = np.ceil(positions).astype(np.int64)
|
| 103 |
+
output = np.zeros((len(values), bins), dtype=np.float32)
|
| 104 |
+
rows = np.arange(len(values))
|
| 105 |
+
output[rows, lower] += upper - positions
|
| 106 |
+
output[rows, upper] += positions - lower
|
| 107 |
+
exact = lower == upper
|
| 108 |
+
output[rows[exact], lower[exact]] = 1.0
|
| 109 |
+
return output
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@dataclass(frozen=True)
|
| 113 |
+
class TrajectoryContract:
|
| 114 |
+
trajectory_id: str
|
| 115 |
+
task: str
|
| 116 |
+
data_source: str
|
| 117 |
+
quality_label: str
|
| 118 |
+
frames_uint8: np.ndarray
|
| 119 |
+
source_frame_indices: np.ndarray
|
| 120 |
+
frame_valid_mask: np.ndarray
|
| 121 |
+
progress: np.ndarray
|
| 122 |
+
progress_distribution: np.ndarray
|
| 123 |
+
progress_loss_mask: np.ndarray
|
| 124 |
+
success_labels: np.ndarray
|
| 125 |
+
success_loss_mask: np.ndarray
|
| 126 |
+
strategy: TemporalStrategy
|
| 127 |
+
metadata: dict[str, Any]
|
| 128 |
+
|
| 129 |
+
def validate(self) -> None:
|
| 130 |
+
frame_count = self.frames_uint8.shape[0]
|
| 131 |
+
if self.frames_uint8.dtype != np.uint8 or self.frames_uint8.ndim != 4:
|
| 132 |
+
raise ValueError("frames_uint8 must be uint8 [T,H,W,3]")
|
| 133 |
+
if self.frames_uint8.shape[-1] != 3:
|
| 134 |
+
raise ValueError("frames_uint8 must have three RGB channels")
|
| 135 |
+
aligned = (
|
| 136 |
+
self.source_frame_indices,
|
| 137 |
+
self.frame_valid_mask,
|
| 138 |
+
self.progress,
|
| 139 |
+
self.progress_distribution,
|
| 140 |
+
self.progress_loss_mask,
|
| 141 |
+
self.success_labels,
|
| 142 |
+
self.success_loss_mask,
|
| 143 |
+
)
|
| 144 |
+
if any(value.shape[0] != frame_count for value in aligned):
|
| 145 |
+
raise ValueError("all frame-level fields must have the same leading size")
|
| 146 |
+
if np.any(self.progress < 0) or np.any(self.progress > 1):
|
| 147 |
+
raise ValueError("progress values must be in [0,1]")
|
| 148 |
+
if np.any(self.progress_loss_mask & ~self.frame_valid_mask):
|
| 149 |
+
raise ValueError("padding may not enter progress loss")
|
| 150 |
+
if np.any(self.success_loss_mask & ~self.frame_valid_mask):
|
| 151 |
+
raise ValueError("padding may not enter success loss")
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
@dataclass(frozen=True)
|
| 155 |
+
class PreferenceContract:
|
| 156 |
+
trajectory_a: TrajectoryContract
|
| 157 |
+
trajectory_b: TrajectoryContract
|
| 158 |
+
preference_label: float
|
| 159 |
+
strategy: str
|
| 160 |
+
|
| 161 |
+
def swapped(self) -> PreferenceContract:
|
| 162 |
+
return replace(
|
| 163 |
+
self,
|
| 164 |
+
trajectory_a=self.trajectory_b,
|
| 165 |
+
trajectory_b=self.trajectory_a,
|
| 166 |
+
preference_label=1.0 - self.preference_label,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def prepare_trajectory(
|
| 171 |
+
*,
|
| 172 |
+
trajectory_id: str,
|
| 173 |
+
frames: np.ndarray,
|
| 174 |
+
task: str,
|
| 175 |
+
data_source: str,
|
| 176 |
+
quality_label: str,
|
| 177 |
+
partial_success: float | None = None,
|
| 178 |
+
strategy: TemporalStrategy = "forward",
|
| 179 |
+
target_frames: int = 8,
|
| 180 |
+
canvas_size: int = 256,
|
| 181 |
+
progress_bins: int = 10,
|
| 182 |
+
success_cutoff: float = 1.0,
|
| 183 |
+
metadata: dict[str, Any] | None = None,
|
| 184 |
+
) -> TrajectoryContract:
|
| 185 |
+
"""Materialize one auditable trajectory from an official sampler trajectory."""
|
| 186 |
+
|
| 187 |
+
if frames.ndim != 4 or frames.shape[-1] != 3 or frames.dtype != np.uint8:
|
| 188 |
+
raise ValueError("frames must be uint8 [T,H,W,3]")
|
| 189 |
+
if not trajectory_id or not task or not data_source:
|
| 190 |
+
raise ValueError("trajectory_id, task and data_source are required")
|
| 191 |
+
if partial_success is not None and not 0.0 <= partial_success <= 1.0:
|
| 192 |
+
raise ValueError("partial_success must be in [0,1]")
|
| 193 |
+
if not 0.0 <= success_cutoff <= 1.0:
|
| 194 |
+
raise ValueError("success_cutoff must be in [0,1]")
|
| 195 |
+
|
| 196 |
+
selected_indices, frame_valid = deterministic_frame_indices(
|
| 197 |
+
len(frames), target_frames
|
| 198 |
+
)
|
| 199 |
+
permutation = temporal_permutation(target_frames, strategy)
|
| 200 |
+
selected_indices = selected_indices[permutation]
|
| 201 |
+
frame_valid = frame_valid[permutation]
|
| 202 |
+
selected_frames = np.stack(
|
| 203 |
+
[letterbox_rgb(frames[index], canvas_size) for index in selected_indices]
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
denominator = max(len(frames) - 1, 1)
|
| 207 |
+
progress = selected_indices.astype(np.float32) / denominator
|
| 208 |
+
if partial_success is not None:
|
| 209 |
+
progress *= partial_success
|
| 210 |
+
if strategy == "different_task":
|
| 211 |
+
progress.fill(0.0)
|
| 212 |
+
|
| 213 |
+
quality = quality_label.lower()
|
| 214 |
+
dense_progress_allowed = quality not in {"failure", "failed", "suboptimal"}
|
| 215 |
+
progress_loss_mask = frame_valid & dense_progress_allowed
|
| 216 |
+
if strategy == "different_task":
|
| 217 |
+
progress_loss_mask = frame_valid.copy()
|
| 218 |
+
success_labels = (
|
| 219 |
+
(progress >= success_cutoff)
|
| 220 |
+
& (quality == "successful")
|
| 221 |
+
& frame_valid
|
| 222 |
+
).astype(np.float32)
|
| 223 |
+
success_loss_mask = frame_valid.copy()
|
| 224 |
+
audit_metadata = dict(metadata or {})
|
| 225 |
+
audit_metadata.update(
|
| 226 |
+
{
|
| 227 |
+
"source_frame_count": int(len(frames)),
|
| 228 |
+
"selected_frame_indices": selected_indices.tolist(),
|
| 229 |
+
"frame_valid_mask": frame_valid.tolist(),
|
| 230 |
+
"temporal_strategy": strategy,
|
| 231 |
+
"canvas_size": canvas_size,
|
| 232 |
+
"letterbox_fill_rgb": [0, 0, 0],
|
| 233 |
+
}
|
| 234 |
+
)
|
| 235 |
+
result = TrajectoryContract(
|
| 236 |
+
trajectory_id=trajectory_id,
|
| 237 |
+
task=task,
|
| 238 |
+
data_source=data_source,
|
| 239 |
+
quality_label=quality_label,
|
| 240 |
+
frames_uint8=selected_frames,
|
| 241 |
+
source_frame_indices=selected_indices,
|
| 242 |
+
frame_valid_mask=frame_valid,
|
| 243 |
+
progress=progress,
|
| 244 |
+
progress_distribution=_c51_targets(progress, progress_bins),
|
| 245 |
+
progress_loss_mask=progress_loss_mask,
|
| 246 |
+
success_labels=success_labels,
|
| 247 |
+
success_loss_mask=success_loss_mask,
|
| 248 |
+
strategy=strategy,
|
| 249 |
+
metadata=audit_metadata,
|
| 250 |
+
)
|
| 251 |
+
result.validate()
|
| 252 |
+
return result
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def adapt_official_trajectory(
|
| 256 |
+
trajectory: Any,
|
| 257 |
+
*,
|
| 258 |
+
strategy: TemporalStrategy,
|
| 259 |
+
progress_supervised: bool,
|
| 260 |
+
canvas_size: int = 256,
|
| 261 |
+
progress_bins: int = 10,
|
| 262 |
+
) -> TrajectoryContract:
|
| 263 |
+
"""Adapt an official Robometer sampler trajectory without changing its labels.
|
| 264 |
+
|
| 265 |
+
The sampler must expose its selected source indices and padding mask in
|
| 266 |
+
``trajectory.metadata``. This is intentionally strict: silently re-sampling
|
| 267 |
+
here would invalidate the matched-backbone comparison.
|
| 268 |
+
"""
|
| 269 |
+
|
| 270 |
+
metadata = dict(trajectory.metadata or {})
|
| 271 |
+
if "selected_frame_indices" not in metadata or "frame_valid_mask" not in metadata:
|
| 272 |
+
raise ValueError("official sampler metadata lacks frozen frame indices/mask")
|
| 273 |
+
frames = np.asarray(trajectory.frames)
|
| 274 |
+
if frames.ndim != 4 or frames.shape[-1] != 3 or frames.dtype != np.uint8:
|
| 275 |
+
raise ValueError("official sampler frames must be uint8 [T,H,W,3]")
|
| 276 |
+
selected_indices = np.asarray(metadata["selected_frame_indices"], dtype=np.int64)
|
| 277 |
+
frame_valid = np.asarray(metadata["frame_valid_mask"], dtype=np.bool_)
|
| 278 |
+
if len(selected_indices) != len(frames) or len(frame_valid) != len(frames):
|
| 279 |
+
raise ValueError("official sampler frame metadata is not aligned")
|
| 280 |
+
|
| 281 |
+
target_progress = trajectory.target_progress
|
| 282 |
+
if target_progress is None or len(target_progress) != len(frames):
|
| 283 |
+
raise ValueError("official target_progress is missing or misaligned")
|
| 284 |
+
first_target = target_progress[0]
|
| 285 |
+
if isinstance(first_target, torch.Tensor) or np.asarray(first_target).ndim > 0:
|
| 286 |
+
progress_distribution = np.stack(
|
| 287 |
+
[np.asarray(value, dtype=np.float32) for value in target_progress]
|
| 288 |
+
)
|
| 289 |
+
if progress_distribution.shape != (len(frames), progress_bins):
|
| 290 |
+
raise ValueError("official discrete progress target has unexpected shape")
|
| 291 |
+
support = np.linspace(0.0, 1.0, progress_bins, dtype=np.float32)
|
| 292 |
+
progress = progress_distribution @ support
|
| 293 |
+
else:
|
| 294 |
+
progress = np.asarray(target_progress, dtype=np.float32)
|
| 295 |
+
progress_distribution = _c51_targets(progress, progress_bins)
|
| 296 |
+
|
| 297 |
+
predict_mask = trajectory.predict_last_frame_mask
|
| 298 |
+
if predict_mask is None:
|
| 299 |
+
predict_valid = np.ones(len(frames), dtype=np.bool_)
|
| 300 |
+
else:
|
| 301 |
+
predict_valid = np.asarray(predict_mask, dtype=np.float32) > 0
|
| 302 |
+
progress_loss_mask = frame_valid & predict_valid & progress_supervised
|
| 303 |
+
success_labels = np.asarray(trajectory.success_label, dtype=np.float32)
|
| 304 |
+
if success_labels.shape != (len(frames),):
|
| 305 |
+
raise ValueError("official success labels are missing or misaligned")
|
| 306 |
+
|
| 307 |
+
metadata.update(
|
| 308 |
+
{
|
| 309 |
+
"adapter": "official_robometer_sampler_v1",
|
| 310 |
+
"temporal_strategy": strategy,
|
| 311 |
+
"canvas_size": canvas_size,
|
| 312 |
+
"letterbox_fill_rgb": [0, 0, 0],
|
| 313 |
+
}
|
| 314 |
+
)
|
| 315 |
+
result = TrajectoryContract(
|
| 316 |
+
trajectory_id=trajectory.id,
|
| 317 |
+
task=trajectory.task,
|
| 318 |
+
data_source=trajectory.data_source,
|
| 319 |
+
quality_label=trajectory.quality_label,
|
| 320 |
+
frames_uint8=np.stack(
|
| 321 |
+
[letterbox_rgb(frame, canvas_size) for frame in frames]
|
| 322 |
+
),
|
| 323 |
+
source_frame_indices=selected_indices,
|
| 324 |
+
frame_valid_mask=frame_valid,
|
| 325 |
+
progress=progress,
|
| 326 |
+
progress_distribution=progress_distribution,
|
| 327 |
+
progress_loss_mask=progress_loss_mask,
|
| 328 |
+
success_labels=success_labels,
|
| 329 |
+
success_loss_mask=frame_valid.copy(),
|
| 330 |
+
strategy=strategy,
|
| 331 |
+
metadata=metadata,
|
| 332 |
+
)
|
| 333 |
+
result.validate()
|
| 334 |
+
return result
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def make_preference_contract(
|
| 338 |
+
chosen: TrajectoryContract,
|
| 339 |
+
rejected: TrajectoryContract,
|
| 340 |
+
*,
|
| 341 |
+
strategy: str,
|
| 342 |
+
chosen_first: bool = True,
|
| 343 |
+
) -> PreferenceContract:
|
| 344 |
+
if chosen_first:
|
| 345 |
+
return PreferenceContract(chosen, rejected, 1.0, strategy)
|
| 346 |
+
return PreferenceContract(rejected, chosen, 0.0, strategy)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
class CosmosRBMCollator:
|
| 350 |
+
"""Stack the frozen contract without invoking a backbone processor."""
|
| 351 |
+
|
| 352 |
+
schema_version = "cosmos_rbm_batch_v1"
|
| 353 |
+
|
| 354 |
+
@staticmethod
|
| 355 |
+
def _stack_trajectories(items: Sequence[TrajectoryContract]) -> dict[str, Any]:
|
| 356 |
+
for item in items:
|
| 357 |
+
item.validate()
|
| 358 |
+
return {
|
| 359 |
+
"frames_uint8": torch.from_numpy(
|
| 360 |
+
np.stack([item.frames_uint8 for item in items])
|
| 361 |
+
),
|
| 362 |
+
"source_frame_indices": torch.from_numpy(
|
| 363 |
+
np.stack([item.source_frame_indices for item in items])
|
| 364 |
+
),
|
| 365 |
+
"frame_valid_mask": torch.from_numpy(
|
| 366 |
+
np.stack([item.frame_valid_mask for item in items])
|
| 367 |
+
),
|
| 368 |
+
"progress": torch.from_numpy(np.stack([item.progress for item in items])),
|
| 369 |
+
"progress_distribution": torch.from_numpy(
|
| 370 |
+
np.stack([item.progress_distribution for item in items])
|
| 371 |
+
),
|
| 372 |
+
"progress_loss_mask": torch.from_numpy(
|
| 373 |
+
np.stack([item.progress_loss_mask for item in items])
|
| 374 |
+
),
|
| 375 |
+
"success_labels": torch.from_numpy(
|
| 376 |
+
np.stack([item.success_labels for item in items])
|
| 377 |
+
),
|
| 378 |
+
"success_loss_mask": torch.from_numpy(
|
| 379 |
+
np.stack([item.success_loss_mask for item in items])
|
| 380 |
+
),
|
| 381 |
+
"trajectory_ids": [item.trajectory_id for item in items],
|
| 382 |
+
"tasks": [item.task for item in items],
|
| 383 |
+
"data_sources": [item.data_source for item in items],
|
| 384 |
+
"quality_labels": [item.quality_label for item in items],
|
| 385 |
+
"strategies": [item.strategy for item in items],
|
| 386 |
+
"metadata": [item.metadata for item in items],
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
def __call__(
|
| 390 |
+
self, samples: Sequence[TrajectoryContract | PreferenceContract]
|
| 391 |
+
) -> dict[str, Any]:
|
| 392 |
+
trajectories = [item for item in samples if isinstance(item, TrajectoryContract)]
|
| 393 |
+
preferences = [item for item in samples if isinstance(item, PreferenceContract)]
|
| 394 |
+
output: dict[str, Any] = {
|
| 395 |
+
"schema_version": self.schema_version,
|
| 396 |
+
"num_trajectories": len(trajectories),
|
| 397 |
+
"num_preferences": len(preferences),
|
| 398 |
+
}
|
| 399 |
+
if trajectories:
|
| 400 |
+
output["trajectory_inputs"] = self._stack_trajectories(trajectories)
|
| 401 |
+
if preferences:
|
| 402 |
+
output["preference_inputs"] = {
|
| 403 |
+
"trajectory_a": self._stack_trajectories(
|
| 404 |
+
[item.trajectory_a for item in preferences]
|
| 405 |
+
),
|
| 406 |
+
"trajectory_b": self._stack_trajectories(
|
| 407 |
+
[item.trajectory_b for item in preferences]
|
| 408 |
+
),
|
| 409 |
+
"preference_labels": torch.tensor(
|
| 410 |
+
[item.preference_label for item in preferences], dtype=torch.float32
|
| 411 |
+
),
|
| 412 |
+
"strategies": [item.strategy for item in preferences],
|
| 413 |
+
}
|
| 414 |
+
return output
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def validate_span_bounds(
|
| 418 |
+
bounds: Sequence[tuple[int, int]], frame_count: int, sequence_length: int
|
| 419 |
+
) -> None:
|
| 420 |
+
"""Validate the one-frame/one-nonoverlapping-token-span contract."""
|
| 421 |
+
|
| 422 |
+
if len(bounds) != frame_count:
|
| 423 |
+
raise ValueError("visual span count must match frame count")
|
| 424 |
+
previous_stop = -1
|
| 425 |
+
for start, stop in bounds:
|
| 426 |
+
if start < 0 or stop <= start or stop > sequence_length:
|
| 427 |
+
raise ValueError("visual token span is empty or out of bounds")
|
| 428 |
+
if start < previous_stop:
|
| 429 |
+
raise ValueError("visual token spans overlap")
|
| 430 |
+
previous_stop = stop
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
def worker_invariant_sample_ids(
|
| 434 |
+
ids: Iterable[str], *, seed: int, worker_count: int
|
| 435 |
+
) -> list[str]:
|
| 436 |
+
"""Simulate round-robin worker sharding while preserving global sample order."""
|
| 437 |
+
|
| 438 |
+
if worker_count <= 0:
|
| 439 |
+
raise ValueError("worker_count must be positive")
|
| 440 |
+
materialized = list(ids)
|
| 441 |
+
unique = list(dict.fromkeys(materialized))
|
| 442 |
+
if len(unique) != len(materialized):
|
| 443 |
+
raise ValueError("sample ids must be unique")
|
| 444 |
+
ranked = sorted(
|
| 445 |
+
unique,
|
| 446 |
+
key=lambda value: hashlib.sha256(f"{seed}:{value}".encode()).digest(),
|
| 447 |
+
)
|
| 448 |
+
shards = [list(enumerate(ranked))[worker::worker_count] for worker in range(worker_count)]
|
| 449 |
+
return [value for _, value in sorted(item for shard in shards for item in shard)]
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
def audit_group_overlap(train_ids: Iterable[str], eval_ids: Iterable[str]) -> set[str]:
|
| 453 |
+
return set(train_ids) & set(eval_ids)
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
def dump_batch_audit(batch: dict[str, Any], output: Path) -> None:
|
| 457 |
+
"""Write metadata and tensor shapes only; never dump trajectory pixels."""
|
| 458 |
+
|
| 459 |
+
def describe(value: Any) -> Any:
|
| 460 |
+
if isinstance(value, torch.Tensor):
|
| 461 |
+
return {"shape": list(value.shape), "dtype": str(value.dtype)}
|
| 462 |
+
if isinstance(value, dict):
|
| 463 |
+
return {key: describe(item) for key, item in value.items()}
|
| 464 |
+
return value
|
| 465 |
+
|
| 466 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 467 |
+
output.write_text(json.dumps(describe(batch), indent=2) + "\n", encoding="utf-8")
|
src/reward_heads.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared frame pooling, temporal pooling and reward heads for B1/C0/C1."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import math
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass(frozen=True)
|
| 13 |
+
class SpanPoolResult:
|
| 14 |
+
frame_features: torch.Tensor
|
| 15 |
+
frame_mask: torch.Tensor
|
| 16 |
+
span_bounds: tuple[tuple[tuple[int, int], ...], ...]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def pool_image_token_spans(
|
| 20 |
+
hidden_states: torch.Tensor,
|
| 21 |
+
input_ids: torch.Tensor,
|
| 22 |
+
image_token_id: int,
|
| 23 |
+
image_grid_thw: torch.Tensor,
|
| 24 |
+
frames_per_sample: list[int],
|
| 25 |
+
spatial_merge_size: int,
|
| 26 |
+
) -> SpanPoolResult:
|
| 27 |
+
"""Mean-pool each image-token span and return padded per-frame features."""
|
| 28 |
+
|
| 29 |
+
if hidden_states.ndim != 3 or input_ids.ndim != 2:
|
| 30 |
+
raise ValueError("hidden_states/input_ids must have shapes [B,S,D] and [B,S]")
|
| 31 |
+
if hidden_states.shape[:2] != input_ids.shape:
|
| 32 |
+
raise ValueError("hidden_states and input_ids batch/sequence dimensions differ")
|
| 33 |
+
if len(frames_per_sample) != hidden_states.shape[0] or any(
|
| 34 |
+
count <= 0 for count in frames_per_sample
|
| 35 |
+
):
|
| 36 |
+
raise ValueError(
|
| 37 |
+
"frames_per_sample must contain one positive count per batch item"
|
| 38 |
+
)
|
| 39 |
+
if sum(frames_per_sample) != image_grid_thw.shape[0]:
|
| 40 |
+
raise ValueError("frames_per_sample does not match image_grid_thw rows")
|
| 41 |
+
if spatial_merge_size <= 0:
|
| 42 |
+
raise ValueError("spatial_merge_size must be positive")
|
| 43 |
+
|
| 44 |
+
max_frames = max(frames_per_sample)
|
| 45 |
+
features = hidden_states.new_zeros(
|
| 46 |
+
(hidden_states.shape[0], max_frames, hidden_states.shape[-1])
|
| 47 |
+
)
|
| 48 |
+
frame_mask = torch.zeros(
|
| 49 |
+
(hidden_states.shape[0], max_frames),
|
| 50 |
+
dtype=torch.bool,
|
| 51 |
+
device=hidden_states.device,
|
| 52 |
+
)
|
| 53 |
+
all_bounds: list[tuple[tuple[int, int], ...]] = []
|
| 54 |
+
grid_offset = 0
|
| 55 |
+
merge_area = spatial_merge_size**2
|
| 56 |
+
|
| 57 |
+
for batch_index, frame_count in enumerate(frames_per_sample):
|
| 58 |
+
positions = torch.nonzero(
|
| 59 |
+
input_ids[batch_index] == image_token_id, as_tuple=False
|
| 60 |
+
).flatten()
|
| 61 |
+
token_offset = 0
|
| 62 |
+
bounds: list[tuple[int, int]] = []
|
| 63 |
+
for frame_index in range(frame_count):
|
| 64 |
+
grid_t, grid_h, grid_w = (
|
| 65 |
+
int(value) for value in image_grid_thw[grid_offset + frame_index]
|
| 66 |
+
)
|
| 67 |
+
raw_tokens = grid_t * grid_h * grid_w
|
| 68 |
+
if raw_tokens % merge_area:
|
| 69 |
+
raise ValueError(
|
| 70 |
+
"image token grid is not divisible by spatial merge area"
|
| 71 |
+
)
|
| 72 |
+
span_length = raw_tokens // merge_area
|
| 73 |
+
frame_positions = positions[token_offset : token_offset + span_length]
|
| 74 |
+
if frame_positions.numel() != span_length:
|
| 75 |
+
raise ValueError("image token span is empty or truncated")
|
| 76 |
+
if frame_positions.numel() > 1 and not torch.all(
|
| 77 |
+
frame_positions[1:] == frame_positions[:-1] + 1
|
| 78 |
+
):
|
| 79 |
+
raise ValueError("image token span is not contiguous")
|
| 80 |
+
start = int(frame_positions[0])
|
| 81 |
+
stop = int(frame_positions[-1]) + 1
|
| 82 |
+
features[batch_index, frame_index] = hidden_states[
|
| 83 |
+
batch_index, frame_positions
|
| 84 |
+
].mean(dim=0)
|
| 85 |
+
frame_mask[batch_index, frame_index] = True
|
| 86 |
+
bounds.append((start, stop))
|
| 87 |
+
token_offset += span_length
|
| 88 |
+
if token_offset != positions.numel():
|
| 89 |
+
raise ValueError("unassigned image tokens remain after span pooling")
|
| 90 |
+
all_bounds.append(tuple(bounds))
|
| 91 |
+
grid_offset += frame_count
|
| 92 |
+
|
| 93 |
+
return SpanPoolResult(features, frame_mask, tuple(all_bounds))
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class MaskedQueryPool(nn.Module):
|
| 97 |
+
"""Single-query attention pooling over valid ordered-frame features."""
|
| 98 |
+
|
| 99 |
+
def __init__(self, hidden_size: int) -> None:
|
| 100 |
+
super().__init__()
|
| 101 |
+
self.query = nn.Parameter(torch.empty(hidden_size))
|
| 102 |
+
nn.init.normal_(self.query, mean=0.0, std=hidden_size**-0.5)
|
| 103 |
+
|
| 104 |
+
def forward(
|
| 105 |
+
self, frame_features: torch.Tensor, frame_mask: torch.Tensor
|
| 106 |
+
) -> torch.Tensor:
|
| 107 |
+
if frame_features.ndim != 3 or frame_mask.shape != frame_features.shape[:2]:
|
| 108 |
+
raise ValueError("expected frame_features [B,T,D] and frame_mask [B,T]")
|
| 109 |
+
if not torch.all(frame_mask.any(dim=1)):
|
| 110 |
+
raise ValueError("every trajectory must contain at least one valid frame")
|
| 111 |
+
scores = torch.einsum("btd,d->bt", frame_features, self.query) / math.sqrt(
|
| 112 |
+
frame_features.shape[-1]
|
| 113 |
+
)
|
| 114 |
+
scores = scores.masked_fill(~frame_mask, torch.finfo(scores.dtype).min)
|
| 115 |
+
weights = torch.softmax(scores.float(), dim=1).to(frame_features.dtype)
|
| 116 |
+
return torch.einsum("bt,btd->bd", weights, frame_features)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class MLPHead(nn.Module):
|
| 120 |
+
def __init__(
|
| 121 |
+
self, hidden_size: int, output_size: int, dropout: float = 0.1
|
| 122 |
+
) -> None:
|
| 123 |
+
super().__init__()
|
| 124 |
+
bottleneck = hidden_size // 2
|
| 125 |
+
self.net = nn.Sequential(
|
| 126 |
+
nn.Linear(hidden_size, bottleneck),
|
| 127 |
+
nn.LayerNorm(bottleneck),
|
| 128 |
+
nn.GELU(),
|
| 129 |
+
nn.Dropout(dropout),
|
| 130 |
+
nn.Linear(bottleneck, output_size),
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
def forward(self, inputs: torch.Tensor) -> torch.Tensor:
|
| 134 |
+
return self.net(inputs)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class SharedRewardHeads(nn.Module):
|
| 138 |
+
"""The identical topology used for frozen probes and matched-LoRA runs."""
|
| 139 |
+
|
| 140 |
+
def __init__(
|
| 141 |
+
self, hidden_size: int, progress_bins: int = 10, dropout: float = 0.1
|
| 142 |
+
) -> None:
|
| 143 |
+
super().__init__()
|
| 144 |
+
self.temporal_pool = MaskedQueryPool(hidden_size)
|
| 145 |
+
self.progress_head = MLPHead(hidden_size, progress_bins, dropout)
|
| 146 |
+
self.success_head = MLPHead(hidden_size, 1, dropout)
|
| 147 |
+
self.preference_head = MLPHead(hidden_size, 1, dropout)
|
| 148 |
+
|
| 149 |
+
def forward(
|
| 150 |
+
self, frame_features: torch.Tensor, frame_mask: torch.Tensor
|
| 151 |
+
) -> dict[str, torch.Tensor]:
|
| 152 |
+
trajectory = self.temporal_pool(frame_features, frame_mask)
|
| 153 |
+
return {
|
| 154 |
+
"progress_logits": self.progress_head(frame_features),
|
| 155 |
+
"success_logits": self.success_head(frame_features).squeeze(-1),
|
| 156 |
+
"trajectory_features": trajectory,
|
| 157 |
+
"trajectory_scores": self.preference_head(trajectory).squeeze(-1),
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
def preference_logit(
|
| 161 |
+
self,
|
| 162 |
+
frame_features_a: torch.Tensor,
|
| 163 |
+
frame_mask_a: torch.Tensor,
|
| 164 |
+
frame_features_b: torch.Tensor,
|
| 165 |
+
frame_mask_b: torch.Tensor,
|
| 166 |
+
) -> torch.Tensor:
|
| 167 |
+
score_a = self.preference_head(
|
| 168 |
+
self.temporal_pool(frame_features_a, frame_mask_a)
|
| 169 |
+
).squeeze(-1)
|
| 170 |
+
score_b = self.preference_head(
|
| 171 |
+
self.temporal_pool(frame_features_b, frame_mask_b)
|
| 172 |
+
).squeeze(-1)
|
| 173 |
+
return score_a - score_b
|
tests/conftest.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
EXPERIMENT_ROOT = Path(__file__).resolve().parents[1]
|
| 8 |
+
if str(EXPERIMENT_ROOT) not in sys.path:
|
| 9 |
+
sys.path.insert(0, str(EXPERIMENT_ROOT))
|
tests/test_data_contract.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pytest
|
| 5 |
+
import torch
|
| 6 |
+
from types import SimpleNamespace
|
| 7 |
+
|
| 8 |
+
from src.data_contract import (
|
| 9 |
+
CosmosRBMCollator,
|
| 10 |
+
adapt_official_trajectory,
|
| 11 |
+
audit_group_overlap,
|
| 12 |
+
deterministic_frame_indices,
|
| 13 |
+
make_preference_contract,
|
| 14 |
+
prepare_trajectory,
|
| 15 |
+
validate_span_bounds,
|
| 16 |
+
worker_invariant_sample_ids,
|
| 17 |
+
)
|
| 18 |
+
from src.reward_heads import SharedRewardHeads
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def synthetic_frames(count: int, height: int = 12, width: int = 20) -> np.ndarray:
|
| 22 |
+
frames = np.empty((count, height, width, 3), dtype=np.uint8)
|
| 23 |
+
for index in range(count):
|
| 24 |
+
frames[index].fill(index)
|
| 25 |
+
return frames
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def prepared(
|
| 29 |
+
*,
|
| 30 |
+
strategy: str = "forward",
|
| 31 |
+
quality: str = "successful",
|
| 32 |
+
frame_count: int = 13,
|
| 33 |
+
trajectory_id: str = "trajectory-a",
|
| 34 |
+
):
|
| 35 |
+
return prepare_trajectory(
|
| 36 |
+
trajectory_id=trajectory_id,
|
| 37 |
+
frames=synthetic_frames(frame_count),
|
| 38 |
+
task="insert cable",
|
| 39 |
+
data_source="unit_test",
|
| 40 |
+
quality_label=quality,
|
| 41 |
+
strategy=strategy,
|
| 42 |
+
canvas_size=16,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def test_forward_progress_is_nondecreasing_and_sampling_is_exact() -> None:
|
| 47 |
+
item = prepared()
|
| 48 |
+
expected, _ = deterministic_frame_indices(13, 8)
|
| 49 |
+
assert np.array_equal(item.source_frame_indices, expected)
|
| 50 |
+
assert np.all(np.diff(item.progress) >= 0)
|
| 51 |
+
assert item.frames_uint8.shape == (8, 16, 16, 3)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def test_reverse_keeps_all_frame_fields_synchronized() -> None:
|
| 55 |
+
forward = prepared()
|
| 56 |
+
reverse = prepared(strategy="reverse")
|
| 57 |
+
assert np.array_equal(reverse.source_frame_indices, forward.source_frame_indices[::-1])
|
| 58 |
+
assert np.array_equal(reverse.progress, forward.progress[::-1])
|
| 59 |
+
assert np.array_equal(reverse.frame_valid_mask, forward.frame_valid_mask[::-1])
|
| 60 |
+
assert np.array_equal(reverse.success_labels, forward.success_labels[::-1])
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def test_rewind_has_a_real_progress_drop() -> None:
|
| 64 |
+
rewind = prepared(strategy="rewind")
|
| 65 |
+
assert np.any(np.diff(rewind.progress) < 0)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def test_different_task_is_zero_with_supervision() -> None:
|
| 69 |
+
item = prepared(strategy="different_task", quality="failure")
|
| 70 |
+
assert np.all(item.progress == 0)
|
| 71 |
+
assert np.array_equal(item.progress_loss_mask, item.frame_valid_mask)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_preference_swap_flips_label_and_bradley_terry_logit() -> None:
|
| 75 |
+
chosen = prepared(trajectory_id="chosen")
|
| 76 |
+
rejected = prepared(strategy="rewind", trajectory_id="rejected")
|
| 77 |
+
pair = make_preference_contract(chosen, rejected, strategy="rewind")
|
| 78 |
+
swapped = pair.swapped()
|
| 79 |
+
assert swapped.preference_label == 0.0
|
| 80 |
+
assert swapped.trajectory_a.trajectory_id == "rejected"
|
| 81 |
+
|
| 82 |
+
torch.manual_seed(7)
|
| 83 |
+
heads = SharedRewardHeads(hidden_size=8, dropout=0.0)
|
| 84 |
+
features_a = torch.randn(1, 8, 8)
|
| 85 |
+
features_b = torch.randn(1, 8, 8)
|
| 86 |
+
mask = torch.ones(1, 8, dtype=torch.bool)
|
| 87 |
+
ab = heads.preference_logit(features_a, mask, features_b, mask)
|
| 88 |
+
ba = heads.preference_logit(features_b, mask, features_a, mask)
|
| 89 |
+
assert torch.allclose(ab, -ba)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def test_padding_never_enters_loss_or_pooling() -> None:
|
| 93 |
+
item = prepared(frame_count=3)
|
| 94 |
+
batch = CosmosRBMCollator()([item])["trajectory_inputs"]
|
| 95 |
+
assert batch["frame_valid_mask"].tolist() == [
|
| 96 |
+
[True, True, True, False, False, False, False, False]
|
| 97 |
+
]
|
| 98 |
+
assert not batch["progress_loss_mask"][0, 3:].any()
|
| 99 |
+
assert not batch["success_loss_mask"][0, 3:].any()
|
| 100 |
+
|
| 101 |
+
pool_features = torch.randn(1, 8, 4)
|
| 102 |
+
heads = SharedRewardHeads(hidden_size=4, dropout=0.0)
|
| 103 |
+
score_a = heads(pool_features, batch["frame_valid_mask"])["trajectory_scores"]
|
| 104 |
+
pool_features[:, 3:] = 1000
|
| 105 |
+
score_b = heads(pool_features, batch["frame_valid_mask"])["trajectory_scores"]
|
| 106 |
+
assert torch.allclose(score_a, score_b)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def test_failure_does_not_receive_dense_progress() -> None:
|
| 110 |
+
failure = prepared(quality="failure")
|
| 111 |
+
assert not failure.progress_loss_mask.any()
|
| 112 |
+
assert not failure.success_labels.any()
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def test_official_sampler_adapter_preserves_labels_and_indices() -> None:
|
| 116 |
+
official_progress = np.linspace(0.0, 1.0, 8, dtype=np.float32)
|
| 117 |
+
official_success = [0.0] * 7 + [1.0]
|
| 118 |
+
trajectory = SimpleNamespace(
|
| 119 |
+
id="official-a",
|
| 120 |
+
task="insert cable",
|
| 121 |
+
data_source="official_unit_test",
|
| 122 |
+
quality_label="successful",
|
| 123 |
+
frames=synthetic_frames(8),
|
| 124 |
+
target_progress=official_progress.tolist(),
|
| 125 |
+
success_label=official_success,
|
| 126 |
+
predict_last_frame_mask=[1.0] * 8,
|
| 127 |
+
metadata={
|
| 128 |
+
"selected_frame_indices": [0, 4, 9, 13, 18, 22, 27, 31],
|
| 129 |
+
"frame_valid_mask": [True] * 8,
|
| 130 |
+
},
|
| 131 |
+
)
|
| 132 |
+
adapted = adapt_official_trajectory(
|
| 133 |
+
trajectory,
|
| 134 |
+
strategy="forward",
|
| 135 |
+
progress_supervised=True,
|
| 136 |
+
canvas_size=16,
|
| 137 |
+
)
|
| 138 |
+
assert np.allclose(adapted.progress, official_progress)
|
| 139 |
+
assert adapted.success_labels.tolist() == official_success
|
| 140 |
+
assert adapted.source_frame_indices.tolist() == trajectory.metadata[
|
| 141 |
+
"selected_frame_indices"
|
| 142 |
+
]
|
| 143 |
+
assert adapted.metadata["adapter"] == "official_robometer_sampler_v1"
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def test_all_backbones_consume_the_same_frozen_indices() -> None:
|
| 147 |
+
frozen, _ = deterministic_frame_indices(32, 8)
|
| 148 |
+
for _backbone in ("qwen3-vl", "cosmos3-edge", "frozen-cosmos"):
|
| 149 |
+
item = prepared(frame_count=32)
|
| 150 |
+
assert np.array_equal(item.source_frame_indices, frozen)
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def test_visual_span_contract() -> None:
|
| 154 |
+
validate_span_bounds([(1, 4), (8, 11), (12, 15)], 3, 16)
|
| 155 |
+
with pytest.raises(ValueError, match="overlap"):
|
| 156 |
+
validate_span_bounds([(1, 5), (4, 8)], 2, 10)
|
| 157 |
+
with pytest.raises(ValueError, match="count"):
|
| 158 |
+
validate_span_bounds([(1, 5)], 2, 10)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def test_worker_count_does_not_change_seeded_sampling_list() -> None:
|
| 162 |
+
ids = [f"trajectory-{index}" for index in range(100)]
|
| 163 |
+
expected = worker_invariant_sample_ids(ids, seed=17, worker_count=1)
|
| 164 |
+
assert worker_invariant_sample_ids(ids, seed=17, worker_count=2) == expected
|
| 165 |
+
assert worker_invariant_sample_ids(ids, seed=17, worker_count=8) == expected
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def test_group_overlap_gate() -> None:
|
| 169 |
+
assert audit_group_overlap(["a", "b", "c"], ["d", "e"]) == set()
|
| 170 |
+
assert audit_group_overlap(["a", "b"], ["b", "c"]) == {"b"}
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def test_one_hundred_trajectories_cpu_collate_and_backward() -> None:
|
| 174 |
+
item = prepared()
|
| 175 |
+
batch = CosmosRBMCollator()([item] * 100)["trajectory_inputs"]
|
| 176 |
+
assert batch["frames_uint8"].shape == (100, 8, 16, 16, 3)
|
| 177 |
+
features = torch.randn(8, 8, 16, requires_grad=True)
|
| 178 |
+
heads = SharedRewardHeads(hidden_size=16, dropout=0.0)
|
| 179 |
+
output = heads(features, batch["frame_valid_mask"][:8])
|
| 180 |
+
loss = output["progress_logits"].float().mean() + output[
|
| 181 |
+
"success_logits"
|
| 182 |
+
].float().mean()
|
| 183 |
+
loss.backward()
|
| 184 |
+
assert features.grad is not None and torch.isfinite(features.grad).all()
|