diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 362b9e5..5bbcb71 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,3 +67,25 @@ jobs: pylint --disable=trailing-whitespace,missing-class-docstring,missing-final-newline,trailing-newlines \ --fail-under=9.0 \ $(git ls-files '*.py') || echo "::warning::Pylint check failed, but the workflow will continue." + + python-build-n-publish: + name: Build and publish Python distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: | + git fetch --all --tags + python setup.py sdist bdist_wheel + twine upload --verbose dist/* diff --git a/README.md b/README.md index 4418aff..49112bd 100644 --- a/README.md +++ b/README.md @@ -121,16 +121,12 @@ You're all set to dive into goal-oriented, persona-based, diverse, and multi-tur Please use the following citation: ``` -@InProceedings{smith:20xx:CONFERENCE_TITLE, - author = {Hovhannes Tamoyan}, - title = {LLM Roleplay: Simulating Human-Chatbot Interaction}, - booktitle = {Proceedings of the 20XX Conference on XXXX}, - month = mmm, - year = {20xx}, - address = {Gotham City, USA}, - publisher = {Association for XXX}, - pages = {XXXX--XXXX}, - url = {http://xxxx.xxx} +% todo +@article{anonymous, + title={LLM Roleplay: Simulating Human-Chatbot Interaction}, + author={Hovhannes Tamoyan, Hendrik Schuff, Iryna Gurevych}, + journal={axiv}, + year={2024} } ``` diff --git a/hydra_plugins/roleplay_plugin/__init__.py b/hydra_plugins/roleplay_plugin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/hydra_plugins/roleplay_plugin/roleplay_plugin.py b/hydra_plugins/roleplay_plugin/roleplay_plugin.py deleted file mode 100644 index ca4f9bb..0000000 --- a/hydra_plugins/roleplay_plugin/roleplay_plugin.py +++ /dev/null @@ -1,14 +0,0 @@ -from hydra.core.config_search_path import ConfigSearchPath -from hydra.plugins.search_path_plugin import SearchPathPlugin - -from roleplay.utils.user import get_current_user - -current_user = get_current_user() - - -class roleplayPlugin(SearchPathPlugin): - def manipulate_search_path(self, search_path: ConfigSearchPath) -> None: - search_path.prepend(provider="roleplay", path="pkg://configs") - search_path.prepend(provider="roleplay", path="file://configs") - search_path.prepend(provider="roleplay", path=f"pkg://configs_{current_user}") - search_path.prepend(provider="roleplay", path=f"file://configs_{current_user}") diff --git a/requirements.txt b/requirements.txt index e829075..4156e0c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,4 @@ -aim>=3.18.1 -datasets>=2.13.1 -transformers>=4.36.0 -accelerate>=0.21.0 -scikit-learn>=1.3.0 -sentencepiece==0.2.0 -hydra-core==1.3.2 -hydra-submitit-launcher==1.2.0 -iopath==0.1.10 -torch==2.1.2 -jsonlines==4.0.0 -protobuf==3.20.3 \ No newline at end of file +jsonlines +tiktoken +langchain +langchain-openai \ No newline at end of file diff --git a/roleplay/VERSION b/roleplay/VERSION index 6c6aa7c..359a5b9 100644 --- a/roleplay/VERSION +++ b/roleplay/VERSION @@ -1 +1 @@ -0.1.0 \ No newline at end of file +2.0.0 \ No newline at end of file diff --git a/roleplay/__init__.py b/roleplay/__init__.py index 13ee275..e69de29 100644 --- a/roleplay/__init__.py +++ b/roleplay/__init__.py @@ -1,35 +0,0 @@ -import hydra -from aim import Run -from omegaconf import OmegaConf - -from roleplay.utils.launcher import launch, launch_on_slurm -from roleplay.utils.slurm import is_submitit_available - - -@hydra.main(version_base=None, config_path="config", config_name="main") -def main(args): - cfg = OmegaConf.create(OmegaConf.to_container(args, resolve=True, enum_to_str=True)) - - aim_run = Run( - repo=cfg.aim.repo, - experiment=cfg.action_config.experiment_name, - ) - aim_run.set("cfg", cfg, strict=False) - - if cfg.slurm.use_slurm: - assert is_submitit_available(), "Please 'pip install submitit' to schedule jobs on SLURM" - - launch_on_slurm( - action_name=cfg.action_name, - cfg=cfg, - aim_run=aim_run, - ) - else: - launch(action_name=cfg.action_name, cfg=cfg, aim_run=aim_run) - - if aim_run.active: - aim_run.close() - - -if __name__ == "__main__": - main() diff --git a/roleplay/actions/roleplay.py b/roleplay/actions/roleplay.py index 76ff04a..b2fca94 100644 --- a/roleplay/actions/roleplay.py +++ b/roleplay/actions/roleplay.py @@ -8,8 +8,9 @@ from omegaconf import DictConfig from tqdm import tqdm -from roleplay.common.action import Action -from roleplay.common.dataset import Dataset +from urartu.common.action import Action +from urartu.common.dataset import Dataset + from roleplay.common.persona import Persona @@ -24,7 +25,7 @@ def track(self, prompt, name, context=None): context=context, ) - def run(self): + def main(self): self.aim_run["num_no_prompts"] = 0 self.aim_run["num_multiple_prompts"] = 0 self.aim_run["num_non_coherent"] = 0 @@ -221,4 +222,4 @@ def run(self): def main(cfg: DictConfig, aim_run: Run): roleplay = Roleplay(cfg, aim_run) - roleplay.run() + roleplay.main() diff --git a/roleplay/common/action.py b/roleplay/common/action.py deleted file mode 100644 index 57b226b..0000000 --- a/roleplay/common/action.py +++ /dev/null @@ -1,9 +0,0 @@ -from aim import Run -from omegaconf import DictConfig - - -class Action: - def __init__(self, cfg: DictConfig, aim_run: Run): - self.cfg = cfg - self.action_cfg = cfg.action_config - self.aim_run = aim_run diff --git a/roleplay/common/dataset.py b/roleplay/common/dataset.py deleted file mode 100644 index 6048971..0000000 --- a/roleplay/common/dataset.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Any, Dict, List -import hydra - -from torch.utils.data import DataLoader - -from roleplay.common.model import Model - - -class Dataset: - def __init__(self, cfg: List[Dict[str, Any]]) -> None: - self.cfg = cfg - self.dataset = None - self._get_dataset() - - @staticmethod - def get_dataset(cfg): - return hydra.utils.instantiate(cfg.type, cfg) - - def _get_dataset(self): - raise NotImplementedError("method '_get_dataset' is not implemented") - - def get_dataloader(self, dataloader_cfg: List[Dict[str, Any]], tokenizer, input_key): - return DataLoader( - self.dataset, - batch_size=dataloader_cfg.get("batch_size"), - num_workers=dataloader_cfg.get("num_workers", 2), - shuffle=dataloader_cfg.get("shuffle"), - collate_fn=lambda data: Model.collate_tokenize(data=data, tokenizer=tokenizer, input_key=input_key), - ) diff --git a/roleplay/common/device.py b/roleplay/common/device.py deleted file mode 100644 index 274105a..0000000 --- a/roleplay/common/device.py +++ /dev/null @@ -1,4 +0,0 @@ -import torch - -DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") -AUTO_DEVICE = "auto" diff --git a/roleplay/common/metric.py b/roleplay/common/metric.py deleted file mode 100644 index 5182787..0000000 --- a/roleplay/common/metric.py +++ /dev/null @@ -1,9 +0,0 @@ -from typing import Any, Dict, List - -import evaluate - - -class Metric: - @staticmethod - def get_metric(cfg: List[Dict[str, Any]]): - return evaluate.load(cfg.name) diff --git a/roleplay/common/model.py b/roleplay/common/model.py index abeaa61..c7d367f 100644 --- a/roleplay/common/model.py +++ b/roleplay/common/model.py @@ -5,7 +5,7 @@ import string from typing import Any, Dict, List -from roleplay.common.device import DEVICE +from urartu.common.device import DEVICE class Model: diff --git a/roleplay/config/__init__.py b/roleplay/config/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/roleplay/config/action_config/defaults.yaml b/roleplay/config/action_config/defaults.yaml deleted file mode 100644 index 8e6550d..0000000 --- a/roleplay/config/action_config/defaults.yaml +++ /dev/null @@ -1,6 +0,0 @@ -action_config: - workdir: ??? - experiment_name: ??? - debug: false - - task: ??? diff --git a/roleplay/config/main.yaml b/roleplay/config/main.yaml deleted file mode 100644 index 06a59a4..0000000 --- a/roleplay/config/main.yaml +++ /dev/null @@ -1,43 +0,0 @@ -defaults: - - _self_ - - action_config: defaults - -action_name: ??? - -aim: - repo: "aim://0.0.0.0:53800" - -seed: 5 - -slurm: - # Whether or not to run the job on SLURM - use_slurm: false - # Name of the job on SLURM - name: "roleplay" - # Comment of the job on SLURM - comment: "roleplay job" - # Partition of SLURM on which to run the job. This is a required field if using SLURM. - partition: "" - account: "" - # Where the logs produced by the SLURM jobs will be output - log_folder: "./slurm_logs" - # Maximum number of hours / minutes needed by the job to complete. Above this limit, the job might be pre-empted. - time_hours: 1 - time_minutes: 0 - # Additional constraints on the hardware of the nodes to allocate (roleplay 'volta' to select a volta GPU) - constraint: "" - # GB of RAM memory to allocate for each node - mem_gb: 100 - # TCP port on which the workers will synchronize themselves with torch distributed - port_id: 40050 - # Number of CPUs per GPUs to request on the cluster. - num_cpu_per_proc: 4 - # Number of GPUs per node to request on the cluster. - num_gpu_per_node: 4 - # Number of nodes to request on the cluster. - num_nodes: 1 - # Number of processes per node to request on the cluster. - num_proc_per_node: 1 - # Any other parameters for slurm (e.g. account, hint, distribution, etc.,) as dictated by submitit. - # Please see https://github.com/facebookincubator/submitit/issues/23#issuecomment-695217824. - additional_parameters: {} diff --git a/configs/__init__.py b/roleplay/configs/__init__.py similarity index 100% rename from configs/__init__.py rename to roleplay/configs/__init__.py diff --git a/configs/action_config/roleplay.yaml b/roleplay/configs/action_config/roleplay.yaml similarity index 100% rename from configs/action_config/roleplay.yaml rename to roleplay/configs/action_config/roleplay.yaml diff --git a/configs/action_config/task/model_inquirer/falcon.yaml b/roleplay/configs/action_config/task/model_inquirer/falcon.yaml similarity index 100% rename from configs/action_config/task/model_inquirer/falcon.yaml rename to roleplay/configs/action_config/task/model_inquirer/falcon.yaml diff --git a/configs/action_config/task/model_inquirer/gpt3.5.yaml b/roleplay/configs/action_config/task/model_inquirer/gpt3.5.yaml similarity index 100% rename from configs/action_config/task/model_inquirer/gpt3.5.yaml rename to roleplay/configs/action_config/task/model_inquirer/gpt3.5.yaml diff --git a/configs/action_config/task/model_inquirer/gpt4.yaml b/roleplay/configs/action_config/task/model_inquirer/gpt4.yaml similarity index 100% rename from configs/action_config/task/model_inquirer/gpt4.yaml rename to roleplay/configs/action_config/task/model_inquirer/gpt4.yaml diff --git a/configs/action_config/task/model_inquirer/llama.yaml b/roleplay/configs/action_config/task/model_inquirer/llama.yaml similarity index 100% rename from configs/action_config/task/model_inquirer/llama.yaml rename to roleplay/configs/action_config/task/model_inquirer/llama.yaml diff --git a/configs/action_config/task/model_inquirer/mixtral.yaml b/roleplay/configs/action_config/task/model_inquirer/mixtral.yaml similarity index 100% rename from configs/action_config/task/model_inquirer/mixtral.yaml rename to roleplay/configs/action_config/task/model_inquirer/mixtral.yaml diff --git a/configs/action_config/task/model_inquirer/vicuna.yaml b/roleplay/configs/action_config/task/model_inquirer/vicuna.yaml similarity index 100% rename from configs/action_config/task/model_inquirer/vicuna.yaml rename to roleplay/configs/action_config/task/model_inquirer/vicuna.yaml diff --git a/configs/action_config/task/model_responder/llama.yaml b/roleplay/configs/action_config/task/model_responder/llama.yaml similarity index 100% rename from configs/action_config/task/model_responder/llama.yaml rename to roleplay/configs/action_config/task/model_responder/llama.yaml diff --git a/configs_tamoyan/__init__.py b/roleplay/configs_tamoyan/__init__.py similarity index 100% rename from configs_tamoyan/__init__.py rename to roleplay/configs_tamoyan/__init__.py diff --git a/roleplay/datasets/hf_datasets.py b/roleplay/datasets/hf_datasets.py deleted file mode 100644 index 5556ce8..0000000 --- a/roleplay/datasets/hf_datasets.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Any, Dict, List - -from datasets import load_dataset - -from roleplay.common.dataset import Dataset - - -class HFDatasets(Dataset): - def __init__(self, cfg: List[Dict[str, Any]]) -> None: - super().__init__(cfg) - - def _get_dataset(self): - self.dataset = load_dataset( - self.cfg.name, - self.cfg.get("subset"), - split=self.cfg.get("split"), - ) diff --git a/roleplay/models/causal_lm_model.py b/roleplay/models/causal_lm_model.py index 9cae578..f867bd8 100644 --- a/roleplay/models/causal_lm_model.py +++ b/roleplay/models/causal_lm_model.py @@ -4,7 +4,8 @@ import torch from transformers import AutoModelForCausalLM, AutoTokenizer -from roleplay.common.device import AUTO_DEVICE +from urartu.common.device import DEVICE +from urartu.utils.dtype import eval_dtype from roleplay.common.model import Model @@ -21,8 +22,8 @@ def _load_model(self) -> Tuple[AutoModelForCausalLM, AutoTokenizer]: self.model = AutoModelForCausalLM.from_pretrained( self.cfg.name, cache_dir=self.cfg.cache_dir, - device_map=AUTO_DEVICE, - torch_dtype=eval(self.cfg.dtype), + device_map=DEVICE, + torch_dtype=eval_dtype(self.cfg.dtype), token=self.cfg.api_token, ) diff --git a/roleplay/models/pipeline_model.py b/roleplay/models/pipeline_model.py index 1a59138..495fd65 100644 --- a/roleplay/models/pipeline_model.py +++ b/roleplay/models/pipeline_model.py @@ -1,9 +1,9 @@ from typing import Tuple -import torch # NOQA from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline -from roleplay.common.device import DEVICE +from urartu.common.device import DEVICE +from urartu.utils.dtype import eval_dtype from roleplay.common.model import Model @@ -21,7 +21,7 @@ def _load_model(self) -> Tuple[AutoModelForCausalLM, AutoTokenizer]: self.cfg.name, cache_dir=self.cfg.cache_dir, device_map=DEVICE, - torch_dtype=eval(self.cfg.dtype), + torch_dtype=eval_dtype(self.cfg.dtype), token=self.cfg.api_token, ) self.tokenizer = AutoTokenizer.from_pretrained(self.cfg.name) @@ -30,7 +30,7 @@ def _load_model(self) -> Tuple[AutoModelForCausalLM, AutoTokenizer]: "text-generation", model=model, tokenizer=self.tokenizer, - torch_dtype=eval(self.cfg.dtype), + torch_dtype=eval_dtype(self.cfg.dtype), device_map=DEVICE, eos_token_id=self.tokenizer.eos_token_id, ) diff --git a/roleplay/utils/io.py b/roleplay/utils/io.py deleted file mode 100644 index 8aea647..0000000 --- a/roleplay/utils/io.py +++ /dev/null @@ -1,17 +0,0 @@ -import logging - -from iopath.common.file_io import g_pathmgr - - -def makedir(dir_path): - """ - Create the directory if it does not exist. - """ - is_success = False - try: - if not g_pathmgr.exists(dir_path): - g_pathmgr.mkdirs(dir_path) - is_success = True - except BaseException: - logging.info(f"Error creating directory: {dir_path}") - return is_success diff --git a/roleplay/utils/job.py b/roleplay/utils/job.py deleted file mode 100644 index 35780f8..0000000 --- a/roleplay/utils/job.py +++ /dev/null @@ -1,58 +0,0 @@ -from importlib import import_module -from typing import Dict - -from aim import Run - - -class ResumableSlurmJob: - def __init__(self, action_name: str, cfg: Dict, aim_run: Run): - self.action_name = action_name - self.cfg = cfg - - self.aim_run = None - self.aim_run_hash = aim_run.hash - - def get_aim_run(self): - if self.aim_run is None: - self.aim_run = Run(self.aim_run_hash, repo=self.cfg.aim.repo) - return self.aim_run - - def __call__(self): - import submitit - - environment = submitit.JobEnvironment() - master_ip = environment.hostnames[0] - master_port = self.cfg.slurm.port_id - self.cfg.slurm.init_method = "tcp" - self.cfg.slurm.run_id = f"{master_ip}:{master_port}" - - self.get_aim_run() - self.aim_run.set("job", {"job_id": int(environment.job_id), "hostname": environment.hostname}) - - action = import_module(f"roleplay.actions.{self.action_name}") - action.main(cfg=self.cfg, aim_run=self.aim_run) - - def checkpoint(self): - import submitit - - runner = ResumableSlurmJob( - action_name=self.action_name, - cfg=self.cfg, - aim_run=self.aim_run, - ) - return submitit.helpers.DelayedSubmission(runner) - - def on_job_fail(self): - self.get_aim_run() - self.aim_run.close() - - -class ResumableJob: - def __init__(self, action_name: str, cfg: Dict, aim_run: Run): - self.action_name = action_name - self.cfg = cfg - self.aim_run = aim_run - - def __call__(self): - action = import_module(f"roleplay.actions.{self.action_name}") - action.main(cfg=self.cfg, aim_run=self.aim_run) diff --git a/roleplay/utils/launcher.py b/roleplay/utils/launcher.py deleted file mode 100644 index 70826b7..0000000 --- a/roleplay/utils/launcher.py +++ /dev/null @@ -1,52 +0,0 @@ -import time -from pathlib import Path -from typing import Dict - -from aim import Run -from iopath.common.file_io import g_pathmgr - -from roleplay.utils.io import makedir -from roleplay.utils.job import ResumableJob, ResumableSlurmJob - - -def create_submitit_executor(cfg: Dict): - import submitit - - log_folder = Path(cfg.slurm.log_folder).joinpath(str(time.time())) - makedir(log_folder) - assert g_pathmgr.exists(log_folder), f"Specified cfg.slurm.log_folder={log_folder} doesn't exist" - assert cfg.slurm.partition, "slurm.PARTITION must be set when using slurm" - - executor = submitit.AutoExecutor(folder=log_folder) - timeout_min = cfg.slurm.time_hours * 60 + cfg.slurm.time_minutes - executor.update_parameters( - name=cfg.slurm.name, - slurm_comment=cfg.slurm.comment, - slurm_partition=cfg.slurm.partition, - slurm_account=cfg.slurm.account, - slurm_constraint=cfg.slurm.constraint, - timeout_min=timeout_min, - nodes=cfg.slurm.num_nodes, - cpus_per_task=cfg.slurm.num_cpu_per_proc * cfg.slurm.num_proc_per_node, - tasks_per_node=cfg.slurm.num_proc_per_node, - gpus_per_node=cfg.slurm.num_gpu_per_node, - slurm_mem=f"{cfg.slurm.mem_gb}G", - mem_gb=cfg.slurm.mem_gb, - slurm_additional_parameters=cfg.slurm.additional_parameters, - ) - return executor - - -def launch_on_slurm(cfg: Dict, action_name: str, aim_run: Run): - executor = create_submitit_executor(cfg) - trainer = ResumableSlurmJob(action_name=action_name, cfg=cfg, aim_run=aim_run) - - job = executor.submit(trainer) - print(f"SUBMITTED: {job.job_id}") - - return job - - -def launch(cfg: Dict, action_name: str, aim_run: Run): - trainer = ResumableJob(action_name=action_name, cfg=cfg, aim_run=aim_run) - trainer() diff --git a/roleplay/utils/slurm.py b/roleplay/utils/slurm.py deleted file mode 100644 index d5d62eb..0000000 --- a/roleplay/utils/slurm.py +++ /dev/null @@ -1,7 +0,0 @@ -def is_submitit_available() -> bool: - try: - import submitit # NOQA - - return True - except ImportError: - return False diff --git a/roleplay/utils/user.py b/roleplay/utils/user.py deleted file mode 100644 index deff168..0000000 --- a/roleplay/utils/user.py +++ /dev/null @@ -1,17 +0,0 @@ -import importlib -import os -import pwd - - -def get_current_user() -> str: - return pwd.getpwuid(os.getuid()).pw_name - - -def get_user_config_module(file_path): - config_path = f"configs.configs_{get_current_user}" - config_path_hydra = config_path.replace(".", "/") - config_file = file_path.split("/")[-1].split(".")[0] - - config_module = importlib.import_module(f"{config_path}.{config_file}", package=__name__) - - return config_module, config_path_hydra diff --git a/setup.py b/setup.py index 0b5e5ac..e80ff6d 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ import os from setuptools import find_packages, setup +from pathlib import Path here = os.path.abspath(os.path.dirname(__file__)) -# Version version_file = os.path.join(here, "roleplay", "VERSION") with open(version_file) as vf: __version__ = vf.read().strip() @@ -15,22 +15,21 @@ # Package info NAME = "roleplay" -DESCRIPTION = "ML framework" +DESCRIPTION = "LLM Roleplay: Simulating Human-Chatbot Interaction" VERSION = __version__ -REQUIRES_PYTHON = "==3.9.16" +REQUIRES_PYTHON = ">=3.9.0" +this_directory = Path(__file__).parent +long_description = (this_directory / "README.md").read_text() setup( name=NAME, version=VERSION, description=DESCRIPTION, + long_description=long_description, + long_description_content_type='text/markdown', python_requires=REQUIRES_PYTHON, install_requires=requirements, packages=find_packages("."), package_dir={"": "."}, zip_safe=False, - entry_points={ - "console_scripts": [ - "roleplay=roleplay.__init__:main", - ], - }, )