[ci] feat: add more CI workflow (#38)
Browse files* [ci] upload several tests
* [ci] add sanity and tensordict utility workflow
* [ci] fix workflow
* try fix import ci
* [dataproto] update repeat and unpad/pad
* fix rollout test to 2GPU
* add a fsdp vllm hybridengine script, which can be launched by torchrun
* fix import test
* update requirement.txt
* draft vllm fsdp test
* update label
* fix
* upload conda
* test conda
* test ci
* use docker
* test ci
* test ci
* test ci
* update ci
* test ci
* fix model loader
* fix model loader
* test ci
* test
* upload e2e digit completion test
* update running script for e2e test
* update test config
* fix path
* test
* fix import to register autotokenizer
* fix tokenizer
* fix create dataset
* fix
* fix reward model validate
* fix reward module of digit_completion
* fix reward module of digit_completion
* fix reward module of digit_completion
* fix reward module of digit_completion
* fix reward module of digit_completion
* can run but seems to have some test issue
* no problem, add check results
* add e2e training
* l20-0 seems has docker permission problem, test later
* fix
* test l20-0 and torchrun
* test l20-0 and torchrun
* fix
* fix
* fix
* fix
* fix
* tolerate difference
* tolerate difference with levenshtein
* lint
* add more test for ray
* delete
* use docker on l20
* use docker on l20
* add upgrade
* update ci
* delete code
* ignore test
* upgrade ray
* fix workerhelper method
* lint
* revert worker changes
* fix
* fix
* fix
* fix worker missing func
- .github/workflows/{gpu_test.yml → dataset.yml} +5 -5
- .github/workflows/e2e_gpu.yml +38 -0
- .github/workflows/ray_test.yml +42 -0
- .github/workflows/sanity.yml +39 -0
- .github/workflows/vllm.yml +42 -0
- .github/workflows/yapf_format.yml +1 -1
- requirements.txt +1 -0
- tests/e2e/__init__.py +0 -0
- tests/e2e/arithmetic_sequence/data/create_dataset.py +46 -0
- tests/e2e/arithmetic_sequence/data/test.parquet +0 -0
- tests/e2e/arithmetic_sequence/data/train.parquet +0 -0
- tests/e2e/arithmetic_sequence/model/config.json +29 -0
- tests/e2e/arithmetic_sequence/model/create_model_tokenizer.py +61 -0
- tests/e2e/arithmetic_sequence/model/generation_config.json +6 -0
- tests/e2e/arithmetic_sequence/model/model.safetensors +3 -0
- tests/e2e/arithmetic_sequence/model/tokenizer_config.json +18 -0
- tests/e2e/arithmetic_sequence/rl/README.md +37 -0
- tests/e2e/arithmetic_sequence/rl/config/ray_trainer.yaml +132 -0
- tests/e2e/arithmetic_sequence/rl/main_trainer.py +161 -0
- tests/e2e/check_results.py +52 -0
- tests/e2e/envs/__init__.py +17 -0
- tests/e2e/envs/digit_completion/__init__.py +22 -0
- tests/e2e/envs/digit_completion/task.py +177 -0
- tests/e2e/envs/digit_completion/tokenizer.py +158 -0
- tests/e2e/run_ray_trainer.sh +17 -0
- tests/gpu_utility/test_memory_buffers.py +70 -0
- tests/gpu_utility/test_ops.py +47 -0
- tests/gpu_utility/test_torch_functional.py +81 -0
- tests/ray/test_data_transfer.py +1 -1
- tests/ray/test_remote_api.py +0 -89
- tests/rollout/run_fsdp_vllm.py +138 -0
- tests/rollout/test_vllm_hf_loader.py +174 -0
- tests/sanity/test_import.py +23 -0
- verl/single_controller/base/worker.py +6 -1
- verl/third_party/vllm/vllm_v_0_6_3/model_loader.py +6 -0
- verl/trainer/ppo/ray_trainer.py +1 -1
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
name:
|
| 2 |
|
| 3 |
on:
|
| 4 |
# Trigger the workflow on push or pull request,
|
|
@@ -8,13 +8,13 @@ on:
|
|
| 8 |
- main
|
| 9 |
paths:
|
| 10 |
- "**/*.py"
|
| 11 |
-
- .github/workflows/
|
| 12 |
pull_request:
|
| 13 |
branches:
|
| 14 |
- main
|
| 15 |
paths:
|
| 16 |
- "**/*.py"
|
| 17 |
-
- .github/workflows/
|
| 18 |
|
| 19 |
jobs:
|
| 20 |
ray:
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
| 30 |
run: |
|
| 31 |
[ ! -d "$HOME/verl-data" ] && git clone --depth 1 https://github.com/eric-haibin-lin/verl-data ~/verl-data
|
| 32 |
pytest -s -x tests/verl
|
| 33 |
-
- name: Running ray
|
| 34 |
run: |
|
| 35 |
cd tests/ray
|
| 36 |
-
pytest -s -x test_rvdz.py
|
|
|
|
| 1 |
+
name: dataset
|
| 2 |
|
| 3 |
on:
|
| 4 |
# Trigger the workflow on push or pull request,
|
|
|
|
| 8 |
- main
|
| 9 |
paths:
|
| 10 |
- "**/*.py"
|
| 11 |
+
- .github/workflows/dataset.yml
|
| 12 |
pull_request:
|
| 13 |
branches:
|
| 14 |
- main
|
| 15 |
paths:
|
| 16 |
- "**/*.py"
|
| 17 |
+
- .github/workflows/dataset.yml
|
| 18 |
|
| 19 |
jobs:
|
| 20 |
ray:
|
|
|
|
| 30 |
run: |
|
| 31 |
[ ! -d "$HOME/verl-data" ] && git clone --depth 1 https://github.com/eric-haibin-lin/verl-data ~/verl-data
|
| 32 |
pytest -s -x tests/verl
|
| 33 |
+
- name: Running ray test using cupy (move it to L20 when dockerfile ready)
|
| 34 |
run: |
|
| 35 |
cd tests/ray
|
| 36 |
+
pytest -s -x test_rvdz.py
|
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: e2e_gpu
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
# Trigger the workflow on push or pull request,
|
| 5 |
+
# but only for the main branch
|
| 6 |
+
push:
|
| 7 |
+
branches:
|
| 8 |
+
- main
|
| 9 |
+
paths:
|
| 10 |
+
- "**/*.py"
|
| 11 |
+
- .github/workflows/e2e_gpu.yml
|
| 12 |
+
pull_request:
|
| 13 |
+
branches:
|
| 14 |
+
- main
|
| 15 |
+
paths:
|
| 16 |
+
- "**/*.py"
|
| 17 |
+
- .github/workflows/e2e_gpu.yml
|
| 18 |
+
|
| 19 |
+
jobs:
|
| 20 |
+
e2e_gpu:
|
| 21 |
+
runs-on: [self-hosted, l20-1]
|
| 22 |
+
env:
|
| 23 |
+
HTTP_PROXY: ${{ secrets.PROXY_HTTP }}
|
| 24 |
+
HTTPS_PROXY: ${{ secrets.PROXY_HTTPS }}
|
| 25 |
+
NO_PROXY: "localhost,127.0.0.1"
|
| 26 |
+
container:
|
| 27 |
+
image: verlai/verl:vemlp-th2.4.0-cu124-vllm0.6.3-ray2.10-te1.7-v0.0.3
|
| 28 |
+
options: --gpus all --shm-size=10g
|
| 29 |
+
steps:
|
| 30 |
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
| 31 |
+
with:
|
| 32 |
+
fetch-depth: 0
|
| 33 |
+
- name: Install the current repository
|
| 34 |
+
run: |
|
| 35 |
+
pip3 install -e .[test]
|
| 36 |
+
- name: Running digit completon e2e training tests on 8 L20 GPUs
|
| 37 |
+
run: |
|
| 38 |
+
bash tests/e2e/run_ray_trainer.sh
|
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: ray
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
# Trigger the workflow on push or pull request,
|
| 5 |
+
# but only for the main branch
|
| 6 |
+
push:
|
| 7 |
+
branches:
|
| 8 |
+
- main
|
| 9 |
+
paths:
|
| 10 |
+
- "**/*.py"
|
| 11 |
+
- .github/workflows/ray_test.yml
|
| 12 |
+
pull_request:
|
| 13 |
+
branches:
|
| 14 |
+
- main
|
| 15 |
+
paths:
|
| 16 |
+
- "**/*.py"
|
| 17 |
+
- .github/workflows/ray_test.yml
|
| 18 |
+
|
| 19 |
+
jobs:
|
| 20 |
+
ray:
|
| 21 |
+
runs-on: [self-hosted, l20-0]
|
| 22 |
+
env:
|
| 23 |
+
HTTP_PROXY: ${{ secrets.PROXY_HTTP }}
|
| 24 |
+
HTTPS_PROXY: ${{ secrets.PROXY_HTTPS }}
|
| 25 |
+
NO_PROXY: "localhost,127.0.0.1"
|
| 26 |
+
HF_HUB_ENABLE_HF_TRANSFER: 1
|
| 27 |
+
container:
|
| 28 |
+
image: verlai/verl:vemlp-th2.4.0-cu124-vllm0.6.3-ray2.10-te1.7-v0.0.3
|
| 29 |
+
options: --gpus all --shm-size=10g
|
| 30 |
+
steps:
|
| 31 |
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
| 32 |
+
with:
|
| 33 |
+
fetch-depth: 0
|
| 34 |
+
- name: Install the current repository
|
| 35 |
+
run: |
|
| 36 |
+
pip install hf_transfer
|
| 37 |
+
pip install -e .[test]
|
| 38 |
+
pip install --upgrade "ray>=2.40.0"
|
| 39 |
+
- name: Running ray tests that need 8 GPUs
|
| 40 |
+
run: |
|
| 41 |
+
cd tests/ray
|
| 42 |
+
pytest -s -x --ignore=test_check_worker_alive.py --ignore=test_rvdz.py .
|
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: sanity
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
# Trigger the workflow on push or pull request,
|
| 5 |
+
# but only for the main branch
|
| 6 |
+
push:
|
| 7 |
+
branches:
|
| 8 |
+
- main
|
| 9 |
+
paths:
|
| 10 |
+
- "**/*.py"
|
| 11 |
+
- .github/workflows/sanity.yml
|
| 12 |
+
pull_request:
|
| 13 |
+
branches:
|
| 14 |
+
- main
|
| 15 |
+
paths:
|
| 16 |
+
- "**/*.py"
|
| 17 |
+
- .github/workflows/sanity.yml
|
| 18 |
+
|
| 19 |
+
jobs:
|
| 20 |
+
sanity:
|
| 21 |
+
runs-on: ubuntu-latest
|
| 22 |
+
strategy:
|
| 23 |
+
matrix:
|
| 24 |
+
python-version: ["3.10"]
|
| 25 |
+
steps:
|
| 26 |
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
| 27 |
+
- name: Set up Python ${{ matrix.python-version }}
|
| 28 |
+
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
| 29 |
+
with:
|
| 30 |
+
python-version: ${{ matrix.python-version }}
|
| 31 |
+
- name: Install the current repository
|
| 32 |
+
run: |
|
| 33 |
+
pip install -e .[test]
|
| 34 |
+
- name: Run sanity test
|
| 35 |
+
run: |
|
| 36 |
+
pytest -s -x tests/sanity
|
| 37 |
+
- name: Run untility test
|
| 38 |
+
run: |
|
| 39 |
+
pytest -s -x tests/utility
|
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: vllm
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
# Trigger the workflow on push or pull request,
|
| 5 |
+
# but only for the main branch
|
| 6 |
+
push:
|
| 7 |
+
branches:
|
| 8 |
+
- main
|
| 9 |
+
paths:
|
| 10 |
+
- "**/*.py"
|
| 11 |
+
- .github/workflows/vllm.yml
|
| 12 |
+
pull_request:
|
| 13 |
+
branches:
|
| 14 |
+
- main
|
| 15 |
+
paths:
|
| 16 |
+
- "**/*.py"
|
| 17 |
+
- .github/workflows/vllm.yml
|
| 18 |
+
|
| 19 |
+
jobs:
|
| 20 |
+
vllm:
|
| 21 |
+
runs-on: [self-hosted, l20-0]
|
| 22 |
+
env:
|
| 23 |
+
HTTP_PROXY: ${{ secrets.PROXY_HTTP }}
|
| 24 |
+
HTTPS_PROXY: ${{ secrets.PROXY_HTTPS }}
|
| 25 |
+
NO_PROXY: "localhost,127.0.0.1"
|
| 26 |
+
HF_HUB_ENABLE_HF_TRANSFER: 1
|
| 27 |
+
container:
|
| 28 |
+
image: verlai/verl:vemlp-th2.4.0-cu124-vllm0.6.3-ray2.10-te1.7-v0.0.3
|
| 29 |
+
options: --gpus all --shm-size=10g
|
| 30 |
+
steps:
|
| 31 |
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
| 32 |
+
with:
|
| 33 |
+
fetch-depth: 0
|
| 34 |
+
- name: Install the current repository
|
| 35 |
+
run: |
|
| 36 |
+
pip3 install hf_transfer
|
| 37 |
+
pip3 install -e .[test]
|
| 38 |
+
pip3 install vllm==0.5.4
|
| 39 |
+
- name: Running vllm tests on 8 L20 GPUs
|
| 40 |
+
run: |
|
| 41 |
+
cd tests/rollout
|
| 42 |
+
torchrun --standalone --nnodes=1 --nproc_per_node=8 $(which pytest) -s test_vllm_hf_loader.py
|
|
@@ -38,7 +38,7 @@ jobs:
|
|
| 38 |
- name: Install dependencies
|
| 39 |
run: |
|
| 40 |
python -m pip install --upgrade pip
|
| 41 |
-
pip install yapf
|
| 42 |
pip install toml==0.10.2
|
| 43 |
- name: Running yapf
|
| 44 |
run: |
|
|
|
|
| 38 |
- name: Install dependencies
|
| 39 |
run: |
|
| 40 |
python -m pip install --upgrade pip
|
| 41 |
+
pip install --upgrade yapf
|
| 42 |
pip install toml==0.10.2
|
| 43 |
- name: Running yapf
|
| 44 |
run: |
|
|
@@ -4,6 +4,7 @@ datasets
|
|
| 4 |
dill
|
| 5 |
hydra-core
|
| 6 |
numpy
|
|
|
|
| 7 |
pybind11
|
| 8 |
ray
|
| 9 |
tensordict<0.6
|
|
|
|
| 4 |
dill
|
| 5 |
hydra-core
|
| 6 |
numpy
|
| 7 |
+
pandas
|
| 8 |
pybind11
|
| 9 |
ray
|
| 10 |
tensordict<0.6
|
|
File without changes
|
|
@@ -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 |
+
from tests.e2e.envs.digit_completion import DigitCompletion, generate_ground_truth_response
|
| 16 |
+
from torch.utils import data
|
| 17 |
+
import os
|
| 18 |
+
|
| 19 |
+
if __name__ == '__main__':
|
| 20 |
+
simple_task = DigitCompletion(max_number=9, max_diff=9, max_num_in_response=9)
|
| 21 |
+
all_prompts = simple_task.get_all_prompts()
|
| 22 |
+
|
| 23 |
+
# 21 * 6 * 4
|
| 24 |
+
train_data, test_data = data.random_split(all_prompts, lengths=[0.8, 0.2])
|
| 25 |
+
train_data = list(train_data)
|
| 26 |
+
test_data = list(test_data)
|
| 27 |
+
|
| 28 |
+
train_data = [[{'role': 'user', 'content': str(item)}] \
|
| 29 |
+
for item in train_data]
|
| 30 |
+
test_data = [[{'role': 'user', 'content': str(item)}] \
|
| 31 |
+
for item in test_data]
|
| 32 |
+
|
| 33 |
+
print(f'Size of train: {len(train_data)}, size of test: {len(test_data)}')
|
| 34 |
+
|
| 35 |
+
train_data = {'prompt': train_data}
|
| 36 |
+
test_data = {'prompt': test_data}
|
| 37 |
+
|
| 38 |
+
model_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
| 39 |
+
|
| 40 |
+
import pandas as pd
|
| 41 |
+
|
| 42 |
+
train_data_frame = pd.DataFrame(train_data)
|
| 43 |
+
test_data_frame = pd.DataFrame(test_data)
|
| 44 |
+
|
| 45 |
+
train_data_frame.to_parquet(os.path.join(model_folder, 'train.parquet'))
|
| 46 |
+
test_data_frame.to_parquet(os.path.join(model_folder, 'test.parquet'))
|
|
Binary file (3.15 kB). View file
|
|
|
|
Binary file (6.23 kB). View file
|
|
|
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"LlamaForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"attention_bias": false,
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"bos_token_id": null,
|
| 8 |
+
"eos_token_id": 1,
|
| 9 |
+
"hidden_act": "silu",
|
| 10 |
+
"hidden_size": 128,
|
| 11 |
+
"initializer_range": 0.02,
|
| 12 |
+
"intermediate_size": 344,
|
| 13 |
+
"max_position_embeddings": 2048,
|
| 14 |
+
"mlp_bias": false,
|
| 15 |
+
"model_type": "llama",
|
| 16 |
+
"num_attention_heads": 4,
|
| 17 |
+
"num_hidden_layers": 4,
|
| 18 |
+
"num_key_value_heads": 4,
|
| 19 |
+
"pad_token_id": 2,
|
| 20 |
+
"pretraining_tp": 1,
|
| 21 |
+
"rms_norm_eps": 1e-06,
|
| 22 |
+
"rope_scaling": null,
|
| 23 |
+
"rope_theta": 10000.0,
|
| 24 |
+
"tie_word_embeddings": false,
|
| 25 |
+
"torch_dtype": "bfloat16",
|
| 26 |
+
"transformers_version": "4.43.3",
|
| 27 |
+
"use_cache": true,
|
| 28 |
+
"vocab_size": 16
|
| 29 |
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
Create a random model and tokenizer for PPO training
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
import os
|
| 20 |
+
from transformers import AutoModelForCausalLM, LlamaConfig, AutoTokenizer
|
| 21 |
+
|
| 22 |
+
from tests.e2e.envs.digit_completion import CharTokenizer
|
| 23 |
+
|
| 24 |
+
tokenizer = CharTokenizer(
|
| 25 |
+
characters=['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', ':'],
|
| 26 |
+
model_max_length=2048,
|
| 27 |
+
chat_template=
|
| 28 |
+
"{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% set role = message['role'] %}{{ message['content'] }}{% endfor %}{% if add_generation_prompt %}{{ sep_token }}{% endif %}"
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
config = LlamaConfig(vocab_size=(tokenizer.vocab_size + 16 - 1) // 16 * 16,
|
| 32 |
+
hidden_size=128,
|
| 33 |
+
intermediate_size=344,
|
| 34 |
+
num_hidden_layers=4,
|
| 35 |
+
num_attention_heads=4,
|
| 36 |
+
num_key_value_heads=4,
|
| 37 |
+
pad_token_id=tokenizer.pad_token_id,
|
| 38 |
+
bos_token_id=tokenizer.bos_token_id,
|
| 39 |
+
eos_token_id=tokenizer.eos_token_id)
|
| 40 |
+
|
| 41 |
+
model = AutoModelForCausalLM.from_config(config, torch_dtype=torch.bfloat16)
|
| 42 |
+
|
| 43 |
+
model_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
| 44 |
+
os.makedirs(model_folder, exist_ok=True)
|
| 45 |
+
|
| 46 |
+
model.save_pretrained(model_folder)
|
| 47 |
+
|
| 48 |
+
tokenizer_folder = model_folder
|
| 49 |
+
tokenizer.save_pretrained(tokenizer_folder)
|
| 50 |
+
|
| 51 |
+
load_tokenizer = AutoTokenizer.from_pretrained(tokenizer_folder)
|
| 52 |
+
|
| 53 |
+
chat = [{'role': 'user', 'content': '1,0:2,3'}]
|
| 54 |
+
|
| 55 |
+
load_tokenizer.padding_side = 'left'
|
| 56 |
+
print(
|
| 57 |
+
load_tokenizer.apply_chat_template(chat,
|
| 58 |
+
tokenize=True,
|
| 59 |
+
add_generation_prompt=True,
|
| 60 |
+
max_length=10,
|
| 61 |
+
padding='max_length'))
|
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"eos_token_id": 1,
|
| 4 |
+
"pad_token_id": 2,
|
| 5 |
+
"transformers_version": "4.43.3"
|
| 6 |
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3dcf33555c1207f9989d5fba5dc4d8b935c55736456e9b76b604a45215ac5cc4
|
| 3 |
+
size 1595672
|
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"char_ords": [
|
| 3 |
+
48,
|
| 4 |
+
49,
|
| 5 |
+
50,
|
| 6 |
+
51,
|
| 7 |
+
52,
|
| 8 |
+
53,
|
| 9 |
+
54,
|
| 10 |
+
55,
|
| 11 |
+
56,
|
| 12 |
+
57,
|
| 13 |
+
44,
|
| 14 |
+
58
|
| 15 |
+
],
|
| 16 |
+
"model_max_length": 2048,
|
| 17 |
+
"chat_template": "{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% set role = message['role'] %}{{ message['content'] }}{% endfor %}{% if add_generation_prompt %}{{ sep_token }}{% endif %}"
|
| 18 |
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Digit completion
|
| 2 |
+
|
| 3 |
+
This is an example of solving a digit completion problem. The problem is defined as below:
|
| 4 |
+
|
| 5 |
+
The prompt is a sequence of numbers with fixed difference. The agent's goal is to complete the next N numbers.
|
| 6 |
+
If the max number is reached, the next number should be modulo with max number.
|
| 7 |
+
|
| 8 |
+
For example,
|
| 9 |
+
- prompt = [1, 2, 3]
|
| 10 |
+
- N = 5
|
| 11 |
+
- max_number = 6
|
| 12 |
+
|
| 13 |
+
The response should be [4, 5, 6, 7%6, 8%6] = [4, 5, 6, 0, 1].
|
| 14 |
+
|
| 15 |
+
# Environment definition
|
| 16 |
+
|
| 17 |
+
The core definition of the task is defined in verl/envs/digit_completion/task.py
|
| 18 |
+
|
| 19 |
+
It is highly recommended to take a look at it for better understanding.
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# Run experiments
|
| 24 |
+
|
| 25 |
+
The users are required to specify the config path and config name (and the relative model config path to the current working directory)
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
# cd examples/arithmetic_sequence/rl
|
| 29 |
+
|
| 30 |
+
# Specify the config path and config name (current working dir)
|
| 31 |
+
python3 -m verl.trainer.ppo.ray_megatron_train_synchronous --config-path=$(pwd)/config --config-name='ray_megatron'
|
| 32 |
+
|
| 33 |
+
# The default relative path of model config is 'config/model_config', if you want to change it, you can rewrite it in ray_megatron.yaml or using:
|
| 34 |
+
python3 -m verl.trainer.ppo.ray_megatron_train_synchronous --config-path=$(pwd)/config --config-name='ray_megatron' ++model.base_path=config/model_config
|
| 35 |
+
|
| 36 |
+
```
|
| 37 |
+
|
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
data:
|
| 2 |
+
tokenizer: null
|
| 3 |
+
train_files: ~/verl/tests/e2e/arithmetic_sequence/data/train.parquet
|
| 4 |
+
val_files: ~/verl/tests/e2e/arithmetic_sequence/data/test.parquet
|
| 5 |
+
prompt_key: prompt
|
| 6 |
+
max_prompt_length: 16
|
| 7 |
+
max_response_length: 32
|
| 8 |
+
train_batch_size: 800
|
| 9 |
+
val_batch_size: 200
|
| 10 |
+
return_raw_input_ids: True # This should be set to true when the tokenizer between policy and rm differs
|
| 11 |
+
return_raw_chat: False
|
| 12 |
+
|
| 13 |
+
actor_rollout_ref:
|
| 14 |
+
hybrid_engine: True
|
| 15 |
+
model:
|
| 16 |
+
path: ~/verl/tests/e2e/arithmetic_sequence/model
|
| 17 |
+
external_lib: tests.e2e.envs.digit_completion
|
| 18 |
+
override_config: {}
|
| 19 |
+
enable_gradient_checkpointing: False
|
| 20 |
+
actor:
|
| 21 |
+
strategy: fsdp # This is for backward-compatibility
|
| 22 |
+
ppo_mini_batch_size: 200
|
| 23 |
+
ppo_micro_batch_size: 200
|
| 24 |
+
grad_clip: 1.0
|
| 25 |
+
clip_ratio: 0.2
|
| 26 |
+
entropy_coeff: 0.0
|
| 27 |
+
ppo_epochs: 1
|
| 28 |
+
shuffle: True
|
| 29 |
+
optim:
|
| 30 |
+
lr: 1e-4
|
| 31 |
+
fsdp_config:
|
| 32 |
+
wrap_policy:
|
| 33 |
+
# transformer_layer_cls_to_wrap: None
|
| 34 |
+
min_num_params: 0
|
| 35 |
+
param_offload: False
|
| 36 |
+
grad_offload: False
|
| 37 |
+
optimizer_offload: False
|
| 38 |
+
ref:
|
| 39 |
+
fsdp_config:
|
| 40 |
+
param_offload: False
|
| 41 |
+
wrap_policy:
|
| 42 |
+
# transformer_layer_cls_to_wrap: None
|
| 43 |
+
min_num_params: 0
|
| 44 |
+
micro_batch_size: 200
|
| 45 |
+
rollout:
|
| 46 |
+
name: hf
|
| 47 |
+
temperature: 1.0
|
| 48 |
+
top_k: -1 # 0 for hf rollout, -1 for vllm rollout
|
| 49 |
+
top_p: 1
|
| 50 |
+
prompt_length: ${data.max_prompt_length} # for xperf_gpt
|
| 51 |
+
response_length: ${data.max_response_length}
|
| 52 |
+
# for vllm rollout
|
| 53 |
+
dtype: bfloat16 # should align with FSDP
|
| 54 |
+
gpu_memory_utilization: 0.1
|
| 55 |
+
ignore_eos: False
|
| 56 |
+
micro_batch_size: 200
|
| 57 |
+
enforce_eager: True
|
| 58 |
+
free_cache_engine: True
|
| 59 |
+
load_format: dummy_dtensor
|
| 60 |
+
tensor_model_parallel_size: 2
|
| 61 |
+
max_num_batched_tokens: 8192
|
| 62 |
+
max_num_seqs: 1024
|
| 63 |
+
log_prob_micro_batch_size: 200
|
| 64 |
+
# for hf rollout
|
| 65 |
+
do_sample: True
|
| 66 |
+
# number of responses (i.e. num sample times)
|
| 67 |
+
n: 1 # > 1 for grpo
|
| 68 |
+
|
| 69 |
+
critic:
|
| 70 |
+
strategy: fsdp
|
| 71 |
+
optim:
|
| 72 |
+
lr: 1e-3
|
| 73 |
+
model:
|
| 74 |
+
path: ~/verl/tests/e2e/arithmetic_sequence/model
|
| 75 |
+
tokenizer_path: ${actor_rollout_ref.model.path}
|
| 76 |
+
override_config: {}
|
| 77 |
+
external_lib: ${actor_rollout_ref.model.external_lib}
|
| 78 |
+
enable_gradient_checkpointing: False
|
| 79 |
+
fsdp_config:
|
| 80 |
+
param_offload: False
|
| 81 |
+
grad_offload: False
|
| 82 |
+
optimizer_offload: False
|
| 83 |
+
wrap_policy:
|
| 84 |
+
# transformer_layer_cls_to_wrap: None
|
| 85 |
+
min_num_params: 0
|
| 86 |
+
ppo_mini_batch_size: ${actor_rollout_ref.actor.ppo_mini_batch_size}
|
| 87 |
+
ppo_micro_batch_size: 200
|
| 88 |
+
forward_micro_batch_size: ${critic.ppo_micro_batch_size}
|
| 89 |
+
ppo_epochs: ${actor_rollout_ref.actor.ppo_epochs}
|
| 90 |
+
shuffle: ${actor_rollout_ref.actor.shuffle}
|
| 91 |
+
grad_clip: 1.0
|
| 92 |
+
cliprange_value: 0.5
|
| 93 |
+
|
| 94 |
+
# the following parameters are for backward-compatibility and should be removed
|
| 95 |
+
kl_ctrl:
|
| 96 |
+
type: fixed
|
| 97 |
+
kl_coef: 0.001
|
| 98 |
+
|
| 99 |
+
reward_model:
|
| 100 |
+
strategy: fsdp
|
| 101 |
+
enable: False
|
| 102 |
+
model:
|
| 103 |
+
input_tokenizer: ${actor_rollout_ref.model.path} # set this to null if the chat template is identical
|
| 104 |
+
path: ~/models/FsfairX-LLaMA3-RM-v0.1
|
| 105 |
+
external_lib: ${actor_rollout_ref.model.external_lib}
|
| 106 |
+
offload: False
|
| 107 |
+
fsdp_config:
|
| 108 |
+
min_num_params: 0
|
| 109 |
+
micro_batch_size: 8
|
| 110 |
+
max_length: null
|
| 111 |
+
|
| 112 |
+
algorithm:
|
| 113 |
+
gamma: 1.0
|
| 114 |
+
lam: 1.0
|
| 115 |
+
adv_estimator: gae
|
| 116 |
+
kl_penalty: kl # how to estimate kl divergence
|
| 117 |
+
kl_ctrl:
|
| 118 |
+
type: fixed
|
| 119 |
+
kl_coef: 0.005
|
| 120 |
+
|
| 121 |
+
trainer:
|
| 122 |
+
total_epochs: 200
|
| 123 |
+
project_name: verl_examples
|
| 124 |
+
experiment_name: arithmetic_sequences
|
| 125 |
+
logger: ['console']
|
| 126 |
+
nnodes: 1
|
| 127 |
+
n_gpus_per_node: 1
|
| 128 |
+
save_freq: -1
|
| 129 |
+
test_freq: 1
|
| 130 |
+
critic_warmup: 0
|
| 131 |
+
default_hdfs_dir: ~/experiments/gsm8k/ppo/${trainer.experiment_name}
|
| 132 |
+
default_local_dir: checkpoints/${trainer.project_name}/${trainer.experiment_name}
|
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
Using FSDPTrainer
|
| 16 |
+
"""
|
| 17 |
+
import re
|
| 18 |
+
import os
|
| 19 |
+
import hydra
|
| 20 |
+
import numpy as np
|
| 21 |
+
import ray
|
| 22 |
+
import torch
|
| 23 |
+
from torch.utils.data import DataLoader
|
| 24 |
+
from transformers import PreTrainedTokenizer, AutoTokenizer
|
| 25 |
+
|
| 26 |
+
import verl.utils.torch_functional as verl_F
|
| 27 |
+
from verl import DataProto
|
| 28 |
+
from verl.trainer.ppo.ray_trainer import RayPPOTrainer
|
| 29 |
+
from verl.utils.fs import copy_local_path_from_hdfs
|
| 30 |
+
from verl.utils.model import compute_position_id_with_mask
|
| 31 |
+
from tests.e2e.envs.digit_completion import CharTokenizer
|
| 32 |
+
import pandas as pd
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def make_reward_function(tokenizer, num_examine):
|
| 36 |
+
|
| 37 |
+
def arithmetic_sequence_reward_function(data: DataProto):
|
| 38 |
+
from tests.e2e.envs.digit_completion.task import compute_reward
|
| 39 |
+
reward_tensor = torch.zeros_like(data.batch['responses'], dtype=torch.float32)
|
| 40 |
+
|
| 41 |
+
for i in range(data.batch.batch_size[0]):
|
| 42 |
+
data_item = data[i] # DataProtoItem
|
| 43 |
+
|
| 44 |
+
prompt_ids = data_item.batch['prompts']
|
| 45 |
+
|
| 46 |
+
prompt_length = prompt_ids.shape[-1]
|
| 47 |
+
|
| 48 |
+
# extract raw prompt
|
| 49 |
+
valid_prompt_length = data_item.batch['attention_mask'][:prompt_length].sum()
|
| 50 |
+
valid_prompt_ids = prompt_ids[-valid_prompt_length:]
|
| 51 |
+
|
| 52 |
+
# extract response
|
| 53 |
+
response_ids = data_item.batch['responses']
|
| 54 |
+
response_length = response_ids.shape[-1]
|
| 55 |
+
response_mask = data.batch['attention_mask'][i][-response_length:]
|
| 56 |
+
valid_response_length = data_item.batch['attention_mask'][prompt_length:].sum()
|
| 57 |
+
valid_response_ids = response_ids[:valid_response_length]
|
| 58 |
+
|
| 59 |
+
# decode
|
| 60 |
+
prompt = tokenizer.decode(valid_prompt_ids)
|
| 61 |
+
response = tokenizer.decode(valid_response_ids)
|
| 62 |
+
# remove bos and eos
|
| 63 |
+
prompt = prompt.replace(tokenizer.sep_token, '')
|
| 64 |
+
response = response.replace(tokenizer.eos_token, '')
|
| 65 |
+
if i < num_examine:
|
| 66 |
+
print(prompt, response)
|
| 67 |
+
|
| 68 |
+
reward_output = compute_reward(prompt, response)
|
| 69 |
+
dense_reward = reward_output[0].tolist()
|
| 70 |
+
ground_truth_response = reward_output[1]['ground_truth_response']
|
| 71 |
+
if len(dense_reward) > 0:
|
| 72 |
+
last_reward = dense_reward[-1]
|
| 73 |
+
else:
|
| 74 |
+
if len(ground_truth_response) == 0:
|
| 75 |
+
last_reward = 1
|
| 76 |
+
else:
|
| 77 |
+
last_reward = 0
|
| 78 |
+
|
| 79 |
+
# pad to response_length
|
| 80 |
+
for _ in range(reward_tensor.shape[-1] - len(dense_reward)):
|
| 81 |
+
dense_reward.append(last_reward)
|
| 82 |
+
|
| 83 |
+
dense_reward = torch.as_tensor(dense_reward, dtype=torch.float32, device=reward_tensor.device)
|
| 84 |
+
reward_tensor[i] = dense_reward * response_mask
|
| 85 |
+
|
| 86 |
+
return reward_tensor
|
| 87 |
+
|
| 88 |
+
return arithmetic_sequence_reward_function
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
@hydra.main(config_path='config', config_name='ray_trainer', version_base=None)
|
| 92 |
+
def main(config):
|
| 93 |
+
ray.init(
|
| 94 |
+
runtime_env={
|
| 95 |
+
'env_vars': {
|
| 96 |
+
'MEGATRON_USE_CUDA_TIMER': '0',
|
| 97 |
+
'MEGATRON_START_PROCESS_TIMER': 'False',
|
| 98 |
+
'TOKENIZERS_PARALLELISM': 'true',
|
| 99 |
+
'NCCL_DEBUG': 'WARN'
|
| 100 |
+
}
|
| 101 |
+
})
|
| 102 |
+
|
| 103 |
+
# print initial config
|
| 104 |
+
from pprint import pprint
|
| 105 |
+
from omegaconf import OmegaConf
|
| 106 |
+
pprint(OmegaConf.to_container(config, resolve=True)) # resolve=True will eval symbol values
|
| 107 |
+
|
| 108 |
+
dp_size = config.trainer.n_gpus_per_node * config.trainer.nnodes
|
| 109 |
+
# normalize batch_size
|
| 110 |
+
# TODO: move this inside each role
|
| 111 |
+
config.actor_rollout_ref.actor.ppo_mini_batch_size //= dp_size
|
| 112 |
+
config.actor_rollout_ref.actor.ppo_micro_batch_size //= dp_size
|
| 113 |
+
config.critic.ppo_micro_batch_size //= dp_size
|
| 114 |
+
config.actor_rollout_ref.rollout.micro_batch_size //= dp_size
|
| 115 |
+
|
| 116 |
+
# print the config
|
| 117 |
+
# print initial config
|
| 118 |
+
print('Config after normalizing batch_size')
|
| 119 |
+
pprint(OmegaConf.to_container(config, resolve=True)) # resolve=True will eval symbol values
|
| 120 |
+
|
| 121 |
+
# download the checkpoint from hdfs
|
| 122 |
+
local_path = copy_local_path_from_hdfs(config.actor_rollout_ref.model.path)
|
| 123 |
+
local_path = os.path.expanduser(local_path)
|
| 124 |
+
# instantiate tokenizern
|
| 125 |
+
tokenizer = AutoTokenizer.from_pretrained(local_path)
|
| 126 |
+
print(f'Tokenizer vocab_size: {tokenizer.vocab_size}')
|
| 127 |
+
|
| 128 |
+
# define worker classes
|
| 129 |
+
from verl.workers.fsdp_workers import ActorRolloutRefWorker, CriticWorker
|
| 130 |
+
from verl.trainer.ppo.ray_trainer import ResourcePoolManager, Role
|
| 131 |
+
|
| 132 |
+
role_worker_mapping = {
|
| 133 |
+
Role.ActorRollout: ray.remote(ActorRolloutRefWorker),
|
| 134 |
+
Role.Critic: ray.remote(CriticWorker),
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
global_pool_id = 'global_pool'
|
| 138 |
+
resource_pool_spec = {
|
| 139 |
+
global_pool_id: [config.trainer.n_gpus_per_node] * config.trainer.nnodes,
|
| 140 |
+
}
|
| 141 |
+
mapping = {
|
| 142 |
+
Role.ActorRollout: global_pool_id,
|
| 143 |
+
Role.Critic: global_pool_id,
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
reward_fn = make_reward_function(tokenizer=tokenizer, num_examine=1)
|
| 147 |
+
|
| 148 |
+
resource_pool_manager = ResourcePoolManager(resource_pool_spec=resource_pool_spec, mapping=mapping)
|
| 149 |
+
|
| 150 |
+
trainer = RayPPOTrainer(config=config,
|
| 151 |
+
tokenizer=tokenizer,
|
| 152 |
+
role_worker_mapping=role_worker_mapping,
|
| 153 |
+
resource_pool_manager=resource_pool_manager,
|
| 154 |
+
reward_fn=reward_fn,
|
| 155 |
+
val_reward_fn=reward_fn)
|
| 156 |
+
trainer.init_workers()
|
| 157 |
+
trainer.fit()
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
if __name__ == '__main__':
|
| 161 |
+
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 argparse
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def extract_reward_from_line(line):
|
| 21 |
+
# TODO: this function needs error handling
|
| 22 |
+
try:
|
| 23 |
+
key_vals = line.split(' - ')
|
| 24 |
+
for key_val in key_vals:
|
| 25 |
+
key, val = key_val.split(':')
|
| 26 |
+
if key == 'critic/rewards/mean':
|
| 27 |
+
reward = float(val)
|
| 28 |
+
return reward
|
| 29 |
+
return -np.inf
|
| 30 |
+
except Exception:
|
| 31 |
+
return -np.inf
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
if __name__ == '__main__':
|
| 35 |
+
parser = argparse.ArgumentParser()
|
| 36 |
+
parser.add_argument('--output_file', required=True, type=str)
|
| 37 |
+
|
| 38 |
+
args = parser.parse_args()
|
| 39 |
+
|
| 40 |
+
with open(args.output_file, 'r') as f:
|
| 41 |
+
output = f.read().split('\n')
|
| 42 |
+
|
| 43 |
+
best_reward = -np.inf
|
| 44 |
+
for line in output:
|
| 45 |
+
if line.startswith('step'):
|
| 46 |
+
reward = extract_reward_from_line(line)
|
| 47 |
+
if reward > best_reward:
|
| 48 |
+
best_reward = reward
|
| 49 |
+
|
| 50 |
+
print(f'Best reward is {best_reward}')
|
| 51 |
+
assert best_reward > 0.2, f'Best reward must be greater than 0.3. best_reward: {best_reward}'
|
| 52 |
+
print('Check passes')
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 .digit_completion import DigitCompletion
|
| 16 |
+
|
| 17 |
+
__all__ = ['DigitCompletion']
|
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 .task import DigitCompletion, generate_ground_truth_response
|
| 16 |
+
from .tokenizer import CharTokenizer
|
| 17 |
+
|
| 18 |
+
from transformers import AutoTokenizer, LlamaConfig
|
| 19 |
+
|
| 20 |
+
AutoTokenizer.register(LlamaConfig, CharTokenizer, exist_ok=True)
|
| 21 |
+
|
| 22 |
+
__all__ = ['DigitCompletion', 'generate_ground_truth_response', 'CharTokenizer']
|
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"""Task and environment definition for digit completion."""
|
| 15 |
+
|
| 16 |
+
import numpy as np
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class DigitCompletion(object):
|
| 20 |
+
"""
|
| 21 |
+
The implementation of a simple digit completion task.
|
| 22 |
+
The prompt is a sequence of numbers with fixed difference. The task is to complete the next N numbers.
|
| 23 |
+
If the max number is reached, the next number should be modulo with max number.
|
| 24 |
+
|
| 25 |
+
For example,
|
| 26 |
+
- prompt = [1, 2, 3]
|
| 27 |
+
- N = 5
|
| 28 |
+
- max_number = 6
|
| 29 |
+
|
| 30 |
+
the response should be [4, 5, 6, 7%6, 8%6] = [4, 5, 6, 0, 1]
|
| 31 |
+
|
| 32 |
+
Note that the tokenizer is char-level to increase the difficulty.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
def __init__(self, max_number: int, max_diff: int, max_num_in_response: int, seed=0):
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
Args:
|
| 39 |
+
max_number: the maximum number allowed in the arithmetic sequence
|
| 40 |
+
max_diff: the maximum diff. The actual common diff will be sampled from [0, max_diff]
|
| 41 |
+
max_num_in_response: the maximum number in the response
|
| 42 |
+
"""
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.max_number = max_number
|
| 45 |
+
self.max_diff = max_diff
|
| 46 |
+
self.max_num_in_response = max_num_in_response
|
| 47 |
+
assert self.max_num_in_response < 10
|
| 48 |
+
assert self.max_number > 0
|
| 49 |
+
assert self.max_diff > 0
|
| 50 |
+
self.max_number_length = len(str(max_number))
|
| 51 |
+
# {num1},{num2}:{max_num_in_response},{max_number}
|
| 52 |
+
self._prompt_length = self.max_number_length * 2 + 4 + self.max_number_length # no negative is allowed
|
| 53 |
+
|
| 54 |
+
self.np_rng = np.random.default_rng(seed=seed)
|
| 55 |
+
|
| 56 |
+
def __str__(self):
|
| 57 |
+
return f'Prompt length: {self.prompt_length}. Response length: {self.response_length}, ' \
|
| 58 |
+
f'Max number: {self.max_number}. Max diff: {self.max_diff}, ' \
|
| 59 |
+
f'Max number in response: {self.max_num_in_response}'
|
| 60 |
+
|
| 61 |
+
def get_state(self):
|
| 62 |
+
return {'rng': self.np_rng}
|
| 63 |
+
|
| 64 |
+
def set_state(self, state):
|
| 65 |
+
assert 'rng' in state, 'rng must be inside state'
|
| 66 |
+
self.np_rng = state['rng']
|
| 67 |
+
|
| 68 |
+
@property
|
| 69 |
+
def prompt_length(self):
|
| 70 |
+
return self._prompt_length
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def response_length(self):
|
| 74 |
+
# number length + comma length + [EOS]
|
| 75 |
+
# The actual number times 1.5 to allow 'U'
|
| 76 |
+
return (self.max_num_in_response * self.max_number_length + (self.max_num_in_response - 1) + 1) * 2
|
| 77 |
+
|
| 78 |
+
def add(self, a, b):
|
| 79 |
+
return (a + b) % self.max_number
|
| 80 |
+
|
| 81 |
+
def get_all_prompts(self):
|
| 82 |
+
all_prompts = []
|
| 83 |
+
for first_num in range(self.max_number + 1):
|
| 84 |
+
for diff in range(0, self.max_diff + 1):
|
| 85 |
+
second_num = self.add(first_num, diff)
|
| 86 |
+
for num_to_complete in range(self.max_num_in_response + 1):
|
| 87 |
+
prompt = str(first_num) + ',' + str(second_num) + f':{self.max_number},{num_to_complete}'
|
| 88 |
+
all_prompts.append(prompt)
|
| 89 |
+
return all_prompts
|
| 90 |
+
|
| 91 |
+
def sample_str_prompts(self):
|
| 92 |
+
# step 1: sample initial numbers
|
| 93 |
+
first_num = self.np_rng.integers(self.max_number + 1)
|
| 94 |
+
diff = self.np_rng.integers(self.max_diff + 1)
|
| 95 |
+
second_num = self.add(first_num, diff)
|
| 96 |
+
num_to_complete = self.np_rng.integers(self.max_num_in_response + 1)
|
| 97 |
+
prompt = str(first_num) + ',' + str(second_num) + f':{self.max_number},{num_to_complete}'
|
| 98 |
+
return prompt
|
| 99 |
+
|
| 100 |
+
def sample_batch_str_prompts(self, batch_size):
|
| 101 |
+
str_prompts = []
|
| 102 |
+
for _ in range(batch_size):
|
| 103 |
+
str_prompts.append(self.sample_str_prompts())
|
| 104 |
+
return str_prompts
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def compute_attention_mask(prompts, pad_token_id):
|
| 108 |
+
mask = np.ones_like(prompts)
|
| 109 |
+
mask[prompts == pad_token_id] = 0
|
| 110 |
+
return mask
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def compute_position_id_with_mask(mask):
|
| 114 |
+
return np.clip(np.cumsum(mask, axis=-1) - 1, a_min=0, a_max=None)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def generate_ground_truth_response(prompt: str):
|
| 118 |
+
"""Generate ground truth response given a prompt."""
|
| 119 |
+
num, info = prompt.split(':')
|
| 120 |
+
num1, num2 = num.split(',')
|
| 121 |
+
max_number, num_to_gen = info.split(',')
|
| 122 |
+
num1 = int(num1)
|
| 123 |
+
num2 = int(num2)
|
| 124 |
+
max_number = int(max_number)
|
| 125 |
+
num_to_gen = int(num_to_gen)
|
| 126 |
+
diff = (num2 - num1) % max_number
|
| 127 |
+
results = []
|
| 128 |
+
last_num = num2
|
| 129 |
+
for _ in range(num_to_gen):
|
| 130 |
+
curr = (last_num + diff) % max_number
|
| 131 |
+
results.append(str(curr))
|
| 132 |
+
last_num = curr
|
| 133 |
+
response = ','.join(results)
|
| 134 |
+
return response
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def compute_reward(prompt: str, response: str, sequence_reward=1.):
|
| 138 |
+
"""We compute dense reward here so that we can directly train RL without SFT"""
|
| 139 |
+
response_length = len(response)
|
| 140 |
+
ground_truth_response = generate_ground_truth_response(prompt)
|
| 141 |
+
per_token_reward = sequence_reward / (len(ground_truth_response) + 1) # including [EOS]
|
| 142 |
+
|
| 143 |
+
# pad
|
| 144 |
+
reward = np.zeros(response_length, dtype=np.float32) # this assumes that each char is a token
|
| 145 |
+
# assign reward until mismatches
|
| 146 |
+
ground_truth_idx = 0
|
| 147 |
+
for i in range(response_length):
|
| 148 |
+
if ground_truth_idx == len(ground_truth_response):
|
| 149 |
+
break
|
| 150 |
+
|
| 151 |
+
ground_truth_response_token = ground_truth_response[ground_truth_idx]
|
| 152 |
+
response_token = response[i]
|
| 153 |
+
if ground_truth_response_token == response_token:
|
| 154 |
+
reward[i] = per_token_reward
|
| 155 |
+
ground_truth_idx += 1
|
| 156 |
+
else:
|
| 157 |
+
# no matches
|
| 158 |
+
break
|
| 159 |
+
|
| 160 |
+
return reward, {'ground_truth_response': ground_truth_response}
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
if __name__ == '__main__':
|
| 164 |
+
task = DigitCompletion(max_number=20, max_diff=3, max_num_in_response=5)
|
| 165 |
+
print(task.sample_str_prompts())
|
| 166 |
+
|
| 167 |
+
prompt = '7,8:20,0'
|
| 168 |
+
response = ''
|
| 169 |
+
print(compute_reward(prompt, response))
|
| 170 |
+
|
| 171 |
+
prompt = '7,8:20,0'
|
| 172 |
+
response = 'E000'
|
| 173 |
+
print(compute_reward(prompt, response))
|
| 174 |
+
|
| 175 |
+
prompt = '9,10:20,2'
|
| 176 |
+
response = '11,12,13'
|
| 177 |
+
print(compute_reward(prompt, response))
|
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
"""Copied from https://github.com/dariush-bahrami/character-tokenizer/blob/master/charactertokenizer/core.py
|
| 15 |
+
|
| 16 |
+
CharacterTokenzier for Hugging Face Transformers.
|
| 17 |
+
|
| 18 |
+
This is heavily inspired from CanineTokenizer in transformers package.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import json
|
| 22 |
+
import os
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
from typing import Dict, List, Optional, Sequence, Union
|
| 25 |
+
|
| 26 |
+
from transformers.tokenization_utils import AddedToken, PreTrainedTokenizer
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class CharTokenizer(PreTrainedTokenizer):
|
| 30 |
+
|
| 31 |
+
def __init__(self, characters: Sequence[str], model_max_length: int, chat_template, **kwargs):
|
| 32 |
+
"""Character tokenizer for Hugging Face transformers.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
characters (Sequence[str]): List of desired characters. Any character which
|
| 36 |
+
is not included in this list will be replaced by a special token called
|
| 37 |
+
[UNK] with id=6. Following are list of all of the special tokens with
|
| 38 |
+
their corresponding ids:
|
| 39 |
+
"[CLS]": 0
|
| 40 |
+
"[SEP]": 1
|
| 41 |
+
"[BOS]": 2
|
| 42 |
+
"[MASK]": 3
|
| 43 |
+
"[PAD]": 4
|
| 44 |
+
"[RESERVED]": 5
|
| 45 |
+
"[UNK]": 6
|
| 46 |
+
an id (starting at 7) will be assigned to each character.
|
| 47 |
+
|
| 48 |
+
model_max_length (int): Model maximum sequence length.
|
| 49 |
+
"""
|
| 50 |
+
eos_token_str = 'E'
|
| 51 |
+
sep_token_str = 'S'
|
| 52 |
+
pad_token_str = 'P'
|
| 53 |
+
unk_token_str = 'U'
|
| 54 |
+
|
| 55 |
+
self.characters = characters
|
| 56 |
+
self.model_max_length = model_max_length
|
| 57 |
+
eos_token = AddedToken(eos_token_str, lstrip=False, rstrip=False)
|
| 58 |
+
sep_token = AddedToken(sep_token_str, lstrip=False, rstrip=False)
|
| 59 |
+
pad_token = AddedToken(pad_token_str, lstrip=False, rstrip=False)
|
| 60 |
+
unk_token = AddedToken(unk_token_str, lstrip=False, rstrip=False)
|
| 61 |
+
|
| 62 |
+
self._vocab_str_to_int = {
|
| 63 |
+
sep_token_str: 0,
|
| 64 |
+
eos_token_str: 1,
|
| 65 |
+
pad_token_str: 2,
|
| 66 |
+
unk_token_str: 3,
|
| 67 |
+
**{
|
| 68 |
+
ch: i + 4 for i, ch in enumerate(characters)
|
| 69 |
+
},
|
| 70 |
+
}
|
| 71 |
+
self._vocab_int_to_str = {v: k for k, v in self._vocab_str_to_int.items()}
|
| 72 |
+
|
| 73 |
+
super().__init__(
|
| 74 |
+
eos_token=eos_token,
|
| 75 |
+
sep_token=sep_token,
|
| 76 |
+
pad_token=pad_token,
|
| 77 |
+
unk_token=unk_token,
|
| 78 |
+
add_prefix_space=False,
|
| 79 |
+
model_max_length=model_max_length,
|
| 80 |
+
**kwargs,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
self.chat_template = chat_template
|
| 84 |
+
|
| 85 |
+
@property
|
| 86 |
+
def vocab_size(self) -> int:
|
| 87 |
+
return len(self._vocab_str_to_int)
|
| 88 |
+
|
| 89 |
+
def get_vocab(self):
|
| 90 |
+
return self._vocab_str_to_int
|
| 91 |
+
|
| 92 |
+
def _tokenize(self, text: str) -> List[str]:
|
| 93 |
+
return list(text)
|
| 94 |
+
|
| 95 |
+
def _convert_token_to_id(self, token: str) -> int:
|
| 96 |
+
return self._vocab_str_to_int.get(token, self._vocab_str_to_int["U"])
|
| 97 |
+
|
| 98 |
+
def _convert_id_to_token(self, index: int) -> str:
|
| 99 |
+
return self._vocab_int_to_str[index]
|
| 100 |
+
|
| 101 |
+
def convert_tokens_to_string(self, tokens):
|
| 102 |
+
return "".join(tokens)
|
| 103 |
+
|
| 104 |
+
def build_inputs_with_special_tokens(self,
|
| 105 |
+
token_ids_0: List[int],
|
| 106 |
+
token_ids_1: Optional[List[int]] = None) -> List[int]:
|
| 107 |
+
sep = [self.sep_token_id]
|
| 108 |
+
cls = [self.cls_token_id]
|
| 109 |
+
result = cls + token_ids_0 + sep
|
| 110 |
+
if token_ids_1 is not None:
|
| 111 |
+
result += token_ids_1 + sep
|
| 112 |
+
return result
|
| 113 |
+
|
| 114 |
+
def get_special_tokens_mask(
|
| 115 |
+
self,
|
| 116 |
+
token_ids_0: List[int],
|
| 117 |
+
token_ids_1: Optional[List[int]] = None,
|
| 118 |
+
already_has_special_tokens: bool = False,
|
| 119 |
+
) -> List[int]:
|
| 120 |
+
if already_has_special_tokens:
|
| 121 |
+
return super().get_special_tokens_mask(
|
| 122 |
+
token_ids_0=token_ids_0,
|
| 123 |
+
token_ids_1=token_ids_1,
|
| 124 |
+
already_has_special_tokens=True,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
result = [1] + ([0] * len(token_ids_0)) + [1]
|
| 128 |
+
if token_ids_1 is not None:
|
| 129 |
+
result += ([0] * len(token_ids_1)) + [1]
|
| 130 |
+
return result
|
| 131 |
+
|
| 132 |
+
def get_config(self) -> Dict:
|
| 133 |
+
return {
|
| 134 |
+
"char_ords": [ord(ch) for ch in self.characters],
|
| 135 |
+
"model_max_length": self.model_max_length,
|
| 136 |
+
"chat_template": self.chat_template
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
@classmethod
|
| 140 |
+
def from_config(cls, config: Dict) -> "DigitCompletionTokenizer":
|
| 141 |
+
cfg = {}
|
| 142 |
+
cfg["characters"] = [chr(i) for i in config["char_ords"]]
|
| 143 |
+
cfg["model_max_length"] = config["model_max_length"]
|
| 144 |
+
cfg["chat_template"] = config["chat_template"]
|
| 145 |
+
return cls(**cfg)
|
| 146 |
+
|
| 147 |
+
def save_pretrained(self, save_directory: Union[str, os.PathLike], **kwargs):
|
| 148 |
+
cfg_file = Path(save_directory) / "tokenizer_config.json"
|
| 149 |
+
cfg = self.get_config()
|
| 150 |
+
with open(cfg_file, "w") as f:
|
| 151 |
+
json.dump(cfg, f, indent=4)
|
| 152 |
+
|
| 153 |
+
@classmethod
|
| 154 |
+
def from_pretrained(cls, save_directory: Union[str, os.PathLike], **kwargs):
|
| 155 |
+
cfg_file = Path(save_directory) / "tokenizer_config.json"
|
| 156 |
+
with open(cfg_file) as f:
|
| 157 |
+
cfg = json.load(f)
|
| 158 |
+
return cls.from_config(cfg)
|
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
set -e -x
|
| 4 |
+
|
| 5 |
+
OUTPUT_FILE="/tmp/output_ray_trainer.txt"
|
| 6 |
+
|
| 7 |
+
export PATH=$PATH:~/.local/bin
|
| 8 |
+
|
| 9 |
+
rm -rf $OUTPUT_FILE
|
| 10 |
+
python3 tests/e2e/arithmetic_sequence/rl/main_trainer.py \
|
| 11 |
+
data.train_files=tests/e2e/arithmetic_sequence/data/train.parquet \
|
| 12 |
+
data.val_files=tests/e2e/arithmetic_sequence/data/test.parquet \
|
| 13 |
+
actor_rollout_ref.model.path=tests/e2e/arithmetic_sequence/model \
|
| 14 |
+
critic.model.path=tests/e2e/arithmetic_sequence/model | tee $OUTPUT_FILE;
|
| 15 |
+
|
| 16 |
+
python3 tests/e2e/check_results.py --output_file=$OUTPUT_FILE
|
| 17 |
+
rm -rf $OUTPUT_FILE
|
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
Test memory buffers
|
| 16 |
+
- We start with two models with the same weights
|
| 17 |
+
- We use Memory buffer to make one of the models and then compare the parameters
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
import gc
|
| 22 |
+
|
| 23 |
+
from transformers import LlamaModel, LlamaConfig
|
| 24 |
+
from verl.utils.memory_buffer import MemoryBufferModuleWrapper
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def test_memory_buffers():
|
| 28 |
+
llama_config = LlamaConfig(vocab_size=256,
|
| 29 |
+
hidden_size=4096,
|
| 30 |
+
intermediate_size=11008,
|
| 31 |
+
num_hidden_layers=2,
|
| 32 |
+
num_attention_heads=16,
|
| 33 |
+
num_key_value_heads=16)
|
| 34 |
+
|
| 35 |
+
model = LlamaModel(config=llama_config).cuda()
|
| 36 |
+
model_copy = LlamaModel(config=llama_config).cuda()
|
| 37 |
+
model_copy.load_state_dict(model.state_dict())
|
| 38 |
+
|
| 39 |
+
model_named_params = dict(model.named_parameters())
|
| 40 |
+
model_copy_named_params = dict(model_copy.named_parameters())
|
| 41 |
+
|
| 42 |
+
norm_factor = 1024**3
|
| 43 |
+
|
| 44 |
+
t_before = torch.cuda.get_device_properties(0).total_memory / norm_factor
|
| 45 |
+
r_before = torch.cuda.memory_reserved(0) / norm_factor
|
| 46 |
+
a_before = torch.cuda.memory_allocated(0) / norm_factor
|
| 47 |
+
|
| 48 |
+
print(f'Before Total memory: {t_before} GB, reserved: {r_before} GB, allocated: {a_before} GB')
|
| 49 |
+
|
| 50 |
+
model_wrapper = MemoryBufferModuleWrapper(model)
|
| 51 |
+
|
| 52 |
+
t = torch.cuda.get_device_properties(0).total_memory / norm_factor
|
| 53 |
+
r = torch.cuda.memory_reserved(0) / norm_factor
|
| 54 |
+
a = torch.cuda.memory_allocated(0) / norm_factor
|
| 55 |
+
|
| 56 |
+
gc.collect()
|
| 57 |
+
torch.cuda.empty_cache()
|
| 58 |
+
|
| 59 |
+
print(f'After Total memory: {t} GB, reserved: {r} GB, allocated: {a} GB')
|
| 60 |
+
|
| 61 |
+
change_ratio = (a - a_before) / a_before
|
| 62 |
+
assert change_ratio < 0.01, f'make sure the allocated change is less than 1%, Got {change_ratio}'
|
| 63 |
+
|
| 64 |
+
for (name1, param1), (name2, param2) in zip(model.named_parameters(), model_copy.named_parameters()):
|
| 65 |
+
assert name1 == name2
|
| 66 |
+
assert torch.eq(param1.data, param2.data).all(), f'{param1.data}, {param2.data}, {name1}'
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
if __name__ == '__main__':
|
| 70 |
+
test_memory_buffers()
|
|
@@ -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 |
+
|
| 16 |
+
def test_flash_attn_cross_entropy():
|
| 17 |
+
from verl.utils.torch_functional import logprobs_from_logits_naive
|
| 18 |
+
|
| 19 |
+
from verl.utils.debug import log_gpu_memory_usage
|
| 20 |
+
|
| 21 |
+
from flash_attn.ops.triton.cross_entropy import cross_entropy_loss
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
from torch import nn
|
| 25 |
+
|
| 26 |
+
log_gpu_memory_usage('At start')
|
| 27 |
+
|
| 28 |
+
hidden_states = torch.randn(size=(2048, 5120), device='cuda', requires_grad=True, dtype=torch.bfloat16)
|
| 29 |
+
|
| 30 |
+
linear = nn.Linear(in_features=5120, out_features=155136, bias=False, device='cuda', dtype=torch.bfloat16)
|
| 31 |
+
|
| 32 |
+
logits = linear(hidden_states)
|
| 33 |
+
|
| 34 |
+
# logits = logits.float()
|
| 35 |
+
labels = torch.randint(low=0, high=155136, size=(2048,), device='cuda')
|
| 36 |
+
|
| 37 |
+
log_gpu_memory_usage('before computation')
|
| 38 |
+
# output = checkpoint.checkpoint(logprobs_from_logits, logits, labels, use_reentrant=True)
|
| 39 |
+
output = -cross_entropy_loss(logits, labels)[0]
|
| 40 |
+
# output = logprobs_from_logits(logits, labels)
|
| 41 |
+
log_gpu_memory_usage('After forward')
|
| 42 |
+
output.sum().backward()
|
| 43 |
+
log_gpu_memory_usage('After backward')
|
| 44 |
+
|
| 45 |
+
groundtruth = logprobs_from_logits_naive(logits.float(), labels)
|
| 46 |
+
|
| 47 |
+
torch.testing.assert_close(output, groundtruth)
|
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 verl.utils.model import create_random_mask
|
| 16 |
+
from flash_attn.bert_padding import unpad_input
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_log_probs_from_logits_response_rmpad():
|
| 21 |
+
from verl.utils.torch_functional import log_probs_from_logits_response, log_probs_from_logits_response_rmpad
|
| 22 |
+
vocab_size = 32000
|
| 23 |
+
batch_size = 2
|
| 24 |
+
prompt_length = 256
|
| 25 |
+
response_length = 256
|
| 26 |
+
|
| 27 |
+
input_ids = torch.randint(low=0, high=vocab_size, size=(batch_size, prompt_length + response_length), device='cuda')
|
| 28 |
+
attention_mask = create_random_mask(input_ids=input_ids,
|
| 29 |
+
max_ratio_of_left_padding=0.2,
|
| 30 |
+
max_ratio_of_valid_token=0.8,
|
| 31 |
+
min_ratio_of_valid_token=0.6)
|
| 32 |
+
|
| 33 |
+
response_mask = attention_mask[:, -response_length:]
|
| 34 |
+
|
| 35 |
+
assert torch.all(response_mask[:, 0] == 1)
|
| 36 |
+
|
| 37 |
+
logits = torch.randn(batch_size, prompt_length + response_length, vocab_size, device='cuda')
|
| 38 |
+
logits_rmpad = unpad_input(logits, attention_mask)[0]
|
| 39 |
+
|
| 40 |
+
expected_output = log_probs_from_logits_response(input_ids=input_ids,
|
| 41 |
+
logits=logits,
|
| 42 |
+
response_length=response_length)
|
| 43 |
+
actual_output = log_probs_from_logits_response_rmpad(input_ids=input_ids,
|
| 44 |
+
attention_mask=attention_mask,
|
| 45 |
+
logits_rmpad=logits_rmpad,
|
| 46 |
+
response_length=response_length)
|
| 47 |
+
|
| 48 |
+
# This should bitwise align as only this operation only contains gather operators
|
| 49 |
+
assert torch.all(torch.eq(actual_output * response_mask, expected_output * response_mask))
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def test_lr_scheduler():
|
| 53 |
+
from torch import nn
|
| 54 |
+
model = nn.Linear(10, 10)
|
| 55 |
+
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
|
| 56 |
+
|
| 57 |
+
from verl.utils.torch_functional import get_constant_schedule_with_warmup
|
| 58 |
+
constant_lr = get_constant_schedule_with_warmup(optimizer=optimizer, num_warmup_steps=2)
|
| 59 |
+
|
| 60 |
+
lr_lst = []
|
| 61 |
+
|
| 62 |
+
for _ in range(5):
|
| 63 |
+
lr_lst.append(constant_lr.get_last_lr()[0])
|
| 64 |
+
constant_lr.step()
|
| 65 |
+
|
| 66 |
+
torch.testing.assert_close(lr_lst, [0.0, 0.0005, 0.001, 0.001, 0.001])
|
| 67 |
+
|
| 68 |
+
from verl.utils.torch_functional import get_cosine_schedule_with_warmup
|
| 69 |
+
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
|
| 70 |
+
cosine_lr = get_cosine_schedule_with_warmup(optimizer=optimizer,
|
| 71 |
+
num_warmup_steps=2,
|
| 72 |
+
num_training_steps=5,
|
| 73 |
+
min_lr_ratio=0.1)
|
| 74 |
+
|
| 75 |
+
lr_lst = []
|
| 76 |
+
|
| 77 |
+
for _ in range(5):
|
| 78 |
+
lr_lst.append(cosine_lr.get_last_lr()[0])
|
| 79 |
+
cosine_lr.step()
|
| 80 |
+
|
| 81 |
+
torch.testing.assert_close(lr_lst, [0.0, 0.0005, 0.001, 0.0007750000000000002, 0.0003250000000000002])
|
|
@@ -52,7 +52,7 @@ class DummyWorker(Worker):
|
|
| 52 |
def test_data_transfer():
|
| 53 |
ray.init()
|
| 54 |
# construct resource pool
|
| 55 |
-
resource_pool = RayResourcePool([
|
| 56 |
cls_with_init = RayClassWithInitArgs(cls=DummyWorker)
|
| 57 |
# construct worker group
|
| 58 |
wg = RayWorkerGroup(resource_pool, cls_with_init)
|
|
|
|
| 52 |
def test_data_transfer():
|
| 53 |
ray.init()
|
| 54 |
# construct resource pool
|
| 55 |
+
resource_pool = RayResourcePool([8])
|
| 56 |
cls_with_init = RayClassWithInitArgs(cls=DummyWorker)
|
| 57 |
# construct worker group
|
| 58 |
wg = RayWorkerGroup(resource_pool, cls_with_init)
|
|
@@ -1,89 +0,0 @@
|
|
| 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 verl.single_controller.remote import remote, RemoteBackend, SharedResourcePool
|
| 16 |
-
from verl.single_controller.base.decorator import register, Dispatch
|
| 17 |
-
from verl.single_controller.base.worker import Worker
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
@remote(process_on_nodes=[3], use_gpu=True, name_prefix="actor", sharing=SharedResourcePool)
|
| 21 |
-
class Actor(Worker):
|
| 22 |
-
...
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
@remote(process_on_nodes=[3], use_gpu=True, name_prefix="critic", sharing=SharedResourcePool)
|
| 26 |
-
class Critic(Worker):
|
| 27 |
-
...
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
@remote(process_on_nodes=[2], use_gpu=True, name_prefix="reward", sharing=SharedResourcePool.from_role("actor"))
|
| 31 |
-
class Reward(Worker):
|
| 32 |
-
...
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
@remote(process_on_nodes=[2], use_gpu=True, name_prefix="ref", sharing=SharedResourcePool.from_role("actor", "critic"))
|
| 36 |
-
class Ref(Worker):
|
| 37 |
-
...
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
@remote(process_on_nodes=[1], use_gpu=True, name_prefix="sec_rm", sharing=SharedResourcePool.from_role("any"))
|
| 41 |
-
class SecRM(Worker):
|
| 42 |
-
...
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
def test():
|
| 46 |
-
print("Remote.init_distributed")
|
| 47 |
-
remote.init_distributed(backend=RemoteBackend.RAY)
|
| 48 |
-
|
| 49 |
-
print("create actor worker group")
|
| 50 |
-
actor = Actor()
|
| 51 |
-
|
| 52 |
-
print("create critic worker group")
|
| 53 |
-
critic = Critic()
|
| 54 |
-
|
| 55 |
-
print("create rm worker group")
|
| 56 |
-
reward = Reward()
|
| 57 |
-
|
| 58 |
-
print("create ref worker group")
|
| 59 |
-
ref = Ref()
|
| 60 |
-
|
| 61 |
-
print("create sec_rm worker group")
|
| 62 |
-
sec_rm = SecRM()
|
| 63 |
-
|
| 64 |
-
actor_gpus = actor.execute_all_sync("get_cuda_visible_devices")
|
| 65 |
-
critic_gpus = critic.execute_all_sync("get_cuda_visible_devices")
|
| 66 |
-
reward_gpus = reward.execute_all_sync("get_cuda_visible_devices")
|
| 67 |
-
ref_gpus = ref.execute_all_sync("get_cuda_visible_devices")
|
| 68 |
-
sec_rm_gpus = sec_rm.execute_all_sync("get_cuda_visible_devices")
|
| 69 |
-
|
| 70 |
-
for gpu in actor_gpus:
|
| 71 |
-
assert gpu not in critic_gpus, f"actor gpus = {actor_gpus}, critic gpus = {critic_gpus}"
|
| 72 |
-
|
| 73 |
-
for gpu in critic_gpus:
|
| 74 |
-
assert gpu not in actor_gpus, f"actor gpus = {actor_gpus}, critic gpus = {critic_gpus}"
|
| 75 |
-
|
| 76 |
-
for gpu in reward_gpus:
|
| 77 |
-
assert gpu in actor_gpus, f"actor gpus = {actor_gpus}, reward gpus = {reward_gpus}"
|
| 78 |
-
|
| 79 |
-
for gpu in ref_gpus:
|
| 80 |
-
assert gpu in actor_gpus + critic_gpus, \
|
| 81 |
-
f"actor gpus = {actor_gpus}, critic gpus = {critic_gpus}, ref gpus = {ref_gpus}"
|
| 82 |
-
|
| 83 |
-
for gpu in sec_rm_gpus:
|
| 84 |
-
assert gpu in actor_gpus + critic_gpus, \
|
| 85 |
-
f"actor gpus = {actor_gpus}, critic gpus = {critic_gpus}, sec rm gpus = {sec_rm_gpus}"
|
| 86 |
-
|
| 87 |
-
# for ci only
|
| 88 |
-
import ray
|
| 89 |
-
ray.shutdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM
|
| 17 |
+
from torch.distributed.fsdp import FullyShardedDataParallel as FSDP, ShardingStrategy, MixedPrecision, CPUOffload
|
| 18 |
+
from torch.distributed.fsdp.api import ShardingStrategy, ShardedStateDictConfig, StateDictType
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
from verl.utils.distributed import initialize_global_process_group
|
| 22 |
+
from verl.third_party.vllm import LLM
|
| 23 |
+
|
| 24 |
+
from vllm import SamplingParams
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def main():
|
| 28 |
+
assert torch.cuda.is_available(), 'CUDA must be present to run FSDP vLLM example'
|
| 29 |
+
local_rank, rank, world_size = initialize_global_process_group()
|
| 30 |
+
|
| 31 |
+
local_cache_path = '~/.cache/verl/rlhf'
|
| 32 |
+
local_cache_path = os.path.expanduser(local_cache_path)
|
| 33 |
+
hdfs_path = 'Qwen/Qwen2-7B-Instruct'
|
| 34 |
+
|
| 35 |
+
from verl.utils.fs import copy_local_path_from_hdfs
|
| 36 |
+
local_model_path = copy_local_path_from_hdfs(src=hdfs_path, cache_dir=local_cache_path)
|
| 37 |
+
tokenizer = AutoTokenizer.from_pretrained(local_model_path, trust_remote_code=True)
|
| 38 |
+
actor_model_config = AutoConfig.from_pretrained(local_model_path, trust_remote_code=True)
|
| 39 |
+
with torch.device("cuda"):
|
| 40 |
+
actor_model = AutoModelForCausalLM.from_pretrained(local_model_path, trust_remote_code=True)
|
| 41 |
+
actor_model.to(torch.bfloat16)
|
| 42 |
+
|
| 43 |
+
max_prompt_length = 16
|
| 44 |
+
response_length = 32
|
| 45 |
+
preencode_prompts = [
|
| 46 |
+
"The president of the United States is",
|
| 47 |
+
"The capital of France is",
|
| 48 |
+
"The future of AI is",
|
| 49 |
+
]
|
| 50 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 51 |
+
prompts = tokenizer(preencode_prompts, return_tensors='pt', padding=True)
|
| 52 |
+
input_ids = prompts['input_ids']
|
| 53 |
+
attention_mask = prompts['attention_mask']
|
| 54 |
+
from verl.utils.torch_functional import pad_sequence_to_length
|
| 55 |
+
input_ids = pad_sequence_to_length(input_ids, max_prompt_length, tokenizer.pad_token_id, left_pad=True).cuda()
|
| 56 |
+
attention_mask = pad_sequence_to_length(attention_mask, max_prompt_length, 0, left_pad=True).cuda()
|
| 57 |
+
|
| 58 |
+
from transformers import GenerationConfig
|
| 59 |
+
generation_config = GenerationConfig(do_sample=False)
|
| 60 |
+
actor_model.cuda()
|
| 61 |
+
output = actor_model.generate(
|
| 62 |
+
input_ids=input_ids,
|
| 63 |
+
attention_mask=attention_mask,
|
| 64 |
+
max_new_tokens=32,
|
| 65 |
+
# max_length=max_length,
|
| 66 |
+
eos_token_id=tokenizer.eos_token_id,
|
| 67 |
+
pad_token_id=tokenizer.pad_token_id,
|
| 68 |
+
generation_config=generation_config,
|
| 69 |
+
# renormalize_logits=True,
|
| 70 |
+
output_scores=False, # this is potentially very large
|
| 71 |
+
return_dict_in_generate=True,
|
| 72 |
+
use_cache=False) # may OOM when use_cache = True
|
| 73 |
+
seq = output.sequences
|
| 74 |
+
response = seq[:, max_prompt_length:]
|
| 75 |
+
|
| 76 |
+
print(f'hf response: {tokenizer.batch_decode(response)}')
|
| 77 |
+
|
| 78 |
+
tensor_model_parallel_size = 4
|
| 79 |
+
from torch.distributed.device_mesh import init_device_mesh
|
| 80 |
+
device_mesh = init_device_mesh('cuda', mesh_shape=(world_size,), mesh_dim_names=['fsdp'])
|
| 81 |
+
|
| 82 |
+
mixed_precision = MixedPrecision(param_dtype=torch.bfloat16, reduce_dtype=torch.float32, buffer_dtype=torch.float32)
|
| 83 |
+
fsdp_model = FSDP(actor_model,
|
| 84 |
+
use_orig_params=True,
|
| 85 |
+
auto_wrap_policy=None,
|
| 86 |
+
device_id=torch.cuda.current_device(),
|
| 87 |
+
sharding_strategy=ShardingStrategy.FULL_SHARD,
|
| 88 |
+
mixed_precision=mixed_precision,
|
| 89 |
+
cpu_offload=CPUOffload(offload_params=False),
|
| 90 |
+
sync_module_states=False,
|
| 91 |
+
device_mesh=device_mesh)
|
| 92 |
+
|
| 93 |
+
FSDP.set_state_dict_type(fsdp_model,
|
| 94 |
+
state_dict_type=StateDictType.SHARDED_STATE_DICT,
|
| 95 |
+
state_dict_config=ShardedStateDictConfig())
|
| 96 |
+
|
| 97 |
+
state_dict = fsdp_model.state_dict()
|
| 98 |
+
|
| 99 |
+
sampling_params = SamplingParams(temperature=0,
|
| 100 |
+
top_p=1,
|
| 101 |
+
n=1,
|
| 102 |
+
max_tokens=response_length,
|
| 103 |
+
logprobs=1,
|
| 104 |
+
ignore_eos=True,
|
| 105 |
+
detokenize=False)
|
| 106 |
+
|
| 107 |
+
print(actor_model_config)
|
| 108 |
+
llm = LLM(model=None,
|
| 109 |
+
tokenizer=tokenizer,
|
| 110 |
+
model_hf_config=actor_model_config,
|
| 111 |
+
tensor_parallel_size=tensor_model_parallel_size,
|
| 112 |
+
enforce_eager=True,
|
| 113 |
+
dtype='bfloat16',
|
| 114 |
+
load_format='dummy_dtensor',
|
| 115 |
+
gpu_memory_utilization=0.1,
|
| 116 |
+
trust_remote_code=True)
|
| 117 |
+
|
| 118 |
+
llm.sync_model_weights(actor_weights=state_dict, load_format='dtensor')
|
| 119 |
+
|
| 120 |
+
input_ids = input_ids.cuda()
|
| 121 |
+
attention_mask = attention_mask.cuda()
|
| 122 |
+
idx_list = []
|
| 123 |
+
batch_size = input_ids.shape[0]
|
| 124 |
+
|
| 125 |
+
pad_token_id = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else tokenizer.eos_token_id
|
| 126 |
+
from verl.workers.rollout.vllm_rollout.vllm_rollout import _pre_process_inputs
|
| 127 |
+
for i in range(batch_size):
|
| 128 |
+
idx_list.append(_pre_process_inputs(pad_token_id, input_ids[i]))
|
| 129 |
+
print('start generation')
|
| 130 |
+
outputs = llm.generate(prompt_token_ids=idx_list, sampling_params=sampling_params, use_tqdm=False)
|
| 131 |
+
vllm_output = outputs[0].cuda()
|
| 132 |
+
if torch.distributed.get_rank() == 0:
|
| 133 |
+
print(f'hf response: {tokenizer.batch_decode(response)}')
|
| 134 |
+
print(f'vllm response: {tokenizer.batch_decode(vllm_output)}')
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
if __name__ == "__main__":
|
| 138 |
+
main()
|
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 torch
|
| 17 |
+
import transformers
|
| 18 |
+
|
| 19 |
+
from verl.third_party.vllm import LLM, vllm_version
|
| 20 |
+
from verl.utils.model import update_model_config
|
| 21 |
+
from vllm import SamplingParams
|
| 22 |
+
from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM
|
| 23 |
+
|
| 24 |
+
from transformers import GenerationConfig
|
| 25 |
+
|
| 26 |
+
from verl.utils.torch_functional import pad_sequence_to_length
|
| 27 |
+
from verl.workers.rollout.vllm_rollout.vllm_rollout import _pre_process_inputs
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def levenshtein(s1, s2):
|
| 31 |
+
m, n = len(s1), len(s2)
|
| 32 |
+
# Initialize matrix of zeros
|
| 33 |
+
dp = [[0] * (n + 1) for _ in range(m + 1)]
|
| 34 |
+
# Initialize first column and first row of the matrix
|
| 35 |
+
for i in range(m + 1):
|
| 36 |
+
dp[i][0] = i # Deletion from s1 to empty string
|
| 37 |
+
for j in range(n + 1):
|
| 38 |
+
dp[0][j] = j # Insertion to s1 from empty string
|
| 39 |
+
# Compute the Levenshtein distance matrix
|
| 40 |
+
for i in range(1, m + 1):
|
| 41 |
+
for j in range(1, n + 1):
|
| 42 |
+
cost = 0 if s1[i - 1] == s2[j - 1] else 1 # No cost if characters match
|
| 43 |
+
dp[i][j] = min(
|
| 44 |
+
dp[i - 1][j] + 1, # Deletion
|
| 45 |
+
dp[i][j - 1] + 1, # Insertion
|
| 46 |
+
dp[i - 1][j - 1] + cost # Substitution
|
| 47 |
+
)
|
| 48 |
+
return dp[m][n]
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def are_lists_similar(a, b):
|
| 52 |
+
if len(a) != len(b):
|
| 53 |
+
print("The lists are of different lengths.")
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
total_length = 0
|
| 57 |
+
total_diff = 0
|
| 58 |
+
|
| 59 |
+
for s1, s2 in zip(a, b):
|
| 60 |
+
max_len = max(len(s1), len(s2))
|
| 61 |
+
total_length += max_len
|
| 62 |
+
diff = levenshtein(s1, s2)
|
| 63 |
+
total_diff += diff
|
| 64 |
+
print(f"Comparing strings:\n{s1}\n{s2}\nDifference: {diff} characters\n")
|
| 65 |
+
|
| 66 |
+
percentage_difference = (total_diff / total_length) * 100
|
| 67 |
+
print(f"Total difference: {percentage_difference:.2f}%")
|
| 68 |
+
|
| 69 |
+
return percentage_difference <= 10
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def test_vllm_with_hf():
|
| 73 |
+
assert torch.cuda.device_count() >= 2, 'At least 2 GPUs is required to run tp+dp tests.'
|
| 74 |
+
|
| 75 |
+
# fill rollout config
|
| 76 |
+
max_prompt_length = 16
|
| 77 |
+
max_response_length = 16
|
| 78 |
+
|
| 79 |
+
# Initialize model and token
|
| 80 |
+
local_cache_path = '~/.cache/verl/rlhf'
|
| 81 |
+
local_cache_path = os.path.expanduser(local_cache_path)
|
| 82 |
+
hdfs_path = 'deepseek-ai/deepseek-llm-7b-chat'
|
| 83 |
+
from verl.utils.fs import copy_local_path_from_hdfs
|
| 84 |
+
local_model_path = copy_local_path_from_hdfs(src=hdfs_path, cache_dir=local_cache_path)
|
| 85 |
+
tokenizer = AutoTokenizer.from_pretrained(local_model_path)
|
| 86 |
+
|
| 87 |
+
preencode_prompts = [
|
| 88 |
+
"Who won the Champions League in 2019?",
|
| 89 |
+
"The founder of Apple is",
|
| 90 |
+
"What's your name",
|
| 91 |
+
]
|
| 92 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 93 |
+
prompts = tokenizer(preencode_prompts, return_tensors='pt', padding=True)
|
| 94 |
+
input_ids = prompts['input_ids']
|
| 95 |
+
attention_mask = prompts['attention_mask']
|
| 96 |
+
|
| 97 |
+
input_ids = pad_sequence_to_length(input_ids, max_prompt_length, tokenizer.pad_token_id, left_pad=True)
|
| 98 |
+
attention_mask = pad_sequence_to_length(attention_mask, max_prompt_length, 0, left_pad=True)
|
| 99 |
+
|
| 100 |
+
actor_model = AutoModelForCausalLM.from_pretrained(local_model_path)
|
| 101 |
+
actor_model.to(torch.bfloat16)
|
| 102 |
+
|
| 103 |
+
actor_model_config = AutoConfig.from_pretrained(local_model_path)
|
| 104 |
+
|
| 105 |
+
temperature = 0
|
| 106 |
+
top_p = 1
|
| 107 |
+
|
| 108 |
+
kwargs = dict(n=1,
|
| 109 |
+
temperature=temperature,
|
| 110 |
+
top_p=top_p,
|
| 111 |
+
max_tokens=max_response_length,
|
| 112 |
+
logprobs=1,
|
| 113 |
+
ignore_eos=True)
|
| 114 |
+
|
| 115 |
+
if vllm_version in ('0.4.2', '0.5.4', '0.6.3'):
|
| 116 |
+
kwargs['detokenize'] = False
|
| 117 |
+
sampling_params = SamplingParams(**kwargs)
|
| 118 |
+
|
| 119 |
+
tensor_parallel_size = 2
|
| 120 |
+
|
| 121 |
+
llm = LLM(model=actor_model,
|
| 122 |
+
tokenizer=tokenizer,
|
| 123 |
+
model_hf_config=actor_model_config,
|
| 124 |
+
tensor_parallel_size=tensor_parallel_size,
|
| 125 |
+
dtype='bfloat16',
|
| 126 |
+
gpu_memory_utilization=0.1,
|
| 127 |
+
load_format='hf')
|
| 128 |
+
|
| 129 |
+
print('start generation')
|
| 130 |
+
input_ids = input_ids.cuda()
|
| 131 |
+
attention_mask = attention_mask.cuda()
|
| 132 |
+
batch_size = input_ids.size(0)
|
| 133 |
+
|
| 134 |
+
idx_list = []
|
| 135 |
+
# parse idx from torch.Tensor to List[List[str]]
|
| 136 |
+
for i in range(batch_size):
|
| 137 |
+
idx_list.append(_pre_process_inputs(tokenizer.pad_token_id, input_ids[i]))
|
| 138 |
+
outputs = llm.generate(prompt_token_ids=idx_list, sampling_params=sampling_params, use_tqdm=False)
|
| 139 |
+
vllm_output = outputs[0].cuda()
|
| 140 |
+
llm.free_cache_engine()
|
| 141 |
+
llm = None
|
| 142 |
+
import gc
|
| 143 |
+
torch.cuda.empty_cache()
|
| 144 |
+
gc.collect()
|
| 145 |
+
|
| 146 |
+
generation_config = GenerationConfig(do_sample=False)
|
| 147 |
+
actor_model.cuda()
|
| 148 |
+
output = actor_model.generate(
|
| 149 |
+
input_ids=input_ids,
|
| 150 |
+
attention_mask=attention_mask,
|
| 151 |
+
max_new_tokens=max_response_length,
|
| 152 |
+
# max_length=max_length,
|
| 153 |
+
eos_token_id=tokenizer.eos_token_id,
|
| 154 |
+
pad_token_id=tokenizer.pad_token_id,
|
| 155 |
+
generation_config=generation_config,
|
| 156 |
+
# renormalize_logits=True,
|
| 157 |
+
output_scores=False, # this is potentially very large
|
| 158 |
+
return_dict_in_generate=True,
|
| 159 |
+
use_cache=False) # may OOM when use_cache = True
|
| 160 |
+
seq = output.sequences
|
| 161 |
+
response = seq[:, max_prompt_length:]
|
| 162 |
+
|
| 163 |
+
hf_response_tokens = tokenizer.batch_decode(response)
|
| 164 |
+
vllm_response_tokens = tokenizer.batch_decode(vllm_output)
|
| 165 |
+
|
| 166 |
+
print(f'hf response: {hf_response_tokens}')
|
| 167 |
+
print(f'vllm response: {vllm_response_tokens}')
|
| 168 |
+
assert are_lists_similar(hf_response_tokens, vllm_response_tokens), \
|
| 169 |
+
f'Strings differ more than 10%:\n'
|
| 170 |
+
print('Check Pass')
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# if __name__ == "__main__":
|
| 174 |
+
# test_vllm_with_hf()
|
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
def test_import():
|
| 17 |
+
import verl
|
| 18 |
+
print(verl.__version__)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def test_single_controller_import():
|
| 22 |
+
import verl.single_controller
|
| 23 |
+
print(verl.single_controller.__version__)
|
|
@@ -17,7 +17,7 @@ the class for Worker
|
|
| 17 |
import os
|
| 18 |
import socket
|
| 19 |
from dataclasses import dataclass
|
| 20 |
-
from verl.single_controller.base.decorator import register, Dispatch
|
| 21 |
|
| 22 |
|
| 23 |
@dataclass
|
|
@@ -179,3 +179,8 @@ class Worker(WorkerHelper):
|
|
| 179 |
def execute_with_func_generator(self, func, *args, **kwargs):
|
| 180 |
ret_proto = func(self, *args, **kwargs)
|
| 181 |
return ret_proto
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
import os
|
| 18 |
import socket
|
| 19 |
from dataclasses import dataclass
|
| 20 |
+
from verl.single_controller.base.decorator import register, Dispatch, Execute
|
| 21 |
|
| 22 |
|
| 23 |
@dataclass
|
|
|
|
| 179 |
def execute_with_func_generator(self, func, *args, **kwargs):
|
| 180 |
ret_proto = func(self, *args, **kwargs)
|
| 181 |
return ret_proto
|
| 182 |
+
|
| 183 |
+
@register(dispatch_mode=Dispatch.ALL_TO_ALL, execute_mode=Execute.RANK_ZERO)
|
| 184 |
+
def execute_func_rank_zero(self, func, *args, **kwargs):
|
| 185 |
+
result = func(*args, **kwargs)
|
| 186 |
+
return result
|
|
@@ -196,6 +196,9 @@ class HFLoader(BaseModelLoader):
|
|
| 196 |
raise ValueError(f"Model loader extra config is not supported for "
|
| 197 |
f"load format {load_config.load_format}")
|
| 198 |
|
|
|
|
|
|
|
|
|
|
| 199 |
def _get_weights_iterator(self, actor_model: Union[PreTrainedModel, Dict]):
|
| 200 |
if isinstance(actor_model, Dict):
|
| 201 |
return actor_model.items()
|
|
@@ -241,6 +244,9 @@ class DTensorLoader(BaseModelLoader):
|
|
| 241 |
raise ValueError(f"Model loader extra config is not supported for "
|
| 242 |
f"load format {load_config.load_format}")
|
| 243 |
|
|
|
|
|
|
|
|
|
|
| 244 |
def _get_weights_iterator(actor_model: Union[PreTrainedModel, Dict]):
|
| 245 |
# NOTE(shengguangming) Load the weights from the actor model
|
| 246 |
pass
|
|
|
|
| 196 |
raise ValueError(f"Model loader extra config is not supported for "
|
| 197 |
f"load format {load_config.load_format}")
|
| 198 |
|
| 199 |
+
def download_model(self, model_config: ModelConfig) -> None:
|
| 200 |
+
pass # Nothing to download
|
| 201 |
+
|
| 202 |
def _get_weights_iterator(self, actor_model: Union[PreTrainedModel, Dict]):
|
| 203 |
if isinstance(actor_model, Dict):
|
| 204 |
return actor_model.items()
|
|
|
|
| 244 |
raise ValueError(f"Model loader extra config is not supported for "
|
| 245 |
f"load format {load_config.load_format}")
|
| 246 |
|
| 247 |
+
def download_model(self, model_config: ModelConfig) -> None:
|
| 248 |
+
pass # Nothing to download
|
| 249 |
+
|
| 250 |
def _get_weights_iterator(actor_model: Union[PreTrainedModel, Dict]):
|
| 251 |
# NOTE(shengguangming) Load the weights from the actor model
|
| 252 |
pass
|
|
@@ -335,7 +335,7 @@ class RayPPOTrainer(object):
|
|
| 335 |
# test_batch = test_batch.to('cuda')
|
| 336 |
|
| 337 |
# we only do validation on rule-based rm
|
| 338 |
-
if test_batch[0].non_tensor_batch['reward_model']['style'] == 'model':
|
| 339 |
return {}
|
| 340 |
|
| 341 |
test_gen_batch = test_batch.pop(['input_ids', 'attention_mask', 'position_ids'])
|
|
|
|
| 335 |
# test_batch = test_batch.to('cuda')
|
| 336 |
|
| 337 |
# we only do validation on rule-based rm
|
| 338 |
+
if self.config.reward_model.enable and test_batch[0].non_tensor_batch['reward_model']['style'] == 'model':
|
| 339 |
return {}
|
| 340 |
|
| 341 |
test_gen_batch = test_batch.pop(['input_ids', 'attention_mask', 'position_ids'])
|