openvla/vla/datasets/rlds/oxe/configs.py define your processed images (according to the dataset in use) and the state and action encoding:1 "overall900": {
2 "image_obs_keys": {"primary": "image_front", "secondary":"image_side", "wrist": None},
3 "depth_obs_keys": {"primary": None, "secondary": None, "wrist": None},
4 "state_obs_keys": ["EEF_state", None, "gripper_state"],
5 "state_encoding": StateEncoding.POS_EULER,
6 "action_encoding": ActionEncoding.EEF_POS,
7 },
8openvla/vla/datasets/rlds/oxe/transforms.py Define the transformation function to relabel data from your specific dataset into the format required by the OpenVLA model:1def overall900_dataset_transform(trajectory: Dict[str, Any]) -> Dict[str, Any]:
2
3
4 trajectory["observation"]["EEF_state"] = trajectory["observation"]["state"][:, :6]
5 trajectory["observation"]["gripper_state"] = trajectory["observation"]["state"][:, -1]
6 trajectory["language_instruction"] = trajectory["natural_language_instruction"]
7
8 return trajectory
91
2 "overall900": overall900_dataset_transform, openvla/vla/datasets/rlds/oxe/mixtures.py Add your dataset to the overall mixture (by default, the weight is set to 1.0):1
2 "overall900": [
3 ("overall900", 1.0),
4 ],
51# Extract and unzip the OpenVLA folder
2zip_path = "/content/openvla.zip"
3!unzip -o "$zip_path" -d /content
4
5# Install PyTorch with CUDA (update the CUDA version if necessary, based on Colab support)
6!pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu118
7
8# Minimum requirements: timm==0.9.10, tokenizers==0.19.1, torch>=2.2.0, torchvision>=0.16.0, transformers==4.40.1
9
10%cd openvla
11!pip install -e .
12
13# Install Flash Attention 2 and Ninja
14!pip install packaging ninja==1.11.1.2
15!ninja --version # Verify Ninja installation
16
17!pip install "flash-attn==2.5.5" --no-build-isolation
18!pip install bitsandbytes==0.45.0
19!pip install accelerate==0.24.1
20rlds_dataset_builder/overall_900/overall_900_dataset_builder.py . Here is the script for convereting the dataset starting from episodes saved in the Numpy format (also find a plausible but not fixed structure for these datasets):1from typing import Iterator, Tuple, Any
2import glob
3import numpy as np
4import tensorflow as tf
5import tensorflow_datasets as tfds
6import tensorflow_hub as hub
7
8class Overall900(tfds.core.GeneratorBasedBuilder):
9 """DatasetBuilder for example dataset."""
10
11 VERSION = tfds.core.Version('1.0.0')
12 RELEASE_NOTES = {
13 '1.0.0': 'Initial release.',
14 }
15
16 def __init__(self, *args, **kwargs):
17 super().__init__(*args, **kwargs)
18 self._embed = hub.load("https://tfhub.dev/google/universal-sentence-encoder-large/5")
19
20 def _info(self) -> tfds.core.DatasetInfo:
21 """Dataset metadata (homepage, citation,...)."""
22 return self.dataset_info_from_configs(
23 features=tfds.features.FeaturesDict({
24 'steps': tfds.features.Dataset({
25 'observation': tfds.features.FeaturesDict({
26 'image_front': tfds.features.Image(
27 shape=(224, 224, 3),
28 dtype=np.uint8,
29 encoding_format='png',
30 doc='Main camera RGB observation.',
31 ),
32 'image_side': tfds.features.Image(
33 shape=(224, 224, 3),
34 dtype=np.uint8,
35 encoding_format='png',
36 doc='Side camera RGB observation.',
37 ),
38 'image_hand': tfds.features.Image(
39 shape=(224, 224, 3),
40 dtype=np.uint8,
41 encoding_format='png',
42 doc='Hand camera RGB observation.',
43 ),
44 'state': tfds.features.Tensor(
45 shape=(7,),
46 dtype=np.float32,
47 doc='Robot state, consists of [3x robot displacements, 3x rotations, and gripper closedness].',
48 ),
49 }),
50 'action': tfds.features.Tensor(
51 shape=(7,),
52 dtype=np.float32,
53 doc='Action consists of [3x robot delta displacements, 3x delta rotations, and gripper closedness action].',
54 ),
55 'discount': tfds.features.Scalar(
56 dtype=np.float32,
57 doc='Discount if provided, default to 1.'
58 ),
59 'reward': tfds.features.Scalar(
60 dtype=np.float32,
61 doc='Reward if provided, 1 on final step for demos.'
62 ),
63 'is_first': tfds.features.Scalar(
64 dtype=np.bool_,
65 doc='True on the first step of the episode.'
66 ),
67 'is_last': tfds.features.Scalar(
68 dtype=np.bool_,
69 doc='True on the last step of the episode.'
70 ),
71 'is_terminal': tfds.features.Scalar(
72 dtype=np.bool_,
73 doc='True on the last step of the episode if it is a terminal step, True for demos.'
74 ),
75 'natural_language_instruction': tfds.features.Text(
76 doc='Language Instruction.'
77 ),
78 'natural_language_embedding': tfds.features.Tensor(
79 shape=(512,),
80 dtype=np.float32,
81 doc='Kona language embedding. See https://tfhub.dev/google/universal-sentence-encoder-large/5'
82 ),
83 }),
84 'episode_metadata': tfds.features.FeaturesDict({
85 'file_path': tfds.features.Text(
86 doc='Path to the original data file.'
87 ),
88 }),
89 }))
90
91 def _split_generators(self, dl_manager: tfds.download.DownloadManager):
92 """Define data splits."""
93 return {
94 'train': self._generate_examples(path='data/train/episode_*.npy'),
95 }
96
97 def _generate_examples(self, path) -> Iterator[Tuple[str, Any]]:
98 """Generator of examples for each split."""
99
100 def _parse_example(episode_path):
101 # Load raw data --> this should change for your dataset
102 data = np.load(episode_path, allow_pickle=True).item()
103
104 # Extract data
105 images_front = data["images_front"]
106 images_side = data["images_side"]
107 images_hand = data["images_hand"]
108 states = data["states"]
109 actions = data["actions"]
110 natural_language_instruction = data["natural_language"]
111
112 # Verify that the lengths of the data match
113 if not (len(images_front) == len(images_side) == len(images_hand) == len(states) == len(actions)):
114 raise ValueError(f"Mismatch in lengths of images, states, and actions in `{episode_path}`")
115
116 # Assemble episode
117 episode = []
118 for i in range(len(states)):
119 # Compute language embedding (use a fixed instruction for now)
120 language_embedding = self._embed([natural_language_instruction[0]])[0].numpy()
121
122 episode.append({
123 'observation': {
124 'image_front': images_front[i],
125 'image_side': images_side[i],
126 'image_hand': images_hand[i],
127 'state': states[i],
128 },
129 'action': actions[i],
130 'discount': 1.0,
131 'reward': float(i == (len(states) - 1)),
132 'is_first': i == 0,
133 'is_last': i == (len(states) - 1),
134 'is_terminal': i == (len(states) - 1),
135 'natural_language_instruction': natural_language_instruction[i],
136 'natural_language_embedding': language_embedding,
137 })
138
139 # Create output data sample
140 sample = {
141 'steps': episode,
142 'episode_metadata': {
143 'file_path': episode_path
144 }
145 }
146
147 return episode_path, sample
148
149 # Create a list of all examples
150 episode_paths = glob.glob(path)
151
152 # For small datasets, use single-thread parsing
153 for sample in episode_paths:
154 yield _parse_example(sample)
1551# Install the gdown library if not already present
2!pip install gdown --quiet
3
4# Specify the Google Drive file ID
5file_id = "<Your_shared_Google_Drive_link>"
6
7# Download the file from Google Drive
8!gdown $file_id -O /content/overall900.zip
9
10# Load the dataset
11import os
12
13# Path to the ZIP file
14zip_path_data = "/content/overall900.zip"
15
16# Path to the output directory
17output_dir = "/content/openvla/overall900_dataset"
18
19# Create the output directory if it doesn't exist
20os.makedirs(output_dir, exist_ok=True)
21
22# Extract the ZIP file to the specified directory
23!unzip -o "$zip_path_data" -d "$output_dir"
241!pip install torch==2.2.0 accelerate==0.24.1 timm==0.9.10 peft==0.11.1 wandb==0.18.7 draccus==0.8.0 transformers==4.40.1
2
3# Import libraries
4import os
5from collections import deque
6from dataclasses import dataclass
7from pathlib import Path
8from typing import Optional
9import timm
10import torch
11import torch.nn as nn
12from typing import Callable
13from PIL import Image
14import torch.distributed as dist
15import tqdm
16from accelerate import PartialState
17from peft import LoraConfig, PeftModel, get_peft_model, prepare_model_for_kbit_training
18from torch.nn.parallel import DistributedDataParallel as DDP
19from torch.optim import AdamW
20from torch.utils.data import DataLoader
21from transformers import AutoModelForVision2Seq, AutoProcessor, BitsAndBytesConfig
22from transformers import AutoConfig, AutoImageProcessor
23from transformers.modeling_outputs import CausalLMOutputWithPast
24import wandb
25from prismatic.models.backbones.llm.prompting import PurePromptBuilder, VicunaV15ChatPromptBuilder
26from prismatic.util.data_utils import PaddedCollatorForActionPrediction
27from prismatic.vla.action_tokenizer import ActionTokenizer
28from prismatic.vla.datasets import RLDSBatchTransform, RLDSDataset
29from prismatic.vla.datasets.rlds.utils.data_utils import save_dataset_statistics
30from prismatic.extern.hf.configuration_prismatic import OpenVLAConfig
31from prismatic.extern.hf.modeling_prismatic import OpenVLAForActionPrediction
32from prismatic.extern.hf.processing_prismatic import PrismaticImageProcessor, PrismaticProcessor
33
34# Log in to Weights & Biases
35wandb.login(key="YOUR_WANDB_KEY") # Keep your key secure!
36
37# Disable tokenizer parallelism to avoid conflicts on a single GPU
38os.environ["TOKENIZERS_PARALLELISM"] = "false"
39
40@dataclass
41class FinetuneConfig:
42 vla_path: str = "openvla/openvla-7b" # Path to the OpenVLA model
43 data_root_dir: Path = Path("/content/openvla/overall900_dataset") # Dataset path
44 dataset_name: str = "overall900" # Dataset name
45 run_root_dir: Path = Path("checkpoints") # Path for logs and checkpoints
46 adapter_tmp_dir: Path = Path("adapter-tmp") # Temporary directory for LoRA weights
47
48 # Training parameters
49 batch_size: int = 16
50 max_steps: int = 1200
51 save_steps: int = 1200
52 learning_rate: float = 5e-4
53 grad_accumulation_steps: int = 2
54 image_aug: bool = False
55 shuffle_buffer_size: int = 600
56 save_latest_checkpoint_only: bool = True
57
58 # LoRA and quantization settings
59 use_lora: bool = True
60 lora_rank: int = 32
61 lora_dropout: float = 0.1
62 use_quantization: bool = True
63
64 # Monitoring parameters
65 wandb_project: str = "model-overall900"
66 wandb_entity: str = "YOUR_WANDB_ENTITY"
67 run_id_note: Optional[str] = None
68
69# Finetuning function
70def finetune(cfg: FinetuneConfig) -> None:
71 print(f"Fine-tuning OpenVLA Model `{cfg.vla_path}` on `{cfg.dataset_name}`")
72
73 # Validate GPU availability
74 assert torch.cuda.is_available(), "Fine-tuning requires at least one GPU!"
75 device = torch.device("cuda:0")
76 torch.cuda.set_device(device)
77 torch.cuda.empty_cache()
78
79 # Configure experiment ID
80 exp_id = (
81 f"{cfg.vla_path.split('/')[-1]}+{cfg.dataset_name}"
82 f"+b{cfg.batch_size * cfg.grad_accumulation_steps}"
83 f"+lr-{cfg.learning_rate}"
84 )
85 if cfg.use_lora:
86 exp_id += f"+lora-r{cfg.lora_rank}+dropout-{cfg.lora_dropout}"
87 if cfg.use_quantization:
88 exp_id += "+q-4bit"
89 if cfg.run_id_note:
90 exp_id += f"--{cfg.run_id_note}"
91 if cfg.image_aug:
92 exp_id += "--image_aug"
93
94 run_dir, adapter_dir = cfg.run_root_dir / exp_id, cfg.adapter_tmp_dir / exp_id
95 os.makedirs(run_dir, exist_ok=True)
96
97 quantization_config = None
98 if cfg.use_quantization:
99 assert cfg.use_lora, "Quantization is only supported for LoRA fine-tuning!"
100 quantization_config = BitsAndBytesConfig(
101 load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_quant_type="nf4"
102 )
103
104 # Register the model with HuggingFace
105 AutoConfig.register("openvla", OpenVLAConfig)
106 AutoImageProcessor.register(OpenVLAConfig, PrismaticImageProcessor)
107 AutoProcessor.register(OpenVLAConfig, PrismaticProcessor)
108 AutoModelForVision2Seq.register(OpenVLAConfig, OpenVLAForActionPrediction)
109
110 processor = AutoProcessor.from_pretrained(cfg.vla_path, trust_remote_code=True)
111 vla = AutoModelForVision2Seq.from_pretrained(
112 cfg.vla_path,
113 torch_dtype=torch.bfloat16,
114 quantization_config=quantization_config,
115 attn_implementation="flash_attention_2",
116 low_cpu_mem_usage=True,
117 trust_remote_code=True,
118 )
119
120 if cfg.use_quantization:
121 vla = prepare_model_for_kbit_training(vla)
122 else:
123 vla = vla.to(device)
124
125 if cfg.use_lora:
126 lora_config = LoraConfig(
127 r=cfg.lora_rank,
128 lora_alpha=min(cfg.lora_rank, 16),
129 lora_dropout=cfg.lora_dropout,
130 target_modules="all-linear",
131 init_lora_weights="gaussian",
132 )
133 vla = get_peft_model(vla, lora_config)
134 vla.print_trainable_parameters()
135
136 vla.to(device)
137
138 # Create optimizer
139 trainable_params = [param for param in vla.parameters() if param.requires_grad]
140 optimizer = AdamW(trainable_params, lr=cfg.learning_rate)
141 action_tokenizer = ActionTokenizer(processor.tokenizer)
142
143 batch_transform = RLDSBatchTransform(
144 action_tokenizer,
145 processor.tokenizer,
146 image_transform=processor.image_processor.apply_transform,
147 prompt_builder_fn=PurePromptBuilder if "v01" not in cfg.vla_path else VicunaV15ChatPromptBuilder,
148 )
149
150 vla_dataset = RLDSDataset(
151 cfg.data_root_dir,
152 cfg.dataset_name,
153 batch_transform,
154 resize_resolution=tuple(vla.config.image_sizes),
155 shuffle_buffer_size=cfg.shuffle_buffer_size,
156 image_aug=cfg.image_aug,
157 )
158
159 # Save dataset statistics
160 save_dataset_statistics(vla_dataset.dataset_statistics, run_dir)
161
162 collator = PaddedCollatorForActionPrediction(
163 processor.tokenizer.model_max_length, processor.tokenizer.pad_token_id, padding_side="right"
164 )
165
166 dataloader = DataLoader(
167 vla_dataset,
168 batch_size=cfg.batch_size,
169 collate_fn=collator,
170 num_workers=0,
171 )
172
173 # Initialize Weights & Biases logging
174 wandb.init(entity=cfg.wandb_entity, project=cfg.wandb_project, name=f"ft+{exp_id}", force=True)
175
176 recent_losses = deque(maxlen=cfg.grad_accumulation_steps)
177 recent_action_accuracies = deque(maxlen=cfg.grad_accumulation_steps)
178 recent_l1_losses = deque(maxlen=cfg.grad_accumulation_steps)
179
180 # Training loop
181 with tqdm.tqdm(total=cfg.max_steps, leave=False) as progress:
182 vla.train()
183 optimizer.zero_grad()
184 for batch_idx, batch in enumerate(dataloader):
185 with torch.autocast("cuda", dtype=torch.bfloat16):
186 output: CausalLMOutputWithPast = vla(
187 input_ids=batch["input_ids"].to(device),
188 attention_mask=batch["attention_mask"].to(device),
189 pixel_values=batch["pixel_values"].to(torch.bfloat16).to(device),
190 labels=batch["labels"],
191 )
192 loss = output.loss
193
194 # Normalize loss for gradient accumulation
195 normalized_loss = loss / cfg.grad_accumulation_steps
196 normalized_loss.backward()
197
198 # Compute accuracy and L1 loss for logging
199 action_logits = output.logits[:, vla.vision_backbone.featurizer.patch_embed.num_patches : -1]
200 action_preds = action_logits.argmax(dim=2).to(device)
201 action_gt = batch["labels"][:, 1:].to(action_preds.device).to(device)
202 mask = action_gt > action_tokenizer.action_token_begin_idx
203
204 # Compute accuracy
205 correct_preds = (action_preds == action_gt) & mask
206 action_accuracy = correct_preds.sum().float() / mask.sum().float()
207
208 # Compute L1 loss on predicted (continuous) actions
209 continuous_actions_pred = torch.tensor(
210 action_tokenizer.decode_token_ids_to_actions(action_preds[mask].cpu().numpy())
211 ).to(device)
212 continuous_actions_gt = torch.tensor(
213 action_tokenizer.decode_token_ids_to_actions(action_gt[mask].cpu().numpy())
214 ).to(device)
215 action_l1_loss = torch.nn.functional.l1_loss(continuous_actions_pred, continuous_actions_gt)
216
217 # Store recent metrics
218 recent_losses.append(loss.item())
219 recent_action_accuracies.append(action_accuracy.item())
220 recent_l1_losses.append(action_l1_loss.item())
221
222 # Compute gradient step index
223 gradient_step_idx = batch_idx // cfg.grad_accumulation_steps
224
225 # Log metrics to Weights & Biases every 10 gradient steps
226 if gradient_step_idx % 10 == 0:
227 wandb.log(
228 {
229 "train_loss": sum(recent_losses) / len(recent_losses),
230 "action_accuracy": sum(recent_action_accuracies) / len(recent_action_accuracies),
231 "l1_loss": sum(recent_l1_losses) / len(recent_l1_losses),
232 },
233 step=gradient_step_idx,
234 )
235
236 # Optimizer step
237 if (batch_idx + 1) % cfg.grad_accumulation_steps == 0:
238 optimizer.step()
239 optimizer.zero_grad()
240 progress.update()
241
242 # Save model checkpoint
243 if gradient_step_idx > 0 and gradient_step_idx % cfg.save_steps == 0:
244 print(f"Saving Model Checkpoint for Step {gradient_step_idx}")
245 save_dir = adapter_dir if cfg.use_lora else run_dir
246 processor.save_pretrained(run_dir)
247 vla.save_pretrained(save_dir)
248
249 if cfg.use_lora:
250 base_vla = AutoModelForVision2Seq.from_pretrained(
251 cfg.vla_path, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True, trust_remote_code=True
252 )
253 merged_vla = PeftModel.from_pretrained(base_vla, adapter_dir)
254 merged_vla = merged_vla.merge_and_unload()
255 if cfg.save_latest_checkpoint_only:
256 merged_vla.save_pretrained(run_dir)
257 print(f"Saved Model Checkpoint for Step {gradient_step_idx} at: {run_dir}")
258 else:
259 checkpoint_dir = Path(str(run_dir) + f"--{gradient_step_idx}_chkpt")
260 os.makedirs(checkpoint_dir, exist_ok=True)
261 save_dataset_statistics(vla_dataset.dataset_statistics, checkpoint_dir)
262 processor.save_pretrained(checkpoint_dir)
263 merged_vla.save_pretrained(checkpoint_dir)
264 print(f"Saved Model Checkpoint for Step {gradient_step_idx} at: {checkpoint_dir}")
265
266 # Stop training when max_steps is reached
267 if gradient_step_idx == cfg.max_steps:
268 print(f"Max step {cfg.max_steps} reached! Stopping training...")
269 break
270
271# Start fine-tuning in Colab
272if __name__ == "__main__":
273 cfg = FinetuneConfig()
274 finetune(cfg)
2751import shutil
2
3# Path to the folder to compress
4checkpoint_folder = '/content/openvla/checkpoints'
5
6# Name of the .tar file
7tar_filename = '/content/checkpoints_backup.tar'
8
9# Create the .tar archive
10shutil.make_archive(tar_filename.replace('.tar', ''), 'tar', checkpoint_folder)
11
12# Mount Google Drive
13from google.colab import drive
14drive.mount('/content/drive')
15
16# Copy the .tar file to Google Drive
17!cp /content/checkpoints_backup.tar /content/drive/MyDrive/
18
19# Sync to ensure the file is written to Google Drive
20!sync