File size: 5,185 Bytes
57af0e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/usr/bin/env python
"""VLA closed-loop на MicroDuck: 61D obs из сима + head_camera рендер -> чанк -> исполнение."""
import sys, json
from pathlib import Path
import numpy as np
import torch, mujoco
sys.path.insert(0,'/root/tinyvla_h100'); sys.path.insert(0,'/root/tinyvla_h100/src')
from closedloop import model, data, key, HOME, SPC, build_obs
from scripts.eval_microduck import BEHAVIORS, DUCK_MORPH, DUCK_TEXT, load_split, behavior_of, decode_img
from tinyvla.modeling_tinyvla import TinyVLAPolicy
from tinyvla.modules.embodiment import MORPH_FIELDS
from transformers import AutoTokenizer
from collections import defaultdict

torch.backends.cuda.enable_cudnn_sdp(False)
CKPT='/root/outputs/microduck_ft/final'
beh=sys.argv[1] if len(sys.argv)>1 else 'walking'
SECONDS=float(sys.argv[2]) if len(sys.argv)>2 else 8

man=json.loads(Path('/root/microduck/train/manifest.json').read_text())
s_mean=np.asarray(man['norm_stats']['state']['mean'],np.float32)
s_std=np.maximum(np.asarray(man['norm_stats']['state']['std'],np.float32),1e-6)
a_mean=np.asarray(man['norm_stats']['action']['mean'],np.float32)
a_std=np.maximum(np.asarray(man['norm_stats']['action']['std'],np.float32),1e-6)

pol=TinyVLAPolicy.from_pretrained(CKPT).cuda().eval(); cfg=pol.config
tok=AutoTokenizer.from_pretrained(cfg.lm_model_name)
sc={"arm_dof":0.1,"reach_m":2,"gripper_width_m":10,"num_cameras":1/3,"control_hz":1/30,
    "joint_lo_mean":1/3.1416,"joint_hi_mean":1/3.1416,"workspace_x":2,"workspace_y":2,"workspace_z":2,"payload_kg":0.2}
morph=torch.tensor([DUCK_MORPH.get(f,0)*sc.get(f,1) for f in MORPH_FIELDS],dtype=torch.float32)[None].cuda()
mt=tok([DUCK_TEXT],padding="max_length",truncation=True,max_length=cfg.morph_text_max_len,return_tensors="pt")
tt=tok([f"MicroDuck: perform {beh.replace('_',' ')}"],padding="max_length",truncation=True,
       max_length=cfg.tokenizer_max_length,return_tensors="pt")
# демо из train-эпизодов этого поведения + КОМАНДА из реального состояния датасета
eps=load_split(Path('/root/microduck'),'train')
by=defaultdict(list)
for e in sorted(eps): by[behavior_of(e)].append(e)
sup_ep=by[beh][0]; sup=eps[sup_ep]
sidx=np.linspace(0,len(sup)-1,3).astype(int)
sup_img=torch.stack([decode_img(sup[i][1]) for i in sidx])[None].cuda()
sup_act=torch.stack([torch.nn.functional.pad(torch.from_numpy(sup[i][3]),(0,cfg.max_action_dim-14)) for i in sidx])[None].cuda()
raw_state0=sup[len(sup)//2][2]*s_std+s_mean   # денорм состояния из середины эпизода
command=raw_state0[48:61].astype(np.float32)   # родная команда поведения из данных
print(f"команда из датасета ({beh}): vx,vy,vyaw={command[:3].round(3)} bodyz={command[9]:.3f}")

# камера головы: детерминированная установка (yaw180, pitch25 от мира на STAND),
# найдена сверкой с кадрами датасета; RL-сценная quat смотрела внутрь головы
import mujoco as _mj
_cid=model.camera("head_camera").id
_bid=model.cam_bodyid[_cid]
_mj.mj_resetDataKeyframe(model,data,key.id); _mj.mj_forward(model,data)
_bpos=data.xpos[_bid].copy(); _bR=data.xmat[_bid].reshape(3,3).copy()
import numpy as _np
_p=_np.deg2rad(25); _y=_np.deg2rad(180)
_fwd=_np.array([_np.cos(_p)*_np.cos(_y),_np.cos(_p)*_np.sin(_y),-_np.sin(_p)])
_r=_np.cross(_fwd,[0,0,1.0]); _r/=_np.linalg.norm(_r); _u=_np.cross(_r,_fwd)
_Rw=_np.stack([_r,_u,-_fwd],axis=1); _Rl=_bR.T@_Rw
_q=_np.empty(4); _mj.mju_mat2Quat(_q,_Rl.flatten())
model.cam_quat[_cid]=_q
model.cam_pos[_cid]=_bR.T@((_bpos+_np.array([-0.06,0,0.0]))-_bpos)
model.vis.map.znear=0.02
DS_MEAN=0.116  # средняя яркость кадров датасета
head_renderer=mujoco.Renderer(model,height=256,width=256)
_gain=[None]
def head_img():
    head_renderer.update_scene(data,camera="head_camera")
    a=head_renderer.render().astype("float32")/255.
    if _gain[0] is None:
        _gain[0]=DS_MEAN/max(a.mean(),1e-3)
    a=(a*_gain[0]).clip(0,1)
    return torch.from_numpy(a).permute(2,0,1).float()

def vla_step(obs61,i):
    st=torch.from_numpy((obs61-s_mean)/s_std)
    st=torch.nn.functional.pad(st,(0,cfg.max_state_dim-61))
    img=head_img()
    b={"observation.images.cam0":img[None].cuda(),
       "observation.images.cam1":torch.zeros_like(img)[None].cuda(),
       "observation.state":st[None].cuda(),
       "observation.language.tokens":tt["input_ids"].cuda(),
       "observation.language.attention_mask":tt["attention_mask"].bool().cuda(),
       "morph_text_ids":mt["input_ids"].cuda(),"morph_text_mask":mt["attention_mask"].bool().cuda(),
       "morphology":morph,"support_images":sup_img,"support_actions":sup_act,
       "embodiment_id":torch.tensor([0]).cuda()}
    with torch.no_grad(), torch.autocast("cuda",torch.bfloat16):
        pr=pol.predict_action_chunk(b)[0].float().cpu().numpy()[:,:14]
    return pr*a_std+a_mean                     # денорм: единицы учителя (оффсеты от home)

from closedloop import run
run(vla_step, command, seconds=SECONDS, tag=f"vla_{beh}")