Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/baselines/qwen2_5_vl_3b_clevr.sh +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/baselines/qwen2_5_vl_3b_geoqa8k.sh +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/config.yaml +96 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/format_prompt/caption_format.jinja +1 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/format_prompt/math_format.jinja +1 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/format_prompt/r1v_format.jinja +1 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/llama3.2v_critic_grpo.sh +29 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/mimo_vl_7b_llava_critic_grpo.sh +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/mimo_vl_rl_7b_llava_critic_grpo.sh +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/qwen2_5_vl_7b_llava_critic_grpo.sh +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/reward_function/caption.py +46 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/reward_function/math.py +46 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/reward_function/r1v.py +47 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/runtime_env.yaml +9 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/examples/thinklite_vl_7b_llava_critic_grpo.sh +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl.egg-info/PKG-INFO +262 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/Dockerfile +76 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/Makefile +18 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/README.md +217 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/__init__.py +32 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/init +1 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/__init__.py +13 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/monkey_patch.py +32 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/transformers/__init__.py +13 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/transformers/flash_attention_utils.py +191 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/transformers/qwen2_vl.py +189 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/protocol.py +689 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/pyproject.toml +39 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/requirements.txt +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/__init__.py +13 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/__init__.py +19 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/decorator.py +213 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/register_center/__init__.py +13 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/register_center/ray.py +28 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/worker.py +202 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/worker_group.py +194 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/ray/__init__.py +18 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/ray/base.py +493 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/__init__.py +13 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/config.py +123 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/core_algos.py +428 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/data_loader.py +88 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/main.py +130 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/metrics.py +120 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/ray_trainer.py +637 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/__init__.py +13 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/checkpoint/__init__.py +18 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/checkpoint/checkpoint_manager.py +160 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/checkpoint/fsdp_checkpoint_manager.py +113 -0
- LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/dataset.py +235 -0
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/baselines/qwen2_5_vl_3b_clevr.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
set -x
|
| 4 |
+
|
| 5 |
+
export PYTHONUNBUFFERED=1
|
| 6 |
+
|
| 7 |
+
MODEL_PATH=Qwen/Qwen2.5-VL-3B-Instruct # replace it with your local file path
|
| 8 |
+
|
| 9 |
+
python3 -m verl.trainer.main \
|
| 10 |
+
config=examples/config.yaml \
|
| 11 |
+
data.train_files=BUAADreamer/clevr_count_70k@train \
|
| 12 |
+
data.val_files=BUAADreamer/clevr_count_70k@test \
|
| 13 |
+
data.format_prompt=./examples/format_prompt/r1v_format.jinja \
|
| 14 |
+
worker.actor.model.model_path=${MODEL_PATH} \
|
| 15 |
+
worker.rollout.tensor_parallel_size=1 \
|
| 16 |
+
worker.reward.reward_type=sequential \
|
| 17 |
+
worker.reward.reward_function=./examples/reward_function/r1v.py:compute_score \
|
| 18 |
+
trainer.experiment_name=qwen2_5_vl_3b_clevr \
|
| 19 |
+
trainer.n_gpus_per_node=2
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/baselines/qwen2_5_vl_3b_geoqa8k.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
set -x
|
| 4 |
+
|
| 5 |
+
export PYTHONUNBUFFERED=1
|
| 6 |
+
|
| 7 |
+
MODEL_PATH=Qwen/Qwen2.5-VL-3B-Instruct # replace it with your local file path
|
| 8 |
+
|
| 9 |
+
python3 -m verl.trainer.main \
|
| 10 |
+
config=examples/config.yaml \
|
| 11 |
+
data.train_files=leonardPKU/GEOQA_8K_R1V@train \
|
| 12 |
+
data.val_files=leonardPKU/GEOQA_8K_R1V@test \
|
| 13 |
+
data.format_prompt=./examples/format_prompt/r1v_format.jinja \
|
| 14 |
+
worker.actor.model.model_path=${MODEL_PATH} \
|
| 15 |
+
worker.rollout.tensor_parallel_size=1 \
|
| 16 |
+
worker.reward.reward_type=sequential \
|
| 17 |
+
worker.reward.reward_function=./examples/reward_function/r1v.py:compute_score \
|
| 18 |
+
trainer.experiment_name=qwen2_5_vl_3b_geoqa8k \
|
| 19 |
+
trainer.n_gpus_per_node=8
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/config.yaml
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
data:
|
| 2 |
+
train_files: hiyouga/math12k@train
|
| 3 |
+
val_files: hiyouga/math12k@test
|
| 4 |
+
prompt_key: problem
|
| 5 |
+
answer_key: answer
|
| 6 |
+
image_key: image
|
| 7 |
+
max_prompt_length: 4096
|
| 8 |
+
max_response_length: 2048
|
| 9 |
+
rollout_batch_size: 256
|
| 10 |
+
val_batch_size: 1024
|
| 11 |
+
format_prompt: ./examples/format_prompt/math_format.jinja
|
| 12 |
+
override_chat_template: null
|
| 13 |
+
shuffle: true
|
| 14 |
+
seed: 1
|
| 15 |
+
max_pixels: 4194304
|
| 16 |
+
min_pixels: 262144
|
| 17 |
+
filter_overlong_prompts: true
|
| 18 |
+
|
| 19 |
+
algorithm:
|
| 20 |
+
adv_estimator: grpo
|
| 21 |
+
disable_kl: false
|
| 22 |
+
use_kl_loss: true
|
| 23 |
+
kl_penalty: low_var_kl
|
| 24 |
+
kl_coef: 1.0e-2
|
| 25 |
+
|
| 26 |
+
worker:
|
| 27 |
+
actor:
|
| 28 |
+
global_batch_size: 128
|
| 29 |
+
micro_batch_size_per_device_for_update: 4
|
| 30 |
+
micro_batch_size_per_device_for_experience: 8
|
| 31 |
+
max_grad_norm: 1.0
|
| 32 |
+
padding_free: true
|
| 33 |
+
ulysses_sequence_parallel_size: 1
|
| 34 |
+
is_entropy_reg: False
|
| 35 |
+
entropy_coef: 0.001
|
| 36 |
+
model:
|
| 37 |
+
model_path: Qwen/Qwen2.5-7B-Instruct
|
| 38 |
+
enable_gradient_checkpointing: true
|
| 39 |
+
trust_remote_code: true
|
| 40 |
+
freeze_vision_tower: false
|
| 41 |
+
optim:
|
| 42 |
+
lr: 1.0e-6
|
| 43 |
+
weight_decay: 1.0e-2
|
| 44 |
+
strategy: adamw # {adamw, adamw_bf16}
|
| 45 |
+
lr_warmup_ratio: 0.0
|
| 46 |
+
fsdp:
|
| 47 |
+
enable_full_shard: true
|
| 48 |
+
enable_cpu_offload: false
|
| 49 |
+
enable_rank0_init: true
|
| 50 |
+
offload:
|
| 51 |
+
offload_params: true # true: more CPU memory; false: more GPU memory
|
| 52 |
+
offload_optimizer: true # true: more CPU memory; false: more GPU memory
|
| 53 |
+
|
| 54 |
+
rollout:
|
| 55 |
+
n: 8
|
| 56 |
+
temperature: 1.0
|
| 57 |
+
top_p: 0.99
|
| 58 |
+
gpu_memory_utilization: 0.6
|
| 59 |
+
max_num_batched_tokens: 8192
|
| 60 |
+
max_model_len: 8192
|
| 61 |
+
enforce_eager: false
|
| 62 |
+
enable_chunked_prefill: false
|
| 63 |
+
tensor_parallel_size: 2
|
| 64 |
+
limit_images: 10
|
| 65 |
+
val_override_config:
|
| 66 |
+
temperature: 0.5
|
| 67 |
+
n: 1
|
| 68 |
+
|
| 69 |
+
ref:
|
| 70 |
+
fsdp:
|
| 71 |
+
enable_full_shard: true
|
| 72 |
+
enable_cpu_offload: true # true: more CPU memory; false: more GPU memory
|
| 73 |
+
enable_rank0_init: true
|
| 74 |
+
offload:
|
| 75 |
+
offload_params: false
|
| 76 |
+
|
| 77 |
+
reward:
|
| 78 |
+
reward_type: batch
|
| 79 |
+
reward_function: ./examples/reward_function/math.py:compute_score
|
| 80 |
+
|
| 81 |
+
trainer:
|
| 82 |
+
total_epochs: 15
|
| 83 |
+
max_steps: null
|
| 84 |
+
project_name: easy_r1
|
| 85 |
+
experiment_name: qwen2_5_7b_math_grpo
|
| 86 |
+
logger: ["console", "wandb"]
|
| 87 |
+
nnodes: 1
|
| 88 |
+
n_gpus_per_node: 8
|
| 89 |
+
val_freq: -1 # -1 to disable
|
| 90 |
+
val_before_train: false
|
| 91 |
+
val_only: false
|
| 92 |
+
val_generations_to_log: 3
|
| 93 |
+
save_freq: 10 # -1 to disable
|
| 94 |
+
save_limit: -1 # -1 to disable
|
| 95 |
+
save_checkpoint_path: null
|
| 96 |
+
load_checkpoint_path: null
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/format_prompt/caption_format.jinja
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{{ content | trim }} You FIRST describe the given image in a detailed manner, THEN think about the reasoning process as an internal monologue based on the generated description and given question and then provide the final answer. The description MUST BE enclosed within <caption> </caption> tags. The reasoning process MUST BE enclosed within <think> </think> tags. The final answer MUST BE put in \boxed{}.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/format_prompt/math_format.jinja
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{{ content | trim }} You FIRST think about the reasoning process as an internal monologue and then provide the final answer. The reasoning process MUST BE enclosed within <think> </think> tags. The final answer MUST BE put in \boxed{}.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/format_prompt/r1v_format.jinja
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{{ content | trim }} A conversation between User and Assistant. The user asks a question, and the Assistant solves it. The assistant first thinks about the reasoning process in the mind and then provides the user with the answer. The reasoning process and answer are enclosed within <think> </think> and <answer> </answer> tags, respectively, i.e., <think> reasoning process here </think><answer> answer here </answer>
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/llama3.2v_critic_grpo.sh
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
set -x
|
| 4 |
+
|
| 5 |
+
export PYTHONUNBUFFERED=1
|
| 6 |
+
|
| 7 |
+
MODEL_PATH=meta-llama/Llama-3.2-11B-Vision-Instruct # replace it with your local file path
|
| 8 |
+
|
| 9 |
+
python3 -m verl.trainer.main \
|
| 10 |
+
config=examples/config.yaml \
|
| 11 |
+
data.train_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 12 |
+
data.val_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 13 |
+
data.shuffle=True \
|
| 14 |
+
data.format_prompt=./examples/format_prompt/math_format.jinja \
|
| 15 |
+
data.rollout_batch_size=256 \
|
| 16 |
+
data.max_prompt_length=4096 \
|
| 17 |
+
data.prompt_key=question \
|
| 18 |
+
worker.actor.is_entropy_reg=False \
|
| 19 |
+
worker.actor.global_batch_size=128 \
|
| 20 |
+
worker.actor.optim.lr=5.0e-7 \
|
| 21 |
+
worker.actor.micro_batch_size_per_device_for_update=4 \
|
| 22 |
+
worker.actor.micro_batch_size_per_device_for_experience=8 \
|
| 23 |
+
worker.actor.model.model_path=${MODEL_PATH} \
|
| 24 |
+
worker.rollout.tensor_parallel_size=8 \
|
| 25 |
+
worker.rollout.limit_images=1 \
|
| 26 |
+
worker.rollout.gpu_memory_utilization=0.6 \
|
| 27 |
+
worker.reward.reward_function=./examples/reward_function/math.py:compute_score \
|
| 28 |
+
trainer.experiment_name=llama3.2v_critic_grpo \
|
| 29 |
+
trainer.n_gpus_per_node=8
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/mimo_vl_7b_llava_critic_grpo.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
MODEL_PATH=XiaomiMiMo/MiMo-VL-7B-SFT-2508
|
| 4 |
+
|
| 5 |
+
python3 -m verl.trainer.main \
|
| 6 |
+
config=examples/config.yaml \
|
| 7 |
+
data.train_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 8 |
+
data.val_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 9 |
+
data.filter_overlong_prompts=True \
|
| 10 |
+
data.prompt_key=question \
|
| 11 |
+
data.max_prompt_length=6142 \
|
| 12 |
+
worker.actor.is_entropy_reg=False \
|
| 13 |
+
worker.actor.global_batch_size=128 \
|
| 14 |
+
worker.actor.micro_batch_size_per_device_for_update=4 \
|
| 15 |
+
worker.actor.micro_batch_size_per_device_for_experience=8 \
|
| 16 |
+
worker.actor.model.model_path=${MODEL_PATH} \
|
| 17 |
+
trainer.experiment_name=mimo_vl_7b_llava_critic_grpo \
|
| 18 |
+
trainer.n_gpus_per_node=8
|
| 19 |
+
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/mimo_vl_rl_7b_llava_critic_grpo.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
MODEL_PATH=XiaomiMiMo/MiMo-VL-7B-RL-2508
|
| 4 |
+
|
| 5 |
+
python3 -m verl.trainer.main \
|
| 6 |
+
config=examples/config.yaml \
|
| 7 |
+
data.train_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 8 |
+
data.val_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 9 |
+
data.filter_overlong_prompts=True \
|
| 10 |
+
data.prompt_key=question \
|
| 11 |
+
data.max_prompt_length=6142 \
|
| 12 |
+
worker.actor.is_entropy_reg=False \
|
| 13 |
+
worker.actor.global_batch_size=128 \
|
| 14 |
+
worker.actor.micro_batch_size_per_device_for_update=4 \
|
| 15 |
+
worker.actor.micro_batch_size_per_device_for_experience=8 \
|
| 16 |
+
worker.actor.model.model_path=${MODEL_PATH} \
|
| 17 |
+
trainer.experiment_name=mimo_vl_rl_7b_llava_critic_grpo \
|
| 18 |
+
trainer.n_gpus_per_node=8
|
| 19 |
+
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/qwen2_5_vl_7b_llava_critic_grpo.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
MODEL_PATH=Qwen/Qwen2.5-VL-7B-Instruct
|
| 4 |
+
|
| 5 |
+
python3 -m verl.trainer.main \
|
| 6 |
+
config=examples/config.yaml \
|
| 7 |
+
data.train_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 8 |
+
data.val_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 9 |
+
data.filter_overlong_prompts=True \
|
| 10 |
+
data.prompt_key=question \
|
| 11 |
+
data.max_prompt_length=6142 \
|
| 12 |
+
worker.actor.is_entropy_reg=False \
|
| 13 |
+
worker.actor.global_batch_size=128 \
|
| 14 |
+
worker.actor.micro_batch_size_per_device_for_update=4 \
|
| 15 |
+
worker.actor.micro_batch_size_per_device_for_experience=8 \
|
| 16 |
+
worker.actor.model.model_path=${MODEL_PATH} \
|
| 17 |
+
trainer.experiment_name=qwen2_5_vl_7b_llava_critic_grpo \
|
| 18 |
+
trainer.n_gpus_per_node=8
|
| 19 |
+
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/reward_function/caption.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import re
|
| 16 |
+
from typing import Dict, List
|
| 17 |
+
|
| 18 |
+
from mathruler.grader import extract_boxed_content, grade_answer
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def format_reward(predict: str) -> float:
|
| 22 |
+
pattern = re.compile(r"<caption>.*</caption>.*<think>.*</think>.*\\boxed\{.*\}.*", re.DOTALL)
|
| 23 |
+
format_match = re.fullmatch(pattern, predict)
|
| 24 |
+
return 1.0 if format_match else 0.0
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def accuracy_reward(predict: str, ground_truth: str) -> float:
|
| 28 |
+
answer = extract_boxed_content(predict)
|
| 29 |
+
return 1.0 if grade_answer(answer, ground_truth) else 0.0
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def compute_score(predicts: List[str], ground_truths: List[str], format_weight: float = 0.1) -> List[Dict[str, float]]:
|
| 33 |
+
scores = []
|
| 34 |
+
for predict, ground_truth in zip(predicts, ground_truths):
|
| 35 |
+
predict = re.sub(r"\s*(<|>|/)\s*", r"\1", predict) # handle qwen2.5vl-32b format
|
| 36 |
+
format_score = format_reward(predict)
|
| 37 |
+
accuracy_score = accuracy_reward(predict, ground_truth)
|
| 38 |
+
scores.append(
|
| 39 |
+
{
|
| 40 |
+
"overall": (1 - format_weight) * accuracy_score + format_weight * format_score,
|
| 41 |
+
"format": format_score,
|
| 42 |
+
"accuracy": accuracy_score,
|
| 43 |
+
}
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
return scores
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/reward_function/math.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import re
|
| 16 |
+
from typing import Dict, List
|
| 17 |
+
|
| 18 |
+
from mathruler.grader import extract_boxed_content, grade_answer
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def format_reward(predict: str) -> float:
|
| 22 |
+
pattern = re.compile(r"<think>.*</think>.*\\boxed\{.*\}.*", re.DOTALL)
|
| 23 |
+
format_match = re.fullmatch(pattern, predict)
|
| 24 |
+
return 1.0 if format_match else 0.0
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def accuracy_reward(predict: str, ground_truth: str) -> float:
|
| 28 |
+
answer = extract_boxed_content(predict)
|
| 29 |
+
return 1.0 if grade_answer(answer, ground_truth) else 0.0
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def compute_score(predicts: List[str], ground_truths: List[str], format_weight: float = 0.1) -> List[Dict[str, float]]:
|
| 33 |
+
scores = []
|
| 34 |
+
for predict, ground_truth in zip(predicts, ground_truths):
|
| 35 |
+
predict = re.sub(r"\s*(<|>|/)\s*", r"\1", predict) # handle qwen2.5vl-32b format
|
| 36 |
+
format_score = format_reward(predict)
|
| 37 |
+
accuracy_score = accuracy_reward(predict, ground_truth)
|
| 38 |
+
scores.append(
|
| 39 |
+
{
|
| 40 |
+
"overall": (1 - format_weight) * accuracy_score + format_weight * format_score,
|
| 41 |
+
"format": format_score,
|
| 42 |
+
"accuracy": accuracy_score,
|
| 43 |
+
}
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
return scores
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/reward_function/r1v.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import re
|
| 16 |
+
from typing import Dict
|
| 17 |
+
|
| 18 |
+
from mathruler.grader import grade_answer
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def format_reward(predict: str) -> float:
|
| 22 |
+
pattern = re.compile(r"<think>.*?</think>\s*<answer>.*?</answer>", re.DOTALL)
|
| 23 |
+
format_match = re.fullmatch(pattern, predict)
|
| 24 |
+
return 1.0 if format_match else 0.0
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def accuracy_reward(predict: str, ground_truth: str) -> float:
|
| 28 |
+
try:
|
| 29 |
+
content_match = re.search(r"<answer>(.*?)</answer>", predict)
|
| 30 |
+
given_answer = content_match.group(1).strip() if content_match else predict.strip()
|
| 31 |
+
if grade_answer(given_answer, ground_truth.strip()):
|
| 32 |
+
return 1.0
|
| 33 |
+
|
| 34 |
+
except Exception:
|
| 35 |
+
pass
|
| 36 |
+
|
| 37 |
+
return 0.0
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def compute_score(predict: str, ground_truth: str, format_weight: float = 0.5) -> Dict[str, float]:
|
| 41 |
+
format_score = format_reward(predict)
|
| 42 |
+
accuracy_score = accuracy_reward(predict, ground_truth)
|
| 43 |
+
return {
|
| 44 |
+
"overall": (1 - format_weight) * accuracy_score + format_weight * format_score,
|
| 45 |
+
"format": format_score,
|
| 46 |
+
"accuracy": accuracy_score,
|
| 47 |
+
}
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/runtime_env.yaml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
working_dir: ./
|
| 2 |
+
excludes: ["/.git/"]
|
| 3 |
+
env_vars:
|
| 4 |
+
TOKENIZERS_PARALLELISM: "true"
|
| 5 |
+
NCCL_DEBUG: "WARN"
|
| 6 |
+
VLLM_LOGGING_LEVEL: "WARN"
|
| 7 |
+
TORCH_NCCL_AVOID_RECORD_STREAMS: "1"
|
| 8 |
+
PYTORCH_CUDA_ALLOC_CONF: "expandable_segments:False"
|
| 9 |
+
PYTHONUNBUFFERED: "1"
|
LLaVA-NeXT/llava-critic-r1/EasyR1/examples/thinklite_vl_7b_llava_critic_grpo.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
MODEL_PATH=russwang/ThinkLite-VL-7B
|
| 4 |
+
|
| 5 |
+
python3 -m verl.trainer.main \
|
| 6 |
+
config=examples/config.yaml \
|
| 7 |
+
data.train_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 8 |
+
data.val_files=lmms-lab/LLaVA-Critic-GRPO-dataset@train \
|
| 9 |
+
data.filter_overlong_prompts=True \
|
| 10 |
+
data.prompt_key=question \
|
| 11 |
+
data.max_prompt_length=6142 \
|
| 12 |
+
worker.actor.is_entropy_reg=False \
|
| 13 |
+
worker.actor.global_batch_size=128 \
|
| 14 |
+
worker.actor.optim.lr=5.0e-7 \
|
| 15 |
+
worker.actor.micro_batch_size_per_device_for_update=4 \
|
| 16 |
+
worker.actor.micro_batch_size_per_device_for_experience=8 \
|
| 17 |
+
worker.actor.model.model_path=${MODEL_PATH} \
|
| 18 |
+
trainer.experiment_name=thinklite_vl_7b_llava_critic_grpo \
|
| 19 |
+
trainer.n_gpus_per_node=8
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl.egg-info/PKG-INFO
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: verl
|
| 3 |
+
Version: 0.3.1.dev0
|
| 4 |
+
Summary: An Efficient, Scalable, Multi-Modality RL Training Framework based on veRL
|
| 5 |
+
Home-page: https://github.com/volcengine/verl
|
| 6 |
+
Author: verl
|
| 7 |
+
Author-email: zhangchi.usc1992@bytedance.com, gmsheng@connect.hku.hk, hiyouga@buaa.edu.cn
|
| 8 |
+
License: Apache 2.0 License
|
| 9 |
+
Requires-Python: >=3.9.0
|
| 10 |
+
Description-Content-Type: text/markdown
|
| 11 |
+
License-File: LICENSE
|
| 12 |
+
Requires-Dist: accelerate
|
| 13 |
+
Requires-Dist: codetiming
|
| 14 |
+
Requires-Dist: datasets
|
| 15 |
+
Requires-Dist: liger-kernel
|
| 16 |
+
Requires-Dist: mathruler
|
| 17 |
+
Requires-Dist: numpy
|
| 18 |
+
Requires-Dist: omegaconf
|
| 19 |
+
Requires-Dist: pandas
|
| 20 |
+
Requires-Dist: peft
|
| 21 |
+
Requires-Dist: pillow
|
| 22 |
+
Requires-Dist: pyarrow>=15.0.0
|
| 23 |
+
Requires-Dist: pylatexenc
|
| 24 |
+
Requires-Dist: qwen-vl-utils
|
| 25 |
+
Requires-Dist: ray[default]
|
| 26 |
+
Requires-Dist: tensordict
|
| 27 |
+
Requires-Dist: torchdata
|
| 28 |
+
Requires-Dist: transformers>=4.51.0
|
| 29 |
+
Requires-Dist: vllm>=0.7.3
|
| 30 |
+
Requires-Dist: wandb
|
| 31 |
+
Provides-Extra: dev
|
| 32 |
+
Requires-Dist: pre-commit; extra == "dev"
|
| 33 |
+
Requires-Dist: ruff; extra == "dev"
|
| 34 |
+
Dynamic: author
|
| 35 |
+
Dynamic: author-email
|
| 36 |
+
Dynamic: description
|
| 37 |
+
Dynamic: description-content-type
|
| 38 |
+
Dynamic: home-page
|
| 39 |
+
Dynamic: license
|
| 40 |
+
Dynamic: license-file
|
| 41 |
+
Dynamic: provides-extra
|
| 42 |
+
Dynamic: requires-dist
|
| 43 |
+
Dynamic: requires-python
|
| 44 |
+
Dynamic: summary
|
| 45 |
+
|
| 46 |
+
# EasyR1: An Efficient, Scalable, Multi-Modality RL Training Framework
|
| 47 |
+
|
| 48 |
+
[](https://github.com/hiyouga/EasyR1/stargazers)
|
| 49 |
+
[](https://twitter.com/llamafactory_ai)
|
| 50 |
+
|
| 51 |
+
This project is a clean fork of the original [veRL](https://github.com/volcengine/verl) project to support vision language models, we thank all the authors for providing such a high-performance RL training framework.
|
| 52 |
+
|
| 53 |
+
EasyR1 is efficient and scalable due to the design of **[HybirdEngine](https://arxiv.org/abs/2409.19256)** and the latest release of **[vLLM](https://github.com/vllm-project/vllm)**'s SPMD mode.
|
| 54 |
+
|
| 55 |
+
## Features
|
| 56 |
+
|
| 57 |
+
- Supported models
|
| 58 |
+
- Llama3/Qwen2/Qwen2.5/Qwen3 language models
|
| 59 |
+
- Qwen2/Qwen2.5-VL vision language models
|
| 60 |
+
- DeepSeek-R1 distill models
|
| 61 |
+
|
| 62 |
+
- Supported algorithms
|
| 63 |
+
- GRPO
|
| 64 |
+
- Reinforce++
|
| 65 |
+
- ReMax
|
| 66 |
+
- RLOO
|
| 67 |
+
|
| 68 |
+
- Supported datasets
|
| 69 |
+
- Any text, vision-text dataset in a [specific format](#custom-dataset)
|
| 70 |
+
|
| 71 |
+
- Supported tricks
|
| 72 |
+
- Padding-free training
|
| 73 |
+
- Resuming from checkpoint
|
| 74 |
+
- Wandb & SwanLab & Mlflow & Tensorboard tracking
|
| 75 |
+
|
| 76 |
+
## Requirements
|
| 77 |
+
|
| 78 |
+
### Software Requirements
|
| 79 |
+
|
| 80 |
+
- Python 3.9+
|
| 81 |
+
- transformers>=4.51.0
|
| 82 |
+
- flash-attn>=2.4.3
|
| 83 |
+
- vllm>=0.8.3
|
| 84 |
+
|
| 85 |
+
We provide a [Dockerfile](./Dockerfile) to easily build environments.
|
| 86 |
+
|
| 87 |
+
We recommend using the [pre-built docker image](https://hub.docker.com/r/hiyouga/verl) in EasyR1.
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
docker pull hiyouga/verl:ngc-th2.6.0-cu126-vllm0.8.4-flashinfer0.2.2-cxx11abi0
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Hardware Requirements
|
| 94 |
+
|
| 95 |
+
\* *estimated*
|
| 96 |
+
|
| 97 |
+
| Method | Bits | 1.5B | 3B | 7B | 32B |
|
| 98 |
+
| ------------------------ | ---- | ------ | ------ | ------ | ------- |
|
| 99 |
+
| GRPO Full Fine-Tuning | AMP | 2*24GB | 4*40GB | 8*40GB | 16*80GB |
|
| 100 |
+
| GRPO Full Fine-Tuning | BF16 | 1*24GB | 1*40GB | 4*40GB | 8*80GB |
|
| 101 |
+
|
| 102 |
+
> [!NOTE]
|
| 103 |
+
> Use `worker.actor.fsdp.torch_dtype=bf16` and `worker.actor.optim.strategy=adamw_bf16` to enable bf16 training.
|
| 104 |
+
>
|
| 105 |
+
> We are working hard to reduce the VRAM in RL training, LoRA support will be integrated in next updates.
|
| 106 |
+
|
| 107 |
+
## Tutorial: Run Qwen2.5-VL GRPO on [Geometry3K](https://huggingface.co/datasets/hiyouga/geometry3k) Dataset in Just 3 Steps
|
| 108 |
+
|
| 109 |
+

|
| 110 |
+
|
| 111 |
+
### Installation
|
| 112 |
+
|
| 113 |
+
```bash
|
| 114 |
+
git clone https://github.com/hiyouga/EasyR1.git
|
| 115 |
+
cd EasyR1
|
| 116 |
+
pip install -e .
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
### GRPO Training
|
| 120 |
+
|
| 121 |
+
```bash
|
| 122 |
+
bash examples/qwen2_5_vl_7b_geo3k_grpo.sh
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
### Merge Checkpoint in Hugging Face Format
|
| 126 |
+
|
| 127 |
+
```bash
|
| 128 |
+
python3 scripts/model_merger.py --local_dir checkpoints/easy_r1/exp_name/global_step_1/actor
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
> [!TIP]
|
| 132 |
+
> If you encounter issues with connecting to Hugging Face, consider using `export HF_ENDPOINT=https://hf-mirror.com`.
|
| 133 |
+
>
|
| 134 |
+
> If you want to use SwanLab logger, consider using `bash examples/qwen2_5_vl_7b_geo3k_swanlab.sh`.
|
| 135 |
+
|
| 136 |
+
## Custom Dataset
|
| 137 |
+
|
| 138 |
+
Please refer to the example datasets to prepare your own dataset.
|
| 139 |
+
|
| 140 |
+
- Text dataset: https://huggingface.co/datasets/hiyouga/math12k
|
| 141 |
+
- Image-text dataset: https://huggingface.co/datasets/hiyouga/geometry3k
|
| 142 |
+
- Multi-image-text dataset: https://huggingface.co/datasets/hiyouga/journeybench-multi-image-vqa
|
| 143 |
+
|
| 144 |
+
## How to Understand GRPO in EasyR1
|
| 145 |
+
|
| 146 |
+

|
| 147 |
+
|
| 148 |
+
- To learn about the GRPO algorithm, you can refer to [Hugging Face's blog](https://huggingface.co/docs/trl/v0.16.1/en/grpo_trainer).
|
| 149 |
+
|
| 150 |
+
## How to Run 70B+ Model in Multi-node Environment
|
| 151 |
+
|
| 152 |
+
1. Start the Ray head node.
|
| 153 |
+
|
| 154 |
+
```bash
|
| 155 |
+
ray start --head --port=6379 --dashboard-host=0.0.0.0
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
2. Start the Ray worker node and connect to the head node.
|
| 159 |
+
|
| 160 |
+
```bash
|
| 161 |
+
ray start --address=<head_node_ip>:6379
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
3. Check the Ray resource pool.
|
| 165 |
+
|
| 166 |
+
```bash
|
| 167 |
+
ray status
|
| 168 |
+
```
|
| 169 |
+
|
| 170 |
+
4. Run training script on the Ray head node only.
|
| 171 |
+
|
| 172 |
+
```bash
|
| 173 |
+
bash examples/qwen2_5_vl_7b_geo3k_grpo.sh
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
See the **[veRL's official doc](https://verl.readthedocs.io/en/latest/start/multinode.html)** for more details about multi-node training and Ray debugger.
|
| 177 |
+
|
| 178 |
+
## Other Baselines
|
| 179 |
+
|
| 180 |
+
We also reproduced the following two baselines of the [R1-V](https://github.com/deep-agent/R1-V) project.
|
| 181 |
+
- [CLEVR-70k-Counting](examples/baselines/qwen2_5_vl_3b_clevr.sh): Train the Qwen2.5-VL-3B-Instruct model on counting problem.
|
| 182 |
+
- [GeoQA-8k](examples/baselines/qwen2_5_vl_3b_geoqa8k.sh): Train the Qwen2.5-VL-3B-Instruct model on GeoQA problem.
|
| 183 |
+
|
| 184 |
+
## Performance Baselines
|
| 185 |
+
|
| 186 |
+
See [baselines.md](assets/baselines.md).
|
| 187 |
+
|
| 188 |
+
## Awesome Work using EasyR1
|
| 189 |
+
|
| 190 |
+
- **MMR1**: Advancing the Frontiers of Multimodal Reasoning. [![[code]](https://img.shields.io/github/stars/LengSicong/MMR1)](https://github.com/LengSicong/MMR1)
|
| 191 |
+
- **Vision-R1**: Incentivizing Reasoning Capability in Multimodal Large Language Models. [![[code]](https://img.shields.io/github/stars/Osilly/Vision-R1)](https://github.com/Osilly/Vision-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2503.06749-blue)](https://arxiv.org/abs/2503.06749)
|
| 192 |
+
- **Seg-Zero**: Reasoning-Chain Guided Segmentation via Cognitive Reinforcement. [![[code]](https://img.shields.io/github/stars/dvlab-research/Seg-Zero)](https://github.com/dvlab-research/Seg-Zero) [![[arxiv]](https://img.shields.io/badge/arxiv-2503.06520-blue)](https://arxiv.org/abs/2503.06520)
|
| 193 |
+
- **MetaSpatial**: Reinforcing 3D Spatial Reasoning in VLMs for the Metaverse. [![[code]](https://img.shields.io/github/stars/PzySeere/MetaSpatial)](https://github.com/PzySeere/MetaSpatial) [![[arxiv]](https://img.shields.io/badge/arxiv-2503.18470-blue)](https://arxiv.org/abs/2503.18470)
|
| 194 |
+
- **Temporal-R1**: Envolving Temporal Reasoning Capability into LMMs via Temporal Consistent Reward. [![[code]](https://img.shields.io/github/stars/appletea233/Temporal-R1)](https://github.com/appletea233/Temporal-R1)
|
| 195 |
+
- **NoisyRollout**: Reinforcing Visual Reasoning with Data Augmentation. [![[code]](https://img.shields.io/github/stars/John-AI-Lab/NoisyRollout)](https://github.com/John-AI-Lab/NoisyRollout) [![[arxiv]](https://img.shields.io/badge/arxiv-2504.13055-blue)](https://arxiv.org/pdf/2504.13055)
|
| 196 |
+
- **GUI-R1**: A Generalist R1-Style Vision-Language Action Model For GUI Agents. [![[code]](https://img.shields.io/github/stars/ritzz-ai/GUI-R1)](https://github.com/ritzz-ai/GUI-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2504.10458-blue)](https://arxiv.org/abs/2504.10458)
|
| 197 |
+
- **R1-Track**: Direct Application of MLLMs to Visual Object Tracking via Reinforcement Learning. [![[code]](https://img.shields.io/github/stars/Wangbiao2/R1-Track)](https://github.com/Wangbiao2/R1-Track)
|
| 198 |
+
- **VisionReasoner**: Unified Visual Perception and Reasoning via Reinforcement Learning. [![[code]](https://img.shields.io/github/stars/dvlab-research/VisionReasoner)](https://github.com/dvlab-research/VisionReasoner) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.12081-blue)](https://arxiv.org/abs/2505.12081)
|
| 199 |
+
- **MM-UPT**: Unsupervised Post-Training for Multi-Modal LLM Reasoning via GRPO. [![[code]](https://img.shields.io/github/stars/waltonfuture/MM-UPT)](https://github.com/waltonfuture/MM-UPT) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.22453-blue)](https://arxiv.org/pdf/2505.22453)
|
| 200 |
+
- **RL-with-Cold-Start**: Advancing Multimodal Reasoning via Reinforcement Learning with Cold Start. [![[code]](https://img.shields.io/github/stars/waltonfuture/RL-with-Cold-Start)](https://github.com/waltonfuture/RL-with-Cold-Start) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.22334-blue)](https://arxiv.org/pdf/2505.22334)
|
| 201 |
+
- **ViGoRL**: Grounded Reinforcement Learning for Visual Reasoning. [![[code]](https://img.shields.io/github/stars/Gabesarch/grounded-rl)](https://github.com/Gabesarch/grounded-rl) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.22334-blue)](https://arxiv.org/abs/2505.23678)
|
| 202 |
+
- **Revisual-R1**: Advancing Multimodal Reasoning: From Optimized Cold Start to Staged Reinforcement Learning. [![[code]](https://img.shields.io/github/stars/CSfufu/Revisual-R1)](https://github.com/CSfufu/Revisual-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2506.04207-blue)](https://arxiv.org/abs/2506.04207)
|
| 203 |
+
- **SophiaVL-R1**: Reinforcing MLLMs Reasoning with Thinking Reward. [![[code]](https://img.shields.io/github/stars/kxfan2002/SophiaVL-R1)](https://github.com/kxfan2002/SophiaVL-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.17018-blue)](https://arxiv.org/abs/2505.17018)
|
| 204 |
+
|
| 205 |
+
## TODO
|
| 206 |
+
|
| 207 |
+
- Support LoRA (high priority).
|
| 208 |
+
- Support ulysses parallelism for VLMs (middle priority).
|
| 209 |
+
- Support more VLM architectures.
|
| 210 |
+
|
| 211 |
+
> [!NOTE]
|
| 212 |
+
> We will not provide scripts for supervised fine-tuning and inference in this project. If you have such requirements, we recommend using [LLaMA-Factory](https://github.com/hiyouga/LLaMA-Factory).
|
| 213 |
+
|
| 214 |
+
### Known bugs
|
| 215 |
+
|
| 216 |
+
These features are temporarily disabled for now, we plan to fix them one-by-one in the future updates.
|
| 217 |
+
|
| 218 |
+
- Vision language models are not compatible with ulysses parallelism yet.
|
| 219 |
+
|
| 220 |
+
## Discussion Group
|
| 221 |
+
|
| 222 |
+
👋 Join our [WeChat group](assets/wechat.jpg).
|
| 223 |
+
|
| 224 |
+
## FAQs
|
| 225 |
+
|
| 226 |
+
> ValueError: Image features and image tokens do not match: tokens: 8192, features 9800
|
| 227 |
+
|
| 228 |
+
Increase the `data.max_prompt_length` or reduce the `data.max_pixels`.
|
| 229 |
+
|
| 230 |
+
> RuntimeError: CUDA Error: out of memory at /workspace/csrc/cumem_allocator.cpp:62
|
| 231 |
+
|
| 232 |
+
Reduce the `worker.rollout.gpu_memory_utilization` and enable `worker.actor.offload.offload_params`.
|
| 233 |
+
|
| 234 |
+
> RuntimeError: 0 active drivers ([]). There should only be one.
|
| 235 |
+
|
| 236 |
+
Uninstall `deepspeed` from the current python environment.
|
| 237 |
+
|
| 238 |
+
## Citation
|
| 239 |
+
|
| 240 |
+
Core contributors: [Yaowei Zheng](https://github.com/hiyouga), [Junting Lu](https://github.com/AL-377), [Shenzhi Wang](https://github.com/Shenzhi-Wang), [Zhangchi Feng](https://github.com/BUAADreamer), [Dongdong Kuang](https://github.com/Kuangdd01) and Yuwen Xiong
|
| 241 |
+
|
| 242 |
+
We also thank Guangming Sheng and Chi Zhang for helpful discussions.
|
| 243 |
+
|
| 244 |
+
```bibtex
|
| 245 |
+
@misc{zheng2025easyr1,
|
| 246 |
+
title = {EasyR1: An Efficient, Scalable, Multi-Modality RL Training Framework},
|
| 247 |
+
author = {Yaowei Zheng, Junting Lu, Shenzhi Wang, Zhangchi Feng, Dongdong Kuang, Yuwen Xiong},
|
| 248 |
+
howpublished = {\url{https://github.com/hiyouga/EasyR1}},
|
| 249 |
+
year = {2025}
|
| 250 |
+
}
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
We recommend to also cite the original work.
|
| 254 |
+
|
| 255 |
+
```bibtex
|
| 256 |
+
@article{sheng2024hybridflow,
|
| 257 |
+
title = {HybridFlow: A Flexible and Efficient RLHF Framework},
|
| 258 |
+
author = {Guangming Sheng and Chi Zhang and Zilingfeng Ye and Xibin Wu and Wang Zhang and Ru Zhang and Yanghua Peng and Haibin Lin and Chuan Wu},
|
| 259 |
+
year = {2024},
|
| 260 |
+
journal = {arXiv preprint arXiv: 2409.19256}
|
| 261 |
+
}
|
| 262 |
+
```
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/Dockerfile
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Start from the NVIDIA official image (ubuntu-22.04 + cuda-12.6 + python-3.10)
|
| 2 |
+
# https://docs.nvidia.com/deeplearning/frameworks/pytorch-release-notes/rel-24-08.html
|
| 3 |
+
FROM nvcr.io/nvidia/pytorch:24.08-py3
|
| 4 |
+
|
| 5 |
+
# Define environments
|
| 6 |
+
ENV MAX_JOBS=32
|
| 7 |
+
ENV VLLM_WORKER_MULTIPROC_METHOD=spawn
|
| 8 |
+
ENV DEBIAN_FRONTEND=noninteractive
|
| 9 |
+
ENV NODE_OPTIONS=""
|
| 10 |
+
ENV PIP_ROOT_USER_ACTION=ignore
|
| 11 |
+
ENV HF_HUB_ENABLE_HF_TRANSFER="1"
|
| 12 |
+
|
| 13 |
+
# Define installation arguments
|
| 14 |
+
ARG APT_SOURCE=https://mirrors.tuna.tsinghua.edu.cn/ubuntu/
|
| 15 |
+
ARG PIP_INDEX=https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
|
| 16 |
+
|
| 17 |
+
# Set apt source
|
| 18 |
+
RUN cp /etc/apt/sources.list /etc/apt/sources.list.bak && \
|
| 19 |
+
{ \
|
| 20 |
+
echo "deb ${APT_SOURCE} jammy main restricted universe multiverse"; \
|
| 21 |
+
echo "deb ${APT_SOURCE} jammy-updates main restricted universe multiverse"; \
|
| 22 |
+
echo "deb ${APT_SOURCE} jammy-backports main restricted universe multiverse"; \
|
| 23 |
+
echo "deb ${APT_SOURCE} jammy-security main restricted universe multiverse"; \
|
| 24 |
+
} > /etc/apt/sources.list
|
| 25 |
+
|
| 26 |
+
# Install systemctl
|
| 27 |
+
RUN apt-get update && \
|
| 28 |
+
apt-get install -y -o Dpkg::Options::="--force-confdef" systemd && \
|
| 29 |
+
apt-get clean
|
| 30 |
+
|
| 31 |
+
# Install tini
|
| 32 |
+
RUN apt-get update && \
|
| 33 |
+
apt-get install -y tini && \
|
| 34 |
+
apt-get clean
|
| 35 |
+
|
| 36 |
+
# Change pip source
|
| 37 |
+
RUN pip config set global.index-url "${PIP_INDEX}" && \
|
| 38 |
+
pip config set global.extra-index-url "${PIP_INDEX}" && \
|
| 39 |
+
python -m pip install --upgrade pip
|
| 40 |
+
|
| 41 |
+
# Uninstall nv-pytorch fork
|
| 42 |
+
RUN pip uninstall -y torch torchvision torchaudio \
|
| 43 |
+
pytorch-quantization pytorch-triton torch-tensorrt \
|
| 44 |
+
transformer_engine flash_attn apex megatron-core \
|
| 45 |
+
xgboost opencv grpcio
|
| 46 |
+
|
| 47 |
+
# Fix cv2
|
| 48 |
+
RUN rm -rf /usr/local/lib/python3.10/dist-packages/cv2
|
| 49 |
+
|
| 50 |
+
# Install torch-2.6.0+cu124 + vllm-0.8.4
|
| 51 |
+
# torch-2.6.0+cu124: cxx11abi=False
|
| 52 |
+
# torch-2.6.0+cu126: cxx11abi=True
|
| 53 |
+
# see https://github.com/flashinfer-ai/flashinfer/issues/911
|
| 54 |
+
RUN pip install --no-cache-dir "vllm==0.8.4" "torch==2.6.0" "torchvision==0.21.0" "torchaudio==2.6.0" tensordict torchdata \
|
| 55 |
+
"transformers[hf_xet]>=4.51.0" accelerate datasets peft hf-transfer \
|
| 56 |
+
"numpy<2.0.0" "pyarrow>=15.0.0" pandas \
|
| 57 |
+
ray[default] codetiming hydra-core pylatexenc qwen-vl-utils wandb liger-kernel mathruler \
|
| 58 |
+
pytest yapf py-spy pyext pre-commit ruff
|
| 59 |
+
|
| 60 |
+
# Install flash-attn-2.7.4.post1 (cxx11abi=False)
|
| 61 |
+
RUN wget -nv https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp310-cp310-linux_x86_64.whl && \
|
| 62 |
+
pip install --no-cache-dir flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp310-cp310-linux_x86_64.whl
|
| 63 |
+
|
| 64 |
+
# Install flashinfer-0.2.2.post1+cu124 (cxx11abi=False)
|
| 65 |
+
# vllm-0.8.3 does not support flashinfer>=0.2.3
|
| 66 |
+
# see https://github.com/vllm-project/vllm/pull/15777
|
| 67 |
+
RUN wget -nv https://github.com/flashinfer-ai/flashinfer/releases/download/v0.2.2.post1/flashinfer_python-0.2.2.post1+cu124torch2.6-cp38-abi3-linux_x86_64.whl && \
|
| 68 |
+
pip install --no-cache-dir flashinfer_python-0.2.2.post1+cu124torch2.6-cp38-abi3-linux_x86_64.whl
|
| 69 |
+
|
| 70 |
+
# Fix packages
|
| 71 |
+
RUN pip uninstall -y pynvml nvidia-ml-py && \
|
| 72 |
+
pip install --no-cache-dir --upgrade "nvidia-ml-py>=12.560.30" "fastapi[standard]>=0.115.0" "optree>=0.13.0" "pydantic>=2.9" "grpcio>=1.62.1"
|
| 73 |
+
|
| 74 |
+
# Reset pip config
|
| 75 |
+
RUN pip config unset global.index-url && \
|
| 76 |
+
pip config unset global.extra-index-url
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/Makefile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.PHONY: build commit quality style
|
| 2 |
+
|
| 3 |
+
check_dirs := examples scripts verl setup.py
|
| 4 |
+
|
| 5 |
+
build:
|
| 6 |
+
python3 setup.py sdist bdist_wheel
|
| 7 |
+
|
| 8 |
+
commit:
|
| 9 |
+
pre-commit install
|
| 10 |
+
pre-commit run --all-files
|
| 11 |
+
|
| 12 |
+
quality:
|
| 13 |
+
ruff check $(check_dirs)
|
| 14 |
+
ruff format --check $(check_dirs)
|
| 15 |
+
|
| 16 |
+
style:
|
| 17 |
+
ruff check $(check_dirs) --fix
|
| 18 |
+
ruff format $(check_dirs)
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EasyR1: An Efficient, Scalable, Multi-Modality RL Training Framework
|
| 2 |
+
|
| 3 |
+
[](https://github.com/hiyouga/EasyR1/stargazers)
|
| 4 |
+
[](https://twitter.com/llamafactory_ai)
|
| 5 |
+
|
| 6 |
+
This project is a clean fork of the original [veRL](https://github.com/volcengine/verl) project to support vision language models, we thank all the authors for providing such a high-performance RL training framework.
|
| 7 |
+
|
| 8 |
+
EasyR1 is efficient and scalable due to the design of **[HybirdEngine](https://arxiv.org/abs/2409.19256)** and the latest release of **[vLLM](https://github.com/vllm-project/vllm)**'s SPMD mode.
|
| 9 |
+
|
| 10 |
+
## Features
|
| 11 |
+
|
| 12 |
+
- Supported models
|
| 13 |
+
- Llama3/Qwen2/Qwen2.5/Qwen3 language models
|
| 14 |
+
- Qwen2/Qwen2.5-VL vision language models
|
| 15 |
+
- DeepSeek-R1 distill models
|
| 16 |
+
|
| 17 |
+
- Supported algorithms
|
| 18 |
+
- GRPO
|
| 19 |
+
- Reinforce++
|
| 20 |
+
- ReMax
|
| 21 |
+
- RLOO
|
| 22 |
+
|
| 23 |
+
- Supported datasets
|
| 24 |
+
- Any text, vision-text dataset in a [specific format](#custom-dataset)
|
| 25 |
+
|
| 26 |
+
- Supported tricks
|
| 27 |
+
- Padding-free training
|
| 28 |
+
- Resuming from checkpoint
|
| 29 |
+
- Wandb & SwanLab & Mlflow & Tensorboard tracking
|
| 30 |
+
|
| 31 |
+
## Requirements
|
| 32 |
+
|
| 33 |
+
### Software Requirements
|
| 34 |
+
|
| 35 |
+
- Python 3.9+
|
| 36 |
+
- transformers>=4.51.0
|
| 37 |
+
- flash-attn>=2.4.3
|
| 38 |
+
- vllm>=0.8.3
|
| 39 |
+
|
| 40 |
+
We provide a [Dockerfile](./Dockerfile) to easily build environments.
|
| 41 |
+
|
| 42 |
+
We recommend using the [pre-built docker image](https://hub.docker.com/r/hiyouga/verl) in EasyR1.
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
docker pull hiyouga/verl:ngc-th2.6.0-cu126-vllm0.8.4-flashinfer0.2.2-cxx11abi0
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
### Hardware Requirements
|
| 49 |
+
|
| 50 |
+
\* *estimated*
|
| 51 |
+
|
| 52 |
+
| Method | Bits | 1.5B | 3B | 7B | 32B |
|
| 53 |
+
| ------------------------ | ---- | ------ | ------ | ------ | ------- |
|
| 54 |
+
| GRPO Full Fine-Tuning | AMP | 2*24GB | 4*40GB | 8*40GB | 16*80GB |
|
| 55 |
+
| GRPO Full Fine-Tuning | BF16 | 1*24GB | 1*40GB | 4*40GB | 8*80GB |
|
| 56 |
+
|
| 57 |
+
> [!NOTE]
|
| 58 |
+
> Use `worker.actor.fsdp.torch_dtype=bf16` and `worker.actor.optim.strategy=adamw_bf16` to enable bf16 training.
|
| 59 |
+
>
|
| 60 |
+
> We are working hard to reduce the VRAM in RL training, LoRA support will be integrated in next updates.
|
| 61 |
+
|
| 62 |
+
## Tutorial: Run Qwen2.5-VL GRPO on [Geometry3K](https://huggingface.co/datasets/hiyouga/geometry3k) Dataset in Just 3 Steps
|
| 63 |
+
|
| 64 |
+

|
| 65 |
+
|
| 66 |
+
### Installation
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
git clone https://github.com/hiyouga/EasyR1.git
|
| 70 |
+
cd EasyR1
|
| 71 |
+
pip install -e .
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### GRPO Training
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
bash examples/qwen2_5_vl_7b_geo3k_grpo.sh
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### Merge Checkpoint in Hugging Face Format
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
python3 scripts/model_merger.py --local_dir checkpoints/easy_r1/exp_name/global_step_1/actor
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
> [!TIP]
|
| 87 |
+
> If you encounter issues with connecting to Hugging Face, consider using `export HF_ENDPOINT=https://hf-mirror.com`.
|
| 88 |
+
>
|
| 89 |
+
> If you want to use SwanLab logger, consider using `bash examples/qwen2_5_vl_7b_geo3k_swanlab.sh`.
|
| 90 |
+
|
| 91 |
+
## Custom Dataset
|
| 92 |
+
|
| 93 |
+
Please refer to the example datasets to prepare your own dataset.
|
| 94 |
+
|
| 95 |
+
- Text dataset: https://huggingface.co/datasets/hiyouga/math12k
|
| 96 |
+
- Image-text dataset: https://huggingface.co/datasets/hiyouga/geometry3k
|
| 97 |
+
- Multi-image-text dataset: https://huggingface.co/datasets/hiyouga/journeybench-multi-image-vqa
|
| 98 |
+
|
| 99 |
+
## How to Understand GRPO in EasyR1
|
| 100 |
+
|
| 101 |
+

|
| 102 |
+
|
| 103 |
+
- To learn about the GRPO algorithm, you can refer to [Hugging Face's blog](https://huggingface.co/docs/trl/v0.16.1/en/grpo_trainer).
|
| 104 |
+
|
| 105 |
+
## How to Run 70B+ Model in Multi-node Environment
|
| 106 |
+
|
| 107 |
+
1. Start the Ray head node.
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
ray start --head --port=6379 --dashboard-host=0.0.0.0
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
2. Start the Ray worker node and connect to the head node.
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
ray start --address=<head_node_ip>:6379
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
3. Check the Ray resource pool.
|
| 120 |
+
|
| 121 |
+
```bash
|
| 122 |
+
ray status
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
4. Run training script on the Ray head node only.
|
| 126 |
+
|
| 127 |
+
```bash
|
| 128 |
+
bash examples/qwen2_5_vl_7b_geo3k_grpo.sh
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
See the **[veRL's official doc](https://verl.readthedocs.io/en/latest/start/multinode.html)** for more details about multi-node training and Ray debugger.
|
| 132 |
+
|
| 133 |
+
## Other Baselines
|
| 134 |
+
|
| 135 |
+
We also reproduced the following two baselines of the [R1-V](https://github.com/deep-agent/R1-V) project.
|
| 136 |
+
- [CLEVR-70k-Counting](examples/baselines/qwen2_5_vl_3b_clevr.sh): Train the Qwen2.5-VL-3B-Instruct model on counting problem.
|
| 137 |
+
- [GeoQA-8k](examples/baselines/qwen2_5_vl_3b_geoqa8k.sh): Train the Qwen2.5-VL-3B-Instruct model on GeoQA problem.
|
| 138 |
+
|
| 139 |
+
## Performance Baselines
|
| 140 |
+
|
| 141 |
+
See [baselines.md](assets/baselines.md).
|
| 142 |
+
|
| 143 |
+
## Awesome Work using EasyR1
|
| 144 |
+
|
| 145 |
+
- **MMR1**: Advancing the Frontiers of Multimodal Reasoning. [![[code]](https://img.shields.io/github/stars/LengSicong/MMR1)](https://github.com/LengSicong/MMR1)
|
| 146 |
+
- **Vision-R1**: Incentivizing Reasoning Capability in Multimodal Large Language Models. [![[code]](https://img.shields.io/github/stars/Osilly/Vision-R1)](https://github.com/Osilly/Vision-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2503.06749-blue)](https://arxiv.org/abs/2503.06749)
|
| 147 |
+
- **Seg-Zero**: Reasoning-Chain Guided Segmentation via Cognitive Reinforcement. [![[code]](https://img.shields.io/github/stars/dvlab-research/Seg-Zero)](https://github.com/dvlab-research/Seg-Zero) [![[arxiv]](https://img.shields.io/badge/arxiv-2503.06520-blue)](https://arxiv.org/abs/2503.06520)
|
| 148 |
+
- **MetaSpatial**: Reinforcing 3D Spatial Reasoning in VLMs for the Metaverse. [![[code]](https://img.shields.io/github/stars/PzySeere/MetaSpatial)](https://github.com/PzySeere/MetaSpatial) [![[arxiv]](https://img.shields.io/badge/arxiv-2503.18470-blue)](https://arxiv.org/abs/2503.18470)
|
| 149 |
+
- **Temporal-R1**: Envolving Temporal Reasoning Capability into LMMs via Temporal Consistent Reward. [![[code]](https://img.shields.io/github/stars/appletea233/Temporal-R1)](https://github.com/appletea233/Temporal-R1)
|
| 150 |
+
- **NoisyRollout**: Reinforcing Visual Reasoning with Data Augmentation. [![[code]](https://img.shields.io/github/stars/John-AI-Lab/NoisyRollout)](https://github.com/John-AI-Lab/NoisyRollout) [![[arxiv]](https://img.shields.io/badge/arxiv-2504.13055-blue)](https://arxiv.org/pdf/2504.13055)
|
| 151 |
+
- **GUI-R1**: A Generalist R1-Style Vision-Language Action Model For GUI Agents. [![[code]](https://img.shields.io/github/stars/ritzz-ai/GUI-R1)](https://github.com/ritzz-ai/GUI-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2504.10458-blue)](https://arxiv.org/abs/2504.10458)
|
| 152 |
+
- **R1-Track**: Direct Application of MLLMs to Visual Object Tracking via Reinforcement Learning. [![[code]](https://img.shields.io/github/stars/Wangbiao2/R1-Track)](https://github.com/Wangbiao2/R1-Track)
|
| 153 |
+
- **VisionReasoner**: Unified Visual Perception and Reasoning via Reinforcement Learning. [![[code]](https://img.shields.io/github/stars/dvlab-research/VisionReasoner)](https://github.com/dvlab-research/VisionReasoner) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.12081-blue)](https://arxiv.org/abs/2505.12081)
|
| 154 |
+
- **MM-UPT**: Unsupervised Post-Training for Multi-Modal LLM Reasoning via GRPO. [![[code]](https://img.shields.io/github/stars/waltonfuture/MM-UPT)](https://github.com/waltonfuture/MM-UPT) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.22453-blue)](https://arxiv.org/pdf/2505.22453)
|
| 155 |
+
- **RL-with-Cold-Start**: Advancing Multimodal Reasoning via Reinforcement Learning with Cold Start. [![[code]](https://img.shields.io/github/stars/waltonfuture/RL-with-Cold-Start)](https://github.com/waltonfuture/RL-with-Cold-Start) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.22334-blue)](https://arxiv.org/pdf/2505.22334)
|
| 156 |
+
- **ViGoRL**: Grounded Reinforcement Learning for Visual Reasoning. [![[code]](https://img.shields.io/github/stars/Gabesarch/grounded-rl)](https://github.com/Gabesarch/grounded-rl) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.22334-blue)](https://arxiv.org/abs/2505.23678)
|
| 157 |
+
- **Revisual-R1**: Advancing Multimodal Reasoning: From Optimized Cold Start to Staged Reinforcement Learning. [![[code]](https://img.shields.io/github/stars/CSfufu/Revisual-R1)](https://github.com/CSfufu/Revisual-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2506.04207-blue)](https://arxiv.org/abs/2506.04207)
|
| 158 |
+
- **SophiaVL-R1**: Reinforcing MLLMs Reasoning with Thinking Reward. [![[code]](https://img.shields.io/github/stars/kxfan2002/SophiaVL-R1)](https://github.com/kxfan2002/SophiaVL-R1) [![[arxiv]](https://img.shields.io/badge/arxiv-2505.17018-blue)](https://arxiv.org/abs/2505.17018)
|
| 159 |
+
|
| 160 |
+
## TODO
|
| 161 |
+
|
| 162 |
+
- Support LoRA (high priority).
|
| 163 |
+
- Support ulysses parallelism for VLMs (middle priority).
|
| 164 |
+
- Support more VLM architectures.
|
| 165 |
+
|
| 166 |
+
> [!NOTE]
|
| 167 |
+
> We will not provide scripts for supervised fine-tuning and inference in this project. If you have such requirements, we recommend using [LLaMA-Factory](https://github.com/hiyouga/LLaMA-Factory).
|
| 168 |
+
|
| 169 |
+
### Known bugs
|
| 170 |
+
|
| 171 |
+
These features are temporarily disabled for now, we plan to fix them one-by-one in the future updates.
|
| 172 |
+
|
| 173 |
+
- Vision language models are not compatible with ulysses parallelism yet.
|
| 174 |
+
|
| 175 |
+
## Discussion Group
|
| 176 |
+
|
| 177 |
+
👋 Join our [WeChat group](assets/wechat.jpg).
|
| 178 |
+
|
| 179 |
+
## FAQs
|
| 180 |
+
|
| 181 |
+
> ValueError: Image features and image tokens do not match: tokens: 8192, features 9800
|
| 182 |
+
|
| 183 |
+
Increase the `data.max_prompt_length` or reduce the `data.max_pixels`.
|
| 184 |
+
|
| 185 |
+
> RuntimeError: CUDA Error: out of memory at /workspace/csrc/cumem_allocator.cpp:62
|
| 186 |
+
|
| 187 |
+
Reduce the `worker.rollout.gpu_memory_utilization` and enable `worker.actor.offload.offload_params`.
|
| 188 |
+
|
| 189 |
+
> RuntimeError: 0 active drivers ([]). There should only be one.
|
| 190 |
+
|
| 191 |
+
Uninstall `deepspeed` from the current python environment.
|
| 192 |
+
|
| 193 |
+
## Citation
|
| 194 |
+
|
| 195 |
+
Core contributors: [Yaowei Zheng](https://github.com/hiyouga), [Junting Lu](https://github.com/AL-377), [Shenzhi Wang](https://github.com/Shenzhi-Wang), [Zhangchi Feng](https://github.com/BUAADreamer), [Dongdong Kuang](https://github.com/Kuangdd01) and Yuwen Xiong
|
| 196 |
+
|
| 197 |
+
We also thank Guangming Sheng and Chi Zhang for helpful discussions.
|
| 198 |
+
|
| 199 |
+
```bibtex
|
| 200 |
+
@misc{zheng2025easyr1,
|
| 201 |
+
title = {EasyR1: An Efficient, Scalable, Multi-Modality RL Training Framework},
|
| 202 |
+
author = {Yaowei Zheng, Junting Lu, Shenzhi Wang, Zhangchi Feng, Dongdong Kuang, Yuwen Xiong},
|
| 203 |
+
howpublished = {\url{https://github.com/hiyouga/EasyR1}},
|
| 204 |
+
year = {2025}
|
| 205 |
+
}
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
We recommend to also cite the original work.
|
| 209 |
+
|
| 210 |
+
```bibtex
|
| 211 |
+
@article{sheng2024hybridflow,
|
| 212 |
+
title = {HybridFlow: A Flexible and Efficient RLHF Framework},
|
| 213 |
+
author = {Guangming Sheng and Chi Zhang and Zilingfeng Ye and Xibin Wu and Wang Zhang and Ru Zhang and Yanghua Peng and Haibin Lin and Chuan Wu},
|
| 214 |
+
year = {2024},
|
| 215 |
+
journal = {arXiv preprint arXiv: 2409.19256}
|
| 216 |
+
}
|
| 217 |
+
```
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
|
| 17 |
+
from .utils.py_functional import is_package_available
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
if is_package_available("modelscope"):
|
| 21 |
+
from modelscope.utils.hf_util import patch_hub # type: ignore
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
__version__ = "0.3.1.dev0"
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
if os.getenv("USE_MODELSCOPE_HUB", "0").lower() in ["true", "y", "1"]:
|
| 28 |
+
# Patch hub to download models from modelscope to speed up.
|
| 29 |
+
if not is_package_available("modelscope"):
|
| 30 |
+
raise ImportError("You are using the modelscope hub, please install modelscope by `pip install modelscope`.")
|
| 31 |
+
|
| 32 |
+
patch_hub()
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/init
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/monkey_patch.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
|
| 17 |
+
|
| 18 |
+
from .transformers.flash_attention_utils import flash_attention_forward
|
| 19 |
+
from .transformers.qwen2_vl import qwen2_vl_attn_forward
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def apply_ulysses_patch(model_type: str) -> None:
|
| 23 |
+
if model_type in ("llama", "mllama", "gemma", "gemma2", "mistral", "qwen2", "qwen3", "qwen3_moe"):
|
| 24 |
+
ALL_ATTENTION_FUNCTIONS["flash_attention_2"] = flash_attention_forward
|
| 25 |
+
elif model_type in ("qwen2_vl", "qwen2_5_vl"):
|
| 26 |
+
from transformers.models.qwen2_5_vl.modeling_qwen2_5_vl import Qwen2_5_VLFlashAttention2
|
| 27 |
+
from transformers.models.qwen2_vl.modeling_qwen2_vl import Qwen2VLFlashAttention2
|
| 28 |
+
|
| 29 |
+
Qwen2VLFlashAttention2.forward = qwen2_vl_attn_forward
|
| 30 |
+
Qwen2_5_VLFlashAttention2.forward = qwen2_vl_attn_forward
|
| 31 |
+
else:
|
| 32 |
+
raise NotImplementedError(f"Model architecture {model_type} is not supported yet.")
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/transformers/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/transformers/flash_attention_utils.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 The Fairseq Authors and the HuggingFace Inc. team
|
| 2 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 3 |
+
# Based on https://github.com/huggingface/transformers/blob/v4.49.0/src/transformers/modeling_flash_attention_utils.py
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
|
| 17 |
+
import inspect
|
| 18 |
+
import os
|
| 19 |
+
from typing import Optional, Tuple
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
import torch.distributed as dist
|
| 23 |
+
from transformers.modeling_flash_attention_utils import _flash_attention_forward, fa_peft_integration_check
|
| 24 |
+
from transformers.utils import is_flash_attn_2_available, is_flash_attn_greater_or_equal_2_10
|
| 25 |
+
|
| 26 |
+
from ...utils.ulysses import (
|
| 27 |
+
gather_heads_scatter_seq,
|
| 28 |
+
gather_seq_scatter_heads,
|
| 29 |
+
get_ulysses_sequence_parallel_group,
|
| 30 |
+
get_ulysses_sequence_parallel_world_size,
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
if is_flash_attn_2_available():
|
| 35 |
+
from flash_attn import flash_attn_func, flash_attn_varlen_func
|
| 36 |
+
|
| 37 |
+
_flash_supports_window_size = "window_size" in inspect.signature(flash_attn_func).parameters
|
| 38 |
+
_flash_supports_deterministic = "deterministic" in inspect.signature(flash_attn_func).parameters
|
| 39 |
+
_flash_deterministic_enabled = os.environ.get("FLASH_ATTENTION_DETERMINISTIC", "0") == "1"
|
| 40 |
+
_flash_use_top_left_mask = not is_flash_attn_greater_or_equal_2_10()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def prepare_fa2_from_position_ids(
|
| 44 |
+
query: torch.Tensor, key: torch.Tensor, value: torch.Tensor, position_ids: torch.Tensor
|
| 45 |
+
):
|
| 46 |
+
query = query.view(-1, query.size(-2), query.size(-1))
|
| 47 |
+
key = key.contiguous().view(-1, key.size(-2), key.size(-1))
|
| 48 |
+
value = value.contiguous().view(-1, value.size(-2), value.size(-1))
|
| 49 |
+
position_ids = position_ids.flatten()
|
| 50 |
+
indices_q = torch.arange(position_ids.size(0), device=position_ids.device, dtype=torch.int32)
|
| 51 |
+
cu_seqlens = torch.cat(
|
| 52 |
+
(
|
| 53 |
+
indices_q[position_ids == 0],
|
| 54 |
+
torch.tensor(position_ids.size(), device=position_ids.device, dtype=torch.int32),
|
| 55 |
+
)
|
| 56 |
+
)
|
| 57 |
+
max_length = cu_seqlens.diff().max() # use cu_seqlens to infer max_length for qwen2vl mrope
|
| 58 |
+
return (query, key, value, indices_q, (cu_seqlens, cu_seqlens), (max_length, max_length))
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _custom_flash_attention_forward(
|
| 62 |
+
query_states: torch.Tensor,
|
| 63 |
+
key_states: torch.Tensor,
|
| 64 |
+
value_states: torch.Tensor,
|
| 65 |
+
attention_mask: Optional[torch.Tensor],
|
| 66 |
+
query_length: int,
|
| 67 |
+
is_causal: bool = True,
|
| 68 |
+
position_ids: Optional[torch.Tensor] = None,
|
| 69 |
+
sliding_window: Optional[int] = None,
|
| 70 |
+
use_top_left_mask: bool = False,
|
| 71 |
+
deterministic: Optional[bool] = None,
|
| 72 |
+
**kwargs,
|
| 73 |
+
):
|
| 74 |
+
"""
|
| 75 |
+
Patches flash attention forward to handle 3D position ids in mrope. (3, batch_size, seq_length)
|
| 76 |
+
"""
|
| 77 |
+
if not use_top_left_mask:
|
| 78 |
+
causal = is_causal
|
| 79 |
+
else:
|
| 80 |
+
causal = is_causal and query_length != 1
|
| 81 |
+
|
| 82 |
+
# Assuming 4D tensors, key_states.shape[1] is the key/value sequence length (source length).
|
| 83 |
+
use_sliding_windows = (
|
| 84 |
+
_flash_supports_window_size and sliding_window is not None and key_states.shape[1] > sliding_window
|
| 85 |
+
)
|
| 86 |
+
flash_kwargs = {"window_size": (sliding_window, sliding_window)} if use_sliding_windows else {}
|
| 87 |
+
|
| 88 |
+
if _flash_supports_deterministic:
|
| 89 |
+
flash_kwargs["deterministic"] = deterministic if deterministic is not None else _flash_deterministic_enabled
|
| 90 |
+
|
| 91 |
+
if kwargs.get("softcap") is not None:
|
| 92 |
+
flash_kwargs["softcap"] = kwargs.pop("softcap")
|
| 93 |
+
|
| 94 |
+
query_states, key_states, value_states = fa_peft_integration_check(
|
| 95 |
+
query_states, key_states, value_states, target_dtype=torch.bfloat16
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
sp_size = get_ulysses_sequence_parallel_world_size()
|
| 99 |
+
if sp_size > 1:
|
| 100 |
+
# (batch_size, seq_length, num_head, head_size)
|
| 101 |
+
query_states = gather_seq_scatter_heads(query_states, seq_dim=1, head_dim=2)
|
| 102 |
+
key_states = gather_seq_scatter_heads(key_states, seq_dim=1, head_dim=2)
|
| 103 |
+
value_states = gather_seq_scatter_heads(value_states, seq_dim=1, head_dim=2)
|
| 104 |
+
position_ids_lst = [torch.empty_like(position_ids) for _ in range(sp_size)]
|
| 105 |
+
position_ids = dist.all_gather(position_ids_lst, position_ids, group=get_ulysses_sequence_parallel_group())
|
| 106 |
+
position_ids = torch.cat(position_ids_lst, dim=-1) # (..., batch_size, seq_length)
|
| 107 |
+
|
| 108 |
+
if position_ids is not None and position_ids.dim() == 3: # qwen2vl mrope
|
| 109 |
+
position_ids = position_ids[0]
|
| 110 |
+
|
| 111 |
+
if position_ids is not None and query_length != 1 and not (torch.diff(position_ids, dim=-1) >= 0).all():
|
| 112 |
+
batch_size = query_states.size(0)
|
| 113 |
+
query_states, key_states, value_states, _, cu_seq_lens, max_seq_lens = prepare_fa2_from_position_ids(
|
| 114 |
+
query_states, key_states, value_states, position_ids
|
| 115 |
+
)
|
| 116 |
+
cu_seqlens_q, cu_seqlens_k = cu_seq_lens
|
| 117 |
+
max_seqlen_in_batch_q, max_seqlen_in_batch_k = max_seq_lens
|
| 118 |
+
attn_output = flash_attn_varlen_func(
|
| 119 |
+
query_states,
|
| 120 |
+
key_states,
|
| 121 |
+
value_states,
|
| 122 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 123 |
+
cu_seqlens_k=cu_seqlens_k,
|
| 124 |
+
max_seqlen_q=max_seqlen_in_batch_q,
|
| 125 |
+
max_seqlen_k=max_seqlen_in_batch_k,
|
| 126 |
+
dropout_p=kwargs.pop("dropout", 0.0),
|
| 127 |
+
softmax_scale=kwargs.pop("softmax_scale", None),
|
| 128 |
+
causal=causal,
|
| 129 |
+
**flash_kwargs,
|
| 130 |
+
)
|
| 131 |
+
attn_output = attn_output.view(batch_size, -1, attn_output.size(-2), attn_output.size(-1))
|
| 132 |
+
else:
|
| 133 |
+
attn_output = _flash_attention_forward(
|
| 134 |
+
query_states,
|
| 135 |
+
key_states,
|
| 136 |
+
value_states,
|
| 137 |
+
attention_mask,
|
| 138 |
+
query_length,
|
| 139 |
+
is_causal=is_causal,
|
| 140 |
+
sliding_window=sliding_window,
|
| 141 |
+
use_top_left_mask=use_top_left_mask,
|
| 142 |
+
deterministic=deterministic,
|
| 143 |
+
**kwargs,
|
| 144 |
+
) # do not pass position_ids to old flash_attention_forward
|
| 145 |
+
|
| 146 |
+
if sp_size > 1:
|
| 147 |
+
# (batch_size, seq_length, num_head, head_size)
|
| 148 |
+
attn_output = gather_heads_scatter_seq(attn_output, head_dim=2, seq_dim=1)
|
| 149 |
+
|
| 150 |
+
return attn_output
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def flash_attention_forward(
|
| 154 |
+
module: torch.nn.Module,
|
| 155 |
+
query: torch.Tensor,
|
| 156 |
+
key: torch.Tensor,
|
| 157 |
+
value: torch.Tensor,
|
| 158 |
+
attention_mask: Optional[torch.Tensor],
|
| 159 |
+
dropout: float = 0.0,
|
| 160 |
+
scaling: Optional[float] = None,
|
| 161 |
+
sliding_window: Optional[int] = None,
|
| 162 |
+
softcap: Optional[float] = None,
|
| 163 |
+
**kwargs,
|
| 164 |
+
) -> Tuple[torch.Tensor, None]:
|
| 165 |
+
# This is before the transpose
|
| 166 |
+
q_len = query.shape[2]
|
| 167 |
+
|
| 168 |
+
# FA2 uses non-transposed inputs
|
| 169 |
+
query = query.transpose(1, 2)
|
| 170 |
+
key = key.transpose(1, 2)
|
| 171 |
+
value = value.transpose(1, 2)
|
| 172 |
+
|
| 173 |
+
# FA2 always relies on the value set in the module, so remove it if present in kwargs to avoid passing it twice
|
| 174 |
+
kwargs.pop("is_causal", None)
|
| 175 |
+
|
| 176 |
+
attn_output = _custom_flash_attention_forward(
|
| 177 |
+
query,
|
| 178 |
+
key,
|
| 179 |
+
value,
|
| 180 |
+
attention_mask,
|
| 181 |
+
query_length=q_len,
|
| 182 |
+
is_causal=True,
|
| 183 |
+
dropout=dropout,
|
| 184 |
+
softmax_scale=scaling,
|
| 185 |
+
sliding_window=sliding_window,
|
| 186 |
+
softcap=softcap,
|
| 187 |
+
use_top_left_mask=_flash_use_top_left_mask,
|
| 188 |
+
**kwargs,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
return attn_output, None
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/models/transformers/qwen2_vl.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 The Qwen team, Alibaba Group and the HuggingFace Inc. team
|
| 2 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 3 |
+
# Based on:
|
| 4 |
+
# https://github.com/huggingface/transformers/blob/v4.49.0/src/transformers/models/qwen2_vl/modeling_qwen2_vl.py
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
|
| 18 |
+
from typing import Optional, Tuple
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
from .flash_attention_utils import flash_attention_forward
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
from transformers.models.qwen2_vl.modeling_qwen2_vl import (
|
| 27 |
+
Qwen2VLAttention,
|
| 28 |
+
apply_multimodal_rotary_pos_emb,
|
| 29 |
+
repeat_kv,
|
| 30 |
+
)
|
| 31 |
+
from transformers.models.qwen2_vl.processing_qwen2_vl import Qwen2VLProcessor
|
| 32 |
+
except ImportError:
|
| 33 |
+
pass
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def get_rope_index(
|
| 37 |
+
processor: "Qwen2VLProcessor",
|
| 38 |
+
input_ids: torch.Tensor,
|
| 39 |
+
image_grid_thw: Optional[torch.Tensor] = None,
|
| 40 |
+
video_grid_thw: Optional[torch.Tensor] = None,
|
| 41 |
+
second_per_grid_ts: Optional[torch.Tensor] = None,
|
| 42 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 43 |
+
) -> torch.Tensor:
|
| 44 |
+
"""
|
| 45 |
+
Gets the position ids for Qwen2-VL, it should be generated before sharding the sequence.
|
| 46 |
+
The batch dim has been removed and the input_ids should be a 1D tensor representing a single example.
|
| 47 |
+
https://github.com/huggingface/transformers/blob/v4.49.0/src/transformers/models/qwen2_5_vl/modeling_qwen2_5_vl.py#L1546
|
| 48 |
+
"""
|
| 49 |
+
spatial_merge_size = processor.image_processor.merge_size
|
| 50 |
+
tokens_per_second = 2
|
| 51 |
+
image_token_id = processor.tokenizer.convert_tokens_to_ids("<|image_pad|>")
|
| 52 |
+
video_token_id = processor.tokenizer.convert_tokens_to_ids("<|video_pad|>")
|
| 53 |
+
vision_start_token_id = processor.tokenizer.convert_tokens_to_ids("<|vision_start|>")
|
| 54 |
+
if input_ids is not None and (image_grid_thw is not None or video_grid_thw is not None):
|
| 55 |
+
if attention_mask is None:
|
| 56 |
+
attention_mask = torch.ones_like(input_ids)
|
| 57 |
+
|
| 58 |
+
position_ids = torch.ones(3, input_ids.size(0), dtype=input_ids.dtype, device=input_ids.device) # (3, seqlen)
|
| 59 |
+
image_index, video_index = 0, 0
|
| 60 |
+
input_ids = input_ids[attention_mask == 1]
|
| 61 |
+
image_nums, video_nums = 0, 0
|
| 62 |
+
vision_start_indices = torch.argwhere(input_ids == vision_start_token_id)
|
| 63 |
+
vision_tokens = input_ids[vision_start_indices + 1]
|
| 64 |
+
image_nums = (vision_tokens == image_token_id).sum()
|
| 65 |
+
video_nums = (vision_tokens == video_token_id).sum()
|
| 66 |
+
input_tokens = input_ids.tolist()
|
| 67 |
+
llm_pos_ids_list: list = []
|
| 68 |
+
st = 0
|
| 69 |
+
remain_images, remain_videos = image_nums, video_nums
|
| 70 |
+
for _ in range(image_nums + video_nums):
|
| 71 |
+
if image_token_id in input_tokens and remain_images > 0:
|
| 72 |
+
ed_image = input_tokens.index(image_token_id, st)
|
| 73 |
+
else:
|
| 74 |
+
ed_image = len(input_tokens) + 1
|
| 75 |
+
if video_token_id in input_tokens and remain_videos > 0:
|
| 76 |
+
ed_video = input_tokens.index(video_token_id, st)
|
| 77 |
+
else:
|
| 78 |
+
ed_video = len(input_tokens) + 1
|
| 79 |
+
if ed_image < ed_video:
|
| 80 |
+
t, h, w = (
|
| 81 |
+
image_grid_thw[image_index][0],
|
| 82 |
+
image_grid_thw[image_index][1],
|
| 83 |
+
image_grid_thw[image_index][2],
|
| 84 |
+
)
|
| 85 |
+
second_per_grid_t = 0
|
| 86 |
+
image_index += 1
|
| 87 |
+
remain_images -= 1
|
| 88 |
+
ed = ed_image
|
| 89 |
+
else:
|
| 90 |
+
t, h, w = (
|
| 91 |
+
video_grid_thw[video_index][0],
|
| 92 |
+
video_grid_thw[video_index][1],
|
| 93 |
+
video_grid_thw[video_index][2],
|
| 94 |
+
)
|
| 95 |
+
if second_per_grid_ts is not None:
|
| 96 |
+
second_per_grid_t = second_per_grid_ts[video_index]
|
| 97 |
+
else:
|
| 98 |
+
second_per_grid_t = 1.0
|
| 99 |
+
|
| 100 |
+
video_index += 1
|
| 101 |
+
remain_videos -= 1
|
| 102 |
+
ed = ed_video
|
| 103 |
+
|
| 104 |
+
llm_grid_t, llm_grid_h, llm_grid_w = (
|
| 105 |
+
t.item(),
|
| 106 |
+
h.item() // spatial_merge_size,
|
| 107 |
+
w.item() // spatial_merge_size,
|
| 108 |
+
)
|
| 109 |
+
text_len = ed - st
|
| 110 |
+
|
| 111 |
+
st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
|
| 112 |
+
llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx)
|
| 113 |
+
|
| 114 |
+
t_index = torch.arange(llm_grid_t).view(-1, 1).expand(-1, llm_grid_h * llm_grid_w)
|
| 115 |
+
t_index = (t_index * second_per_grid_t * tokens_per_second).long().flatten()
|
| 116 |
+
h_index = torch.arange(llm_grid_h).view(1, -1, 1).expand(llm_grid_t, -1, llm_grid_w).flatten()
|
| 117 |
+
w_index = torch.arange(llm_grid_w).view(1, 1, -1).expand(llm_grid_t, llm_grid_h, -1).flatten()
|
| 118 |
+
llm_pos_ids_list.append(torch.stack([t_index, h_index, w_index]) + text_len + st_idx)
|
| 119 |
+
st = ed + llm_grid_t * llm_grid_h * llm_grid_w
|
| 120 |
+
|
| 121 |
+
if st < len(input_tokens):
|
| 122 |
+
st_idx = llm_pos_ids_list[-1].max() + 1 if len(llm_pos_ids_list) > 0 else 0
|
| 123 |
+
text_len = len(input_tokens) - st
|
| 124 |
+
llm_pos_ids_list.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx)
|
| 125 |
+
|
| 126 |
+
llm_positions = torch.cat(llm_pos_ids_list, dim=1).reshape(3, -1)
|
| 127 |
+
position_ids[..., attention_mask == 1] = llm_positions.to(position_ids.device)
|
| 128 |
+
else:
|
| 129 |
+
if attention_mask is not None:
|
| 130 |
+
position_ids = attention_mask.long().cumsum(-1) - 1
|
| 131 |
+
position_ids.masked_fill_(attention_mask == 0, 1)
|
| 132 |
+
position_ids = position_ids.unsqueeze(0).expand(3, -1).to(input_ids.device)
|
| 133 |
+
else:
|
| 134 |
+
position_ids = torch.arange(input_ids.shape[1], device=input_ids.device).view(1, -1).expand(3, -1)
|
| 135 |
+
|
| 136 |
+
return position_ids
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def qwen2_vl_attn_forward(
|
| 140 |
+
self: "Qwen2VLAttention",
|
| 141 |
+
hidden_states: torch.Tensor,
|
| 142 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 143 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 144 |
+
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # will become mandatory in v4.46
|
| 145 |
+
**kwargs,
|
| 146 |
+
) -> Tuple[torch.Tensor, None, None]:
|
| 147 |
+
bsz, q_len, _ = hidden_states.size() # q_len = seq_length / sp_size
|
| 148 |
+
query_states = self.q_proj(hidden_states) # (batch_size, seq_length / sp_size, num_heads * head_size)
|
| 149 |
+
key_states = self.k_proj(hidden_states)
|
| 150 |
+
value_states = self.v_proj(hidden_states)
|
| 151 |
+
|
| 152 |
+
query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 153 |
+
key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
|
| 154 |
+
value_states = value_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
|
| 155 |
+
|
| 156 |
+
# Because the input can be padded, the absolute sequence length depends on the max position id.
|
| 157 |
+
if position_embeddings is None:
|
| 158 |
+
cos, sin = self.rotary_emb(value_states, position_ids)
|
| 159 |
+
else:
|
| 160 |
+
cos, sin = position_embeddings
|
| 161 |
+
|
| 162 |
+
query_states, key_states = apply_multimodal_rotary_pos_emb(
|
| 163 |
+
query_states, key_states, cos, sin, self.rope_scaling["mrope_section"]
|
| 164 |
+
)
|
| 165 |
+
key_states = repeat_kv(key_states, self.num_key_value_groups)
|
| 166 |
+
value_states = repeat_kv(value_states, self.num_key_value_groups)
|
| 167 |
+
dropout_rate = 0.0 if not self.training else self.attention_dropout
|
| 168 |
+
|
| 169 |
+
sliding_window = None
|
| 170 |
+
if (
|
| 171 |
+
self.config.use_sliding_window
|
| 172 |
+
and getattr(self.config, "sliding_window", None) is not None
|
| 173 |
+
and self.layer_idx >= self.config.max_window_layers
|
| 174 |
+
):
|
| 175 |
+
sliding_window = self.config.sliding_window
|
| 176 |
+
|
| 177 |
+
attn_output, _ = flash_attention_forward(
|
| 178 |
+
self,
|
| 179 |
+
query_states,
|
| 180 |
+
key_states,
|
| 181 |
+
value_states,
|
| 182 |
+
attention_mask,
|
| 183 |
+
dropout=dropout_rate,
|
| 184 |
+
sliding_window=sliding_window,
|
| 185 |
+
position_ids=position_ids, # important: pass position ids
|
| 186 |
+
) # (batch_size, seq_length, num_head / sp_size, head_size)
|
| 187 |
+
attn_output = attn_output.reshape(bsz, q_len, self.hidden_size).contiguous()
|
| 188 |
+
attn_output = self.o_proj(attn_output)
|
| 189 |
+
return attn_output, None, None
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/protocol.py
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""
|
| 15 |
+
Implement base data transfer protocol between any two functions, modules.
|
| 16 |
+
We can subclass Protocol to define more detailed batch info with specific keys
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import copy
|
| 20 |
+
import io
|
| 21 |
+
import pickle
|
| 22 |
+
from collections import defaultdict
|
| 23 |
+
from dataclasses import dataclass, field
|
| 24 |
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
| 25 |
+
|
| 26 |
+
import numpy as np
|
| 27 |
+
import ray
|
| 28 |
+
import torch
|
| 29 |
+
from numpy.typing import NDArray
|
| 30 |
+
from tensordict import TensorDict
|
| 31 |
+
from torch.distributed import ProcessGroup
|
| 32 |
+
from torch.utils.data import DataLoader
|
| 33 |
+
|
| 34 |
+
from .utils.py_functional import union_two_dict
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
import tensordict
|
| 39 |
+
|
| 40 |
+
tensordict.set_lazy_legacy(False).set()
|
| 41 |
+
except Exception:
|
| 42 |
+
pass
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
__all__ = ["DataProto", "union_tensor_dict"]
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def pad_dataproto_to_divisor(data: "DataProto", size_divisor: int) -> Tuple["DataProto", int]:
|
| 49 |
+
"""Pad a DataProto to size divisible by size_divisor
|
| 50 |
+
|
| 51 |
+
Args:
|
| 52 |
+
data (DataProto): the unpadded DataProto
|
| 53 |
+
size_divisor (int): size divisor
|
| 54 |
+
|
| 55 |
+
Returns:
|
| 56 |
+
data (DataProto): the padded DataProto
|
| 57 |
+
pad_size (int)
|
| 58 |
+
"""
|
| 59 |
+
assert isinstance(data, DataProto), "data must be a DataProto"
|
| 60 |
+
if len(data) % size_divisor != 0:
|
| 61 |
+
pad_size = size_divisor - len(data) % size_divisor
|
| 62 |
+
padding_protos = []
|
| 63 |
+
remaining_pad = pad_size
|
| 64 |
+
while remaining_pad > 0:
|
| 65 |
+
take_size = min(remaining_pad, len(data))
|
| 66 |
+
padding_protos.append(data[:take_size])
|
| 67 |
+
remaining_pad -= take_size
|
| 68 |
+
|
| 69 |
+
data_padded = DataProto.concat([data] + padding_protos)
|
| 70 |
+
else:
|
| 71 |
+
pad_size = 0
|
| 72 |
+
data_padded = data
|
| 73 |
+
|
| 74 |
+
return data_padded, pad_size
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def unpad_dataproto(data: "DataProto", pad_size: int) -> "DataProto":
|
| 78 |
+
if pad_size != 0:
|
| 79 |
+
data = data[:-pad_size]
|
| 80 |
+
|
| 81 |
+
return data
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def union_tensor_dict(tensor_dict1: TensorDict, tensor_dict2: TensorDict) -> TensorDict:
|
| 85 |
+
"""Union two tensordicts."""
|
| 86 |
+
if tensor_dict1.batch_size != tensor_dict2.batch_size:
|
| 87 |
+
raise ValueError(
|
| 88 |
+
f"Two tensor dict must have identical batch size. Got {tensor_dict1.batch_size} and {tensor_dict2.batch_size}"
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
for key in tensor_dict2.keys():
|
| 92 |
+
if key in tensor_dict1 and not torch.equal(tensor_dict1[key], tensor_dict2[key]):
|
| 93 |
+
raise ValueError(f"Key already exists: {key}.")
|
| 94 |
+
|
| 95 |
+
tensor_dict1[key] = tensor_dict2[key]
|
| 96 |
+
|
| 97 |
+
return tensor_dict1
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def union_numpy_dict(tensor_dict1: Dict[str, NDArray], tensor_dict2: Dict[str, NDArray]) -> Dict[str, NDArray]:
|
| 101 |
+
for key in tensor_dict2.keys():
|
| 102 |
+
if key in tensor_dict1:
|
| 103 |
+
assert isinstance(tensor_dict2[key], np.ndarray)
|
| 104 |
+
assert isinstance(tensor_dict1[key], np.ndarray)
|
| 105 |
+
if not np.all(tensor_dict1[key] == tensor_dict2[key]):
|
| 106 |
+
raise ValueError(f"Key already exists: {key}.")
|
| 107 |
+
|
| 108 |
+
tensor_dict1[key] = tensor_dict2[key]
|
| 109 |
+
|
| 110 |
+
return tensor_dict1
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def batch_collate(features: List[Dict[str, Any]]) -> Dict[str, List[Any]]:
|
| 114 |
+
if len(features) == 0:
|
| 115 |
+
return {}
|
| 116 |
+
|
| 117 |
+
batch_features = defaultdict(list)
|
| 118 |
+
for feature in features:
|
| 119 |
+
for key, value in feature.items():
|
| 120 |
+
batch_features[key].append(value)
|
| 121 |
+
|
| 122 |
+
return batch_features
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def fold_batch_dim(data: "DataProto", new_batch_size: int):
|
| 126 |
+
"""
|
| 127 |
+
Fold a batch dim from [bsz, xxx] into [new_bsz, bsz // new_bsz, xxx]
|
| 128 |
+
"""
|
| 129 |
+
batch_size = data.batch.batch_size[0]
|
| 130 |
+
|
| 131 |
+
assert batch_size % new_batch_size == 0
|
| 132 |
+
|
| 133 |
+
tensor: TensorDict = data.batch
|
| 134 |
+
non_tensor = data.non_tensor_batch
|
| 135 |
+
|
| 136 |
+
tensor = tensor.view(new_batch_size, -1)
|
| 137 |
+
tensor.auto_batch_size_(batch_dims=1)
|
| 138 |
+
|
| 139 |
+
for key, value in non_tensor.items():
|
| 140 |
+
non_tensor[key] = np.reshape(value, newshape=(new_batch_size, -1, *value.shape[1:]))
|
| 141 |
+
|
| 142 |
+
return DataProto(batch=tensor, non_tensor_batch=non_tensor, meta_info=data.meta_info)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def collate_fn(data_items: list["DataProtoItem"]):
|
| 146 |
+
batch = []
|
| 147 |
+
non_tensor_batch = []
|
| 148 |
+
for data in data_items:
|
| 149 |
+
batch.append(data.batch)
|
| 150 |
+
non_tensor_batch.append(data.non_tensor_batch)
|
| 151 |
+
|
| 152 |
+
batch = torch.stack(batch).contiguous()
|
| 153 |
+
non_tensor_batch = batch_collate(non_tensor_batch)
|
| 154 |
+
non_tensor_batch = {key: np.array(value, dtype=object) for key, value in non_tensor_batch.items()}
|
| 155 |
+
return DataProto(batch=batch, non_tensor_batch=non_tensor_batch)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
@dataclass
|
| 159 |
+
class DataProtoItem:
|
| 160 |
+
batch: Optional[TensorDict] = None
|
| 161 |
+
non_tensor_batch: Dict[str, NDArray] = field(default_factory=dict)
|
| 162 |
+
meta_info: Dict[str, Any] = field(default_factory=dict)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
@dataclass
|
| 166 |
+
class DataProto:
|
| 167 |
+
"""
|
| 168 |
+
A DataProto is a data structure that aims to provide a standard protocol for data exchange between functions.
|
| 169 |
+
It contains a batch (TensorDict) and a meta_info (Dict). The batch is a TensorDict https://pytorch.org/tensordict/.
|
| 170 |
+
TensorDict allows you to manipulate a dictionary of Tensors like a single Tensor. Ideally, the tensors with the
|
| 171 |
+
same batch size should be put inside batch.
|
| 172 |
+
"""
|
| 173 |
+
|
| 174 |
+
batch: Optional[TensorDict] = None
|
| 175 |
+
non_tensor_batch: Dict[str, NDArray] = field(default_factory=dict)
|
| 176 |
+
meta_info: Dict[str, Any] = field(default_factory=dict)
|
| 177 |
+
|
| 178 |
+
def __post_init__(self):
|
| 179 |
+
self.check_consistency() # perform necessary checking
|
| 180 |
+
|
| 181 |
+
def __len__(self) -> int:
|
| 182 |
+
if self.batch is not None:
|
| 183 |
+
return self.batch.batch_size[0]
|
| 184 |
+
elif self.non_tensor_batch is not None and len(self.non_tensor_batch) > 0:
|
| 185 |
+
pivot_key = list(self.non_tensor_batch.keys())[0]
|
| 186 |
+
return self.non_tensor_batch[pivot_key].shape[0]
|
| 187 |
+
else:
|
| 188 |
+
return 0
|
| 189 |
+
|
| 190 |
+
def __getitem__(self, item: Union[int, slice]) -> Union["DataProto", "DataProtoItem"]:
|
| 191 |
+
tensor_data = self.batch[item]
|
| 192 |
+
non_tensor_data = {key: value[item] for key, value in self.non_tensor_batch.items()}
|
| 193 |
+
return_type = DataProto if isinstance(item, slice) else DataProtoItem
|
| 194 |
+
return return_type(batch=tensor_data, non_tensor_batch=non_tensor_data, meta_info=self.meta_info)
|
| 195 |
+
|
| 196 |
+
def __getstate__(self) -> Tuple[bytes, Dict[str, NDArray], Dict[str, Any]]:
|
| 197 |
+
buffer = io.BytesIO()
|
| 198 |
+
if self.batch is not None:
|
| 199 |
+
self.batch: TensorDict = self.batch.contiguous()
|
| 200 |
+
self.batch: TensorDict = self.batch.consolidate()
|
| 201 |
+
|
| 202 |
+
torch.save(self.batch, buffer)
|
| 203 |
+
buffer_bytes = buffer.getvalue()
|
| 204 |
+
return buffer_bytes, self.non_tensor_batch, self.meta_info
|
| 205 |
+
|
| 206 |
+
def __setstate__(self, data: Tuple[bytes, Dict[str, NDArray], Dict[str, Any]]) -> None:
|
| 207 |
+
batch_deserialized_bytes, non_tensor_batch, meta_info = data
|
| 208 |
+
batch_deserialized = io.BytesIO(batch_deserialized_bytes)
|
| 209 |
+
batch = torch.load(batch_deserialized, weights_only=False, map_location="cpu")
|
| 210 |
+
self.batch = batch
|
| 211 |
+
self.non_tensor_batch = non_tensor_batch
|
| 212 |
+
self.meta_info = meta_info
|
| 213 |
+
|
| 214 |
+
def save_to_disk(self, filepath: str) -> None:
|
| 215 |
+
with open(filepath, "wb") as f:
|
| 216 |
+
pickle.dump(self, f)
|
| 217 |
+
|
| 218 |
+
@staticmethod
|
| 219 |
+
def load_from_disk(filepath: str) -> "DataProto":
|
| 220 |
+
with open(filepath, "rb") as f:
|
| 221 |
+
data = pickle.load(f)
|
| 222 |
+
return data
|
| 223 |
+
|
| 224 |
+
def print_size(self, prefix: str = "") -> None:
|
| 225 |
+
size_of_tensordict = 0
|
| 226 |
+
if self.batch is not None:
|
| 227 |
+
for tensor in self.batch.values():
|
| 228 |
+
if isinstance(tensor, torch.Tensor):
|
| 229 |
+
size_of_tensordict += tensor.element_size() * tensor.numel()
|
| 230 |
+
|
| 231 |
+
size_of_numpy_array = 0
|
| 232 |
+
for value in self.non_tensor_batch.values():
|
| 233 |
+
size_of_numpy_array += value.nbytes
|
| 234 |
+
|
| 235 |
+
size_of_numpy_array /= 1024**3
|
| 236 |
+
size_of_tensordict /= 1024**3
|
| 237 |
+
|
| 238 |
+
message = f"Size of tensordict: {size_of_tensordict} GB, size of non_tensor_batch: {size_of_numpy_array} GB."
|
| 239 |
+
print({prefix}, {message})
|
| 240 |
+
|
| 241 |
+
def check_consistency(self):
|
| 242 |
+
"""Check the consistency of the DataProto. Mainly for batch and non_tensor_batch
|
| 243 |
+
We expose this function as a public one so that user can call themselves directly
|
| 244 |
+
"""
|
| 245 |
+
if self.batch is not None:
|
| 246 |
+
assert len(self.batch.batch_size) == 1, "only support num_batch_dims=1"
|
| 247 |
+
|
| 248 |
+
if self.batch is not None and len(self.non_tensor_batch) != 0:
|
| 249 |
+
# TODO: we can actually lift this restriction if needed
|
| 250 |
+
assert len(self.batch.batch_size) == 1, "only support num_batch_dims=1 when non_tensor_batch is not empty."
|
| 251 |
+
|
| 252 |
+
batch_size = self.batch.batch_size[0]
|
| 253 |
+
for key, value in self.non_tensor_batch.items():
|
| 254 |
+
assert len(value) == batch_size, f"key {key} length {len(value)} is not equal to bsz {batch_size}."
|
| 255 |
+
|
| 256 |
+
@classmethod
|
| 257 |
+
def from_single_dict(
|
| 258 |
+
cls,
|
| 259 |
+
data: Dict[str, Union[torch.Tensor, NDArray]],
|
| 260 |
+
meta_info: Optional[Dict[str, Any]] = None,
|
| 261 |
+
) -> "DataProto":
|
| 262 |
+
tensors, non_tensors = {}, {}
|
| 263 |
+
for key, value in data.items():
|
| 264 |
+
if isinstance(value, torch.Tensor):
|
| 265 |
+
tensors[key] = value
|
| 266 |
+
elif isinstance(value, np.ndarray):
|
| 267 |
+
non_tensors[key] = value
|
| 268 |
+
else:
|
| 269 |
+
raise ValueError(f"Unsupported type in data {type(value)}")
|
| 270 |
+
|
| 271 |
+
return DataProto.from_dict(tensors=tensors, non_tensors=non_tensors, meta_info=meta_info)
|
| 272 |
+
|
| 273 |
+
@classmethod
|
| 274 |
+
def from_dict(
|
| 275 |
+
cls,
|
| 276 |
+
tensors: Dict[str, torch.Tensor],
|
| 277 |
+
non_tensors: Dict[str, NDArray] = None,
|
| 278 |
+
meta_info: Optional[Dict[str, Any]] = None,
|
| 279 |
+
num_batch_dims: int = 1,
|
| 280 |
+
) -> "DataProto":
|
| 281 |
+
"""Create a DataProto from a dict of tensors. This assumes that
|
| 282 |
+
1. All the tensor in tensors have the same dim0
|
| 283 |
+
2. Only dim0 is the batch dim
|
| 284 |
+
"""
|
| 285 |
+
assert len(tensors) > 0, "tensors must not be empty"
|
| 286 |
+
assert num_batch_dims > 0, "num_batch_dims must be greater than zero"
|
| 287 |
+
if non_tensors is not None:
|
| 288 |
+
assert num_batch_dims == 1, "only support num_batch_dims=1 when non_tensors is not None."
|
| 289 |
+
|
| 290 |
+
meta_info = meta_info or {}
|
| 291 |
+
non_tensors = non_tensors or {}
|
| 292 |
+
assert isinstance(non_tensors, dict), "non_tensors should be a dictionary."
|
| 293 |
+
|
| 294 |
+
# get and check batch size
|
| 295 |
+
batch_size = None
|
| 296 |
+
pivot_key = None
|
| 297 |
+
for key, tensor in tensors.items():
|
| 298 |
+
if batch_size is None:
|
| 299 |
+
batch_size = tensor.shape[:num_batch_dims]
|
| 300 |
+
pivot_key = key
|
| 301 |
+
else:
|
| 302 |
+
current_batch = tensor.shape[:num_batch_dims]
|
| 303 |
+
assert batch_size == current_batch, (
|
| 304 |
+
f"Not all the tensor in tensors have the same batch size with batch_dims={num_batch_dims}. "
|
| 305 |
+
f"Got {pivot_key} has {batch_size}, {key} has {current_batch}"
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
tensor_dict = TensorDict(source=tensors, batch_size=batch_size)
|
| 309 |
+
return cls(batch=tensor_dict, non_tensor_batch=non_tensors, meta_info=meta_info)
|
| 310 |
+
|
| 311 |
+
def to(self, device: torch.device) -> "DataProto":
|
| 312 |
+
"""move the batch to device
|
| 313 |
+
|
| 314 |
+
Args:
|
| 315 |
+
device (torch.device, str): torch device
|
| 316 |
+
|
| 317 |
+
Returns:
|
| 318 |
+
DataProto: the current DataProto
|
| 319 |
+
|
| 320 |
+
"""
|
| 321 |
+
if self.batch is not None:
|
| 322 |
+
self.batch = self.batch.to(device)
|
| 323 |
+
|
| 324 |
+
return self
|
| 325 |
+
|
| 326 |
+
def select(
|
| 327 |
+
self,
|
| 328 |
+
batch_keys: Optional[List[str]] = None,
|
| 329 |
+
non_tensor_batch_keys: Optional[List[str]] = None,
|
| 330 |
+
meta_info_keys: Optional[List[str]] = None,
|
| 331 |
+
deepcopy: bool = False,
|
| 332 |
+
) -> "DataProto":
|
| 333 |
+
"""Select a subset of the DataProto via batch_keys and meta_info_keys
|
| 334 |
+
|
| 335 |
+
Args:
|
| 336 |
+
batch_keys (list, optional): a list of strings indicating the keys in batch to select
|
| 337 |
+
meta_info_keys (list, optional): a list of keys indicating the meta info to select
|
| 338 |
+
|
| 339 |
+
Returns:
|
| 340 |
+
DataProto: the DataProto with the selected batch_keys and meta_info_keys
|
| 341 |
+
"""
|
| 342 |
+
# TODO (zhangchi.usc1992) whether to copy
|
| 343 |
+
if batch_keys is not None:
|
| 344 |
+
batch_keys = tuple(batch_keys)
|
| 345 |
+
sub_batch = self.batch.select(*batch_keys)
|
| 346 |
+
else:
|
| 347 |
+
sub_batch = self.batch
|
| 348 |
+
|
| 349 |
+
if non_tensor_batch_keys is not None:
|
| 350 |
+
non_tensor_batch = {k: v for k, v in self.non_tensor_batch.items() if k in non_tensor_batch_keys}
|
| 351 |
+
else:
|
| 352 |
+
non_tensor_batch = self.non_tensor_batch
|
| 353 |
+
|
| 354 |
+
if deepcopy:
|
| 355 |
+
non_tensor_batch = copy.deepcopy(non_tensor_batch)
|
| 356 |
+
|
| 357 |
+
if meta_info_keys is not None:
|
| 358 |
+
sub_meta_info = {k: v for k, v in self.meta_info.items() if k in meta_info_keys}
|
| 359 |
+
else:
|
| 360 |
+
sub_meta_info = self.meta_info
|
| 361 |
+
|
| 362 |
+
if deepcopy:
|
| 363 |
+
sub_meta_info = copy.deepcopy(sub_meta_info)
|
| 364 |
+
|
| 365 |
+
return DataProto(batch=sub_batch, non_tensor_batch=non_tensor_batch, meta_info=sub_meta_info)
|
| 366 |
+
|
| 367 |
+
def pop(
|
| 368 |
+
self,
|
| 369 |
+
batch_keys: Optional[List[str]] = None,
|
| 370 |
+
non_tensor_batch_keys: Optional[List[str]] = None,
|
| 371 |
+
meta_info_keys: Optional[List[str]] = None,
|
| 372 |
+
) -> "DataProto":
|
| 373 |
+
"""Pop a subset of the DataProto via `batch_keys` and `meta_info_keys`
|
| 374 |
+
|
| 375 |
+
Args:
|
| 376 |
+
batch_keys (list, optional): a list of strings indicating the keys in batch to pop
|
| 377 |
+
meta_info_keys (list, optional): a list of keys indicating the meta info to pop
|
| 378 |
+
|
| 379 |
+
Returns:
|
| 380 |
+
DataProto: the DataProto with the poped batch_keys and meta_info_keys
|
| 381 |
+
"""
|
| 382 |
+
assert batch_keys is not None
|
| 383 |
+
non_tensor_batch_keys = non_tensor_batch_keys or []
|
| 384 |
+
meta_info_keys = meta_info_keys or []
|
| 385 |
+
|
| 386 |
+
tensors = {}
|
| 387 |
+
for key in batch_keys:
|
| 388 |
+
tensors[key] = self.batch.pop(key)
|
| 389 |
+
|
| 390 |
+
non_tensors = {}
|
| 391 |
+
for key in non_tensor_batch_keys:
|
| 392 |
+
non_tensors[key] = self.non_tensor_batch.pop(key)
|
| 393 |
+
|
| 394 |
+
meta_info = {}
|
| 395 |
+
for key in meta_info_keys:
|
| 396 |
+
meta_info[key] = self.meta_info.pop(key)
|
| 397 |
+
|
| 398 |
+
return DataProto.from_dict(tensors=tensors, non_tensors=non_tensors, meta_info=meta_info)
|
| 399 |
+
|
| 400 |
+
def rename(
|
| 401 |
+
self, old_keys: Optional[Union[str, List[str]]] = None, new_keys: Optional[Union[str, List[str]]] = None
|
| 402 |
+
) -> "DataProto":
|
| 403 |
+
"""
|
| 404 |
+
Note that this function only rename the key in the batch
|
| 405 |
+
"""
|
| 406 |
+
|
| 407 |
+
def validate_input(keys):
|
| 408 |
+
if keys is not None:
|
| 409 |
+
if isinstance(keys, str):
|
| 410 |
+
keys = [keys]
|
| 411 |
+
elif isinstance(keys, list):
|
| 412 |
+
pass
|
| 413 |
+
else:
|
| 414 |
+
raise TypeError(f"keys must be a list or a string, but got {type(keys)}")
|
| 415 |
+
return keys
|
| 416 |
+
|
| 417 |
+
old_keys = validate_input(old_keys)
|
| 418 |
+
new_keys = validate_input(new_keys)
|
| 419 |
+
|
| 420 |
+
if len(new_keys) != len(old_keys):
|
| 421 |
+
raise ValueError(
|
| 422 |
+
f"new_keys and old_keys must have the same length, but got {len(new_keys)} and {len(old_keys)}"
|
| 423 |
+
)
|
| 424 |
+
|
| 425 |
+
self.batch.rename_key_(tuple(old_keys), tuple(new_keys))
|
| 426 |
+
|
| 427 |
+
return self
|
| 428 |
+
|
| 429 |
+
def union(self, other: "DataProto") -> "DataProto":
|
| 430 |
+
"""Union with another DataProto. Union batch and meta_info separately.
|
| 431 |
+
Throw an error if
|
| 432 |
+
- there are conflict keys in batch and they are not equal
|
| 433 |
+
- the batch size of two data batch is not the same
|
| 434 |
+
- there are conflict keys in meta_info and they are not the same.
|
| 435 |
+
|
| 436 |
+
Args:
|
| 437 |
+
other (DataProto): another DataProto to union
|
| 438 |
+
|
| 439 |
+
Returns:
|
| 440 |
+
DataProto: the DataProto after union
|
| 441 |
+
"""
|
| 442 |
+
self.batch = union_tensor_dict(self.batch, other.batch)
|
| 443 |
+
self.non_tensor_batch = union_numpy_dict(self.non_tensor_batch, other.non_tensor_batch)
|
| 444 |
+
self.meta_info = union_two_dict(self.meta_info, other.meta_info)
|
| 445 |
+
return self
|
| 446 |
+
|
| 447 |
+
def make_iterator(
|
| 448 |
+
self, mini_batch_size: int, epochs: int, seed: int = None, dataloader_kwargs: Dict[str, Any] = None
|
| 449 |
+
):
|
| 450 |
+
"""Make an iterator from the DataProto. This is built upon that TensorDict can be used as a normal Pytorch
|
| 451 |
+
dataset. See https://pytorch.org/tensordict/tutorials/data_fashion for more details.
|
| 452 |
+
|
| 453 |
+
Args:
|
| 454 |
+
mini_batch_size (int): mini-batch size when iterating the dataset. We require that
|
| 455 |
+
``batch.batch_size[0] % mini_batch_size == 0``
|
| 456 |
+
epochs (int): number of epochs when iterating the dataset.
|
| 457 |
+
dataloader_kwargs: internally, it returns a DataLoader over the batch.
|
| 458 |
+
The dataloader_kwargs is the kwargs passed to the DataLoader
|
| 459 |
+
|
| 460 |
+
Returns:
|
| 461 |
+
Iterator: an iterator that yields a mini-batch data at a time. The total number of iteration steps is
|
| 462 |
+
``self.batch.batch_size * epochs // mini_batch_size``
|
| 463 |
+
"""
|
| 464 |
+
assert self.batch.batch_size[0] % mini_batch_size == 0, f"{self.batch.batch_size[0]} % {mini_batch_size} != 0"
|
| 465 |
+
# we can directly create a dataloader from TensorDict
|
| 466 |
+
if dataloader_kwargs is None:
|
| 467 |
+
dataloader_kwargs = {}
|
| 468 |
+
|
| 469 |
+
if seed is not None:
|
| 470 |
+
generator = torch.Generator()
|
| 471 |
+
generator.manual_seed(seed)
|
| 472 |
+
else:
|
| 473 |
+
generator = None
|
| 474 |
+
|
| 475 |
+
assert isinstance(dataloader_kwargs, Dict)
|
| 476 |
+
train_dataloader = DataLoader(
|
| 477 |
+
dataset=self, batch_size=mini_batch_size, collate_fn=collate_fn, generator=generator, **dataloader_kwargs
|
| 478 |
+
)
|
| 479 |
+
|
| 480 |
+
def get_data():
|
| 481 |
+
for _ in range(epochs):
|
| 482 |
+
for d in train_dataloader:
|
| 483 |
+
d.meta_info = self.meta_info
|
| 484 |
+
yield d
|
| 485 |
+
|
| 486 |
+
return iter(get_data())
|
| 487 |
+
|
| 488 |
+
def chunk(self, chunks: int) -> List["DataProto"]:
|
| 489 |
+
"""Split the batch among dim=0 into chunks. The meta_info is passed to each DataProto after split.
|
| 490 |
+
|
| 491 |
+
Args:
|
| 492 |
+
chunks (int): the number of chunks to split on dim=0
|
| 493 |
+
|
| 494 |
+
Returns:
|
| 495 |
+
List[DataProto]: a list of DataProto after splitting
|
| 496 |
+
"""
|
| 497 |
+
assert len(self) % chunks == 0, (
|
| 498 |
+
f"only support equal chunk. Got size of DataProto {len(self)} and chunk {chunks}."
|
| 499 |
+
)
|
| 500 |
+
if self.batch is not None:
|
| 501 |
+
batch_lst = self.batch.chunk(chunks=chunks, dim=0)
|
| 502 |
+
else:
|
| 503 |
+
batch_lst = [None for _ in range(chunks)]
|
| 504 |
+
|
| 505 |
+
non_tensor_batch_lst = [{} for _ in range(chunks)]
|
| 506 |
+
for key, value in self.non_tensor_batch.items():
|
| 507 |
+
assert isinstance(value, np.ndarray)
|
| 508 |
+
non_tensor_lst = np.array_split(value, chunks)
|
| 509 |
+
assert len(non_tensor_lst) == chunks
|
| 510 |
+
for i in range(chunks):
|
| 511 |
+
non_tensor_batch_lst[i][key] = non_tensor_lst[i]
|
| 512 |
+
|
| 513 |
+
output = []
|
| 514 |
+
for i in range(chunks):
|
| 515 |
+
output.append(
|
| 516 |
+
DataProto(batch=batch_lst[i], non_tensor_batch=non_tensor_batch_lst[i], meta_info=self.meta_info)
|
| 517 |
+
)
|
| 518 |
+
|
| 519 |
+
return output
|
| 520 |
+
|
| 521 |
+
def split(self, split_size: int) -> List["DataProto"]:
|
| 522 |
+
chunks = len(self) // split_size
|
| 523 |
+
return self.chunk(chunks)
|
| 524 |
+
|
| 525 |
+
@staticmethod
|
| 526 |
+
def concat(data: List["DataProto"]) -> "DataProto":
|
| 527 |
+
"""Concat a list of DataProto. The batch is concatenated among dim=0.
|
| 528 |
+
The meta_info is assumed to be identical and will use the first one.
|
| 529 |
+
|
| 530 |
+
Args:
|
| 531 |
+
data (List[DataProto]): list of DataProto
|
| 532 |
+
|
| 533 |
+
Returns:
|
| 534 |
+
DataProto: concatenated DataProto
|
| 535 |
+
"""
|
| 536 |
+
batch_lst = [batch.batch for batch in data]
|
| 537 |
+
if batch_lst[0] is not None:
|
| 538 |
+
new_batch = torch.cat(batch_lst, dim=0)
|
| 539 |
+
else:
|
| 540 |
+
new_batch = None
|
| 541 |
+
|
| 542 |
+
non_tensor_batch = batch_collate([d.non_tensor_batch for d in data])
|
| 543 |
+
for key, value in non_tensor_batch.items():
|
| 544 |
+
non_tensor_batch[key] = np.concatenate(value, axis=0)
|
| 545 |
+
|
| 546 |
+
return DataProto(batch=new_batch, non_tensor_batch=non_tensor_batch, meta_info=data[0].meta_info)
|
| 547 |
+
|
| 548 |
+
def reorder(self, indices: torch.Tensor) -> None:
|
| 549 |
+
"""
|
| 550 |
+
Note that this operation is in-place
|
| 551 |
+
"""
|
| 552 |
+
indices_np = indices.detach().numpy()
|
| 553 |
+
self.batch = self.batch[indices]
|
| 554 |
+
self.non_tensor_batch = {key: value[indices_np] for key, value in self.non_tensor_batch.items()}
|
| 555 |
+
|
| 556 |
+
def repeat(self, repeat_times: int = 2, interleave: bool = True) -> "DataProto":
|
| 557 |
+
"""
|
| 558 |
+
Repeat the batch data a specified number of times.
|
| 559 |
+
|
| 560 |
+
Args:
|
| 561 |
+
repeat_times (int): Number of times to repeat the data.
|
| 562 |
+
interleave (bool): Whether to interleave the repeated data.
|
| 563 |
+
|
| 564 |
+
Returns:
|
| 565 |
+
DataProto: A new DataProto with repeated data.
|
| 566 |
+
"""
|
| 567 |
+
if self.batch is not None:
|
| 568 |
+
if interleave:
|
| 569 |
+
# Interleave the data
|
| 570 |
+
repeated_tensors = {
|
| 571 |
+
key: tensor.repeat_interleave(repeat_times, dim=0) for key, tensor in self.batch.items()
|
| 572 |
+
}
|
| 573 |
+
else:
|
| 574 |
+
# Stack the data
|
| 575 |
+
repeated_tensors = {
|
| 576 |
+
key: tensor.unsqueeze(0).expand(repeat_times, *tensor.shape).reshape(-1, *tensor.shape[1:])
|
| 577 |
+
for key, tensor in self.batch.items()
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
repeated_batch = TensorDict(
|
| 581 |
+
source=repeated_tensors,
|
| 582 |
+
batch_size=(self.batch.batch_size[0] * repeat_times,),
|
| 583 |
+
)
|
| 584 |
+
else:
|
| 585 |
+
repeated_batch = None
|
| 586 |
+
|
| 587 |
+
repeated_non_tensor_batch = {}
|
| 588 |
+
for key, value in self.non_tensor_batch.items():
|
| 589 |
+
if interleave:
|
| 590 |
+
repeated_non_tensor_batch[key] = np.repeat(value, repeat_times, axis=0)
|
| 591 |
+
else:
|
| 592 |
+
repeated_non_tensor_batch[key] = np.tile(value, (repeat_times,) + (1,) * (value.ndim - 1))
|
| 593 |
+
|
| 594 |
+
return DataProto(
|
| 595 |
+
batch=repeated_batch,
|
| 596 |
+
non_tensor_batch=repeated_non_tensor_batch,
|
| 597 |
+
meta_info=self.meta_info,
|
| 598 |
+
)
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
@dataclass
|
| 602 |
+
class DataProtoFuture:
|
| 603 |
+
"""
|
| 604 |
+
DataProtoFuture aims to eliminate actual data fetching on driver. By doing so, the driver doesn't have to wait
|
| 605 |
+
for data so that asynchronous execution becomes possible.
|
| 606 |
+
DataProtoFuture contains a list of futures from another WorkerGroup of size world_size.
|
| 607 |
+
- collect_fn is a Callable that reduces the list of futures to a DataProto
|
| 608 |
+
- dispatch_fn is a Callable that partitions the DataProto into a list of DataProto of size world_size and then select
|
| 609 |
+
|
| 610 |
+
Potential issue: we can optimize dispatch_fn(collect_fn) such that only needed data is fetched on destination
|
| 611 |
+
- DataProtoFuture only supports directly passing from the output of a method to another input. You can't perform any
|
| 612 |
+
operation on the DataProtoFuture in driver.
|
| 613 |
+
"""
|
| 614 |
+
|
| 615 |
+
collect_fn: Callable
|
| 616 |
+
futures: List[ray.ObjectRef]
|
| 617 |
+
dispatch_fn: Callable = None
|
| 618 |
+
|
| 619 |
+
@staticmethod
|
| 620 |
+
def concat(data: List[ray.ObjectRef]) -> "DataProtoFuture":
|
| 621 |
+
output = DataProtoFuture(collect_fn=DataProto.concat, futures=data)
|
| 622 |
+
return output
|
| 623 |
+
|
| 624 |
+
def chunk(self, chunks: int) -> List["DataProtoFuture"]:
|
| 625 |
+
from functools import partial
|
| 626 |
+
|
| 627 |
+
arg_future_lst = []
|
| 628 |
+
for i in range(chunks):
|
| 629 |
+
# note that we can't directly pass i and chunks
|
| 630 |
+
def dispatch_fn(x, i, chunks):
|
| 631 |
+
return x.chunk(chunks=chunks)[i]
|
| 632 |
+
|
| 633 |
+
arg_future = DataProtoFuture(
|
| 634 |
+
collect_fn=self.collect_fn, dispatch_fn=partial(dispatch_fn, i=i, chunks=chunks), futures=self.futures
|
| 635 |
+
)
|
| 636 |
+
arg_future_lst.append(arg_future)
|
| 637 |
+
return arg_future_lst
|
| 638 |
+
|
| 639 |
+
def get(self):
|
| 640 |
+
outputs = ray.get(self.futures) # dp_size.
|
| 641 |
+
for output in outputs:
|
| 642 |
+
assert isinstance(output, DataProto)
|
| 643 |
+
|
| 644 |
+
outputs = self.collect_fn(outputs) # select dp, concat
|
| 645 |
+
if self.dispatch_fn is not None:
|
| 646 |
+
outputs = self.dispatch_fn(outputs) # split in batch dim, select using dp
|
| 647 |
+
|
| 648 |
+
return outputs
|
| 649 |
+
|
| 650 |
+
|
| 651 |
+
def allgather_dict_tensors(
|
| 652 |
+
tensors: Union[Dict[str, torch.Tensor], TensorDict], size: int, group: ProcessGroup, dim: int = 0
|
| 653 |
+
) -> Union[Dict[str, torch.Tensor], TensorDict]:
|
| 654 |
+
"""
|
| 655 |
+
TODO: optimize this.
|
| 656 |
+
- We can use async ops
|
| 657 |
+
- We can use only one allgather
|
| 658 |
+
"""
|
| 659 |
+
if isinstance(tensors, TensorDict):
|
| 660 |
+
is_tensor_dict = True
|
| 661 |
+
tensors_as_dict = tensors.to_dict()
|
| 662 |
+
else:
|
| 663 |
+
tensors_as_dict = tensors
|
| 664 |
+
is_tensor_dict = False
|
| 665 |
+
|
| 666 |
+
output = {}
|
| 667 |
+
sorted_keys = sorted(tensors_as_dict.keys())
|
| 668 |
+
for key in sorted_keys:
|
| 669 |
+
value = tensors_as_dict[key]
|
| 670 |
+
output[key] = [torch.empty_like(value) for _ in range(size)]
|
| 671 |
+
torch.distributed.all_gather(output[key], value, group=group, async_op=False)
|
| 672 |
+
output[key] = torch.cat(output[key], dim=dim)
|
| 673 |
+
|
| 674 |
+
if is_tensor_dict:
|
| 675 |
+
output = TensorDict(source=output, batch_size=tensors.batch_size[0] * size)
|
| 676 |
+
|
| 677 |
+
return output
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
def all_gather_data_proto(data: DataProto, size: int, group: ProcessGroup) -> None:
|
| 681 |
+
# Note that this is an inplace operator just like torch.distributed.all_gather
|
| 682 |
+
prev_device = data.batch.device
|
| 683 |
+
data.batch = data.batch.cuda(device=torch.cuda.current_device())
|
| 684 |
+
data.batch = allgather_dict_tensors(data.batch.contiguous(), size=size, group=group, dim=0)
|
| 685 |
+
data.batch = data.batch.to(prev_device)
|
| 686 |
+
# all gather non_tensor_batch
|
| 687 |
+
all_non_tensor_batch = [None for _ in range(size)]
|
| 688 |
+
torch.distributed.all_gather_object(all_non_tensor_batch, data.non_tensor_batch, group=group)
|
| 689 |
+
data.non_tensor_batch = {k: np.concatenate([d[k] for d in all_non_tensor_batch]) for k in data.non_tensor_batch}
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/pyproject.toml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=61.0"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "verl"
|
| 7 |
+
dynamic = [
|
| 8 |
+
"version",
|
| 9 |
+
"dependencies",
|
| 10 |
+
"optional-dependencies",
|
| 11 |
+
"requires-python",
|
| 12 |
+
"authors",
|
| 13 |
+
"description",
|
| 14 |
+
"readme",
|
| 15 |
+
"license"
|
| 16 |
+
]
|
| 17 |
+
|
| 18 |
+
[tool.ruff]
|
| 19 |
+
target-version = "py39"
|
| 20 |
+
line-length = 119
|
| 21 |
+
indent-width = 4
|
| 22 |
+
|
| 23 |
+
[tool.ruff.lint]
|
| 24 |
+
ignore = ["C901", "E501", "E741", "W605", "C408"]
|
| 25 |
+
select = ["C", "E", "F", "I", "W", "RUF022"]
|
| 26 |
+
|
| 27 |
+
[tool.ruff.lint.per-file-ignores]
|
| 28 |
+
"__init__.py" = ["E402", "F401", "F403", "F811"]
|
| 29 |
+
|
| 30 |
+
[tool.ruff.lint.isort]
|
| 31 |
+
lines-after-imports = 2
|
| 32 |
+
known-first-party = ["verl"]
|
| 33 |
+
known-third-party = ["torch", "transformers", "wandb"]
|
| 34 |
+
|
| 35 |
+
[tool.ruff.format]
|
| 36 |
+
quote-style = "double"
|
| 37 |
+
indent-style = "space"
|
| 38 |
+
skip-magic-trailing-comma = false
|
| 39 |
+
line-ending = "auto"
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/requirements.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
accelerate
|
| 2 |
+
codetiming
|
| 3 |
+
datasets
|
| 4 |
+
liger-kernel
|
| 5 |
+
mathruler
|
| 6 |
+
numpy
|
| 7 |
+
omegaconf
|
| 8 |
+
pandas
|
| 9 |
+
peft
|
| 10 |
+
pillow
|
| 11 |
+
pyarrow>=15.0.0
|
| 12 |
+
pylatexenc
|
| 13 |
+
qwen-vl-utils
|
| 14 |
+
ray[default]
|
| 15 |
+
tensordict
|
| 16 |
+
torchdata
|
| 17 |
+
transformers>=4.51.0
|
| 18 |
+
vllm>=0.7.3
|
| 19 |
+
wandb
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from .worker import Worker
|
| 16 |
+
from .worker_group import ClassWithInitArgs, ResourcePool, WorkerGroup
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
__all__ = ["ClassWithInitArgs", "ResourcePool", "Worker", "WorkerGroup"]
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/decorator.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from enum import Enum, auto
|
| 16 |
+
from functools import wraps
|
| 17 |
+
from types import FunctionType
|
| 18 |
+
from typing import TYPE_CHECKING, Dict, List, Literal, Union
|
| 19 |
+
|
| 20 |
+
import ray
|
| 21 |
+
|
| 22 |
+
from ...protocol import DataProto, DataProtoFuture
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
if TYPE_CHECKING:
|
| 26 |
+
from .worker_group import WorkerGroup
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# here we add a magic number of avoid user-defined function already have this attribute
|
| 30 |
+
MAGIC_ATTR = "attrs_3141562937"
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class Dispatch(Enum):
|
| 34 |
+
RANK_ZERO = auto()
|
| 35 |
+
ONE_TO_ALL = auto()
|
| 36 |
+
ALL_TO_ALL = auto()
|
| 37 |
+
DP_COMPUTE = auto()
|
| 38 |
+
DP_COMPUTE_PROTO = auto()
|
| 39 |
+
DP_COMPUTE_PROTO_WITH_FUNC = auto()
|
| 40 |
+
DP_COMPUTE_METRIC = auto()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class Execute(Enum):
|
| 44 |
+
ALL = 0
|
| 45 |
+
RANK_ZERO = 1
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _split_args_kwargs_data_proto(chunks: int, *args, **kwargs):
|
| 49 |
+
splitted_args = []
|
| 50 |
+
for arg in args:
|
| 51 |
+
assert isinstance(arg, (DataProto, DataProtoFuture))
|
| 52 |
+
splitted_args.append(arg.chunk(chunks=chunks))
|
| 53 |
+
|
| 54 |
+
splitted_kwargs = {}
|
| 55 |
+
for key, value in kwargs.items():
|
| 56 |
+
assert isinstance(value, (DataProto, DataProtoFuture))
|
| 57 |
+
splitted_kwargs[key] = value.chunk(chunks=chunks)
|
| 58 |
+
|
| 59 |
+
return splitted_args, splitted_kwargs
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def dispatch_one_to_all(worker_group: "WorkerGroup", *args, **kwargs):
|
| 63 |
+
args = tuple([arg] * worker_group.world_size for arg in args)
|
| 64 |
+
kwargs = {k: [v] * worker_group.world_size for k, v in kwargs.items()}
|
| 65 |
+
return args, kwargs
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def dispatch_all_to_all(worker_group: "WorkerGroup", *args, **kwargs):
|
| 69 |
+
return args, kwargs
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def collect_all_to_all(worker_group: "WorkerGroup", output):
|
| 73 |
+
return output
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _concat_data_proto_or_future(outputs: List[DataProto]) -> DataProto:
|
| 77 |
+
# make sure all the elements in output has the same type
|
| 78 |
+
for output in outputs:
|
| 79 |
+
assert type(output) is type(outputs[0])
|
| 80 |
+
|
| 81 |
+
output = outputs[0]
|
| 82 |
+
|
| 83 |
+
if isinstance(output, DataProto):
|
| 84 |
+
return DataProto.concat(outputs)
|
| 85 |
+
elif isinstance(output, ray.ObjectRef):
|
| 86 |
+
return DataProtoFuture.concat(outputs)
|
| 87 |
+
else:
|
| 88 |
+
raise NotImplementedError
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def dispatch_dp_compute(worker_group: "WorkerGroup", *args, **kwargs):
|
| 92 |
+
for arg in args:
|
| 93 |
+
assert isinstance(arg, (tuple, list)) and len(arg) == worker_group.world_size
|
| 94 |
+
|
| 95 |
+
for value in kwargs.values():
|
| 96 |
+
assert isinstance(value, (tuple, list)) and len(value) == worker_group.world_size
|
| 97 |
+
|
| 98 |
+
return args, kwargs
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def collect_dp_compute(worker_group: "WorkerGroup", outputs: List[DataProto]) -> List[DataProto]:
|
| 102 |
+
assert len(outputs) == worker_group.world_size
|
| 103 |
+
return outputs
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def dispatch_dp_compute_data_proto(worker_group: "WorkerGroup", *args, **kwargs):
|
| 107 |
+
splitted_args, splitted_kwargs = _split_args_kwargs_data_proto(worker_group.world_size, *args, **kwargs)
|
| 108 |
+
return splitted_args, splitted_kwargs
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def dispatch_dp_compute_data_proto_with_func(worker_group: "WorkerGroup", *args, **kwargs):
|
| 112 |
+
assert type(args[0]) is FunctionType # NOTE: The first one args is a function!
|
| 113 |
+
splitted_args, splitted_kwargs = _split_args_kwargs_data_proto(worker_group.world_size, *args[1:], **kwargs)
|
| 114 |
+
splitted_args_with_func = [[args[0]] * worker_group.world_size] + splitted_args
|
| 115 |
+
return splitted_args_with_func, splitted_kwargs
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def collect_dp_compute_data_proto(worker_group: "WorkerGroup", outputs: List[DataProto]) -> DataProto:
|
| 119 |
+
for output in outputs:
|
| 120 |
+
assert isinstance(output, (DataProto, ray.ObjectRef)), f"Expect a DataProto, but got {type(output)}"
|
| 121 |
+
|
| 122 |
+
outputs = collect_dp_compute(worker_group, outputs)
|
| 123 |
+
return _concat_data_proto_or_future(outputs)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def get_predefined_dispatch_fn(dispatch_mode: Dispatch):
|
| 127 |
+
predefined_dispatch_mode_fn = {
|
| 128 |
+
Dispatch.ONE_TO_ALL: {
|
| 129 |
+
"dispatch_fn": dispatch_one_to_all,
|
| 130 |
+
"collect_fn": collect_all_to_all,
|
| 131 |
+
},
|
| 132 |
+
Dispatch.ALL_TO_ALL: {
|
| 133 |
+
"dispatch_fn": dispatch_all_to_all,
|
| 134 |
+
"collect_fn": collect_all_to_all,
|
| 135 |
+
},
|
| 136 |
+
Dispatch.DP_COMPUTE: {
|
| 137 |
+
"dispatch_fn": dispatch_dp_compute,
|
| 138 |
+
"collect_fn": collect_dp_compute,
|
| 139 |
+
},
|
| 140 |
+
Dispatch.DP_COMPUTE_PROTO: {
|
| 141 |
+
"dispatch_fn": dispatch_dp_compute_data_proto,
|
| 142 |
+
"collect_fn": collect_dp_compute_data_proto,
|
| 143 |
+
},
|
| 144 |
+
Dispatch.DP_COMPUTE_PROTO_WITH_FUNC: {
|
| 145 |
+
"dispatch_fn": dispatch_dp_compute_data_proto_with_func,
|
| 146 |
+
"collect_fn": collect_dp_compute_data_proto,
|
| 147 |
+
},
|
| 148 |
+
Dispatch.DP_COMPUTE_METRIC: {
|
| 149 |
+
"dispatch_fn": dispatch_dp_compute_data_proto,
|
| 150 |
+
"collect_fn": collect_dp_compute,
|
| 151 |
+
},
|
| 152 |
+
}
|
| 153 |
+
return predefined_dispatch_mode_fn[dispatch_mode]
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def get_predefined_execute_fn(execute_mode: Execute):
|
| 157 |
+
"""
|
| 158 |
+
Note that here we only asks execute_all and execute_rank_zero to be implemented
|
| 159 |
+
Leave the choice of how these two functions handle argument 'blocking' to users
|
| 160 |
+
"""
|
| 161 |
+
predefined_execute_mode_fn = {
|
| 162 |
+
Execute.ALL: {"execute_fn_name": "execute_all"},
|
| 163 |
+
Execute.RANK_ZERO: {"execute_fn_name": "execute_rank_zero"},
|
| 164 |
+
}
|
| 165 |
+
return predefined_execute_mode_fn[execute_mode]
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def _check_dispatch_mode(dispatch_mode: Union[Dispatch, Dict[Literal["dispatch_fn", "collect_fn"], FunctionType]]):
|
| 169 |
+
assert isinstance(dispatch_mode, (Dispatch, dict)), (
|
| 170 |
+
f"dispatch_mode must be a Dispatch or a Dict. Got {dispatch_mode}"
|
| 171 |
+
)
|
| 172 |
+
if isinstance(dispatch_mode, dict):
|
| 173 |
+
necessary_keys = ["dispatch_fn", "collect_fn"]
|
| 174 |
+
for key in necessary_keys:
|
| 175 |
+
assert key in dispatch_mode, f"key {key} should be in dispatch_mode if it is a dictionary"
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
def _check_execute_mode(execute_mode: Execute):
|
| 179 |
+
assert isinstance(execute_mode, Execute), f"execute_mode must be a Execute. Got {execute_mode}"
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def _materialize_futures(*args, **kwargs):
|
| 183 |
+
new_args = []
|
| 184 |
+
for arg in args:
|
| 185 |
+
if isinstance(arg, DataProtoFuture):
|
| 186 |
+
arg = arg.get()
|
| 187 |
+
# add more type to materialize
|
| 188 |
+
new_args.append(arg)
|
| 189 |
+
|
| 190 |
+
for key, value in kwargs.items():
|
| 191 |
+
if isinstance(value, DataProtoFuture):
|
| 192 |
+
kwargs[key] = value.get()
|
| 193 |
+
|
| 194 |
+
new_args = tuple(new_args)
|
| 195 |
+
return new_args, kwargs
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def register(dispatch_mode=Dispatch.ALL_TO_ALL, execute_mode=Execute.ALL, blocking=True, materialize_futures=True):
|
| 199 |
+
_check_dispatch_mode(dispatch_mode=dispatch_mode)
|
| 200 |
+
_check_execute_mode(execute_mode=execute_mode)
|
| 201 |
+
|
| 202 |
+
def decorator(func):
|
| 203 |
+
@wraps(func)
|
| 204 |
+
def inner(*args, **kwargs):
|
| 205 |
+
if materialize_futures:
|
| 206 |
+
args, kwargs = _materialize_futures(*args, **kwargs)
|
| 207 |
+
return func(*args, **kwargs)
|
| 208 |
+
|
| 209 |
+
attrs = {"dispatch_mode": dispatch_mode, "execute_mode": execute_mode, "blocking": blocking}
|
| 210 |
+
setattr(inner, MAGIC_ATTR, attrs)
|
| 211 |
+
return inner
|
| 212 |
+
|
| 213 |
+
return decorator
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/register_center/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/register_center/ray.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import ray
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@ray.remote
|
| 19 |
+
class WorkerGroupRegisterCenter:
|
| 20 |
+
def __init__(self, rank_zero_info):
|
| 21 |
+
self.rank_zero_info = rank_zero_info
|
| 22 |
+
|
| 23 |
+
def get_rank_zero_info(self):
|
| 24 |
+
return self.rank_zero_info
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def create_worker_group_register_center(name, info):
|
| 28 |
+
return WorkerGroupRegisterCenter.options(name=name).remote(info)
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/worker.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""
|
| 15 |
+
the class for Worker
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import socket
|
| 20 |
+
from dataclasses import dataclass
|
| 21 |
+
from typing import Tuple
|
| 22 |
+
|
| 23 |
+
import ray
|
| 24 |
+
import torch
|
| 25 |
+
|
| 26 |
+
from .decorator import Dispatch, Execute, register
|
| 27 |
+
from .register_center.ray import create_worker_group_register_center
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@dataclass
|
| 31 |
+
class DistRankInfo:
|
| 32 |
+
tp_rank: int
|
| 33 |
+
dp_rank: int
|
| 34 |
+
pp_rank: int
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclass
|
| 38 |
+
class DistGlobalInfo:
|
| 39 |
+
tp_size: int
|
| 40 |
+
dp_size: int
|
| 41 |
+
pp_size: int
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class WorkerHelper:
|
| 45 |
+
def _get_node_ip(self) -> str:
|
| 46 |
+
host_ipv4 = os.getenv("MY_HOST_IP", None)
|
| 47 |
+
host_ipv6 = os.getenv("MY_HOST_IPV6", None)
|
| 48 |
+
host_ip_by_env = host_ipv4 or host_ipv6
|
| 49 |
+
host_ip_by_sdk = ray._private.services.get_node_ip_address()
|
| 50 |
+
|
| 51 |
+
host_ip = host_ip_by_env or host_ip_by_sdk
|
| 52 |
+
return host_ip
|
| 53 |
+
|
| 54 |
+
def _get_free_port(self) -> int:
|
| 55 |
+
with socket.socket() as sock:
|
| 56 |
+
sock.bind(("", 0))
|
| 57 |
+
return sock.getsockname()[1]
|
| 58 |
+
|
| 59 |
+
def get_availale_master_addr_port(self) -> Tuple[str, str]:
|
| 60 |
+
return self._get_node_ip(), str(self._get_free_port())
|
| 61 |
+
|
| 62 |
+
def _get_pid(self):
|
| 63 |
+
return
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class WorkerMeta:
|
| 67 |
+
keys = [
|
| 68 |
+
"WORLD_SIZE",
|
| 69 |
+
"RANK",
|
| 70 |
+
"LOCAL_WORLD_SIZE",
|
| 71 |
+
"LOCAL_RANK",
|
| 72 |
+
"MASTER_ADDR",
|
| 73 |
+
"MASTER_PORT",
|
| 74 |
+
"CUDA_VISIBLE_DEVICES",
|
| 75 |
+
]
|
| 76 |
+
|
| 77 |
+
def __init__(self, store) -> None:
|
| 78 |
+
self._store = store
|
| 79 |
+
|
| 80 |
+
def to_dict(self):
|
| 81 |
+
return {f"_{key.lower()}": self._store.get(f"_{key.lower()}", None) for key in WorkerMeta.keys}
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
# we assume that in each WorkerGroup, there is a Master Worker
|
| 85 |
+
class Worker(WorkerHelper):
|
| 86 |
+
"""A (distributed) worker."""
|
| 87 |
+
|
| 88 |
+
_world_size: int
|
| 89 |
+
_rank: int
|
| 90 |
+
_local_world_size: int
|
| 91 |
+
_local_rank: int
|
| 92 |
+
_master_addr: str
|
| 93 |
+
_master_port: str
|
| 94 |
+
_cuda_visible_devices: str
|
| 95 |
+
|
| 96 |
+
def __new__(cls, *args, **kwargs):
|
| 97 |
+
instance = super().__new__(cls)
|
| 98 |
+
|
| 99 |
+
# note that here we use int to distinguish
|
| 100 |
+
disable_worker_init = int(os.getenv("DISABLE_WORKER_INIT", 0))
|
| 101 |
+
if disable_worker_init:
|
| 102 |
+
return instance
|
| 103 |
+
|
| 104 |
+
rank = os.getenv("RANK", None)
|
| 105 |
+
worker_group_prefix = os.getenv("WG_PREFIX", None)
|
| 106 |
+
|
| 107 |
+
# when decorator @ray.remote applies, __new__ will be called while we don't want to apply _configure_before_init
|
| 108 |
+
if None not in [rank, worker_group_prefix] and "ActorClass(" not in cls.__name__:
|
| 109 |
+
instance._configure_before_init(f"{worker_group_prefix}_register_center", int(rank))
|
| 110 |
+
|
| 111 |
+
return instance
|
| 112 |
+
|
| 113 |
+
def _configure_before_init(self, register_center_name: str, rank: int):
|
| 114 |
+
assert isinstance(rank, int), f"rank must be int, instead of {type(rank)}"
|
| 115 |
+
|
| 116 |
+
if rank == 0:
|
| 117 |
+
master_addr, master_port = self.get_availale_master_addr_port()
|
| 118 |
+
rank_zero_info = {
|
| 119 |
+
"MASTER_ADDR": master_addr,
|
| 120 |
+
"MASTER_PORT": master_port,
|
| 121 |
+
}
|
| 122 |
+
self.register_center = create_worker_group_register_center(name=register_center_name, info=rank_zero_info)
|
| 123 |
+
os.environ.update(rank_zero_info)
|
| 124 |
+
|
| 125 |
+
def __init__(self, cuda_visible_devices=None) -> None:
|
| 126 |
+
# construct a meta from envrionment variable. Note that the import must be inside the class because it is executed remotely
|
| 127 |
+
world_size = int(os.getenv("WORLD_SIZE"))
|
| 128 |
+
rank = int(os.getenv("RANK"))
|
| 129 |
+
self._rank = rank
|
| 130 |
+
self._world_size = world_size
|
| 131 |
+
|
| 132 |
+
if "AMD" in torch.cuda.get_device_name():
|
| 133 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = os.getenv("ROCR_VISIBLE_DEVICES")
|
| 134 |
+
os.environ["LOCAL_RANK"] = os.getenv("RAY_LOCAL_RANK")
|
| 135 |
+
cuda_visible_devices = os.getenv("LOCAL_RANK", "0")
|
| 136 |
+
torch.cuda.set_device(int(cuda_visible_devices))
|
| 137 |
+
|
| 138 |
+
master_addr = os.getenv("MASTER_ADDR")
|
| 139 |
+
master_port = os.getenv("MASTER_PORT")
|
| 140 |
+
|
| 141 |
+
local_world_size = int(os.getenv("LOCAL_WORLD_SIZE", "1"))
|
| 142 |
+
local_rank = int(os.getenv("LOCAL_RANK", "0"))
|
| 143 |
+
|
| 144 |
+
store = {
|
| 145 |
+
"_world_size": world_size,
|
| 146 |
+
"_rank": rank,
|
| 147 |
+
"_local_world_size": local_world_size,
|
| 148 |
+
"_local_rank": local_rank,
|
| 149 |
+
"_master_addr": master_addr,
|
| 150 |
+
"_master_port": master_port,
|
| 151 |
+
}
|
| 152 |
+
if cuda_visible_devices is not None:
|
| 153 |
+
store["_cuda_visible_devices"] = cuda_visible_devices
|
| 154 |
+
|
| 155 |
+
meta = WorkerMeta(store=store)
|
| 156 |
+
self._configure_with_meta(meta=meta)
|
| 157 |
+
|
| 158 |
+
def _configure_with_meta(self, meta: WorkerMeta):
|
| 159 |
+
"""
|
| 160 |
+
This function should only be called inside by WorkerGroup
|
| 161 |
+
"""
|
| 162 |
+
assert isinstance(meta, WorkerMeta)
|
| 163 |
+
self.__dict__.update(meta.to_dict()) # this is hacky
|
| 164 |
+
# print(f"__dict__: {self.__dict__}")
|
| 165 |
+
for key in WorkerMeta.keys:
|
| 166 |
+
val = self.__dict__.get(f"_{key.lower()}", None)
|
| 167 |
+
if val is not None:
|
| 168 |
+
# print(f"set {key} to {val}")
|
| 169 |
+
os.environ[key] = str(val)
|
| 170 |
+
|
| 171 |
+
os.environ["REDIS_STORE_SERVER_HOST"] = (
|
| 172 |
+
str(self._master_addr).replace("[", "").replace("]", "") if self._master_addr else ""
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
def get_master_addr_port(self):
|
| 176 |
+
return self._master_addr, self._master_port
|
| 177 |
+
|
| 178 |
+
def get_cuda_visible_devices(self):
|
| 179 |
+
cuda_visible_devices = os.getenv("CUDA_VISIBLE_DEVICES", "not set")
|
| 180 |
+
return cuda_visible_devices
|
| 181 |
+
|
| 182 |
+
def print_rank0(self, *args, **kwargs):
|
| 183 |
+
if self.rank == 0:
|
| 184 |
+
print(*args, **kwargs)
|
| 185 |
+
|
| 186 |
+
@property
|
| 187 |
+
def world_size(self):
|
| 188 |
+
return self._world_size
|
| 189 |
+
|
| 190 |
+
@property
|
| 191 |
+
def rank(self):
|
| 192 |
+
return self._rank
|
| 193 |
+
|
| 194 |
+
@register(dispatch_mode=Dispatch.DP_COMPUTE_PROTO_WITH_FUNC)
|
| 195 |
+
def execute_with_func_generator(self, func, *args, **kwargs):
|
| 196 |
+
ret_proto = func(self, *args, **kwargs)
|
| 197 |
+
return ret_proto
|
| 198 |
+
|
| 199 |
+
@register(dispatch_mode=Dispatch.ALL_TO_ALL, execute_mode=Execute.RANK_ZERO)
|
| 200 |
+
def execute_func_rank_zero(self, func, *args, **kwargs):
|
| 201 |
+
result = func(*args, **kwargs)
|
| 202 |
+
return result
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/base/worker_group.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""
|
| 15 |
+
the class of WorkerGroup
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import logging
|
| 19 |
+
import signal
|
| 20 |
+
import threading
|
| 21 |
+
import time
|
| 22 |
+
from typing import Any, Callable, Dict, List, Optional
|
| 23 |
+
|
| 24 |
+
from .decorator import MAGIC_ATTR, Dispatch, get_predefined_dispatch_fn, get_predefined_execute_fn
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class ResourcePool:
|
| 28 |
+
"""The resource pool with meta info such as world size."""
|
| 29 |
+
|
| 30 |
+
def __init__(
|
| 31 |
+
self, process_on_nodes: Optional[Any] = None, max_colocate_count: int = 10, n_gpus_per_node: int = 8
|
| 32 |
+
) -> None:
|
| 33 |
+
if process_on_nodes is None:
|
| 34 |
+
process_on_nodes = []
|
| 35 |
+
|
| 36 |
+
self._store = process_on_nodes
|
| 37 |
+
self.max_colocate_count = max_colocate_count
|
| 38 |
+
self.n_gpus_per_node = n_gpus_per_node # this is left for future huawei GPU that contains 16 GPUs per node
|
| 39 |
+
|
| 40 |
+
def add_node(self, process_count):
|
| 41 |
+
self._store.append(process_count)
|
| 42 |
+
|
| 43 |
+
@property
|
| 44 |
+
def world_size(self):
|
| 45 |
+
return sum(self._store)
|
| 46 |
+
|
| 47 |
+
def __call__(self) -> Any:
|
| 48 |
+
return self._store
|
| 49 |
+
|
| 50 |
+
@property
|
| 51 |
+
def store(self):
|
| 52 |
+
return self._store
|
| 53 |
+
|
| 54 |
+
def local_world_size_list(self) -> List[int]:
|
| 55 |
+
nested_local_world_size_list = [
|
| 56 |
+
[local_world_size for _ in range(local_world_size)] for local_world_size in self._store
|
| 57 |
+
]
|
| 58 |
+
return [item for row in nested_local_world_size_list for item in row]
|
| 59 |
+
|
| 60 |
+
def local_rank_list(self) -> List[int]:
|
| 61 |
+
nested_local_rank_list = [[i for i in range(local_world_size)] for local_world_size in self._store] # noqa: C416
|
| 62 |
+
return [item for row in nested_local_rank_list for item in row]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class ClassWithInitArgs:
|
| 66 |
+
"""
|
| 67 |
+
This class stores a class constructor and the args/kwargs to construct the class.
|
| 68 |
+
It is used to instantiate the remote class.
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
def __init__(self, cls, *args, **kwargs) -> None:
|
| 72 |
+
self.cls = cls
|
| 73 |
+
self.args = args
|
| 74 |
+
self.kwargs = kwargs
|
| 75 |
+
|
| 76 |
+
def __call__(self) -> Any:
|
| 77 |
+
return self.cls(*self.args, **self.kwargs)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def check_workers_alive(workers: List, is_alive: Callable, gap_time: float = 1) -> None:
|
| 81 |
+
while True:
|
| 82 |
+
for worker in workers:
|
| 83 |
+
if not is_alive(worker):
|
| 84 |
+
logging.warning(f"Worker {worker} is not alive, sending signal to main thread")
|
| 85 |
+
signal.raise_signal(signal.SIGABRT)
|
| 86 |
+
|
| 87 |
+
time.sleep(gap_time)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class WorkerGroup:
|
| 91 |
+
"""A group of workers"""
|
| 92 |
+
|
| 93 |
+
def __init__(self, resource_pool: ResourcePool, **kwargs) -> None:
|
| 94 |
+
self._is_init_with_detached_workers = True if resource_pool is None else False
|
| 95 |
+
|
| 96 |
+
if resource_pool is not None:
|
| 97 |
+
# handle the case when WorkGroup is attached to an existing one
|
| 98 |
+
self._procecss_dispatch_config = resource_pool()
|
| 99 |
+
else:
|
| 100 |
+
self._procecss_dispatch_config = None
|
| 101 |
+
|
| 102 |
+
self._workers = []
|
| 103 |
+
self._worker_names = []
|
| 104 |
+
|
| 105 |
+
self._master_addr = None
|
| 106 |
+
self._master_port = None
|
| 107 |
+
|
| 108 |
+
self._checker_thread: threading.Thread = None
|
| 109 |
+
|
| 110 |
+
def _is_worker_alive(self, worker):
|
| 111 |
+
raise NotImplementedError("WorkerGroup._is_worker_alive called, should be implemented in derived class.")
|
| 112 |
+
|
| 113 |
+
def _block_until_all_workers_alive(self) -> None:
|
| 114 |
+
while True:
|
| 115 |
+
all_state = [self._is_worker_alive(worker) for worker in self._workers]
|
| 116 |
+
if False in all_state:
|
| 117 |
+
time.sleep(1)
|
| 118 |
+
else:
|
| 119 |
+
break
|
| 120 |
+
|
| 121 |
+
def start_worker_aliveness_check(self, every_n_seconds=1) -> None:
|
| 122 |
+
# before starting checking worker aliveness, make sure all workers are already alive
|
| 123 |
+
self._block_until_all_workers_alive()
|
| 124 |
+
|
| 125 |
+
self._checker_thread = threading.Thread(
|
| 126 |
+
target=check_workers_alive, args=(self._workers, self._is_worker_alive, every_n_seconds)
|
| 127 |
+
)
|
| 128 |
+
self._checker_thread.start()
|
| 129 |
+
|
| 130 |
+
@property
|
| 131 |
+
def world_size(self):
|
| 132 |
+
return len(self._workers)
|
| 133 |
+
|
| 134 |
+
def _bind_worker_method(self, user_defined_cls, func_generator):
|
| 135 |
+
"""
|
| 136 |
+
Bind the worker method to the WorkerGroup
|
| 137 |
+
"""
|
| 138 |
+
for method_name in dir(user_defined_cls):
|
| 139 |
+
try:
|
| 140 |
+
method = getattr(user_defined_cls, method_name)
|
| 141 |
+
assert callable(method), f"{method_name} in {user_defined_cls} is not callable"
|
| 142 |
+
except Exception:
|
| 143 |
+
# if it is a property, it will fail because Class doesn't have instance property
|
| 144 |
+
continue
|
| 145 |
+
|
| 146 |
+
if hasattr(method, MAGIC_ATTR):
|
| 147 |
+
# this method is decorated by register
|
| 148 |
+
attribute = getattr(method, MAGIC_ATTR)
|
| 149 |
+
assert isinstance(attribute, Dict), f"attribute must be a dictionary. Got {type(attribute)}"
|
| 150 |
+
assert "dispatch_mode" in attribute, "attribute must contain dispatch_mode in its key"
|
| 151 |
+
|
| 152 |
+
dispatch_mode = attribute["dispatch_mode"]
|
| 153 |
+
execute_mode = attribute["execute_mode"]
|
| 154 |
+
blocking = attribute["blocking"]
|
| 155 |
+
|
| 156 |
+
# get dispatch fn
|
| 157 |
+
if isinstance(dispatch_mode, Dispatch):
|
| 158 |
+
# get default dispatch fn
|
| 159 |
+
fn = get_predefined_dispatch_fn(dispatch_mode=dispatch_mode)
|
| 160 |
+
dispatch_fn = fn["dispatch_fn"]
|
| 161 |
+
collect_fn = fn["collect_fn"]
|
| 162 |
+
else:
|
| 163 |
+
assert isinstance(dispatch_mode, dict)
|
| 164 |
+
assert "dispatch_fn" in dispatch_mode
|
| 165 |
+
assert "collect_fn" in dispatch_mode
|
| 166 |
+
dispatch_fn = dispatch_mode["dispatch_fn"]
|
| 167 |
+
collect_fn = dispatch_mode["collect_fn"]
|
| 168 |
+
|
| 169 |
+
# get execute_fn_name
|
| 170 |
+
execute_mode = get_predefined_execute_fn(execute_mode=execute_mode)
|
| 171 |
+
wg_execute_fn_name = execute_mode["execute_fn_name"]
|
| 172 |
+
|
| 173 |
+
# get execute_fn from string
|
| 174 |
+
try:
|
| 175 |
+
execute_fn = getattr(self, wg_execute_fn_name)
|
| 176 |
+
assert callable(execute_fn), "execute_fn must be callable"
|
| 177 |
+
except Exception:
|
| 178 |
+
print(f"execute_fn {wg_execute_fn_name} is invalid")
|
| 179 |
+
raise
|
| 180 |
+
|
| 181 |
+
# bind a new method to the RayWorkerGroup
|
| 182 |
+
func = func_generator(
|
| 183 |
+
self,
|
| 184 |
+
method_name,
|
| 185 |
+
dispatch_fn=dispatch_fn,
|
| 186 |
+
collect_fn=collect_fn,
|
| 187 |
+
execute_fn=execute_fn,
|
| 188 |
+
blocking=blocking,
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
try:
|
| 192 |
+
setattr(self, method_name, func)
|
| 193 |
+
except Exception:
|
| 194 |
+
raise ValueError(f"Fail to set method_name {method_name}")
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/ray/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from .base import RayClassWithInitArgs, RayResourcePool, RayWorkerGroup, create_colocated_worker_cls
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
__all__ = ["RayClassWithInitArgs", "RayResourcePool", "RayWorkerGroup", "create_colocated_worker_cls"]
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/single_controller/ray/base.py
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import random
|
| 17 |
+
import re
|
| 18 |
+
import string
|
| 19 |
+
import time
|
| 20 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 21 |
+
from unittest.mock import patch
|
| 22 |
+
|
| 23 |
+
import ray
|
| 24 |
+
from ray.actor import ActorHandle
|
| 25 |
+
from ray.experimental.state.api import get_actor
|
| 26 |
+
from ray.util import list_named_actors
|
| 27 |
+
from ray.util.placement_group import PlacementGroup, placement_group
|
| 28 |
+
from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy, PlacementGroupSchedulingStrategy
|
| 29 |
+
|
| 30 |
+
from ..base import ClassWithInitArgs, ResourcePool, Worker, WorkerGroup
|
| 31 |
+
from ..base.decorator import MAGIC_ATTR
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
__all__ = ["Worker"]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def get_random_string(length: int) -> str:
|
| 38 |
+
letters_digits = string.ascii_letters + string.digits
|
| 39 |
+
return "".join(random.choice(letters_digits) for _ in range(length))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def func_generator(self, method_name, dispatch_fn, collect_fn, execute_fn, blocking):
|
| 43 |
+
def func(*args, **kwargs):
|
| 44 |
+
args, kwargs = dispatch_fn(self, *args, **kwargs)
|
| 45 |
+
output = execute_fn(method_name, *args, **kwargs)
|
| 46 |
+
if blocking:
|
| 47 |
+
output = ray.get(output)
|
| 48 |
+
output = collect_fn(self, output)
|
| 49 |
+
return output
|
| 50 |
+
|
| 51 |
+
return func
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def sort_placement_group_by_node_ip(pgs: List[PlacementGroup]) -> List[PlacementGroup]:
|
| 55 |
+
"""
|
| 56 |
+
Sort the placement groups by node ip, all bundles in a single placement group should be on the same node.
|
| 57 |
+
|
| 58 |
+
FSDPCheckpointManager saves sharded model states and optimizer states in local storage, which requires RANK
|
| 59 |
+
to be consistent across nodes when resume from checkpoint.
|
| 60 |
+
|
| 61 |
+
With this function, if there's only one resource pool and there's no node change, RANK should be consistent
|
| 62 |
+
across nodes in multiple ray jobs, even if the whole ray cluster is restarted.
|
| 63 |
+
"""
|
| 64 |
+
node_ip = {node["NodeID"]: node["NodeManagerAddress"] for node in ray.nodes()}
|
| 65 |
+
pg_ip = {}
|
| 66 |
+
for pg in pgs:
|
| 67 |
+
specs = ray._private.state.state.placement_group_table(pg.id)
|
| 68 |
+
# all bunles should be on the same node
|
| 69 |
+
node_id = specs["bundles_to_node_id"][0]
|
| 70 |
+
pg_ip[pg.id] = node_ip[node_id]
|
| 71 |
+
|
| 72 |
+
return sorted(pgs, key=lambda pg: pg_ip[pg.id])
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
class RayResourcePool(ResourcePool):
|
| 76 |
+
def __init__(
|
| 77 |
+
self,
|
| 78 |
+
process_on_nodes: List[int] = None,
|
| 79 |
+
use_gpu: bool = True,
|
| 80 |
+
name_prefix: str = "",
|
| 81 |
+
max_colocate_count: int = 5,
|
| 82 |
+
detached: bool = False,
|
| 83 |
+
) -> None:
|
| 84 |
+
super().__init__(process_on_nodes, max_colocate_count)
|
| 85 |
+
self.use_gpu = use_gpu
|
| 86 |
+
# print(f"in RayProcessDispatchConfiguration: name_prefix = {name_prefix}")
|
| 87 |
+
self.name_prefix = name_prefix
|
| 88 |
+
self.pgs = None
|
| 89 |
+
self.detached = detached
|
| 90 |
+
|
| 91 |
+
def get_placement_groups(self, strategy: str = "STRICT_PACK", name: Optional[str] = None) -> List[PlacementGroup]:
|
| 92 |
+
if self.pgs is not None:
|
| 93 |
+
return self.pgs
|
| 94 |
+
|
| 95 |
+
pg_name_prefix = (
|
| 96 |
+
name if name else f"{self.name_prefix}verl_group_{'_'.join([str(count) for count in self._store])}:"
|
| 97 |
+
)
|
| 98 |
+
# print(f"pg_name_prefix = {pg_name_prefix}")
|
| 99 |
+
pg_scheme = [
|
| 100 |
+
[
|
| 101 |
+
{"CPU": self.max_colocate_count, "GPU": 1} if self.use_gpu else {"CPU": self.max_colocate_count}
|
| 102 |
+
for _ in range(process_count)
|
| 103 |
+
]
|
| 104 |
+
for process_count in self._store
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
lifetime = "detached" if self.detached else None
|
| 108 |
+
|
| 109 |
+
pgs = [
|
| 110 |
+
placement_group(bundles=bundles, strategy=strategy, name=pg_name_prefix + str(idx), lifetime=lifetime)
|
| 111 |
+
for idx, bundles in enumerate(pg_scheme)
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
ray.get([pg.ready() for pg in pgs])
|
| 115 |
+
|
| 116 |
+
self.pgs = pgs
|
| 117 |
+
return pgs
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def extract_pg_from_exist(
|
| 121 |
+
resource_pools: Dict[str, RayResourcePool], src_role_names: List[str], resource_pool: RayResourcePool
|
| 122 |
+
) -> List[PlacementGroup]:
|
| 123 |
+
src_pgs = [
|
| 124 |
+
pg
|
| 125 |
+
for role_name, resource_pool in resource_pools.items()
|
| 126 |
+
for pg in resource_pool.get_placement_groups()
|
| 127 |
+
if role_name in src_role_names
|
| 128 |
+
]
|
| 129 |
+
|
| 130 |
+
sorted_src_pgs = sorted(src_pgs, key=lambda pg: pg.bundle_count, reverse=True)
|
| 131 |
+
sorted_process_on_nodes = sorted([(val, idx) for idx, val in enumerate(resource_pool.store)], reverse=True)
|
| 132 |
+
|
| 133 |
+
unsorted_pgs: List[Tuple[int, PlacementGroup]] = []
|
| 134 |
+
searching_idx = 0
|
| 135 |
+
for request_process, original_idx in sorted_process_on_nodes:
|
| 136 |
+
assert searching_idx < len(sorted_src_pgs), f"no enough nodes for request: searching {searching_idx} th node"
|
| 137 |
+
assert request_process <= sorted_src_pgs[searching_idx].bundle_count, (
|
| 138 |
+
f"requesting {request_process} processes, bundle count cannot satisfy"
|
| 139 |
+
)
|
| 140 |
+
unsorted_pgs.append((original_idx, sorted_src_pgs[searching_idx]))
|
| 141 |
+
searching_idx += 1
|
| 142 |
+
|
| 143 |
+
return [pg for _, pg in sorted(unsorted_pgs)]
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def merge_resource_pool(rp1: RayResourcePool, rp2: RayResourcePool) -> RayResourcePool:
|
| 147 |
+
assert rp1.use_gpu == rp2.use_gpu, "Both RayResourcePool must either use_gpu or not"
|
| 148 |
+
assert rp1.max_colocate_count == rp2.max_colocate_count, (
|
| 149 |
+
"Both RayResourcePool must has the same max_colocate_count"
|
| 150 |
+
)
|
| 151 |
+
assert rp1.n_gpus_per_node == rp2.n_gpus_per_node, "Both RayResourcePool must has the same n_gpus_per_node"
|
| 152 |
+
assert rp1.detached == rp2.detached, "Detached ResourcePool cannot be merged with non-detached ResourcePool"
|
| 153 |
+
|
| 154 |
+
new_store = rp1.store + rp2.store
|
| 155 |
+
|
| 156 |
+
merged = RayResourcePool(new_store, rp1.use_gpu, f"{rp1.name_prefix}_{rp2.name_prefix}")
|
| 157 |
+
merged.pgs = rp1.get_placement_groups() + rp2.get_placement_groups()
|
| 158 |
+
|
| 159 |
+
return merged
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class RayClassWithInitArgs(ClassWithInitArgs):
|
| 163 |
+
def __init__(self, cls, *args, **kwargs) -> None:
|
| 164 |
+
# self._options = kwargs.pop('options', dict())
|
| 165 |
+
super().__init__(cls, *args, **kwargs)
|
| 166 |
+
self._options = {}
|
| 167 |
+
self._additional_resource = {}
|
| 168 |
+
|
| 169 |
+
def set_additional_resource(self, additional_resource):
|
| 170 |
+
self._additional_resource = additional_resource
|
| 171 |
+
|
| 172 |
+
def update_options(self, options: Dict):
|
| 173 |
+
self._options.update(options)
|
| 174 |
+
|
| 175 |
+
def __call__(
|
| 176 |
+
self,
|
| 177 |
+
placement_group: PlacementGroup,
|
| 178 |
+
placement_group_bundle_idx: int,
|
| 179 |
+
use_gpu: bool = True,
|
| 180 |
+
num_gpus: int = 1,
|
| 181 |
+
sharing_with: Worker = None,
|
| 182 |
+
) -> Any:
|
| 183 |
+
if sharing_with is not None:
|
| 184 |
+
target_node_id = ray.get(sharing_with.get_node_id.remote())
|
| 185 |
+
cuda_visible_devices = ray.get(sharing_with.get_cuda_visible_devices.remote())
|
| 186 |
+
options = {"scheduling_strategy": NodeAffinitySchedulingStrategy(node_id=target_node_id, soft=False)}
|
| 187 |
+
return self.cls.options(**options).remote(
|
| 188 |
+
*self.args, cuda_visible_devices=cuda_visible_devices, **self.kwargs
|
| 189 |
+
)
|
| 190 |
+
|
| 191 |
+
options = {
|
| 192 |
+
"scheduling_strategy": PlacementGroupSchedulingStrategy(
|
| 193 |
+
placement_group=placement_group, placement_group_bundle_index=placement_group_bundle_idx
|
| 194 |
+
)
|
| 195 |
+
}
|
| 196 |
+
options.update(self._options)
|
| 197 |
+
|
| 198 |
+
if use_gpu:
|
| 199 |
+
options["num_gpus"] = num_gpus
|
| 200 |
+
|
| 201 |
+
if len(self._additional_resource) > 1:
|
| 202 |
+
for k, v in self._additional_resource.items():
|
| 203 |
+
options[k] = v
|
| 204 |
+
|
| 205 |
+
# print("cls:", self.cls)
|
| 206 |
+
# print("args: ", self.args)
|
| 207 |
+
# print("kwargs: ", self.kwargs)
|
| 208 |
+
return self.cls.options(**options).remote(*self.args, **self.kwargs)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
class RayWorkerGroup(WorkerGroup):
|
| 212 |
+
def __init__(
|
| 213 |
+
self,
|
| 214 |
+
resource_pool: RayResourcePool = None,
|
| 215 |
+
ray_cls_with_init: RayClassWithInitArgs = None,
|
| 216 |
+
bin_pack: bool = True,
|
| 217 |
+
name_prefix: str = None,
|
| 218 |
+
detached: bool = False,
|
| 219 |
+
worker_names: List[str] = None,
|
| 220 |
+
**kwargs,
|
| 221 |
+
) -> None:
|
| 222 |
+
super().__init__(resource_pool=resource_pool, **kwargs)
|
| 223 |
+
self.ray_cls_with_init = ray_cls_with_init
|
| 224 |
+
self.name_prefix = get_random_string(length=6) if name_prefix is None else name_prefix
|
| 225 |
+
|
| 226 |
+
if worker_names is not None:
|
| 227 |
+
assert self._is_init_with_detached_workers
|
| 228 |
+
self._worker_names = worker_names
|
| 229 |
+
|
| 230 |
+
if self._is_init_with_detached_workers:
|
| 231 |
+
self._init_with_detached_workers(worker_names=worker_names)
|
| 232 |
+
else:
|
| 233 |
+
self._init_with_resource_pool(
|
| 234 |
+
resource_pool=resource_pool, ray_cls_with_init=ray_cls_with_init, bin_pack=bin_pack, detached=detached
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
if ray_cls_with_init is not None:
|
| 238 |
+
self._bind_worker_method(self.ray_cls_with_init.cls, func_generator)
|
| 239 |
+
|
| 240 |
+
def _is_worker_alive(self, worker: ActorHandle) -> bool:
|
| 241 |
+
worker_state_dict = get_actor(worker._actor_id.hex())
|
| 242 |
+
return worker_state_dict.get("state", "undefined") == "ALIVE" if worker_state_dict is not None else False
|
| 243 |
+
|
| 244 |
+
def _init_with_detached_workers(self, worker_names: List[str]) -> None:
|
| 245 |
+
workers = [ray.get_actor(name=name) for name in worker_names]
|
| 246 |
+
self._workers = workers
|
| 247 |
+
self._world_size = len(worker_names)
|
| 248 |
+
|
| 249 |
+
def _init_with_resource_pool(
|
| 250 |
+
self, resource_pool: RayResourcePool, ray_cls_with_init: RayClassWithInitArgs, bin_pack: bool, detached: bool
|
| 251 |
+
):
|
| 252 |
+
use_gpu = resource_pool.use_gpu
|
| 253 |
+
|
| 254 |
+
strategy = "PACK"
|
| 255 |
+
if bin_pack:
|
| 256 |
+
strategy = "STRICT_PACK"
|
| 257 |
+
|
| 258 |
+
pgs = resource_pool.get_placement_groups(strategy=strategy)
|
| 259 |
+
world_size = resource_pool.world_size
|
| 260 |
+
self._world_size = world_size
|
| 261 |
+
# cia.add_kwarg("_world_size", world_size)
|
| 262 |
+
num_gpus = 1 / resource_pool.max_colocate_count
|
| 263 |
+
|
| 264 |
+
rank = -1
|
| 265 |
+
local_world_size = resource_pool.store[0]
|
| 266 |
+
for pg_idx, pg in enumerate(sort_placement_group_by_node_ip(pgs)):
|
| 267 |
+
assert local_world_size <= pg.bundle_count, f"when generating for {self.name_prefix}, for the "
|
| 268 |
+
for local_rank in range(local_world_size):
|
| 269 |
+
rank += 1
|
| 270 |
+
|
| 271 |
+
# we pass in environment variable at option so that Worker can use environment variable to set
|
| 272 |
+
env_vars = {
|
| 273 |
+
"WORLD_SIZE": str(world_size),
|
| 274 |
+
"RANK": str(rank),
|
| 275 |
+
"WG_PREFIX": self.name_prefix,
|
| 276 |
+
"WG_BACKEND": "ray",
|
| 277 |
+
"RAY_LOCAL_WORLD_SIZE": str(local_world_size),
|
| 278 |
+
"RAY_LOCAL_RANK": str(local_rank),
|
| 279 |
+
}
|
| 280 |
+
if rank != 0:
|
| 281 |
+
env_vars["MASTER_ADDR"] = self._master_addr
|
| 282 |
+
env_vars["MASTER_PORT"] = self._master_port
|
| 283 |
+
|
| 284 |
+
cia_name = type(ray_cls_with_init.cls).__name__
|
| 285 |
+
match = re.search(r"ActorClass\(([^)]+)\)", cia_name) # ray.remote(Obj) -> "ActorClass(Obj)"
|
| 286 |
+
cia_name = match.group(1) if match else cia_name # "ActorClass(Obj)" -> "Obj"
|
| 287 |
+
name = f"{self.name_prefix}{cia_name}_{pg_idx}:{local_rank}" # e.g. Worker_2:5
|
| 288 |
+
|
| 289 |
+
ray_cls_with_init.update_options({"runtime_env": {"env_vars": env_vars}, "name": name})
|
| 290 |
+
|
| 291 |
+
if detached:
|
| 292 |
+
ray_cls_with_init.update_options({"lifetime": "detached"})
|
| 293 |
+
|
| 294 |
+
# create a worker
|
| 295 |
+
worker = ray_cls_with_init(
|
| 296 |
+
placement_group=pg, placement_group_bundle_idx=local_rank, use_gpu=use_gpu, num_gpus=num_gpus
|
| 297 |
+
)
|
| 298 |
+
self._workers.append(worker)
|
| 299 |
+
self._worker_names.append(name)
|
| 300 |
+
|
| 301 |
+
if rank == 0:
|
| 302 |
+
register_center_actor = None
|
| 303 |
+
for _ in range(120):
|
| 304 |
+
if f"{self.name_prefix}_register_center" not in list_named_actors():
|
| 305 |
+
time.sleep(1)
|
| 306 |
+
else:
|
| 307 |
+
register_center_actor = ray.get_actor(f"{self.name_prefix}_register_center")
|
| 308 |
+
break
|
| 309 |
+
assert register_center_actor is not None, (
|
| 310 |
+
f"failed to get register_center_actor: {self.name_prefix}_register_center in {list_named_actors(all_namespaces=True)}"
|
| 311 |
+
)
|
| 312 |
+
rank_zero_info = ray.get(register_center_actor.get_rank_zero_info.remote())
|
| 313 |
+
self._master_addr, self._master_port = rank_zero_info["MASTER_ADDR"], rank_zero_info["MASTER_PORT"]
|
| 314 |
+
# print(f"rank_zero_info: {rank_zero_info}")
|
| 315 |
+
# print(f"master_addr: {self._master_addr}, master_port: {self._master_port}")
|
| 316 |
+
|
| 317 |
+
@property
|
| 318 |
+
def worker_names(self):
|
| 319 |
+
return self._worker_names
|
| 320 |
+
|
| 321 |
+
@classmethod
|
| 322 |
+
def from_detached(cls, worker_names=None, ray_cls_with_init=None):
|
| 323 |
+
worker_group = cls(
|
| 324 |
+
resource_pool=None, ray_cls_with_init=ray_cls_with_init, name_prefix=None, worker_names=worker_names
|
| 325 |
+
)
|
| 326 |
+
return worker_group
|
| 327 |
+
|
| 328 |
+
def spawn(self, prefix_set):
|
| 329 |
+
"""
|
| 330 |
+
spawn to a dictionary of worker groups, each with a subset of method with prefix.
|
| 331 |
+
|
| 332 |
+
"""
|
| 333 |
+
|
| 334 |
+
def _rebind_actor_methods(worker_group, actor_name):
|
| 335 |
+
"""
|
| 336 |
+
bind the method with actor_prefix to its original name
|
| 337 |
+
"""
|
| 338 |
+
prefix: str = actor_name + "_"
|
| 339 |
+
for method_name in dir(worker_group):
|
| 340 |
+
if method_name.startswith(prefix):
|
| 341 |
+
# only valid when Python >= 3.9
|
| 342 |
+
original_method_name = method_name.removeprefix(prefix)
|
| 343 |
+
method = getattr(worker_group, method_name)
|
| 344 |
+
setattr(worker_group, original_method_name, method)
|
| 345 |
+
|
| 346 |
+
new_worker_group_dict = {}
|
| 347 |
+
for prefix in prefix_set:
|
| 348 |
+
new_worker_group = self.from_detached(
|
| 349 |
+
worker_names=self._worker_names, ray_cls_with_init=self.ray_cls_with_init
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
_rebind_actor_methods(new_worker_group, prefix)
|
| 353 |
+
new_worker_group_dict[prefix] = new_worker_group
|
| 354 |
+
return new_worker_group_dict
|
| 355 |
+
|
| 356 |
+
def execute_rank_zero_sync(self, method_name: str, *args, **kwargs):
|
| 357 |
+
return ray.get(self.execute_rank_zero_async(method_name, *args, **kwargs))
|
| 358 |
+
|
| 359 |
+
def execute_rank_zero_async(self, method_name: str, *args, **kwargs):
|
| 360 |
+
remote_call = getattr(self._workers[0], method_name)
|
| 361 |
+
return remote_call.remote(*args, **kwargs)
|
| 362 |
+
|
| 363 |
+
def execute_rank_zero(self, method_name: str, *args, **kwargs):
|
| 364 |
+
return self.execute_rank_zero_async(method_name, *args, **kwargs)
|
| 365 |
+
|
| 366 |
+
def execute_all(self, method_name: str, *args, **kwargs):
|
| 367 |
+
return self.execute_all_async(method_name, *args, **kwargs)
|
| 368 |
+
|
| 369 |
+
def execute_all_sync(self, method_name: str, *args, **kwargs):
|
| 370 |
+
return ray.get(self.execute_all_async(method_name, *args, **kwargs))
|
| 371 |
+
|
| 372 |
+
def execute_all_async(self, method_name: str, *args, **kwargs):
|
| 373 |
+
# Here we assume that if all the parameters in args and kwargs are lists,
|
| 374 |
+
# and the lengths of all these lists are the same as len(self._workers),
|
| 375 |
+
# then we will send each element in the list to the corresponding worker.
|
| 376 |
+
# print(f"execute_all_async: method {method_name}({args}, {kwargs})")
|
| 377 |
+
length = len(self._workers)
|
| 378 |
+
if all(isinstance(arg, list) for arg in args) and all(isinstance(kwarg, list) for kwarg in kwargs.values()):
|
| 379 |
+
if all(len(arg) == length for arg in args) and all(len(kwarg) == length for kwarg in kwargs.values()):
|
| 380 |
+
# print(f"splitting args and kwargs into {length} shards")
|
| 381 |
+
result = []
|
| 382 |
+
for i in range(length):
|
| 383 |
+
sliced_args = tuple(arg[i] for arg in args)
|
| 384 |
+
sliced_kwargs = {k: v[i] for k, v in kwargs.items()}
|
| 385 |
+
remote_call = getattr(self._workers[i], method_name)
|
| 386 |
+
result.append(remote_call.remote(*sliced_args, **sliced_kwargs))
|
| 387 |
+
return result
|
| 388 |
+
|
| 389 |
+
return [getattr(worker, method_name).remote(*args, **kwargs) for worker in self._workers]
|
| 390 |
+
|
| 391 |
+
@property
|
| 392 |
+
def master_address(self):
|
| 393 |
+
return self._master_addr
|
| 394 |
+
|
| 395 |
+
@property
|
| 396 |
+
def master_port(self):
|
| 397 |
+
return self._master_port
|
| 398 |
+
|
| 399 |
+
@property
|
| 400 |
+
def workers(self):
|
| 401 |
+
return self._workers
|
| 402 |
+
|
| 403 |
+
@property
|
| 404 |
+
def world_size(self):
|
| 405 |
+
return self._world_size
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
"""
|
| 409 |
+
Utilities that enables creating workers inside the same ray.Actor,
|
| 410 |
+
with code written in separate ray.Actors.
|
| 411 |
+
"""
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def _bind_workers_method_to_parent(cls, key, user_defined_cls):
|
| 415 |
+
"""
|
| 416 |
+
Binds the methods of each worker to the WorkerDict.
|
| 417 |
+
Note that we only bind public methods that are decorated by register
|
| 418 |
+
"""
|
| 419 |
+
for method_name in dir(user_defined_cls):
|
| 420 |
+
try:
|
| 421 |
+
method = getattr(user_defined_cls, method_name)
|
| 422 |
+
assert callable(method), f"{method_name} in {user_defined_cls} is not callable"
|
| 423 |
+
except Exception:
|
| 424 |
+
# if it is a property, it will fail because Class doesn't have instance property
|
| 425 |
+
continue
|
| 426 |
+
|
| 427 |
+
if hasattr(method, MAGIC_ATTR):
|
| 428 |
+
|
| 429 |
+
def generate_function(name):
|
| 430 |
+
def func(self, *args, **kwargs):
|
| 431 |
+
# dispatch to the actual worker
|
| 432 |
+
return getattr(self.worker_dict[key], name)(*args, **kwargs)
|
| 433 |
+
|
| 434 |
+
return func
|
| 435 |
+
|
| 436 |
+
func = generate_function(method_name)
|
| 437 |
+
# pass MAGIC_ATTR for outer worker group
|
| 438 |
+
setattr(func, MAGIC_ATTR, getattr(method, MAGIC_ATTR))
|
| 439 |
+
try:
|
| 440 |
+
method_name_with_prefix = key + "_" + method_name
|
| 441 |
+
setattr(cls, method_name_with_prefix, func)
|
| 442 |
+
# print(f'Binding {method_name_with_prefix}')
|
| 443 |
+
except Exception:
|
| 444 |
+
raise ValueError(f"Fail to set method_name {method_name}")
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
def _unwrap_ray_remote(cls):
|
| 448 |
+
if hasattr(cls, "__ray_actor_class__"):
|
| 449 |
+
cls = cls.__ray_actor_class__
|
| 450 |
+
return cls
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
def create_colocated_worker_cls(class_dict: dict[str, RayClassWithInitArgs]):
|
| 454 |
+
"""
|
| 455 |
+
This function should return a class instance that delegates the calls to every
|
| 456 |
+
cls in cls_dict
|
| 457 |
+
"""
|
| 458 |
+
cls_dict = {}
|
| 459 |
+
init_args_dict = {}
|
| 460 |
+
worker_cls = None
|
| 461 |
+
for key, cls in class_dict.items():
|
| 462 |
+
if worker_cls is None:
|
| 463 |
+
worker_cls = cls.cls.__ray_actor_class__.__base__
|
| 464 |
+
else:
|
| 465 |
+
assert worker_cls == cls.cls.__ray_actor_class__.__base__, (
|
| 466 |
+
"the worker class should be the same when share the same process"
|
| 467 |
+
)
|
| 468 |
+
cls_dict[key] = cls.cls
|
| 469 |
+
init_args_dict[key] = {"args": cls.args, "kwargs": cls.kwargs}
|
| 470 |
+
|
| 471 |
+
assert cls_dict.keys() == init_args_dict.keys()
|
| 472 |
+
|
| 473 |
+
# TODO: create a class with customizable name
|
| 474 |
+
class WorkerDict(worker_cls):
|
| 475 |
+
def __init__(self):
|
| 476 |
+
super().__init__()
|
| 477 |
+
self.worker_dict = {}
|
| 478 |
+
for key, user_defined_cls in cls_dict.items():
|
| 479 |
+
user_defined_cls = _unwrap_ray_remote(user_defined_cls)
|
| 480 |
+
# directly instantiate the class without remote
|
| 481 |
+
with patch.dict(os.environ, {"DISABLE_WORKER_INIT": "1"}):
|
| 482 |
+
self.worker_dict[key] = user_defined_cls(
|
| 483 |
+
*init_args_dict[key].get("args", ()), **init_args_dict[key].get("kwargs", {})
|
| 484 |
+
)
|
| 485 |
+
|
| 486 |
+
# now monkey-patch the methods from inner class to WorkerDict
|
| 487 |
+
for key, user_defined_cls in cls_dict.items():
|
| 488 |
+
user_defined_cls = _unwrap_ray_remote(user_defined_cls)
|
| 489 |
+
_bind_workers_method_to_parent(WorkerDict, key, user_defined_cls)
|
| 490 |
+
|
| 491 |
+
remote_cls = ray.remote(WorkerDict)
|
| 492 |
+
remote_cls = RayClassWithInitArgs(cls=remote_cls)
|
| 493 |
+
return remote_cls
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/config.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""
|
| 15 |
+
PPO config
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
from dataclasses import asdict, dataclass, field, fields, is_dataclass
|
| 20 |
+
from typing import Optional, Tuple
|
| 21 |
+
|
| 22 |
+
from ..workers.config import WorkerConfig
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def recursive_post_init(dataclass_obj):
|
| 26 |
+
if hasattr(dataclass_obj, "post_init"):
|
| 27 |
+
dataclass_obj.post_init()
|
| 28 |
+
|
| 29 |
+
for attr in fields(dataclass_obj):
|
| 30 |
+
if is_dataclass(getattr(dataclass_obj, attr.name)):
|
| 31 |
+
recursive_post_init(getattr(dataclass_obj, attr.name))
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclass
|
| 35 |
+
class DataConfig:
|
| 36 |
+
train_files: str = ""
|
| 37 |
+
val_files: str = ""
|
| 38 |
+
prompt_key: str = "prompt"
|
| 39 |
+
answer_key: str = "answer"
|
| 40 |
+
image_key: str = "images"
|
| 41 |
+
max_prompt_length: int = 512
|
| 42 |
+
max_response_length: int = 512
|
| 43 |
+
rollout_batch_size: int = 512
|
| 44 |
+
val_batch_size: int = -1
|
| 45 |
+
format_prompt: Optional[str] = None
|
| 46 |
+
override_chat_template: Optional[str] = None
|
| 47 |
+
shuffle: bool = True
|
| 48 |
+
seed: int = 1
|
| 49 |
+
max_pixels: int = 4194304
|
| 50 |
+
min_pixels: int = 262144
|
| 51 |
+
filter_overlong_prompts: bool = True
|
| 52 |
+
|
| 53 |
+
def post_init(self):
|
| 54 |
+
if self.format_prompt is not None:
|
| 55 |
+
if os.path.exists(self.format_prompt): # ray job uses absolute path
|
| 56 |
+
self.format_prompt = os.path.abspath(self.format_prompt)
|
| 57 |
+
else:
|
| 58 |
+
self.format_prompt = None
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@dataclass
|
| 62 |
+
class AlgorithmConfig:
|
| 63 |
+
gamma: float = 1.0
|
| 64 |
+
lam: float = 1.0
|
| 65 |
+
adv_estimator: str = "grpo"
|
| 66 |
+
disable_kl: bool = False
|
| 67 |
+
use_kl_loss: bool = False
|
| 68 |
+
kl_penalty: str = "kl"
|
| 69 |
+
kl_coef: float = 1e-3
|
| 70 |
+
kl_type: str = "fixed"
|
| 71 |
+
kl_horizon: float = 0.0
|
| 72 |
+
kl_target: float = 0.0
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@dataclass
|
| 76 |
+
class TrainerConfig:
|
| 77 |
+
total_epochs: int = 10
|
| 78 |
+
max_steps: Optional[int] = None
|
| 79 |
+
project_name: str = "easy_r1"
|
| 80 |
+
experiment_name: str = "demo"
|
| 81 |
+
logger: Tuple[str] = ("console", "wandb")
|
| 82 |
+
nnodes: int = 1
|
| 83 |
+
n_gpus_per_node: int = 8
|
| 84 |
+
critic_warmup: int = 0
|
| 85 |
+
val_freq: int = -1
|
| 86 |
+
val_before_train: bool = True
|
| 87 |
+
val_only: bool = False
|
| 88 |
+
val_generations_to_log: int = 0
|
| 89 |
+
save_freq: int = -1
|
| 90 |
+
save_limit: int = -1
|
| 91 |
+
save_checkpoint_path: Optional[str] = None
|
| 92 |
+
load_checkpoint_path: Optional[str] = None
|
| 93 |
+
|
| 94 |
+
def post_init(self):
|
| 95 |
+
if self.save_checkpoint_path is None:
|
| 96 |
+
self.save_checkpoint_path = os.path.join("checkpoints", self.project_name, self.experiment_name)
|
| 97 |
+
|
| 98 |
+
self.save_checkpoint_path = os.path.abspath(self.save_checkpoint_path) # ray job uses absolute path
|
| 99 |
+
if self.load_checkpoint_path is not None:
|
| 100 |
+
self.load_checkpoint_path = os.path.abspath(self.load_checkpoint_path)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
@dataclass
|
| 104 |
+
class PPOConfig:
|
| 105 |
+
data: DataConfig = field(default_factory=DataConfig)
|
| 106 |
+
worker: WorkerConfig = field(default_factory=WorkerConfig)
|
| 107 |
+
algorithm: AlgorithmConfig = field(default_factory=AlgorithmConfig)
|
| 108 |
+
trainer: TrainerConfig = field(default_factory=TrainerConfig)
|
| 109 |
+
|
| 110 |
+
def post_init(self):
|
| 111 |
+
self.worker.rollout.prompt_length = self.data.max_prompt_length
|
| 112 |
+
self.worker.rollout.response_length = self.data.max_response_length
|
| 113 |
+
self.worker.rollout.trust_remote_code = self.worker.actor.model.trust_remote_code
|
| 114 |
+
self.worker.actor.disable_kl = self.algorithm.disable_kl
|
| 115 |
+
self.worker.actor.use_kl_loss = self.algorithm.use_kl_loss
|
| 116 |
+
self.worker.actor.kl_penalty = self.algorithm.kl_penalty
|
| 117 |
+
self.worker.actor.kl_coef = self.algorithm.kl_coef
|
| 118 |
+
|
| 119 |
+
def deep_post_init(self):
|
| 120 |
+
recursive_post_init(self)
|
| 121 |
+
|
| 122 |
+
def to_dict(self):
|
| 123 |
+
return asdict(self)
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/core_algos.py
ADDED
|
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2022 The HuggingFace Team
|
| 2 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
"""
|
| 16 |
+
Core functions to implement PPO algorithms.
|
| 17 |
+
The function implemented in this file should be used by trainer with different distributed strategies to
|
| 18 |
+
implement PPO
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from abc import ABC, abstractmethod
|
| 22 |
+
from collections import defaultdict
|
| 23 |
+
from typing import TYPE_CHECKING, Tuple
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
import torch
|
| 27 |
+
import torch.nn.functional as F
|
| 28 |
+
|
| 29 |
+
from ..utils import torch_functional as VF
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
if TYPE_CHECKING:
|
| 33 |
+
from .config import AlgorithmConfig
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class KLController(ABC):
|
| 37 |
+
kl_coef: float
|
| 38 |
+
"""KL coefficient."""
|
| 39 |
+
|
| 40 |
+
@abstractmethod
|
| 41 |
+
def update(self, current_kl: float, n_steps: int) -> None:
|
| 42 |
+
"""Update kl_coef according to current KL."""
|
| 43 |
+
...
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class AdaptiveKLController(KLController):
|
| 47 |
+
"""Adaptive KL controller described in: https://arxiv.org/pdf/1909.08593.pdf
|
| 48 |
+
|
| 49 |
+
Copied from https://github.com/huggingface/trl/blob/v0.11.0/trl/trainer/utils.py#L54"""
|
| 50 |
+
|
| 51 |
+
def __init__(self, init_kl_coef: float, target_kl: float, horizon: float):
|
| 52 |
+
self.kl_coef = init_kl_coef
|
| 53 |
+
self.target = target_kl
|
| 54 |
+
self.horizon = horizon
|
| 55 |
+
|
| 56 |
+
def update(self, current_kl: float, n_steps: int) -> None:
|
| 57 |
+
target = self.target
|
| 58 |
+
proportional_error = np.clip(current_kl / target - 1, -0.2, 0.2)
|
| 59 |
+
mult = 1 + proportional_error * n_steps / self.horizon
|
| 60 |
+
self.kl_coef *= mult
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class FixedKLController(KLController):
|
| 64 |
+
"""Fixed KL controller.
|
| 65 |
+
|
| 66 |
+
Copeid from https://github.com/huggingface/trl/blob/v0.11.0/trl/trainer/utils.py#L72"""
|
| 67 |
+
|
| 68 |
+
def __init__(self, init_kl_coef: float):
|
| 69 |
+
self.kl_coef = init_kl_coef
|
| 70 |
+
|
| 71 |
+
def update(self, current_kl: float, n_steps: int) -> None:
|
| 72 |
+
pass
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def get_kl_controller(algorithm_config: "AlgorithmConfig") -> KLController:
|
| 76 |
+
"""Adapted from https://github.com/huggingface/trl/blob/v0.11.0/trl/trainer/ppo_trainer.py#L319"""
|
| 77 |
+
if algorithm_config.kl_type == "fixed":
|
| 78 |
+
kl_ctrl = FixedKLController(init_kl_coef=algorithm_config.kl_coef)
|
| 79 |
+
elif algorithm_config.kl_type == "adaptive":
|
| 80 |
+
assert algorithm_config.kl_horizon > 0, f"horizon must be larger than 0. Got {algorithm_config.kl_horizon}."
|
| 81 |
+
kl_ctrl = AdaptiveKLController(
|
| 82 |
+
init_kl_coef=algorithm_config.kl_coef,
|
| 83 |
+
target_kl=algorithm_config.kl_target,
|
| 84 |
+
horizon=algorithm_config.kl_horizon,
|
| 85 |
+
)
|
| 86 |
+
else:
|
| 87 |
+
raise ValueError(f"Unknown kl type: {algorithm_config.kl_type}.")
|
| 88 |
+
|
| 89 |
+
return kl_ctrl
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@torch.no_grad()
|
| 93 |
+
def compute_gae_advantage_return(
|
| 94 |
+
token_level_rewards: torch.Tensor,
|
| 95 |
+
values: torch.Tensor,
|
| 96 |
+
response_mask: torch.Tensor,
|
| 97 |
+
gamma: torch.Tensor,
|
| 98 |
+
lam: torch.Tensor,
|
| 99 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 100 |
+
"""Adapted from https://github.com/huggingface/trl/blob/v0.16.0/trl/trainer/ppo_trainer.py#L513
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
token_level_rewards: `(torch.Tensor)`
|
| 104 |
+
shape: (bs, response_length)
|
| 105 |
+
values: `(torch.Tensor)`
|
| 106 |
+
shape: (bs, response_length)
|
| 107 |
+
response_mask: `(torch.Tensor)`
|
| 108 |
+
shape: (bs, response_length). The token after eos tokens have mask zero.
|
| 109 |
+
gamma: `(float)`
|
| 110 |
+
discounted factor used in RL
|
| 111 |
+
lam: `(float)`
|
| 112 |
+
lambda value when computing Generalized Advantage Estimation (https://arxiv.org/abs/1506.02438)
|
| 113 |
+
|
| 114 |
+
Returns:
|
| 115 |
+
advantages: `(torch.Tensor)`
|
| 116 |
+
shape: (bs, response_length)
|
| 117 |
+
returns: `(torch.Tensor)`
|
| 118 |
+
shape: (bs, response_length)
|
| 119 |
+
|
| 120 |
+
"""
|
| 121 |
+
lastgaelam = 0
|
| 122 |
+
advantages_reversed = []
|
| 123 |
+
gen_len = token_level_rewards.shape[-1]
|
| 124 |
+
for t in reversed(range(gen_len)):
|
| 125 |
+
nextvalues = values[:, t + 1] if t < gen_len - 1 else 0.0
|
| 126 |
+
delta = token_level_rewards[:, t] + gamma * nextvalues - values[:, t]
|
| 127 |
+
lastgaelam = delta + gamma * lam * lastgaelam
|
| 128 |
+
advantages_reversed.append(lastgaelam)
|
| 129 |
+
|
| 130 |
+
advantages = torch.stack(advantages_reversed[::-1], dim=1)
|
| 131 |
+
returns = advantages + values
|
| 132 |
+
advantages = VF.masked_whiten(advantages, response_mask)
|
| 133 |
+
return advantages, returns
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# NOTE(sgm): this implementation only consider outcome supervision, where the reward is a scalar.
|
| 137 |
+
@torch.no_grad()
|
| 138 |
+
def compute_grpo_outcome_advantage(
|
| 139 |
+
token_level_rewards: torch.Tensor, response_mask: torch.Tensor, index: torch.Tensor, eps: float = 1e-6
|
| 140 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 141 |
+
"""
|
| 142 |
+
Compute advantage for GRPO, operating only on Outcome reward
|
| 143 |
+
(with only one scalar reward for each response).
|
| 144 |
+
|
| 145 |
+
Args:
|
| 146 |
+
token_level_rewards: `(torch.Tensor)`
|
| 147 |
+
shape: (bs, response_length)
|
| 148 |
+
response_mask: `(torch.Tensor)`
|
| 149 |
+
shape: (bs, response_length)
|
| 150 |
+
|
| 151 |
+
Returns:
|
| 152 |
+
advantages: `(torch.Tensor)`
|
| 153 |
+
shape: (bs, response_length)
|
| 154 |
+
returns: `(torch.Tensor)`
|
| 155 |
+
shape: (bs, response_length)
|
| 156 |
+
|
| 157 |
+
"""
|
| 158 |
+
scores = token_level_rewards.sum(dim=-1)
|
| 159 |
+
id2score = defaultdict(list)
|
| 160 |
+
id2mean, id2std = {}, {}
|
| 161 |
+
|
| 162 |
+
bsz = scores.shape[0]
|
| 163 |
+
for i in range(bsz):
|
| 164 |
+
id2score[index[i]].append(scores[i])
|
| 165 |
+
|
| 166 |
+
for idx in id2score:
|
| 167 |
+
assert len(id2score[idx]) > 1, "GRPO needs rollout.n > 1."
|
| 168 |
+
id2mean[idx] = torch.mean(torch.tensor(id2score[idx]))
|
| 169 |
+
id2std[idx] = torch.std(torch.tensor(id2score[idx]))
|
| 170 |
+
|
| 171 |
+
for i in range(bsz):
|
| 172 |
+
scores[i] = (scores[i] - id2mean[index[i]]) / (id2std[index[i]] + eps)
|
| 173 |
+
|
| 174 |
+
returns = scores.unsqueeze(-1) * response_mask
|
| 175 |
+
return returns, returns
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
@torch.no_grad()
|
| 179 |
+
def compute_rloo_outcome_advantage(
|
| 180 |
+
token_level_rewards: torch.Tensor, response_mask: torch.Tensor, index: torch.Tensor
|
| 181 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 182 |
+
"""
|
| 183 |
+
Compute advantage for RLOO based on https://arxiv.org/abs/2402.14740
|
| 184 |
+
|
| 185 |
+
Args:
|
| 186 |
+
token_level_rewards: `(torch.Tensor)`
|
| 187 |
+
shape: (bs, response_length)
|
| 188 |
+
response_mask: `(torch.Tensor)`
|
| 189 |
+
shape: (bs, response_length)
|
| 190 |
+
|
| 191 |
+
Returns:
|
| 192 |
+
advantages: `(torch.Tensor)`
|
| 193 |
+
shape: (bs, response_length)
|
| 194 |
+
returns: `(torch.Tensor)`
|
| 195 |
+
shape: (bs, response_length)
|
| 196 |
+
|
| 197 |
+
"""
|
| 198 |
+
scores = token_level_rewards.sum(dim=-1)
|
| 199 |
+
|
| 200 |
+
id2score = defaultdict(list)
|
| 201 |
+
id2sum = {}
|
| 202 |
+
bsz = scores.shape[0]
|
| 203 |
+
for i in range(bsz):
|
| 204 |
+
id2score[index[i]].append(scores[i])
|
| 205 |
+
|
| 206 |
+
for idx in id2score:
|
| 207 |
+
id2sum[idx] = torch.sum(torch.tensor(id2score[idx]))
|
| 208 |
+
|
| 209 |
+
for i in range(bsz):
|
| 210 |
+
sample_num = len(id2score[index[i]])
|
| 211 |
+
assert sample_num > 1, "RLOO needs rollout.n > 1."
|
| 212 |
+
baseline = (id2sum[index[i]] - scores[i]) / (sample_num - 1)
|
| 213 |
+
scores[i] = scores[i] - baseline
|
| 214 |
+
|
| 215 |
+
returns = scores.unsqueeze(-1) * response_mask
|
| 216 |
+
return returns, returns
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
@torch.no_grad()
|
| 220 |
+
def compute_reinforce_plus_plus_outcome_advantage(
|
| 221 |
+
token_level_rewards: torch.Tensor, response_mask: torch.Tensor, gamma: torch.Tensor
|
| 222 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 223 |
+
"""
|
| 224 |
+
Compute advantage for REINFORCE++.
|
| 225 |
+
This implementation is based on the paper: https://arxiv.org/abs/2501.03262
|
| 226 |
+
|
| 227 |
+
Args:
|
| 228 |
+
token_level_rewards: `(torch.Tensor)`
|
| 229 |
+
shape: (bs, response_length)
|
| 230 |
+
response_mask: `(torch.Tensor)`
|
| 231 |
+
shape: (bs, response_length)
|
| 232 |
+
|
| 233 |
+
Returns:
|
| 234 |
+
advantages: `(torch.Tensor)`
|
| 235 |
+
shape: (bs, response_length)
|
| 236 |
+
returns: `(torch.Tensor)`
|
| 237 |
+
shape: (bs, response_length)
|
| 238 |
+
|
| 239 |
+
"""
|
| 240 |
+
returns = torch.zeros_like(token_level_rewards)
|
| 241 |
+
running_return = 0
|
| 242 |
+
for t in reversed(range(token_level_rewards.shape[1])):
|
| 243 |
+
running_return = token_level_rewards[:, t] + gamma * running_return
|
| 244 |
+
returns[:, t] = running_return
|
| 245 |
+
# Reset after EOS
|
| 246 |
+
running_return = running_return * response_mask[:, t]
|
| 247 |
+
|
| 248 |
+
advantages = VF.masked_whiten(returns, response_mask)
|
| 249 |
+
return advantages, returns
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
@torch.no_grad()
|
| 253 |
+
def compute_remax_outcome_advantage(
|
| 254 |
+
token_level_rewards: torch.Tensor, reward_baselines: torch.Tensor, response_mask: torch.Tensor
|
| 255 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 256 |
+
"""
|
| 257 |
+
Compute advantage for ReMax, operating only on Outcome reward
|
| 258 |
+
This implementation is based on the paper: https://arxiv.org/abs/2310.10505
|
| 259 |
+
|
| 260 |
+
(with only one scalar reward for each response).
|
| 261 |
+
Args:
|
| 262 |
+
token_level_rewards: `(torch.Tensor)`
|
| 263 |
+
shape: (bs, response_length)
|
| 264 |
+
reward_baselines: `(torch.Tensor)`
|
| 265 |
+
shape: (bs,)
|
| 266 |
+
response_mask: `(torch.Tensor)`
|
| 267 |
+
shape: (bs, response_length)
|
| 268 |
+
|
| 269 |
+
Returns:
|
| 270 |
+
advantages: `(torch.Tensor)`
|
| 271 |
+
shape: (bs, response_length)
|
| 272 |
+
returns: `(torch.Tensor)`
|
| 273 |
+
shape: (bs, response_length)
|
| 274 |
+
|
| 275 |
+
"""
|
| 276 |
+
scores = token_level_rewards.sum(dim=-1) - reward_baselines
|
| 277 |
+
returns = scores.unsqueeze(-1) * response_mask
|
| 278 |
+
return returns, returns
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def compute_rewards(
|
| 282 |
+
token_level_scores: torch.Tensor,
|
| 283 |
+
log_probs: torch.Tensor,
|
| 284 |
+
ref_log_probs: torch.Tensor,
|
| 285 |
+
kl_ratio: float,
|
| 286 |
+
) -> torch.Tensor:
|
| 287 |
+
kl = log_probs - ref_log_probs
|
| 288 |
+
return token_level_scores - kl * kl_ratio
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def compute_policy_loss(
|
| 292 |
+
old_log_probs: torch.Tensor,
|
| 293 |
+
log_probs: torch.Tensor,
|
| 294 |
+
advantages: torch.Tensor,
|
| 295 |
+
response_mask: torch.Tensor,
|
| 296 |
+
clip_ratio_low: float,
|
| 297 |
+
clip_ratio_high: float,
|
| 298 |
+
clip_ratio_dual: float,
|
| 299 |
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 300 |
+
"""Compute the policy loss.
|
| 301 |
+
|
| 302 |
+
Adapted from https://github.com/huggingface/trl/blob/v0.15.0/trl/trainer/ppo_trainer.py#L568
|
| 303 |
+
|
| 304 |
+
Args:
|
| 305 |
+
old_log_prob: `(torch.Tensor)`
|
| 306 |
+
shape: (bs, response_length)
|
| 307 |
+
log_prob: `(torch.Tensor)`
|
| 308 |
+
shape: (bs, response_length)
|
| 309 |
+
advantages: `(torch.Tensor)`
|
| 310 |
+
shape: (bs, response_length)
|
| 311 |
+
response_mask: `(torch.Tensor)`
|
| 312 |
+
shape: (bs, response_length)
|
| 313 |
+
clip_ratio_low: (float)
|
| 314 |
+
The lower clip range used in PPO. See https://arxiv.org/abs/1707.06347
|
| 315 |
+
clip_ratio_high: (float)
|
| 316 |
+
The higher clip range used in DAPO. See https://arxiv.org/pdf/2503.14476
|
| 317 |
+
clip_ratio_dual: (float)
|
| 318 |
+
The dual clip range used in Dual-clip PPO. See https://arxiv.org/pdf/1912.09729
|
| 319 |
+
|
| 320 |
+
Returns:
|
| 321 |
+
pg_loss: `a scalar torch.Tensor`
|
| 322 |
+
policy gradient loss computed via PPO
|
| 323 |
+
pg_clipfrac_higher: (float)
|
| 324 |
+
a float number indicating the fraction of policy gradient loss being clipped to a higher value
|
| 325 |
+
pg_clipfrac_lower: (float)
|
| 326 |
+
a float number indicating the fraction of policy gradient loss being clipped to a lower value
|
| 327 |
+
ppo_kl: (float)
|
| 328 |
+
a float number indicating the mean KL divergence between the old policy and the new policy
|
| 329 |
+
|
| 330 |
+
"""
|
| 331 |
+
negative_approx_kl = log_probs - old_log_probs
|
| 332 |
+
# clamp the ratio before exp to avoid nan
|
| 333 |
+
# see: https://github.com/pytorch/pytorch/issues/10729
|
| 334 |
+
ratio = torch.exp(negative_approx_kl)
|
| 335 |
+
clipped_ratio = torch.exp(
|
| 336 |
+
torch.clamp(negative_approx_kl, np.log(1.0 - clip_ratio_low), np.log(1.0 + clip_ratio_high))
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
pg_loss = -advantages * ratio
|
| 340 |
+
pg_loss2 = -advantages * clipped_ratio
|
| 341 |
+
pg_loss3 = -advantages * clip_ratio_dual
|
| 342 |
+
|
| 343 |
+
clipped_pg_loss_higher = torch.max(pg_loss, pg_loss2) # clip if pg_loss < pg_loss2
|
| 344 |
+
pg_clipfrac_higher = (pg_loss < pg_loss2).float()
|
| 345 |
+
clipped_pg_loss_lower = torch.min(clipped_pg_loss_higher, pg_loss3) # clip if pg_loss > pg_loss3 and adv < 0
|
| 346 |
+
final_pg_loss = torch.where(advantages < 0, clipped_pg_loss_lower, clipped_pg_loss_higher)
|
| 347 |
+
pg_clipfrac_lower = (clipped_pg_loss_higher > pg_loss3).float() * (advantages < 0).float()
|
| 348 |
+
|
| 349 |
+
final_pg_loss = VF.masked_mean(final_pg_loss, response_mask)
|
| 350 |
+
pg_clipfrac_higher = VF.masked_mean(pg_clipfrac_higher, response_mask)
|
| 351 |
+
pg_clipfrac_lower = VF.masked_mean(pg_clipfrac_lower, response_mask)
|
| 352 |
+
ppo_kl = VF.masked_mean(-negative_approx_kl, response_mask)
|
| 353 |
+
return final_pg_loss, pg_clipfrac_higher, pg_clipfrac_lower, ppo_kl
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
def compute_value_loss(
|
| 357 |
+
vpreds: torch.Tensor,
|
| 358 |
+
returns: torch.Tensor,
|
| 359 |
+
values: torch.Tensor,
|
| 360 |
+
action_mask: torch.Tensor,
|
| 361 |
+
cliprange_value: float,
|
| 362 |
+
) -> Tuple[torch.Tensor, float]:
|
| 363 |
+
"""Compute the value loss.
|
| 364 |
+
|
| 365 |
+
Adapted from https://github.com/huggingface/trl/blob/v0.15.0/trl/trainer/ppo_trainer.py#L556
|
| 366 |
+
|
| 367 |
+
Args:
|
| 368 |
+
vpreds (`torch.FloatTensor`):
|
| 369 |
+
Predicted values of the value head, shape (`batch_size`, `response_length`)
|
| 370 |
+
returns: (`torch.FloatTensor`):
|
| 371 |
+
Ground truth returns, shape (`batch_size`, `response_length`)
|
| 372 |
+
values (`torch.FloatTensor`):
|
| 373 |
+
Old values of value head, shape (`batch_size`, `response_length`)
|
| 374 |
+
action_mask: `(torch.Tensor)`
|
| 375 |
+
shape: (bs, response_length)
|
| 376 |
+
cliprange_value: (float)
|
| 377 |
+
The clip range for value net used in PPO. See https://arxiv.org/abs/1707.06347
|
| 378 |
+
|
| 379 |
+
Returns:
|
| 380 |
+
vf_loss: a scalar (`torch.FloatTensor`):
|
| 381 |
+
value function loss
|
| 382 |
+
vf_clipfrac: a float
|
| 383 |
+
The ratio of vf being clipped
|
| 384 |
+
|
| 385 |
+
"""
|
| 386 |
+
vpredclipped = torch.clamp(vpreds, values - cliprange_value, values + cliprange_value)
|
| 387 |
+
vf_loss1 = torch.square(vpreds - returns)
|
| 388 |
+
vf_loss2 = torch.square(vpredclipped - returns)
|
| 389 |
+
vf_loss = 0.5 * VF.masked_mean(torch.max(vf_loss1, vf_loss2), action_mask) # clip if vf_loss1 < vf_loss2
|
| 390 |
+
vf_clipfrac = VF.masked_mean((vf_loss1 < vf_loss2).float(), action_mask)
|
| 391 |
+
return vf_loss, vf_clipfrac
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def compute_kl(log_probs: torch.FloatTensor, ref_log_probs: torch.FloatTensor, kl_penalty: str) -> torch.Tensor:
|
| 395 |
+
"""Compute KL divergence given log_probs and ref_log_probs.
|
| 396 |
+
|
| 397 |
+
Adapted from https://github.com/huggingface/trl/blob/v0.11.0/trl/trainer/ppo_trainer.py#L1150
|
| 398 |
+
|
| 399 |
+
Args:
|
| 400 |
+
log_probs: torch.Tensor
|
| 401 |
+
ref_log_probs: torch.Tensor
|
| 402 |
+
kl_penalty: str
|
| 403 |
+
|
| 404 |
+
Returns:
|
| 405 |
+
kl_div: torch.Tensor
|
| 406 |
+
|
| 407 |
+
"""
|
| 408 |
+
log_probs, ref_log_probs = log_probs.float(), ref_log_probs.float()
|
| 409 |
+
if kl_penalty == "kl":
|
| 410 |
+
return log_probs - ref_log_probs
|
| 411 |
+
|
| 412 |
+
if kl_penalty == "abs":
|
| 413 |
+
return (log_probs - ref_log_probs).abs()
|
| 414 |
+
|
| 415 |
+
if kl_penalty == "mse":
|
| 416 |
+
return 0.5 * (log_probs - ref_log_probs).square()
|
| 417 |
+
|
| 418 |
+
# J. Schulman. Approximating kl divergence, 2020.
|
| 419 |
+
# URL http://joschu.net/blog/kl-approx.html
|
| 420 |
+
if kl_penalty == "low_var_kl":
|
| 421 |
+
kl = ref_log_probs - log_probs
|
| 422 |
+
kld = (kl.exp() - kl - 1).contiguous()
|
| 423 |
+
return torch.clamp(kld, min=-10, max=10)
|
| 424 |
+
|
| 425 |
+
if kl_penalty == "full":
|
| 426 |
+
return F.kl_div(ref_log_probs, log_probs, log_target=True, reduction="none").sum(-1)
|
| 427 |
+
|
| 428 |
+
raise NotImplementedError(f"Unknown KL penalty: {kl_penalty}.")
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/data_loader.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from typing import Optional
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
from torch.utils.data import RandomSampler, SequentialSampler
|
| 19 |
+
from torchdata.stateful_dataloader import StatefulDataLoader
|
| 20 |
+
from transformers import PreTrainedTokenizer, ProcessorMixin
|
| 21 |
+
|
| 22 |
+
from ..utils.dataset import RLHFDataset, collate_fn
|
| 23 |
+
from .config import DataConfig
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def create_dataloader(config: DataConfig, tokenizer: PreTrainedTokenizer, processor: Optional[ProcessorMixin]) -> None:
|
| 27 |
+
train_dataset = RLHFDataset(
|
| 28 |
+
data_path=config.train_files,
|
| 29 |
+
tokenizer=tokenizer,
|
| 30 |
+
processor=processor,
|
| 31 |
+
prompt_key=config.prompt_key,
|
| 32 |
+
answer_key=config.answer_key,
|
| 33 |
+
image_key=config.image_key,
|
| 34 |
+
max_prompt_length=config.max_prompt_length,
|
| 35 |
+
truncation="right",
|
| 36 |
+
format_prompt=config.format_prompt,
|
| 37 |
+
min_pixels=config.min_pixels,
|
| 38 |
+
max_pixels=config.max_pixels,
|
| 39 |
+
filter_overlong_prompts=config.filter_overlong_prompts,
|
| 40 |
+
)
|
| 41 |
+
# use sampler for better ckpt resume
|
| 42 |
+
if config.shuffle:
|
| 43 |
+
train_dataloader_generator = torch.Generator()
|
| 44 |
+
train_dataloader_generator.manual_seed(config.seed)
|
| 45 |
+
print("train_dataset size:", len(train_dataset))
|
| 46 |
+
sampler = RandomSampler(data_source=train_dataset, generator=train_dataloader_generator)
|
| 47 |
+
else:
|
| 48 |
+
sampler = SequentialSampler(data_source=train_dataset)
|
| 49 |
+
|
| 50 |
+
train_dataloader = StatefulDataLoader(
|
| 51 |
+
dataset=train_dataset,
|
| 52 |
+
batch_size=config.rollout_batch_size,
|
| 53 |
+
sampler=sampler,
|
| 54 |
+
num_workers=8,
|
| 55 |
+
collate_fn=collate_fn,
|
| 56 |
+
pin_memory=False,
|
| 57 |
+
drop_last=True,
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
val_dataset = RLHFDataset(
|
| 61 |
+
data_path=config.val_files,
|
| 62 |
+
tokenizer=tokenizer,
|
| 63 |
+
processor=processor,
|
| 64 |
+
prompt_key=config.prompt_key,
|
| 65 |
+
answer_key=config.answer_key,
|
| 66 |
+
image_key=config.image_key,
|
| 67 |
+
max_prompt_length=config.max_prompt_length,
|
| 68 |
+
truncation="right",
|
| 69 |
+
format_prompt=config.format_prompt,
|
| 70 |
+
min_pixels=config.min_pixels,
|
| 71 |
+
max_pixels=config.max_pixels,
|
| 72 |
+
filter_overlong_prompts=config.filter_overlong_prompts,
|
| 73 |
+
)
|
| 74 |
+
val_dataloader = StatefulDataLoader(
|
| 75 |
+
dataset=val_dataset,
|
| 76 |
+
batch_size=len(val_dataset) if config.val_batch_size == -1 else config.val_batch_size,
|
| 77 |
+
shuffle=False,
|
| 78 |
+
num_workers=8,
|
| 79 |
+
collate_fn=collate_fn,
|
| 80 |
+
pin_memory=False,
|
| 81 |
+
drop_last=False,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
assert len(train_dataloader) >= 1
|
| 85 |
+
assert len(val_dataloader) >= 1
|
| 86 |
+
print(f"Size of train dataloader: {len(train_dataloader)}")
|
| 87 |
+
print(f"Size of val dataloader: {len(val_dataloader)}")
|
| 88 |
+
return train_dataloader, val_dataloader
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/main.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
|
| 17 |
+
import ray
|
| 18 |
+
from omegaconf import OmegaConf
|
| 19 |
+
|
| 20 |
+
from ..single_controller.ray import RayWorkerGroup
|
| 21 |
+
from ..utils.tokenizer import get_processor, get_tokenizer
|
| 22 |
+
from ..workers.fsdp_workers import FSDPWorker
|
| 23 |
+
from ..workers.reward import BatchFunctionRewardManager, SequentialFunctionRewardManager
|
| 24 |
+
from .config import PPOConfig
|
| 25 |
+
from .data_loader import create_dataloader
|
| 26 |
+
from .ray_trainer import RayPPOTrainer, ResourcePoolManager, Role
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# please make sure main_task is not scheduled on head
|
| 30 |
+
@ray.remote(num_cpus=1)
|
| 31 |
+
class Runner:
|
| 32 |
+
"""A runner for RL training."""
|
| 33 |
+
|
| 34 |
+
def run(self, config: PPOConfig):
|
| 35 |
+
# print config
|
| 36 |
+
print(json.dumps(config.to_dict(), indent=2))
|
| 37 |
+
|
| 38 |
+
# instantiate tokenizer
|
| 39 |
+
tokenizer = get_tokenizer(
|
| 40 |
+
config.worker.actor.model.model_path,
|
| 41 |
+
override_chat_template=config.data.override_chat_template,
|
| 42 |
+
trust_remote_code=config.worker.actor.model.trust_remote_code,
|
| 43 |
+
use_fast=True,
|
| 44 |
+
)
|
| 45 |
+
processor = get_processor(
|
| 46 |
+
config.worker.actor.model.model_path,
|
| 47 |
+
override_chat_template=config.data.override_chat_template,
|
| 48 |
+
trust_remote_code=config.worker.actor.model.trust_remote_code,
|
| 49 |
+
use_fast=True,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
# define worker classes
|
| 53 |
+
ray_worker_group_cls = RayWorkerGroup
|
| 54 |
+
role_worker_mapping = {
|
| 55 |
+
Role.ActorRollout: ray.remote(FSDPWorker),
|
| 56 |
+
Role.Critic: ray.remote(FSDPWorker),
|
| 57 |
+
Role.RefPolicy: ray.remote(FSDPWorker),
|
| 58 |
+
}
|
| 59 |
+
global_pool_id = "global_pool"
|
| 60 |
+
resource_pool_spec = {
|
| 61 |
+
global_pool_id: [config.trainer.n_gpus_per_node] * config.trainer.nnodes,
|
| 62 |
+
}
|
| 63 |
+
mapping = {
|
| 64 |
+
Role.ActorRollout: global_pool_id,
|
| 65 |
+
Role.Critic: global_pool_id,
|
| 66 |
+
Role.RefPolicy: global_pool_id,
|
| 67 |
+
}
|
| 68 |
+
resource_pool_manager = ResourcePoolManager(resource_pool_spec=resource_pool_spec, mapping=mapping)
|
| 69 |
+
|
| 70 |
+
if config.worker.reward.reward_type == "sequential":
|
| 71 |
+
RewardManager = SequentialFunctionRewardManager
|
| 72 |
+
elif config.worker.reward.reward_type == "batch":
|
| 73 |
+
RewardManager = BatchFunctionRewardManager
|
| 74 |
+
else:
|
| 75 |
+
raise NotImplementedError(f"Unknown reward type {config.worker.reward.reward_type}.")
|
| 76 |
+
|
| 77 |
+
RemoteRewardManager = ray.remote(RewardManager).options(num_cpus=config.worker.reward.num_cpus)
|
| 78 |
+
reward_fn = RemoteRewardManager.remote(config.worker.reward, tokenizer)
|
| 79 |
+
val_reward_fn = RemoteRewardManager.remote(config.worker.reward, tokenizer)
|
| 80 |
+
|
| 81 |
+
train_dataloader, val_dataloader = create_dataloader(config.data, tokenizer, processor)
|
| 82 |
+
|
| 83 |
+
trainer = RayPPOTrainer(
|
| 84 |
+
config=config,
|
| 85 |
+
tokenizer=tokenizer,
|
| 86 |
+
processor=processor,
|
| 87 |
+
train_dataloader=train_dataloader,
|
| 88 |
+
val_dataloader=val_dataloader,
|
| 89 |
+
role_worker_mapping=role_worker_mapping,
|
| 90 |
+
resource_pool_manager=resource_pool_manager,
|
| 91 |
+
ray_worker_group_cls=ray_worker_group_cls,
|
| 92 |
+
reward_fn=reward_fn,
|
| 93 |
+
val_reward_fn=val_reward_fn,
|
| 94 |
+
)
|
| 95 |
+
trainer.init_workers()
|
| 96 |
+
trainer.fit()
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def main():
|
| 100 |
+
cli_args = OmegaConf.from_cli()
|
| 101 |
+
default_config = OmegaConf.structured(PPOConfig())
|
| 102 |
+
|
| 103 |
+
if hasattr(cli_args, "config"):
|
| 104 |
+
config_path = cli_args.pop("config", None)
|
| 105 |
+
file_config = OmegaConf.load(config_path)
|
| 106 |
+
default_config = OmegaConf.merge(default_config, file_config)
|
| 107 |
+
|
| 108 |
+
ppo_config = OmegaConf.merge(default_config, cli_args)
|
| 109 |
+
ppo_config: PPOConfig = OmegaConf.to_object(ppo_config)
|
| 110 |
+
ppo_config.deep_post_init()
|
| 111 |
+
|
| 112 |
+
if not ray.is_initialized():
|
| 113 |
+
runtime_env = {
|
| 114 |
+
"env_vars": {
|
| 115 |
+
"TOKENIZERS_PARALLELISM": "true",
|
| 116 |
+
"NCCL_DEBUG": "WARN",
|
| 117 |
+
"VLLM_LOGGING_LEVEL": "WARN",
|
| 118 |
+
"TORCH_NCCL_AVOID_RECORD_STREAMS": "1",
|
| 119 |
+
"PYTORCH_CUDA_ALLOC_CONF": "expandable_segments:False",
|
| 120 |
+
"PYTHONUNBUFFERED": "1",
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
ray.init(runtime_env=runtime_env)
|
| 124 |
+
|
| 125 |
+
runner = Runner.remote()
|
| 126 |
+
ray.get(runner.run.remote(ppo_config))
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
if __name__ == "__main__":
|
| 130 |
+
main()
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/metrics.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from typing import Any, Dict, List
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from ..protocol import DataProto
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def reduce_metrics(metrics: Dict[str, List[Any]]) -> Dict[str, Any]:
|
| 24 |
+
return {key: np.mean(value) for key, value in metrics.items()}
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def compute_data_metrics(batch: DataProto, use_critic: bool = False) -> Dict[str, Any]:
|
| 28 |
+
sequence_score = batch.batch["token_level_scores"].sum(-1)
|
| 29 |
+
sequence_reward = batch.batch["token_level_rewards"].sum(-1)
|
| 30 |
+
|
| 31 |
+
advantages = batch.batch["advantages"]
|
| 32 |
+
returns = batch.batch["returns"]
|
| 33 |
+
|
| 34 |
+
max_response_length = batch.batch["responses"].size(-1)
|
| 35 |
+
|
| 36 |
+
prompt_mask = batch.batch["attention_mask"][:, :-max_response_length].bool()
|
| 37 |
+
response_mask = batch.batch["attention_mask"][:, -max_response_length:].bool()
|
| 38 |
+
|
| 39 |
+
max_prompt_length = prompt_mask.size(-1)
|
| 40 |
+
prompt_length = prompt_mask.sum(-1).float()
|
| 41 |
+
response_length = response_mask.sum(-1).float()
|
| 42 |
+
|
| 43 |
+
valid_adv = torch.masked_select(advantages, response_mask)
|
| 44 |
+
valid_returns = torch.masked_select(returns, response_mask)
|
| 45 |
+
|
| 46 |
+
if use_critic:
|
| 47 |
+
values = batch.batch["values"]
|
| 48 |
+
valid_values = torch.masked_select(values, response_mask)
|
| 49 |
+
return_diff_var = torch.var(valid_returns - valid_values)
|
| 50 |
+
return_var = torch.var(valid_returns)
|
| 51 |
+
|
| 52 |
+
metrics = {
|
| 53 |
+
# score
|
| 54 |
+
"critic/score/mean": torch.mean(sequence_score).detach().item(),
|
| 55 |
+
"critic/score/max": torch.max(sequence_score).detach().item(),
|
| 56 |
+
"critic/score/min": torch.min(sequence_score).detach().item(),
|
| 57 |
+
# reward
|
| 58 |
+
"critic/rewards/mean": torch.mean(sequence_reward).detach().item(),
|
| 59 |
+
"critic/rewards/max": torch.max(sequence_reward).detach().item(),
|
| 60 |
+
"critic/rewards/min": torch.min(sequence_reward).detach().item(),
|
| 61 |
+
# adv
|
| 62 |
+
"critic/advantages/mean": torch.mean(valid_adv).detach().item(),
|
| 63 |
+
"critic/advantages/max": torch.max(valid_adv).detach().item(),
|
| 64 |
+
"critic/advantages/min": torch.min(valid_adv).detach().item(),
|
| 65 |
+
# returns
|
| 66 |
+
"critic/returns/mean": torch.mean(valid_returns).detach().item(),
|
| 67 |
+
"critic/returns/max": torch.max(valid_returns).detach().item(),
|
| 68 |
+
"critic/returns/min": torch.min(valid_returns).detach().item(),
|
| 69 |
+
**(
|
| 70 |
+
{
|
| 71 |
+
# values
|
| 72 |
+
"critic/values/mean": torch.mean(valid_values).detach().item(),
|
| 73 |
+
"critic/values/max": torch.max(valid_values).detach().item(),
|
| 74 |
+
"critic/values/min": torch.min(valid_values).detach().item(),
|
| 75 |
+
# vf explained var
|
| 76 |
+
"critic/vf_explained_var": (1.0 - return_diff_var / (return_var + 1e-5)).detach().item(),
|
| 77 |
+
}
|
| 78 |
+
if use_critic
|
| 79 |
+
else {}
|
| 80 |
+
),
|
| 81 |
+
# response length
|
| 82 |
+
"response_length/mean": torch.mean(response_length).detach().item(),
|
| 83 |
+
"response_length/max": torch.max(response_length).detach().item(),
|
| 84 |
+
"response_length/min": torch.min(response_length).detach().item(),
|
| 85 |
+
"response_length/clip_ratio": torch.mean(torch.eq(response_length, max_response_length).float())
|
| 86 |
+
.detach()
|
| 87 |
+
.item(),
|
| 88 |
+
# prompt length
|
| 89 |
+
"prompt_length/mean": torch.mean(prompt_length).detach().item(),
|
| 90 |
+
"prompt_length/max": torch.max(prompt_length).detach().item(),
|
| 91 |
+
"prompt_length/min": torch.min(prompt_length).detach().item(),
|
| 92 |
+
"prompt_length/clip_ratio": torch.mean(torch.eq(prompt_length, max_prompt_length).float()).detach().item(),
|
| 93 |
+
}
|
| 94 |
+
return metrics
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def compute_timing_metrics(batch: DataProto, timing_raw: Dict[str, float]) -> Dict[str, Any]:
|
| 98 |
+
num_response_tokens = torch.sum(batch.batch["response_mask"]).item()
|
| 99 |
+
num_overall_tokens = sum(batch.meta_info["global_token_num"])
|
| 100 |
+
num_tokens_of_section = {
|
| 101 |
+
**dict.fromkeys(["gen", "reward"], num_response_tokens),
|
| 102 |
+
**dict.fromkeys(["ref", "old", "values", "adv", "update_critic", "update_actor"], num_overall_tokens),
|
| 103 |
+
}
|
| 104 |
+
return {
|
| 105 |
+
**{f"timing_s/{name}": value for name, value in timing_raw.items()},
|
| 106 |
+
**{
|
| 107 |
+
f"timing_per_token_ms/{name}": timing_raw[name] * 1000 / num_tokens_of_section[name]
|
| 108 |
+
for name in set(num_tokens_of_section.keys()) & set(timing_raw.keys())
|
| 109 |
+
},
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def compute_throughout_metrics(batch: DataProto, timing_raw: Dict[str, float], num_gpus: int) -> Dict[str, Any]:
|
| 114 |
+
total_num_tokens = sum(batch.meta_info["global_token_num"])
|
| 115 |
+
time = timing_raw["step"]
|
| 116 |
+
return {
|
| 117 |
+
"perf/total_num_tokens": total_num_tokens,
|
| 118 |
+
"perf/time_per_step": time,
|
| 119 |
+
"perf/throughput": total_num_tokens / (time * num_gpus),
|
| 120 |
+
}
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/trainer/ray_trainer.py
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""
|
| 15 |
+
FSDP PPO Trainer with Ray-based single controller.
|
| 16 |
+
This trainer supports model-agonistic model initialization with huggingface
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import os
|
| 20 |
+
import uuid
|
| 21 |
+
from collections import defaultdict
|
| 22 |
+
from copy import deepcopy
|
| 23 |
+
from dataclasses import dataclass, field
|
| 24 |
+
from enum import Enum, IntEnum, auto
|
| 25 |
+
from typing import Any, Dict, List, Optional, Type
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
import ray
|
| 29 |
+
import torch
|
| 30 |
+
from ray.experimental.tqdm_ray import tqdm
|
| 31 |
+
from torchdata.stateful_dataloader import StatefulDataLoader
|
| 32 |
+
from transformers import PreTrainedTokenizer, ProcessorMixin
|
| 33 |
+
|
| 34 |
+
from ..protocol import DataProto, pad_dataproto_to_divisor, unpad_dataproto
|
| 35 |
+
from ..single_controller.base import Worker
|
| 36 |
+
from ..single_controller.ray import RayClassWithInitArgs, RayResourcePool, RayWorkerGroup
|
| 37 |
+
from ..single_controller.ray.base import create_colocated_worker_cls
|
| 38 |
+
from ..utils import torch_functional as VF
|
| 39 |
+
from ..utils.checkpoint import CHECKPOINT_TRACKER, remove_obsolete_ckpt
|
| 40 |
+
from ..utils.logger import Tracker
|
| 41 |
+
from ..utils.py_functional import convert_dict_to_str, timer
|
| 42 |
+
from ..utils.seqlen_balancing import get_seqlen_balanced_partitions, log_seqlen_unbalance
|
| 43 |
+
from ..workers.fsdp_workers import FSDPWorker
|
| 44 |
+
from ..workers.reward import FunctionRewardManager
|
| 45 |
+
from . import core_algos
|
| 46 |
+
from .config import PPOConfig
|
| 47 |
+
from .metrics import compute_data_metrics, compute_throughout_metrics, compute_timing_metrics, reduce_metrics
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class Role(IntEnum):
|
| 51 |
+
"""
|
| 52 |
+
To create more roles dynamically, you can subclass Role and add new members
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
Actor = auto()
|
| 56 |
+
Rollout = auto()
|
| 57 |
+
ActorRollout = auto()
|
| 58 |
+
Critic = auto()
|
| 59 |
+
RefPolicy = auto()
|
| 60 |
+
RewardModel = auto()
|
| 61 |
+
ActorRolloutRef = auto()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class AdvantageEstimator(str, Enum):
|
| 65 |
+
"""
|
| 66 |
+
Using an enumeration class to avoid spelling errors in adv_estimator
|
| 67 |
+
"""
|
| 68 |
+
|
| 69 |
+
GAE = "gae"
|
| 70 |
+
GRPO = "grpo"
|
| 71 |
+
REINFORCE_PLUS_PLUS = "reinforce_plus_plus"
|
| 72 |
+
REMAX = "remax"
|
| 73 |
+
RLOO = "rloo"
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
@dataclass
|
| 77 |
+
class ResourcePoolManager:
|
| 78 |
+
"""
|
| 79 |
+
Define a resource pool specification. Resource pool will be initialized first.
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
resource_pool_spec: dict[str, list[int]]
|
| 83 |
+
mapping: dict[Role, str]
|
| 84 |
+
resource_pool_dict: dict[str, RayResourcePool] = field(default_factory=dict)
|
| 85 |
+
|
| 86 |
+
def create_resource_pool(self):
|
| 87 |
+
for resource_pool_name, process_on_nodes in self.resource_pool_spec.items():
|
| 88 |
+
# max_colocate_count means the number of WorkerGroups (i.e. processes) in each RayResourcePool
|
| 89 |
+
# For FSDP backend, we recommend using max_colocate_count=1 that merge all WorkerGroups into one.
|
| 90 |
+
# For Megatron backend, we recommend using max_colocate_count>1 that can utilize different WorkerGroup for differnt models
|
| 91 |
+
resource_pool = RayResourcePool(
|
| 92 |
+
process_on_nodes=process_on_nodes, use_gpu=True, max_colocate_count=1, name_prefix=resource_pool_name
|
| 93 |
+
)
|
| 94 |
+
self.resource_pool_dict[resource_pool_name] = resource_pool
|
| 95 |
+
|
| 96 |
+
self._check_resource_available()
|
| 97 |
+
|
| 98 |
+
def get_resource_pool(self, role: Role) -> RayResourcePool:
|
| 99 |
+
"""Get the resource pool of the worker."""
|
| 100 |
+
return self.resource_pool_dict[self.mapping[role]]
|
| 101 |
+
|
| 102 |
+
def get_num_gpus(self) -> int:
|
| 103 |
+
"""Get the number of gpus in this cluster."""
|
| 104 |
+
return sum([n_gpus for process_on_nodes in self.resource_pool_spec.values() for n_gpus in process_on_nodes])
|
| 105 |
+
|
| 106 |
+
def _check_resource_available(self):
|
| 107 |
+
"""Check if the resource pool can be satisfied in this ray cluster."""
|
| 108 |
+
gpus_available = ray.available_resources().get("GPU", 0)
|
| 109 |
+
gpus_required = self.get_num_gpus()
|
| 110 |
+
if gpus_available < gpus_required:
|
| 111 |
+
raise ValueError(f"Total available GPUs {gpus_available} is less than total desired GPUs {gpus_required}.")
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def apply_kl_penalty(data: DataProto, kl_ctrl: core_algos.KLController, kl_penalty="kl"):
|
| 115 |
+
token_level_scores = data.batch["token_level_scores"]
|
| 116 |
+
batch_size = data.batch.batch_size[0]
|
| 117 |
+
response_mask = data.batch["response_mask"]
|
| 118 |
+
|
| 119 |
+
# compute kl between ref_policy and current policy
|
| 120 |
+
kld = core_algos.compute_kl(data.batch["old_log_probs"], data.batch["ref_log_probs"], kl_penalty=kl_penalty)
|
| 121 |
+
kld = kld * response_mask # (batch_size, response_length)
|
| 122 |
+
|
| 123 |
+
data.batch["token_level_rewards"] = token_level_scores - kl_ctrl.kl_coef * kld
|
| 124 |
+
|
| 125 |
+
current_kl = VF.masked_mean(kld, mask=response_mask, dim=-1) # average over sequence
|
| 126 |
+
current_kl = torch.mean(current_kl, dim=0).item()
|
| 127 |
+
metrics = {"critic/kl": current_kl, "critic/kl_coef": kl_ctrl.kl_coef}
|
| 128 |
+
|
| 129 |
+
# According to https://github.com/huggingface/trl/blob/v0.11.0/trl/trainer/ppo_trainer.py#L880
|
| 130 |
+
kl_ctrl.update(current_kl=current_kl, n_steps=batch_size)
|
| 131 |
+
return data, metrics
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def compute_advantage(data: DataProto, adv_estimator: AdvantageEstimator, gamma: float = 1.0, lam: float = 1.0):
|
| 135 |
+
token_level_rewards = data.batch["token_level_rewards"]
|
| 136 |
+
response_mask = data.batch["response_mask"]
|
| 137 |
+
index = data.non_tensor_batch["uid"]
|
| 138 |
+
if adv_estimator == AdvantageEstimator.GAE:
|
| 139 |
+
values = data.batch["values"]
|
| 140 |
+
advantages, returns = core_algos.compute_gae_advantage_return(
|
| 141 |
+
token_level_rewards, values, response_mask, gamma, lam
|
| 142 |
+
)
|
| 143 |
+
elif adv_estimator == AdvantageEstimator.GRPO:
|
| 144 |
+
advantages, returns = core_algos.compute_grpo_outcome_advantage(token_level_rewards, response_mask, index)
|
| 145 |
+
elif adv_estimator == AdvantageEstimator.REINFORCE_PLUS_PLUS:
|
| 146 |
+
advantages, returns = core_algos.compute_reinforce_plus_plus_outcome_advantage(
|
| 147 |
+
token_level_rewards, response_mask, gamma
|
| 148 |
+
)
|
| 149 |
+
elif adv_estimator == AdvantageEstimator.REMAX:
|
| 150 |
+
reward_baselines = data.batch["reward_baselines"]
|
| 151 |
+
advantages, returns = core_algos.compute_remax_outcome_advantage(
|
| 152 |
+
token_level_rewards, reward_baselines, response_mask
|
| 153 |
+
)
|
| 154 |
+
elif adv_estimator == AdvantageEstimator.RLOO:
|
| 155 |
+
advantages, returns = core_algos.compute_rloo_outcome_advantage(token_level_rewards, response_mask, index)
|
| 156 |
+
else:
|
| 157 |
+
raise NotImplementedError
|
| 158 |
+
|
| 159 |
+
data.batch["advantages"] = advantages
|
| 160 |
+
data.batch["returns"] = returns
|
| 161 |
+
return data
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
class RayPPOTrainer:
|
| 165 |
+
"""
|
| 166 |
+
Note that this trainer runs on the driver process on a single CPU/GPU node.
|
| 167 |
+
"""
|
| 168 |
+
|
| 169 |
+
def __init__(
|
| 170 |
+
self,
|
| 171 |
+
config: PPOConfig,
|
| 172 |
+
tokenizer: PreTrainedTokenizer,
|
| 173 |
+
processor: Optional[ProcessorMixin],
|
| 174 |
+
train_dataloader: StatefulDataLoader,
|
| 175 |
+
val_dataloader: StatefulDataLoader,
|
| 176 |
+
role_worker_mapping: dict[Role, Type[Worker]],
|
| 177 |
+
resource_pool_manager: ResourcePoolManager,
|
| 178 |
+
ray_worker_group_cls: Type[RayWorkerGroup] = RayWorkerGroup,
|
| 179 |
+
reward_fn: Optional[FunctionRewardManager] = None,
|
| 180 |
+
val_reward_fn: Optional[FunctionRewardManager] = None,
|
| 181 |
+
):
|
| 182 |
+
self.tokenizer = tokenizer
|
| 183 |
+
self.processor = processor
|
| 184 |
+
self.train_dataloader = train_dataloader
|
| 185 |
+
self.val_dataloader = val_dataloader
|
| 186 |
+
self.config = config
|
| 187 |
+
self.reward_fn = reward_fn
|
| 188 |
+
self.val_reward_fn = val_reward_fn
|
| 189 |
+
|
| 190 |
+
self.hybrid_engine = config.worker.hybrid_engine
|
| 191 |
+
if self.hybrid_engine:
|
| 192 |
+
assert Role.ActorRollout in role_worker_mapping, (
|
| 193 |
+
f"ActorRollout should be included in {role_worker_mapping.keys()}."
|
| 194 |
+
)
|
| 195 |
+
else:
|
| 196 |
+
raise NotImplementedError
|
| 197 |
+
|
| 198 |
+
self.role_worker_mapping = role_worker_mapping
|
| 199 |
+
self.resource_pool_manager = resource_pool_manager
|
| 200 |
+
self.use_reward_model = Role.RewardModel in role_worker_mapping
|
| 201 |
+
self.ray_worker_group_cls = ray_worker_group_cls
|
| 202 |
+
|
| 203 |
+
# define KL control
|
| 204 |
+
if Role.RefPolicy in role_worker_mapping and not config.algorithm.disable_kl:
|
| 205 |
+
self.use_reference_policy = True
|
| 206 |
+
self.kl_ctrl = core_algos.get_kl_controller(config.algorithm)
|
| 207 |
+
else:
|
| 208 |
+
self.use_reference_policy = False
|
| 209 |
+
self.kl_ctrl = core_algos.FixedKLController(init_kl_coef=0.0)
|
| 210 |
+
print("KL is disabled, no KL metrics will be logged. Please set `kl_coef=0` to log KL metrics.")
|
| 211 |
+
|
| 212 |
+
if config.algorithm.adv_estimator == AdvantageEstimator.GAE:
|
| 213 |
+
self.use_critic = True
|
| 214 |
+
else:
|
| 215 |
+
self.use_critic = False
|
| 216 |
+
|
| 217 |
+
if config.algorithm.adv_estimator not in list(AdvantageEstimator):
|
| 218 |
+
raise NotImplementedError(f"Unknown advantage estimator: {config.algorithm.adv_estimator}.")
|
| 219 |
+
|
| 220 |
+
if config.data.rollout_batch_size % config.worker.actor.global_batch_size != 0:
|
| 221 |
+
raise ValueError("Rollout batch size must be divisible by actor global batch size.")
|
| 222 |
+
|
| 223 |
+
if (
|
| 224 |
+
config.data.rollout_batch_size * config.worker.rollout.n
|
| 225 |
+
) % config.worker.actor.micro_batch_size_per_device_for_experience != 0:
|
| 226 |
+
raise ValueError(
|
| 227 |
+
"Rollout batch size * rollout.n must be divisible by actor micro batch size for experience."
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
if self.use_critic:
|
| 231 |
+
if config.data.rollout_batch_size % config.worker.critic.global_batch_size != 0:
|
| 232 |
+
raise ValueError("Rollout batch size must be divisible by critic global batch size.")
|
| 233 |
+
|
| 234 |
+
if (
|
| 235 |
+
config.data.rollout_batch_size * config.worker.rollout.n
|
| 236 |
+
) % config.worker.critic.micro_batch_size_per_device_for_experience != 0:
|
| 237 |
+
raise ValueError(
|
| 238 |
+
"Rollout batch size * rollout.n must be divisible by critic micro batch size for experience."
|
| 239 |
+
)
|
| 240 |
+
|
| 241 |
+
if (
|
| 242 |
+
config.algorithm.adv_estimator in (AdvantageEstimator.GRPO, AdvantageEstimator.RLOO)
|
| 243 |
+
and config.worker.rollout.n == 1
|
| 244 |
+
):
|
| 245 |
+
raise ValueError("GRPO and RLOO algorithm need `config.worker.rollout.n > 1`.")
|
| 246 |
+
|
| 247 |
+
if config.trainer.max_steps is not None:
|
| 248 |
+
self.training_steps = config.trainer.max_steps
|
| 249 |
+
else:
|
| 250 |
+
self.training_steps = len(train_dataloader) * config.trainer.total_epochs
|
| 251 |
+
|
| 252 |
+
config.worker.actor.optim.training_steps = self.training_steps
|
| 253 |
+
config.worker.critic.optim.training_steps = self.training_steps
|
| 254 |
+
print(f"Total training steps: {self.training_steps}")
|
| 255 |
+
|
| 256 |
+
def _maybe_log_val_generations(
|
| 257 |
+
self, inputs: List[str], outputs: List[str], labels: List[str], scores: List[float]
|
| 258 |
+
) -> None:
|
| 259 |
+
"""Log a table of validation samples"""
|
| 260 |
+
if self.config.trainer.val_generations_to_log <= 0:
|
| 261 |
+
return
|
| 262 |
+
|
| 263 |
+
# Create tuples of (input, output, score) and sort by input text
|
| 264 |
+
samples = list(zip(inputs, outputs, labels, scores))
|
| 265 |
+
samples.sort(key=lambda x: x[0]) # Sort by input text
|
| 266 |
+
|
| 267 |
+
# Use fixed random seed for deterministic shuffling
|
| 268 |
+
rng = np.random.RandomState(42)
|
| 269 |
+
rng.shuffle(samples)
|
| 270 |
+
|
| 271 |
+
samples = samples[: self.config.trainer.val_generations_to_log]
|
| 272 |
+
self.logger.log_generation(samples, self.global_step)
|
| 273 |
+
|
| 274 |
+
def _validate(self) -> Dict[str, Any]:
|
| 275 |
+
reward_tensor_lst = []
|
| 276 |
+
# Lists to collect samples for the table
|
| 277 |
+
sample_inputs, sample_outputs, sample_labels, sample_scores = [], [], [], []
|
| 278 |
+
reward_metrics_lst = defaultdict(list)
|
| 279 |
+
for batch_dict in self.val_dataloader:
|
| 280 |
+
test_batch = DataProto.from_single_dict(batch_dict)
|
| 281 |
+
# Store original inputs
|
| 282 |
+
input_ids = test_batch.batch["input_ids"]
|
| 283 |
+
input_texts = [self.tokenizer.decode(ids, skip_special_tokens=True) for ids in input_ids]
|
| 284 |
+
sample_inputs.extend(input_texts)
|
| 285 |
+
|
| 286 |
+
if "multi_modal_data" in test_batch.non_tensor_batch.keys():
|
| 287 |
+
test_gen_batch = test_batch.pop(
|
| 288 |
+
batch_keys=["input_ids", "attention_mask", "position_ids"],
|
| 289 |
+
non_tensor_batch_keys=["raw_prompt_ids", "multi_modal_data"],
|
| 290 |
+
)
|
| 291 |
+
else:
|
| 292 |
+
test_gen_batch = test_batch.pop(
|
| 293 |
+
batch_keys=["input_ids", "attention_mask", "position_ids"],
|
| 294 |
+
non_tensor_batch_keys=["raw_prompt_ids"],
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
test_gen_batch.meta_info = self.config.worker.rollout.val_override_config
|
| 298 |
+
test_gen_batch.meta_info.update({
|
| 299 |
+
"min_pixels": self.config.data.min_pixels,
|
| 300 |
+
"max_pixels": self.config.data.max_pixels,
|
| 301 |
+
})
|
| 302 |
+
test_gen_batch, pad_size = pad_dataproto_to_divisor(test_gen_batch, self.actor_rollout_wg.world_size)
|
| 303 |
+
test_output_gen_batch = self.actor_rollout_wg.generate_sequences(test_gen_batch)
|
| 304 |
+
test_output_gen_batch = unpad_dataproto(test_output_gen_batch, pad_size=pad_size)
|
| 305 |
+
|
| 306 |
+
# Store generated outputs
|
| 307 |
+
output_ids = test_output_gen_batch.batch["responses"]
|
| 308 |
+
output_texts = [self.tokenizer.decode(ids, skip_special_tokens=True) for ids in output_ids]
|
| 309 |
+
sample_outputs.extend(output_texts)
|
| 310 |
+
sample_labels.extend(test_batch.non_tensor_batch["ground_truth"].tolist())
|
| 311 |
+
test_batch = test_batch.union(test_output_gen_batch)
|
| 312 |
+
|
| 313 |
+
# evaluate using reward_function
|
| 314 |
+
reward_tensor, reward_metrics = ray.get(self.val_reward_fn.compute_reward.remote(test_batch))
|
| 315 |
+
|
| 316 |
+
# Store scores
|
| 317 |
+
scores = reward_tensor.sum(-1).cpu().tolist()
|
| 318 |
+
sample_scores.extend(scores)
|
| 319 |
+
|
| 320 |
+
reward_tensor_lst.append(reward_tensor)
|
| 321 |
+
for key, value in reward_metrics.items():
|
| 322 |
+
reward_metrics_lst[key].extend(value)
|
| 323 |
+
|
| 324 |
+
self._maybe_log_val_generations(sample_inputs, sample_outputs, sample_labels, sample_scores)
|
| 325 |
+
reward_score = torch.cat(reward_tensor_lst, dim=0).sum(-1).mean().item()
|
| 326 |
+
val_reward_metrics = {f"val/{key}_reward": value for key, value in reduce_metrics(reward_metrics_lst).items()}
|
| 327 |
+
return {"val/reward_score": reward_score, **val_reward_metrics}
|
| 328 |
+
|
| 329 |
+
def init_workers(self) -> None:
|
| 330 |
+
"""Init resource pool and worker group"""
|
| 331 |
+
self.resource_pool_manager.create_resource_pool()
|
| 332 |
+
self.resource_pool_to_cls = {pool: {} for pool in self.resource_pool_manager.resource_pool_dict.values()}
|
| 333 |
+
|
| 334 |
+
# create actor and rollout
|
| 335 |
+
if self.hybrid_engine:
|
| 336 |
+
resource_pool = self.resource_pool_manager.get_resource_pool(Role.ActorRollout)
|
| 337 |
+
actor_rollout_cls = RayClassWithInitArgs(
|
| 338 |
+
cls=self.role_worker_mapping[Role.ActorRollout], config=self.config.worker, role="actor_rollout"
|
| 339 |
+
)
|
| 340 |
+
self.resource_pool_to_cls[resource_pool]["actor_rollout"] = actor_rollout_cls
|
| 341 |
+
else:
|
| 342 |
+
raise NotImplementedError
|
| 343 |
+
|
| 344 |
+
# create critic
|
| 345 |
+
if self.use_critic:
|
| 346 |
+
resource_pool = self.resource_pool_manager.get_resource_pool(Role.Critic)
|
| 347 |
+
critic_cls = RayClassWithInitArgs(
|
| 348 |
+
cls=self.role_worker_mapping[Role.Critic], config=self.config.worker, role="critic"
|
| 349 |
+
)
|
| 350 |
+
self.resource_pool_to_cls[resource_pool]["critic"] = critic_cls
|
| 351 |
+
|
| 352 |
+
# create reference policy if needed
|
| 353 |
+
if self.use_reference_policy:
|
| 354 |
+
resource_pool = self.resource_pool_manager.get_resource_pool(Role.RefPolicy)
|
| 355 |
+
ref_policy_cls = RayClassWithInitArgs(
|
| 356 |
+
self.role_worker_mapping[Role.RefPolicy], config=self.config.worker, role="ref"
|
| 357 |
+
)
|
| 358 |
+
self.resource_pool_to_cls[resource_pool]["ref"] = ref_policy_cls
|
| 359 |
+
|
| 360 |
+
# create a reward model if reward_fn is None
|
| 361 |
+
if self.use_reward_model:
|
| 362 |
+
# we create a RM here
|
| 363 |
+
resource_pool = self.resource_pool_manager.get_resource_pool(Role.RewardModel)
|
| 364 |
+
rm_cls = RayClassWithInitArgs(
|
| 365 |
+
cls=self.role_worker_mapping[Role.RewardModel], config=self.config.worker, role="reward"
|
| 366 |
+
)
|
| 367 |
+
self.resource_pool_to_cls[resource_pool]["rm"] = rm_cls
|
| 368 |
+
|
| 369 |
+
# initialize WorkerGroup
|
| 370 |
+
# NOTE: if you want to use a different resource pool for each role, which can support different parallel size,
|
| 371 |
+
# you should not use `create_colocated_worker_cls`. Instead, directly pass different resource pool to different worker groups.
|
| 372 |
+
# See https://github.com/volcengine/verl/blob/master/examples/ray/tutorial.ipynb for more information.
|
| 373 |
+
all_wg: Dict[str, FSDPWorker] = {}
|
| 374 |
+
self.wg_dicts = []
|
| 375 |
+
for resource_pool, class_dict in self.resource_pool_to_cls.items():
|
| 376 |
+
worker_dict_cls = create_colocated_worker_cls(class_dict=class_dict)
|
| 377 |
+
wg_dict = self.ray_worker_group_cls(resource_pool=resource_pool, ray_cls_with_init=worker_dict_cls)
|
| 378 |
+
spawn_wg = wg_dict.spawn(prefix_set=class_dict.keys())
|
| 379 |
+
all_wg.update(spawn_wg)
|
| 380 |
+
# keep the referece of WorkerDict to support ray >= 2.31. Ref: https://github.com/ray-project/ray/pull/45699
|
| 381 |
+
self.wg_dicts.append(wg_dict)
|
| 382 |
+
|
| 383 |
+
if self.use_critic:
|
| 384 |
+
self.critic_wg = all_wg["critic"]
|
| 385 |
+
self.critic_wg.init_model()
|
| 386 |
+
|
| 387 |
+
if self.use_reference_policy:
|
| 388 |
+
self.ref_policy_wg = all_wg["ref"]
|
| 389 |
+
self.ref_policy_wg.init_model()
|
| 390 |
+
|
| 391 |
+
if self.use_reward_model:
|
| 392 |
+
self.rm_wg = all_wg["rm"]
|
| 393 |
+
self.rm_wg.init_model()
|
| 394 |
+
|
| 395 |
+
# we should create rollout at the end so that vllm can have a better estimation of kv cache memory
|
| 396 |
+
self.actor_rollout_wg = all_wg["actor_rollout"]
|
| 397 |
+
self.actor_rollout_wg.init_model()
|
| 398 |
+
|
| 399 |
+
def _save_checkpoint(self) -> None:
|
| 400 |
+
# path: {save_checkpoint_path}/global_step_{global_step}/{actor,critic}
|
| 401 |
+
remove_obsolete_ckpt(
|
| 402 |
+
self.config.trainer.save_checkpoint_path, self.global_step, self.config.trainer.save_limit
|
| 403 |
+
)
|
| 404 |
+
folder_path = os.path.join(self.config.trainer.save_checkpoint_path, f"global_step_{self.global_step}")
|
| 405 |
+
actor_path = os.path.join(folder_path, "actor")
|
| 406 |
+
self.actor_rollout_wg.save_checkpoint(actor_path)
|
| 407 |
+
|
| 408 |
+
if self.use_critic:
|
| 409 |
+
critic_path = os.path.join(folder_path, "critic")
|
| 410 |
+
self.critic_wg.save_checkpoint(critic_path)
|
| 411 |
+
|
| 412 |
+
dataloader_path = os.path.join(folder_path, "dataloader.pt")
|
| 413 |
+
dataloader_state_dict = self.train_dataloader.state_dict()
|
| 414 |
+
torch.save(dataloader_state_dict, dataloader_path)
|
| 415 |
+
|
| 416 |
+
last_global_step_path = os.path.join(self.config.trainer.save_checkpoint_path, CHECKPOINT_TRACKER)
|
| 417 |
+
with open(last_global_step_path, "w") as f:
|
| 418 |
+
f.write(str(self.global_step))
|
| 419 |
+
|
| 420 |
+
def _load_checkpoint(self) -> None:
|
| 421 |
+
if self.config.trainer.load_checkpoint_path is None:
|
| 422 |
+
return
|
| 423 |
+
|
| 424 |
+
if "global_step_" not in self.config.trainer.load_checkpoint_path.strip(os.path.sep).split(os.path.sep)[-1]:
|
| 425 |
+
raise ValueError("`load_checkpoint_path` should end with `global_step_*`.")
|
| 426 |
+
|
| 427 |
+
print(f"Load from checkpoint: {self.config.trainer.load_checkpoint_path}.")
|
| 428 |
+
self.global_step = int(self.config.trainer.load_checkpoint_path.strip(os.path.sep).split("global_step_")[-1])
|
| 429 |
+
actor_path = os.path.join(self.config.trainer.load_checkpoint_path, "actor")
|
| 430 |
+
self.actor_rollout_wg.load_checkpoint(actor_path)
|
| 431 |
+
if self.use_critic:
|
| 432 |
+
critic_path = os.path.join(self.config.trainer.load_checkpoint_path, "critic")
|
| 433 |
+
self.critic_wg.load_checkpoint(critic_path)
|
| 434 |
+
|
| 435 |
+
dataloader_path = os.path.join(self.config.trainer.load_checkpoint_path, "dataloader.pt")
|
| 436 |
+
if os.path.exists(dataloader_path):
|
| 437 |
+
dataloader_state_dict = torch.load(dataloader_path, weights_only=False)
|
| 438 |
+
self.train_dataloader.load_state_dict(dataloader_state_dict)
|
| 439 |
+
else:
|
| 440 |
+
print(f"No dataloader state found at {dataloader_path}, will start from scratch.")
|
| 441 |
+
|
| 442 |
+
def _balance_batch(self, batch: DataProto, metrics: Dict[str, Any], logging_prefix: str = "global_seqlen") -> None:
|
| 443 |
+
"""Reorder the data on single controller such that each dp rank gets similar total tokens"""
|
| 444 |
+
attention_mask = batch.batch["attention_mask"]
|
| 445 |
+
batch_size = attention_mask.shape[0]
|
| 446 |
+
global_seqlen_lst = batch.batch["attention_mask"].view(batch_size, -1).sum(-1).tolist() # (train_batch_size,)
|
| 447 |
+
world_size = self.actor_rollout_wg.world_size
|
| 448 |
+
global_partition_lst = get_seqlen_balanced_partitions(
|
| 449 |
+
global_seqlen_lst, k_partitions=world_size, equal_size=True
|
| 450 |
+
)
|
| 451 |
+
# reorder based on index. The data will be automatically equally partitioned by dispatch function
|
| 452 |
+
global_idx = torch.tensor([j for partition in global_partition_lst for j in partition])
|
| 453 |
+
batch.reorder(global_idx)
|
| 454 |
+
global_balance_stats = log_seqlen_unbalance(
|
| 455 |
+
seqlen_list=global_seqlen_lst, partitions=global_partition_lst, prefix=logging_prefix
|
| 456 |
+
)
|
| 457 |
+
metrics.update(global_balance_stats)
|
| 458 |
+
|
| 459 |
+
def fit(self):
|
| 460 |
+
"""
|
| 461 |
+
The training loop of PPO.
|
| 462 |
+
The driver process only need to call the compute functions of the worker group through RPC to construct the PPO dataflow.
|
| 463 |
+
The light-weight advantage computation is done on the driver process.
|
| 464 |
+
"""
|
| 465 |
+
self.logger = Tracker(loggers=self.config.trainer.logger, config=self.config.to_dict())
|
| 466 |
+
self.global_step = 0
|
| 467 |
+
val_metrics: Optional[Dict[str, Any]] = None
|
| 468 |
+
|
| 469 |
+
# load checkpoint before doing anything
|
| 470 |
+
self._load_checkpoint()
|
| 471 |
+
|
| 472 |
+
# perform validation before training
|
| 473 |
+
# currently, we only support validation using the reward_function.
|
| 474 |
+
if self.val_reward_fn is not None and self.config.trainer.val_before_train:
|
| 475 |
+
val_metrics = self._validate()
|
| 476 |
+
self.logger.log(data=val_metrics, step=self.global_step)
|
| 477 |
+
if self.config.trainer.val_only:
|
| 478 |
+
return
|
| 479 |
+
|
| 480 |
+
for _ in tqdm(range(self.config.trainer.total_epochs), desc="Epoch", position=0):
|
| 481 |
+
for batch_dict in tqdm(self.train_dataloader, desc="Running step", position=1):
|
| 482 |
+
self.global_step += 1
|
| 483 |
+
if self.global_step > self.training_steps:
|
| 484 |
+
break
|
| 485 |
+
|
| 486 |
+
metrics, timing_raw = {}, {}
|
| 487 |
+
batch: DataProto = DataProto.from_single_dict(batch_dict)
|
| 488 |
+
|
| 489 |
+
# pop those keys for generation
|
| 490 |
+
if "multi_modal_data" in batch.non_tensor_batch.keys():
|
| 491 |
+
print(batch.non_tensor_batch['multi_modal_data'].shape)
|
| 492 |
+
gen_batch = batch.pop(
|
| 493 |
+
batch_keys=["input_ids", "attention_mask", "position_ids"],
|
| 494 |
+
non_tensor_batch_keys=["raw_prompt_ids", "multi_modal_data"],
|
| 495 |
+
)
|
| 496 |
+
gen_batch.meta_info.update({
|
| 497 |
+
"min_pixels": self.config.data.min_pixels,
|
| 498 |
+
"max_pixels": self.config.data.max_pixels,
|
| 499 |
+
})
|
| 500 |
+
else:
|
| 501 |
+
gen_batch = batch.pop(
|
| 502 |
+
batch_keys=["input_ids", "attention_mask", "position_ids"],
|
| 503 |
+
non_tensor_batch_keys=["raw_prompt_ids"],
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
with timer("step", timing_raw):
|
| 507 |
+
# generate a batch
|
| 508 |
+
with timer("gen", timing_raw): # wg: worker group
|
| 509 |
+
gen_batch_output = self.actor_rollout_wg.generate_sequences(gen_batch)
|
| 510 |
+
|
| 511 |
+
if self.config.algorithm.adv_estimator == "remax":
|
| 512 |
+
with timer("gen_max", timing_raw):
|
| 513 |
+
gen_baseline_batch = deepcopy(gen_batch)
|
| 514 |
+
gen_baseline_batch.meta_info["temperature"] = 0
|
| 515 |
+
gen_baseline_batch.meta_info["n"] = 1
|
| 516 |
+
gen_baseline_output = self.actor_rollout_wg.generate_sequences(gen_baseline_batch)
|
| 517 |
+
|
| 518 |
+
batch = batch.union(gen_baseline_output)
|
| 519 |
+
reward_baseline_tensor, _ = ray.get(self.reward_fn.compute_reward.remote(batch))
|
| 520 |
+
reward_baseline_tensor = reward_baseline_tensor.sum(dim=-1)
|
| 521 |
+
|
| 522 |
+
batch.pop(batch_keys=list(gen_baseline_output.batch.keys()))
|
| 523 |
+
batch.batch["reward_baselines"] = reward_baseline_tensor
|
| 524 |
+
del gen_baseline_batch, gen_baseline_output
|
| 525 |
+
|
| 526 |
+
batch.non_tensor_batch["uid"] = np.array(
|
| 527 |
+
[str(uuid.uuid4()) for _ in range(len(batch.batch))], dtype=object
|
| 528 |
+
)
|
| 529 |
+
# repeat to align with repeated responses in rollout
|
| 530 |
+
batch = batch.repeat(repeat_times=self.config.worker.rollout.n, interleave=True)
|
| 531 |
+
batch = batch.union(gen_batch_output)
|
| 532 |
+
|
| 533 |
+
# balance the number of valid tokens on each dp rank.
|
| 534 |
+
# Note that this breaks the order of data inside the batch.
|
| 535 |
+
# Please take care when you implement group based adv computation such as GRPO and rloo
|
| 536 |
+
self._balance_batch(batch, metrics=metrics)
|
| 537 |
+
|
| 538 |
+
# compute global_valid tokens
|
| 539 |
+
batch.meta_info["global_token_num"] = torch.sum(batch.batch["attention_mask"], dim=-1).tolist()
|
| 540 |
+
|
| 541 |
+
# compute reward
|
| 542 |
+
with timer("reward", timing_raw):
|
| 543 |
+
reward_ref = self.reward_fn.compute_reward.remote(batch)
|
| 544 |
+
|
| 545 |
+
# recompute old_log_probs
|
| 546 |
+
with timer("old", timing_raw):
|
| 547 |
+
old_log_probs = self.actor_rollout_wg.compute_log_probs(batch)
|
| 548 |
+
batch = batch.union(old_log_probs)
|
| 549 |
+
|
| 550 |
+
# compute ref_log_probs
|
| 551 |
+
if self.use_reference_policy:
|
| 552 |
+
with timer("ref", timing_raw):
|
| 553 |
+
ref_log_probs = self.ref_policy_wg.compute_ref_log_probs(batch)
|
| 554 |
+
batch = batch.union(ref_log_probs)
|
| 555 |
+
|
| 556 |
+
# compute values
|
| 557 |
+
if self.use_critic:
|
| 558 |
+
with timer("values", timing_raw):
|
| 559 |
+
values = self.critic_wg.compute_values(batch)
|
| 560 |
+
batch = batch.union(values)
|
| 561 |
+
|
| 562 |
+
with timer("adv", timing_raw):
|
| 563 |
+
# get token level scores
|
| 564 |
+
reward_tensor, reward_metrics = ray.get(reward_ref)
|
| 565 |
+
batch.batch["token_level_scores"] = reward_tensor
|
| 566 |
+
reward_metrics = {f"reward/{k}": v for k, v in reduce_metrics(reward_metrics).items()}
|
| 567 |
+
metrics.update(reward_metrics)
|
| 568 |
+
|
| 569 |
+
# apply kl penalty if available
|
| 570 |
+
if not self.config.algorithm.use_kl_loss and self.use_reference_policy:
|
| 571 |
+
# apply kl penalty to reward
|
| 572 |
+
batch, kl_metrics = apply_kl_penalty(batch, self.kl_ctrl, self.config.algorithm.kl_penalty)
|
| 573 |
+
metrics.update(kl_metrics)
|
| 574 |
+
else:
|
| 575 |
+
batch.batch["token_level_rewards"] = batch.batch["token_level_scores"]
|
| 576 |
+
|
| 577 |
+
# compute advantages, executed on the driver process
|
| 578 |
+
batch = compute_advantage(
|
| 579 |
+
batch,
|
| 580 |
+
adv_estimator=self.config.algorithm.adv_estimator,
|
| 581 |
+
gamma=self.config.algorithm.gamma,
|
| 582 |
+
lam=self.config.algorithm.lam,
|
| 583 |
+
)
|
| 584 |
+
|
| 585 |
+
# update critic
|
| 586 |
+
if self.use_critic:
|
| 587 |
+
with timer("update_critic", timing_raw):
|
| 588 |
+
critic_output = self.critic_wg.update_critic(batch)
|
| 589 |
+
|
| 590 |
+
critic_metrics = reduce_metrics(critic_output.non_tensor_batch)
|
| 591 |
+
metrics.update(critic_metrics)
|
| 592 |
+
|
| 593 |
+
# update actor
|
| 594 |
+
if self.config.trainer.critic_warmup <= self.global_step:
|
| 595 |
+
with timer("update_actor", timing_raw):
|
| 596 |
+
actor_output = self.actor_rollout_wg.update_actor(batch)
|
| 597 |
+
|
| 598 |
+
actor_metrics = reduce_metrics(actor_output.non_tensor_batch)
|
| 599 |
+
metrics.update(actor_metrics)
|
| 600 |
+
|
| 601 |
+
# validate
|
| 602 |
+
if (
|
| 603 |
+
self.val_reward_fn is not None
|
| 604 |
+
and self.config.trainer.val_freq > 0
|
| 605 |
+
and self.global_step % self.config.trainer.val_freq == 0
|
| 606 |
+
):
|
| 607 |
+
with timer("validation", timing_raw):
|
| 608 |
+
val_metrics = self._validate()
|
| 609 |
+
|
| 610 |
+
metrics.update(val_metrics)
|
| 611 |
+
|
| 612 |
+
if self.config.trainer.save_freq > 0 and self.global_step % self.config.trainer.save_freq == 0:
|
| 613 |
+
with timer("save_checkpoint", timing_raw):
|
| 614 |
+
self._save_checkpoint()
|
| 615 |
+
|
| 616 |
+
# collect metrics
|
| 617 |
+
num_gpus = self.resource_pool_manager.get_num_gpus()
|
| 618 |
+
metrics.update(compute_data_metrics(batch=batch, use_critic=self.use_critic))
|
| 619 |
+
metrics.update(compute_timing_metrics(batch=batch, timing_raw=timing_raw))
|
| 620 |
+
metrics.update(compute_throughout_metrics(batch=batch, timing_raw=timing_raw, num_gpus=num_gpus))
|
| 621 |
+
|
| 622 |
+
self.logger.log(data=metrics, step=self.global_step)
|
| 623 |
+
|
| 624 |
+
# perform validation after training
|
| 625 |
+
if self.val_reward_fn is not None:
|
| 626 |
+
if (
|
| 627 |
+
val_metrics is None
|
| 628 |
+
or self.config.trainer.val_freq <= 0
|
| 629 |
+
or self.global_step % self.config.trainer.val_freq != 0
|
| 630 |
+
):
|
| 631 |
+
val_metrics = self._validate()
|
| 632 |
+
self.logger.log(data=val_metrics, step=self.global_step)
|
| 633 |
+
|
| 634 |
+
print(f"Final validation metrics: {convert_dict_to_str(val_metrics)}")
|
| 635 |
+
|
| 636 |
+
if self.config.trainer.save_freq <= 0 or self.global_step % self.config.trainer.save_freq != 0:
|
| 637 |
+
self._save_checkpoint()
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/checkpoint/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from .checkpoint_manager import CHECKPOINT_TRACKER, remove_obsolete_ckpt
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
__all__ = ["CHECKPOINT_TRACKER", "remove_obsolete_ckpt"]
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/checkpoint/checkpoint_manager.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import random
|
| 17 |
+
import re
|
| 18 |
+
import shutil
|
| 19 |
+
import tempfile
|
| 20 |
+
from abc import ABC, abstractmethod
|
| 21 |
+
from typing import Any, Dict, Optional, Union
|
| 22 |
+
|
| 23 |
+
import numpy as np
|
| 24 |
+
import torch
|
| 25 |
+
import torch.distributed as dist
|
| 26 |
+
from filelock import FileLock
|
| 27 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
|
| 28 |
+
from transformers import PreTrainedTokenizer, ProcessorMixin
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
CHECKPOINT_TRACKER = "latest_global_step.txt"
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class BaseCheckpointManager(ABC):
|
| 35 |
+
"""
|
| 36 |
+
A checkpoint manager that saves and loads
|
| 37 |
+
- model
|
| 38 |
+
- optimizer
|
| 39 |
+
- lr_scheduler
|
| 40 |
+
- extra_states
|
| 41 |
+
in a SPMD way.
|
| 42 |
+
|
| 43 |
+
We save
|
| 44 |
+
- sharded model states and optimizer states
|
| 45 |
+
- full lr_scheduler states
|
| 46 |
+
- huggingface tokenizer and config for ckpt merge
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def __init__(
|
| 50 |
+
self,
|
| 51 |
+
model: FSDP,
|
| 52 |
+
optimizer: torch.optim.Optimizer,
|
| 53 |
+
lr_scheduler: torch.optim.lr_scheduler.LRScheduler,
|
| 54 |
+
processing_class: Union[PreTrainedTokenizer, ProcessorMixin],
|
| 55 |
+
):
|
| 56 |
+
self.model = model
|
| 57 |
+
self.optimizer = optimizer
|
| 58 |
+
self.lr_scheduler = lr_scheduler
|
| 59 |
+
self.processing_class = processing_class
|
| 60 |
+
|
| 61 |
+
assert isinstance(self.model, FSDP)
|
| 62 |
+
self.rank = dist.get_rank()
|
| 63 |
+
self.world_size = dist.get_world_size()
|
| 64 |
+
|
| 65 |
+
@abstractmethod
|
| 66 |
+
def load_checkpoint(self, *args, **kwargs):
|
| 67 |
+
raise NotImplementedError
|
| 68 |
+
|
| 69 |
+
@abstractmethod
|
| 70 |
+
def save_checkpoint(self, *args, **kwargs):
|
| 71 |
+
raise NotImplementedError
|
| 72 |
+
|
| 73 |
+
@staticmethod
|
| 74 |
+
def local_mkdir(path: str) -> str:
|
| 75 |
+
if not os.path.isabs(path):
|
| 76 |
+
working_dir = os.getcwd()
|
| 77 |
+
path = os.path.join(working_dir, path)
|
| 78 |
+
|
| 79 |
+
# Using hash value of path as lock file name to avoid long file name
|
| 80 |
+
lock_filename = f"ckpt_{hash(path) & 0xFFFFFFFF:08x}.lock"
|
| 81 |
+
lock_path = os.path.join(tempfile.gettempdir(), lock_filename)
|
| 82 |
+
|
| 83 |
+
try:
|
| 84 |
+
with FileLock(lock_path, timeout=60):
|
| 85 |
+
os.makedirs(path, exist_ok=True)
|
| 86 |
+
except Exception as e:
|
| 87 |
+
print(f"Warning: Failed to acquire lock for {path}: {e}")
|
| 88 |
+
os.makedirs(path, exist_ok=True) # even if the lock is not acquired, try to create the directory
|
| 89 |
+
|
| 90 |
+
return path
|
| 91 |
+
|
| 92 |
+
@staticmethod
|
| 93 |
+
def get_rng_state() -> Dict[str, Any]:
|
| 94 |
+
rng_state = {
|
| 95 |
+
"cpu": torch.get_rng_state(),
|
| 96 |
+
"cuda": torch.cuda.get_rng_state(),
|
| 97 |
+
"numpy": np.random.get_state(),
|
| 98 |
+
"random": random.getstate(),
|
| 99 |
+
}
|
| 100 |
+
return rng_state
|
| 101 |
+
|
| 102 |
+
@staticmethod
|
| 103 |
+
def load_rng_state(rng_state: Dict[str, Any]):
|
| 104 |
+
torch.set_rng_state(rng_state["cpu"])
|
| 105 |
+
torch.cuda.set_rng_state(rng_state["cuda"])
|
| 106 |
+
np.random.set_state(rng_state["numpy"])
|
| 107 |
+
random.setstate(rng_state["random"])
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def find_latest_ckpt_path(path: Optional[str] = None, directory_format: str = "global_step_{}") -> Optional[str]:
|
| 111 |
+
if path is None:
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
tracker_file = get_checkpoint_tracker_filename(path)
|
| 115 |
+
if not os.path.exists(tracker_file):
|
| 116 |
+
print("Checkpoint tracker file does not exist: %s", tracker_file)
|
| 117 |
+
return None
|
| 118 |
+
|
| 119 |
+
with open(tracker_file, "rb") as f:
|
| 120 |
+
iteration = int(f.read().decode())
|
| 121 |
+
|
| 122 |
+
ckpt_path = os.path.join(path, directory_format.format(iteration))
|
| 123 |
+
if not os.path.exists(ckpt_path):
|
| 124 |
+
print("Checkpoint does not exist: %s", ckpt_path)
|
| 125 |
+
return None
|
| 126 |
+
|
| 127 |
+
print("Found checkpoint: %s", ckpt_path)
|
| 128 |
+
return ckpt_path
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def get_checkpoint_tracker_filename(root_path: str) -> str:
|
| 132 |
+
"""
|
| 133 |
+
Tracker file rescords the latest chckpoint during training to restart from.
|
| 134 |
+
"""
|
| 135 |
+
return os.path.join(root_path, CHECKPOINT_TRACKER)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def remove_obsolete_ckpt(path: str, global_step: int, save_limit: int = -1, directory_format: str = "global_step_{}"):
|
| 139 |
+
"""
|
| 140 |
+
Remove the obsolete checkpoints that exceed the save_limit.
|
| 141 |
+
"""
|
| 142 |
+
if save_limit <= 0:
|
| 143 |
+
return
|
| 144 |
+
|
| 145 |
+
if not os.path.exists(path):
|
| 146 |
+
return
|
| 147 |
+
|
| 148 |
+
pattern = re.escape(directory_format).replace(r"\{\}", r"(\d+)")
|
| 149 |
+
ckpt_folders = []
|
| 150 |
+
for folder in os.listdir(path):
|
| 151 |
+
if match := re.match(pattern, folder):
|
| 152 |
+
step = int(match.group(1))
|
| 153 |
+
if step < global_step:
|
| 154 |
+
ckpt_folders.append((step, folder))
|
| 155 |
+
|
| 156 |
+
ckpt_folders.sort(reverse=True)
|
| 157 |
+
for _, folder in ckpt_folders[save_limit - 1 :]:
|
| 158 |
+
folder_path = os.path.join(path, folder)
|
| 159 |
+
shutil.rmtree(folder_path, ignore_errors=True)
|
| 160 |
+
print(f"Removed obsolete checkpoint: {folder_path}")
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/checkpoint/fsdp_checkpoint_manager.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
from typing import Optional, Union
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
import torch.distributed as dist
|
| 20 |
+
from torch.distributed.checkpoint.state_dict import StateDictOptions, get_state_dict, set_state_dict
|
| 21 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP
|
| 22 |
+
from transformers import PreTrainedModel, PreTrainedTokenizer, ProcessorMixin
|
| 23 |
+
|
| 24 |
+
from .checkpoint_manager import BaseCheckpointManager
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class FSDPCheckpointManager(BaseCheckpointManager):
|
| 28 |
+
"""
|
| 29 |
+
A checkpoint manager that saves and loads
|
| 30 |
+
- model
|
| 31 |
+
- optimizer
|
| 32 |
+
- lr_scheduler
|
| 33 |
+
- extra_states
|
| 34 |
+
in a SPMD way.
|
| 35 |
+
|
| 36 |
+
We save
|
| 37 |
+
- sharded model states and optimizer states
|
| 38 |
+
- full lr_scheduler states
|
| 39 |
+
- huggingface tokenizer and config for ckpt merge
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
model: FSDP,
|
| 45 |
+
optimizer: torch.optim.Optimizer,
|
| 46 |
+
lr_scheduler: torch.optim.lr_scheduler.LRScheduler,
|
| 47 |
+
processing_class: Union[PreTrainedTokenizer, ProcessorMixin],
|
| 48 |
+
):
|
| 49 |
+
super().__init__(model, optimizer, lr_scheduler, processing_class)
|
| 50 |
+
|
| 51 |
+
def load_checkpoint(self, path: Optional[str] = None):
|
| 52 |
+
if path is None:
|
| 53 |
+
return
|
| 54 |
+
|
| 55 |
+
# every rank download its own checkpoint
|
| 56 |
+
model_path = os.path.join(path, f"model_world_size_{self.world_size}_rank_{self.rank}.pt")
|
| 57 |
+
optim_path = os.path.join(path, f"optim_world_size_{self.world_size}_rank_{self.rank}.pt")
|
| 58 |
+
extra_path = os.path.join(path, f"extra_state_world_size_{self.world_size}_rank_{self.rank}.pt")
|
| 59 |
+
print(f"[rank-{self.rank}]: Loading model from {os.path.abspath(model_path)}.")
|
| 60 |
+
print(f"[rank-{self.rank}]: Loading optimizer from {os.path.abspath(optim_path)}.")
|
| 61 |
+
print(f"[rank-{self.rank}]: Loading extra_state from {os.path.abspath(extra_path)}.")
|
| 62 |
+
model_state_dict = torch.load(model_path, weights_only=False)
|
| 63 |
+
optim_state_dict = torch.load(optim_path, weights_only=False)
|
| 64 |
+
extra_state_dict = torch.load(extra_path, weights_only=False)
|
| 65 |
+
|
| 66 |
+
state_dict_options = StateDictOptions(cpu_offload=True)
|
| 67 |
+
set_state_dict(
|
| 68 |
+
model=self.model,
|
| 69 |
+
optimizers=self.optimizer,
|
| 70 |
+
model_state_dict=model_state_dict,
|
| 71 |
+
optim_state_dict=optim_state_dict,
|
| 72 |
+
options=state_dict_options,
|
| 73 |
+
)
|
| 74 |
+
self.lr_scheduler.load_state_dict(extra_state_dict["lr_scheduler"])
|
| 75 |
+
|
| 76 |
+
# recover random state
|
| 77 |
+
if "rng" in extra_state_dict:
|
| 78 |
+
self.load_rng_state(extra_state_dict["rng"])
|
| 79 |
+
|
| 80 |
+
def save_checkpoint(self, path: str):
|
| 81 |
+
path = self.local_mkdir(path)
|
| 82 |
+
dist.barrier()
|
| 83 |
+
|
| 84 |
+
# every rank will save its own model and optim shard
|
| 85 |
+
state_dict_options = StateDictOptions(cpu_offload=True)
|
| 86 |
+
model_state_dict, optim_state_dict = get_state_dict(self.model, self.optimizer, options=state_dict_options)
|
| 87 |
+
extra_state_dict = {
|
| 88 |
+
"lr_scheduler": self.lr_scheduler.state_dict(),
|
| 89 |
+
"rng": self.get_rng_state(),
|
| 90 |
+
}
|
| 91 |
+
model_path = os.path.join(path, f"model_world_size_{self.world_size}_rank_{self.rank}.pt")
|
| 92 |
+
optim_path = os.path.join(path, f"optim_world_size_{self.world_size}_rank_{self.rank}.pt")
|
| 93 |
+
extra_path = os.path.join(path, f"extra_state_world_size_{self.world_size}_rank_{self.rank}.pt")
|
| 94 |
+
|
| 95 |
+
print(f"[rank-{self.rank}]: Saving model to {os.path.abspath(model_path)}.")
|
| 96 |
+
print(f"[rank-{self.rank}]: Saving optimizer to {os.path.abspath(optim_path)}.")
|
| 97 |
+
print(f"[rank-{self.rank}]: Saving extra_state to {os.path.abspath(extra_path)}.")
|
| 98 |
+
torch.save(model_state_dict, model_path)
|
| 99 |
+
torch.save(optim_state_dict, optim_path)
|
| 100 |
+
torch.save(extra_state_dict, extra_path)
|
| 101 |
+
|
| 102 |
+
# wait for everyone to dump to local
|
| 103 |
+
dist.barrier()
|
| 104 |
+
|
| 105 |
+
if self.rank == 0:
|
| 106 |
+
hf_path = os.path.join(path, "huggingface")
|
| 107 |
+
os.makedirs(hf_path, exist_ok=True)
|
| 108 |
+
assert isinstance(self.model._fsdp_wrapped_module, PreTrainedModel)
|
| 109 |
+
self.model._fsdp_wrapped_module.config.save_pretrained(hf_path)
|
| 110 |
+
self.model._fsdp_wrapped_module.generation_config.save_pretrained(hf_path)
|
| 111 |
+
self.processing_class.save_pretrained(hf_path)
|
| 112 |
+
|
| 113 |
+
dist.barrier()
|
LLaVA-NeXT/llava-critic-r1/EasyR1/verl/utils/dataset.py
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2024 Bytedance Ltd. and/or its affiliates
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import math
|
| 16 |
+
import os
|
| 17 |
+
from collections import defaultdict
|
| 18 |
+
from io import BytesIO
|
| 19 |
+
from typing import Any, Dict, List, Optional, Union
|
| 20 |
+
|
| 21 |
+
import numpy as np
|
| 22 |
+
import torch
|
| 23 |
+
from datasets import load_dataset
|
| 24 |
+
from jinja2 import Template
|
| 25 |
+
from PIL import Image
|
| 26 |
+
from PIL.Image import Image as ImageObject
|
| 27 |
+
from PIL.Image import Image as PILImage
|
| 28 |
+
from torch.utils.data import Dataset
|
| 29 |
+
from transformers import PreTrainedTokenizer, ProcessorMixin
|
| 30 |
+
from PIL import Image, JpegImagePlugin
|
| 31 |
+
|
| 32 |
+
from ..models.transformers.qwen2_vl import get_rope_index
|
| 33 |
+
from . import torch_functional as VF
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def collate_fn(features: List[Dict[str, Any]]) -> Dict[str, Any]:
|
| 37 |
+
tensors = defaultdict(list)
|
| 38 |
+
non_tensors = defaultdict(list)
|
| 39 |
+
for feature in features:
|
| 40 |
+
for key, value in feature.items():
|
| 41 |
+
if isinstance(value, torch.Tensor):
|
| 42 |
+
tensors[key].append(value)
|
| 43 |
+
else:
|
| 44 |
+
non_tensors[key].append(value)
|
| 45 |
+
|
| 46 |
+
for key, value in tensors.items():
|
| 47 |
+
tensors[key] = torch.stack(value, dim=0)
|
| 48 |
+
|
| 49 |
+
for key, value in non_tensors.items():
|
| 50 |
+
non_tensors[key] = np.array(value, dtype=object)
|
| 51 |
+
|
| 52 |
+
return {**tensors, **non_tensors}
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def process_image(image: Union[Dict[str, Any], ImageObject, str], min_pixels: int, max_pixels: int) -> ImageObject:
|
| 57 |
+
if isinstance(image, dict):
|
| 58 |
+
if 'path' in image:
|
| 59 |
+
image = Image.open(image['path']).copy()
|
| 60 |
+
else:
|
| 61 |
+
image = Image.open(BytesIO(image["bytes"]))
|
| 62 |
+
elif isinstance(image, bytes):
|
| 63 |
+
image = Image.open(BytesIO(image))
|
| 64 |
+
elif isinstance(image, list):
|
| 65 |
+
image = Image.open(image[0]).copy()
|
| 66 |
+
# image = image[0]
|
| 67 |
+
elif isinstance(image, PILImage):
|
| 68 |
+
image = image.copy()
|
| 69 |
+
else:
|
| 70 |
+
# image = image
|
| 71 |
+
raise TypeError(f'Unsupported image type: {type(image)}')
|
| 72 |
+
|
| 73 |
+
if (image.width * image.height) > max_pixels:
|
| 74 |
+
resize_factor = math.sqrt(max_pixels / (image.width * image.height))
|
| 75 |
+
width, height = int(image.width * resize_factor), int(image.height * resize_factor)
|
| 76 |
+
image = image.resize((width, height))
|
| 77 |
+
|
| 78 |
+
if (image.width * image.height) < min_pixels:
|
| 79 |
+
resize_factor = math.sqrt(min_pixels / (image.width * image.height))
|
| 80 |
+
width, height = int(image.width * resize_factor), int(image.height * resize_factor)
|
| 81 |
+
image = image.resize((width, height))
|
| 82 |
+
|
| 83 |
+
if image.mode != "RGB":
|
| 84 |
+
image = image.convert("RGB")
|
| 85 |
+
|
| 86 |
+
return image
|
| 87 |
+
|
| 88 |
+
def process_video(
|
| 89 |
+
video: str, min_pixels: Optional[int], max_pixels: Optional[int], video_fps: float, return_fps: bool = False
|
| 90 |
+
) -> Union[list[ImageObject], tuple[list[ImageObject], list[float]]]:
|
| 91 |
+
vision_info = {"video": video, "min_pixels": min_pixels, "max_pixels": max_pixels, "fps": video_fps}
|
| 92 |
+
return fetch_video(vision_info, return_video_sample_fps=return_fps)
|
| 93 |
+
|
| 94 |
+
class RLHFDataset(Dataset):
|
| 95 |
+
"""
|
| 96 |
+
We assume the dataset contains a column that contains prompts and other information
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
data_path: str,
|
| 102 |
+
tokenizer: PreTrainedTokenizer,
|
| 103 |
+
processor: Optional[ProcessorMixin],
|
| 104 |
+
prompt_key: str = "prompt",
|
| 105 |
+
answer_key: str = "answer",
|
| 106 |
+
image_key: str = "images",
|
| 107 |
+
max_prompt_length: int = 1024,
|
| 108 |
+
truncation: str = "error",
|
| 109 |
+
format_prompt: Optional[str] = None,
|
| 110 |
+
max_pixels: Optional[int] = None,
|
| 111 |
+
min_pixels: Optional[int] = None,
|
| 112 |
+
filter_overlong_prompts: bool = False,
|
| 113 |
+
):
|
| 114 |
+
self.tokenizer = tokenizer
|
| 115 |
+
self.processor = processor
|
| 116 |
+
self.prompt_key = prompt_key
|
| 117 |
+
self.answer_key = answer_key
|
| 118 |
+
self.image_key = image_key
|
| 119 |
+
self.max_prompt_length = max_prompt_length
|
| 120 |
+
self.truncation = truncation
|
| 121 |
+
self.max_pixels = max_pixels
|
| 122 |
+
self.min_pixels = min_pixels
|
| 123 |
+
self.filter_overlong_prompts = filter_overlong_prompts
|
| 124 |
+
|
| 125 |
+
if "@" in data_path:
|
| 126 |
+
data_path, data_split = data_path.split("@")
|
| 127 |
+
else:
|
| 128 |
+
data_split = "train"
|
| 129 |
+
|
| 130 |
+
if os.path.isdir(data_path):
|
| 131 |
+
# when we use dataset builder, we should always refer to the train split
|
| 132 |
+
self.dataset = load_dataset("parquet", data_dir=data_path, split="train")
|
| 133 |
+
elif os.path.isfile(data_path):
|
| 134 |
+
self.dataset = load_dataset("parquet", data_files=data_path, split="train")
|
| 135 |
+
else:
|
| 136 |
+
# load remote dataset from huggingface hub
|
| 137 |
+
self.dataset = load_dataset(data_path, split=data_split)
|
| 138 |
+
|
| 139 |
+
self.format_prompt = None
|
| 140 |
+
if format_prompt:
|
| 141 |
+
with open(format_prompt, encoding="utf-8") as f:
|
| 142 |
+
self.format_prompt = f.read()
|
| 143 |
+
|
| 144 |
+
if self.filter_overlong_prompts:
|
| 145 |
+
self.dataset = self.dataset.filter(self._filter_overlong_prompts, desc="Filtering overlong prompts")
|
| 146 |
+
|
| 147 |
+
def _build_messages(self, example: Dict[str, Any]) -> List[Dict[str, Any]]:
|
| 148 |
+
prompt_str: str = example[self.prompt_key]
|
| 149 |
+
if self.format_prompt:
|
| 150 |
+
format_prompt = Template(self.format_prompt.strip())
|
| 151 |
+
prompt_str = format_prompt.render(content=prompt_str)
|
| 152 |
+
|
| 153 |
+
if self.image_key in example:
|
| 154 |
+
# https://huggingface.co/docs/transformers/en/tasks/image_text_to_text
|
| 155 |
+
content_list = []
|
| 156 |
+
for i, content in enumerate(prompt_str.split("<image>")):
|
| 157 |
+
if i != 0:
|
| 158 |
+
content_list.append({"type": "image"})
|
| 159 |
+
|
| 160 |
+
if content:
|
| 161 |
+
content_list.append({"type": "text", "text": content})
|
| 162 |
+
|
| 163 |
+
return [{"role": "user", "content": content_list}]
|
| 164 |
+
else:
|
| 165 |
+
return [{"role": "user", "content": prompt_str}]
|
| 166 |
+
|
| 167 |
+
def _filter_overlong_prompts(self, example: Dict[str, Any]) -> bool:
|
| 168 |
+
messages = self._build_messages(example)
|
| 169 |
+
processing_class = self.processor if self.processor is not None else self.tokenizer
|
| 170 |
+
return (
|
| 171 |
+
len(processing_class.apply_chat_template(messages, add_generation_prompt=True)) <= self.max_prompt_length
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
def __len__(self):
|
| 175 |
+
return len(self.dataset)
|
| 176 |
+
|
| 177 |
+
def __getitem__(self, index):
|
| 178 |
+
example: dict = self.dataset[index]
|
| 179 |
+
messages = self._build_messages(example)
|
| 180 |
+
|
| 181 |
+
if self.image_key in example:
|
| 182 |
+
prompt = self.processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
|
| 183 |
+
raw_image_data = example.pop(self.image_key)
|
| 184 |
+
if not isinstance(raw_image_data, (list, tuple)):
|
| 185 |
+
raw_image_data = [raw_image_data]
|
| 186 |
+
|
| 187 |
+
images = [
|
| 188 |
+
process_image(image, min_pixels=self.min_pixels, max_pixels=self.max_pixels)
|
| 189 |
+
for image in raw_image_data
|
| 190 |
+
]
|
| 191 |
+
model_inputs = self.processor(images, [prompt], add_special_tokens=False, return_tensors="pt")
|
| 192 |
+
input_ids = model_inputs.pop("input_ids")[0]
|
| 193 |
+
attention_mask = model_inputs.pop("attention_mask")[0]
|
| 194 |
+
example["multi_modal_data"] = {"image": raw_image_data}
|
| 195 |
+
else:
|
| 196 |
+
prompt = self.tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
|
| 197 |
+
model_inputs = self.tokenizer([prompt], add_special_tokens=False, return_tensors="pt")
|
| 198 |
+
input_ids = model_inputs.pop("input_ids")[0]
|
| 199 |
+
attention_mask = model_inputs.pop("attention_mask")[0]
|
| 200 |
+
|
| 201 |
+
if self.processor is not None and self.processor.image_processor.__class__.__name__ == "Qwen2VLImageProcessor":
|
| 202 |
+
# qwen2vl mrope
|
| 203 |
+
position_ids = get_rope_index(
|
| 204 |
+
self.processor,
|
| 205 |
+
input_ids=input_ids,
|
| 206 |
+
image_grid_thw=model_inputs.get("image_grid_thw"),
|
| 207 |
+
attention_mask=attention_mask,
|
| 208 |
+
) # (3, seq_length)
|
| 209 |
+
else:
|
| 210 |
+
position_ids = torch.clip(attention_mask.cumsum(dim=0) - 1, min=0, max=None) # (seq_length,)
|
| 211 |
+
|
| 212 |
+
input_ids, attention_mask, position_ids = VF.postprocess_data(
|
| 213 |
+
input_ids=input_ids,
|
| 214 |
+
attention_mask=attention_mask,
|
| 215 |
+
position_ids=position_ids,
|
| 216 |
+
max_length=self.max_prompt_length,
|
| 217 |
+
pad_token_id=self.tokenizer.pad_token_id,
|
| 218 |
+
left_pad=True,
|
| 219 |
+
truncation=self.truncation,
|
| 220 |
+
)
|
| 221 |
+
raw_prompt_ids = self.tokenizer.encode(prompt, add_special_tokens=False)
|
| 222 |
+
if len(raw_prompt_ids) > self.max_prompt_length:
|
| 223 |
+
if self.truncation == "left":
|
| 224 |
+
raw_prompt_ids = raw_prompt_ids[-self.max_prompt_length :]
|
| 225 |
+
elif self.truncation == "right":
|
| 226 |
+
raw_prompt_ids = raw_prompt_ids[: self.max_prompt_length]
|
| 227 |
+
elif self.truncation == "error":
|
| 228 |
+
raise RuntimeError(f"Prompt length {len(raw_prompt_ids)} is longer than {self.max_prompt_length}.")
|
| 229 |
+
|
| 230 |
+
example["input_ids"] = input_ids
|
| 231 |
+
example["attention_mask"] = attention_mask
|
| 232 |
+
example["position_ids"] = position_ids
|
| 233 |
+
example["raw_prompt_ids"] = raw_prompt_ids
|
| 234 |
+
example["ground_truth"] = example.pop(self.answer_key)
|
| 235 |
+
return example
|