フィジカルAI研究室を作る Log#6 | π0.5 で mechArm 270 を初めて動かした ― ブロックの真上まで来て、爪がブロックに乗って止まった
お手本を19回見せて覚えさせた AI に、机の上のロボットアームを初めて動かさせました。AI は上のカメラの画像と関節の角度を見て、「次の1秒でどう動くか」を返します。それをアームに送ると、アームは HOME(たたまずに立てた待機姿勢)から赤いブロックの方へ腕を伸ばし、6回の指令でブロックの真上まで来ました。肘の角度はお手本がブロックをつかんだ瞬間とほぼ同じ −34.5° でした。そこから先、手首だけが指令した角度まで動かず、アームは止まりました。
止まった原因は、関節の角度の記録だけでは分かりませんでした。手首は3°動いて止まり、その後は指令を受け付けません。この記録を見て、一緒に作業していた AI(Claude)は「手首のサーボが熱を持った」と見立て、それが外れると「1回の指令が小さすぎて、重さに負けた」と見立て直しました。どちらも外れでした。スマートフォンで撮った動画を見ると、2本の爪の間に入るはずのブロックが、手前の爪の真下にありました。手首を動かしてグリッパーの先を下げようとして、爪がブロックを上から押していたのです。
ロボットに AI を載せると、記録に残るのは「何度に動けと言って、何度まで動いたか」です。何かに当たって止まったのか、力が足りずに止まったのかは、数字の上では同じに見えます。この回は、その区別を数字だけで付けようとして2回間違えた記録でもあります。
使った AI は、Physical Intelligence 社が公開しているロボット用のモデル π0.5(パイレイテンゴ)です。カメラの画像と指示文から、関節をどう動かすかを出します。手元の小型アーム mechArm 270(関節6つ)に合わせて、ブロックを箱に入れるお手本19本で追加の学習(ファインチューニング)をしたものを使っています。学習の様子は前の記録に書きました。
やったことの全体は次のとおりです。
| やったこと | なぜ | 結果 |
|---|---|---|
| 学習したモデル(約6GB)を新しく借りた GPU に送った | 前に借りた GPU は、中身ごと消していたため | 1本ずつ送ると1秒に約1MB。6本同時に送って約7倍に。途中で切れたファイルが1つあり、送り直した |
| 予測を画面に出すだけの試し(ドライラン) | 送る前に変な動きをしないか確かめるため | 最初の1秒は、お手本の動き出しとほぼ同じ |
| π0.5 の指令でアームを4回動かした | 実機で初めて、どこまで進めるかを見るため | 4回とも、つかむ前に止まった。最後の回はブロックの真上まで来た |
| 止まった原因を調べた | 次に何を直すかを決めるため | 熱でも指令の大きさでもなく、爪がブロックの上に乗っていた。位置が爪1本分(2〜3cm)ずれていた |
| 学習を 2,000 ステップから 5,000 ステップまで足した | 位置のずれを減らすため。loss はまだ下がり続けていた | 学習中(約5時間半) |
GPU は前回と同じく、Runpod というサービスで NVIDIA A40 を1時間約 0.6 ドルで借りています。朝にモデルを送って推論サーバーを起動したあと、実機を動かしたのは夕方でした。その間の約8時間、GPU は何もせずに料金だけがかかっています。
この回の作業も、Claude と一緒に進めました。手順とコマンド、記録の分析は Claude が出し、アームの操作、目視の判定、動画の撮影、Runpod の画面操作はこちらで行っています。ここから下は、使ったコマンド・コード・出力を全量で載せた作業ログです。ログの中の Pod の IP アドレスとポート番号は、<POD_IP> と <SSH_PORT> に置き換えています。
作業ログ ― 新しい Pod を作り、openpi を入れ直した
前回の Pod(Runpod で借りた GPU マシン)は、朝 7 時 47 分に止めて削除しました。Volume(Pod を消しても残る保存領域)を付けていなかったので、openpi(π0.5 を動かすための公開コード)も学習したモデルも、Pod と一緒に消えています。モデルは削除の前に KIOXIA(Mac につないだ外付け SSD)へ持ち帰ってあります。
| 場所 | 中身 |
|---|---|
KIOXIA checkpoints/pi05_mecharm_lora_v1/1999/ | 学習したモデル一式 9.0GB |
その中の params/ | 推論に使う重み 6.0GB |
その中の assets/ | 正規化の値(関節角を学習しやすい範囲に直すための統計)2.0MB |
その中の train_state/ | 学習を続けるときだけ使う状態 3.0GB |
推論サーバーの起動に必要なのは params/ と assets/ だけです。前回のサーバーのログでも、読み込んでいたのはこの2つでした。
INFO:absl:Restoring checkpoint from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params.
INFO:absl:[thread=MainThread] Failed to get flag value for EXPERIMENTAL_ORBAX_USE_DISTRIBUTED_PROCESS_ID.
INFO:absl:[process=0] /jax/checkpoint/read/bytes_per_sec: 1.1 GiB/s (total bytes: 6.3 GiB) (time elapsed: 5 seconds) (per-host)
INFO:absl:Finished restoring checkpoint in 5.62 seconds from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params.
INFO:root:Loaded norm stats from /workspace/openpi/assets/pi05_mecharm_lora/local/mecharm_redblock_box_v1
INFO:root:Loaded norm stats from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/assets/local/mecharm_redblock_box_v1
INFO:root:Creating server (host: 896724c37bbf, ip: 192.168.4.2)
INFO:websockets.server:server listening on 0.0.0.0:8000
新しい Pod は、前回と同じ A40・Runpod Pytorch(Ubuntu 24.04)で作りました。今回は学習しない予定だったので、ディスクは 80GB を指定しています。作成から約5分、「Initializing your pod…」のまま止まって見えました。

起動したあと、Mac から SSH で入りました。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % ssh root@<POD_IP> -p <SSH_PORT> -i ~/.ssh/id_ed25519
The authenticity of host '[<POD_IP>]:<SSH_PORT> ([<POD_IP>]:<SSH_PORT>)' can't be established.
ED25519 key fingerprint is: SHA256:RPF2JA93gK7GSdYkfcH7LxtZg5QinaeYPvmIBUgEbUg
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[<POD_IP>]:<SSH_PORT>' (ED25519) to the list of known hosts.
Welcome to Ubuntu 24.04.3 LTS (GNU/Linux 6.8.0-139-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
_____ _
| __ \ | |
| |__) | _ _ __ _ __ ___ __| |
| _ / | | | '_ \| '_ \ / _ \ / _` |
| | \ \ |_| | | | | |_) | (_) | (_| |
|_| \_\__,_|_| |_| .__/ \___/ \__,_|
| |
|_|
For detailed documentation and guides, please visit:
https://docs.runpod.io/ and https://blog.runpod.io/
root@b07c796f894c:/# apt-get update && apt-get install -y tmux
mkdir -p /workspace/logs /workspace/r2r2r /workspace/ckpt
tmux new -s lab
Get:1 http://security.ubuntu.com/ubuntu noble-security InRelease [126 kB]
Get:2 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64 InRelease [1578 B]
Get:3 http://security.ubuntu.com/ubuntu noble-security/main amd64 Packages [1409 kB]
Get:4 http://archive.ubuntu.com/ubuntu noble InRelease [256 kB]
Get:5 http://security.ubuntu.com/ubuntu noble-security/universe amd64 Packages [1549 kB]
Get:6 http://security.ubuntu.com/ubuntu noble-security/restricted amd64 Packages [2122 kB]
Get:7 http://security.ubuntu.com/ubuntu noble-security/multiverse amd64 Packages [55.6 kB]
Get:8 https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64 Packages [1919 kB]
Get:9 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu noble InRelease [17.8 kB]
Get:10 https://ppa.launchpadcontent.net/deadsnakes/ppa/ubuntu noble/main amd64 Packages [60.1 kB]
Get:11 http://archive.ubuntu.com/ubuntu noble-updates InRelease [126 kB]
Get:12 http://archive.ubuntu.com/ubuntu noble-backports InRelease [126 kB]
Get:13 http://archive.ubuntu.com/ubuntu noble/multiverse amd64 Packages [331 kB]
Get:14 http://archive.ubuntu.com/ubuntu noble/main amd64 Packages [1808 kB]
Get:15 http://archive.ubuntu.com/ubuntu noble/universe amd64 Packages [19.3 MB]
Get:16 http://archive.ubuntu.com/ubuntu noble/restricted amd64 Packages [117 kB]
Get:17 http://archive.ubuntu.com/ubuntu noble-updates/main amd64 Packages [1729 kB]
Get:18 http://archive.ubuntu.com/ubuntu noble-updates/restricted amd64 Packages [2231 kB]
Get:19 http://archive.ubuntu.com/ubuntu noble-updates/multiverse amd64 Packages [61.7 kB]
Get:20 http://archive.ubuntu.com/ubuntu noble-updates/universe amd64 Packages [2168 kB]
Get:21 http://archive.ubuntu.com/ubuntu noble-backports/main amd64 Packages [49.0 kB]
Get:22 http://archive.ubuntu.com/ubuntu noble-backports/universe amd64 Packages [36.0 kB]
Get:23 http://archive.ubuntu.com/ubuntu noble-backports/multiverse amd64 Packages [669 B]
Fetched 35.6 MB in 3s (12.7 MB/s)
Reading package lists... Done
W: https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/InRelease: Key is stored in legacy trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in apt-key(8) for details.
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
tmux is already the newest version (3.4-1ubuntu0.1).
0 upgraded, 0 newly installed, 0 to remove and 274 not upgraded.
tmux(SSH が切れても中の作業が止まらない画面)の中で、openpi を入れました。手順書 pi05/RUNPOD_TRAIN.md の「2. Pod の準備」と同じです。
cd /workspace
git clone --recurse-submodules https://github.com/Physical-Intelligence/openpi.git
cd openpi && git checkout 215abfb
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
GIT_LFS_SKIP_SMUDGE=1 uv sync
uv sync の最後の部分です。8時23分には終わっていました。
+ triton==3.3.1
+ typeguard==4.4.2
+ typing-extensions==4.13.2
+ typing-inspect==0.9.0
+ typing-inspection==0.4.1
+ tyro==0.9.22
+ tzdata==2025.2
+ urllib3==2.4.0
+ virtualenv==20.31.2
+ wadler-lindig==0.1.6
+ wandb==0.19.11
+ wcwidth==0.2.13
+ websockets==15.0.1
+ werkzeug==3.1.3
+ widgetsnbextension==4.0.14
+ wrapt==1.14.1
+ xxhash==3.5.0
+ yarl==1.20.0
+ zarr==3.0.8
+ zipp==3.22.0
root@b07c796f894c:/workspace/openpi#
続けて、mechArm 用の設定を openpi に登録しました(前回の Log と同じスクリプト)。
cd /workspace/openpi
python3 /workspace/r2r2r/pi05/openpi_addon/install_addon.py /workspace/openpi
Mac から Pod へ、1本ずつ送ると1秒に約1MBしか出なかった
openpi を入れている間に、Mac からモデルとスクリプトを送りました。Pod の中にはまだ openpi のフォルダがない時点なので、いったん /workspace/ckpt/1999/ に置いて、あとで移す形にしています。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "mkdir -p /workspace/ckpt/1999 /workspace/r2r2r /workspace/logs"
scp -P <SSH_PORT> -i ~/.ssh/id_ed25519 -r ~/r2r2r-scripts/pi05 root@<POD_IP>:/workspace/r2r2r/
cd /Volumes/KIOXIA/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/1999 && scp -P <SSH_PORT> -i ~/.ssh/id_ed25519 -r _CHECKPOINT_METADATA assets params root@<POD_IP>:/workspace/ckpt/1999/
convert_to_lerobot.py 100% 2873 15.3KB/s 00:00
run_policy_client.py 100% 13KB 70.0KB/s 00:00
RUNPOD_TRAIN.md 100% 9229 49.0KB/s 00:00
install_addon.py 100% 4786 25.4KB/s 00:00
mecharm_policy.py 100% 2321 12.3KB/s 00:00
build_demo_episodes.cpython-310.pyc 100% 11KB 41.0KB/s 00:00
policy_ws.cpython-312.pyc 100% 4580 24.7KB/s 00:00
build_demo_episodes.py 100% 11KB 58.9KB/s 00:00
policy_ws.py 100% 2778 14.3KB/s 00:00
_CHECKPOINT_METADATA 100% 426 2.3KB/s 00:00
norm_stats.json 100% 1772 9.3KB/s 00:00
5d6ed67e7fc853e56aa5c1a6bbc51df9 100% 213 1.1KB/s 00:00
4c84bf2843a5c4f4f66e088d72c27478 100% 480 2.5KB/s 00:00
44a8c8e056c1064be41720c247adbc74 100% 496 2.7KB/s 00:00
7b5c35152283df0e1551b18ce9aa5945 100% 519 2.8KB/s 00:00
cff6dee763cb540c3725a4b6f07f8aff 100% 757 4.1KB/s 00:00
4a8a52cbc15bb655383c2076de7f58b6 100% 856 4.6KB/s 00:00
cb951c6e883921e36908b6d25cb385bf 100% 938 5.0KB/s 00:00
b6ffa78be338d330e86eec9ff974753a 100% 979 5.2KB/s 00:00
02a42b2ee1a06c57b0b8d20f96ee12e8 100% 1033 5.5KB/s 00:00
13686640b6f8adbe756c516bb6953ca6 100% 1083 3.9KB/s 00:00
f70a89f62633d2bcd42d31c8b5d4bcb3 100% 1144 6.1KB/s 00:00
0704688aad8ecb7e100c849597eb3b5f 100% 1197 6.4KB/s 00:00
1dd7747aa329f2b4f833186fad142f66 100% 1244 6.6KB/s 00:00
550b1b6d334f9817f4bd166dab4198c7 100% 1259 6.7KB/s 00:00
3fb686396ed3cf9609c7baea4ef248f0 100% 1315 7.0KB/s 00:00
b3830e7a3e9b8382a3738bf3a450574f 100% 1344 7.2KB/s 00:00
1cc3269b72d92fa3ede82ccf6031d4ba 100% 2046 10.8KB/s 00:00
d6aef88b46035ff22a9f8739f24a4b8c 100% 9664 35.4KB/s 00:00
de66dd5ca49f2a5598b917268f2ff461 100% 90MB 1.1MB/s 01:23
645918e3a7395fde54fd82bed64c5e90 100% 61MB 1.0MB/s 00:59
1070f9c6644d4d6db4e149c17303af93 100% 42MB 1.1MB/s 00:37
5acedd2fef6bf0fb4fc2bd8ab6ac8c4a 100% 17MB 743.6KB/s 00:22
2bfb9e124fda2cfe14f83297383e929e 100% 56MB 957.5KB/s 01:00
30951bbf8ff8a45bea4ac4a510b56532 100% 33MB 847.1KB/s 00:40
4310af4417b67646143754732ff31894 100% 127MB 932.7KB/s 02:19
30b8103530fc940275fced8c2ab54146 100% 183MB 863.3KB/s 03:37
e428b2f4fc90a11598c50e35bc3ee2ef 71% 171MB 1.0MB/s 01:06 ETA
1秒に約1MB です。残りは約5.4GB あるので、このままだと約1時間半かかります。重みのフォルダの中身は41個のファイルで、10MB を超えるものが17個、一番大きいものは約950MB でした。
cd $HOME/mnt/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/1999 && find params -type d; find params -type f | wc -l; find params -type f -size +10M | wc -l; find params -type f -exec stat -c '%s %n' {} + | sort -n | tail -3
params
params/ocdbt.process_0
params/ocdbt.process_0/d
params/array_metadatas
params/d
41
17
945626207 params/ocdbt.process_0/d/7eb84d232903ea9e86dbb890f3c326bb
945630499 params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf
945880462 params/ocdbt.process_0/d/ab09b5deb014ad86f3e5ca2faaa039b4
(このコマンドは Claude が Mac 連携の shell で動かしたものです。その shell では、KIOXIA の r2r2r-data が $HOME/mnt/r2r2r-data に見えています。)
秋田から Runpod のアメリカのデータセンターまでは距離があり、1本の接続では、返事を待つ時間のぶん速度が出にくくなります。そこで、1本目の送信を Ctrl+C で止めて、ファイルごとに6本同時に送るように切り替えました。Pod にすでに同じ大きさで届いているファイルは飛ばします。
cd /Volumes/KIOXIA/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/1999
ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "cd /workspace/ckpt/1999 && mkdir -p params/d params/ocdbt.process_0/d params/array_metadatas && find params -type f -printf '%s %p\n'" > /tmp/remote_sizes.txt
find params -type f | while read f; do s=$(stat -f %z "$f"); grep -qx "$s $f" /tmp/remote_sizes.txt || echo "$f"; done > /tmp/todo.txt
wc -l < /tmp/todo.txt
cat /tmp/todo.txt | xargs -P 6 -I{} scp -q -P <SSH_PORT> -i ~/.ssh/id_ed25519 {} root@<POD_IP>:/workspace/ckpt/1999/{}
-q を付けて進み具合の表示を消しているので、実行中は何も出ません。別のターミナルで、Pod に届いた量を1分おきに見ました。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "du -sh /workspace/ckpt/1999"
1.1G /workspace/ckpt/1999
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ %
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "du -sh /workspace/ckpt/1999"
1.5G /workspace/ckpt/1999
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ %
1分で約400MB、1本のときの約7倍です。回線そのものの上限ではなく、1本の接続の速さが頭打ちになっていたことになります。
送り終わる前にフォルダを移したため、重みのファイルが1つ途中で切れていた
Claude は、Pod で du -sh が「6.0G 前後」になったら送り終わり、と案内していました。6.0G と出たのを見て、Pod でモデルを openpi のフォルダへ移し、推論サーバーを起動しました。
mkdir -p checkpoints/pi05_mecharm_lora/mecharm_v1 && mv /workspace/ckpt/1999 checkpoints/pi05_mecharm_lora/mecharm_v1/
mkdir -p assets/pi05_mecharm_lora/local && cp -r checkpoints/pi05_mecharm_lora/mecharm_v1/1999/assets/local/mecharm_redblock_box_v1 assets/pi05_mecharm_lora/local/
uv run scripts/serve_policy.py policy:checkpoint --policy.config=pi05_mecharm_lora --policy.dir=checkpoints/pi05_mecharm_lora/mecharm_v1/1999 2>&1 | tee /workspace/logs/serve.log
サーバーは起動の途中でエラーになり、長い表示の最後だけが画面に残りました。
},
'driver': 'ocdbt',
'path': 'params.PaliGemma.llm.embedder.input_embedding.value/',
},
'metadata': {
'chunks': [257152, 2048],
'compressor': {'id': 'zstd', 'level': 1},
'dimension_separator': '.',
'dtype': 'bfloat16',
'fill_value': None,
'filters': None,
'order': 'C',
'shape': [257152, 2048],
'zarr_format': 2,
},
'recheck_cached_data': False,
'recheck_cached_metadata': False,
},
'context': {
'cache_pool': {},
'cache_pool#ocdbt': {'total_bytes_limit': 100000000},
'data_copy_concurrency': {},
'file_io_concurrency': {'limit': 128},
'file_io_locking': {},
'file_io_memmap': False,
'file_io_sync': True,
'ocdbt_coordinator': {},
},
'driver': 'cast',
'dtype': 'bfloat16',
'transform': {
'input_exclusive_max': [[257152], [2048]],
'input_inclusive_min': [0, 0],
},
})>.
原因の行は上のほうにあるので、サーバーのログから探しました。
root@b07c796f894c:/workspace/openpi# grep -n -i -m8 "error\|Traceback\|NOT_FOUND\|DATA_LOSS\|No such" /workspace/logs/serve.log | cut -c1-300
4:INFO:2026-10-16 23:41:42,128:jax._src.xla_bridge:925: Unable to initialize backend 'tpu': INTERNAL: Failed to open libtpu.so: libtpu.so: cannot open shared object file: No such file or directory
5:INFO:jax._src.xla_bridge:Unable to initialize backend 'tpu': INTERNAL: Failed to open libtpu.so: libtpu.so: cannot open shared object file: No such file or directory
10:ERROR:asyncio:unhandled exception during asyncio.run() shutdown
11:task: <Task finished name='Task-274' coro=<_read_array_index_and_device_put() done, defined at /workspace/openpi/.venv/lib/python3.11/site-packages/orbax/checkpoint/_src/serialization/serialization.py:393> exception=Exception("Encountered error while reading array index: (slice(0, 18, 1), slice(0
12:Traceback (most recent call last):
18:ValueError: OUT_OF_RANGE: Error reading "params.PaliGemma.llm.embedder.input_embedding.value/0.0" in OCDBT database at local file "/workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params/": Requested byte range [0, 828930702) is not valid for value of size 699540480 [source locatio
22:Traceback (most recent call last):
46:Exception: Encountered error while reading array index: (slice(0, 257152, 1), slice(0, 2048, 1)). See full TensorStore details: <bound method PyCapsule.spec of TensorStore({
root@b07c796f894c:/workspace/openpi#
18行目が原因です。829MB あるはずのデータを読もうとしたら、ファイルが 700MB しかなかった、という意味です。Mac と Pod で、ファイルごとの大きさを比べました。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "du -sh /workspace/ckpt/1999"
6.0G /workspace/ckpt/1999
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % cd /Volumes/KIOXIA/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/1999
ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "cd /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999 && find params assets _CHECKPOINT_METADATA -type f -printf '%s %p\n'" > /tmp/remote_sizes2.txt
find params assets _CHECKPOINT_METADATA -type f | while read f; do s=$(stat -f %z "$f"); grep -qx "$s $f" /tmp/remote_sizes2.txt || echo "不一致: $f"; done; echo "確認おわり"
ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "ls /workspace/ckpt/1999 2>&1; du -sh /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999"
不一致: params/ocdbt.process_0/d/7eb84d232903ea9e86dbb890f3c326bb
不一致: params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf
確認おわり
ls: cannot access '/workspace/ckpt/1999': No such file or directory
5.8G /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 %
1行目の 6.0G は、移す前に見たときの表示です。du -sh はディスク上の使用量を丸めて出すので、2つのファイルが送りかけでも「6.0G」と出ていました。合計の大きさで「送り終わったか」を判断した案内が間違いでした。ファイルごとに大きさを比べるべきでした。
違っていた2つは、まだ送っている最中かどうかを確かめました。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 % pgrep -fl "scp -q" || echo "送信は終わっています"
5365 xargs -P 6 -I{} scp -q -P <SSH_PORT> -i /Users/shimazueiichi/.ssh/id_ed25519 {} root@<POD_IP>:/workspace/ckpt/1999/{}
5662 scp -q -P <SSH_PORT> -i /Users/shimazueiichi/.ssh/id_ed25519 params/ocdbt.process_0/d/7eb84d232903ea9e86dbb890f3c326bb root@<POD_IP>:/workspace/ckpt/1999/params/ocdbt.process_0/d/7eb84d232903ea9e86dbb890f3c326bb
5790 scp -q -P <SSH_PORT> -i /Users/shimazueiichi/.ssh/id_ed25519 params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf root@<POD_IP>:/workspace/ckpt/1999/params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 %
送っている途中でした。Linux では、書き込み中のファイルが入ったフォルダを移しても、開いているファイルへの書き込みは移した先で続きます。移した先で、大きさが増えているかを見ました。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 % ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "ls -l /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params/ocdbt.process_0/d/7eb84d232903ea9e86dbb890f3c326bb /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf"
-rwx------ 1 root root 885457920 Oct 16 23:47 /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params/ocdbt.process_0/d/7eb84d232903ea9e86dbb890f3c326bb
-rwx------ 1 root root 724346880 Oct 16 23:47 /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 %
更新時刻(Pod の時計で 23:47、日本時間 8:47)が今になっていて、どちらも増えていました。残りは 60MB と 220MB です。
Mac 側のネットワークが一瞬切れ、送りかけのファイルがもう1つ切れた
送り終わるのを待っている間に、Pod に入っていた SSH が切れました。
root@b07c796f894c:/workspace/openpi# cd /workspaceRead from remote host <POD_IP>: Can't assign requested address
Connection to <POD_IP> closed.
client_loop: send disconnect: Broken pipe "b07c796f894c" 23:48 16-Oct-26
Can't assign requested address は、Mac 側のネットワークが一瞬切れたときに出る表示です。tmux の中のサーバーは動いていません(起動に失敗したままです)。同じときに、送っていた scp の1本も切れていました。もう一度ファイルごとに比べると、違っているのは1つだけになっていました。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % pgrep -fl "scp -q" || echo "送信は終わっています"
cd /Volumes/KIOXIA/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/1999
ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "cd /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999 && find params assets _CHECKPOINT_METADATA -type f -printf '%s %p\n'" > /tmp/remote_sizes2.txt
find params assets _CHECKPOINT_METADATA -type f | while read f; do s=$(stat -f %z "$f"); grep -qx "$s $f" /tmp/remote_sizes2.txt || echo "不一致: $f"; done; echo "確認おわり"
送信は終わっています
不一致: params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf
確認おわり
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 %
約950MB を1本で送り直すと、15分ほどかかります。ファイルを 100MB ずつ10個に分けて同時に送り、Pod でつなぎ直しました。最後に、Mac と Pod のファイルの MD5(中身から計算する32文字の値。1バイトでも違えば別の値になる)を並べて出す形にしています。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 % F=params/ocdbt.process_0/d/c1459bde3a792b3a5da9dfb1d0f0ddcf
R=/workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999
rm -rf /tmp/c145 && mkdir -p /tmp/c145 && split -b 100m $F /tmp/c145/part_
ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "rm -rf /workspace/parts && mkdir -p /workspace/parts"
ls /tmp/c145/part_* | xargs -P 10 -I{} scp -q -P <SSH_PORT> -i ~/.ssh/id_ed25519 {} root@<POD_IP>:/workspace/parts/
ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "cat /workspace/parts/part_* > $R/$F && md5sum $R/$F && rm -rf /workspace/parts"
md5 -q $F
MD5 の2行は記録に残していません。代わりに、サーバーを起動し直すと、重み 6.3GiB を最後まで読み込めました。
INFO:absl:Restoring checkpoint from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params.
INFO:absl:[thread=MainThread] Failed to get flag value for EXPERIMENTAL_ORBAX_USE_DISTRIBUTED_PROCESS_ID.
INFO:absl:[process=0] /jax/checkpoint/read/bytes_per_sec: 1.2 GiB/s (total bytes: 6.3 GiB) (time elapsed: 5 seconds) (per-host)
INFO:absl:Finished restoring checkpoint in 5.22 seconds from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/params.
INFO:openpi.shared.download:Downloading gs://big_vision/paligemma_tokenizer.model to /root/.cache/openpi/big_vision/paligemma_tokenizer.model
INFO:tqdm_loggable.tqdm_logging:Progress on: -/4264023 rate:- remaining:? elapsed:00:00 postfix:-
INFO:tqdm_loggable.tqdm_logging:Progress on: 4.07MiB/4.07MiB rate:2,443,916.7iB/s remaining:00:00 elapsed:00:02 postfix:-
INFO:root:Loaded norm stats from /workspace/openpi/assets/pi05_mecharm_lora/local/mecharm_redblock_box_v1
INFO:root:Loaded norm stats from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999/assets/local/mecharm_redblock_box_v1
INFO:root:Creating server (host: b07c796f894c, ip: 172.27.0.2)
INFO:websockets.server:server listening on 0.0.0.0:8000
9時8分です。Pod を作ってから、サーバーが待ち受けるまで約1時間10分かかりました。
| 区間 | かかった時間 |
|---|---|
| Pod の作成〜起動 | 約6分 |
openpi を入れる(uv sync まで) | 約20分(モデルの送信と並行) |
| モデルの送信(1本 → 6本同時) | 約40分 |
| 切れたファイルの確認と送り直し | 約20分 |
推論サーバーを朝に起動し、実機を動かしたのは8時間後だった
Mac から Pod のサーバーへ、SSH トンネル(Mac の 8000 番ポートを Pod の 8000 番へつなぐ通り道)を張りました。何も表示されず、行頭の % にも戻らないのが、つながっている状態です。
Last login: Sat Oct 17 09:04:17 on ttys003
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % ssh -N -L 8000:localhost:8000 -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP>
実機を動かしたのは、17時20分からです。その間、Pod は推論サーバーを起動したまま、何も処理していません。A40 は1時間約 0.6 ドルなので、約8時間で約5ドルを、使わない時間に払っています。
17時20分にドライランを始めると、つながりませんでした。朝に張ったトンネルが切れていたためです。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % cd ~/r2r2r-scripts && python3 pi05/run_policy_client.py --max-chunks 5
[SAFE ] 安全範囲 (学習 20 本 ±10°): j0[-52,+96] j1[-9,+99] j2[-52,+30] j3[-24,+32] j4[-90,+60] j5[-51,-19]
[MODE ] DRY-RUN (アームには送らない) steps/chunk=5 max_move=20.0° log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_171726
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
[POLICY] サーバー待ち ws://localhost:8000 ... (SSH トンネルは張れていますか)
Traceback (most recent call last):
File "/Users/shimazueiichi/r2r2r-scripts/pi05/run_policy_client.py", line 286, in <module>
main()
File "/Users/shimazueiichi/r2r2r-scripts/pi05/run_policy_client.py", line 194, in main
policy = PolicyClient(args.host, args.port)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/shimazueiichi/r2r2r-scripts/pi05/policy_ws.py", line 46, in __init__
self.ws = websockets.sync.client.connect(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Caskroom/miniforge/base/lib/python3.12/site-packages/websockets/sync/client.py", line 327, in connect
sock = socket.create_connection(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Caskroom/miniforge/base/lib/python3.12/socket.py", line 865, in create_connection
raise exceptions[0]
File "/opt/homebrew/Caskroom/miniforge/base/lib/python3.12/socket.py", line 850, in create_connection
sock.connect(sa)
ConnectionRefusedError: [Errno 61] Connection refused
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts %
トンネルを張り直すと、エラーは出ずに止まりました。Pod はまだ動いていたということです。
Last login: Sat Oct 17 09:08:48 on ttys004
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % ssh -N -L 8000:localhost:8000 -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP>
π0.5 の予測をアームに送るクライアント(run_policy_client.py)
アームに送るのは、Mac で動かす pi05/run_policy_client.py です。前の Log で作ったもので、この回は変更していません。1回の流れは次のとおりです。
- 上のカメラの画像(長い辺 384 ピクセルに縮小)と、6つの関節の角度、グリッパーの開閉を、Pod の π0.5 に送る
- π0.5 は、0.1秒刻みで10ステップ先(1秒先)までの関節角とグリッパーの開閉を返す(これを chunk と呼ぶ)
- クライアントは、そのうち
--steps-per-chunkで決めたステップ(既定は5=0.5秒先)の姿勢を1回の指令としてアームに送る - 送る前に、関節ごとの範囲をお手本20本で使った範囲の ±10° に、1回の移動量を 20° までに絞る
- 届いたかを確かめて、撮り直して次の chunk へ
アームへの送り方は、お手本を集めたスクリプトと同じ関数(_send_and_wait_arrival・速度20)を使っています。届いたとみなす誤差は 3.0° です。誤差が 3.0° 以内なら、動いていなくても「届いた」と表示されます。
#!/usr/bin/env python3
"""run_policy_client.py — π0.5 (Runpod の serve_policy) の出力で mechArm 270 を動かす Mac 側クライアント.
流れ (1 チャンクごと):
上カメラ画像 + 実測 6 軸 + グリッパー状態 → π0.5 → 行動チャンク (10 x 7, 0.1 秒刻みの絶対角)
→ 先頭 N ステップ先 (既定 5 = 0.5 秒先) の姿勢へ単発 send_angles (学習データと同じ速度 20)
→ グリッパーの開閉がチャンク内で切り替わるなら, その直前の姿勢まで動いてから開閉
→ 撮り直して次のチャンクへ
安全策:
- 既定はドライラン (アームに送らない). 送るには --execute
- --execute でも既定は 1 チャンクごとに Enter で確認 (--auto で連続)
- 学習データで通った関節範囲 ±ENVELOPE_MARGIN_DEG の外へは出さない (クランプ)
- 1 回の移動量を --max-move-deg で制限 (クランプ), j4 は J4_SAFE 範囲内
- グリッパー操作後は 2 秒待つ (DAY175 対策・本線と同じ)
前提:
- Pi の daemon が待ち受け中 / 上カメラ (iPhone 連係) が見えている
- Runpod で serve_policy が起動し, Mac から SSH トンネル (localhost:8000) が張れている
- pip install msgpack "websockets>=11"
Usage:
python3 pi05/run_policy_client.py # ドライラン (予測を表示・記録するだけ)
python3 pi05/run_policy_client.py --execute # 1 チャンクごとに Enter で実行
python3 pi05/run_policy_client.py --execute --auto --max-chunks 40
記録: /Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/{stamp}/ (log.jsonl + 各チャンクの入力画像)
--record を付けると recorder.py --top にも trial_start/trial_end を送り, 上カメラ動画を残す.
"""
import argparse
import json
import socket
import sys
import time
import types
from datetime import datetime
from pathlib import Path
import cv2
import numpy as np
# ---- パス (run_pick_place.py と同じ構成) ----------------------------------------
_HERE = Path(__file__).resolve().parent
_ROOT = _HERE.parent
_PLACE = _ROOT / "dynamic_tracking_limits" / "place" / "place_modules"
_DRAG = _ROOT / "dynamic_tracking_limits" / "drag_teaching"
for p in (_HERE, _ROOT, _PLACE, _DRAG):
sys.path.insert(0, str(p))
# pymycobot スタブ (waypoint_replay 経由の import を Mac 上で通すため. アーム制御は daemon 経由のみ)
if "pymycobot" not in sys.modules:
_pkg = types.ModuleType("pymycobot"); _mod = types.ModuleType("pymycobot.mycobot")
class _MyCobotStub:
def __init__(self, *a, **k):
raise RuntimeError("MyCobot は直接使わない (daemon 経由のみ)")
_mod.MyCobot = _MyCobotStub
sys.modules["pymycobot"] = _pkg; sys.modules["pymycobot.mycobot"] = _mod
import camera_resolver # noqa: E402
import waypoint_replay as wr # noqa: E402
from j0_sweep_scan import _send_and_wait_arrival # noqa: E402
from policy_ws import PolicyClient # noqa: E402
from visual_servo_pick import ( # noqa: E402
close_daemon_socket, daemon_get_angles, daemon_set_gripper, daemon_set_gripper_value, get_daemon_socket,
)
TASK = "put the red block in the white box"
IMG_LONG = 384 # build_demo_episodes.py と同じ (学習画像の長辺)
HOME = [0.0, 0.0, 0.0, 0.0, 0.0, -30.0]
GRIP_VALUE = 20 # run_pick_place.py の close 値
GRIPPER_SLEEP = 2.0
J4_SAFE = (-90.0, 60.0) # run_pick_place.py の J4_SAFE_MIN/MAX
ENVELOPE_MARGIN_DEG = 10.0
DATA_ROOT = Path("/Volumes/KIOXIA/r2r2r-data")
DEFAULT_RAW = DATA_ROOT / "datasets" / "mecharm_redblock_box_scripted_v1" / "raw_episodes"
LOG_ROOT = DATA_ROOT / "runs" / "pi05_policy"
RECORDER = ("127.0.0.1", 5556)
def load_envelope(raw_dir: Path):
"""学習データで通った各関節の範囲 ± マージン. 物理可動域 (wr.JOINT_LIMITS) と J4_SAFE で更に絞る."""
qs = [np.load(p)["state"][:, :6] for p in raw_dir.glob("*/episode.npz")]
if not qs:
sys.exit(f"[ERROR] 学習エピソードが見つからない (安全範囲を作れない): {raw_dir}")
q = np.concatenate(qs)
lo = q.min(axis=0) - ENVELOPE_MARGIN_DEG
hi = q.max(axis=0) + ENVELOPE_MARGIN_DEG
for k, (plo, phi) in enumerate(wr.JOINT_LIMITS):
lo[k], hi[k] = max(lo[k], plo), min(hi[k], phi)
lo[4], hi[4] = max(lo[4], J4_SAFE[0]), min(hi[4], J4_SAFE[1])
return lo, hi, len(qs)
def read_angles():
for _ in range(5):
try:
a = daemon_get_angles()
except Exception:
a = None
if wr._is_valid_angles(a):
return np.array([float(x) for x in a])
time.sleep(0.1)
return None
def grab_rgb(cap):
for _ in range(3): # バッファに溜まった古いフレームを捨てる
cap.grab()
ok, frame = cap.read()
if not ok or frame is None:
return None, None
h, w = frame.shape[:2]
s = IMG_LONG / max(h, w)
small = cv2.resize(frame, (round(w * s), round(h * s)), interpolation=cv2.INTER_AREA)
return cv2.cvtColor(small, cv2.COLOR_BGR2RGB), small # モデルへは RGB, 保存は BGR
def plan_chunk(actions, q, grip, n_steps):
"""チャンクから今回動かす目標を決める. 戻り値: (目標6軸, グリッパー指令 None/0/1, 使ったステップ)."""
want = (actions[:, 6] > 0.5).astype(int)
for f in range(min(n_steps, len(actions))):
if want[f] != grip:
target = q.copy() if f == 0 else actions[f - 1, :6]
return np.asarray(target, dtype=float), int(want[f]), f
k = min(n_steps, len(actions)) - 1
return np.asarray(actions[k, :6], dtype=float), None, k
def apply_limits(target, q, lo, hi, max_move):
notes = []
t = target.copy()
clipped = np.clip(t, lo, hi)
for k in np.where(np.abs(clipped - t) > 1e-6)[0]:
notes.append(f"j{k} 安全範囲でクランプ {t[k]:+.1f}→{clipped[k]:+.1f}")
t = clipped
d = t - q
big = np.abs(d) > max_move
for k in np.where(big)[0]:
notes.append(f"j{k} 移動量 {d[k]:+.1f}° を ±{max_move:.0f}° に制限")
t = q + np.clip(d, -max_move, max_move)
return t, notes
def notify_recorder(sock, payload):
try:
payload = dict(payload, ts=time.time())
sock.sendto((json.dumps(payload) + "\n").encode(), RECORDER)
except Exception:
pass
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--host", default="localhost")
ap.add_argument("--port", type=int, default=8000)
ap.add_argument("--execute", action="store_true", help="アームに送る (未指定はドライラン)")
ap.add_argument("--auto", action="store_true", help="Enter 確認なしで連続実行")
ap.add_argument("--steps-per-chunk", type=int, default=5, help="1 チャンクで何ステップ先まで進むか (0.1 秒単位)")
ap.add_argument("--max-chunks", type=int, default=60)
ap.add_argument("--max-move-deg", type=float, default=20.0, help="1 回の移動で各関節が動ける上限")
ap.add_argument("--raw-dir", default=str(DEFAULT_RAW), help="安全範囲の算出に使う学習エピソード")
ap.add_argument("--prompt", default=TASK)
ap.add_argument("--record", action="store_true", help="recorder.py --top に上カメラ動画を記録させる")
args = ap.parse_args()
if not DATA_ROOT.exists():
sys.exit(f"[ERROR] SSD 未マウント: {DATA_ROOT}")
lo, hi, n_eps = load_envelope(Path(args.raw_dir))
print(f"[SAFE ] 安全範囲 (学習 {n_eps} 本 ±{ENVELOPE_MARGIN_DEG:.0f}°): "
+ " ".join(f"j{k}[{lo[k]:+.0f},{hi[k]:+.0f}]" for k in range(6)))
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_dir = LOG_ROOT / f"pi05_{stamp}"
(log_dir / "frames").mkdir(parents=True, exist_ok=True)
log_f = (log_dir / "log.jsonl").open("w")
mode = "EXECUTE" if args.execute else "DRY-RUN (アームには送らない)"
print(f"[MODE ] {mode} steps/chunk={args.steps_per_chunk} max_move={args.max_move_deg}° log={log_dir}")
cams = camera_resolver.resolve_cameras(require_top=True, require_side=False, verbose=False)
cap = cv2.VideoCapture(cams["top"])
if not cap.isOpened():
sys.exit("[ERROR] 上カメラを開けない")
t_cam = time.time()
while time.time() - t_cam < 8.0: # 連係カメラは映像が来るまで数秒かかることがある
ok, _f = cap.read()
if ok and _f is not None:
break
time.sleep(0.2)
else:
sys.exit("[ERROR] 上カメラから映像が来ない. iPhone のロック解除・連係カメラの接続を確認 "
"(Photo Booth の『カメラ』メニューに iPhone が出るか)")
get_daemon_socket()
policy = PolicyClient(args.host, args.port)
print(f"[POLICY] 接続 {policy.uri} metadata={policy.metadata}")
rec_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
run_stem = f"pi05_{stamp}"
if args.record:
notify_recorder(rec_sock, {"type": "trial_start", "run_stem": run_stem})
grip = 0
try:
q = read_angles()
if q is None:
sys.exit("[ERROR] 関節角を読めない (daemon を確認)")
if np.max(np.abs(q - np.array(HOME))) > 5.0:
print(f"[WARN ] HOME から離れています (max|Δ|={np.max(np.abs(q - np.array(HOME))):.1f}°). "
"学習データは HOME から始まっています")
print(" 現在角: " + " ".join(f"j{k}={v:+.1f}" for k, v in enumerate(q)))
if args.execute:
print("[GRIP ] 開始時にグリッパーを開く")
daemon_set_gripper(0)
time.sleep(GRIPPER_SLEEP)
for i in range(args.max_chunks):
q = read_angles()
if q is None:
print("[ERROR] 関節角を読めない (daemon を確認). 停止"); break
rgb = bgr_small = None
for _ in range(10):
rgb, bgr_small = grab_rgb(cap)
if rgb is not None:
break
time.sleep(0.2)
if rgb is None:
print("[ERROR] 上カメラの映像が途切れた (iPhone のスリープ等). 停止"); break
state = np.concatenate([q, [float(grip)]]).astype(np.float32)
t0 = time.time()
out = policy.infer({"observation/image": rgb, "observation/state": state, "prompt": args.prompt})
latency = time.time() - t0
acts = np.asarray(out["actions"], dtype=float)
target, grip_cmd, used = plan_chunk(acts, q, grip, args.steps_per_chunk)
target, notes = apply_limits(target, q, lo, hi, args.max_move_deg)
delta = target - q
cv2.imwrite(str(log_dir / "frames" / f"{i:04d}.jpg"), bgr_small)
rec = {"chunk": i, "t": time.time(), "latency_s": round(latency, 3), "state": state.tolist(),
"actions": acts.tolist(), "used_step": used, "target": target.tolist(),
"grip_cmd": grip_cmd, "notes": notes, "executed": False}
gtxt = "" if grip_cmd is None else (" グリッパー→閉" if grip_cmd == 1 else " グリッパー→開")
print(f"[{i:03d}] 推論 {latency*1000:.0f}ms {used + 1}ステップ先 Δ="
+ " ".join(f"{d:+5.1f}" for d in delta) + gtxt)
for n in notes:
print(f" [LIMIT] {n}")
if args.execute:
if not args.auto:
ans = input(" Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 > ").strip().lower()
if ans == "q":
log_f.write(json.dumps(rec) + "\n"); break
if ans == "h":
_send_and_wait_arrival(HOME, "pi05_home", 2.5)
log_f.write(json.dumps(rec) + "\n"); break
if ans == "s":
log_f.write(json.dumps(rec) + "\n"); continue
if np.max(np.abs(delta)) > 0.5:
ok, _ = _send_and_wait_arrival(target.tolist(), f"pi05_chunk{i:03d}", 3.0)
rec["arrived"] = bool(ok)
if not ok:
print(" [WARN] 未到達. j4 の発熱などを確認してください")
if args.auto:
log_f.write(json.dumps(rec) + "\n"); break
if grip_cmd is not None:
if grip_cmd == 1:
daemon_set_gripper_value(GRIP_VALUE)
else:
daemon_set_gripper(0)
time.sleep(GRIPPER_SLEEP)
grip = grip_cmd
rec["executed"] = True
log_f.write(json.dumps(rec) + "\n"); log_f.flush()
except KeyboardInterrupt:
print("\n[STOP ] Ctrl-C")
finally:
if args.record:
notify_recorder(rec_sock, {"type": "trial_end", "run_stem": run_stem})
log_f.close(); cap.release(); policy.close()
try:
close_daemon_socket()
except Exception:
pass
print(f"[DONE ] 記録: {log_dir}")
if __name__ == "__main__":
main()
Pod のサーバーとの通信は、pi05/policy_ws.py が受け持ちます。
"""policy_ws.py — openpi のポリシーサーバー (scripts/serve_policy.py) と話す最小クライアント.
openpi-client パッケージ (packages/openpi-client) の websocket_client_policy / msgpack_numpy と
同じ通信形式を、依存を増やさずに写したもの. openpi-client は numpy<2 を要求するため,
既存の Mac 環境を壊さないよう直接は入れない. 必要なのは msgpack と websockets(>=11) だけ.
pip install msgpack "websockets>=11"
"""
import functools
import time
import msgpack
import numpy as np
import websockets.sync.client
def _pack_array(obj):
if isinstance(obj, (np.ndarray, np.generic)) and obj.dtype.kind in ("V", "O", "c"):
raise ValueError(f"Unsupported dtype: {obj.dtype}")
if isinstance(obj, np.ndarray):
return {b"__ndarray__": True, b"data": obj.tobytes(), b"dtype": obj.dtype.str, b"shape": obj.shape}
if isinstance(obj, np.generic):
return {b"__npgeneric__": True, b"data": obj.item(), b"dtype": obj.dtype.str}
return obj
def _unpack_array(obj):
if b"__ndarray__" in obj:
return np.ndarray(buffer=obj[b"data"], dtype=np.dtype(obj[b"dtype"]), shape=obj[b"shape"])
if b"__npgeneric__" in obj:
return np.dtype(obj[b"dtype"]).type(obj[b"data"])
return obj
packb = functools.partial(msgpack.packb, default=_pack_array)
unpackb = functools.partial(msgpack.unpackb, object_hook=_unpack_array)
class PolicyClient:
def __init__(self, host="localhost", port=8000, wait_sec=60.0):
self.uri = host if host.startswith("ws") else f"ws://{host}:{port}"
t0 = time.time()
while True:
try:
# ping_interval=None: openpi のサーバーは推論 (特に初回のコンパイル数分) の間 ping に応答できないため,
# keepalive を切らないと約 40 秒で接続が落ちる (2026-10-15 実測).
self.ws = websockets.sync.client.connect(
self.uri, compression=None, max_size=None, ping_interval=None, close_timeout=5)
self.metadata = unpackb(self.ws.recv())
return
except (ConnectionRefusedError, OSError):
if time.time() - t0 > wait_sec:
raise
print(f"[POLICY] サーバー待ち {self.uri} ... (SSH トンネルは張れていますか)")
time.sleep(5)
def infer(self, obs: dict) -> dict:
self.ws.send(packb(obs))
resp = self.ws.recv()
if isinstance(resp, str):
raise RuntimeError(f"ポリシーサーバー側のエラー:\n{resp}")
return unpackb(resp)
def close(self):
try:
self.ws.close()
except Exception:
pass
ドライランの最初の予測は、お手本の動き出しの平均とほぼ同じだった
アームを HOME に置き、ブロックをいつもの位置に置いて、予測だけを出しました(17時23分)。
Last login: Sat Oct 17 17:22:35 on ttys005
(base) shimazueiichi@shimadzueiichinoMacBook-Pro ~ % cd ~/r2r2r-scripts && python3 pi05/run_policy_client.py --max-chunks 5
[SAFE ] 安全範囲 (学習 20 本 ±10°): j0[-52,+96] j1[-9,+99] j2[-52,+30] j3[-24,+32] j4[-90,+60] j5[-51,-19]
[MODE ] DRY-RUN (アームには送らない) steps/chunk=5 max_move=20.0° log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_172323
[POLICY] 接続 ws://localhost:8000 metadata={}
[000] 推論 54872ms 5ステップ先 Δ= -6.0 +16.1 -7.8 -0.0 -13.0 -0.0
[001] 推論 572ms 5ステップ先 Δ= -0.7 +2.4 -0.7 +0.8 -0.9 -0.1
[002] 推論 584ms 5ステップ先 Δ= +0.1 +0.2 -0.0 -0.1 +0.2 +0.0
[003] 推論 1552ms 5ステップ先 Δ= -0.0 +0.0 +0.1 -0.1 +0.1 +0.0
[004] 推論 743ms 5ステップ先 Δ= -4.0 +11.2 -4.7 -0.6 -9.3 -0.1
[DONE ] 記録: /Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_172323
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts %
1回目は、サーバー側の準備(コンパイル)で 55 秒かかりました。2回目からは 0.6〜1.6 秒です。
Δ は、今の角度から 0.5 秒先の予測までの差です。j0 がベース(左右の向き)、j1 が肩、j2 が肘、j3 が前腕のひねり、j4 が手首、j5 が手首の回転です。1回目の予測は、j0 −6.0、j1 +16.1、j2 −7.8、j4 −13.0 でした。前の Log で計算した「お手本19本の動き出し 0.5 秒の平均」は、j0 約 −6、j1 約 +16、j2 約 −7、j4 約 −13 です。朝(前の Pod)に同じことをしたときよりも、さらに近い値でした。2〜4回目がほぼ 0 なのは、お手本が HOME で約1秒止まってから動き出すためで、前の Log と同じ揺れ方です。
1回目の実機(0.5秒先ずつ)は、ブロックの上で動きが細かくなって進まなくなった
アームに送りました(17時25分)。1回分の予測ごとに止まり、Enter を押すと送ります。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % python3 pi05/run_policy_client.py --execute
[SAFE ] 安全範囲 (学習 20 本 ±10°): j0[-52,+96] j1[-9,+99] j2[-52,+30] j3[-24,+32] j4[-90,+60] j5[-51,-19]
[MODE ] EXECUTE steps/chunk=5 max_move=20.0° log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_172526
[POLICY] 接続 ws://localhost:8000 metadata={}
[GRIP ] 開始時にグリッパーを開く
[Pi] GRIPPER: OPEN result= -1
[000] 推論 745ms 5ステップ先 Δ= -4.6 +14.5 -6.3 -0.4 -11.9 -0.2
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 > n
[SEND] pi05_chunk000: send_angles (single) [ -4.95, +15.74, -4.42, -0.48, -12.13, -30.41] speed=20 wait=1.0s (pre max|Δ|=14.51°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=2.23° (tol 3.0°)
[001] 推論 624ms 5ステップ先 Δ= -6.9 +15.9 -6.0 +0.4 -14.7 -0.3
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk001: send_angles (single) [ -11.87, +32.59, -8.17, +0.42, -25.56, -30.52] speed=20 wait=1.0s (pre max|Δ|=15.90°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=2.02° (tol 3.0°)
[002] 推論 609ms 5ステップ先 Δ= -6.1 +16.5 -6.1 -0.1 -13.6 -0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk002: send_angles (single) [ -18.04, +50.11, -12.26, +0.00, -38.82, -30.38] speed=20 wait=1.0s (pre max|Δ|=16.54°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.81° (tol 3.0°)
[003] 推論 649ms 5ステップ先 Δ= -6.2 +15.8 -5.9 +0.1 -15.3 -0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk003: send_angles (single) [ -23.92, +67.11, -16.39, +0.18, -53.57, -30.34] speed=20 wait=1.0s (pre max|Δ|=15.79°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.63° (tol 3.0°)
[004] 推論 1549ms 5ステップ先 Δ= -5.5 +14.1 -5.5 -0.1 -11.7 -0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 > [SEND] pi05_chunk004: send_angles (single) [ -29.21, +82.37, -20.27, +0.12, -65.60, -30.35] speed=20 wait=1.0s (pre max|Δ|=14.08°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.29° (tol 3.0°)
[005] 推論 649ms 5ステップ先 Δ= -0.4 +0.4 -0.3 -0.3 +0.3 -0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[006] 推論 560ms 5ステップ先 Δ= -0.3 +0.3 +0.5 -0.5 +1.1 -0.2
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk006: send_angles (single) [ -29.88, +83.75, -18.49, +0.17, -64.46, -30.45] speed=20 wait=1.0s (pre max|Δ|=1.10°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.15° (tol 3.0°)
[007] 推論 631ms 5ステップ先 Δ= -0.5 +0.2 -0.2 -0.3 +0.2 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk007: send_angles (single) [ -30.12, +85.06, -17.98, +0.23, -65.37, -30.27] speed=20 wait=1.0s (pre max|Δ|=0.51°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.07° (tol 3.0°)
[008] 推論 1532ms 5ステップ先 Δ= -0.3 -0.0 -0.1 -0.2 +0.7 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk008: send_angles (single) [ -29.87, +86.10, -17.73, +0.29, -65.14, -29.97] speed=20 wait=1.0s (pre max|Δ|=0.69°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.12° (tol 3.0°)
[009] 推論 563ms 5ステップ先 Δ= -0.2 -0.1 +0.4 -0.6 +2.0 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk009: send_angles (single) [ -29.84, +86.71, -17.29, -0.12, -64.21, -30.00] speed=20 wait=1.0s (pre max|Δ|=2.05°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.62° (tol 3.0°)
[010] 推論 1632ms 5ステップ先 Δ= -0.2 -0.1 +0.1 -0.5 +2.0 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk010: send_angles (single) [ -29.94, +87.15, -17.54, -0.35, -63.79, -29.98] speed=20 wait=1.0s (pre max|Δ|=1.68°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.16° (tol 3.0°)
[011] 推論 777ms 5ステップ先 Δ= -0.1 -0.1 +0.2 -0.3 +1.2 +0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
最初の5回(chunk000〜004)は、1回に 15° 前後ずつ、ブロックの方へ腕を伸ばしました。chunk005 からは、予測が 1〜2° の小さな動きになり、アームはほとんど進まなくなりました。chunk005 は Δ が小さすぎて(0.5° 以下)送っていません。
KIOXIA の記録(log.jsonl)から、各回の予測を読み直しました。
cd $HOME/mnt/r2r2r-data 2>/dev/null || cd $HOME/mnt/KIOXIA/r2r2r-data; pwd; python3 - <<'PY'
import json, numpy as np, glob
L=[json.loads(l) for l in open("runs/pi05_policy/pi05_20261017_172526/log.jsonl")]
print("keys",sorted(L[0].keys()))
for c in L:
a=np.array(c["actions"]); s=np.array(c["state"])
print(c["chunk"], "state",np.round(s[:7],1).tolist(), "grip pred first/last", round(float(a[0,6]),3), round(float(a[-1,6]),3), "step10Δ", np.round(a[min(9,len(a)-1),:5]-s[:5],1).tolist())
PY
/sessions/rcw-015zy8pzgzrsph1mzucxbome/mnt/r2r2r-data
keys ['actions', 'arrived', 'chunk', 'executed', 'grip_cmd', 'latency_s', 'notes', 'state', 't', 'target', 'used_step']
0 state [-0.3, 1.2, 1.8, -0.1, -0.3, -30.2, 0.0] grip pred first/last 0.003 -0.006 step10Δ [-10.4, 32.6, -14.3, -0.8, -27.8]
1 state [-4.9, 16.7, -2.2, 0.0, -10.9, -30.2, 0.0] grip pred first/last 0.003 0.002 step10Δ [-13.5, 31.6, -12.3, 0.8, -29.0]
2 state [-11.9, 33.6, -6.2, 0.1, -25.2, -30.2, 0.0] grip pred first/last -0.001 -0.0 step10Δ [-12.3, 32.4, -12.9, -0.5, -25.9]
3 state [-17.8, 51.3, -10.4, 0.1, -38.3, -30.2, 0.0] grip pred first/last 0.001 0.004 step10Δ [-12.1, 29.7, -11.5, 0.1, -28.1]
4 state [-23.7, 68.3, -14.8, 0.2, -53.9, -30.2, 0.0] grip pred first/last 0.003 0.003 step10Δ [-8.1, 19.7, -7.9, -0.1, -17.2]
5 state [-29.6, 83.5, -19.0, 0.7, -65.6, -30.2, 0.0] grip pred first/last 0.004 0.001 step10Δ [-0.5, 0.6, 0.1, -0.4, 1.1]
6 state [-29.6, 83.5, -19.0, 0.7, -65.6, -30.2, 0.0] grip pred first/last 0.004 0.0 step10Δ [0.6, 0.1, 1.6, -0.8, 3.9]
7 state [-29.6, 84.9, -17.8, 0.5, -65.6, -30.2, 0.0] grip pred first/last 0.004 -0.005 step10Δ [-0.7, 0.3, -0.1, -0.8, 1.1]
8 state [-29.6, 86.1, -17.7, 0.5, -65.8, -30.0, 0.0] grip pred first/last 0.003 0.001 step10Δ [-0.2, 0.1, 0.5, -0.3, 1.6]
9 state [-29.6, 86.8, -17.7, 0.4, -66.3, -30.0, 0.0] grip pred first/last 0.006 -0.003 step10Δ [-0.3, -0.3, 1.5, -1.3, 3.5]
10 state [-29.7, 87.3, -17.7, 0.2, -65.8, -30.0, 0.0] grip pred first/last 0.002 -0.004 step10Δ [-0.3, -0.3, -0.2, -0.9, 3.6]
グリッパーの予測(0 が開く、1 が閉じる)は、最後まで 0 のままです。chunk005 以降の1秒先の予測は、j4(手首)を +1〜+4° 上げる向きでした。
お手本の中で、この姿勢のあとに何が起きているかを見ました。お手本19本それぞれで、「つかむ直前に近い姿勢」がどこにあり、グリッパーをいつ閉じたかを出しています。
cd $HOME/mnt/r2r2r-data/datasets/mecharm_redblock_box_scripted_v1/raw_episodes && cat */meta.json | head -c 600; echo; python3 - <<'PY'
import numpy as np, glob, json
eps=[d for d in sorted(glob.glob("run_*")) if not d.endswith("182552")]
z=np.load(f"{eps[0]}/episode.npz"); print({k:z[k].shape for k in z.files})
target=np.array([-29.7,87.3,-17.7,0.2,-65.8])
for d in eps:
s=np.load(f"{d}/episode.npz")["state"]
g=s[:,6]; ch=np.where(np.abs(np.diff(g))>0.1)[0]
dist=np.abs(s[:,:5]-target).max(1); i=int(dist.argmin())
# how long arm stays near-still after reaching this region
mv=np.abs(np.diff(s[:,:6],axis=0)).max(1)
still=0
for k in range(i,len(mv)):
if mv[k]<0.3: still+=1
else: break
c=ch[0] if len(ch) else None
print(d[-6:], "len",len(s), "nearest@",i,"maxdiff",round(float(dist[i]),1), "静止",still, "grip変化@",c, "grip時姿勢", None if c is None else np.round(s[c,:5],0).tolist())
PY
{
"run_stem": "run_0001_20261015_173315",
"task": "put the red block in the white box",
"fps": 10,
"steps": 307,
"duration_sec": 30.7,
"idle_trimmed_sec": 77.0,
"image_size": [
384,
216
],
"state_dim": 7,
"state_names": [
"j0",
"j1",
"j2",
"j3",
"j4",
"j5",
"gripper"
],
"action": "absolute target = state 0.1s later",
"actual_samples": 49,
"gripper_cmds": 3,
"human_verdict": {
"pick": "yes",
"place": "yes"
}
}{
"run_stem": "run_0001_20261015_175333",
"task": "put the red block in the white box",
"fps": 10,
"step
{'t': (307,), 'state': (307, 7), 'actions': (307, 7)}
173315 len 307 nearest@ 150 maxdiff 9.5 静止 0 grip変化@ 79 grip時姿勢 [-42.0, 89.0, -33.0, -12.0, -52.0]
175333 len 293 nearest@ 134 maxdiff 9.8 静止 0 grip変化@ 79 grip時姿勢 [-42.0, 89.0, -33.0, -6.0, -51.0]
181752 len 291 nearest@ 158 maxdiff 11.4 静止 0 grip変化@ 75 grip時姿勢 [-40.0, 87.0, -30.0, -9.0, -52.0]
182056 len 279 nearest@ 137 maxdiff 11.9 静止 0 grip変化@ 82 grip時姿勢 [-36.0, 89.0, -33.0, -1.0, -51.0]
183310 len 294 nearest@ 154 maxdiff 9.3 静止 0 grip変化@ 88 grip時姿勢 [-42.0, 89.0, -33.0, -11.0, -51.0]
185536 len 267 nearest@ 137 maxdiff 8.8 静止 0 grip変化@ 83 grip時姿勢 [-42.0, 89.0, -33.0, -10.0, -50.0]
193907 len 282 nearest@ 148 maxdiff 8.1 静止 0 grip変化@ 84 grip時姿勢 [-38.0, 88.0, -32.0, -4.0, -49.0]
194135 len 524 nearest@ 388 maxdiff 9.0 静止 0 grip変化@ 328 grip時姿勢 [-42.0, 89.0, -33.0, -6.0, -52.0]
194420 len 287 nearest@ 153 maxdiff 9.0 静止 0 grip変化@ 89 grip時姿勢 [-31.0, 88.0, -33.0, -11.0, -48.0]
175444 len 328 nearest@ 179 maxdiff 13.8 静止 0 grip変化@ 85 grip時姿勢 [-32.0, 88.0, -29.0, 18.0, -52.0]
175708 len 289 nearest@ 72 maxdiff 13.2 静止 0 grip変化@ 79 grip時姿勢 [-30.0, 87.0, -30.0, -12.0, -52.0]
180013 len 271 nearest@ 135 maxdiff 10.5 静止 0 grip変化@ 83 grip時姿勢 [-28.0, 88.0, -33.0, 6.0, -49.0]
184725 len 274 nearest@ 144 maxdiff 9.4 静止 0 grip変化@ 78 grip時姿勢 [-42.0, 89.0, -33.0, -6.0, -50.0]
185201 len 266 nearest@ 90 maxdiff 13.0 静止 0 grip変化@ 79 grip時姿勢 [-37.0, 89.0, -28.0, 13.0, -57.0]
190925 len 227 nearest@ 71 maxdiff 12.3 静止 0 grip変化@ 78 grip時姿勢 [-32.0, 88.0, -29.0, 12.0, -52.0]
191539 len 290 nearest@ 143 maxdiff 13.0 静止 0 grip変化@ 80 grip時姿勢 [-30.0, 86.0, -29.0, -8.0, -52.0]
191942 len 276 nearest@ 146 maxdiff 12.0 静止 0 grip変化@ 80 grip時姿勢 [-38.0, 88.0, -33.0, 10.0, -49.0]
192229 len 308 nearest@ 73 maxdiff 10.2 静止 15 grip変化@ 78 grip時姿勢 [-32.0, 89.0, -28.0, 9.0, -57.0]
194238 len 282 nearest@ 150 maxdiff 16.4 静止 0 grip変化@ 82 grip時姿勢 [-30.0, 88.0, -30.0, 17.0, -52.0]
grip時姿勢 の列が、お手本がグリッパーを閉じた瞬間の姿勢です。どれも j2(肘)が −28〜−33°、j4(手首)が −48〜−57° でした。π0.5 のアームは、j2 が −17.7°、j4 が −65.8° で止まっていて、どちらもお手本のつかむ姿勢から 10° 以上離れています。
お手本2本の、動き出しから100ステップ(10秒)までの関節角を4ステップごとに並べました。
cd $HOME/mnt/r2r2r-data/datasets/mecharm_redblock_box_scripted_v1/raw_episodes && python3 - <<'PY'
import numpy as np
for d in ["run_0001_20261015_173315","run_0001_20261016_191539"]:
s=np.load(f"{d}/episode.npz")["state"]
print(d)
for i in range(0,100,4): print(i, np.round(s[i],1).tolist())
PY
run_0001_20261015_173315
0 [-0.30000001192092896, 1.5, 1.7999999523162842, 0.30000001192092896, -0.30000001192092896, -29.700000762939453, 0.0]
4 [-0.30000001192092896, 1.5, 1.7999999523162842, 0.30000001192092896, -0.30000001192092896, -29.700000762939453, 0.0]
8 [-0.30000001192092896, 1.5, 1.7999999523162842, 0.30000001192092896, -0.30000001192092896, -29.700000762939453, 0.0]
12 [-3.0999999046325684, 7.400000095367432, -1.2000000476837158, -0.6000000238418579, -4.400000095367432, -29.700000762939453, 0.0]
16 [-9.5, 20.799999237060547, -7.900000095367432, -2.5999999046325684, -14.100000381469727, -29.700000762939453, 0.0]
20 [-15.899999618530273, 34.29999923706055, -14.600000381469727, -4.5, -23.700000762939453, -29.700000762939453, 0.0]
24 [-22.399999618530273, 47.79999923706055, -21.299999237060547, -6.5, -33.29999923706055, -29.700000762939453, 0.0]
28 [-28.799999237060547, 61.29999923706055, -28.0, -8.399999618530273, -42.900001525878906, -29.700000762939453, 0.0]
32 [-35.20000076293945, 74.80000305175781, -34.70000076293945, -10.399999618530273, -52.5, -29.700000762939453, 0.0]
36 [-41.599998474121094, 88.30000305175781, -41.5, -12.399999618530273, -62.099998474121094, -29.700000762939453, 0.0]
40 [-41.900001525878906, 89.0, -41.79999923706055, -12.5, -62.79999923706055, -29.700000762939453, 0.0]
44 [-41.900001525878906, 89.0, -41.79999923706055, -12.5, -63.0, -29.700000762939453, 0.0]
48 [-41.900001525878906, 89.0, -41.29999923706055, -12.399999618530273, -62.79999923706055, -29.700000762939453, 0.0]
52 [-41.900001525878906, 89.0, -40.0, -12.300000190734863, -61.0, -29.600000381469727, 0.0]
56 [-41.900001525878906, 89.0, -38.599998474121094, -12.199999809265137, -59.20000076293945, -29.399999618530273, 0.0]
60 [-41.900001525878906, 89.0, -37.29999923706055, -12.100000381469727, -57.400001525878906, -29.299999237060547, 0.0]
64 [-41.900001525878906, 89.0, -35.900001525878906, -11.899999618530273, -55.599998474121094, -29.200000762939453, 0.0]
68 [-41.79999923706055, 89.0, -34.5, -11.800000190734863, -53.79999923706055, -29.100000381469727, 0.0]
72 [-41.79999923706055, 89.0, -33.20000076293945, -11.699999809265137, -52.0, -29.0, 0.0]
76 [-41.79999923706055, 89.0, -32.900001525878906, -11.699999809265137, -51.599998474121094, -29.0, 0.0]
80 [-41.70000076293945, 89.0, -32.900001525878906, -11.699999809265137, -51.599998474121094, -29.100000381469727, 1.0]
84 [-41.70000076293945, 89.0, -32.900001525878906, -11.699999809265137, -51.599998474121094, -29.100000381469727, 1.0]
88 [-41.599998474121094, 89.0, -32.900001525878906, -11.699999809265137, -51.599998474121094, -29.100000381469727, 1.0]
92 [-41.29999923706055, 89.0, -33.900001525878906, -11.899999618530273, -52.79999923706055, -29.5, 1.0]
96 [-41.400001525878906, 89.0, -35.20000076293945, -11.800000190734863, -54.400001525878906, -29.700000762939453, 1.0]
run_0001_20261016_191539
0 [-0.10000000149011612, 1.600000023841858, 1.899999976158142, 0.30000001192092896, -0.4000000059604645, -29.5, 0.0]
4 [-0.10000000149011612, 1.600000023841858, 1.899999976158142, 0.30000001192092896, -0.4000000059604645, -29.5, 0.0]
8 [-0.10000000149011612, 1.600000023841858, 1.899999976158142, 0.30000001192092896, -0.4000000059604645, -29.5, 0.0]
12 [-2.200000047683716, 8.0, -0.4000000059604645, 0.30000001192092896, -5.5, -29.899999618530273, 0.0]
16 [-6.400000095367432, 20.799999237060547, -5.099999904632568, 0.4000000059604645, -16.0, -30.399999618530273, 0.0]
20 [-10.600000381469727, 33.5, -9.800000190734863, 0.6000000238418579, -26.399999618530273, -30.799999237060547, 0.0]
24 [-14.800000190734863, 46.29999923706055, -14.5, 0.699999988079071, -36.900001525878906, -31.299999237060547, 0.0]
28 [-19.0, 59.099998474121094, -19.200000762939453, 0.800000011920929, -47.400001525878906, -31.700000762939453, 0.0]
32 [-23.200000762939453, 71.9000015258789, -23.899999618530273, 0.8999999761581421, -57.79999923706055, -32.20000076293945, 0.0]
36 [-27.200000762939453, 84.19999694824219, -28.5, 1.0, -67.9000015258789, -32.599998474121094, 0.0]
40 [-27.200000762939453, 84.19999694824219, -28.5, 1.0, -67.9000015258789, -32.599998474121094, 0.0]
44 [-27.200000762939453, 84.19999694824219, -28.5, 1.0, -68.0, -32.599998474121094, 0.0]
48 [-27.200000762939453, 84.19999694824219, -28.5, 1.0, -69.0999984741211, -32.599998474121094, 0.0]
52 [-27.5, 84.4000015258789, -28.5, 0.10000000149011612, -67.4000015258789, -32.79999923706055, 0.0]
56 [-27.899999618530273, 84.69999694824219, -28.600000381469727, -1.2000000476837158, -64.5999984741211, -33.099998474121094, 0.0]
60 [-28.200000762939453, 85.0, -28.700000762939453, -2.5999999046325684, -61.79999923706055, -33.5, 0.0]
64 [-28.600000381469727, 85.30000305175781, -28.700000762939453, -4.0, -59.099998474121094, -33.79999923706055, 0.0]
68 [-28.899999618530273, 85.5999984741211, -28.700000762939453, -5.300000190734863, -56.29999923706055, -34.099998474121094, 0.0]
72 [-29.299999237060547, 85.80000305175781, -28.899999618530273, -6.699999809265137, -53.5, -34.400001525878906, 0.0]
76 [-29.5, 86.0, -28.899999618530273, -7.599999904632568, -51.70000076293945, -34.599998474121094, 0.0]
80 [-29.5, 86.0, -28.899999618530273, -7.699999809265137, -51.70000076293945, -34.599998474121094, 0.0]
84 [-29.5, 86.0, -28.899999618530273, -7.800000190734863, -51.70000076293945, -34.599998474121094, 1.0]
88 [-29.5, 86.0, -28.899999618530273, -8.0, -51.599998474121094, -34.599998474121094, 1.0]
92 [-29.5, 86.0, -28.899999618530273, -8.0, -51.599998474121094, -34.599998474121094, 1.0]
96 [-29.100000381469727, 86.0, -29.0, -6.800000190734863, -54.79999923706055, -34.5, 1.0]
お手本の流れは、どれも同じ形です。
| ステップ(0.1秒刻み) | お手本で起きていること |
|---|---|
| 0〜11 | HOME で止まっている |
| 12〜36 | ブロックの上へ腕を伸ばす(1回の大きな指令) |
| 36〜48 | 約1.2秒止まる |
| 48〜76 | j2 と j4 を少しずつ上げて、グリッパーの先をブロックの高さまで下ろす(約3秒) |
| 76〜80 | 止まる |
| 80前後 | グリッパーを閉じる |
π0.5 の chunk005 以降は、この「少しずつ下ろす」の部分を予測していると考えました。お手本では 0.5 秒で j4 が約 2° ずつ動きます。0.5 秒先までしか送らない設定だと、1回の指令は 1〜2° です。chunk009 で j4 に −64.21° を指令し、送ったあとの角度が −65.8° のまま「届いた」と表示されているのは、誤差 3.0° 以内なら届いたとみなす決まりのためです。
この時点で Claude は「1回の指令が小さすぎて、サーボが動かない」と見立て、1回に送る量を2倍にすることにしました。モデルが一度に返すのは10ステップ(1秒)先までなので、それが上限です。
1秒先ずつに増やすと、手首 j4 が指令に届かなくなった
止まった姿勢のまま q で終わり、--steps-per-chunk 10 で続きから送りました(17時28分)。
[SEND] arrival OK: max|Δ|=1.16° (tol 3.0°)
[011] 推論 777ms 5ステップ先 Δ= -0.1 -0.1 +0.2 -0.3 +1.2 +0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 > q
[DONE ] 記録: /Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_172526
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % python3 pi05/run_policy_client.py --execute --steps-per-chunk 10
[SAFE ] 安全範囲 (学習 20 本 ±10°): j0[-52,+96] j1[-9,+99] j2[-52,+30] j3[-24,+32] j4[-90,+60] j5[-51,-19]
[MODE ] EXECUTE steps/chunk=10 max_move=20.0° log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_172828
[POLICY] 接続 ws://localhost:8000 metadata={}
[WARN ] HOME から離れています (max|Δ|=87.7°). 学習データは HOME から始まっています
現在角: j0=-29.7 j1=+87.7 j2=-18.2 j3=-0.2 j4=-64.6 j5=-30.0
[GRIP ] 開始時にグリッパーを開く
[Pi] GRIPPER: OPEN result= -1
[000] 推論 830ms 10ステップ先 Δ= -0.4 -0.6 +1.1 -1.2 +3.2 -0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk000: send_angles (single) [ -30.10, +87.11, -17.09, -1.41, -61.63, -30.07] speed=20 wait=1.0s (pre max|Δ|=3.32°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=2.96° (tol 3.0°)
[001] 推論 734ms 10ステップ先 Δ= -0.2 -0.5 +1.3 -0.5 +2.8 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk001: send_angles (single) [ -30.00, +87.18, -16.54, -1.24, -61.79, -29.99] speed=20 wait=1.0s (pre max|Δ|=2.19°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=2.19° (tol 3.0°)
[002] 推論 702ms 10ステップ先 Δ= -0.2 -0.7 +1.9 -1.0 +4.3 -0.3
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk002: send_angles (single) [ -30.03, +87.02, -15.90, -1.95, -59.36, -30.26] speed=20 wait=1.0s (pre max|Δ|=4.09°, post-send timeout 8.0s)
[SEND] [TIMEOUT] 8.0s 内に未到達: max|Δ|=7.34° actual=[ -29.88, +87.36, -16.17, -2.02, -66.70, -29.79]
[SEND] リトライしません: pre→last max|Δ| 3.25° >= 1.0° (部分移動/ストール疑い)
[WARN] 未到達. j4 の発熱などを確認してください
[003] 推論 1560ms 10ステップ先 Δ= -0.0 -0.2 +1.4 -0.7 +3.2 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk003: send_angles (single) [ -29.91, +87.18, -14.76, -2.73, -63.53, -29.80] speed=20 wait=1.0s (pre max|Δ|=2.82°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=2.82° (tol 3.0°)
[004] 推論 875ms 10ステップ先 Δ= -0.3 -0.2 +2.2 -0.9 +5.8 -0.2
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk004: send_angles (single) [ -30.22, +87.16, -13.56, -3.26, -60.57, -30.03] speed=20 wait=1.0s (pre max|Δ|=5.78°, post-send timeout 8.0s)
[SEND] [TIMEOUT] 8.0s 内に未到達: max|Δ|=5.78° actual=[ -29.88, +87.36, -15.02, -2.90, -66.35, -29.79]
[RETRY] 1回目送信が無視された(pre→last max|Δ| 0.71°)。5.0s 待って再送します
[RETRY] pi05_chunk004 (retry): send_angles (single) [ -30.22, +87.16, -13.56, -3.26, -60.57, -30.03] speed=20 wait=1.0s (pre max|Δ|=5.78°, post-send timeout 8.0s)
j4 に −59.36° を指令すると、届くどころか −66.70° まで下がりました。−60.57° の指令は、ほとんど受け付けられていません。画面の出力はここまでしか残していないので、このあとは記録から読みます。
cd $HOME/mnt/r2r2r-data/runs/pi05_policy && for d in pi05_20261017_1723* pi05_20261017_1725* pi05_20261017_1728* pi05_20261017_1730* pi05_20261017_1745* pi05_20261017_1746*; do echo "== $d"; ls $d | head; ls $d/frames 2>/dev/null | wc -l; python3 - "$d" <<'PY'
import json,sys,numpy as np
d=sys.argv[1]
L=[json.loads(l) for l in open(f"{d}/log.jsonl")]
for c in L:
s=np.array(c["state"]);
print(c["chunk"], "lat",c["latency_s"], "used",c["used_step"],"exec",c["executed"],"arr",c.get("arrived"),"state",np.round(s[:6],1).tolist(),"target",np.round(c["target"],1).tolist(),"notes",c["notes"],"grip",c["grip_cmd"])
PY
done 2>&1 | head -120
== pi05_20261017_172323
frames
log.jsonl
5
0 lat 54.872 used 4 exec False arr None state [-0.3, 1.2, 1.8, -0.1, -0.3, -30.2] target [-6.3, 17.3, -6.0, -0.1, -13.3, -30.3] notes [] grip None
1 lat 0.572 used 4 exec False arr None state [-0.3, 1.2, 1.8, -0.1, -0.3, -30.2] target [-1.1, 3.6, 1.2, 0.7, -1.2, -30.4] notes [] grip None
2 lat 0.584 used 4 exec False arr None state [-0.3, 1.2, 1.8, -0.1, -0.3, -30.2] target [-0.3, 1.4, 1.8, -0.1, -0.1, -30.2] notes [] grip None
3 lat 1.552 used 4 exec False arr None state [-0.3, 1.2, 1.8, -0.1, -0.3, -30.2] target [-0.4, 1.2, 2.0, -0.2, -0.2, -30.2] notes [] grip None
4 lat 0.743 used 4 exec False arr None state [-0.3, 1.2, 1.8, -0.1, -0.3, -30.2] target [-4.4, 12.4, -2.9, -0.6, -9.6, -30.3] notes [] grip None
== pi05_20261017_172526
frames
log.jsonl
12
0 lat 0.745 used 4 exec True arr True state [-0.3, 1.2, 1.8, -0.1, -0.3, -30.2] target [-4.9, 15.7, -4.4, -0.5, -12.1, -30.4] notes [] grip None
1 lat 0.624 used 4 exec True arr True state [-4.9, 16.7, -2.2, 0.0, -10.9, -30.2] target [-11.9, 32.6, -8.2, 0.4, -25.6, -30.5] notes [] grip None
2 lat 0.609 used 4 exec True arr True state [-11.9, 33.6, -6.2, 0.1, -25.2, -30.2] target [-18.0, 50.1, -12.3, 0.0, -38.8, -30.4] notes [] grip None
3 lat 0.649 used 4 exec True arr True state [-17.8, 51.3, -10.4, 0.1, -38.3, -30.2] target [-23.9, 67.1, -16.4, 0.2, -53.6, -30.3] notes [] grip None
4 lat 1.549 used 4 exec True arr True state [-23.7, 68.3, -14.8, 0.2, -53.9, -30.2] target [-29.2, 82.4, -20.3, 0.1, -65.6, -30.3] notes [] grip None
5 lat 0.649 used 4 exec True arr None state [-29.6, 83.5, -19.0, 0.7, -65.6, -30.2] target [-30.1, 83.8, -19.2, 0.4, -65.2, -30.3] notes [] grip None
6 lat 0.56 used 4 exec True arr True state [-29.6, 83.5, -19.0, 0.7, -65.6, -30.2] target [-29.9, 83.8, -18.5, 0.2, -64.5, -30.5] notes [] grip None
7 lat 0.631 used 4 exec True arr True state [-29.6, 84.9, -17.8, 0.5, -65.6, -30.2] target [-30.1, 85.1, -18.0, 0.2, -65.4, -30.3] notes [] grip None
8 lat 1.532 used 4 exec True arr True state [-29.6, 86.1, -17.7, 0.5, -65.8, -30.0] target [-29.9, 86.1, -17.7, 0.3, -65.1, -30.0] notes [] grip None
9 lat 0.563 used 4 exec True arr True state [-29.6, 86.8, -17.7, 0.4, -66.3, -30.0] target [-29.8, 86.7, -17.3, -0.1, -64.2, -30.0] notes [] grip None
10 lat 1.632 used 4 exec True arr True state [-29.7, 87.3, -17.7, 0.2, -65.8, -30.0] target [-29.9, 87.1, -17.5, -0.4, -63.8, -30.0] notes [] grip None
11 lat 0.777 used 4 exec False arr None state [-29.7, 87.4, -17.8, 0.0, -64.9, -30.0] target [-29.8, 87.3, -17.5, -0.3, -63.7, -30.0] notes [] grip None
== pi05_20261017_172828
frames
log.jsonl
7
0 lat 0.83 used 9 exec True arr True state [-29.7, 87.7, -18.2, -0.2, -64.9, -30.0] target [-30.1, 87.1, -17.1, -1.4, -61.6, -30.1] notes [] grip None
1 lat 0.734 used 9 exec True arr True state [-29.8, 87.7, -17.8, -0.8, -64.6, -30.0] target [-30.0, 87.2, -16.5, -1.2, -61.8, -30.0] notes [] grip None
2 lat 0.702 used 9 exec True arr False state [-29.8, 87.7, -17.8, -1.0, -63.6, -30.0] target [-30.0, 87.0, -15.9, -2.0, -59.4, -30.3] notes [] grip None
3 lat 1.56 used 9 exec True arr True state [-29.9, 87.4, -16.2, -2.0, -66.7, -29.8] target [-29.9, 87.2, -14.8, -2.7, -63.5, -29.8] notes [] grip None
4 lat 0.875 used 9 exec True arr False state [-29.9, 87.4, -15.7, -2.4, -66.3, -29.8] target [-30.2, 87.2, -13.6, -3.3, -60.6, -30.0] notes [] grip None
5 lat 1.558 used 9 exec True arr False state [-29.9, 87.4, -15.0, -2.9, -66.3, -29.8] target [-29.8, 86.9, -12.4, -3.4, -60.8, -30.0] notes [] grip None
6 lat 0.774 used 9 exec False arr None state [-29.8, 87.7, -12.9, -3.6, -70.8, -29.6] target [-29.4, 86.0, -9.9, -4.3, -63.8, -29.9] notes [] grip None
== pi05_20261017_173054
frames
log.jsonl
10
0 lat 0.609 used 9 exec True arr True state [-0.3, 1.5, 0.7, -0.2, 56.0, -30.2] target [-10.5, 21.5, -12.5, -3.2, 36.0, -30.3] notes ['j1 移動量 +29.9° を ±20° に制限', 'j4 移動量 -36.4° を ±20° に制限'] grip None
1 lat 0.71 used 9 exec True arr True state [-10.4, 22.5, -10.6, -2.3, 35.3, -30.3] target [-25.6, 42.5, -25.3, -6.2, 15.3, -30.4] notes ['j1 移動量 +28.8° を ±20° に制限', 'j4 移動量 -39.9° を ±20° に制限'] grip None
2 lat 1.562 used 9 exec True arr True state [-26.0, 43.6, -23.6, -5.9, 16.5, -30.4] target [-39.2, 63.6, -37.3, -9.5, -3.5, -30.6] notes ['j1 移動量 +28.3° を ±20° に制限', 'j4 移動量 -39.8° を ±20° に制限'] grip None
3 lat 0.633 used 9 exec True arr True state [-39.0, 64.9, -35.7, -8.9, -2.5, -30.4] target [-45.4, 75.3, -39.7, -9.5, -16.8, -30.4] notes [] grip None
4 lat 0.772 used 9 exec True arr True state [-44.9, 76.3, -38.2, -9.1, -17.9, -30.1] target [-48.3, 82.3, -39.9, -9.7, -23.5, -30.2] notes [] grip None
5 lat 1.702 used 9 exec True arr True state [-47.7, 82.8, -39.5, -9.2, -26.4, -29.5] target [-50.2, 86.5, -40.7, -9.9, -31.9, -29.5] notes [] grip None
6 lat 0.786 used 9 exec True arr True state [-49.5, 86.2, -40.3, -9.6, -31.2, -29.5] target [-50.7, 87.3, -40.9, -10.0, -32.6, -29.4] notes [] grip None
7 lat 1.611 used 9 exec True arr True state [-49.8, 87.3, -40.8, -10.2, -32.8, -29.5] target [-50.3, 88.1, -40.6, -10.4, -32.9, -29.3] notes [] grip None
8 lat 0.774 used 9 exec True arr True state [-49.9, 87.5, -40.9, -10.2, -33.0, -29.5] target [-50.5, 88.0, -41.0, -10.6, -34.1, -29.4] notes [] grip None
9 lat 0.755 used 9 exec False arr None state [-49.9, 87.8, -40.9, -10.2, -33.8, -29.5] target [-50.8, 88.8, -41.3, -10.9, -36.2, -29.2] notes [] grip None
== pi05_20261017_174533
frames
log.jsonl
1
0 lat 1.408 used 4 exec False arr None state [-0.2, 1.3, 0.9, -0.3, -0.3, -30.2] target [-0.0, 1.4, 1.0, -0.4, -0.2, -30.2] notes [] grip None
== pi05_20261017_174600
frames
log.jsonl
10
0 lat 0.831 used 9 exec True arr True state [-0.2, 1.3, 0.9, -0.3, -0.3, -30.2] target [-1.2, 4.4, -0.2, -0.5, -2.5, -30.3] notes [] grip None
1 lat 0.595 used 9 exec True arr True state [-1.5, 5.4, 0.9, -0.2, -1.2, -30.2] target [-3.6, 8.6, -1.3, 0.1, -4.5, -30.3] notes [] grip None
2 lat 0.562 used 9 exec True arr True state [-4.0, 9.5, 0.8, 0.1, -4.2, -30.2] target [-15.0, 29.5, -10.8, -1.8, -24.2, -31.7] notes ['j1 移動量 +30.0° を ±20° に制限', 'j4 移動量 -24.7° を ±20° に制限'] grip None
3 lat 0.709 used 9 exec True arr True state [-14.9, 30.6, -9.0, -0.9, -23.9, -30.8] target [-28.7, 50.6, -23.0, -2.4, -43.9, -31.6] notes ['j1 移動量 +34.8° を ±20° に制限', 'j4 移動量 -28.8° を ±20° に制限'] grip None
4 lat 1.572 used 9 exec True arr True state [-28.4, 51.8, -21.4, -2.5, -43.4, -31.1] target [-40.0, 71.8, -34.8, -4.3, -63.4, -31.5] notes ['j1 移動量 +30.1° を ±20° に制限', 'j4 移動量 -25.0° を ±20° に制限'] grip None
5 lat 0.6 used 9 exec True arr True state [-39.7, 72.9, -33.3, -3.8, -63.2, -31.2] target [-46.1, 88.5, -39.4, -5.2, -69.5, -31.7] notes [] grip None
6 lat 0.742 used 9 exec True arr True state [-46.2, 89.7, -37.9, -5.1, -69.3, -31.4] target [-46.0, 89.7, -36.3, -4.8, -68.0, -31.2] notes [] grip None
7 lat 1.563 used 9 exec True arr False state [-46.2, 90.8, -35.4, -5.1, -69.3, -31.4] target [-45.9, 90.6, -33.2, -5.3, -58.8, -31.4] notes [] grip None
8 lat 0.63 used 9 exec True arr False state [-46.3, 90.9, -34.5, -5.1, -65.1, -31.4] target [-46.4, 91.1, -32.9, -5.3, -58.6, -30.3] notes [] grip None
9 lat 0.649 used 9 exec False arr None state [-46.3, 90.9, -34.5, -5.1, -65.0, -31.4] target [-46.4, 91.1, -32.7, -5.1, -61.1, -29.8] notes [] grip None
この記録は、この回の実機・ドライラン6回分すべてです(171726 はサーバーにつながらなかった回なので、記録がありません)。pi05_20261017_172828 を見ると、j4 は指令のたびに届かず(arr False)、最後は −70.8° まで垂れていました。
伸ばした姿勢のまま4〜5分止まっていたので、Claude はここで「手首のサーボが熱を持った」と見立てました。10月15日・16日にお手本を集めたときも、j4 が熱を持つと指令した角度に届かなくなることがあったためです。HOME に戻して10分休ませる案内を出しました。
h で HOME に戻したはずの手首が、+56° のまま次の回が始まった
h(HOME に戻して終了)を押し、休ませずにすぐ、HOME から通しで送りました(17時30分)。この回の画面は、先頭の数行(安全範囲と接続の表示)を貼り損ねていて残っていません。
[GRIP ] 開始時にグリッパーを開く
[Pi] GRIPPER: OPEN result= -1
[000] 推論 609ms 10ステップ先 Δ=-10.1 +20.0 -13.2 -3.0 -20.0 -0.1
[LIMIT] j1 移動量 +29.9° を ±20° に制限
[LIMIT] j4 移動量 -36.4° を ±20° に制限
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk000: send_angles (single) [ -10.48, +21.49, -12.46, -3.17, +35.98, -30.30] speed=20 wait=1.0s (pre max|Δ|=20.97°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.83° (tol 3.0°)
[001] 推論 710ms 10ステップ先 Δ=-15.2 +20.0 -14.7 -3.9 -20.0 -0.1
[LIMIT] j1 移動量 +28.8° を ±20° に制限
[LIMIT] j4 移動量 -39.9° を ±20° に制限
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk001: send_angles (single) [ -25.62, +42.50, -25.30, -6.16, +15.33, -30.43] speed=20 wait=1.0s (pre max|Δ|=20.17°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.66° (tol 3.0°)
[002] 推論 1562ms 10ステップ先 Δ=-13.2 +20.0 -13.7 -3.6 -20.0 -0.2
[LIMIT] j1 移動量 +28.3° を ±20° に制限
[LIMIT] j4 移動量 -39.8° を ±20° に制限
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk002: send_angles (single) [ -39.16, +63.59, -37.33, -9.49, -3.48, -30.62] speed=20 wait=1.0s (pre max|Δ|=20.00°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.65° (tol 3.0°)
[003] 推論 633ms 10ステップ先 Δ= -6.3 +10.4 -4.0 -0.6 -14.3 +0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk003: send_angles (single) [ -45.36, +75.27, -39.65, -9.49, -16.77, -30.41] speed=20 wait=1.0s (pre max|Δ|=12.56°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.42° (tol 3.0°)
[004] 推論 772ms 10ステップ先 Δ= -3.3 +6.1 -1.7 -0.6 -5.6 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk004: send_angles (single) [ -48.26, +82.35, -39.91, -9.71, -23.55, -30.16] speed=20 wait=1.0s (pre max|Δ|=6.07°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=2.81° (tol 3.0°)
[005] 推論 1702ms 10ステップ先 Δ= -2.5 +3.7 -1.2 -0.7 -5.6 +0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk005: send_angles (single) [ -50.24, +86.50, -40.71, -9.94, -31.92, -29.48] speed=20 wait=1.0s (pre max|Δ|=6.26°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=0.76° (tol 3.0°)
[006] 推論 786ms 10ステップ先 Δ= -1.3 +1.1 -0.6 -0.4 -1.4 +0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk006: send_angles (single) [ -50.74, +87.34, -40.95, -9.99, -32.63, -29.38] speed=20 wait=1.0s (pre max|Δ|=1.26°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=0.91° (tol 3.0°)
[007] 推論 1611ms 10ステップ先 Δ= -0.5 +0.9 +0.2 -0.2 -0.2 +0.2
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk007: send_angles (single) [ -50.32, +88.13, -40.62, -10.35, -32.94, -29.32] speed=20 wait=1.0s (pre max|Δ|=0.86°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=0.60° (tol 3.0°)
[008] 推論 774ms 10ステップ先 Δ= -0.6 +0.4 -0.1 -0.5 -1.1 +0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk008: send_angles (single) [ -50.52, +87.97, -41.00, -10.65, -34.10, -29.44] speed=20 wait=1.0s (pre max|Δ|=1.15°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=0.60° (tol 3.0°)
[009] 推論 755ms 10ステップ先 Δ= -0.9 +1.0 -0.5 -0.7 -2.4 +0.3
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
上の記録(pi05_20261017_173054)の chunk0 を見ると、始めたときの j4 は +56.0° でした。HOME は j4 = 0° です。h で HOME に戻したとき、j4 だけが戻らず、反対側の +56° で止まっていたことになります。
モデルは、学習で見たことのない手首の角度から始めることになりました。j4 を 20° ずつ(1回の上限)下げ続けましたが、ブロックの上に来た時点で j4 は −33.8° です。お手本のこの位置では −62° 前後なので、手首の角度が約30°ずれたまま、予測は 1° 前後の小さな動きを繰り返すだけになりました。j0(ベース)は −49.9° まで回っていて、これもお手本のつかむ位置(−28〜−42°)より外です。
j4 が 0° を目指して +54° 付近で止まる現象は、過去にも記録があります。j4_isolation_test.py(7月の j4 単体の実験)の説明には、こう書いてあります。
仮説 (07-04 の 12 ラン実測から更新):
j4 は絶対値 54° 近傍を両方向で通過できず停止する.
- 負方向: -54° 付近 (stage3/6 の j4=-70.11 目標が -54° で停止)
- 正方向: +54° 付近 (stage10 の j4=0 目標が +54° で停止)
この回の h の HOME 復帰は、クライアントの出力を残しておらず、到達したかどうかの表示も記録にありません。+56° になった仕組みは確かめていません。
q で終えて、お手本を集めたスクリプトの HOME 復帰を使い、j4 が 0° に戻るかを確かめました(17時45分)。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % cd ~/r2r2r-scripts && ./run_pick_place.sh --human-judge
======================================================================
[LAUNCH] dynamic_tracking_limits/place/place_modules/run_pick_place.py
[CWD ] /Users/shimazueiichi/r2r2r-scripts
[PYTHON] python3
======================================================================
[LOOP] 常駐モード開始. コマンド: n または Enter=次試行 / h=HOME 復帰 / q=終了
[LOOP] 注意: 前試行が把持中に失敗 (HOME 未到達で握ったまま等) した場合, 次試行 stage1 の gripper open で空中落下し得る. その場合は h で HOME を試行してから n で次試行へ進むこと.
[LOOP] 次の操作 [n=次試行 (Enter可) / h=HOME / q=終了]: h
[HOME ] 手動 HOME 復帰開始 (試行外・台帳追記なし)
[SEND] manual_home: send_angles (single) [ +0.00, +0.00, +0.00, +0.00, +0.00, -30.00] speed=20 wait=1.0s (pre max|Δ|=87.80°, post-send timeout 15.2s)
[SEND] arrival OK: max|Δ|=2.46° (tol 2.5°)
[HOME ] 手動 HOME 完了 (arrived=True)
[LOOP] 次の操作 [n=次試行 (Enter可) / h=HOME / q=終了]: q
[LOOP] q 受領 → 常駐モード終了
[LOOP] 常駐モード終了. 実行試行数 = 0
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % cd ~/r2r2r-scripts && python3 pi05/run_policy_client.py --max-chunks 1
[SAFE ] 安全範囲 (学習 20 本 ±10°): j0[-52,+96] j1[-9,+99] j2[-52,+30] j3[-24,+32] j4[-90,+60] j5[-51,-19]
[MODE ] DRY-RUN (アームには送らない) steps/chunk=5 max_move=20.0° log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_174533
[POLICY] 接続 ws://localhost:8000 metadata={}
[000] 推論 1408ms 5ステップ先 Δ= +0.2 +0.1 +0.1 -0.1 +0.1 +0.0
[DONE ] 記録: /Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_174533
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts %
ドライランで「HOME から離れています」が出ないので、全部の関節が HOME から 5° 以内です。記録では j4 = −0.3° でした。
HOME から通しで送ると、ブロックの真上で手首が止まった
HOME からもう一度、1秒先ずつで送りました(17時46分)。予測が出たらすぐ Enter を押し、伸ばした姿勢で止まる時間を短くしています。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % python3 pi05/run_policy_client.py --execute --steps-per-chunk 10
[SAFE ] 安全範囲 (学習 20 本 ±10°): j0[-52,+96] j1[-9,+99] j2[-52,+30] j3[-24,+32] j4[-90,+60] j5[-51,-19]
[MODE ] EXECUTE steps/chunk=10 max_move=20.0° log=/Volumes/KIOXIA/r2r2r-data/runs/pi05_policy/pi05_20261017_174600
[POLICY] 接続 ws://localhost:8000 metadata={}
[GRIP ] 開始時にグリッパーを開く
[Pi] GRIPPER: OPEN result= -1
[000] 推論 831ms 10ステップ先 Δ= -1.0 +3.1 -1.0 -0.2 -2.3 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk000: send_angles (single) [ -1.21, +4.42, -0.16, -0.47, -2.53, -30.27] speed=20 wait=1.0s (pre max|Δ|=3.11°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.39° (tol 3.0°)
[001] 推論 595ms 10ステップ先 Δ= -2.1 +3.2 -2.1 +0.3 -3.3 -0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk001: send_angles (single) [ -3.61, +8.58, -1.27, +0.14, -4.49, -30.28] speed=20 wait=1.0s (pre max|Δ|=3.26°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=2.06° (tol 3.0°)
[002] 推論 562ms 10ステップ先 Δ=-11.0 +20.0 -11.6 -1.9 -20.0 -1.4
[LIMIT] j1 移動量 +30.0° を ±20° に制限
[LIMIT] j4 移動量 -24.7° を ±20° に制限
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk002: send_angles (single) [ -15.00, +29.49, -10.79, -1.84, -24.21, -31.67] speed=20 wait=1.0s (pre max|Δ|=20.00°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.83° (tol 3.0°)
[003] 推論 709ms 10ステップ先 Δ=-13.8 +20.0 -14.1 -1.6 -20.0 -0.8
[LIMIT] j1 移動量 +34.8° を ±20° に制限
[LIMIT] j4 移動量 -28.8° を ±20° に制限
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk003: send_angles (single) [ -28.71, +50.58, -23.02, -2.43, -43.90, -31.59] speed=20 wait=1.0s (pre max|Δ|=20.00°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.67° (tol 3.0°)
[004] 推論 1572ms 10ステップ先 Δ=-11.6 +20.0 -13.4 -1.8 -20.0 -0.4
[LIMIT] j1 移動量 +30.1° を ±20° に制限
[LIMIT] j4 移動量 -25.0° を ±20° に制限
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk004: send_angles (single) [ -39.99, +71.76, -34.76, -4.30, -63.41, -31.53] speed=20 wait=1.0s (pre max|Δ|=20.00°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.45° (tol 3.0°)
[005] 推論 600ms 10ステップ先 Δ= -6.3 +15.6 -6.0 -1.4 -6.3 -0.5
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk005: send_angles (single) [ -46.06, +88.51, -39.36, -5.18, -69.53, -31.69] speed=20 wait=1.0s (pre max|Δ|=15.57°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.48° (tol 3.0°)
[006] 推論 742ms 10ステップ先 Δ= +0.2 -0.0 +1.6 +0.3 +1.4 +0.1
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk006: send_angles (single) [ -45.99, +89.72, -36.27, -4.83, -67.99, -31.25] speed=20 wait=1.0s (pre max|Δ|=1.61°, post-send timeout 8.0s)
[SEND] arrival OK: max|Δ|=1.35° (tol 3.0°)
[007] 推論 1563ms 10ステップ先 Δ= +0.3 -0.2 +2.2 -0.2 +10.5 -0.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk007: send_angles (single) [ -45.93, +90.64, -33.17, -5.33, -58.84, -31.39] speed=20 wait=1.0s (pre max|Δ|=10.24°, post-send timeout 8.0s)
[SEND] [TIMEOUT] 8.0s 内に未到達: max|Δ|=6.28° actual=[ -46.31, +90.87, -34.45, -5.09, -65.12, -31.37]
[SEND] リトライしません: pre→last max|Δ| 3.96° >= 1.0° (部分移動/ストール疑い)
[WARN] 未到達. j4 の発熱などを確認してください
[008] 推論 630ms 10ステップ先 Δ= -0.1 +0.1 +1.5 -0.2 +6.5 +1.0
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
[SEND] pi05_chunk008: send_angles (single) [ -46.39, +90.98, -32.91, -5.33, -58.60, -30.33] speed=20 wait=1.0s (pre max|Δ|=6.43°, post-send timeout 8.0s)
[SEND] [TIMEOUT] 8.0s 内に未到達: max|Δ|=6.43° actual=[ -46.31, +90.87, -34.45, -5.09, -65.03, -31.37]
[RETRY] 1回目送信が無視された(pre→last max|Δ| 0.00°)。5.0s 待って再送します
[RETRY] pi05_chunk008 (retry): send_angles (single) [ -46.39, +90.98, -32.91, -5.33, -58.60, -30.33] speed=20 wait=1.0s (pre max|Δ|=6.43°, post-send timeout 8.0s)
[RETRY] 失敗。ABORT へ. 8.0s 内に未到達: max|Δ|=6.43° actual=[ -46.31, +90.87, -34.45, -5.09, -65.03, -31.37]
[WARN] 未到達. j4 の発熱などを確認してください
[009] 推論 649ms 10ステップ先 Δ= -0.1 +0.3 +1.7 +0.0 +3.9 +1.5
Enter=実行 / s=送らず次へ / h=HOMEに戻して終了 / q=終了 >
chunk0〜1 はお手本の「HOME で1秒止まる」に当たる小さな動きで、chunk2〜5 の4回でブロックの上まで腕を伸ばしました。chunk7 で π0.5 は、j4 を +10.5°、j2 を +2.2° 動かす指令を出しています。お手本の「グリッパーの先を下ろす」動きと同じ向きです。j4 は −69.3° から −65.1° まで 4° 動いて止まり、chunk8 の指令は 0.00° も動きませんでした。
上のカメラの画像(π0.5 に送った入力そのもの)を並べると、chunk9 では、グリッパーの手前の爪がブロックの上にかかって見えます。

各 chunk の関節角を、お手本19本がグリッパーを閉じた瞬間の範囲と並べました。お手本の範囲は、次のスクリプトで出しています。
ls $HOME/mnt/r2r2r-data/runs/pi05_policy/pi05_20261017_174600/frames; python3 -c "from PIL import Image; print(Image.open('pi05_20261017_174600/frames/0000.jpg').size)"; cd $HOME/mnt/r2r2r-data/datasets/mecharm_redblock_box_scripted_v1/raw_episodes && python3 - <<'PY'
import numpy as np, glob, json
eps=[d for d in sorted(glob.glob("run_*")) if not d.endswith("182552")]
# demo j-trajectories from motion start to grasp: resample by step index relative to motion start
out={}
A=[]
for d in eps:
s=np.load(f"{d}/episode.npz")["state"]
mv=np.where(np.abs(s[:,:6]-s[0,:6]).max(1)>1)[0][0]
g=np.where(np.diff(s[:,6])>0.5)[0][0]+1
A.append(dict(start=int(mv),grasp=int(g),pre=np.round(s[mv+25,:6],1).tolist(),grasp_pose=np.round(s[g,:6],1).tolist()))
pre=np.array([a["pre"] for a in A]); gp=np.array([a["grasp_pose"] for a in A])
print("n",len(A))
print("pre(動き出し+2.5s) mean",np.round(pre.mean(0),1).tolist(),"min",np.round(pre.min(0),1).tolist(),"max",np.round(pre.max(0),1).tolist())
print("grasp mean",np.round(gp.mean(0),1).tolist(),"min",np.round(gp.min(0),1).tolist(),"max",np.round(gp.max(0),1).tolist())
print("grasp step from motion start", [a["grasp"]-a["start"] for a in A])
# mean trajectory j4 and j2 per 10 steps from motion start up to 80
T=[]
for d in eps:
s=np.load(f"{d}/episode.npz")["state"]
mv=np.where(np.abs(s[:,:6]-s[0,:6]).max(1)>1)[0][0]
T.append(s[mv-1:mv-1+80,:6])
T=np.array(T); m=T.mean(0)
json.dump({"mean":np.round(m,2).tolist(),"lo":np.round(T.min(0),2).tolist(),"hi":np.round(T.max(0),2).tolist()},open("/tmp/demo_traj.json","w"))
for k in range(0,80,10): print(k, np.round(m[k],1).tolist())
PY
0000.jpg
0001.jpg
0002.jpg
0003.jpg
0004.jpg
0005.jpg
0006.jpg
0007.jpg
0008.jpg
0009.jpg
(384, 216)
n 19
pre(動き出し+2.5s) mean [-32.9, 82.3, -33.8, 1.4, -63.2, -32.5] min [-41.6, 10.3, -41.7, -12.4, -72.4, -39.6] max [-4.3, 89.0, -2.5, 22.3, -6.4, -29.7]
grasp mean [-36.0, 88.3, -31.2, -0.5, -51.5, -31.5] min [-42.3, 86.0, -33.4, -12.5, -57.4, -36.2] max [-28.3, 89.1, -28.0, 18.0, -47.8, -29.1]
grasp step from motion start [69, 69, 65, 71, 78, 73, 74, 316, 79, 75, 69, 72, 68, 69, 68, 70, 70, 68, 72]
0 [-0.20000000298023224, 1.600000023841858, 1.7999999523162842, 0.10000000149011612, 7.300000190734863, -29.700000762939453]
10 [-12.600000381469727, 32.20000076293945, -11.699999809265137, 0.6000000238418579, -19.399999618530273, -30.799999237060547]
20 [-25.299999237060547, 63.5, -25.5, 1.2000000476837158, -46.79999923706055, -31.899999618530273]
30 [-33.29999923706055, 83.30000305175781, -34.29999923706055, 1.399999976158142, -64.19999694824219, -32.599998474121094]
40 [-33.5, 83.5, -34.0, 1.399999976158142, -64.0, -32.5]
50 [-33.79999923706055, 84.0, -32.400001525878906, 0.800000011920929, -58.599998474121094, -32.099998474121094]
60 [-34.20000076293945, 84.5, -30.700000762939453, 0.20000000298023224, -52.70000076293945, -31.700000762939453]
70 [-34.400001525878906, 84.9000015258789, -30.0, -0.20000000298023224, -49.70000076293945, -31.600000381469727]
(pre の min/max には、つかみ直しをした長い1本(524ステップ)が混ざっているので、幅が大きく出ています。)

| 関節 | π0.5 が止まった角度 | お手本がつかんだ瞬間(19本の範囲) |
|---|---|---|
| j0 ベース | −46.3° | −42.3〜−28.3°(平均 −36.0°) |
| j1 肩 | +90.9° | +86.0〜+89.1° |
| j2 肘 | −34.5° | −33.4〜−28.0° |
| j4 手首 | −65.0° | −57.4〜−47.8°(平均 −51.5°) |
肘はほぼつかむ位置まで来ていて、手首だけが 8° 以上足りません。ベースは、お手本のどの回よりも 4° 以上外側まで回っていました。
2回目の 未到達 のあと、Claude は「この日はここで止める」と案内し、この回はここで送るのをやめました。
見立てを2回外した ― 熱でも、指令の小ささでもなかった
この時点で Claude が出していた見立ては「手首の熱」でした。止める理由として、熱を挙げて案内したところ、こう返ってきました。
発熱なんてしてないよ。
手首を触っても熱くなかった、ということです。Claude は熱の見立てを取り下げ、止まり方(3〜4°動いてから止まり、その後は指令を受け付けない)が何かに当たったときと同じだとして、「グリッパーの先がブロックかマットに当たっているのでは」と尋ねました。
横にずれていた 高さはブロックの少し上だね
ブロックの横にずれていて、高さはブロックの少し上。当たってはいない、という答えでした。Claude はここで当たっている線を外し、お手本を集めたときの記録と比べました。同じ姿勢で、スクリプトは手首をどう動かしていたかです。
まず、π0.5 のクライアントとスクリプトで、アームへの送り方が違わないかを見ました。
cd $HOME/mnt/r2r2r-scripts && grep -n "speed" pi05/run_policy_client.py | head; echo ===; grep -rn "def _send_and_wait_arrival" --include=*.py . | head -3; echo ===; f=dynamic_tracking_limits/place/place_modules/run_pick_place.py; grep -n "speed=" $f | head -40
===
./dynamic_tracking_limits/place/place_modules/j0_sweep_scan.py:189:def _send_and_wait_arrival(target, label, reach_tol_deg, retry_axis=None,
===
cd $HOME/mnt/r2r2r-scripts/dynamic_tracking_limits/place/place_modules && sed -n 189,215p j0_sweep_scan.py; grep -n "SPEED\|speed" j0_sweep_scan.py | head -20
def _send_and_wait_arrival(target, label, reach_tol_deg, retry_axis=None,
send_wait_sec=SEND_WAIT_SEC, observer=None):
"""target を単発 send_angles で送信し、tol 内に入るまで到達確認する。
本スクリプトは set_gripper(close) を一切呼ばないため、DAY228 で問題となった
「close 直後の単発 send_angles 沈黙」事象は本スクリプトには該当しない。
一方、ストリーミング送信(0.1s 周期連打)は PICK 基準姿勢からのスイープ移動で
コントローラに完全無視される事象が実機2セッションで再現したため、
08_home_from_anywhere 等で実績のある単発 send_angles 方式に統一する。
手順:
1. 送信前姿勢を取得し、wr._compute_arrival_timeout(pre_max) で timeout 算出
(送信前姿勢が取れない場合は ABORT。fallback はしない)
2. target を 1 回だけ daemon_send_angles(target, speed=SPEED, wait=send_wait_sec) で送信
(送信回数は増やさず、daemon側の送信直後 get_angles ログを少し遅らせる)
3. timeout 窓で get_angles 実測ポーリング、tol reach_tol_deg で到達判定
4. 1回目がタイムアウトした場合、pre_actual と最後の実測 last_actual の移動量を見て
RETRY_NO_MOVE_THRESHOLD_DEG 未満なら「送信無視」とみなし、RETRY_PAUSE_SEC 待って
現在姿勢を取り直して target を1回だけ単発再送(最大1回・ループしない)。
1.0° 以上動いている(部分移動/ストール)場合はリトライしない。
base移動は全軸 max|Δ|、j0スイープ点は j0 の移動量で判定する。
返り値: (success: bool, retried: bool)。
retried は「タイムアウト時に1回限りの再送を実行したか」のフラグ。
ABORT 経路は変更しない。
DAY287 R2 A1: 任意引数 observer(ts_send, ts_arrival, attempt, arrived) を追加.
102:SPEED = 20 # 全送信共通(task 仕様)
111:# (DAY228実績の式:BASE + max_delta/SPEED * FACTOR、MIN/MAX で clamp)。
202: 2. target を 1 回だけ daemon_send_angles(target, speed=SPEED, wait=send_wait_sec) で送信
241: print("[SEND] {}: send_angles (single) {} speed={} wait={:.1f}s "
243: label, _fmt_angles(target), SPEED, send_wait_sec, pre_max, timeout_sec))
248: daemon_send_angles(target, speed=SPEED, wait=send_wait_sec)
308: print("[RETRY] {} (retry): send_angles (single) {} speed={} wait={:.1f}s "
310: label, _fmt_angles(target), SPEED, send_wait_sec, pre_max_retry, timeout_retry_sec))
314: daemon_send_angles(target, speed=SPEED, wait=send_wait_sec)
801: "speed": SPEED,
送る関数も速さ(20)も同じです。次に、成功したお手本1本(10月16日 19:15:39)の記録で、つかむときの指令を見ました。
cd $HOME/mnt/r2r2r-data/runs/run_pick_place/run_0001_20261016_191539 && python3 - <<'PY'
import json
j=json.load(open("run_0001_20261016_191539.json"))
print("stages", j["stages"][:12] if isinstance(j["stages"],list) else j["stages"])
m=j["moves"]
print(type(m), len(m))
for v in (m[:8] if isinstance(m,list) else list(m.items())[:8]):
s=json.dumps(v,ensure_ascii=False); print(s[:420])
PY
stages ['stage1', 'stage2', 'stage3_approach_pick', 'stage4_pick', 'stage5', 'stage6', 'stage7_approach_place', 'stage8_place', 'stage8_5_replace_align', 'stage9', 'stage9b_judge_place', 'stage10']
<class 'list'> 11
{"label": "stage3_approach_pick", "target_angles": [-27.5, 83.0, -30.0, 1.4, -70.48, -32.87], "reach_tol_deg": 2.0, "pre_actual": [-0.08, 1.58, 1.93, 0.26, -0.26, -29.7], "pre_max_delta_deg": 81.42, "dry_run": false, "arrived": true, "retried": false, "post_actual": [-27.24, 84.19, -28.47, 1.05, -69.34, -32.6], "post_max_delta_deg": 1.5300000000000011}
{"label": "stage4_pick", "target_angles": [-30.0, 85.0, -30.0, -8.0, -50.0, -35.0], "reach_tol_deg": 3.0, "pre_actual": [-27.24, 84.19, -28.47, 1.05, -69.34, -32.6], "pre_max_delta_deg": 19.340000000000003, "dry_run": false, "arrived": true, "retried": false, "post_actual": [-29.53, 86.04, -28.91, -7.55, -51.67, -34.62], "post_max_delta_deg": 1.6700000000000017}
{"label": "stage6_lift", "target_angles": [-27.5, 83.0, -30.0, 1.4, -70.48, -32.87], "reach_tol_deg": 3.0, "pre_actual": [-29.53, 86.04, -29.0, -8.87, -51.59, -34.62], "pre_max_delta_deg": 18.89, "dry_run": false, "arrived": false, "retried": false, "post_actual": [-27.15, 85.95, -28.82, 1.31, -69.96, -33.13], "post_max_delta_deg": 2.950000000000003}
{"label": "stage7_approach_place", "target_angles": [38.76710137177105, 57.39, 17.49, 0.08, -89.2, -29.44], "reach_tol_deg": 3.0, "pre_actual": [-27.15, 85.86, -28.82, 1.31, -70.75, -33.04], "pre_max_delta_deg": 65.91710137177105, "dry_run": false, "arrived": true, "retried": false, "post_actual": [38.75, 60.11, 17.75, 0.0, -86.22, -29.0], "post_max_delta_deg": 2.980000000000004}
{"label": "stage8_place", "target_angles": [41.215, 57.0, 15.0, -7.0, -81.0, -30.0], "reach_tol_deg": 3.0, "pre_actual": [38.75, 60.11, 17.75, 0.0, -86.48, -29.0], "pre_max_delta_deg": 7.0, "dry_run": false, "arrived": true, "retried": false, "post_actual": [41.13, 59.67, 16.43, -6.76, -82.0, -29.17], "post_max_delta_deg": 2.6700000000000017}
{"label": "stage8_place_zservo_i1", "target_angles": [41.215, 57.0, 20.0, -7.0, -86.0, -30.0], "reach_tol_deg": 3.0, "pre_actual": [39.28, 60.82, 16.52, -6.59, -81.82, -29.17], "pre_max_delta_deg": 4.180000000000007, "dry_run": false, "arrived": true, "retried": false, "post_actual": [41.3, 59.5, 20.3, -7.11, -84.9, -29.35], "post_max_delta_deg": 2.5}
{"label": "stage8_place_zservo_i2", "target_angles": [41.215, 57.0, 20.0, -12.0, -86.0, -30.0], "reach_tol_deg": 3.0, "pre_actual": [41.3, 59.5, 20.3, -7.11, -85.07, -29.35], "pre_max_delta_deg": 4.89, "dry_run": false, "arrived": true, "retried": false, "post_actual": [41.13, 59.5, 20.3, -11.68, -85.07, -29.53], "post_max_delta_deg": 2.5}
{"label": "stage8_place_zservo_i3", "target_angles": [41.215, 57.0, 20.0, -12.0, -86.0, -30.0], "reach_tol_deg": 3.0, "pre_actual": [38.58, 59.5, 20.3, -11.77, -85.07, -29.7], "pre_max_delta_deg": 4.89, "dry_run": false, "arrived": true, "retried": false, "post_actual": [41.13, 59.5, 20.3, -12.12, -87.62, -29.7], "post_max_delta_deg": 2.5}
stage4_pick で、スクリプトは j4 を −69.34° から −50° まで、1回の指令で 19° 動かし、届いています。π0.5 は同じあたりの姿勢で 9〜10° の指令を出して、4° で止まりました。
| j4 への1回の指令 | 結果 | |
|---|---|---|
| スクリプト(成功した回) | −69.3° → −50°(19°) | 届いた |
| π0.5(17:46 の回) | −69.3° → −58.8°(10°) | 4° 動いて止まった |
Claude はこの表から「腕を伸ばした姿勢では手首に重さがかかっていて、大きく動かす指令なら重さに勝つが、小さく刻むと途中で力負けする」と見立てました。直すには、予測を2〜3回分つなげて大きく動かすようにクライアントを書き換える必要がある、という案です。
この見立ても外れでした。
動画で見ると、手前の爪がブロックの上に乗っていた
17時46分の回を、スマートフォンで横から撮った動画(約1分50秒)があります。4秒ごとにコマを抜いて並べました。
S=/tmp/claude-0/-home-claude/d71f313e-8fa7-5727-83e9-ec87ff098ade/scratchpad/vid; mkdir -p $S; cd $S; F="/root/.claude/uploads/d71f313e-8fa7-5727-83e9-ec87ff098ade/f4167a60-IMG_4124-compressed.mp4"; which ffmpeg ffprobe; ffprobe -v error -show_entries format=duration:stream=width,height,r_frame_rate -of compact "$F"
/usr/bin/ffmpeg
/usr/bin/ffprobe
stream|width=1280|height=720|r_frame_rate=24/1
stream|r_frame_rate=0/0
format|duration=109.435646
S=/tmp/claude-0/-home-claude/d71f313e-8fa7-5727-83e9-ec87ff098ade/scratchpad/vid; F="/root/.claude/uploads/d71f313e-8fa7-5727-83e9-ec87ff098ade/f4167a60-IMG_4124-compressed.mp4"; cd $S && ffmpeg -v error -y -i "$F" -vf "fps=1/4,scale=480:-1" f_%02d.jpg && ls | wc -l && python3 -c "
from PIL import Image, ImageDraw
import glob
fs=sorted(glob.glob('$S/f_*.jpg'))
ims=[Image.open(f) for f in fs]
w,h=ims[0].size
cols=4
for part in range(0,len(ims),16):
sub=ims[part:part+16]; rows=(len(sub)+cols-1)//cols
c=Image.new('RGB',(w*cols,h*rows),'white')
for i,im in enumerate(sub):
d=ImageDraw.Draw(im); d.rectangle([0,0,70,22],fill='black'); d.text((4,4),f'{(part+i)*4}s',fill='white')
c.paste(im,((i%cols)*w,(i//cols)*h))
c.save(f'$S/grid_{part//16}.jpg')
print('ok')"
27
ok
(このコマンドは、Claude のクラウドの作業環境で動かしたものです。動画は、チャットに添付されたものをそのまま使っています。)

グリッパーのあたりを拡大しました。
S=/tmp/claude-0/-home-claude/d71f313e-8fa7-5727-83e9-ec87ff098ade/scratchpad/vid; F="/root/.claude/uploads/d71f313e-8fa7-5727-83e9-ec87ff098ade/f4167a60-IMG_4124-compressed.mp4"; cd $S && for t in 30 50 70 100; do ffmpeg -v error -y -ss $t -i "$F" -frames:v 1 full_$t.jpg; done && python3 -c "
from PIL import Image
ims=[Image.open('$S/full_%d.jpg'%t).crop((40,330,560,620)) for t in (30,50,70,100)]
w,h=ims[0].size; c=Image.new('RGB',(w*2,h*2))
for i,im in enumerate(ims): c.paste(im,((i%2)*w,(i//2)*h))
c.save('$S/crop.jpg')"

30秒の時点では、ブロックは2本の爪の間の下にあります。そこから j2 と j4 が動いてグリッパーの先が下がると、50秒では手前の爪がブロックの上面に乗っていました。そのあと100秒まで、同じ形のままです。
これで、記録の数字が説明できます。
- j4 を上げると、グリッパーの先が下がる。お手本ではこれでブロックの高さまで下ろしている
- 爪の先がブロックの上面に当たると、それ以上は下がれない。j4 は 4° 動いたところで止まり、次の指令は 0.00° も動かなかった
- 「横にずれていた・高さはブロックの少し上」は、爪の間ではなく爪の真下にブロックがある状態を、真横から見たときの見え方と合う。上のカメラの chunk9 の画像でも、爪がブロックにかかっている
スクリプトが同じ姿勢で j4 を 19° 動かせたのは、2本の爪の間にブロックが入る位置で下ろしていたからで、指令の大きさの違いは原因ではありませんでした。「小さく刻むと力負けする」という見立てに沿ってクライアントを書き換えていたら、爪がブロックを押す力が大きくなるだけでした。
ずれの大きさは、動画で見て爪1本分(2〜3cm)です。関節で言えば、j0(ベース)がお手本のどの回よりも 4° 以上外側まで回っていて、これが横のずれになっていると考えています。
ブロックをお手本の範囲の外に置いてしまったのか、π0.5 が位置を読み違えたのかを分けるため、上のカメラの最初のコマで、赤いブロックの位置を比べました。赤い画素(R が 140 より大きく、G と B が 90 より小さい)の重心を出しています。
cd $HOME/mnt/r2r2r-data && python3 - <<'PY'
import glob, numpy as np
from PIL import Image
def red_centroid(p):
a=np.asarray(Image.open(p).convert("RGB")).astype(int)
r,g,b=a[...,0],a[...,1],a[...,2]
m=(r>140)&(g<90)&(b<90)&(r-g>70)
ys,xs=np.nonzero(m)
if len(xs)<5: return None
return round(xs.mean(),1), round(ys.mean(),1), int(m.sum())
eps=[d for d in sorted(glob.glob("datasets/mecharm_redblock_box_scripted_v1/raw_episodes/run_*")) if not d.endswith("182552")]
print("demo first frames (x,y,pixels):")
for d in eps:
f=sorted(glob.glob(d+"/frames/*.jpg"))[0]
s=np.load(d+"/episode.npz")["state"]; g=np.where(np.diff(s[:,6])>0.5)[0][0]+1
print(d[-15:], red_centroid(f), "grasp j0", round(float(s[g,0]),1))
for r in ["pi05_20261017_172526","pi05_20261017_173054","pi05_20261017_174600"]:
print(r, red_centroid(f"runs/pi05_policy/{r}/frames/0000.jpg"))
PY
demo first frames (x,y,pixels):
20261015_173315 None grasp j0 -41.7
20261015_175333 (np.float64(97.8), np.float64(154.1), 10) grasp j0 -41.8
20261015_181752 None grasp j0 -40.0
20261015_182056 (np.float64(105.3), np.float64(160.0), 7) grasp j0 -36.5
20261015_183310 (np.float64(107.2), np.float64(158.2), 10) grasp j0 -41.7
20261015_185536 (np.float64(109.3), np.float64(155.6), 7) grasp j0 -42.3
20261015_193907 None grasp j0 -37.6
20261015_194135 (np.float64(103.4), np.float64(153.0), 7) grasp j0 -41.5
20261015_194420 (np.float64(125.5), np.float64(167.7), 6) grasp j0 -31.5
20261016_175444 (np.float64(113.6), np.float64(149.4), 5) grasp j0 -32.3
20261016_175708 (np.float64(123.5), np.float64(175.9), 18) grasp j0 -30.0
20261016_180013 (np.float64(118.8), np.float64(167.0), 10) grasp j0 -28.3
20261016_184725 (np.float64(107.1), np.float64(153.5), 8) grasp j0 -41.5
20261016_185201 (np.float64(99.9), np.float64(141.3), 9) grasp j0 -37.0
20261016_190925 (np.float64(102.2), np.float64(157.9), 8) grasp j0 -31.9
20261016_191539 (np.float64(126.4), np.float64(165.9), 9) grasp j0 -29.5
20261016_191942 (np.float64(98.8), np.float64(144.6), 5) grasp j0 -38.1
20261016_192229 None grasp j0 -31.9
20261016_194238 (np.float64(107.4), np.float64(150.4), 8) grasp j0 -29.6
pi05_20261017_172526 None
pi05_20261017_173054 (np.float64(109.6), np.float64(152.8), 5)
pi05_20261017_174600 (np.float64(111.2), np.float64(151.2), 6)
画像は 384×216 ピクセルに縮めたもので、ブロックとして拾えた赤い画素は5〜18個しかありません(None は5個未満)。大まかな比較です。それでも、17時46分の回のブロック(x 111、y 151)は、お手本のブロックの範囲(x 98〜126、y 141〜176)の中にあります。近い位置に置いたお手本(x 107〜109、y 153〜158 の3本)では、つかむときの j0 は −41.5〜−42.3° でした。
| 回 | ブロックの位置(画素) | 止まったときの j0 |
|---|---|---|
| お手本(近い位置の3本) | x 107〜109、y 153〜158 | −41.5〜−42.3°(つかんだ瞬間) |
| π0.5・17時25分 | 赤い画素が5個未満で拾えず | −29.7°(グリッパーがブロックの右横で止まった) |
| π0.5・17時46分 | x 111、y 151 | −46.3°(手前の爪がブロックの上) |
ブロックはお手本の範囲の中にあり、π0.5 は同じあたりのブロックに対して、ある回は j0 を 12° 少なく、ある回は 4〜5° 多く回しています。ずれの原因は、ブロックの置き方より、π0.5 がブロックの位置をまだ正確に読めていないことのほうだと考えています。
17時25分の回は、上のカメラの最後のコマ(chunk11)で、グリッパーがブロックの右横にありました。動画がないので、止まったときに爪がどこかに当たっていたかは分かりません。17時28分の回はその続き、17時30分の回は手首が +56° から始まったので、条件が違います。
loss はまだ下がっていたので、学習を 5,000 ステップまで足した
ここで、どうするかを決めました。
まだ掴めそうにないね。もっと学習させよう。
位置のずれは、学習の量か、お手本の数の問題です。前回の学習(2,000 ステップ)の loss を、200 ステップごとに見ました。
cd $HOME/mnt/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/logs && grep -o "Step [0-9]*: .*loss=[0-9.]*" train.log | awk 'NR%4==1' | sed -E 's/grad_norm=[0-9.]*, //' | head -60; grep -c "loss=" train.log; grep -m2 -i "num_train_steps\|batch_size" train.log | cut -c1-200
Step 0: loss=0.1155
Step 200: loss=0.0240
Step 400: loss=0.0136
Step 600: loss=0.0119
Step 800: loss=0.0095
Step 1000: loss=0.0082
Step 1200: loss=0.0072
Step 1400: loss=0.0060
Step 1600: loss=0.0062
Step 1800: loss=0.0057
40
12:03:27.893 [I] local_batch_size: 32 (3312:data_loader.py:324)
1,000 ステップから 1,800 ステップでも、0.0082 から 0.0057 まで下がり続けています。2,000 ステップ × バッチ32 で約6万4千コマ、5,635 コマのデータを約11周した計算です。手順書の 5,000 ステップまで、あと 3,000 ステップ足すことにしました。
ゼロからやり直すと約9時間かかるので、2,000 ステップの続きから学習します。続きから学習するには、学習の途中の状態(train_state・3.0GB)とデータが要ります。train_state の中身です。
cd $HOME/mnt/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/1999 && find train_state -type f -exec stat -c '%s %n' {} + | sort -n | tail -6; find train_state -type f | wc -l; find train_state -type d; ls -la ~ 2>/dev/null | head -1; cat _CHECKPOINT_METADATA | head -c 400
258270103 train_state/ocdbt.process_0/d/ab92feaddafa6f642825895dd2370b1a
436842472 train_state/ocdbt.process_0/d/1a9cfdb434b3b5fbca1c8674be011bf9
443255705 train_state/ocdbt.process_0/d/ee60a913c538beb93dcf7839ec3d4543
450587212 train_state/ocdbt.process_0/d/aa29634d5f34e76704668d31011b301a
463261049 train_state/ocdbt.process_0/d/eb90cce070b5d11609f7ee02e0b0abaa
528182784 train_state/ocdbt.process_0/d/5d8bbf72bbf8bc6bf67d968c0863e353
22
train_state
train_state/ocdbt.process_0
train_state/ocdbt.process_0/d
train_state/array_metadatas
train_state/d
total 32
{"item_handlers": {"assets": "openpi.training.checkpoints.CallbackHandler", "params": "orbax.checkpoint._src.handlers.pytree_checkpoint_handler.PyTreeCheckpointHandler", "train_state": "orbax.checkpoint._src.handlers.pytree_checkpoint_handler.PyTreeCheckpointHandler"}, "metrics": {}, "performance_metrics": {}, "init_timestamp_nsecs": 1792166849592985733, "commit_timestamp_nsecs": 17921668649422335
一番大きいファイルは約530MB です。朝の失敗を踏まえて、今回は8本同時に送り、終わったら wait で全部の送信が終わるのを待ってから、ファイルごとに大きさを比べる形にしました。データ(前回まとめた19本・147MB)も同時に送っています。
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % ls -lh ~/mecharm_raw_episodes.tar
-rw-r--r-- 1 shimazueiichi staff 147M 10月 16 19:56 /Users/shimazueiichi/mecharm_raw_episodes.tar
(base) shimazueiichi@shimadzueiichinoMacBook-Pro r2r2r-scripts % cd /Volumes/KIOXIA/r2r2r-data/checkpoints/pi05_mecharm_lora_v1/1999
R=/workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999
find train_state -type d | ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "mkdir -p /workspace/data && cd $R && xargs mkdir -p"
scp -P <SSH_PORT> -i ~/.ssh/id_ed25519 ~/mecharm_raw_episodes.tar root@<POD_IP>:/workspace/data/ &
find train_state -type f | xargs -P 8 -I{} scp -q -P <SSH_PORT> -i ~/.ssh/id_ed25519 {} root@<POD_IP>:$R/{}
wait
ssh -p <SSH_PORT> -i ~/.ssh/id_ed25519 root@<POD_IP> "cd $R && find train_state -type f -printf '%s %p\n'" > /tmp/remote_ts.txt
find train_state -type f | while read f; do s=$(stat -f %z "$f"); grep -qx "$s $f" /tmp/remote_ts.txt || echo "不一致: $f"; done; echo "確認おわり"
[1] 22225
[1] + done scp -P <SSH_PORT> -i ~/.ssh/id_ed25519 ~/mecharm_raw_episodes.tar
確認おわり
(base) shimazueiichi@shimadzueiichinoMacBook-Pro 1999 %
18時7分に始めて、18時18分に終わりました。不一致: は1行も出ていません。
Pod の tmux に戻り、サーバーを Ctrl+C で止めてから、データを LeRobot 形式(openpi が学習に使う形)に変換し、続きから学習を始めました。正規化の値は、同じ19本から作った前回のものをそのまま使っています。
cd /workspace/data && tar xf mecharm_raw_episodes.tar && ls raw_episodes | wc -l
cd /workspace/openpi
uv run python /workspace/r2r2r/pi05/convert_to_lerobot.py --raw-dir /workspace/data/raw_episodes
XLA_PYTHON_CLIENT_MEM_FRACTION=0.9 uv run scripts/train.py pi05_mecharm_lora --exp-name=mecharm_v1 --resume --num-train-steps=5000 2>&1 | tee /workspace/logs/train_resume.log
データの読み込みまでの表示の最後です。
9058, -9.28128714,
-24.90883683, -2.27691551, 0. ]), q99=array([25.7347027 , 29.32432346, 18.48213864, 4.55526524, 29.28554506,
2.00465884, 0.9998 ]))}, repack_transforms=Group(inputs=[RepackTransform(structure={'observation/image': 'image', 'observation/state': 'state', 'actions': 'actions', 'prompt': 'prompt'})], outputs=()), data_transforms=Group(inputs=(MecharmInputs(model_type=<ModelType.PI05: 'pi05'>), DeltaActions(mask=(True, True, True, True, True, True, False))), outputs=(AbsoluteActions(mask=(True, True, True, True, True, True, False)), MecharmOutputs())), model_transforms=Group(inputs=[InjectDefaultPrompt(prompt=None), ResizeImages(height=224, width=224), TokenizePrompt(tokenizer=<openpi.models.tokenizer.PaligemmaTokenizer object at 0x7b9fe8837310>, discrete_state_input=True), PadStatesAndActions(model_action_dim=32)], outputs=()), use_quantile_norm=True, action_sequence_keys=('actions',), prompt_from_task=True, rlds_data_dir=None, action_space=None, datasets=()) (16124:data_loader.py:243)
Downloading data: 100%|██████████| 19/19 [00:00<00:00, 20329.53files/s]
Generating train split: 5635 examples [00:01, 3491.66 examples/s]
09:21:11.790 [I] local_batch_size: 32 (16124:data_loader.py:324)
5635 examples は前回と同じ19本・5,635 コマです。そのあと、2,000 ステップ目の状態を読み込んで、続きから始まりました。

画面の最後の数行を文字で書き起こしたものです。
09:21:27.532 [I] Restoring checkpoint from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999. (16124:checkpointer.py:298)
/workspace/openpi/.venv/lib/python3.11/site-packages/orbax/checkpoint/_src/serialization/type_handlers.py:1251: UserWarning: Sharding info not provided when restoring. Populating sharding info from sharding file. Please note restoration time will be slightly increased due to reading from file. Note also that this option is unsafe when restoring on a different topology than the checkpoint was saved with.
warnings.warn(
09:21:33.392 [I] [process=0] /jax/checkpoint/read/bytes_per_sec: 1.2 GiB/s (total bytes: 7.2 GiB) (time elapsed: 5 seconds) (per-host) (16124:base_pytree_checkpoint_handler.py:114)
09:21:36.557 [I] [process=0] /jax/checkpoint/read/bytes_per_sec: 1.1 GiB/s (total bytes: 3.5 GiB) (time elapsed: 3 seconds) (per-host) (16124:base_pytree_checkpoint_handler.py:114)
09:21:36.557 [I] Finished restoring checkpoint in 9.02 seconds from /workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1/1999. (16124:checkpointer.py:309)
09:21:36.557 [I] {'step': 1999, 'event_type': 'restore', 'directory': '/workspace/openpi/checkpoints/pi05_mecharm_lora/mecharm_v1', 'checkpointer_start_time': 1792228887.5328803, 'checkpointer_duration_secs': 9.024739980697632, 'checkpoint_manager_start_time': 1792228887.5325272, 'checkpoint_manager_duration_secs': 9.025097131729126} (16124:standard_logger.py:34)
09:21:36.679 [I] Progress on: 2.00kit/5.00kit rate:- remaining:? elapsed:00:00 postfix:-
(16124:tqdm_logging.py:145)
時刻は Pod の時計(UTC)なので、日本時間では 18時21分です。前回の速さ(1ステップ約7秒)なら、終わるのは0時過ぎです。
A40 を借りた時間と費用
| 項目 | 値 |
|---|---|
| 前回の Pod(Log#5)の合計 | 約 7.3 ドル(残高 8.51 ドル → 1.25 ドル。学習約 2.5 ドル、使わずに置いていた時間約 3.7 ドルを含む) |
| この回の Pod | NVIDIA A40(48GB)× 1、Runpod On-Demand、ディスク 80GB 指定、1時間約 0.6 ドル |
| 作成 | 2026-10-17 7時56分ごろ(日本時間) |
| 推論サーバーの待ち受け | 9時8分 |
| 何もしていなかった時間 | 9時8分〜17時23分(約8時間15分、約5ドル) |
| 実機テスト | 17時23分〜17時48分(ドライラン2回、実機4回) |
| 学習の再開 | 18時21分〜(2,000 → 5,000 ステップ) |
この回の Pod は、学習が終わってモデルを持ち帰るまで消せません。合計の費用は次の Log に書きます。朝にモデルを送ってから夕方まで何もしなかった約8時間は、実機を動かす時間を決めてから Pod を作れば払わずに済んだ分です。
学習が終わったら、同じ位置にブロックを置いて爪の間に入るかを見る
学習が終わると、Pod の checkpoints/pi05_mecharm_lora/mecharm_v1/4999/ にモデルができます。同じ Pod で、そのままサーバーを起動して試せます。
uv run scripts/serve_policy.py policy:checkpoint --policy.config=pi05_mecharm_lora --policy.dir=checkpoints/pi05_mecharm_lora/mecharm_v1/4999 2>&1 | tee /workspace/logs/serve.log
確かめることは次の3つです。
| 確かめること | 見る記録 |
|---|---|
| 5,000 ステップの loss が、2,000 ステップの 0.0057 前後より下がったか | /workspace/logs/train_resume.log |
| ブロックの上に来たとき、j0 がお手本のつかむ範囲(−42〜−28°)に入るか | runs/pi05_policy/ の log.jsonl |
ブロックが2本の爪の間に入り、グリッパー→閉 まで進むか | 横からの動画と、上のカメラの画像 |
それでも同じようにずれる場合は、学習の量ではなく、お手本の数とブロックの置き方のばらつきが足りていないことになります。そのときは、ブロックの位置を変えたお手本を足して、学習し直します。手首の送り方(1回に送る量)は、爪がブロックの間に入るようになるまで変えません。
保存先は次のとおりです。
| データ | 場所(KIOXIA の r2r2r-data/ 以下) |
|---|---|
| 実機・ドライランの記録(6回分) | runs/pi05_policy/pi05_20261017_172323/・172526/・172828/・173054/・174533/・174600/ |
| 横から撮った動画と、爪の拡大画像 | lab/Log06/ |
| お手本の関節角の平均(この回の分析で作ったもの) | diag/demo_traj.json |
| 学習したモデル(2,000 ステップ) | checkpoints/pi05_mecharm_lora_v1/1999/ |
