Download scripts/test_zero2_vlm_gradient_bucket.py from Jack04810/agentic-rl-main: direct link, hf CLI and curl.
- Browser
- Download file 7.14 kB
-
https://huggingface.co/Jack04810/agentic-rl-main/resolve/main/scripts/test_zero2_vlm_gradient_bucket.py
- Command line
-
hf download hf://Jack04810/agentic-rl-main/scripts/test_zero2_vlm_gradient_bucket.py
-
curl -L -o test_zero2_vlm_gradient_bucket.py https://huggingface.co/Jack04810/agentic-rl-main/resolve/main/scripts/test_zero2_vlm_gradient_bucket.py
7.14 kB
| #!/usr/bin/env python3 | |
| """Diagnostic for ZeRO-2 with unequal real ChartQA image-token lengths. | |
| This is a bounded regression diagnostic for the OPD-only training failure where | |
| one rank reached a Qwen MLP gradient all-reduce while a longer-image rank had | |
| not yet reached the same autograd hook. It loads the 0.5B student only, picks | |
| the highest-resolution hard-correct ChartQA rows, and performs a few real VLM | |
| backward/optimizer steps with the bounded production ZeRO-2 JSON. Formal | |
| OPD-only training uses native DDP; this script is retained only to inspect | |
| DeepSpeed behavior when explicitly requested. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import copy | |
| import json | |
| import os | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import deepspeed | |
| import torch | |
| import torch.distributed as dist | |
| from PIL import Image | |
| from transformers import AutoProcessor, LlavaOnevisionForConditionalGeneration | |
| from transformers.models.llava_onevision.modeling_llava_onevision import image_size_to_num_patches | |
| PROJECT_ROOT = Path(__file__).resolve().parents[1] | |
| if str(PROJECT_ROOT) not in sys.path: | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| from config.loader import load_config | |
| from data_utils.chart.data_collector import prepare_chart_rl_data | |
| from data_utils.chart.teacher_gate import filter_chartqa_rows_with_teacher_gate | |
| from data_utils.commom_util import collate_fn | |
| from opsd_utils.teacher_batching import student_batch_num_images_tensor | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--config", default="config/config_opd_only_7b_chartqa.yaml") | |
| parser.add_argument("--iterations", type=int, default=3) | |
| parser.add_argument("--samples-per-rank", type=int, default=2) | |
| return parser.parse_args() | |
| def _deepspeed_config(*, world_size: int, micro_batch_size: int) -> dict: | |
| root = Path(__file__).resolve().parents[1] | |
| payload = json.loads((root / "configs/deepspeed/zero2_bf16.json").read_text(encoding="utf-8")) | |
| payload = copy.deepcopy(payload) | |
| payload.pop("scheduler", None) | |
| payload["optimizer"]["params"] = { | |
| "lr": 1.0e-6, | |
| "betas": [0.9, 0.999], | |
| "eps": 1.0e-8, | |
| "weight_decay": 0.0, | |
| "torch_adam": True, | |
| "adam_w_mode": True, | |
| } | |
| payload["gradient_accumulation_steps"] = 1 | |
| payload["gradient_clipping"] = 0.0 | |
| payload["train_micro_batch_size_per_gpu"] = micro_batch_size | |
| payload["train_batch_size"] = world_size * micro_batch_size | |
| return payload | |
| def _highest_resolution_rows(config: dict, count: int) -> tuple[list[dict], list[int]]: | |
| rows = prepare_chart_rl_data(config["dataset"]["train_dataset"]) | |
| for index, row in enumerate(rows): | |
| row["_dyme_index"] = index | |
| rows, _ = filter_chartqa_rows_with_teacher_gate(rows, config["dataset"]["teacher_gate"]) | |
| processor = AutoProcessor.from_pretrained(config["model"]["pretrained_model_path"], local_files_only=True) | |
| grid = processor.image_processor.image_grid_pinpoints | |
| patch_size = processor.image_processor.size["height"] | |
| ranked: list[tuple[int, int, dict]] = [] | |
| for index, row in enumerate(rows): | |
| with Image.open(row["image"]) as image: | |
| num_patches = image_size_to_num_patches(image.size, grid, patch_size) | |
| ranked.append((int(num_patches), index, row)) | |
| ranked.sort(key=lambda item: (-item[0], item[1])) | |
| chosen = ranked[:count] | |
| return [row for _patches, _index, row in chosen], [patches for patches, _index, _row in chosen] | |
| def main() -> None: | |
| args = parse_args() | |
| if args.iterations <= 0 or args.samples_per_rank <= 0: | |
| raise ValueError("--iterations and --samples-per-rank must be positive") | |
| local_rank = int(os.environ["LOCAL_RANK"]) | |
| torch.cuda.set_device(local_rank) | |
| deepspeed.init_distributed(dist_backend="nccl") | |
| rank = dist.get_rank() | |
| world_size = dist.get_world_size() | |
| device = torch.device("cuda", local_rank) | |
| try: | |
| config = load_config(args.config) | |
| candidate_count = world_size * args.samples_per_rank | |
| rows, patch_counts = _highest_resolution_rows(config, candidate_count) | |
| offset = rank * args.samples_per_rank | |
| local_rows = rows[offset : offset + args.samples_per_rank] | |
| local_patch_counts = patch_counts[offset : offset + args.samples_per_rank] | |
| if len(local_rows) != args.samples_per_rank: | |
| raise RuntimeError("not enough ChartQA rows for distributed high-resolution diagnostic") | |
| model_path = config["model"]["pretrained_model_path"] | |
| processor = AutoProcessor.from_pretrained(model_path, local_files_only=True) | |
| processor.tokenizer.padding_side = "left" | |
| model = LlavaOnevisionForConditionalGeneration.from_pretrained( | |
| model_path, | |
| torch_dtype=torch.bfloat16, | |
| attn_implementation="flash_attention_2", | |
| local_files_only=True, | |
| low_cpu_mem_usage=True, | |
| ) | |
| model.base_model.vision_tower.requires_grad_(False) | |
| model.config.use_cache = False | |
| model.to(device) | |
| trainable_parameters = sum(parameter.numel() for parameter in model.parameters() if parameter.requires_grad) | |
| engine, _, _, _ = deepspeed.initialize( | |
| model=model, | |
| model_parameters=[parameter for parameter in model.parameters() if parameter.requires_grad], | |
| config=_deepspeed_config(world_size=world_size, micro_batch_size=args.samples_per_rank), | |
| ) | |
| bucket_size = int(engine.optimizer.reduce_bucket_size) | |
| batch = collate_fn(local_rows, processor, label_id=None) | |
| input_ids = batch["input_ids"].to(device) | |
| attention_mask = batch["attention_mask"].to(device) | |
| pixel_values = batch["pixel_values"].to(device=device, dtype=torch.bfloat16) | |
| image_sizes = batch["image_sizes"].to(device) | |
| batch_num_images = student_batch_num_images_tensor(pixel_values, input_ids.size(0)) | |
| dist.barrier() | |
| started = time.perf_counter() | |
| for _ in range(args.iterations): | |
| outputs = engine( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| pixel_values=pixel_values, | |
| image_sizes=image_sizes, | |
| batch_num_images=batch_num_images, | |
| logits_to_keep=2, | |
| ) | |
| loss = outputs.logits.float().mean() | |
| engine.backward(loss) | |
| engine.step() | |
| torch.cuda.synchronize(device) | |
| elapsed = time.perf_counter() - started | |
| print( | |
| "ZeRO-2 VLM bounded-bucket diagnostic passed: " | |
| f"rank={rank}/{world_size} patch_counts={local_patch_counts} " | |
| f"input_shape={tuple(input_ids.shape)} bucket={bucket_size} " | |
| f"trainable={trainable_parameters} iterations={args.iterations} " | |
| f"elapsed_s={elapsed:.3f} loss={float(loss.detach().item()):.6f}", | |
| flush=True, | |
| ) | |
| dist.barrier() | |
| finally: | |
| if dist.is_initialized(): | |
| dist.destroy_process_group() | |
| if __name__ == "__main__": | |
| main() | |