From aaf1e48717541a8f3ef0eeee46b5ae9b1bacd80e Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Fri, 21 Feb 2025 15:47:27 -0500 Subject: [PATCH 1/8] clean commit of bionemo2 upgrade --- examples/advanced/bionemo/README.md | 6 +- .../bionemo/downstream/bionemo_filters.py | 113 ++++ .../downstream/downstream_nvflare.ipynb | 305 +++++++---- .../bionemo/downstream/finetune_esm2.py | 512 ++++++++++++++++++ .../figs/tb_curve_sabdab_central_local.png | Bin 0 -> 122876 bytes .../sabdab/figs/tb_curve_sabdab_fedavg.png | Bin 0 -> 120956 bytes .../app/config/config_fed_client.conf | 94 ---- .../app/config/config_fed_server.conf | 110 ---- .../app/custom/base_config.yaml | 181 ------- .../app/custom/downstream_flip.py | 113 ---- .../app/custom/downstream_flip_sabdab.yaml | 74 --- .../app/custom/infer.yaml | 25 - .../app/custom/pretrain_small.yaml | 33 -- .../jobs/central_sabdab_esm1nv/meta.conf | 10 - .../app/config/config_fed_client.conf | 94 ---- .../app/config/config_fed_server.conf | 110 ---- .../app/custom/base_config.yaml | 181 ------- .../app/custom/downstream_flip.py | 123 ----- .../app/custom/downstream_flip_sabdab.yaml | 74 --- .../app/custom/infer.yaml | 25 - .../app/custom/pretrain_small.yaml | 33 -- .../jobs/fedavg_sabdab_esm1nv/meta.conf | 10 - .../app/config/config_fed_client.conf | 94 ---- .../app/config/config_fed_server.conf | 110 ---- .../app/custom/base_config.yaml | 181 ------- .../app/custom/downstream_flip.py | 123 ----- .../app/custom/downstream_flip_sabdab.yaml | 74 --- .../local_sabdab_esm1nv/app/custom/infer.yaml | 25 - .../app/custom/pretrain_small.yaml | 33 -- .../sabdab/jobs/local_sabdab_esm1nv/meta.conf | 10 - .../downstream/sabdab/prepare_sabdab_data.py | 24 +- .../downstream/sabdab/run_sim_sabdab.py | 102 +++- .../downstream/scl/figs/tb_curve_scl.png | Bin 0 -> 83773 bytes .../app1/config/config_fed_client.conf | 94 ---- .../app1/custom/base_config.yaml | 266 --------- .../app1/custom/downstream_flip.py | 105 ---- .../app1/custom/downstream_flip_scl.yaml | 67 --- .../app1/custom/pretrain_esm2_650M.yaml | 14 - .../app2/config/config_fed_client.conf | 94 ---- .../app2/custom/base_config.yaml | 266 --------- .../app2/custom/downstream_flip.py | 105 ---- .../app2/custom/downstream_flip_scl.yaml | 67 --- .../app2/custom/pretrain_esm2_650M.yaml | 14 - .../app3/config/config_fed_client.conf | 94 ---- .../app3/custom/base_config.yaml | 266 --------- .../app3/custom/downstream_flip.py | 105 ---- .../app3/custom/downstream_flip_scl.yaml | 67 --- .../app3/custom/pretrain_esm2_650M.yaml | 14 - .../jobs/fedavg_scl_finetune_esm2nv/meta.conf | 13 - .../server/config/config_fed_server.conf | 110 ---- .../app1/config/config_fed_client.conf | 94 ---- .../app1/custom/base_config.yaml | 266 --------- .../app1/custom/downstream_flip.py | 105 ---- .../app1/custom/downstream_flip_scl.yaml | 67 --- .../app1/custom/pretrain_esm2_650M.yaml | 14 - .../app2/config/config_fed_client.conf | 94 ---- .../app2/custom/base_config.yaml | 266 --------- .../app2/custom/downstream_flip.py | 105 ---- .../app2/custom/downstream_flip_scl.yaml | 67 --- .../app2/custom/pretrain_esm2_650M.yaml | 14 - .../app3/config/config_fed_client.conf | 94 ---- .../app3/custom/base_config.yaml | 266 --------- .../app3/custom/downstream_flip.py | 105 ---- .../app3/custom/downstream_flip_scl.yaml | 67 --- .../app3/custom/pretrain_esm2_650M.yaml | 14 - .../jobs/local_scl_finetune_esm2nv/meta.conf | 13 - .../server/config/config_fed_server.conf | 110 ---- .../bionemo/downstream/scl/run_sim_scl.py | 94 +++- .../downstream/tap/figs/esm_multi_task.svg | 1 + .../tap/figs/tap_alpha1.0_results.svg | 1 - .../tap/figs/tap_uniform_results.svg | 1 - .../app1/config/config_fed_client.conf | 94 ---- .../app1/custom/base_config.yaml | 181 ------- .../app1/custom/downstream_flip.py | 105 ---- .../app1/custom/downstream_flip_tap.yaml | 74 --- .../central_tap_esm1nv/app1/custom/infer.yaml | 25 - .../app1/custom/pretrain_small.yaml | 31 -- .../app2/config/config_fed_client.conf | 94 ---- .../app2/custom/base_config.yaml | 181 ------- .../app2/custom/downstream_flip.py | 105 ---- .../app2/custom/downstream_flip_tap.yaml | 74 --- .../central_tap_esm1nv/app2/custom/infer.yaml | 25 - .../app2/custom/pretrain_small.yaml | 31 -- .../app3/config/config_fed_client.conf | 94 ---- .../app3/custom/base_config.yaml | 181 ------- .../app3/custom/downstream_flip.py | 105 ---- .../app3/custom/downstream_flip_tap.yaml | 74 --- .../central_tap_esm1nv/app3/custom/infer.yaml | 25 - .../app3/custom/pretrain_small.yaml | 31 -- .../app4/config/config_fed_client.conf | 94 ---- .../app4/custom/base_config.yaml | 181 ------- .../app4/custom/downstream_flip.py | 105 ---- .../app4/custom/downstream_flip_tap.yaml | 74 --- .../central_tap_esm1nv/app4/custom/infer.yaml | 25 - .../app4/custom/pretrain_small.yaml | 31 -- .../tap/jobs/central_tap_esm1nv/meta.conf | 14 - .../server/config/config_fed_server.conf | 110 ---- .../app1/config/config_fed_client.conf | 96 ---- .../app1/custom/base_config.yaml | 181 ------- .../app1/custom/downstream_flip.py | 105 ---- .../app1/custom/downstream_flip_tap.yaml | 74 --- .../fedavg_tap_esm1nv/app1/custom/infer.yaml | 25 - .../app1/custom/pretrain_small.yaml | 31 -- .../app2/config/config_fed_client.conf | 96 ---- .../app2/custom/base_config.yaml | 181 ------- .../app2/custom/downstream_flip.py | 105 ---- .../app2/custom/downstream_flip_tap.yaml | 74 --- .../fedavg_tap_esm1nv/app2/custom/infer.yaml | 25 - .../app2/custom/pretrain_small.yaml | 31 -- .../app3/config/config_fed_client.conf | 96 ---- .../app3/custom/base_config.yaml | 181 ------- .../app3/custom/downstream_flip.py | 105 ---- .../app3/custom/downstream_flip_tap.yaml | 74 --- .../fedavg_tap_esm1nv/app3/custom/infer.yaml | 25 - .../app3/custom/pretrain_small.yaml | 31 -- .../app4/config/config_fed_client.conf | 96 ---- .../app4/custom/base_config.yaml | 181 ------- .../app4/custom/downstream_flip.py | 105 ---- .../app4/custom/downstream_flip_tap.yaml | 74 --- .../fedavg_tap_esm1nv/app4/custom/infer.yaml | 25 - .../app4/custom/pretrain_small.yaml | 31 -- .../tap/jobs/fedavg_tap_esm1nv/meta.conf | 14 - .../server/config/config_fed_server.conf | 112 ---- .../app1/config/config_fed_client.conf | 94 ---- .../app1/custom/base_config.yaml | 181 ------- .../app1/custom/downstream_flip.py | 105 ---- .../app1/custom/downstream_flip_tap.yaml | 74 --- .../local_tap_esm1nv/app1/custom/infer.yaml | 25 - .../app1/custom/pretrain_small.yaml | 31 -- .../app2/config/config_fed_client.conf | 94 ---- .../app2/custom/base_config.yaml | 181 ------- .../app2/custom/downstream_flip.py | 104 ---- .../app2/custom/downstream_flip_tap.yaml | 74 --- .../local_tap_esm1nv/app2/custom/infer.yaml | 25 - .../app2/custom/pretrain_small.yaml | 31 -- .../app3/config/config_fed_client.conf | 94 ---- .../app3/custom/base_config.yaml | 181 ------- .../app3/custom/downstream_flip.py | 105 ---- .../app3/custom/downstream_flip_tap.yaml | 74 --- .../local_tap_esm1nv/app3/custom/infer.yaml | 25 - .../app3/custom/pretrain_small.yaml | 31 -- .../app4/config/config_fed_client.conf | 94 ---- .../app4/custom/base_config.yaml | 181 ------- .../app4/custom/downstream_flip.py | 105 ---- .../app4/custom/downstream_flip_tap.yaml | 74 --- .../local_tap_esm1nv/app4/custom/infer.yaml | 25 - .../app4/custom/pretrain_small.yaml | 31 -- .../tap/jobs/local_tap_esm1nv/meta.conf | 14 - .../server/config/config_fed_server.conf | 110 ---- .../downstream/tap/prepare_tap_data.py | 16 +- .../bionemo/downstream/tap/run_sim_tap.py | 100 +++- examples/advanced/bionemo/start_bionemo.sh | 7 +- examples/advanced/bionemo/start_jupyter.sh | 2 +- .../app/config/base_infer_config.yaml | 84 --- .../app/config/config_fed_client.json | 18 - .../app/config/config_fed_server.json | 22 - .../jobs/embeddings/app/config/infer.yaml | 22 - .../embeddings/app/custom/bionemo_inferer.py | 212 -------- .../app/models/vocab/protein_sequence.vocab | 30 - .../protein_sequence_sentencepiece.model | Bin 237988 -> 0 bytes .../task_fitting/jobs/embeddings/meta.json | 11 - .../fedavg/app/config/config_fed_client.json | 34 -- .../fedavg/app/config/config_fed_server.json | 73 --- .../task_fitting/jobs/fedavg/meta.json | 10 - .../bionemo/task_fitting/split_data.py | 14 +- .../app/custom => src}/bionemo_constants.py | 6 +- .../app/custom => src}/bionemo_inference.py | 7 +- .../bionemo_inference_processor.py | 26 +- .../task_fitting/src/bionemo_launcher.py | 127 +++++ .../task_fitting/src/bionemo_mlp_job.py | 113 ++++ .../app/custom => src}/bionemo_mlp_learner.py | 44 +- .../bionemo_mlp_model_persistor.py | 5 +- .../bionemo/task_fitting/task_fitting.ipynb | 274 ++++++---- .../bionemo/task_fitting/tb_curve.png | Bin 14050 -> 65567 bytes 174 files changed, 1551 insertions(+), 12828 deletions(-) create mode 100644 examples/advanced/bionemo/downstream/bionemo_filters.py create mode 100644 examples/advanced/bionemo/downstream/finetune_esm2.py create mode 100644 examples/advanced/bionemo/downstream/sabdab/figs/tb_curve_sabdab_central_local.png create mode 100644 examples/advanced/bionemo/downstream/sabdab/figs/tb_curve_sabdab_fedavg.png delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_server.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/meta.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_server.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/meta.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_server.conf delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/meta.conf create mode 100644 examples/advanced/bionemo/downstream/scl/figs/tb_curve_scl.png delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/custom/downstream_flip_scl.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/custom/pretrain_esm2_650M.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app2/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app2/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app2/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app2/custom/downstream_flip_scl.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app2/custom/pretrain_esm2_650M.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app3/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app3/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app3/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app3/custom/downstream_flip_scl.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app3/custom/pretrain_esm2_650M.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/meta.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/server/config/config_fed_server.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app1/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app1/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app1/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app1/custom/downstream_flip_scl.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app1/custom/pretrain_esm2_650M.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app2/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app2/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app2/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app2/custom/downstream_flip_scl.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app2/custom/pretrain_esm2_650M.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app3/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app3/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app3/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app3/custom/downstream_flip_scl.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/app3/custom/pretrain_esm2_650M.yaml delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/meta.conf delete mode 100644 examples/advanced/bionemo/downstream/scl/jobs/local_scl_finetune_esm2nv/server/config/config_fed_server.conf create mode 100644 examples/advanced/bionemo/downstream/tap/figs/esm_multi_task.svg delete mode 100644 examples/advanced/bionemo/downstream/tap/figs/tap_alpha1.0_results.svg delete mode 100644 examples/advanced/bionemo/downstream/tap/figs/tap_uniform_results.svg delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app1/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app1/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app1/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app1/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app1/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app1/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app2/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app2/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app2/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app2/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app2/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app2/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app3/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app3/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app3/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app3/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app3/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app3/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app4/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app4/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app4/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app4/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app4/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/app4/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/meta.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/central_tap_esm1nv/server/config/config_fed_server.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app1/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app1/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app1/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app1/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app1/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app1/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app2/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app2/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app2/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app2/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app2/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app2/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app3/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app3/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app3/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app3/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app3/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app3/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app4/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app4/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app4/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app4/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app4/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/app4/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/meta.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/fedavg_tap_esm1nv/server/config/config_fed_server.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app1/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app1/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app1/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app1/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app1/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app1/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app2/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app2/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app2/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app2/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app2/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app2/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app3/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app3/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app3/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app3/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app3/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app3/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app4/config/config_fed_client.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app4/custom/base_config.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app4/custom/downstream_flip.py delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app4/custom/downstream_flip_tap.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app4/custom/infer.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/app4/custom/pretrain_small.yaml delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/meta.conf delete mode 100644 examples/advanced/bionemo/downstream/tap/jobs/local_tap_esm1nv/server/config/config_fed_server.conf delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/app/config/base_infer_config.yaml delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/app/config/config_fed_client.json delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/app/config/config_fed_server.json delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/app/config/infer.yaml delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/app/custom/bionemo_inferer.py delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/app/models/vocab/protein_sequence.vocab delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/app/models/vocab/protein_sequence_sentencepiece.model delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/embeddings/meta.json delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/fedavg/app/config/config_fed_client.json delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/fedavg/app/config/config_fed_server.json delete mode 100644 examples/advanced/bionemo/task_fitting/jobs/fedavg/meta.json rename examples/advanced/bionemo/task_fitting/{jobs/embeddings/app/custom => src}/bionemo_constants.py (89%) rename examples/advanced/bionemo/task_fitting/{jobs/embeddings/app/custom => src}/bionemo_inference.py (84%) rename examples/advanced/bionemo/task_fitting/{jobs/embeddings/app/custom => src}/bionemo_inference_processor.py (72%) create mode 100644 examples/advanced/bionemo/task_fitting/src/bionemo_launcher.py create mode 100644 examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py rename examples/advanced/bionemo/task_fitting/{jobs/fedavg/app/custom => src}/bionemo_mlp_learner.py (91%) rename examples/advanced/bionemo/task_fitting/{jobs/fedavg/app/custom => src}/bionemo_mlp_model_persistor.py (95%) diff --git a/examples/advanced/bionemo/README.md b/examples/advanced/bionemo/README.md index eb9366ff62..5aadf07f63 100644 --- a/examples/advanced/bionemo/README.md +++ b/examples/advanced/bionemo/README.md @@ -7,14 +7,14 @@ This directory contains examples of running BioNeMo in a federated learning envi ## Notebooks In this repo you will find two notebooks under the `task_fitting` and `downstream` folders respectively: -1. The [task_fitting](./task_fitting/task_fitting.ipynb) notebook example includes a notebook that shows how to obtain protein learned representations in the form of embeddings using the ESM-1nv pre-trained model. -The model is trained with NVIDIA's BioNeMo framework for Large Language Model training and inference. +1. The [task_fitting](./task_fitting/task_fitting.ipynb) notebook example includes a notebook that shows how to obtain protein-learned representations in the form of embeddings using an ESM-2 pre-trained model. + 2. The [downstream](./downstream/downstream_nvflare.ipynb) notebook example shows three different downstream tasks for fine-tuning a BioNeMo ESM-style model. ## Requirements Download and run the [BioNeMo docker container](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/clara/containers/bionemo-framework). -> **Note:** The examples here were tested with `nvcr.io/nvidia/clara/bionemo-framework:1.8` +> **Note:** The examples here were tested with `nvcr.io/nvidia/clara/bionemo-framework:2.4` We recommend following the [Quickstart Guide](https://docs.nvidia.com/bionemo-framework/latest/access-startup.html?highlight=docker) on how to get the BioNeMo container. diff --git a/examples/advanced/bionemo/downstream/bionemo_filters.py b/examples/advanced/bionemo/downstream/bionemo_filters.py new file mode 100644 index 0000000000..d207c1f3b0 --- /dev/null +++ b/examples/advanced/bionemo/downstream/bionemo_filters.py @@ -0,0 +1,113 @@ +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Union +from torch import Tensor + +from nvflare.apis.dxo import DXO, DataKind, MetaKey +from nvflare.apis.dxo_filter import DXOFilter +from nvflare.apis.fl_context import FLContext +from nvflare.apis.shareable import Shareable + + +class BioNeMoParamsFilter(DXOFilter): + def __init__( + self, + precision="bf16-mixed" + ): + """Filter to add a prefix to global state dict to avoid key mismatches between global and local state dictionaries. + This is needed because of NeMo training framework adding module wrappers depending on the used training precision. + + Args: + precision: training precision + """ + + # support weight and weight_diff data kinds + data_kinds = [DataKind.WEIGHTS, DataKind.WEIGHT_DIFF] + super().__init__(supported_data_kinds=data_kinds, data_kinds_to_filter=data_kinds) + + self._precision = precision + if self._precision == "bf16-mixed": + self._prefix = "module.module." + else: + self._prefix = "module." + + def process_dxo(self, dxo: DXO, shareable: Shareable, fl_ctx: FLContext) -> Union[None, DXO]: + """Filter process apply to the Shareable object. + + Args: + dxo: data to be processed + shareable: that the dxo belongs to + fl_ctx: FLContext + + Returns: DXO object with updated state dictionary + + """ + + self.log_info(fl_ctx, f"Adding `{self._prefix}` prefix...") + + params = dxo.data + new_params = {} + for k, v in params.items(): + new_key = self._prefix + k + new_params[new_key] = v + + dxo.data = new_params + return dxo + + +class BioNeMoExcludeParamsFilter(DXOFilter): + def __init__( + self, + exclude_vars="head" + ): + """Filter to remove parameters from state dictionary that shouldn't be shared with other party. + + Args: + exclude_vars: variables will be excluded if the string is part of a state dictionary key. + """ + + # support weight and weight_diff data kinds + data_kinds = [DataKind.WEIGHTS, DataKind.WEIGHT_DIFF] + super().__init__(supported_data_kinds=data_kinds, data_kinds_to_filter=data_kinds) + + self.exclude_vars = exclude_vars + + + def process_dxo(self, dxo: DXO, shareable: Shareable, fl_ctx: FLContext) -> Union[None, DXO]: + """Filter process apply to the Shareable object. + + Args: + dxo: data to be processed + shareable: that the dxo belongs to + fl_ctx: FLContext + + Returns: DXO object with updated state dictionary + + """ + + params = dxo.data + new_params = {} + for k, v in params.items(): + if self.exclude_vars not in k: + new_params[k] = v + + if len(new_params) < len(params): + self.log_info(fl_ctx, f"Excluded {len(params)-len(new_params)} parameters matching '{self.exclude_vars}'") + else: + raise ValueError(f"State dictionary did not match any exclude keys that matched '{self.exclude_vars}'") + + dxo.data = new_params + return dxo + diff --git a/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb b/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb index efd6109bcc..0a04bd65c5 100644 --- a/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb +++ b/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb @@ -6,7 +6,7 @@ "source": [ "# Federated Protein Downstream Fine-tuning\n", "\n", - "
NOTE This notebook was tested on a single A1000 GPU and is compatible with BioNeMo Framework v1.8. To leverage additional or higher-performance GPUs, you can modify the configuration files and simulation script to accommodate multiple devices and increase thread utilization respectively.
\n", + "
NOTE This notebook was tested on a DGX with 8 A100 GPUs with 80 GB memory each and is compatible with BioNeMo Framework v2.4. To leverage additional or higher-performance GPUs, you can modify the configuration files and simulation script to accommodate multiple devices and increase thread utilization respectively. To run with less memory consumption, you can reduce the micro-batch sizes in the `run_*.py` scripts.
\n", "\n", "The example datasets used here are made available by [Therapeutics Data Commons](https://tdcommons.ai/) through PyTDC.\n", "\n", @@ -35,28 +35,45 @@ }, "outputs": [], "source": [ - "%%capture --no-display --no-stderr cell_output\n", - "! pip install PyTDC\n", - "! pip install nvflare~=2.5.1\n", - "! pip install biopython\n", - "! pip install scikit-learn\n", - "! pip install matplotlib\n", - "! pip install protobuf==3.20\n", - "! pip install huggingface-hub==0.22.0\n", + "# %%capture --no-display --no-stderr cell_output\n", + "! pip install fuzzywuzzy PyTDC --no-dependencies # install tdc without dependencies to avoid version conflicts in the BioNeMo container\n", + "! pip install nvflare~=2.6\n", "\n", "import os\n", "import warnings\n", "\n", - "\n", - "warnings.filterwarnings('ignore')\n", - "warnings.simplefilter('ignore')" + "warnings.filterwarnings(\"ignore\")\n", + "warnings.simplefilter(\"ignore\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Home Directory" + "---\n", + "### Task 1: Cross-endpoint multi-task fitting\n", + "\n", + "#### Data: Five computational developability guidelines for therapeutic antibody profiling\n", + "See https://tdcommons.ai/single_pred_tasks/develop/#tap\n", + "- 241 Antibodies (both chains)\n", + "\n", + "#### Task Description: *Regression*. \n", + "Given the antibody's heavy chain and light chain sequence, predict its developability. The input X is a list of two sequences where the first is the heavy chain and the second light chain.\n", + "\n", + "Includes five metrics measuring developability of an antibody: \n", + " - Complementarity-determining regions (CDR) length - Trivial (excluded)\n", + " - patches of surface hydrophobicity (PSH) - Run on site-1\n", + " - patches of positive charge (PPC) - Run on site-2\n", + " - patches of negative charge (PNC) - Run on site-3\n", + " - structural Fv charge symmetry parameter (SFvCSP) - Run on site-4\n", + "\n", + "As indicated, we run each endpoint regression task on a different client. This simulates the multi-task fitting scenario with multiple endpoints where all client jointly train a shared ESM encoder trunk but keep their private regression heads for different endpoints (see the `BioNeMoExcludeParamsFilter` in [run_sum_tap.py](tap/run_sum_tap.py).\n", + "\n", + "\"ESM\n", + "\n", + "In the data preparation script, one can choose between uniform sampling of the data among clients and\n", + "heterogeneous data splits using a Dirichlet sampling strategy. \n", + "Here, different values of alpha control the level of heterogeneity. Below, we show a Dirichlet sampling of `alpha=1`." ] }, { @@ -65,17 +82,27 @@ "metadata": {}, "outputs": [], "source": [ - "bionemo_home = \"/workspace/bionemo\"\n", - "os.environ['BIONEMO_HOME'] = bionemo_home" + "! cd /bionemo_nvflare_examples/downstream/tap && python prepare_tap_data.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "| Uniform sampling | Dirichlet sampling |\n", + "|:-------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------:|\n", + "| \"Uniform | \"Dirichlet |" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Download Model Checkpoints\n", + "**Run training (central, local, & FL)**\n", "\n", - "In order to download pretrained models from the NGC registry, **please ensure that you have installed and configured the NGC CLI**, check the [Quickstart Guide](https://docs.nvidia.com/bionemo-framework/latest) for more info. The following code will download the pretrained model `esm2nv_650M_converted.nemo` from the NGC registry." + "You can change the FL job that's going to be simulated by changing the arguments of `run_sim_tap.py` script. You can choose which ESM2 model to download (8M or 650M parameters). The ESM2 finetuning arguments such as learning rate and others can be modified inside the script itself.\n", + "\n", + "First, let's check its arguments." ] }, { @@ -84,21 +111,16 @@ "metadata": {}, "outputs": [], "source": [ - "# Define the NGC CLI API KEY and ORG for the model download\n", - "# If these variables are not already set in the container, uncomment below\n", - "# to define and set with your API KEY and ORG\n", - "# api_key = \n", - "# ngc_cli_org = \n", - "# # Update the environment variable\n", - "# os.environ['NGC_CLI_API_KEY'] = api_key\n", - "# os.environ['NGC_CLI_ORG'] = ngc_cli_org\n", + "!python run_sim_tap.py --help" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1. Central training**\n", "\n", - "# Set variables and paths for model and checkpoint\n", - "model_name = \"esm2nv_650m\" # \"esm1nv\" \n", - "actual_checkpoint_name = \"esm2nv_650M_converted.nemo\" # \"esm1nv.nemo\"\n", - "model_path = os.path.join(bionemo_home, 'models')\n", - "checkpoint_path = os.path.join(model_path, actual_checkpoint_name)\n", - "os.environ['MODEL_PATH'] = model_path" + "To simulate central training, we use four clients, running one round of training for several steps on a different regression task using the full dataset. Note that if the `--exp_name` argument contains `\"central\"`, the combined training dataset is used." ] }, { @@ -107,19 +129,16 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture --no-display --no-stderr cell_output\n", - "if not os.path.exists(checkpoint_path):\n", - " !cd /workspace/bionemo && \\\n", - " python download_artifacts.py --model_dir models --models {model_name}\n", - "else:\n", - " print(f\"Model {model_name} already exists at {model_path}.\")" + "!python run_sim_tap.py --num_clients=4 --num_rounds=1 --local_steps=1000 --exp_name central" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Again for esm1nv: " + "**2. Local training**\n", + "\n", + "To simulate local training, we use four clients, each running one round of training for several steps using the split datasets." ] }, { @@ -128,11 +147,16 @@ "metadata": {}, "outputs": [], "source": [ - "model_name = \"esm1nv\"\n", - "actual_checkpoint_name = \"esm1nv.nemo\"\n", - "model_path = os.path.join(bionemo_home, 'models')\n", - "checkpoint_path = os.path.join(model_path, actual_checkpoint_name)\n", - "os.environ['MODEL_PATH'] = model_path" + "!python run_sim_tap.py --num_clients=4 --num_rounds=1 --local_steps=1000 --exp_name local" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**3. Federated training with FedAvg**\n", + "\n", + "To simulate federated training, we use four clients, running several rounds with FedAvg, each with a smaller number of local steps. The number of rounds and local steps matches the setting of the local training scenario." ] }, { @@ -141,37 +165,31 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture --no-display --no-stderr cell_output\n", - "if not os.path.exists(checkpoint_path):\n", - " !cd /workspace/bionemo && \\\n", - " python download_artifacts.py --model_dir models --models {model_name}\n", - "else:\n", - " print(f\"Model {model_name} already exists at {model_path}.\")" + "!python run_sim_tap.py --num_clients=4 --num_rounds=10 --local_steps=100 --exp_name fedavg" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Task 1: Cross-endpoint multi-task fitting\n", - "\n", - "#### Data: Five computational developability guidelines for therapeutic antibody profiling\n", - "See https://tdcommons.ai/single_pred_tasks/develop/#tap\n", - "- 241 Antibodies (both chains)\n", + "You can visualize the results in TensorBoard using `tensorboard --logdir /tmp/nvflare/bionemo/tap`. Note, that for the FedAvg, you can sort the x-axis by wall-time as each FL round is creating a new TensorBoard output folder.\n", "\n", - "#### Task Description: *Regression*. \n", - "Given the antibody's heavy chain and light chain sequence, predict its developability. The input X is a list of two sequences where the first is the heavy chain and the second light chain.\n", + "
NOTE This public dataset is very small, and therefore, we only use it to illustrate the code example. The regression results are likely not reliable in practice. Hence, we skip the visualization here.
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "### Task 2: Cross-compound task fitting\n", "\n", - "Includes five metrics measuring developability of an antibody: \n", - " - Complementarity-determining regions (CDR) length - Trivial (excluded)\n", - " - patches of surface hydrophobicity (PSH)\n", - " - patches of positive charge (PPC)\n", - " - patches of negative charge (PNC)\n", - " - structural Fv charge symmetry parameter (SFvCSP)\n", + "#### Data: Predicting Antibody Developability from Sequence using Machine Learning\n", + "See https://tdcommons.ai/single_pred_tasks/develop/#sabdab-chen-et-al\n", + "- 2,409 Antibodies (both chains)\n", "\n", - "In the data preparation script, one can choose between uniform sampling of the data among clients and\n", - "heterogeneous data splits using a Dirichlet sampling strategy. \n", - "Here, different values of alpha control the level of heterogeneity. Below, we show a Dirichlet sampling of `alpha=1`." + "#### Task Description: *Binary classification*. \n", + "Given the antibody's heavy chain and light chain sequence, predict its developability. The input X is a list of two sequences where the first is the heavy chain and the second light chain." ] }, { @@ -180,25 +198,33 @@ "metadata": {}, "outputs": [], "source": [ - "! cd /bionemo_nvflare_examples/downstream/tap && python prepare_tap_data.py" + "# you may need to fix these paths to your own scripts\n", + "! cd /bionemo_nvflare_examples/downstream/sabdab && python prepare_sabdab_data.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "| Uniform sampling | Dirichlet sampling |\n", - "|:-------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------:|\n", - "| \"Uniform | \"Dirichlet |\n" + "Again, we are using the Dirichlet sampling strategy to generate heterogeneous data distributions among clients.\n", + "Lower values of `alpha` generate higher levels of heterogeneity.\n", + "\n", + "| Alpha 10.0 | Alpha 1.0 |\n", + "|:-------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------:|\n", + "| \"Dirichlet | \"Dirichlet |\n" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "tags": [] + }, "source": [ "**Run training (central, local, & FL)**\n", "\n", - "You can change the FL job that's going to be simulated inside the `run_sim_tap.py` script." + "You can change the FL job that's going to be simulated by changing the arguments of `run_sim_sabdab.py` script. You can choose which ESM2 model to download (8M or 650M parameters). The ESM2 finetuning arguments such as learning rate and others can be modified inside the script itself.\n", + "\n", + "First, let's check its arguments." ] }, { @@ -207,21 +233,16 @@ "metadata": {}, "outputs": [], "source": [ - "! cd /bionemo_nvflare_examples/downstream/tap && python run_sim_tap.py" + "!python run_sim_sabdab.py --help" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Task 2: Cross-compound task fitting\n", - "\n", - "#### Data: Predicting Antibody Developability from Sequence using Machine Learning\n", - "See https://tdcommons.ai/single_pred_tasks/develop/#sabdab-chen-et-al\n", - "- 2,409 Antibodies (both chains)\n", + "**1. Central training**\n", "\n", - "#### Task Description: *Binary classification*. \n", - "Given the antibody's heavy chain and light chain sequence, predict its developability. The input X is a list of two sequences where the first is the heavy chain and the second light chain." + "To simulate central training, we use one client, running one round of training for several steps. Note that if the `--exp_name` argument contains `\"central\"`, the combined training dataset is used." ] }, { @@ -230,31 +251,34 @@ "metadata": {}, "outputs": [], "source": [ - " # you may need to fix these paths to your own scripts\n", - "! cd /bionemo_nvflare_examples/downstream/sabdab && python prepare_sabdab_data.py" + "!python run_sim_sabdab.py --num_clients=1 --num_rounds=1 --local_steps=3000 --exp_name central" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Again, we are using the Dirichlet sampling strategy to generate heterogeneous data distributions among clients.\n", - "Lower values of `alpha` generate higher levels of heterogeneity.\n", + "**2. Local training**\n", "\n", - "| Alpha 10.0 | Alpha 1.0 |\n", - "|:-------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------:|\n", - "| \"Dirichlet | \"Dirichlet |\n" + "To simulate central training, we use six clients, each running one round of training for several steps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!python run_sim_sabdab.py --num_clients=6 --num_rounds=1 --local_steps=3000 --exp_name local" ] }, { "cell_type": "markdown", - "metadata": { - "tags": [] - }, + "metadata": {}, "source": [ - "**Run training (central, local, & FL)**\n", + "**3. Federated training with FedAvg**\n", "\n", - "You can change the FL job that's going to be simulated inside the `run_sim_sabdab.py` script." + "To simulate federated training, we use six clients, running several rounds with FedAvg, each with a smaller number of local steps." ] }, { @@ -263,31 +287,61 @@ "metadata": {}, "outputs": [], "source": [ - "! cd /bionemo_nvflare_examples/downstream/sabdab && python run_sim_sabdab.py" + "!python run_sim_sabdab.py --num_clients=6 --num_rounds=10 --local_steps=300 --exp_name fedavg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can visualize the results in TensorBoard using `tensorboard --logdir /tmp/nvflare/bionemo/sabdab`. Note, that for the FedAvg, you can sort the x-axis by wall-time as each FL round is creating a new TensorBoard output folder." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Results with heterogeneous data sampling (alpha=10.0)\n", - "| Setting | Accuracy |\n", - "|:-------:|:---------:|\n", - "| Local | 0.821 |\n", - "| FL | **0.833** |\n", - "\n", "#### Results with heterogeneous data sampling (alpha=1.0)\n", - "| Setting | Accuracy |\n", - "|:-------:|:---------:|\n", - "| Local | 0.813 |\n", - "| FL | **0.835** |\n", + "| Setting | Accuracy |\n", + "|:--------:|:---------:|\n", + "| Central | *0.8415* |\n", + "| Local | 0.8047 |\n", + "| FedAvg | **0.8319** |\n", "\n", + "\n", + "| Central & Local | FedAvg |\n", + "|:-------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------:|\n", + "| \"sabdab | \"sabdab |" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", "### Task 3. Subcellular location prediction with ESM2nv 650M\n", + "In this example, we use the `--encoder-frozen` option inside the `run_sim_scl.py` script. You can specify different base ESM2 models using the `--model` option.\n", "Follow the data download and preparation in [task_fitting.ipynb](../task_fitting/task_fitting.ipynb).\n", "\n", "Here, we use a heterogeneous sampling with `alpha=1.0`.\n", "\n", - "\"Dirichlet\n" + "\"Dirichlet" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**1. Local training**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!cd /bionemo_nvflare_examples/downstream/scl" ] }, { @@ -296,24 +350,45 @@ "metadata": {}, "outputs": [], "source": [ - "# for this to work run the task_fitting notebook first in ../nvflare_with_bionemo/task_fitting/task_fitting.ipynb\n", - "! cd /bionemo_nvflare_examples/downstream/scl && python run_sim_scl.py" + "# for this to work run the task_fitting notebook first in ../nvflare_with_bionemo/task_fitting/task_fitting.ipynb in order to download the SCL dataset\n", + "!python run_sim_scl.py --num_clients=3 --num_rounds=1 --local_steps=5000 --exp_name \"local\" --model \"650m\" --sim_gpus=\"0,1,2\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Note, you can switch between local and FL jobs by modifying the `run_sim_scl.py` script.\n", - "\n", - "#### Results with heterogeneous data sampling (alpha=10.0)\n", - "| Setting | Accuracy |\n", - "|:-------:|:---------:|\n", - "| Local | 0.773 |\n", - "| FL | **0.776** |\n", - "\n", + "**2. Federated training with FedAvg**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!python run_sim_scl.py --num_clients=3 --num_rounds=10 --local_steps=500 --exp_name \"fedavg\" --model \"650m\" --sim_gpus=\"0,1,2\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can visualize the results in TensorBoard using `tensorboard --logdir /tmp/nvflare/bionemo/scl`. Note, that for the FedAvg, you can sort the x-axis by wall-time as each FL round is creating a new TensorBoard output folder." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Results with heterogeneous data sampling (alpha=1.0)\n", + "| Client | Site-1 | Site-2 | Site-3 | Average |\n", + "|:---------:|:-------:|:------:|:------:|:----------:|\n", + "| # Samples | 1844 | 2921 | 2151 | Accuracy |\n", + "| Local | 0.7819 |\t0.7885 | 0.7921 | 0.7875 |\n", + "| FedAvg | 0.8179 |\t0.8131 | 0.8209 | **0.8173** |\n", "\n", - "\"Dirichlet" + "\"SCL" ] }, { @@ -340,7 +415,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/examples/advanced/bionemo/downstream/finetune_esm2.py b/examples/advanced/bionemo/downstream/finetune_esm2.py new file mode 100644 index 0000000000..a9556f569e --- /dev/null +++ b/examples/advanced/bionemo/downstream/finetune_esm2.py @@ -0,0 +1,512 @@ +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Copied and adapted for NVFlare from https://github.com/NVIDIA/bionemo-framework/blob/main/sub-packages/bionemo-esm2/src/bionemo/esm2/scripts/finetune_esm2.py + +import shutil +import argparse +import random +from pathlib import Path +from lightning import seed_everything +from typing import Dict, List, Optional, Sequence, Tuple, Type, get_args + +from lightning.pytorch.callbacks import Callback, LearningRateMonitor, RichModelSummary +from megatron.core.distributed import DistributedDataParallelConfig +from megatron.core.optimizer import OptimizerConfig +from nemo import lightning as nl +from nemo.collections import llm +from nemo.lightning import resume +from nemo.lightning.pytorch import callbacks as nl_callbacks +from nemo.lightning.pytorch.optim import MegatronOptimizerModule + +from bionemo.core.utils.dtypes import PrecisionTypes, get_autocast_dtype +from bionemo.esm2.data.tokenizer import get_tokenizer +from bionemo.esm2.model.finetune.datamodule import ESM2FineTuneDataModule +from bionemo.esm2.model.finetune.dataset import ( + InMemoryPerTokenValueDataset, + InMemoryProteinDataset, + InMemorySingleValueDataset, +) +from bionemo.esm2.model.finetune.sequence_model import ESM2FineTuneSeqConfig +from bionemo.esm2.model.finetune.token_model import ESM2FineTuneTokenConfig +from bionemo.llm.model.biobert.lightning import biobert_lightning_module +from bionemo.llm.model.biobert.model import BioBertConfig +from bionemo.llm.model.config import TorchmetricsConfig +from bionemo.llm.utils.datamodule_utils import float_or_int_or_none, infer_global_batch_size +from bionemo.llm.utils.logger_utils import WandbConfig, setup_nemo_lightning_logger + +# Resue parser and config constants from bionemo +from bionemo.esm2.scripts.finetune_esm2 import get_parser, SUPPORTED_CONFIGS, SUPPORTED_DATASETS + +# (1) import nvflare lightning client API +import nvflare.client.lightning as flare + + +def train_model( + train_data_path: Path, + valid_data_path: Path, + num_nodes: int, + devices: int, + min_seq_length: Optional[int], + max_seq_length: int, + result_dir: Path, + num_steps: int, + limit_val_batches: int, + val_check_interval: int, + log_every_n_steps: Optional[int], + num_dataset_workers: int, + lr: float, + micro_batch_size: int, + accumulate_grad_batches: int, + experiment_name: str, + resume_if_exists: bool, + precision: PrecisionTypes, + task_type: str = "regression", + encoder_frozen: bool = False, + scale_lr_layer: Optional[str] = None, + lr_multiplier: float = 1.0, + # single value classification / regression mlp + mlp_ft_dropout: float = 0.25, + mlp_hidden_size: int = 256, + mlp_target_size: int = 1, + # token-level classification cnn + cnn_dropout: float = 0.25, + cnn_hidden_size: int = 32, + cnn_num_classes: int = 3, + wandb_entity: Optional[str] = None, + wandb_project: Optional[str] = None, + wandb_offline: bool = False, + wandb_tags: Optional[List[str]] = None, + wandb_group: Optional[str] = None, + wandb_id: Optional[str] = None, + wandb_anonymous: Optional[bool] = False, + wandb_log_model: bool = False, + pipeline_model_parallel_size: int = 1, + tensor_model_parallel_size: int = 1, + create_tensorboard_logger: bool = False, + restore_from_checkpoint_path: Optional[str] = None, + save_last_checkpoint: bool = True, + metric_to_monitor_for_checkpoints: str = "val_loss", + save_top_k: int = 2, + nsys_profiling: bool = False, + nsys_start_step: int = 0, + nsys_end_step: Optional[int] = None, + nsys_ranks: List[int] = [0], + dataset_class: Type[InMemoryProteinDataset] = InMemorySingleValueDataset, + config_class: Type[BioBertConfig] = ESM2FineTuneSeqConfig, + metric_tracker: Callback | None = None, + overlap_grad_reduce: bool = False, # Default to False to avoid communication issue in gradient synchronization step + overlap_param_gather: bool = True, + average_in_collective: bool = True, + grad_reduce_in_fp32: bool = False, + label_column: str = "labels", + classes: List[str] = None +) -> Tuple[Path, Callback | None, nl.Trainer]: + """Train an ESM2 model on UR data. + + Args: + train_data_path (Path): path to train CSV + valid_data_path (Path): path to validation CSV + num_nodes (int): Number of nodes to run on + devices (int): number of devices + min_seq_length (Optional[int]): minimum sequence length + max_seq_length (int): maximum sequence length + result_dir (Path): directory to store results, logs and checkpoints + num_steps (int): number of steps to train the model for + limit_val_batches (int): limit the number of validation global batches to this many + val_check_interval (int): number of steps to periodically check the validation loss + log_every_n_steps (Optional[int]): log every n steps + num_dataset_workers (int): number of dataset workers + lr (float): learning rate + micro_batch_size (int): micro batch size, from this and parallelism settings we infer the global batch size + accumulate_grad_batches (int): number of batches to accumulate gradients for + experiment_name (str): experiment name, this is the name used for the wandb run, and the sub-directory of the + result_dir that stores the logs and checkpoints. + resume_if_exists (bool): attempt to resume if the checkpoint exists [FIXME @skothenhill this doesn't work yet] + precision (PrecisionTypes): Precision type for training (e.g., float16, float32) + task_type (Literal["classification", "regression"]): Fine-tuning task type. Default is regression. + encoder_frozen (bool): Freeze the encoder parameters. Default is False. + scale_lr_layer (Optional[str]): layer names for which the lr is scaled by lr_multiplier + lr_multiplier (float): lr multiplier for parameters in scale_lr_layer + mlp_ft_dropout (float): dropout for single value classification / regression mlp + mlp_hidden_size (int): dimension of hidden layer in mlp task head + mlp_target_size: (int): output dimension of the mlp task head (number of classes in classification tasks) + cnn_dropout (float): dropout for token-level classification cnn + cnn_hidden_size (int): hidden dimension of cnn head + cnn_num_classes (int): number of classes in token-level classification + wandb_entity (Optional[str]): The team posting this run (default: your username or your default team) + wandb_project (Optional[str]): The name of the project to which this run will belong + wandb_offline (bool): Run offline (data can be streamed later to wandb servers). + wandb_tags (Optional[List[str]]): Tags associated with this run + wandb_group (Optional[str]): A unique string shared by all runs in a given group + wandb_id (Optional[str]): Sets the version, mainly used to resume a previous run + wandb_anonymous (Optional[bool]): Enables or explicitly disables anonymous logging + wandb_log_model (bool): Save checkpoints in wandb dir to upload on W&B servers + pipeline_model_parallel_size (int): pipeline model parallel size + tensor_model_parallel_size (int): tensor model parallel size + create_tensorboard_logger (bool): create the tensorboard logger + restore_from_checkpoint_path (Optional[str]): If set, restores the model from the directory passed in. Expects the + checkpoint to be created by using the ModelCheckpoint class and always_save_context=True. + save_last_checkpoint (bool): whether to save the last checkpoint + metric_to_monitor_for_checkpoints (str): metric to monitor for checkpoints + save_top_k (int): number of top checkpoints to save + nsys_profiling (bool): whether to enable nsys profiling + nsys_start_step (int): start step for nsys profiling + nsys_end_step (Optional[int]): end step for nsys profiling + nsys_ranks (List[int]): ranks for nsys profiling + dataset_class (Type[InMemoryProteinDataset]): The dataset class for loading the data from a CSV file + config_class (Type[BioBertConfig]): The config class for configuring the model using checkpoint provided + metric_tracker: Optional callback to track metrics (used for testing) + overlap_grad_reduce (bool): overlap gradient reduction + overlap_param_gather (bool): overlap parameter gather + average_in_collective (bool): average in collective + grad_reduce_in_fp32 (bool): gradient reduction in fp32 + classes (List[str]): unique strings describing the classes for classification. Used to build the same label vocabulary on each client. Should be comma-separated list of strings, e.g. ['pos', 'neg']. + """ + # Create the result directory if it does not exist. + result_dir.mkdir(parents=True, exist_ok=True) + + # Setup the strategy and trainer + global_batch_size = infer_global_batch_size( + micro_batch_size=micro_batch_size, + num_nodes=num_nodes, + devices=devices, + accumulate_grad_batches=accumulate_grad_batches, + tensor_model_parallel_size=tensor_model_parallel_size, + pipeline_model_parallel_size=pipeline_model_parallel_size, + ) + + strategy = nl.MegatronStrategy( + tensor_model_parallel_size=tensor_model_parallel_size, + pipeline_model_parallel_size=pipeline_model_parallel_size, + ddp=DistributedDataParallelConfig( + check_for_nan_in_grad=True, + overlap_grad_reduce=overlap_grad_reduce, + overlap_param_gather=overlap_param_gather, + average_in_collective=average_in_collective, + grad_reduce_in_fp32=grad_reduce_in_fp32, + use_distributed_optimizer=True, + ), + find_unused_parameters=True, + gradient_as_bucket_view=True, + ckpt_include_optimizer=True, + ckpt_async_save=False, # do not use `ckpt_async_save=True` as the checkpoint might still be saved while the next round already removed that saving directory + ckpt_parallel_load=True, + ) + + # for wandb integration + # Please refer to https://pytorch-lightning.readthedocs.io/en/0.7.6/api/lightning.pytorch.loggers.html" + wandb_config: Optional[WandbConfig] = ( + None + if wandb_project is None + else WandbConfig( + offline=wandb_offline, + project=wandb_project, + entity=wandb_entity, + tags=wandb_tags, + group=wandb_group, + id=wandb_id, + anonymous=wandb_anonymous, + log_model=wandb_log_model, + ) + ) + + callbacks = [ + RichModelSummary(max_depth=4), + LearningRateMonitor(), + nl_callbacks.PreemptionCallback(), + ] + if metric_tracker is not None: + callbacks.append(metric_tracker) + if nsys_profiling: + if nsys_end_step is None: + nsys_end_step = num_steps + callbacks.append( + nl_callbacks.NsysCallback( + start_step=nsys_start_step, end_step=nsys_end_step, ranks=nsys_ranks, gen_shape=True + ) + ) + + trainer = nl.Trainer( + devices=devices, + max_steps=num_steps, + accelerator="gpu", + strategy=strategy, + limit_val_batches=limit_val_batches, # This controls upsampling and downsampling + val_check_interval=val_check_interval, + log_every_n_steps=log_every_n_steps, + num_nodes=num_nodes, + callbacks=callbacks, + plugins=nl.MegatronMixedPrecision( + precision=precision, + params_dtype=get_autocast_dtype(precision), + pipeline_dtype=get_autocast_dtype(precision), + grad_reduce_in_fp32=grad_reduce_in_fp32, + autocast_enabled=False, + ), + ) + + # (2) patch the lightning trainer + flare.patch(trainer, restore_state=False, load_state_dict_strict=False) + + # (3) receives FLModel from NVFlare + # Note that we don't need to pass this input_model to trainer + # because after flare.patch the trainer.fit/validate will get the + # global model internally + input_model = flare.receive() + print(f"\n[Current Round={input_model.current_round}, Site = {flare.get_site_name()}, Global model = {input_model} ({len(input_model.params)} params)]\n") + # use a unique result directory for each round + + # Remove previous checkpoints to preserve disk space + keep_last_ckpt_only = True # TODO: make configurable + if keep_last_ckpt_only: + previous_ckpt_dir = result_dir / f"round{input_model.current_round-1}" / experiment_name / "dev" / "checkpoints" + if previous_ckpt_dir.is_dir(): + print(f"Removing previous checkpoint directory {previous_ckpt_dir}") + shutil.rmtree(previous_ckpt_dir) + + # create output folder for this round + result_dir = result_dir / f"round{input_model.current_round}" + + # add a learning rate decay for each round + if input_model.current_round > 0: + lr_step_reduce = 1.05 # TODO: make lr_step_reduce configurable + new_lr = lr/(input_model.current_round*lr_step_reduce) + new_lr_multiplier = lr_multiplier/(input_model.current_round*lr_step_reduce) + print(f"Reduce lr {lr} by {input_model.current_round*lr_step_reduce}: {new_lr}") + else: + new_lr = lr + new_lr_multiplier = lr_multiplier + + # remaining bionemo training code + tokenizer = get_tokenizer() + + # Initialize the data module. + train_dataset = dataset_class.from_csv(train_data_path, task_type=task_type, label_column=label_column) + valid_dataset = dataset_class.from_csv(valid_data_path, task_type=task_type, label_column=label_column) + if task_type == "classification": + if classes: + if not isinstance(classes, List): + raise ValueError(f"classes is expected to be list of strings but received {type(classes)}: {classes}") + train_dataset.label_tokenizer.build_vocab([classes]) + print(f"Build custom label tokenizer based on label classes: {classes}") + valid_dataset.label_tokenizer = train_dataset.label_tokenizer + + data_module = ESM2FineTuneDataModule( + train_dataset=train_dataset, + valid_dataset=valid_dataset, + global_batch_size=global_batch_size, + micro_batch_size=micro_batch_size, + min_seq_length=min_seq_length, + max_seq_length=max_seq_length, + num_workers=num_dataset_workers, + tokenizer=tokenizer, + ) + # Configure the model + train_metric = None + if task_type == "regression": + valid_metric = TorchmetricsConfig(class_path="MeanSquaredError", task="regression", metric_name="val_mse") + else: + valid_metric = TorchmetricsConfig( + class_path="Accuracy", + task="classification", + kwargs={ + "task": "multiclass", + "threshold": 0.5, + "num_classes": data_module.train_dataset.label_tokenizer.vocab_size, + }, + metric_name="val_acc", + ) + + if tensor_model_parallel_size * pipeline_model_parallel_size > 1 and ( + train_metric is not None or valid_metric is not None + ): + raise NotImplementedError("Metric logging under model parallelism is not supported yet.") + + config = config_class( + task_type=task_type, + encoder_frozen=encoder_frozen, + params_dtype=get_autocast_dtype(precision), + pipeline_dtype=get_autocast_dtype(precision), + autocast_dtype=get_autocast_dtype(precision), # setting this speeds things up a lot + tensor_model_parallel_size=tensor_model_parallel_size, + pipeline_model_parallel_size=pipeline_model_parallel_size, + initial_ckpt_path=str(restore_from_checkpoint_path), + initial_ckpt_skip_keys_with_these_prefixes=[f"{task_type}_head"], + train_metric=train_metric, + valid_metric=valid_metric, + ) + # Mapping of task-dependent config attributes to their new values + task_dependent_attr = { + "mlp_ft_dropout": mlp_ft_dropout, + "mlp_hidden_size": mlp_hidden_size, + "mlp_target_size": mlp_target_size, + "cnn_dropout": cnn_dropout, + "cnn_hidden_size": cnn_hidden_size, + "cnn_num_classes": cnn_num_classes, + } + # Update attributes only if they exist in the config + for attr, value in task_dependent_attr.items(): + if hasattr(config, attr): + setattr(config, attr, value) + + optimizer = MegatronOptimizerModule( + config=OptimizerConfig( + lr=new_lr, + optimizer="adam", # fused_adam not supported + use_distributed_optimizer=True, + weight_decay=0.01, + adam_beta1=0.9, + adam_beta2=0.98, + ), + ) + # fiddle is not serializing lambda fn + # to bypass serialization of lambda fn scale_lr_condition as part of optimizer configuration + if scale_lr_layer: + optimizer.scale_lr_cond = lambda name, param: scale_lr_layer in name + optimizer.lr_mult = new_lr_multiplier + + module = biobert_lightning_module(config=config, tokenizer=tokenizer, optimizer=optimizer) + + #If client should save best local checkpoints, set to `save_local_ckpt=True`, + save_local_ckpt = False + if save_local_ckpt: + # Configure our custom Checkpointer + checkpoint_callback = nl_callbacks.ModelCheckpoint( + save_last=save_last_checkpoint, + monitor=metric_to_monitor_for_checkpoints, # "val_loss", + save_top_k=save_top_k, + every_n_train_steps=val_check_interval, + always_save_context=True, # Enables the .nemo file-like checkpointing where all IOMixins are under SerDe + filename="checkpoint-{step}-{consumed_samples}", # Including step and consumed_samples in the checkpoint filename prevents duplicate filenames and bugs related to this. + ) + else: + checkpoint_callback = None + + # Setup the logger and train the model + nemo_logger = setup_nemo_lightning_logger( + root_dir=result_dir, + name=experiment_name, + initialize_tensorboard_logger=create_tensorboard_logger, + wandb_config=wandb_config, + ckpt_callback=checkpoint_callback, + ) + + # perform local training starting with the received global model + llm.train( + model=module, + data=data_module, + trainer=trainer, + log=nemo_logger, + resume=None, + ) + + if checkpoint_callback: + ckpt_path = Path(checkpoint_callback.last_model_path.replace(".ckpt", "")) + else: + ckpt_path = None + return ckpt_path, metric_tracker, trainer + + +def finetune_esm2_entrypoint(): + """Entrypoint for running ESM2 finetuning.""" + # 1. get arguments + parser = get_parser() + + # Add some FL specific arguments + parser.add_argument( + "--classes", + type=str, + required=False, + default=None, + help="Unique strings describing the classes for classification. Used to build the same label vocabulary on each client. Should be comma separate list of strings, e.g. 'pos,neg'", + ) + args = parser.parse_args() + + if args.classes: + if args.task_type != "classification": + parser.error("Use --classes argument only with --task-type 'classification'") + classes = args.classes.split(",") + else: + classes = None + + + # to avoid padding for single value labels: + if args.min_seq_length is not None and args.datset_class is InMemorySingleValueDataset: + parser.error("Arguments --min-seq-length cannot be set when using InMemorySingleValueDataset.") + + # 2. Call pretrain with args + train_model( + train_data_path=args.train_data_path, + valid_data_path=args.valid_data_path, + num_nodes=args.num_nodes, + devices=args.num_gpus, + min_seq_length=args.min_seq_length, + max_seq_length=args.max_seq_length, + result_dir=args.result_dir, + wandb_entity=args.wandb_entity, + wandb_project=args.wandb_project, + wandb_tags=args.wandb_tags, + wandb_group=args.wandb_group, + wandb_id=args.wandb_id, + wandb_anonymous=args.wandb_anonymous, + wandb_log_model=args.wandb_log_model, + wandb_offline=args.wandb_offline, + num_steps=args.num_steps, + limit_val_batches=args.limit_val_batches, + val_check_interval=args.val_check_interval, + log_every_n_steps=args.log_every_n_steps, + num_dataset_workers=args.num_dataset_workers, + lr=args.lr, + micro_batch_size=args.micro_batch_size, + pipeline_model_parallel_size=args.pipeline_model_parallel_size, + tensor_model_parallel_size=args.tensor_model_parallel_size, + accumulate_grad_batches=args.accumulate_grad_batches, + precision=args.precision, + task_type=args.task_type, + encoder_frozen=args.encoder_frozen, + scale_lr_layer=args.scale_lr_layer, + lr_multiplier=args.lr_multiplier, + # single value classification / regression mlp + mlp_ft_dropout=args.mlp_ft_dropout, + mlp_hidden_size=args.mlp_hidden_size, + mlp_target_size=args.mlp_target_size, + # token-level classification cnn + cnn_dropout=args.cnn_dropout, + cnn_hidden_size=args.cnn_hidden_size, + cnn_num_classes=args.cnn_num_classes, + experiment_name=args.experiment_name, + resume_if_exists=args.resume_if_exists, + restore_from_checkpoint_path=args.restore_from_checkpoint_path, + save_last_checkpoint=args.save_last_checkpoint, + metric_to_monitor_for_checkpoints=args.metric_to_monitor_for_checkpoints, + save_top_k=args.save_top_k, + nsys_profiling=args.nsys_profiling, + nsys_start_step=args.nsys_start_step, + nsys_end_step=args.nsys_end_step, + nsys_ranks=args.nsys_ranks, + dataset_class=args.dataset_class, + config_class=args.config_class, + overlap_grad_reduce=args.overlap_grad_reduce, + overlap_param_gather=not args.no_overlap_param_gather, + average_in_collective=not args.no_average_in_collective, + grad_reduce_in_fp32=args.grad_reduce_in_fp32, + label_column=args.label_column, + classes=classes + ) + +if __name__ == "__main__": + finetune_esm2_entrypoint() + flare.shutdown() + diff --git a/examples/advanced/bionemo/downstream/sabdab/figs/tb_curve_sabdab_central_local.png b/examples/advanced/bionemo/downstream/sabdab/figs/tb_curve_sabdab_central_local.png new file mode 100644 index 0000000000000000000000000000000000000000..438f1503def960ce1f122e1109ce665436a0785f GIT binary patch literal 122876 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 literal 0 HcmV?d00001 diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_client.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_client.conf deleted file mode 100644 index 95a31d24c0..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_client.conf +++ /dev/null @@ -1,94 +0,0 @@ -{ - # version of the configuration - format_version = 2 - - # This is the application script which will be invoked. Client can replace this script with user's own training script. - app_script = "downstream_flip.py" - - # Additional arguments needed by the training code. For example, in lightning, these can be --trainer.batch_size=xxx. - app_config = "" - - # Additional arguments needed by DDP. - #ddp_config = "--nnodes=1 --nproc_per_node=1 --master_port=7777" - - # Client Computing Executors. - executors = [ - { - # tasks the executors are defined to handle - tasks = ["train"] - - # This particular executor - executor { - - # This is an executor for pytorch + Client API. The underline data exchange is using Pipe. - path = "nvflare.app_opt.pt.client_api_launcher_executor.PTClientAPILauncherExecutor" - - args { - # launcher_id is used to locate the Launcher object in "components" - launcher_id = "launcher" - - # pipe_id is used to locate the Pipe object in "components" - pipe_id = "pipe" - - # Timeout in seconds for waiting for a heartbeat from the training script. Defaults to 30 seconds. - # Please refer to the class docstring for all available arguments - heartbeat_timeout = 60 - - # format of the exchange parameters - params_exchange_format = "pytorch" - - # if the transfer_type is FULL, then it will be sent directly - # if the transfer_type is DIFF, then we will calculate the - # difference VS received parameters and send the difference - params_transfer_type = "FULL" - - # if train_with_evaluation is true, the executor will expect - # the custom code need to send back both the trained parameters and the evaluation metric - # otherwise only trained parameters are expected - train_with_evaluation = false - } - } - } - ], - - # this defined an array of task data filters. If provided, it will control the data from server controller to client executor - task_data_filters = [] - - # this defined an array of task result filters. If provided, it will control the result from client executor to server controller - task_result_filters = [] - - components = [ - { - # component id is "launcher" - id = "launcher" - - # the class path of this component - path = "nvflare.app_common.launchers.subprocess_launcher.SubprocessLauncher" - - args { - # the launcher will invoke the script - #script = "python3 -m torch.distributed.run {ddp_config} custom/{app_script} {app_config} " - script = "python3 custom/{app_script} {app_config} " - # if launch_once is true, the SubprocessLauncher will launch once for the whole job - # if launch_once is false, the SubprocessLauncher will launch a process for each task it receives from server - launch_once = true - } - } - { - id = "pipe" - - path = "nvflare.fuel.utils.pipe.file_pipe.FilePipe" - - args { - # Mode of the endpoint. A pipe has two endpoints. - # An endpoint can be either the one that initiates communication or the one listening. - # PASSIVE is the one listening. - mode = "PASSIVE" - - # root_path: is the directory location of the parameters exchange. - # You can also set it to an absolute path in your system. - root_path = "{WORKSPACE}/{JOB_ID}/{SITE_NAME}" - } - } - ] -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_server.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_server.conf deleted file mode 100644 index d9d4d7fe61..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/config/config_fed_server.conf +++ /dev/null @@ -1,110 +0,0 @@ -{ - # version of the configuration - format_version = 2 - - # task data filter: if filters are provided, the filter will filter the data flow out of server to client. - task_data_filters =[] - - # task result filter: if filters are provided, the filter will filter the result flow out of client to server. - task_result_filters = [] - - # This assumes that there will be a "net.py" file with class name "Net". - # If your model code is not in "net.py" and class name is not "Net", please modify here - #model_class_path = "nemo_nvflare.peft_model.PEFTmodel" - - # Location of pre-trained NeMo model file. - #restore_from_path = "/models/megatron_gpt_345m.nemo" - - # Location of pre-trained peft model file. - #peft_restore_from_path = null - - # workflows: Array of workflows the control the Federated Learning workflow lifecycle. - # One can specify multiple workflows. The NVFLARE will run them in the order specified. - workflows = [ - { - # 1st workflow" - id = "scatter_and_gather" - - # name = ScatterAndGather, path is the class path of the ScatterAndGather controller. - path = "nvflare.app_common.workflows.scatter_and_gather.ScatterAndGather" - args { - # argument of the ScatterAndGather class. - # min number of clients required for ScatterAndGather controller to move to the next round - # during the workflow cycle. The controller will wait until the min_clients returned from clients - # before move to the next step. - min_clients = 1 - - # number of global round of the training. - num_rounds = 1 - - # starting round is 0-based - start_round = 0 - - # after received min number of clients' result, - # how much time should we wait further before move to the next step - wait_time_after_min_received = 0 - - # For ScatterAndGather, the server will aggregate the weights based on the client's result. - # the aggregator component id is named here. One can use the this ID to find the corresponding - # aggregator component listed below - # - aggregator_id = "aggregator" - - # The Scatter and Gather controller use an persistor to load the model and save the model. - # The persistent component can be identified by component ID specified here. - #persistor_id = "persistor" - - # Shareable to a communication message, i.e. shared between clients and server. - # Shareable generator is a component that responsible to take the model convert to/from this communication message: sharable. - # The component can be identified via "shareable_generator_id" - shareable_generator_id = "shareable_generator" - - # train task name: Client will start training once received such task. - train_task_name = "train" - - # train timeout in second. If zero, meaning no timeout. - train_timeout = 0 - } - } - ] - - # List of components used in the server side workflow. - components = [ - #{ - # This is the persistence component used in above workflow. - # PTFileModelPersistor is a Pytorch persistor which save/read the model to/from file. - - # id = "persistor" - # path = "nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor" - - # the persistor class take model class as argument - # This imply that the model is initialized from the server-side. - # The initialized model will be broadcast to all the clients to start the training. - # args.model.path = "{model_class_path}" - # args.model.args.restore_from_path = "{restore_from_path}" - # args.model.args.peft_restore_from_path = "{peft_restore_from_path}" - #}, - { - # This is the generator that convert the model to shareable communication message structure used in workflow - id = "shareable_generator" - path = "nvflare.app_common.shareablegenerators.full_model_shareable_generator.FullModelShareableGenerator" - args = {} - }, - { - # This is the aggregator that perform the weighted average aggregation. - # the aggregation is "in-time", so it doesn't wait for client results, but aggregates as soon as it received the data. - id = "aggregator" - path = "nvflare.app_common.aggregators.intime_accumulate_model_aggregator.InTimeAccumulateWeightedAggregator" - args.expected_data_kind = "WEIGHTS" - }, - { - # This component is not directly used in Workflow. - # it select the best model based on the incoming global validation metrics. - id = "model_selector" - path = "nvflare.app_common.widgets.intime_model_selector.IntimeModelSelector" - # need to make sure this "key_metric" match what server side received - args.key_metric = "validation_exact_string_match" - } - ] - -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/base_config.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/base_config.yaml deleted file mode 100644 index 5c1d4686ad..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/base_config.yaml +++ /dev/null @@ -1,181 +0,0 @@ -name: esm1nv -do_training: True # set to false if data preprocessing steps must be completed -do_testing: False # set to true to run evaluation on test data after training, requires test_dataset section -restore_from_path: null # used when starting from a .nemo file - -trainer: - devices: 1 # number of GPUs or CPUs - num_nodes: 1 - accelerator: gpu #gpu or cpu - precision: 16-mixed #16 or 32 - logger: False # logger is provided by NeMo exp_manager - enable_checkpointing: False # checkpointing is done by NeMo exp_manager - max_epochs: null # # use max_steps instead with NeMo Megatron model - max_steps: 1000000 # consumed_samples = global_step * micro_batch_size * data_parallel_size * accumulate_grad_batches - log_every_n_steps: 1 # number of iterations between logging - val_check_interval: 1500 - limit_val_batches: 50 # number of batches in validation step, use fraction for fraction of data, 0 to disable - limit_test_batches: 500 # number of batches in test step, use fraction for fraction of data, 0 to disable - accumulate_grad_batches: 1 - gradient_clip_val: 1.0 - benchmark: False - -exp_manager: - name: ${name} - exp_dir: ${.name}/${.wandb_logger_kwargs.name} - explicit_log_dir: ${.exp_dir} - create_wandb_logger: False - create_tensorboard_logger: True - wandb_logger_kwargs: - project: ${name}_pretraining - name: ${name}_pretraining - group: ${name} - job_type: Localhost_nodes_${trainer.num_nodes}_gpus_${trainer.devices} - notes: "date: ${now:%y%m%d-%H%M%S}" - tags: - - ${name} - offline: False # set to True if there are issues uploading to WandB during training - resume_if_exists: True # automatically resume if checkpoint exists - resume_ignore_no_checkpoint: True # leave as True, will start new training if resume_if_exists is True but no checkpoint exists - create_checkpoint_callback: False # Setting this to False so to avoid overwriting the model sent and received to the server - checkpoint_callback_params: - monitor: val_TARGET_accuracy - save_top_k: 1 # number of checkpoints to save - mode: max # use min or max of monitored metric to select best checkpoints - always_save_nemo: False # saves nemo file during validation, not implemented for model parallel - filename: 'esm1nv--{val_TARGET_accuracy:.4f}-{step}-{consumed_samples}' - model_parallel_size: ${multiply:${model.tensor_model_parallel_size}, ${model.pipeline_model_parallel_size}} - - -model: - # model parallelism - micro_batch_size: 8 # NOTE: adjust to occupy ~ 90% of GPU memory - tensor_model_parallel_size: 1 # model parallelism - pipeline_model_parallel_size: 1 - - # model architecture - seq_length: 512 # FIXME: remove me (replaced by encoder_seq_length) - max_position_embeddings: ${.seq_length} - encoder_seq_length: ${.seq_length} - num_layers: 6 - hidden_size: 768 - ffn_hidden_size: 3072 # Transformer FFN hidden size. Usually 4 * hidden_size. - num_attention_heads: 12 - init_method_std: 0.02 # Standard deviation of the zero mean normal distribution used for weight initialization.') - hidden_dropout: 0.1 # 0.1 # Dropout probability for hidden state transformer. - kv_channels: null # Projection weights dimension in multi-head attention. Set to hidden_size // num_attention_heads if null - apply_query_key_layer_scaling: True # scale Q * K^T by 1 / layer-number. - layernorm_epsilon: 1e-5 - make_vocab_size_divisible_by: 128 # Pad the vocab size to be divisible by this value for computation efficiency. - pre_process: True # add embedding - post_process: True # add pooler - bert_binary_head: False # BERT binary head - resume_from_checkpoint: null # manually set the checkpoint file to load from - masked_softmax_fusion: True # Use a kernel that fuses the attention softmax with it's mask. - - tokenizer: - library: 'megatron' - type: 'BertWordPieceLowerCase' - model: null - vocab_file: null - merge_file: null - - # precision - native_amp_init_scale: 4294967296 # 2 ** 32 - native_amp_growth_interval: 1000 - fp32_residual_connection: False # Move residual connections to fp32 - fp16_lm_cross_entropy: False # Move the cross entropy unreduced loss calculation for lm head to fp16 - - - # miscellaneous - seed: 4 - use_cpu_initialization: False # Init weights on the CPU (slow for large model) - onnx_safe: False # Use work-arounds for known problems with Torch ONNX exporter. - - # not implemented in NeMo yet - activations_checkpoint_method: null # 'uniform', 'block' - activations_checkpoint_num_layers: 1 - - data: - ngc_registry_target: uniref50_2022_05 - ngc_registry_version: v23.06 - data_prefix: "" # must be null or "" - num_workers: 2 - dataloader_type: single # cyclic - reset_position_ids: False # Reset position ids after end-of-document token - reset_attention_mask: False # Reset attention mask after end-of-document token - eod_mask_loss: False # Mask loss for the end of document tokens - masked_lm_prob: 0.15 # Probability of replacing a token with mask. - short_seq_prob: 0.1 # Probability of producing a short sequence. - skip_lines: 0 - drop_last: False - pin_memory: False - index_mapping_dir: null # path to store cached indexing files (if empty, will be stored in the same directory as dataset_path) - data_impl: "csv_mmap" - # Supported kwargs (with default values): - # text_mmap (newline_int=10, header_lines=0, workers=None, sort_dataset_paths=True) - # csv_mmap (newline_int=10, header_lines=0,workers=None, sort_dataset_paths=True, data_col=1, data_sep=",") - data_impl_kwargs: - csv_mmap: - header_lines: 1 - newline_int: 10 # byte-value of newline - workers: ${model.data.num_workers} # number of workers when creating missing index files (null defaults to cpu_num // 2) - sort_dataset_paths: True # if True datasets will be sorted by name - data_sep: ',' # string to split text into columns - # column number of csv to take values from - data_col: 3 - use_upsampling: True # if the data should be upsampled to max number of steps in the training - seed: ${model.seed} # Random seed - max_seq_length: ${model.seq_length} # Maximum input sequence length. Longer sequences are truncated - dynamic_padding: False # If True, each batch is padded to the maximum sequence length within that batch. - # Set it to False when model.pipeline_model_parallel_size > 1, as pipeline parallelism requires fixed-length padding. - - optim: - name: fused_adam # fused optimizers used by Megatron model - lr: 2e-4 - weight_decay: 0.01 - betas: - - 0.9 - - 0.98 - sched: - name: CosineAnnealing - warmup_steps: 500 # use to set warmup_steps explicitly or leave as null to calculate - constant_steps: 50000 - min_lr: 2e-5 - - dwnstr_task_validation: - enabled: False - dataset: - class: bionemo.model.core.dwnstr_task_callbacks.PerTokenPredictionCallback - task_type: token-level-classification - infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference - max_seq_length: ${model.seq_length} - emb_batch_size: 128 - batch_size: 128 - num_epochs: 10 - shuffle: True - num_workers: 2 - task_name: secondary_structure - dataset_path: /data/FLIP/${model.dwnstr_task_validation.dataset.task_name} - dataset: - train: x000 - test: x000 - sequence_column: "sequence" # name of column with protein sequence in csv file - target_column: [ "3state", "resolved" ] # names of label columns in csv file - target_sizes: [ 3, 2 ] # number of classes in each label - mask_column: [ "resolved", null ] # names of mask columns in csv file, masks must be 0 or 1 - random_seed: ${model.seed} - optim: - name: adam - lr: 0.0001 - betas: - - 0.9 - - 0.999 - eps: 1e-8 - weight_decay: 0.01 - sched: - name: WarmupAnnealing - min_lr: 0.00001 - last_epoch: -1 - warmup_ratio: 0.01 - max_steps: 1000 diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip.py b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip.py deleted file mode 100644 index 067cf09552..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from bionemo.data import FLIPPreprocess -from bionemo.data.metrics import accuracy, mse, per_token_accuracy -from bionemo.model.protein.downstream import FineTuneProteinModel -from bionemo.model.utils import setup_trainer -from nemo.collections.nlp.parts.nlp_overrides import NLPSaveRestoreConnector -from nemo.core.config import hydra_runner -from nemo.utils import logging -from omegaconf.omegaconf import OmegaConf, open_dict - -# Import nvflare lightning API for federated learning -import nvflare.client.lightning as flare -from nvflare.client.api import init - -micro_batch_size = 32 - - -@hydra_runner(config_path=".", config_name="downstream_flip_sabdab") # ESM1 -def main(cfg) -> None: - logging.info("\n\n************* Finetune config ****************") - logging.info(f"\n{OmegaConf.to_yaml(cfg)}") - init() - # Get FL system info and set site-specific parameters - fl_sys_info = flare.system_info() - site_name = fl_sys_info["site_name"] - print(f"Running client {site_name} with train data: {cfg.model.data.dataset.train}") - print(f"Validation check interval: {cfg.trainer.val_check_interval}") - - # Do preprocessing if specified in config - if cfg.do_preprocessing: - logging.info("************** Starting Preprocessing ***********") - preprocessor = FLIPPreprocess() - preprocessor.prepare_all_datasets(output_dir=cfg.model.data.preprocessed_data_path) - - if not cfg.do_training and not cfg.do_testing: - return - - trainer = setup_trainer(cfg, builder=None, reset_accumulate_grad_batches=False) - - # Load model - with open_dict(cfg): - cfg.model.encoder_cfg = cfg - - if cfg.restore_from_path: - logging.info("\nRestoring model from .nemo file " + cfg.restore_from_path) - model = FineTuneProteinModel.restore_from( - cfg.restore_from_path, cfg.model, trainer=trainer, save_restore_connector=NLPSaveRestoreConnector() - ) - else: - model = FineTuneProteinModel(cfg.model, trainer) - - metrics = {} - metrics_args = {} - for idx, name in enumerate(cfg.model.data.target_column): - if cfg.model.data.task_type == "token-level-classification": - metrics[name + "_accuracy"] = per_token_accuracy - metrics_args[name + "_accuracy"] = {"label_id": idx} - elif cfg.model.data.task_type == "classification": - metrics[name + "_accuracy"] = accuracy - metrics_args[name + "_accuracy"] = {} - elif cfg.model.data.task_type == "regression": - metrics[name + "_MSE"] = mse - metrics_args[name + "_MSE"] = {} - - model.add_metrics(metrics=metrics, metrics_args=metrics_args) - - # Patch trainer for NVFlare federated learning - flare.patch(trainer) - - # Federated learning loop - while flare.is_running(): - fl_sys_info = flare.system_info() - print("--- fl_sys_info ---") - print(fl_sys_info) - - # Validate current global model - print("--- validate global model ---") - trainer.validate(model) - - # Perform local training with received global model - print("--- train new model ---") - trainer.fit(model) - logging.info("************** Finished Training ***********") - - if cfg.do_testing: - logging.info("************** Starting Testing ***********") - if "test" in cfg.model.data.dataset: - trainer.limit_train_batches = 0 - trainer.limit_val_batches = 0 - trainer.fit(model) - trainer.test(model, ckpt_path=None) - else: - raise UserWarning( - "Skipping testing, test dataset file was not provided. Please specify 'dataset.test' in yaml config" - ) - logging.info("************** Finished Testing ***********") - - -if __name__ == "__main__": - main() diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml deleted file mode 100644 index fd9507cf80..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml +++ /dev/null @@ -1,74 +0,0 @@ -name: esm1nv_flip -defaults: - - pretrain_small - - _self_ -do_preprocessing: False -do_training: True # set to false if data preprocessing steps must be completed -do_testing: False # set to true to run evaluation on test data after training -restore_from_path: null # path to nemo checkpoint of the fine-tuned model (encoder + task head) to be used for further training, testing or inference -target: bionemo.model.protein.esm1nv.ESM1nvModel # target class for protein model -infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference # target inference class for protein model -encoder_frozen: False - -trainer: - devices: 1 # number of GPUs or CPUs - num_nodes: 1 - max_epochs: 20 - val_check_interval: 0.0 - limit_val_batches: 0.0 # number of batches in validation step, use fraction for fraction of data, 0 to disable - limit_test_batches: 0.0 # number of batches in test step, use fraction for fraction of data, 0 to disable - use_distributed_sampler: False - -exp_manager: - wandb_logger_kwargs: - project: ${name}_${model.data.task_name}_finetuning - name: ${name}_${model.data.task_name}_finetuning_encoder_frozen_${model.encoder_frozen} - -model: - restore_encoder_path: ${oc.env:BIONEMO_HOME}/models/protein/esm1nv/esm1nv.nemo - encoder_frozen: False # encoder trainable or frozen - post_process: False # must be False for downstream task - micro_batch_size: 32 # NOTE: adjust to occupy ~ 90% of GPU memory - global_batch_size: null # if null will be computed automatically - tensor_model_parallel_size: 1 # model parallelism - loss_func: CrossEntropyLoss - hidden_layer_size: 256 - dropout_rate: 0.25 - - optim_param_groups: - encoder_model: - lr: 1e-5 - task_head: - lr: 5e-4 - - data: - task_name: tap # options: aav, bind, conservation, gb1, meltome, sav, scl, secondary_structure - task_type: classification #'token-level-classification' # alternative: classification, regression - preprocessed_data_path: /tmp/data # path where all preprocessed FLIP datasets are saved - dataset_path: ${model.data.preprocessed_data_path}/sabdab_chen # path to a training data - dataset: - train: sabdab_chen_full_train - val: sabdab_chen_valid - test: sabdab_chen_test - sequence_column: "Antibody" # name of column with protein sequence in csv file - target_column: ["Y"] #["3state"np.sum(test_df['Y']==0), "resolved"] # names of label columns in csv file - target_sizes: [2] # number of classes in each label for classifications or 1 for regression - num_classes: 2 - num_workers: 2 - shuffle: True # shuffle training dataset - max_seq_length: ${model.seq_length} - emb_batch_size: ${model.micro_batch_size} - - finetuning_optim: # optimizer parameters for downstream task model - name: adam - #lr: 0.0005 - betas: - - 0.9 - - 0.999 - eps: 1e-8 - weight_decay: 0.001 - #sched: - # name: WarmupAnnealing - # min_lr: 0.00001 #0.00001 - # last_epoch: -1 - # warmup_steps: 10 diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/infer.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/infer.yaml deleted file mode 100644 index 3368831d85..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/infer.yaml +++ /dev/null @@ -1,25 +0,0 @@ -defaults: - - base_infer_config - # allow this config to override defaults - - _self_ - -hydra: - searchpath: - - /workspace/bionemo/examples/conf/ - -name: ESM1nv_Inference -desc: Minimum configuration for initializing a ESM1nv model for inference. - -model: - post_process: False - tokenizer: - vocab_path: /tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.vocab - model_path: /tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.model - downstream_task: - restore_from_path: "/model/protein/esm1nv/esm1nv.nemo" - outputs: [embeddings, hiddens] # Which outputs to extract per sample (a value or list). Possible values: hiddens, embeddings. - data: - dataset_path: /data/FLIP/secondary_structure/test/x000 # full path to dataset (can include range or a list) - -target: bionemo.model.protein.esm1nv.esm1nv_model.ESM1nvModel # path to model class to load -infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference # path to inferende class to load \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/pretrain_small.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/pretrain_small.yaml deleted file mode 100644 index 4599d92bcc..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/app/custom/pretrain_small.yaml +++ /dev/null @@ -1,33 +0,0 @@ -defaults: - - base_config -restore_from_path: null # used when starting from a .nemo file - -model: - tokenizer: - library: 'sentencepiece' - type: null - model: ${oc.env:BIONEMO_HOME}/tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.model - vocab_file: ${oc.env:BIONEMO_HOME}/tokenizers/vocab/protein_sequence_sentencepiece.vocab - data: - dataset_path: ${oc.env:BIONEMO_HOME}/data/uniref2022_05 # parent directory for data, contains train / val / test folders. Needs to be writeable for index creation. - dataset: # inclusive range of data files to load x[000..049] or can a single file, e.g. x000 - train: x[000..049] - test: x[000..049] - val: x[000..049] - micro_batch_size: ${model.micro_batch_size} - num_workers: 2 - - # Supported kwargs (with default values): - # text_mmap (newline_int=10, header_lines=0, workers=None, sort_dataset_paths=True) - # csv_mmap (newline_int=10, header_lines=0,workers=None, sort_dataset_paths=True, data_col=1, data_sep=",") - data_impl_kwargs: - csv_mmap: - data_col: 3 # 0-based - - # These control the MLM token probabilities. The following settings are commonly used in literature. - modify_percent: 0.15 # Fraction of characters in a protein sequence to modify. (Modification means replacing with another amino acid or with a mask token) - perturb_percent: 0.1 # Of the modify_percent, what fraction of characters are to be replaced with another amino acid. - mask_percent: 0.8 # Of the modify_percent, what fraction of characters are to be replaced with a mask token. - identity_percent: 0.1 # Of the modify_percent, what fraction of characters are to be unchanged as the original amino acid. - dwnst_task_validation: - enabled: True \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/meta.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/meta.conf deleted file mode 100644 index a21a6ec425..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/central_sabdab_esm1nv/meta.conf +++ /dev/null @@ -1,10 +0,0 @@ -{ - name = "bionemo_local_finetune_esm1nv" - resource_spec = {} - deploy_map { - # change deploy map as needed. - app: ["@ALL"] - } - min_clients = 1 - mandatory_clients = [] -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_client.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_client.conf deleted file mode 100644 index 95a31d24c0..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_client.conf +++ /dev/null @@ -1,94 +0,0 @@ -{ - # version of the configuration - format_version = 2 - - # This is the application script which will be invoked. Client can replace this script with user's own training script. - app_script = "downstream_flip.py" - - # Additional arguments needed by the training code. For example, in lightning, these can be --trainer.batch_size=xxx. - app_config = "" - - # Additional arguments needed by DDP. - #ddp_config = "--nnodes=1 --nproc_per_node=1 --master_port=7777" - - # Client Computing Executors. - executors = [ - { - # tasks the executors are defined to handle - tasks = ["train"] - - # This particular executor - executor { - - # This is an executor for pytorch + Client API. The underline data exchange is using Pipe. - path = "nvflare.app_opt.pt.client_api_launcher_executor.PTClientAPILauncherExecutor" - - args { - # launcher_id is used to locate the Launcher object in "components" - launcher_id = "launcher" - - # pipe_id is used to locate the Pipe object in "components" - pipe_id = "pipe" - - # Timeout in seconds for waiting for a heartbeat from the training script. Defaults to 30 seconds. - # Please refer to the class docstring for all available arguments - heartbeat_timeout = 60 - - # format of the exchange parameters - params_exchange_format = "pytorch" - - # if the transfer_type is FULL, then it will be sent directly - # if the transfer_type is DIFF, then we will calculate the - # difference VS received parameters and send the difference - params_transfer_type = "FULL" - - # if train_with_evaluation is true, the executor will expect - # the custom code need to send back both the trained parameters and the evaluation metric - # otherwise only trained parameters are expected - train_with_evaluation = false - } - } - } - ], - - # this defined an array of task data filters. If provided, it will control the data from server controller to client executor - task_data_filters = [] - - # this defined an array of task result filters. If provided, it will control the result from client executor to server controller - task_result_filters = [] - - components = [ - { - # component id is "launcher" - id = "launcher" - - # the class path of this component - path = "nvflare.app_common.launchers.subprocess_launcher.SubprocessLauncher" - - args { - # the launcher will invoke the script - #script = "python3 -m torch.distributed.run {ddp_config} custom/{app_script} {app_config} " - script = "python3 custom/{app_script} {app_config} " - # if launch_once is true, the SubprocessLauncher will launch once for the whole job - # if launch_once is false, the SubprocessLauncher will launch a process for each task it receives from server - launch_once = true - } - } - { - id = "pipe" - - path = "nvflare.fuel.utils.pipe.file_pipe.FilePipe" - - args { - # Mode of the endpoint. A pipe has two endpoints. - # An endpoint can be either the one that initiates communication or the one listening. - # PASSIVE is the one listening. - mode = "PASSIVE" - - # root_path: is the directory location of the parameters exchange. - # You can also set it to an absolute path in your system. - root_path = "{WORKSPACE}/{JOB_ID}/{SITE_NAME}" - } - } - ] -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_server.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_server.conf deleted file mode 100644 index f13692a589..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/config/config_fed_server.conf +++ /dev/null @@ -1,110 +0,0 @@ -{ - # version of the configuration - format_version = 2 - - # task data filter: if filters are provided, the filter will filter the data flow out of server to client. - task_data_filters =[] - - # task result filter: if filters are provided, the filter will filter the result flow out of client to server. - task_result_filters = [] - - # This assumes that there will be a "net.py" file with class name "Net". - # If your model code is not in "net.py" and class name is not "Net", please modify here - #model_class_path = "nemo_nvflare.peft_model.PEFTmodel" - - # Location of pre-trained NeMo model file. - #restore_from_path = "/models/megatron_gpt_345m.nemo" - - # Location of pre-trained peft model file. - #peft_restore_from_path = null - - # workflows: Array of workflows the control the Federated Learning workflow lifecycle. - # One can specify multiple workflows. The NVFLARE will run them in the order specified. - workflows = [ - { - # 1st workflow" - id = "scatter_and_gather" - - # name = ScatterAndGather, path is the class path of the ScatterAndGather controller. - path = "nvflare.app_common.workflows.scatter_and_gather.ScatterAndGather" - args { - # argument of the ScatterAndGather class. - # min number of clients required for ScatterAndGather controller to move to the next round - # during the workflow cycle. The controller will wait until the min_clients returned from clients - # before move to the next step. - min_clients = 6 - - # number of global round of the training. - num_rounds = 20 - - # starting round is 0-based - start_round = 0 - - # after received min number of clients' result, - # how much time should we wait further before move to the next step - wait_time_after_min_received = 0 - - # For ScatterAndGather, the server will aggregate the weights based on the client's result. - # the aggregator component id is named here. One can use the this ID to find the corresponding - # aggregator component listed below - # - aggregator_id = "aggregator" - - # The Scatter and Gather controller use an persistor to load the model and save the model. - # The persistent component can be identified by component ID specified here. - #persistor_id = "persistor" - - # Shareable to a communication message, i.e. shared between clients and server. - # Shareable generator is a component that responsible to take the model convert to/from this communication message: sharable. - # The component can be identified via "shareable_generator_id" - shareable_generator_id = "shareable_generator" - - # train task name: Client will start training once received such task. - train_task_name = "train" - - # train timeout in second. If zero, meaning no timeout. - train_timeout = 0 - } - } - ] - - # List of components used in the server side workflow. - components = [ - #{ - # This is the persistence component used in above workflow. - # PTFileModelPersistor is a Pytorch persistor which save/read the model to/from file. - - # id = "persistor" - # path = "nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor" - - # the persistor class take model class as argument - # This imply that the model is initialized from the server-side. - # The initialized model will be broadcast to all the clients to start the training. - # args.model.path = "{model_class_path}" - # args.model.args.restore_from_path = "{restore_from_path}" - # args.model.args.peft_restore_from_path = "{peft_restore_from_path}" - #}, - { - # This is the generator that convert the model to shareable communication message structure used in workflow - id = "shareable_generator" - path = "nvflare.app_common.shareablegenerators.full_model_shareable_generator.FullModelShareableGenerator" - args = {} - }, - { - # This is the aggregator that perform the weighted average aggregation. - # the aggregation is "in-time", so it doesn't wait for client results, but aggregates as soon as it received the data. - id = "aggregator" - path = "nvflare.app_common.aggregators.intime_accumulate_model_aggregator.InTimeAccumulateWeightedAggregator" - args.expected_data_kind = "WEIGHTS" - }, - { - # This component is not directly used in Workflow. - # it select the best model based on the incoming global validation metrics. - id = "model_selector" - path = "nvflare.app_common.widgets.intime_model_selector.IntimeModelSelector" - # need to make sure this "key_metric" match what server side received - args.key_metric = "validation_exact_string_match" - } - ] - -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/base_config.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/base_config.yaml deleted file mode 100644 index 5c1d4686ad..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/base_config.yaml +++ /dev/null @@ -1,181 +0,0 @@ -name: esm1nv -do_training: True # set to false if data preprocessing steps must be completed -do_testing: False # set to true to run evaluation on test data after training, requires test_dataset section -restore_from_path: null # used when starting from a .nemo file - -trainer: - devices: 1 # number of GPUs or CPUs - num_nodes: 1 - accelerator: gpu #gpu or cpu - precision: 16-mixed #16 or 32 - logger: False # logger is provided by NeMo exp_manager - enable_checkpointing: False # checkpointing is done by NeMo exp_manager - max_epochs: null # # use max_steps instead with NeMo Megatron model - max_steps: 1000000 # consumed_samples = global_step * micro_batch_size * data_parallel_size * accumulate_grad_batches - log_every_n_steps: 1 # number of iterations between logging - val_check_interval: 1500 - limit_val_batches: 50 # number of batches in validation step, use fraction for fraction of data, 0 to disable - limit_test_batches: 500 # number of batches in test step, use fraction for fraction of data, 0 to disable - accumulate_grad_batches: 1 - gradient_clip_val: 1.0 - benchmark: False - -exp_manager: - name: ${name} - exp_dir: ${.name}/${.wandb_logger_kwargs.name} - explicit_log_dir: ${.exp_dir} - create_wandb_logger: False - create_tensorboard_logger: True - wandb_logger_kwargs: - project: ${name}_pretraining - name: ${name}_pretraining - group: ${name} - job_type: Localhost_nodes_${trainer.num_nodes}_gpus_${trainer.devices} - notes: "date: ${now:%y%m%d-%H%M%S}" - tags: - - ${name} - offline: False # set to True if there are issues uploading to WandB during training - resume_if_exists: True # automatically resume if checkpoint exists - resume_ignore_no_checkpoint: True # leave as True, will start new training if resume_if_exists is True but no checkpoint exists - create_checkpoint_callback: False # Setting this to False so to avoid overwriting the model sent and received to the server - checkpoint_callback_params: - monitor: val_TARGET_accuracy - save_top_k: 1 # number of checkpoints to save - mode: max # use min or max of monitored metric to select best checkpoints - always_save_nemo: False # saves nemo file during validation, not implemented for model parallel - filename: 'esm1nv--{val_TARGET_accuracy:.4f}-{step}-{consumed_samples}' - model_parallel_size: ${multiply:${model.tensor_model_parallel_size}, ${model.pipeline_model_parallel_size}} - - -model: - # model parallelism - micro_batch_size: 8 # NOTE: adjust to occupy ~ 90% of GPU memory - tensor_model_parallel_size: 1 # model parallelism - pipeline_model_parallel_size: 1 - - # model architecture - seq_length: 512 # FIXME: remove me (replaced by encoder_seq_length) - max_position_embeddings: ${.seq_length} - encoder_seq_length: ${.seq_length} - num_layers: 6 - hidden_size: 768 - ffn_hidden_size: 3072 # Transformer FFN hidden size. Usually 4 * hidden_size. - num_attention_heads: 12 - init_method_std: 0.02 # Standard deviation of the zero mean normal distribution used for weight initialization.') - hidden_dropout: 0.1 # 0.1 # Dropout probability for hidden state transformer. - kv_channels: null # Projection weights dimension in multi-head attention. Set to hidden_size // num_attention_heads if null - apply_query_key_layer_scaling: True # scale Q * K^T by 1 / layer-number. - layernorm_epsilon: 1e-5 - make_vocab_size_divisible_by: 128 # Pad the vocab size to be divisible by this value for computation efficiency. - pre_process: True # add embedding - post_process: True # add pooler - bert_binary_head: False # BERT binary head - resume_from_checkpoint: null # manually set the checkpoint file to load from - masked_softmax_fusion: True # Use a kernel that fuses the attention softmax with it's mask. - - tokenizer: - library: 'megatron' - type: 'BertWordPieceLowerCase' - model: null - vocab_file: null - merge_file: null - - # precision - native_amp_init_scale: 4294967296 # 2 ** 32 - native_amp_growth_interval: 1000 - fp32_residual_connection: False # Move residual connections to fp32 - fp16_lm_cross_entropy: False # Move the cross entropy unreduced loss calculation for lm head to fp16 - - - # miscellaneous - seed: 4 - use_cpu_initialization: False # Init weights on the CPU (slow for large model) - onnx_safe: False # Use work-arounds for known problems with Torch ONNX exporter. - - # not implemented in NeMo yet - activations_checkpoint_method: null # 'uniform', 'block' - activations_checkpoint_num_layers: 1 - - data: - ngc_registry_target: uniref50_2022_05 - ngc_registry_version: v23.06 - data_prefix: "" # must be null or "" - num_workers: 2 - dataloader_type: single # cyclic - reset_position_ids: False # Reset position ids after end-of-document token - reset_attention_mask: False # Reset attention mask after end-of-document token - eod_mask_loss: False # Mask loss for the end of document tokens - masked_lm_prob: 0.15 # Probability of replacing a token with mask. - short_seq_prob: 0.1 # Probability of producing a short sequence. - skip_lines: 0 - drop_last: False - pin_memory: False - index_mapping_dir: null # path to store cached indexing files (if empty, will be stored in the same directory as dataset_path) - data_impl: "csv_mmap" - # Supported kwargs (with default values): - # text_mmap (newline_int=10, header_lines=0, workers=None, sort_dataset_paths=True) - # csv_mmap (newline_int=10, header_lines=0,workers=None, sort_dataset_paths=True, data_col=1, data_sep=",") - data_impl_kwargs: - csv_mmap: - header_lines: 1 - newline_int: 10 # byte-value of newline - workers: ${model.data.num_workers} # number of workers when creating missing index files (null defaults to cpu_num // 2) - sort_dataset_paths: True # if True datasets will be sorted by name - data_sep: ',' # string to split text into columns - # column number of csv to take values from - data_col: 3 - use_upsampling: True # if the data should be upsampled to max number of steps in the training - seed: ${model.seed} # Random seed - max_seq_length: ${model.seq_length} # Maximum input sequence length. Longer sequences are truncated - dynamic_padding: False # If True, each batch is padded to the maximum sequence length within that batch. - # Set it to False when model.pipeline_model_parallel_size > 1, as pipeline parallelism requires fixed-length padding. - - optim: - name: fused_adam # fused optimizers used by Megatron model - lr: 2e-4 - weight_decay: 0.01 - betas: - - 0.9 - - 0.98 - sched: - name: CosineAnnealing - warmup_steps: 500 # use to set warmup_steps explicitly or leave as null to calculate - constant_steps: 50000 - min_lr: 2e-5 - - dwnstr_task_validation: - enabled: False - dataset: - class: bionemo.model.core.dwnstr_task_callbacks.PerTokenPredictionCallback - task_type: token-level-classification - infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference - max_seq_length: ${model.seq_length} - emb_batch_size: 128 - batch_size: 128 - num_epochs: 10 - shuffle: True - num_workers: 2 - task_name: secondary_structure - dataset_path: /data/FLIP/${model.dwnstr_task_validation.dataset.task_name} - dataset: - train: x000 - test: x000 - sequence_column: "sequence" # name of column with protein sequence in csv file - target_column: [ "3state", "resolved" ] # names of label columns in csv file - target_sizes: [ 3, 2 ] # number of classes in each label - mask_column: [ "resolved", null ] # names of mask columns in csv file, masks must be 0 or 1 - random_seed: ${model.seed} - optim: - name: adam - lr: 0.0001 - betas: - - 0.9 - - 0.999 - eps: 1e-8 - weight_decay: 0.01 - sched: - name: WarmupAnnealing - min_lr: 0.00001 - last_epoch: -1 - warmup_ratio: 0.01 - max_steps: 1000 diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip.py b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip.py deleted file mode 100644 index 9b254a2990..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from bionemo.data import FLIPPreprocess -from bionemo.data.metrics import accuracy, mse, per_token_accuracy -from bionemo.model.protein.downstream import FineTuneProteinModel -from bionemo.model.utils import setup_trainer -from nemo.collections.nlp.parts.nlp_overrides import NLPSaveRestoreConnector -from nemo.core.config import hydra_runner -from nemo.utils import logging -from omegaconf.omegaconf import OmegaConf, open_dict - -# Import nvflare lightning API for federated learning -import nvflare.client.lightning as flare -from nvflare.client.api import init - -micro_batch_size = 32 -val_check_intervals = { - "site-1": min(int(416 / micro_batch_size), 3), # Use min to ensure it's <= 3 - "site-2": min(int(238 / micro_batch_size), 3), - "site-3": min(int(282 / micro_batch_size), 3), - "site-4": min(int(472 / micro_batch_size), 3), - "site-5": min(int(361 / micro_batch_size), 3), - "site-6": min(int(157 / micro_batch_size), 3), -} - - -@hydra_runner(config_path=".", config_name="downstream_flip_sabdab") # ESM1 -def main(cfg) -> None: - logging.info("\n\n************* Finetune config ****************") - logging.info(f"\n{OmegaConf.to_yaml(cfg)}") - init() - # Get FL system info and set site-specific parameters - fl_sys_info = flare.system_info() - site_name = fl_sys_info["site_name"] - cfg.model.data.dataset.train = f"sabdab_chen_{site_name}_train" - cfg.trainer.val_check_interval = val_check_intervals[site_name] - print(f"Running client {site_name} with train data: {cfg.model.data.dataset.train}") - print(f"Validation check interval: {cfg.trainer.val_check_interval}") - - # Do preprocessing if specified in config - if cfg.do_preprocessing: - logging.info("************** Starting Preprocessing ***********") - preprocessor = FLIPPreprocess() - preprocessor.prepare_all_datasets(output_dir=cfg.model.data.preprocessed_data_path) - - if not cfg.do_training and not cfg.do_testing: - return - - trainer = setup_trainer(cfg, builder=None, reset_accumulate_grad_batches=False) - - # Load model - with open_dict(cfg): - cfg.model.encoder_cfg = cfg - - if cfg.restore_from_path: - logging.info("\nRestoring model from .nemo file " + cfg.restore_from_path) - model = FineTuneProteinModel.restore_from( - cfg.restore_from_path, cfg.model, trainer=trainer, save_restore_connector=NLPSaveRestoreConnector() - ) - else: - model = FineTuneProteinModel(cfg.model, trainer) - - metrics = {} - metrics_args = {} - for idx, name in enumerate(cfg.model.data.target_column): - if cfg.model.data.task_type == "token-level-classification": - metrics[name + "_accuracy"] = per_token_accuracy - metrics_args[name + "_accuracy"] = {"label_id": idx} - elif cfg.model.data.task_type == "classification": - metrics[name + "_accuracy"] = accuracy - metrics_args[name + "_accuracy"] = {} - elif cfg.model.data.task_type == "regression": - metrics[name + "_MSE"] = mse - metrics_args[name + "_MSE"] = {} - - model.add_metrics(metrics=metrics, metrics_args=metrics_args) - - # Patch trainer for NVFlare federated learning - flare.patch(trainer) - - # Federated learning loop - while flare.is_running(): - fl_sys_info = flare.system_info() - print("--- fl_sys_info ---") - print(fl_sys_info) - - # Validate current global model - print("--- validate global model ---") - trainer.validate(model) - - # Perform local training with received global model - print("--- train new model ---") - trainer.fit(model) - logging.info("************** Finished Training ***********") - - if cfg.do_testing: - logging.info("************** Starting Testing ***********") - if "test" in cfg.model.data.dataset: - trainer.limit_train_batches = 0 - trainer.limit_val_batches = 0 - trainer.fit(model) - trainer.test(model, ckpt_path=None) - else: - raise UserWarning( - "Skipping testing, test dataset file was not provided. Please specify 'dataset.test' in yaml config" - ) - logging.info("************** Finished Testing ***********") - - -if __name__ == "__main__": - main() diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml deleted file mode 100644 index 6488ad5205..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml +++ /dev/null @@ -1,74 +0,0 @@ -name: esm1nv_flip -defaults: - - pretrain_small - - _self_ -do_preprocessing: False -do_training: True # set to false if data preprocessing steps must be completed -do_testing: False # set to true to run evaluation on test data after training -restore_from_path: null # path to nemo checkpoint of the fine-tuned model (encoder + task head) to be used for further training, testing or inference -target: bionemo.model.protein.esm1nv.ESM1nvModel # target class for protein model -infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference # target inference class for protein model -encoder_frozen: False - -trainer: - devices: 1 # number of GPUs or CPUs - num_nodes: 1 - max_epochs: 1 - val_check_interval: 0.0 - limit_val_batches: 0.0 # number of batches in validation step, use fraction for fraction of data, 0 to disable - limit_test_batches: 0.0 # number of batches in test step, use fraction for fraction of data, 0 to disable - use_distributed_sampler: False - -exp_manager: - wandb_logger_kwargs: - project: ${name}_${model.data.task_name}_finetuning - name: ${name}_${model.data.task_name}_finetuning_encoder_frozen_${model.encoder_frozen} - -model: - restore_encoder_path: ${oc.env:BIONEMO_HOME}/models/protein/esm1nv/esm1nv.nemo - encoder_frozen: False # encoder trainable or frozen - post_process: False # must be False for downstream task - micro_batch_size: 32 # NOTE: adjust to occupy ~ 90% of GPU memory - global_batch_size: null # if null will be computed automatically - tensor_model_parallel_size: 1 # model parallelism - loss_func: CrossEntropyLoss - hidden_layer_size: 256 - dropout_rate: 0.25 - - optim_param_groups: - encoder_model: - lr: 1e-5 - task_head: - lr: 5e-4 - - data: - task_name: tap # options: aav, bind, conservation, gb1, meltome, sav, scl, secondary_structure - task_type: classification #'token-level-classification' # alternative: classification, regression - preprocessed_data_path: /tmp/data # path where all preprocessed FLIP datasets are saved - dataset_path: ${model.data.preprocessed_data_path}/sabdab_chen # path to a training data - dataset: - train: ??? # train data will be set in `custom/downstream_flip.py`, e.g. to "sabdab_chen_site-1_train" - val: sabdab_chen_valid - test: sabdab_chen_test - sequence_column: "Antibody" # name of column with protein sequence in csv file - target_column: ["Y"] #["3state"np.sum(test_df['Y']==0), "resolved"] # names of label columns in csv file - target_sizes: [2] # number of classes in each label for classifications or 1 for regression - num_classes: 2 - num_workers: 2 - shuffle: True # shuffle training dataset - max_seq_length: ${model.seq_length} - emb_batch_size: ${model.micro_batch_size} - - finetuning_optim: # optimizer parameters for downstream task model - name: adam - #lr: 0.0005 - betas: - - 0.9 - - 0.999 - eps: 1e-8 - weight_decay: 0.001 - #sched: - # name: WarmupAnnealing - # min_lr: 0.00001 #0.00001 - # last_epoch: -1 - # warmup_steps: 10 diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/infer.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/infer.yaml deleted file mode 100644 index 3368831d85..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/infer.yaml +++ /dev/null @@ -1,25 +0,0 @@ -defaults: - - base_infer_config - # allow this config to override defaults - - _self_ - -hydra: - searchpath: - - /workspace/bionemo/examples/conf/ - -name: ESM1nv_Inference -desc: Minimum configuration for initializing a ESM1nv model for inference. - -model: - post_process: False - tokenizer: - vocab_path: /tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.vocab - model_path: /tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.model - downstream_task: - restore_from_path: "/model/protein/esm1nv/esm1nv.nemo" - outputs: [embeddings, hiddens] # Which outputs to extract per sample (a value or list). Possible values: hiddens, embeddings. - data: - dataset_path: /data/FLIP/secondary_structure/test/x000 # full path to dataset (can include range or a list) - -target: bionemo.model.protein.esm1nv.esm1nv_model.ESM1nvModel # path to model class to load -infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference # path to inferende class to load \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/pretrain_small.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/pretrain_small.yaml deleted file mode 100644 index 4599d92bcc..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/app/custom/pretrain_small.yaml +++ /dev/null @@ -1,33 +0,0 @@ -defaults: - - base_config -restore_from_path: null # used when starting from a .nemo file - -model: - tokenizer: - library: 'sentencepiece' - type: null - model: ${oc.env:BIONEMO_HOME}/tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.model - vocab_file: ${oc.env:BIONEMO_HOME}/tokenizers/vocab/protein_sequence_sentencepiece.vocab - data: - dataset_path: ${oc.env:BIONEMO_HOME}/data/uniref2022_05 # parent directory for data, contains train / val / test folders. Needs to be writeable for index creation. - dataset: # inclusive range of data files to load x[000..049] or can a single file, e.g. x000 - train: x[000..049] - test: x[000..049] - val: x[000..049] - micro_batch_size: ${model.micro_batch_size} - num_workers: 2 - - # Supported kwargs (with default values): - # text_mmap (newline_int=10, header_lines=0, workers=None, sort_dataset_paths=True) - # csv_mmap (newline_int=10, header_lines=0,workers=None, sort_dataset_paths=True, data_col=1, data_sep=",") - data_impl_kwargs: - csv_mmap: - data_col: 3 # 0-based - - # These control the MLM token probabilities. The following settings are commonly used in literature. - modify_percent: 0.15 # Fraction of characters in a protein sequence to modify. (Modification means replacing with another amino acid or with a mask token) - perturb_percent: 0.1 # Of the modify_percent, what fraction of characters are to be replaced with another amino acid. - mask_percent: 0.8 # Of the modify_percent, what fraction of characters are to be replaced with a mask token. - identity_percent: 0.1 # Of the modify_percent, what fraction of characters are to be unchanged as the original amino acid. - dwnst_task_validation: - enabled: True \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/meta.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/meta.conf deleted file mode 100644 index a21a6ec425..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/fedavg_sabdab_esm1nv/meta.conf +++ /dev/null @@ -1,10 +0,0 @@ -{ - name = "bionemo_local_finetune_esm1nv" - resource_spec = {} - deploy_map { - # change deploy map as needed. - app: ["@ALL"] - } - min_clients = 1 - mandatory_clients = [] -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_client.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_client.conf deleted file mode 100644 index 95a31d24c0..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_client.conf +++ /dev/null @@ -1,94 +0,0 @@ -{ - # version of the configuration - format_version = 2 - - # This is the application script which will be invoked. Client can replace this script with user's own training script. - app_script = "downstream_flip.py" - - # Additional arguments needed by the training code. For example, in lightning, these can be --trainer.batch_size=xxx. - app_config = "" - - # Additional arguments needed by DDP. - #ddp_config = "--nnodes=1 --nproc_per_node=1 --master_port=7777" - - # Client Computing Executors. - executors = [ - { - # tasks the executors are defined to handle - tasks = ["train"] - - # This particular executor - executor { - - # This is an executor for pytorch + Client API. The underline data exchange is using Pipe. - path = "nvflare.app_opt.pt.client_api_launcher_executor.PTClientAPILauncherExecutor" - - args { - # launcher_id is used to locate the Launcher object in "components" - launcher_id = "launcher" - - # pipe_id is used to locate the Pipe object in "components" - pipe_id = "pipe" - - # Timeout in seconds for waiting for a heartbeat from the training script. Defaults to 30 seconds. - # Please refer to the class docstring for all available arguments - heartbeat_timeout = 60 - - # format of the exchange parameters - params_exchange_format = "pytorch" - - # if the transfer_type is FULL, then it will be sent directly - # if the transfer_type is DIFF, then we will calculate the - # difference VS received parameters and send the difference - params_transfer_type = "FULL" - - # if train_with_evaluation is true, the executor will expect - # the custom code need to send back both the trained parameters and the evaluation metric - # otherwise only trained parameters are expected - train_with_evaluation = false - } - } - } - ], - - # this defined an array of task data filters. If provided, it will control the data from server controller to client executor - task_data_filters = [] - - # this defined an array of task result filters. If provided, it will control the result from client executor to server controller - task_result_filters = [] - - components = [ - { - # component id is "launcher" - id = "launcher" - - # the class path of this component - path = "nvflare.app_common.launchers.subprocess_launcher.SubprocessLauncher" - - args { - # the launcher will invoke the script - #script = "python3 -m torch.distributed.run {ddp_config} custom/{app_script} {app_config} " - script = "python3 custom/{app_script} {app_config} " - # if launch_once is true, the SubprocessLauncher will launch once for the whole job - # if launch_once is false, the SubprocessLauncher will launch a process for each task it receives from server - launch_once = true - } - } - { - id = "pipe" - - path = "nvflare.fuel.utils.pipe.file_pipe.FilePipe" - - args { - # Mode of the endpoint. A pipe has two endpoints. - # An endpoint can be either the one that initiates communication or the one listening. - # PASSIVE is the one listening. - mode = "PASSIVE" - - # root_path: is the directory location of the parameters exchange. - # You can also set it to an absolute path in your system. - root_path = "{WORKSPACE}/{JOB_ID}/{SITE_NAME}" - } - } - ] -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_server.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_server.conf deleted file mode 100644 index 95e9235d89..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/config/config_fed_server.conf +++ /dev/null @@ -1,110 +0,0 @@ -{ - # version of the configuration - format_version = 2 - - # task data filter: if filters are provided, the filter will filter the data flow out of server to client. - task_data_filters =[] - - # task result filter: if filters are provided, the filter will filter the result flow out of client to server. - task_result_filters = [] - - # This assumes that there will be a "net.py" file with class name "Net". - # If your model code is not in "net.py" and class name is not "Net", please modify here - #model_class_path = "nemo_nvflare.peft_model.PEFTmodel" - - # Location of pre-trained NeMo model file. - #restore_from_path = "/models/megatron_gpt_345m.nemo" - - # Location of pre-trained peft model file. - #peft_restore_from_path = null - - # workflows: Array of workflows the control the Federated Learning workflow lifecycle. - # One can specify multiple workflows. The NVFLARE will run them in the order specified. - workflows = [ - { - # 1st workflow" - id = "scatter_and_gather" - - # name = ScatterAndGather, path is the class path of the ScatterAndGather controller. - path = "nvflare.app_common.workflows.scatter_and_gather.ScatterAndGather" - args { - # argument of the ScatterAndGather class. - # min number of clients required for ScatterAndGather controller to move to the next round - # during the workflow cycle. The controller will wait until the min_clients returned from clients - # before move to the next step. - min_clients = 6 - - # number of global round of the training. - num_rounds = 1 - - # starting round is 0-based - start_round = 0 - - # after received min number of clients' result, - # how much time should we wait further before move to the next step - wait_time_after_min_received = 0 - - # For ScatterAndGather, the server will aggregate the weights based on the client's result. - # the aggregator component id is named here. One can use the this ID to find the corresponding - # aggregator component listed below - # - aggregator_id = "aggregator" - - # The Scatter and Gather controller use an persistor to load the model and save the model. - # The persistent component can be identified by component ID specified here. - #persistor_id = "persistor" - - # Shareable to a communication message, i.e. shared between clients and server. - # Shareable generator is a component that responsible to take the model convert to/from this communication message: sharable. - # The component can be identified via "shareable_generator_id" - shareable_generator_id = "shareable_generator" - - # train task name: Client will start training once received such task. - train_task_name = "train" - - # train timeout in second. If zero, meaning no timeout. - train_timeout = 0 - } - } - ] - - # List of components used in the server side workflow. - components = [ - #{ - # This is the persistence component used in above workflow. - # PTFileModelPersistor is a Pytorch persistor which save/read the model to/from file. - - # id = "persistor" - # path = "nvflare.app_opt.pt.file_model_persistor.PTFileModelPersistor" - - # the persistor class take model class as argument - # This imply that the model is initialized from the server-side. - # The initialized model will be broadcast to all the clients to start the training. - # args.model.path = "{model_class_path}" - # args.model.args.restore_from_path = "{restore_from_path}" - # args.model.args.peft_restore_from_path = "{peft_restore_from_path}" - #}, - { - # This is the generator that convert the model to shareable communication message structure used in workflow - id = "shareable_generator" - path = "nvflare.app_common.shareablegenerators.full_model_shareable_generator.FullModelShareableGenerator" - args = {} - }, - { - # This is the aggregator that perform the weighted average aggregation. - # the aggregation is "in-time", so it doesn't wait for client results, but aggregates as soon as it received the data. - id = "aggregator" - path = "nvflare.app_common.aggregators.intime_accumulate_model_aggregator.InTimeAccumulateWeightedAggregator" - args.expected_data_kind = "WEIGHTS" - }, - { - # This component is not directly used in Workflow. - # it select the best model based on the incoming global validation metrics. - id = "model_selector" - path = "nvflare.app_common.widgets.intime_model_selector.IntimeModelSelector" - # need to make sure this "key_metric" match what server side received - args.key_metric = "validation_exact_string_match" - } - ] - -} diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/base_config.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/base_config.yaml deleted file mode 100644 index 5c1d4686ad..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/base_config.yaml +++ /dev/null @@ -1,181 +0,0 @@ -name: esm1nv -do_training: True # set to false if data preprocessing steps must be completed -do_testing: False # set to true to run evaluation on test data after training, requires test_dataset section -restore_from_path: null # used when starting from a .nemo file - -trainer: - devices: 1 # number of GPUs or CPUs - num_nodes: 1 - accelerator: gpu #gpu or cpu - precision: 16-mixed #16 or 32 - logger: False # logger is provided by NeMo exp_manager - enable_checkpointing: False # checkpointing is done by NeMo exp_manager - max_epochs: null # # use max_steps instead with NeMo Megatron model - max_steps: 1000000 # consumed_samples = global_step * micro_batch_size * data_parallel_size * accumulate_grad_batches - log_every_n_steps: 1 # number of iterations between logging - val_check_interval: 1500 - limit_val_batches: 50 # number of batches in validation step, use fraction for fraction of data, 0 to disable - limit_test_batches: 500 # number of batches in test step, use fraction for fraction of data, 0 to disable - accumulate_grad_batches: 1 - gradient_clip_val: 1.0 - benchmark: False - -exp_manager: - name: ${name} - exp_dir: ${.name}/${.wandb_logger_kwargs.name} - explicit_log_dir: ${.exp_dir} - create_wandb_logger: False - create_tensorboard_logger: True - wandb_logger_kwargs: - project: ${name}_pretraining - name: ${name}_pretraining - group: ${name} - job_type: Localhost_nodes_${trainer.num_nodes}_gpus_${trainer.devices} - notes: "date: ${now:%y%m%d-%H%M%S}" - tags: - - ${name} - offline: False # set to True if there are issues uploading to WandB during training - resume_if_exists: True # automatically resume if checkpoint exists - resume_ignore_no_checkpoint: True # leave as True, will start new training if resume_if_exists is True but no checkpoint exists - create_checkpoint_callback: False # Setting this to False so to avoid overwriting the model sent and received to the server - checkpoint_callback_params: - monitor: val_TARGET_accuracy - save_top_k: 1 # number of checkpoints to save - mode: max # use min or max of monitored metric to select best checkpoints - always_save_nemo: False # saves nemo file during validation, not implemented for model parallel - filename: 'esm1nv--{val_TARGET_accuracy:.4f}-{step}-{consumed_samples}' - model_parallel_size: ${multiply:${model.tensor_model_parallel_size}, ${model.pipeline_model_parallel_size}} - - -model: - # model parallelism - micro_batch_size: 8 # NOTE: adjust to occupy ~ 90% of GPU memory - tensor_model_parallel_size: 1 # model parallelism - pipeline_model_parallel_size: 1 - - # model architecture - seq_length: 512 # FIXME: remove me (replaced by encoder_seq_length) - max_position_embeddings: ${.seq_length} - encoder_seq_length: ${.seq_length} - num_layers: 6 - hidden_size: 768 - ffn_hidden_size: 3072 # Transformer FFN hidden size. Usually 4 * hidden_size. - num_attention_heads: 12 - init_method_std: 0.02 # Standard deviation of the zero mean normal distribution used for weight initialization.') - hidden_dropout: 0.1 # 0.1 # Dropout probability for hidden state transformer. - kv_channels: null # Projection weights dimension in multi-head attention. Set to hidden_size // num_attention_heads if null - apply_query_key_layer_scaling: True # scale Q * K^T by 1 / layer-number. - layernorm_epsilon: 1e-5 - make_vocab_size_divisible_by: 128 # Pad the vocab size to be divisible by this value for computation efficiency. - pre_process: True # add embedding - post_process: True # add pooler - bert_binary_head: False # BERT binary head - resume_from_checkpoint: null # manually set the checkpoint file to load from - masked_softmax_fusion: True # Use a kernel that fuses the attention softmax with it's mask. - - tokenizer: - library: 'megatron' - type: 'BertWordPieceLowerCase' - model: null - vocab_file: null - merge_file: null - - # precision - native_amp_init_scale: 4294967296 # 2 ** 32 - native_amp_growth_interval: 1000 - fp32_residual_connection: False # Move residual connections to fp32 - fp16_lm_cross_entropy: False # Move the cross entropy unreduced loss calculation for lm head to fp16 - - - # miscellaneous - seed: 4 - use_cpu_initialization: False # Init weights on the CPU (slow for large model) - onnx_safe: False # Use work-arounds for known problems with Torch ONNX exporter. - - # not implemented in NeMo yet - activations_checkpoint_method: null # 'uniform', 'block' - activations_checkpoint_num_layers: 1 - - data: - ngc_registry_target: uniref50_2022_05 - ngc_registry_version: v23.06 - data_prefix: "" # must be null or "" - num_workers: 2 - dataloader_type: single # cyclic - reset_position_ids: False # Reset position ids after end-of-document token - reset_attention_mask: False # Reset attention mask after end-of-document token - eod_mask_loss: False # Mask loss for the end of document tokens - masked_lm_prob: 0.15 # Probability of replacing a token with mask. - short_seq_prob: 0.1 # Probability of producing a short sequence. - skip_lines: 0 - drop_last: False - pin_memory: False - index_mapping_dir: null # path to store cached indexing files (if empty, will be stored in the same directory as dataset_path) - data_impl: "csv_mmap" - # Supported kwargs (with default values): - # text_mmap (newline_int=10, header_lines=0, workers=None, sort_dataset_paths=True) - # csv_mmap (newline_int=10, header_lines=0,workers=None, sort_dataset_paths=True, data_col=1, data_sep=",") - data_impl_kwargs: - csv_mmap: - header_lines: 1 - newline_int: 10 # byte-value of newline - workers: ${model.data.num_workers} # number of workers when creating missing index files (null defaults to cpu_num // 2) - sort_dataset_paths: True # if True datasets will be sorted by name - data_sep: ',' # string to split text into columns - # column number of csv to take values from - data_col: 3 - use_upsampling: True # if the data should be upsampled to max number of steps in the training - seed: ${model.seed} # Random seed - max_seq_length: ${model.seq_length} # Maximum input sequence length. Longer sequences are truncated - dynamic_padding: False # If True, each batch is padded to the maximum sequence length within that batch. - # Set it to False when model.pipeline_model_parallel_size > 1, as pipeline parallelism requires fixed-length padding. - - optim: - name: fused_adam # fused optimizers used by Megatron model - lr: 2e-4 - weight_decay: 0.01 - betas: - - 0.9 - - 0.98 - sched: - name: CosineAnnealing - warmup_steps: 500 # use to set warmup_steps explicitly or leave as null to calculate - constant_steps: 50000 - min_lr: 2e-5 - - dwnstr_task_validation: - enabled: False - dataset: - class: bionemo.model.core.dwnstr_task_callbacks.PerTokenPredictionCallback - task_type: token-level-classification - infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference - max_seq_length: ${model.seq_length} - emb_batch_size: 128 - batch_size: 128 - num_epochs: 10 - shuffle: True - num_workers: 2 - task_name: secondary_structure - dataset_path: /data/FLIP/${model.dwnstr_task_validation.dataset.task_name} - dataset: - train: x000 - test: x000 - sequence_column: "sequence" # name of column with protein sequence in csv file - target_column: [ "3state", "resolved" ] # names of label columns in csv file - target_sizes: [ 3, 2 ] # number of classes in each label - mask_column: [ "resolved", null ] # names of mask columns in csv file, masks must be 0 or 1 - random_seed: ${model.seed} - optim: - name: adam - lr: 0.0001 - betas: - - 0.9 - - 0.999 - eps: 1e-8 - weight_decay: 0.01 - sched: - name: WarmupAnnealing - min_lr: 0.00001 - last_epoch: -1 - warmup_ratio: 0.01 - max_steps: 1000 diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip.py b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip.py deleted file mode 100644 index 9b254a2990..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from bionemo.data import FLIPPreprocess -from bionemo.data.metrics import accuracy, mse, per_token_accuracy -from bionemo.model.protein.downstream import FineTuneProteinModel -from bionemo.model.utils import setup_trainer -from nemo.collections.nlp.parts.nlp_overrides import NLPSaveRestoreConnector -from nemo.core.config import hydra_runner -from nemo.utils import logging -from omegaconf.omegaconf import OmegaConf, open_dict - -# Import nvflare lightning API for federated learning -import nvflare.client.lightning as flare -from nvflare.client.api import init - -micro_batch_size = 32 -val_check_intervals = { - "site-1": min(int(416 / micro_batch_size), 3), # Use min to ensure it's <= 3 - "site-2": min(int(238 / micro_batch_size), 3), - "site-3": min(int(282 / micro_batch_size), 3), - "site-4": min(int(472 / micro_batch_size), 3), - "site-5": min(int(361 / micro_batch_size), 3), - "site-6": min(int(157 / micro_batch_size), 3), -} - - -@hydra_runner(config_path=".", config_name="downstream_flip_sabdab") # ESM1 -def main(cfg) -> None: - logging.info("\n\n************* Finetune config ****************") - logging.info(f"\n{OmegaConf.to_yaml(cfg)}") - init() - # Get FL system info and set site-specific parameters - fl_sys_info = flare.system_info() - site_name = fl_sys_info["site_name"] - cfg.model.data.dataset.train = f"sabdab_chen_{site_name}_train" - cfg.trainer.val_check_interval = val_check_intervals[site_name] - print(f"Running client {site_name} with train data: {cfg.model.data.dataset.train}") - print(f"Validation check interval: {cfg.trainer.val_check_interval}") - - # Do preprocessing if specified in config - if cfg.do_preprocessing: - logging.info("************** Starting Preprocessing ***********") - preprocessor = FLIPPreprocess() - preprocessor.prepare_all_datasets(output_dir=cfg.model.data.preprocessed_data_path) - - if not cfg.do_training and not cfg.do_testing: - return - - trainer = setup_trainer(cfg, builder=None, reset_accumulate_grad_batches=False) - - # Load model - with open_dict(cfg): - cfg.model.encoder_cfg = cfg - - if cfg.restore_from_path: - logging.info("\nRestoring model from .nemo file " + cfg.restore_from_path) - model = FineTuneProteinModel.restore_from( - cfg.restore_from_path, cfg.model, trainer=trainer, save_restore_connector=NLPSaveRestoreConnector() - ) - else: - model = FineTuneProteinModel(cfg.model, trainer) - - metrics = {} - metrics_args = {} - for idx, name in enumerate(cfg.model.data.target_column): - if cfg.model.data.task_type == "token-level-classification": - metrics[name + "_accuracy"] = per_token_accuracy - metrics_args[name + "_accuracy"] = {"label_id": idx} - elif cfg.model.data.task_type == "classification": - metrics[name + "_accuracy"] = accuracy - metrics_args[name + "_accuracy"] = {} - elif cfg.model.data.task_type == "regression": - metrics[name + "_MSE"] = mse - metrics_args[name + "_MSE"] = {} - - model.add_metrics(metrics=metrics, metrics_args=metrics_args) - - # Patch trainer for NVFlare federated learning - flare.patch(trainer) - - # Federated learning loop - while flare.is_running(): - fl_sys_info = flare.system_info() - print("--- fl_sys_info ---") - print(fl_sys_info) - - # Validate current global model - print("--- validate global model ---") - trainer.validate(model) - - # Perform local training with received global model - print("--- train new model ---") - trainer.fit(model) - logging.info("************** Finished Training ***********") - - if cfg.do_testing: - logging.info("************** Starting Testing ***********") - if "test" in cfg.model.data.dataset: - trainer.limit_train_batches = 0 - trainer.limit_val_batches = 0 - trainer.fit(model) - trainer.test(model, ckpt_path=None) - else: - raise UserWarning( - "Skipping testing, test dataset file was not provided. Please specify 'dataset.test' in yaml config" - ) - logging.info("************** Finished Testing ***********") - - -if __name__ == "__main__": - main() diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml deleted file mode 100644 index b21438510e..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/downstream_flip_sabdab.yaml +++ /dev/null @@ -1,74 +0,0 @@ -name: esm1nv_flip -defaults: - - pretrain_small - - _self_ -do_preprocessing: False -do_training: True # set to false if data preprocessing steps must be completed -do_testing: False # set to true to run evaluation on test data after training -restore_from_path: null # path to nemo checkpoint of the fine-tuned model (encoder + task head) to be used for further training, testing or inference -target: bionemo.model.protein.esm1nv.ESM1nvModel # target class for protein model -infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference # target inference class for protein model -encoder_frozen: False - -trainer: - devices: 1 # number of GPUs or CPUs - num_nodes: 1 - max_epochs: 20 - val_check_interval: 0.0 - limit_val_batches: 0.0 # number of batches in validation step, use fraction for fraction of data, 0 to disable - limit_test_batches: 0.0 # number of batches in test step, use fraction for fraction of data, 0 to disable - use_distributed_sampler: False - -exp_manager: - wandb_logger_kwargs: - project: ${name}_${model.data.task_name}_finetuning - name: ${name}_${model.data.task_name}_finetuning_encoder_frozen_${model.encoder_frozen} - -model: - restore_encoder_path: ${oc.env:BIONEMO_HOME}/models/protein/esm1nv/esm1nv.nemo - encoder_frozen: False # encoder trainable or frozen - post_process: False # must be False for downstream task - micro_batch_size: 32 # NOTE: adjust to occupy ~ 90% of GPU memory - global_batch_size: null # if null will be computed automatically - tensor_model_parallel_size: 1 # model parallelism - loss_func: CrossEntropyLoss - hidden_layer_size: 256 - dropout_rate: 0.25 - - optim_param_groups: - encoder_model: - lr: 1e-5 - task_head: - lr: 5e-4 - - data: - task_name: tap # options: aav, bind, conservation, gb1, meltome, sav, scl, secondary_structure - task_type: classification #'token-level-classification' # alternative: classification, regression - preprocessed_data_path: /tmp/data # path where all preprocessed FLIP datasets are saved - dataset_path: ${model.data.preprocessed_data_path}/sabdab_chen # path to a training data - dataset: - train: ??? # train data will be set in `custom/downstream_flip.py`, e.g. to "sabdab_chen_site-1_train" - val: sabdab_chen_valid - test: sabdab_chen_test - sequence_column: "Antibody" # name of column with protein sequence in csv file - target_column: ["Y"] #["3state"np.sum(test_df['Y']==0), "resolved"] # names of label columns in csv file - target_sizes: [2] # number of classes in each label for classifications or 1 for regression - num_classes: 2 - num_workers: 2 - shuffle: True # shuffle training dataset - max_seq_length: ${model.seq_length} - emb_batch_size: ${model.micro_batch_size} - - finetuning_optim: # optimizer parameters for downstream task model - name: adam - #lr: 0.0005 - betas: - - 0.9 - - 0.999 - eps: 1e-8 - weight_decay: 0.001 - #sched: - # name: WarmupAnnealing - # min_lr: 0.00001 #0.00001 - # last_epoch: -1 - # warmup_steps: 10 diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/infer.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/infer.yaml deleted file mode 100644 index 3368831d85..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/infer.yaml +++ /dev/null @@ -1,25 +0,0 @@ -defaults: - - base_infer_config - # allow this config to override defaults - - _self_ - -hydra: - searchpath: - - /workspace/bionemo/examples/conf/ - -name: ESM1nv_Inference -desc: Minimum configuration for initializing a ESM1nv model for inference. - -model: - post_process: False - tokenizer: - vocab_path: /tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.vocab - model_path: /tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.model - downstream_task: - restore_from_path: "/model/protein/esm1nv/esm1nv.nemo" - outputs: [embeddings, hiddens] # Which outputs to extract per sample (a value or list). Possible values: hiddens, embeddings. - data: - dataset_path: /data/FLIP/secondary_structure/test/x000 # full path to dataset (can include range or a list) - -target: bionemo.model.protein.esm1nv.esm1nv_model.ESM1nvModel # path to model class to load -infer_target: bionemo.model.protein.esm1nv.infer.ESM1nvInference # path to inferende class to load \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/pretrain_small.yaml b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/pretrain_small.yaml deleted file mode 100644 index 4599d92bcc..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/app/custom/pretrain_small.yaml +++ /dev/null @@ -1,33 +0,0 @@ -defaults: - - base_config -restore_from_path: null # used when starting from a .nemo file - -model: - tokenizer: - library: 'sentencepiece' - type: null - model: ${oc.env:BIONEMO_HOME}/tokenizers/protein/esm1nv/vocab/protein_sequence_sentencepiece.model - vocab_file: ${oc.env:BIONEMO_HOME}/tokenizers/vocab/protein_sequence_sentencepiece.vocab - data: - dataset_path: ${oc.env:BIONEMO_HOME}/data/uniref2022_05 # parent directory for data, contains train / val / test folders. Needs to be writeable for index creation. - dataset: # inclusive range of data files to load x[000..049] or can a single file, e.g. x000 - train: x[000..049] - test: x[000..049] - val: x[000..049] - micro_batch_size: ${model.micro_batch_size} - num_workers: 2 - - # Supported kwargs (with default values): - # text_mmap (newline_int=10, header_lines=0, workers=None, sort_dataset_paths=True) - # csv_mmap (newline_int=10, header_lines=0,workers=None, sort_dataset_paths=True, data_col=1, data_sep=",") - data_impl_kwargs: - csv_mmap: - data_col: 3 # 0-based - - # These control the MLM token probabilities. The following settings are commonly used in literature. - modify_percent: 0.15 # Fraction of characters in a protein sequence to modify. (Modification means replacing with another amino acid or with a mask token) - perturb_percent: 0.1 # Of the modify_percent, what fraction of characters are to be replaced with another amino acid. - mask_percent: 0.8 # Of the modify_percent, what fraction of characters are to be replaced with a mask token. - identity_percent: 0.1 # Of the modify_percent, what fraction of characters are to be unchanged as the original amino acid. - dwnst_task_validation: - enabled: True \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/meta.conf b/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/meta.conf deleted file mode 100644 index a21a6ec425..0000000000 --- a/examples/advanced/bionemo/downstream/sabdab/jobs/local_sabdab_esm1nv/meta.conf +++ /dev/null @@ -1,10 +0,0 @@ -{ - name = "bionemo_local_finetune_esm1nv" - resource_spec = {} - deploy_map { - # change deploy map as needed. - app: ["@ALL"] - } - min_clients = 1 - mandatory_clients = [] -} diff --git a/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py b/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py index 8b8c73bb03..1c76420c3c 100644 --- a/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py +++ b/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py @@ -30,7 +30,7 @@ def clean_chains(df): - a = df["Antibody"] + a = df["sequences"] b = [] for chains in a: # split chains @@ -41,16 +41,16 @@ def clean_chains(df): assert "\\n" not in chains assert "'" not in chains b.append(chains) - df["Antibody"] = b + df["sequences"] = b return df def break_chains(df): - out_df = {"Antibody": []} + out_df = {"sequences": []} for idx, row in df.iterrows(): # split chains - chains = row["Antibody"] + chains = row["sequences"] chains = chains.replace("['", "").replace("']", "").split("'\\n '") assert "'" not in chains assert "[" not in chains @@ -59,9 +59,9 @@ def break_chains(df): assert "'" not in chains for chain in chains: - out_df["Antibody"].append(chain) + out_df["sequences"].append(chain) for k in row.keys(): - if k == "Antibody": + if k == "sequences": continue if k not in out_df: out_df[k] = [row[k]] @@ -77,6 +77,12 @@ def main(): data = Develop(name="SAbDab_Chen", path="/tmp/data") split = data.get_split() + # rename columns to fit BioNeMo convention of "sequences" and "labels" + for s in ["train", "valid", "test"]: + split[s] = split[s].rename(columns={"Antibody": "sequences"}) + split[s] = split[s].rename(columns={"Y": "labels"}) + split[s]["labels"] = split[s]["labels"].map({0: "neg", 1: "pos"}) + train_df = pd.concat([split["train"], split["valid"]]) test_df = split["test"] @@ -112,7 +118,7 @@ def main(): if do_clean_chains: train_df = clean_chains(train_df) test_df = clean_chains(test_df) - + _split_dir = os.path.join(split_dir, "train") if not os.path.isdir(_split_dir): os.makedirs(_split_dir) @@ -129,8 +135,8 @@ def main(): print(f"Saved {len(train_df)} training and {len(test_df)} testing proteins.") for _set, _df in zip(["TRAIN", "TEST"], [train_df, test_df]): - n_pos = np.sum(_df["Y"] == 0) - n_neg = np.sum(_df["Y"] == 1) + n_pos = np.sum(_df["labels"] == "pos") + n_neg = np.sum(_df["labels"] == "neg") n = len(_df) print(f" {_set} Pos/Neg ratio: neg={n_neg}, pos={n_pos}: {n_pos / n_neg:0.3f}") print(f" {_set} Trivial accuracy: {n_pos / n:0.3f}") diff --git a/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py b/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py index 1c48035c36..37610bbc83 100644 --- a/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py +++ b/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,16 +12,94 @@ # See the License for the specific language governing permissions and # limitations under the License. -from nvflare import SimulatorRunner +import argparse +import logging -# Choose from one of the available jobs -job_name = "central_sabdab_esm1nv" -n_clients = 1 -# job_name = "local_sabdab_esm1nv"; n_clients = 6 -# job_name = "fedavg_sabdab_esm1nv"; n_clients = 6 +from nvflare import FedJob, FilterType +from bionemo.core.data.load import load +from nvflare import FilterType +from nvflare.app_common.workflows.fedavg import FedAvg +from nvflare.app_opt.pt.job_config.base_fed_job import BaseFedJob +from nvflare.job_config.script_runner import ScriptRunner, BaseScriptRunner +from nvflare.app_common.launchers.subprocess_launcher import SubprocessLauncher -simulator = SimulatorRunner( - job_folder=f"jobs/{job_name}", workspace=f"/tmp/nvflare/results/{job_name}", n_clients=n_clients, threads=n_clients -) -run_status = simulator.run() -print("Simulator finished with run_status", run_status) +import os +import pandas as pd +import sys +sys.path.append(os.path.join(os.getcwd(), "..")) # include parent folder in path +from bionemo_filters import BioNeMoParamsFilter + + +def main(args): + # Create BaseFedJob with initial model + job = BaseFedJob( + name=f"{args.exp_name}_sabdab_esm2_{args.model}" + ) + + # Define the controller and send to server + controller = FedAvg( + num_clients=args.num_clients, + num_rounds=args.num_rounds, + ) + job.to_server(controller) + + checkpoint_path = load(f"esm2/{args.model}:2.0") + print(f"Downloaded {args.model} to {checkpoint_path}") + + # Define unique strings describing the classes for classification so we can use the same label vocabulary on each client. + classes = "pos,neg" + + # Add clients + for i in range(args.num_clients): + client_name = f"site-{i+1}" + + # define data paths + # We use the same validation set for each client to make their metrics comparable + val_data_path = "/tmp/data/sabdab_chen/val/sabdab_chen_valid.csv" + if "central" in args.exp_name: + print("Simulating central training...") + assert args.num_clients == 1, "Use num_clients=1 for simulating 'central' training setting." + assert args.num_rounds == 1, "Use num_rounds=1 for simulating 'central' training setting." + train_data_path = "/tmp/data/sabdab_chen/train/sabdab_chen_full_train.csv" + val_check_interval = int(args.local_steps/20) # 20 times per training + else: # local or fedavg setting + train_data_path = f"/tmp/data/sabdab_chen/train/sabdab_chen_{client_name}_train.csv" + if args.num_rounds > 1: + val_check_interval = args.local_steps + else: + val_check_interval = int(args.local_steps/20) # 20 times per training + + # define training script arguments + #precision = "bf16-mixed" + precision = "fp32" + script_args = f"--restore-from-checkpoint-path {checkpoint_path} --train-data-path {train_data_path} --valid-data-path {val_data_path} --config-class ESM2FineTuneSeqConfig --dataset-class InMemorySingleValueDataset --task-type classification --mlp-ft-dropout 0.1 --mlp-hidden-size 256 --mlp-target-size 2 --experiment-name {job.name} --num-steps {args.local_steps} --num-gpus 1 --val-check-interval {val_check_interval} --log-every-n-steps 10 --lr 5e-4 --lr-multiplier 1e3 --scale-lr-layer classification_head --result-dir bionemo --micro-batch-size 64 --precision {precision} --save-top-k 1 --limit-val-batches 1.0 --classes {classes}" + print(f"Running {args.train_script} with args: {script_args}") + + # Define training script runner + runner = BaseScriptRunner(script=args.train_script, + launch_external_process=True, + framework="pytorch", + params_exchange_format="pytorch", + launcher=SubprocessLauncher(script=f"python3 custom/{args.train_script} {script_args}", + launch_once=False)) + job.to(runner, client_name) + job.to(BioNeMoParamsFilter(precision), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_DATA) + + job.export_job("./exported_jobs") + job.simulator_run(f"/tmp/nvflare/bionemo/sabdab/{job.name}", gpu=args.sim_gpus) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--num_clients", type=int, help="Number of clients", required=False, default=1) + parser.add_argument("--num_rounds", type=int, help="Number of rounds", required=False, default=30) + parser.add_argument("--local_steps", type=int, help="Number of rounds", required=False, default=10) + parser.add_argument("--train_script", type=str, help="Training script", required=False, default="../finetune_esm2.py") + parser.add_argument("--exp_name", type=str, help="Job name prefix", required=False, default="fedavg") + parser.add_argument("--model", choices=["8m", "650m", "3b"], help="ESM2 model", required=False, default="8m") + parser.add_argument("--sim_gpus", type=str, help="GPU indexes to simulate clients, e.g., '0,1,2,3' if you want to run 4 clients, each on a separate GPU. By default run all clients on the same GPU 0.", required=False, default="0") + + args = parser.parse_args() + + main(args) + \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/scl/figs/tb_curve_scl.png b/examples/advanced/bionemo/downstream/scl/figs/tb_curve_scl.png new file mode 100644 index 0000000000000000000000000000000000000000..1aabc0e320186241ccb8591a585d4401399509ca GIT binary patch literal 83773 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

lyMSlX*Pp-xnnpdS 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 YCH*FvGOtW0(SRRA-4i-BIOmA}0iG(9+5i9m literal 0 HcmV?d00001 diff --git a/examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/config/config_fed_client.conf b/examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/config/config_fed_client.conf deleted file mode 100644 index 95a31d24c0..0000000000 --- a/examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/config/config_fed_client.conf +++ /dev/null @@ -1,94 +0,0 @@ -{ - # version of the configuration - format_version = 2 - - # This is the application script which will be invoked. Client can replace this script with user's own training script. - app_script = "downstream_flip.py" - - # Additional arguments needed by the training code. For example, in lightning, these can be --trainer.batch_size=xxx. - app_config = "" - - # Additional arguments needed by DDP. - #ddp_config = "--nnodes=1 --nproc_per_node=1 --master_port=7777" - - # Client Computing Executors. - executors = [ - { - # tasks the executors are defined to handle - tasks = ["train"] - - # This particular executor - executor { - - # This is an executor for pytorch + Client API. The underline data exchange is using Pipe. - path = "nvflare.app_opt.pt.client_api_launcher_executor.PTClientAPILauncherExecutor" - - args { - # launcher_id is used to locate the Launcher object in "components" - launcher_id = "launcher" - - # pipe_id is used to locate the Pipe object in "components" - pipe_id = "pipe" - - # Timeout in seconds for waiting for a heartbeat from the training script. Defaults to 30 seconds. - # Please refer to the class docstring for all available arguments - heartbeat_timeout = 60 - - # format of the exchange parameters - params_exchange_format = "pytorch" - - # if the transfer_type is FULL, then it will be sent directly - # if the transfer_type is DIFF, then we will calculate the - # difference VS received parameters and send the difference - params_transfer_type = "FULL" - - # if train_with_evaluation is true, the executor will expect - # the custom code need to send back both the trained parameters and the evaluation metric - # otherwise only trained parameters are expected - train_with_evaluation = false - } - } - } - ], - - # this defined an array of task data filters. If provided, it will control the data from server controller to client executor - task_data_filters = [] - - # this defined an array of task result filters. If provided, it will control the result from client executor to server controller - task_result_filters = [] - - components = [ - { - # component id is "launcher" - id = "launcher" - - # the class path of this component - path = "nvflare.app_common.launchers.subprocess_launcher.SubprocessLauncher" - - args { - # the launcher will invoke the script - #script = "python3 -m torch.distributed.run {ddp_config} custom/{app_script} {app_config} " - script = "python3 custom/{app_script} {app_config} " - # if launch_once is true, the SubprocessLauncher will launch once for the whole job - # if launch_once is false, the SubprocessLauncher will launch a process for each task it receives from server - launch_once = true - } - } - { - id = "pipe" - - path = "nvflare.fuel.utils.pipe.file_pipe.FilePipe" - - args { - # Mode of the endpoint. A pipe has two endpoints. - # An endpoint can be either the one that initiates communication or the one listening. - # PASSIVE is the one listening. - mode = "PASSIVE" - - # root_path: is the directory location of the parameters exchange. - # You can also set it to an absolute path in your system. - root_path = "{WORKSPACE}/{JOB_ID}/{SITE_NAME}" - } - } - ] -} diff --git a/examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/custom/base_config.yaml b/examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/custom/base_config.yaml deleted file mode 100644 index ae3fd5c67e..0000000000 --- a/examples/advanced/bionemo/downstream/scl/jobs/fedavg_scl_finetune_esm2nv/app1/custom/base_config.yaml +++ /dev/null @@ -1,266 +0,0 @@ -defaults: - - _self_ - -name: esm2nv -do_preprocessing: False # set to true if data preprocessing is needed -do_training: True # set to false if data preprocessing steps must be completed -do_testing: False # set to true to run evaluation on test data after training, requires test_dataset section -restore_from_path: null # used when starting from a .nemo file - -trainer: - devices: 1 # number of GPUs or CPUs - num_nodes: 1 - accelerator: gpu #gpu or cpu - precision: 32 # 16-mixed, bf16-mixed or 32 - logger: False # logger is provided by NeMo exp_manager - enable_checkpointing: False # checkpointing is done by NeMo exp_manager - use_distributed_sampler: False # use NeMo Megatron samplers - max_epochs: null # # use max_steps instead with NeMo Megatron model - log_every_n_steps: 10 # number of interations between logging - val_check_interval: 1500 - limit_val_batches: 1.0 # Number of batches in the validation step. Use 0 for no batches, 1 for full dataset, and 0 1 it will take that number as the number of batches. - limit_test_batches: 1.0 # Number of batches in the test set. Use 0 for no batches, or 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 literal 14050 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 From 0c04e94d10484499f13c90cf35fa806fbeb69c23 Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Fri, 21 Feb 2025 15:58:31 -0500 Subject: [PATCH 2/8] formatting --- .../bionemo/downstream/bionemo_filters.py | 31 +++---- .../bionemo/downstream/finetune_esm2.py | 73 ++++++++--------- .../downstream/sabdab/prepare_sabdab_data.py | 2 +- .../downstream/sabdab/run_sim_sabdab.py | 70 ++++++++-------- .../bionemo/downstream/scl/run_sim_scl.py | 68 +++++++++------- .../downstream/tap/prepare_tap_data.py | 2 +- .../bionemo/downstream/tap/run_sim_tap.py | 81 +++++++++++-------- .../src/bionemo_inference_processor.py | 7 +- .../task_fitting/src/bionemo_launcher.py | 38 +++++---- .../task_fitting/src/bionemo_mlp_job.py | 11 +-- .../task_fitting/src/bionemo_mlp_learner.py | 7 +- .../src/bionemo_mlp_model_persistor.py | 2 +- 12 files changed, 193 insertions(+), 199 deletions(-) diff --git a/examples/advanced/bionemo/downstream/bionemo_filters.py b/examples/advanced/bionemo/downstream/bionemo_filters.py index d207c1f3b0..92f4ff71e9 100644 --- a/examples/advanced/bionemo/downstream/bionemo_filters.py +++ b/examples/advanced/bionemo/downstream/bionemo_filters.py @@ -13,19 +13,15 @@ # limitations under the License. from typing import Union -from torch import Tensor -from nvflare.apis.dxo import DXO, DataKind, MetaKey +from nvflare.apis.dxo import DXO, DataKind from nvflare.apis.dxo_filter import DXOFilter from nvflare.apis.fl_context import FLContext from nvflare.apis.shareable import Shareable class BioNeMoParamsFilter(DXOFilter): - def __init__( - self, - precision="bf16-mixed" - ): + def __init__(self, precision="bf16-mixed"): """Filter to add a prefix to global state dict to avoid key mismatches between global and local state dictionaries. This is needed because of NeMo training framework adding module wrappers depending on the used training precision. @@ -60,18 +56,15 @@ def process_dxo(self, dxo: DXO, shareable: Shareable, fl_ctx: FLContext) -> Unio params = dxo.data new_params = {} for k, v in params.items(): - new_key = self._prefix + k - new_params[new_key] = v - + new_key = self._prefix + k + new_params[new_key] = v + dxo.data = new_params return dxo class BioNeMoExcludeParamsFilter(DXOFilter): - def __init__( - self, - exclude_vars="head" - ): + def __init__(self, exclude_vars="head"): """Filter to remove parameters from state dictionary that shouldn't be shared with other party. Args: @@ -84,7 +77,6 @@ def __init__( self.exclude_vars = exclude_vars - def process_dxo(self, dxo: DXO, shareable: Shareable, fl_ctx: FLContext) -> Union[None, DXO]: """Filter process apply to the Shareable object. @@ -100,14 +92,13 @@ def process_dxo(self, dxo: DXO, shareable: Shareable, fl_ctx: FLContext) -> Unio params = dxo.data new_params = {} for k, v in params.items(): - if self.exclude_vars not in k: - new_params[k] = v - + if self.exclude_vars not in k: + new_params[k] = v + if len(new_params) < len(params): self.log_info(fl_ctx, f"Excluded {len(params)-len(new_params)} parameters matching '{self.exclude_vars}'") else: - raise ValueError(f"State dictionary did not match any exclude keys that matched '{self.exclude_vars}'") - + raise ValueError(f"State dictionary did not match any exclude keys that matched '{self.exclude_vars}'") + dxo.data = new_params return dxo - diff --git a/examples/advanced/bionemo/downstream/finetune_esm2.py b/examples/advanced/bionemo/downstream/finetune_esm2.py index a9556f569e..7ca0acea98 100644 --- a/examples/advanced/bionemo/downstream/finetune_esm2.py +++ b/examples/advanced/bionemo/downstream/finetune_esm2.py @@ -15,39 +15,29 @@ # Copied and adapted for NVFlare from https://github.com/NVIDIA/bionemo-framework/blob/main/sub-packages/bionemo-esm2/src/bionemo/esm2/scripts/finetune_esm2.py import shutil -import argparse -import random from pathlib import Path -from lightning import seed_everything -from typing import Dict, List, Optional, Sequence, Tuple, Type, get_args - -from lightning.pytorch.callbacks import Callback, LearningRateMonitor, RichModelSummary -from megatron.core.distributed import DistributedDataParallelConfig -from megatron.core.optimizer import OptimizerConfig -from nemo import lightning as nl -from nemo.collections import llm -from nemo.lightning import resume -from nemo.lightning.pytorch import callbacks as nl_callbacks -from nemo.lightning.pytorch.optim import MegatronOptimizerModule +from typing import List, Optional, Tuple, Type from bionemo.core.utils.dtypes import PrecisionTypes, get_autocast_dtype from bionemo.esm2.data.tokenizer import get_tokenizer from bionemo.esm2.model.finetune.datamodule import ESM2FineTuneDataModule -from bionemo.esm2.model.finetune.dataset import ( - InMemoryPerTokenValueDataset, - InMemoryProteinDataset, - InMemorySingleValueDataset, -) +from bionemo.esm2.model.finetune.dataset import InMemoryProteinDataset, InMemorySingleValueDataset from bionemo.esm2.model.finetune.sequence_model import ESM2FineTuneSeqConfig -from bionemo.esm2.model.finetune.token_model import ESM2FineTuneTokenConfig + +# Resue parser and config constants from bionemo +from bionemo.esm2.scripts.finetune_esm2 import get_parser from bionemo.llm.model.biobert.lightning import biobert_lightning_module from bionemo.llm.model.biobert.model import BioBertConfig from bionemo.llm.model.config import TorchmetricsConfig -from bionemo.llm.utils.datamodule_utils import float_or_int_or_none, infer_global_batch_size +from bionemo.llm.utils.datamodule_utils import infer_global_batch_size from bionemo.llm.utils.logger_utils import WandbConfig, setup_nemo_lightning_logger - -# Resue parser and config constants from bionemo -from bionemo.esm2.scripts.finetune_esm2 import get_parser, SUPPORTED_CONFIGS, SUPPORTED_DATASETS +from lightning.pytorch.callbacks import Callback, LearningRateMonitor, RichModelSummary +from megatron.core.distributed import DistributedDataParallelConfig +from megatron.core.optimizer import OptimizerConfig +from nemo import lightning as nl +from nemo.collections import llm +from nemo.lightning.pytorch import callbacks as nl_callbacks +from nemo.lightning.pytorch.optim import MegatronOptimizerModule # (1) import nvflare lightning client API import nvflare.client.lightning as flare @@ -111,7 +101,7 @@ def train_model( average_in_collective: bool = True, grad_reduce_in_fp32: bool = False, label_column: str = "labels", - classes: List[str] = None + classes: List[str] = None, ) -> Tuple[Path, Callback | None, nl.Trainer]: """Train an ESM2 model on UR data. @@ -265,7 +255,9 @@ def train_model( # because after flare.patch the trainer.fit/validate will get the # global model internally input_model = flare.receive() - print(f"\n[Current Round={input_model.current_round}, Site = {flare.get_site_name()}, Global model = {input_model} ({len(input_model.params)} params)]\n") + print( + f"\n[Current Round={input_model.current_round}, Site = {flare.get_site_name()}, Global model = {input_model} ({len(input_model.params)} params)]\n" + ) # use a unique result directory for each round # Remove previous checkpoints to preserve disk space @@ -274,21 +266,21 @@ def train_model( previous_ckpt_dir = result_dir / f"round{input_model.current_round-1}" / experiment_name / "dev" / "checkpoints" if previous_ckpt_dir.is_dir(): print(f"Removing previous checkpoint directory {previous_ckpt_dir}") - shutil.rmtree(previous_ckpt_dir) + shutil.rmtree(previous_ckpt_dir) # create output folder for this round result_dir = result_dir / f"round{input_model.current_round}" - + # add a learning rate decay for each round if input_model.current_round > 0: lr_step_reduce = 1.05 # TODO: make lr_step_reduce configurable - new_lr = lr/(input_model.current_round*lr_step_reduce) - new_lr_multiplier = lr_multiplier/(input_model.current_round*lr_step_reduce) + new_lr = lr / (input_model.current_round * lr_step_reduce) + new_lr_multiplier = lr_multiplier / (input_model.current_round * lr_step_reduce) print(f"Reduce lr {lr} by {input_model.current_round*lr_step_reduce}: {new_lr}") else: new_lr = lr - new_lr_multiplier = lr_multiplier - + new_lr_multiplier = lr_multiplier + # remaining bionemo training code tokenizer = get_tokenizer() @@ -302,7 +294,7 @@ def train_model( train_dataset.label_tokenizer.build_vocab([classes]) print(f"Build custom label tokenizer based on label classes: {classes}") valid_dataset.label_tokenizer = train_dataset.label_tokenizer - + data_module = ESM2FineTuneDataModule( train_dataset=train_dataset, valid_dataset=valid_dataset, @@ -379,7 +371,7 @@ def train_model( module = biobert_lightning_module(config=config, tokenizer=tokenizer, optimizer=optimizer) - #If client should save best local checkpoints, set to `save_local_ckpt=True`, + # If client should save best local checkpoints, set to `save_local_ckpt=True`, save_local_ckpt = False if save_local_ckpt: # Configure our custom Checkpointer @@ -393,7 +385,7 @@ def train_model( ) else: checkpoint_callback = None - + # Setup the logger and train the model nemo_logger = setup_nemo_lightning_logger( root_dir=result_dir, @@ -402,7 +394,7 @@ def train_model( wandb_config=wandb_config, ckpt_callback=checkpoint_callback, ) - + # perform local training starting with the received global model llm.train( model=module, @@ -410,7 +402,7 @@ def train_model( trainer=trainer, log=nemo_logger, resume=None, - ) + ) if checkpoint_callback: ckpt_path = Path(checkpoint_callback.last_model_path.replace(".ckpt", "")) @@ -431,7 +423,7 @@ def finetune_esm2_entrypoint(): required=False, default=None, help="Unique strings describing the classes for classification. Used to build the same label vocabulary on each client. Should be comma separate list of strings, e.g. 'pos,neg'", - ) + ) args = parser.parse_args() if args.classes: @@ -440,7 +432,6 @@ def finetune_esm2_entrypoint(): classes = args.classes.split(",") else: classes = None - # to avoid padding for single value labels: if args.min_seq_length is not None and args.datset_class is InMemorySingleValueDataset: @@ -503,10 +494,10 @@ def finetune_esm2_entrypoint(): average_in_collective=not args.no_average_in_collective, grad_reduce_in_fp32=args.grad_reduce_in_fp32, label_column=args.label_column, - classes=classes + classes=classes, ) - + + if __name__ == "__main__": finetune_esm2_entrypoint() flare.shutdown() - diff --git a/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py b/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py index 1c76420c3c..ccfc822e2b 100644 --- a/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py +++ b/examples/advanced/bionemo/downstream/sabdab/prepare_sabdab_data.py @@ -118,7 +118,7 @@ def main(): if do_clean_chains: train_df = clean_chains(train_df) test_df = clean_chains(test_df) - + _split_dir = os.path.join(split_dir, "train") if not os.path.isdir(_split_dir): os.makedirs(_split_dir) diff --git a/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py b/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py index 37610bbc83..090775b1bf 100644 --- a/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py +++ b/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py @@ -13,28 +13,24 @@ # limitations under the License. import argparse -import logging +import os +import sys -from nvflare import FedJob, FilterType from bionemo.core.data.load import load + from nvflare import FilterType +from nvflare.app_common.launchers.subprocess_launcher import SubprocessLauncher from nvflare.app_common.workflows.fedavg import FedAvg from nvflare.app_opt.pt.job_config.base_fed_job import BaseFedJob -from nvflare.job_config.script_runner import ScriptRunner, BaseScriptRunner -from nvflare.app_common.launchers.subprocess_launcher import SubprocessLauncher +from nvflare.job_config.script_runner import BaseScriptRunner -import os -import pandas as pd -import sys -sys.path.append(os.path.join(os.getcwd(), "..")) # include parent folder in path +sys.path.append(os.path.join(os.getcwd(), "..")) # include parent folder in path from bionemo_filters import BioNeMoParamsFilter def main(args): # Create BaseFedJob with initial model - job = BaseFedJob( - name=f"{args.exp_name}_sabdab_esm2_{args.model}" - ) + job = BaseFedJob(name=f"{args.exp_name}_sabdab_esm2_{args.model}") # Define the controller and send to server controller = FedAvg( @@ -48,11 +44,11 @@ def main(args): # Define unique strings describing the classes for classification so we can use the same label vocabulary on each client. classes = "pos,neg" - + # Add clients for i in range(args.num_clients): client_name = f"site-{i+1}" - + # define data paths # We use the same validation set for each client to make their metrics comparable val_data_path = "/tmp/data/sabdab_chen/val/sabdab_chen_valid.csv" @@ -61,29 +57,32 @@ def main(args): assert args.num_clients == 1, "Use num_clients=1 for simulating 'central' training setting." assert args.num_rounds == 1, "Use num_rounds=1 for simulating 'central' training setting." train_data_path = "/tmp/data/sabdab_chen/train/sabdab_chen_full_train.csv" - val_check_interval = int(args.local_steps/20) # 20 times per training - else: # local or fedavg setting - train_data_path = f"/tmp/data/sabdab_chen/train/sabdab_chen_{client_name}_train.csv" + val_check_interval = int(args.local_steps / 20) # 20 times per training + else: # local or fedavg setting + train_data_path = f"/tmp/data/sabdab_chen/train/sabdab_chen_{client_name}_train.csv" if args.num_rounds > 1: val_check_interval = args.local_steps else: - val_check_interval = int(args.local_steps/20) # 20 times per training - + val_check_interval = int(args.local_steps / 20) # 20 times per training + # define training script arguments - #precision = "bf16-mixed" + # precision = "bf16-mixed" precision = "fp32" script_args = f"--restore-from-checkpoint-path {checkpoint_path} --train-data-path {train_data_path} --valid-data-path {val_data_path} --config-class ESM2FineTuneSeqConfig --dataset-class InMemorySingleValueDataset --task-type classification --mlp-ft-dropout 0.1 --mlp-hidden-size 256 --mlp-target-size 2 --experiment-name {job.name} --num-steps {args.local_steps} --num-gpus 1 --val-check-interval {val_check_interval} --log-every-n-steps 10 --lr 5e-4 --lr-multiplier 1e3 --scale-lr-layer classification_head --result-dir bionemo --micro-batch-size 64 --precision {precision} --save-top-k 1 --limit-val-batches 1.0 --classes {classes}" print(f"Running {args.train_script} with args: {script_args}") - + # Define training script runner - runner = BaseScriptRunner(script=args.train_script, - launch_external_process=True, - framework="pytorch", - params_exchange_format="pytorch", - launcher=SubprocessLauncher(script=f"python3 custom/{args.train_script} {script_args}", - launch_once=False)) + runner = BaseScriptRunner( + script=args.train_script, + launch_external_process=True, + framework="pytorch", + params_exchange_format="pytorch", + launcher=SubprocessLauncher(script=f"python3 custom/{args.train_script} {script_args}", launch_once=False), + ) job.to(runner, client_name) - job.to(BioNeMoParamsFilter(precision), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_DATA) + job.to( + BioNeMoParamsFilter(precision), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_DATA + ) job.export_job("./exported_jobs") job.simulator_run(f"/tmp/nvflare/bionemo/sabdab/{job.name}", gpu=args.sim_gpus) @@ -94,12 +93,19 @@ def main(args): parser.add_argument("--num_clients", type=int, help="Number of clients", required=False, default=1) parser.add_argument("--num_rounds", type=int, help="Number of rounds", required=False, default=30) parser.add_argument("--local_steps", type=int, help="Number of rounds", required=False, default=10) - parser.add_argument("--train_script", type=str, help="Training script", required=False, default="../finetune_esm2.py") + parser.add_argument( + "--train_script", type=str, help="Training script", required=False, default="../finetune_esm2.py" + ) parser.add_argument("--exp_name", type=str, help="Job name prefix", required=False, default="fedavg") parser.add_argument("--model", choices=["8m", "650m", "3b"], help="ESM2 model", required=False, default="8m") - parser.add_argument("--sim_gpus", type=str, help="GPU indexes to simulate clients, e.g., '0,1,2,3' if you want to run 4 clients, each on a separate GPU. By default run all clients on the same GPU 0.", required=False, default="0") + parser.add_argument( + "--sim_gpus", + type=str, + help="GPU indexes to simulate clients, e.g., '0,1,2,3' if you want to run 4 clients, each on a separate GPU. By default run all clients on the same GPU 0.", + required=False, + default="0", + ) + + args = parser.parse_args() - args = parser.parse_args() - main(args) - \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/scl/run_sim_scl.py b/examples/advanced/bionemo/downstream/scl/run_sim_scl.py index f140a364ef..8171fde75a 100644 --- a/examples/advanced/bionemo/downstream/scl/run_sim_scl.py +++ b/examples/advanced/bionemo/downstream/scl/run_sim_scl.py @@ -13,28 +13,24 @@ # limitations under the License. import argparse -import logging +import os +import sys -from nvflare import FedJob, FilterType from bionemo.core.data.load import load + from nvflare import FilterType +from nvflare.app_common.launchers.subprocess_launcher import SubprocessLauncher from nvflare.app_common.workflows.fedavg import FedAvg from nvflare.app_opt.pt.job_config.base_fed_job import BaseFedJob -from nvflare.job_config.script_runner import ScriptRunner, BaseScriptRunner -from nvflare.app_common.launchers.subprocess_launcher import SubprocessLauncher +from nvflare.job_config.script_runner import BaseScriptRunner -import os -import pandas as pd -import sys -sys.path.append(os.path.join(os.getcwd(), "..")) # include parent folder in path +sys.path.append(os.path.join(os.getcwd(), "..")) # include parent folder in path from bionemo_filters import BioNeMoParamsFilter def main(args): # Create BaseFedJob with initial model - job = BaseFedJob( - name=f"{args.exp_name}_scl_esm2_{args.model}" - ) + job = BaseFedJob(name=f"{args.exp_name}_scl_esm2_{args.model}") # Define the controller and send to server controller = FedAvg( @@ -48,36 +44,39 @@ def main(args): # Define unique strings describing the classes for classification so we can use the same label vocabulary on each client. classes = "Cell_membrane,Cytoplasm,Endoplasmic_reticulum,Extracellular,Golgi_apparatus,Lysosome,Mitochondrion,Nucleus,Peroxisome,Plastid" - + # Add clients for i in range(args.num_clients): client_name = f"site-{i+1}" - + # define data paths # We use the same validation set for each client to make their metrics comparable - train_data_path = f"/tmp/data/mixed_soft/train/data_train_{client_name}.csv" + train_data_path = f"/tmp/data/mixed_soft/train/data_train_{client_name}.csv" val_data_path = f"/tmp/data/mixed_soft/val/data_val_{client_name}.csv" - if args.num_rounds > 1: # assume FL and set validation only at the end of round + if args.num_rounds > 1: # assume FL and set validation only at the end of round val_check_interval = args.local_steps else: - val_check_interval = int(args.local_steps/20) # 20 times per training - + val_check_interval = int(args.local_steps / 20) # 20 times per training + # define training script arguments - #precision = "bf16-mixed" + # precision = "bf16-mixed" precision = "fp32" script_args = f"--restore-from-checkpoint-path {checkpoint_path} --train-data-path {train_data_path} --valid-data-path {val_data_path} --config-class ESM2FineTuneSeqConfig --dataset-class InMemorySingleValueDataset --task-type classification --mlp-ft-dropout 0.1 --mlp-hidden-size 256 --mlp-target-size 10 --experiment-name {job.name} --num-steps {args.local_steps} --num-gpus 1 --val-check-interval {val_check_interval} --log-every-n-steps 10 --lr 5e-4 --result-dir bionemo --micro-batch-size 64 --precision {precision} --save-top-k 1 --encoder-frozen --limit-val-batches 1.0 --classes {classes}" print(f"Running {args.train_script} with args: {script_args}") - + # Define training script runner - runner = BaseScriptRunner(script=args.train_script, - launch_external_process=True, - framework="pytorch", - params_exchange_format="pytorch", - launcher=SubprocessLauncher(script=f"python3 custom/{args.train_script} {script_args}", - launch_once=False)) + runner = BaseScriptRunner( + script=args.train_script, + launch_external_process=True, + framework="pytorch", + params_exchange_format="pytorch", + launcher=SubprocessLauncher(script=f"python3 custom/{args.train_script} {script_args}", launch_once=False), + ) job.to(runner, client_name) - job.to(BioNeMoParamsFilter(precision), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_DATA) + job.to( + BioNeMoParamsFilter(precision), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_DATA + ) job.export_job("./exported_jobs") job.simulator_run(f"/tmp/nvflare/bionemo/scl/{job.name}", gpu=args.sim_gpus) @@ -88,12 +87,19 @@ def main(args): parser.add_argument("--num_clients", type=int, help="Number of clients", required=False, default=1) parser.add_argument("--num_rounds", type=int, help="Number of rounds", required=False, default=30) parser.add_argument("--local_steps", type=int, help="Number of rounds", required=False, default=10) - parser.add_argument("--train_script", type=str, help="Training script", required=False, default="../finetune_esm2.py") + parser.add_argument( + "--train_script", type=str, help="Training script", required=False, default="../finetune_esm2.py" + ) parser.add_argument("--exp_name", type=str, help="Job name prefix", required=False, default="fedavg") parser.add_argument("--model", choices=["8m", "650m", "3b"], help="ESM2 model", required=False, default="8m") - parser.add_argument("--sim_gpus", type=str, help="GPU indexes to simulate clients, e.g., '0,1,2,3' if you want to run 4 clients, each on a separate GPU. By default run all clients on the same GPU 0.", required=False, default="0") + parser.add_argument( + "--sim_gpus", + type=str, + help="GPU indexes to simulate clients, e.g., '0,1,2,3' if you want to run 4 clients, each on a separate GPU. By default run all clients on the same GPU 0.", + required=False, + default="0", + ) + + args = parser.parse_args() - args = parser.parse_args() - main(args) - \ No newline at end of file diff --git a/examples/advanced/bionemo/downstream/tap/prepare_tap_data.py b/examples/advanced/bionemo/downstream/tap/prepare_tap_data.py index 785334233a..d5fe497513 100644 --- a/examples/advanced/bionemo/downstream/tap/prepare_tap_data.py +++ b/examples/advanced/bionemo/downstream/tap/prepare_tap_data.py @@ -84,7 +84,7 @@ def main(): # rename columns to fit BioNeMo convention of "sequences" and "labels" for s in ["train", "valid", "test"]: split[s] = split[s].rename(columns={"Antibody": "sequences"}) - + train_split = pd.concat([split["train"], split["valid"]]) if train_df is None: train_df = train_split diff --git a/examples/advanced/bionemo/downstream/tap/run_sim_tap.py b/examples/advanced/bionemo/downstream/tap/run_sim_tap.py index c04fd62bd6..baf65d3662 100644 --- a/examples/advanced/bionemo/downstream/tap/run_sim_tap.py +++ b/examples/advanced/bionemo/downstream/tap/run_sim_tap.py @@ -13,28 +13,24 @@ # limitations under the License. import argparse -import logging +import os +import sys -from nvflare import FedJob, FilterType from bionemo.core.data.load import load + from nvflare import FilterType +from nvflare.app_common.launchers.subprocess_launcher import SubprocessLauncher from nvflare.app_common.workflows.fedavg import FedAvg from nvflare.app_opt.pt.job_config.base_fed_job import BaseFedJob -from nvflare.job_config.script_runner import ScriptRunner, BaseScriptRunner -from nvflare.app_common.launchers.subprocess_launcher import SubprocessLauncher +from nvflare.job_config.script_runner import BaseScriptRunner -import os -import pandas as pd -import sys -sys.path.append(os.path.join(os.getcwd(), "..")) # include parent folder in path -from bionemo_filters import BioNeMoParamsFilter, BioNeMoExcludeParamsFilter +sys.path.append(os.path.join(os.getcwd(), "..")) # include parent folder in path +from bionemo_filters import BioNeMoExcludeParamsFilter, BioNeMoParamsFilter def main(args): # Create BaseFedJob with initial model - job = BaseFedJob( - name=f"{args.exp_name}_tap_esm2_{args.model}" - ) + job = BaseFedJob(name=f"{args.exp_name}_tap_esm2_{args.model}") # Define the controller and send to server controller = FedAvg( @@ -47,11 +43,11 @@ def main(args): print(f"Downloaded {args.model} to {checkpoint_path}") label_columns = ["PSH", "PPC", "PNC", "SFvCSP"] - + # Add clients - for i, label_column in zip(range(args.num_clients),label_columns): + for i, label_column in zip(range(args.num_clients), label_columns): client_name = f"site-{i+1}" - + # define data paths # We use the same validation set for each client to make their metrics comparable val_data_path = "/tmp/data/tap/val/tap_valid.csv" @@ -59,30 +55,38 @@ def main(args): print("Simulating central training...") assert args.num_rounds == 1, "Use num_rounds=1 for simulating 'central' training setting." train_data_path = "/tmp/data/tap/train/tap_full_train.csv" - val_check_interval = int(args.local_steps/20) # 20 times per training - else: # local or fedavg setting - train_data_path = f"/tmp/data/tap/train/tap_{client_name}_train.csv" + val_check_interval = int(args.local_steps / 20) # 20 times per training + else: # local or fedavg setting + train_data_path = f"/tmp/data/tap/train/tap_{client_name}_train.csv" if args.num_rounds > 1: val_check_interval = args.local_steps else: - val_check_interval = int(args.local_steps/20) # 20 times per training - + val_check_interval = int(args.local_steps / 20) # 20 times per training + # define training script arguments - #precision = "bf16-mixed" + # precision = "bf16-mixed" precision = "fp32" script_args = f"--restore-from-checkpoint-path {checkpoint_path} --train-data-path {train_data_path} --valid-data-path {val_data_path} --config-class ESM2FineTuneSeqConfig --dataset-class InMemorySingleValueDataset --task-type regression --mlp-ft-dropout 0.1 --mlp-hidden-size 256 --mlp-target-size 1 --experiment-name {job.name} --num-steps {args.local_steps} --num-gpus 1 --val-check-interval {val_check_interval} --log-every-n-steps 10 --lr 5e-4 --lr-multiplier 1e3 --scale-lr-layer regression_head --result-dir bionemo --micro-batch-size 8 --precision {precision} --save-top-k 1 --limit-val-batches 1.0 --label-column {label_column}" print(f"Running {args.train_script} with args: {script_args}") - + # Define training script runner - runner = BaseScriptRunner(script=args.train_script, - launch_external_process=True, - framework="pytorch", - params_exchange_format="pytorch", - launcher=SubprocessLauncher(script=f"python3 custom/{args.train_script} {script_args}", - launch_once=False)) + runner = BaseScriptRunner( + script=args.train_script, + launch_external_process=True, + framework="pytorch", + params_exchange_format="pytorch", + launcher=SubprocessLauncher(script=f"python3 custom/{args.train_script} {script_args}", launch_once=False), + ) job.to(runner, client_name) - job.to(BioNeMoParamsFilter(precision), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_DATA) - job.to(BioNeMoExcludeParamsFilter(exclude_vars="regression_head"), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_RESULT) # do not share the regression head with the server; each client will train their personal endpoint in this example + job.to( + BioNeMoParamsFilter(precision), client_name, tasks=["train", "validate"], filter_type=FilterType.TASK_DATA + ) + job.to( + BioNeMoExcludeParamsFilter(exclude_vars="regression_head"), + client_name, + tasks=["train", "validate"], + filter_type=FilterType.TASK_RESULT, + ) # do not share the regression head with the server; each client will train their personal endpoint in this example job.export_job("./exported_jobs") job.simulator_run(f"/tmp/nvflare/bionemo/tap/{job.name}", gpu=args.sim_gpus) @@ -93,12 +97,19 @@ def main(args): parser.add_argument("--num_clients", type=int, help="Number of clients", required=False, default=1) parser.add_argument("--num_rounds", type=int, help="Number of rounds", required=False, default=30) parser.add_argument("--local_steps", type=int, help="Number of rounds", required=False, default=10) - parser.add_argument("--train_script", type=str, help="Training script", required=False, default="../finetune_esm2.py") + parser.add_argument( + "--train_script", type=str, help="Training script", required=False, default="../finetune_esm2.py" + ) parser.add_argument("--exp_name", type=str, help="Job name prefix", required=False, default="fedavg") parser.add_argument("--model", choices=["8m", "650m", "3b"], help="ESM2 model", required=False, default="8m") - parser.add_argument("--sim_gpus", type=str, help="GPU indexes to simulate clients, e.g., '0,1,2,3' if you want to run 4 clients, each on a separate GPU. By default run all clients on the same GPU 0.", required=False, default="0") + parser.add_argument( + "--sim_gpus", + type=str, + help="GPU indexes to simulate clients, e.g., '0,1,2,3' if you want to run 4 clients, each on a separate GPU. By default run all clients on the same GPU 0.", + required=False, + default="0", + ) + + args = parser.parse_args() - args = parser.parse_args() - main(args) - \ No newline at end of file diff --git a/examples/advanced/bionemo/task_fitting/src/bionemo_inference_processor.py b/examples/advanced/bionemo/task_fitting/src/bionemo_inference_processor.py index 860252e559..e113b9cc0a 100644 --- a/examples/advanced/bionemo/task_fitting/src/bionemo_inference_processor.py +++ b/examples/advanced/bionemo/task_fitting/src/bionemo_inference_processor.py @@ -12,12 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import pprint -from bionemo_constants import BioNeMoConstants -from omegaconf import OmegaConf - from nvflare.apis.client import Client from nvflare.apis.dxo import DXO, from_shareable from nvflare.apis.fl_constant import FLContextKey, ReturnCode @@ -30,8 +26,7 @@ class BioNeMoInferenceProcessor(ResponseProcessor): def __init__( self, ): - """Creates task data, runs BioNeMo model inference, and summarizes results. - """ + """Creates task data, runs BioNeMo model inference, and summarizes results.""" super().__init__() self._inference_responses = {} diff --git a/examples/advanced/bionemo/task_fitting/src/bionemo_launcher.py b/examples/advanced/bionemo/task_fitting/src/bionemo_launcher.py index dbed7b6a47..14856ef2be 100644 --- a/examples/advanced/bionemo/task_fitting/src/bionemo_launcher.py +++ b/examples/advanced/bionemo/task_fitting/src/bionemo_launcher.py @@ -14,25 +14,19 @@ import os import time + import torch +from bionemo_constants import BioNeMoConstants -from nvflare.apis.dxo import MetaKey, DXO, from_shareable -from nvflare.apis.event_type import EventType +from nvflare.apis.dxo import DXO from nvflare.apis.executor import Executor from nvflare.apis.fl_constant import ReturnCode from nvflare.apis.fl_context import FLContext from nvflare.apis.shareable import Shareable, make_reply from nvflare.apis.signal import Signal -from nvflare.app_common.abstract.learner_spec import Learner -from nvflare.app_common.app_constant import AppConstants, ValidateType -from nvflare.security.logging import secure_format_exception -from nvflare.app_common.workflows.model_controller import ModelController from nvflare.app_common.abstract.launcher import Launcher, LauncherRunStatus -from nvflare.apis.fl_context import FLContext -from nvflare.apis.shareable import Shareable from nvflare.fuel.utils.validation_utils import check_object_type - -from bionemo_constants import BioNeMoConstants +from nvflare.security.logging import secure_format_exception class BioNeMoLauncher(Executor): @@ -40,7 +34,7 @@ def __init__( self, launcher_id: str = "launcher", task_name: str = BioNeMoConstants.TASK_INFERENCE, - check_interval: float = 10.0 + check_interval: float = 10.0, ): """Run a command on the client using a launcher. @@ -53,7 +47,7 @@ def __init__( self._launcher_id = launcher_id self._task_name = task_name self._check_interval = check_interval - self.is_initialized = False + self.is_initialized = False def _init_launcher(self, fl_ctx: FLContext): engine = fl_ctx.get_engine() @@ -66,16 +60,16 @@ def _init_launcher(self, fl_ctx: FLContext): def execute(self, task_name: str, shareable: Shareable, fl_ctx: FLContext, abort_signal: Signal) -> Shareable: try: - if task_name == self._task_name: + if task_name == self._task_name: if not self.is_initialized: self._init_launcher(fl_ctx) success = self._launch_script(fl_ctx) - + if success: # Get results path from inference script arguments args = self.launcher._script.split() - results_path = args[args.index("--results-path")+1] + results_path = args[args.index("--results-path") + 1] if os.path.isfile(results_path): self.log_info(fl_ctx, f"Get result info from: {results_path}") results = torch.load(results_path) @@ -83,22 +77,27 @@ def execute(self, task_name: str, shareable: Shareable, fl_ctx: FLContext, abort result_shapes = {} for k, v in results.items(): if v is not None: - result_shapes[k] = list(v.shape) # turn torch Size type into a simple list for sharing with server + result_shapes[k] = list( + v.shape + ) # turn torch Size type into a simple list for sharing with server n_sequences = len(results["embeddings"]) else: n_sequences, result_shapes = "n/a", "n/a" # Prepare a DXO for our updated model. Create shareable and return - data_info = {BioNeMoConstants.NUMBER_SEQUENCES: n_sequences, BioNeMoConstants.RESULT_SHAPES: result_shapes} - + data_info = { + BioNeMoConstants.NUMBER_SEQUENCES: n_sequences, + BioNeMoConstants.RESULT_SHAPES: result_shapes, + } + outgoing_dxo = DXO(data_kind=BioNeMoConstants.DATA_INFO, data=data_info) return outgoing_dxo.to_shareable() else: return make_reply(ReturnCode.EXECUTION_EXCEPTION) else: # If unknown task name, set RC accordingly. - return make_reply(ReturnCode.TASK_UNKNOWN) + return make_reply(ReturnCode.TASK_UNKNOWN) except Exception as e: self.log_exception(fl_ctx, f"Exception in execute: {secure_format_exception(e)}.") return make_reply(ReturnCode.EXECUTION_EXCEPTION) @@ -124,4 +123,3 @@ def _launch_script(self, fl_ctx: FLContext): self.launcher.finalize(fl_ctx=fl_ctx) self.log_info(fl_ctx, "Stop Executor Launcher.") return success - diff --git a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py index 65ab1aa74f..2322c0a66c 100644 --- a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py +++ b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py @@ -13,19 +13,16 @@ # limitations under the License. from typing import List, Optional -from torch import nn as nn from bionemo_mlp_model_persistor import BioNeMoMLPModelPersistor +from torch import nn as nn -from nvflare.app_opt.pt.file_model_locator import PTFileModelLocator -from nvflare.app_common.abstract.model_locator import ModelLocator -from nvflare.app_common.abstract.model_persistor import ModelPersistor from nvflare.app_common.tracking.tracker_types import ANALYTIC_EVENT_TYPE from nvflare.app_common.widgets.convert_to_fed_event import ConvertToFedEvent from nvflare.app_common.widgets.intime_model_selector import IntimeModelSelector from nvflare.app_common.widgets.streaming import AnalyticsReceiver from nvflare.app_common.widgets.validation_json_generator import ValidationJsonGenerator -from nvflare.app_opt.pt.job_config.model import PTModel +from nvflare.app_opt.pt.file_model_locator import PTFileModelLocator from nvflare.app_opt.tracking.tb.tb_receiver import TBAnalyticsReceiver from nvflare.job_config.api import FedJob, validate_object_for_job @@ -42,7 +39,7 @@ def __init__( intime_model_selector: Optional[IntimeModelSelector] = None, convert_to_fed_event: Optional[ConvertToFedEvent] = None, analytics_receiver: Optional[AnalyticsReceiver] = None, - embedding_dimensions: int = 320 # embedding dimensions of ESM2-8m + embedding_dimensions: int = 320, # embedding dimensions of ESM2-8m ): """PyTorch BaseFedJob. @@ -106,7 +103,7 @@ def __init__( ) self.to_server(id="persistor", obj=BioNeMoMLPModelPersistor(embedding_dimensions=embedding_dimensions)) - + self.to_server(id="locator", obj=PTFileModelLocator(pt_persistor_id="persistor")) def set_up_client(self, target: str): diff --git a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py index 31f66d81f8..9852060dcf 100644 --- a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py +++ b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py @@ -18,12 +18,11 @@ import pickle from distutils.util import strtobool from typing import Union -import torch - import numpy as np import pandas as pd import sklearn +import torch from sklearn.metrics import accuracy_score from sklearn.neural_network import MLPClassifier from torch.utils.tensorboard import SummaryWriter @@ -46,7 +45,7 @@ def __init__( analytic_sender_id: str = "analytic_sender", batch_size: int = 128, num_workers: int = 0, - embedding_dimensions: int = 320 # embedding dimensions of ESM2-8m + embedding_dimensions: int = 320, # embedding dimensions of ESM2-8m ): """BioNeMo MLP Trainer. @@ -121,7 +120,7 @@ def initialize(self): # Read embeddings results = torch.load(self.inference_result) - protein_embeddings = results['embeddings'] + protein_embeddings = results["embeddings"] self.info(f"Loaded {len(protein_embeddings)} embeddings") # Read labels diff --git a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_model_persistor.py b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_model_persistor.py index 577b120961..cf90eca7d8 100644 --- a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_model_persistor.py +++ b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_model_persistor.py @@ -41,7 +41,7 @@ def __init__( best_global_model_file_name=DefaultCheckpointFileName.BEST_GLOBAL_MODEL, source_ckpt_file_full_name=None, filter_id: str = None, - embedding_dimensions: int = 320 # embedding dimensions of ESM2-8m + embedding_dimensions: int = 320, # embedding dimensions of ESM2-8m ): """Persist sklearn-based model to/from file system. From f29a57a3c55ec032c99814107b20b3c63880bfde Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Fri, 21 Feb 2025 16:04:14 -0500 Subject: [PATCH 3/8] fix link --- examples/advanced/bionemo/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/advanced/bionemo/README.md b/examples/advanced/bionemo/README.md index 5aadf07f63..f713179b4c 100644 --- a/examples/advanced/bionemo/README.md +++ b/examples/advanced/bionemo/README.md @@ -16,8 +16,8 @@ In this repo you will find two notebooks under the `task_fitting` and `downstrea Download and run the [BioNeMo docker container](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/clara/containers/bionemo-framework). > **Note:** The examples here were tested with `nvcr.io/nvidia/clara/bionemo-framework:2.4` -We recommend following the [Quickstart Guide](https://docs.nvidia.com/bionemo-framework/latest/access-startup.html?highlight=docker) -on how to get the BioNeMo container. +We recommend following the [User Guide](https://docs.nvidia.com/bionemo-framework/latest/user-guide/) +on how to get started with BioNeMo 2. Start the container and Jupyter Lab to run the NVFlare experiments with NVFlare using ```commandline From 9846a0edf4da3fbffe664deb928d3002e9e80f3c Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Fri, 21 Feb 2025 17:06:21 -0500 Subject: [PATCH 4/8] uncomment --- examples/advanced/bionemo/downstream/downstream_nvflare.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb b/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb index 0a04bd65c5..045fc63d6a 100644 --- a/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb +++ b/examples/advanced/bionemo/downstream/downstream_nvflare.ipynb @@ -35,7 +35,7 @@ }, "outputs": [], "source": [ - "# %%capture --no-display --no-stderr cell_output\n", + "%%capture --no-display --no-stderr cell_output\n", "! pip install fuzzywuzzy PyTDC --no-dependencies # install tdc without dependencies to avoid version conflicts in the BioNeMo container\n", "! pip install nvflare~=2.6\n", "\n", From a5bf6c62e3f327374f597908c716a989ed0a6f7a Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Mon, 24 Feb 2025 11:24:41 -0500 Subject: [PATCH 5/8] label tokenizer is set in the finetuning class now --- examples/advanced/bionemo/downstream/finetune_esm2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/advanced/bionemo/downstream/finetune_esm2.py b/examples/advanced/bionemo/downstream/finetune_esm2.py index 7ca0acea98..6e9acd8c5f 100644 --- a/examples/advanced/bionemo/downstream/finetune_esm2.py +++ b/examples/advanced/bionemo/downstream/finetune_esm2.py @@ -293,7 +293,6 @@ def train_model( raise ValueError(f"classes is expected to be list of strings but received {type(classes)}: {classes}") train_dataset.label_tokenizer.build_vocab([classes]) print(f"Build custom label tokenizer based on label classes: {classes}") - valid_dataset.label_tokenizer = train_dataset.label_tokenizer data_module = ESM2FineTuneDataModule( train_dataset=train_dataset, From dc509a9e3cc35344c550cc783eddd773742f9b7f Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Mon, 24 Feb 2025 13:59:09 -0500 Subject: [PATCH 6/8] formatting --- examples/advanced/bionemo/downstream/bionemo_filters.py | 2 +- examples/advanced/bionemo/downstream/finetune_esm2.py | 4 ++-- examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py | 2 +- examples/advanced/bionemo/downstream/scl/run_sim_scl.py | 2 +- examples/advanced/bionemo/downstream/tap/run_sim_tap.py | 2 +- examples/advanced/bionemo/start_bionemo.sh | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/advanced/bionemo/downstream/bionemo_filters.py b/examples/advanced/bionemo/downstream/bionemo_filters.py index 92f4ff71e9..26bd99a028 100644 --- a/examples/advanced/bionemo/downstream/bionemo_filters.py +++ b/examples/advanced/bionemo/downstream/bionemo_filters.py @@ -96,7 +96,7 @@ def process_dxo(self, dxo: DXO, shareable: Shareable, fl_ctx: FLContext) -> Unio new_params[k] = v if len(new_params) < len(params): - self.log_info(fl_ctx, f"Excluded {len(params)-len(new_params)} parameters matching '{self.exclude_vars}'") + self.log_info(fl_ctx, f"Excluded {len(params) - len(new_params)} parameters matching '{self.exclude_vars}'") else: raise ValueError(f"State dictionary did not match any exclude keys that matched '{self.exclude_vars}'") diff --git a/examples/advanced/bionemo/downstream/finetune_esm2.py b/examples/advanced/bionemo/downstream/finetune_esm2.py index 6e9acd8c5f..03eea8fe14 100644 --- a/examples/advanced/bionemo/downstream/finetune_esm2.py +++ b/examples/advanced/bionemo/downstream/finetune_esm2.py @@ -263,7 +263,7 @@ def train_model( # Remove previous checkpoints to preserve disk space keep_last_ckpt_only = True # TODO: make configurable if keep_last_ckpt_only: - previous_ckpt_dir = result_dir / f"round{input_model.current_round-1}" / experiment_name / "dev" / "checkpoints" + previous_ckpt_dir = result_dir / f"round{input_model.current_round - 1}" / experiment_name / "dev" / "checkpoints" if previous_ckpt_dir.is_dir(): print(f"Removing previous checkpoint directory {previous_ckpt_dir}") shutil.rmtree(previous_ckpt_dir) @@ -276,7 +276,7 @@ def train_model( lr_step_reduce = 1.05 # TODO: make lr_step_reduce configurable new_lr = lr / (input_model.current_round * lr_step_reduce) new_lr_multiplier = lr_multiplier / (input_model.current_round * lr_step_reduce) - print(f"Reduce lr {lr} by {input_model.current_round*lr_step_reduce}: {new_lr}") + print(f"Reduce lr {lr} by {input_model.current_round * lr_step_reduce}: {new_lr}") else: new_lr = lr new_lr_multiplier = lr_multiplier diff --git a/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py b/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py index 090775b1bf..5eac4ce560 100644 --- a/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py +++ b/examples/advanced/bionemo/downstream/sabdab/run_sim_sabdab.py @@ -47,7 +47,7 @@ def main(args): # Add clients for i in range(args.num_clients): - client_name = f"site-{i+1}" + client_name = f"site-{i + 1}" # define data paths # We use the same validation set for each client to make their metrics comparable diff --git a/examples/advanced/bionemo/downstream/scl/run_sim_scl.py b/examples/advanced/bionemo/downstream/scl/run_sim_scl.py index 8171fde75a..035610a0d8 100644 --- a/examples/advanced/bionemo/downstream/scl/run_sim_scl.py +++ b/examples/advanced/bionemo/downstream/scl/run_sim_scl.py @@ -47,7 +47,7 @@ def main(args): # Add clients for i in range(args.num_clients): - client_name = f"site-{i+1}" + client_name = f"site-{i + 1}" # define data paths # We use the same validation set for each client to make their metrics comparable diff --git a/examples/advanced/bionemo/downstream/tap/run_sim_tap.py b/examples/advanced/bionemo/downstream/tap/run_sim_tap.py index baf65d3662..18ea7a749d 100644 --- a/examples/advanced/bionemo/downstream/tap/run_sim_tap.py +++ b/examples/advanced/bionemo/downstream/tap/run_sim_tap.py @@ -46,7 +46,7 @@ def main(args): # Add clients for i, label_column in zip(range(args.num_clients), label_columns): - client_name = f"site-{i+1}" + client_name = f"site-{i + 1}" # define data paths # We use the same validation set for each client to make their metrics comparable diff --git a/examples/advanced/bionemo/start_bionemo.sh b/examples/advanced/bionemo/start_bionemo.sh index 1773eb89bb..e14c628df4 100755 --- a/examples/advanced/bionemo/start_bionemo.sh +++ b/examples/advanced/bionemo/start_bionemo.sh @@ -4,7 +4,7 @@ DOCKER_IMAGE="nvcr.io/nvidia/clara/bionemo-framework:nightly" NB_DIR="/bionemo_nvflare_examples" -echo "Starting ${DOCKER_IMAGE} with GPU=${GPU}" +echo "Starting ${DOCKER_IMAGE} with all GPUs" echo "" echo "${COMMAND}" docker run \ From fb3ee7925f3ae2aa15e003e357d452d87bcf52c7 Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Mon, 24 Feb 2025 14:25:33 -0500 Subject: [PATCH 7/8] formatting again --- examples/advanced/bionemo/downstream/finetune_esm2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/advanced/bionemo/downstream/finetune_esm2.py b/examples/advanced/bionemo/downstream/finetune_esm2.py index 03eea8fe14..7535c95b36 100644 --- a/examples/advanced/bionemo/downstream/finetune_esm2.py +++ b/examples/advanced/bionemo/downstream/finetune_esm2.py @@ -263,7 +263,9 @@ def train_model( # Remove previous checkpoints to preserve disk space keep_last_ckpt_only = True # TODO: make configurable if keep_last_ckpt_only: - previous_ckpt_dir = result_dir / f"round{input_model.current_round - 1}" / experiment_name / "dev" / "checkpoints" + previous_ckpt_dir = ( + result_dir / f"round{input_model.current_round - 1}" / experiment_name / "dev" / "checkpoints" + ) if previous_ckpt_dir.is_dir(): print(f"Removing previous checkpoint directory {previous_ckpt_dir}") shutil.rmtree(previous_ckpt_dir) From 2822980a8284dcdd5d5624764c4f8c9516c91420 Mon Sep 17 00:00:00 2001 From: Holger Roth Date: Tue, 25 Feb 2025 16:14:49 -0500 Subject: [PATCH 8/8] address comments --- examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py | 2 +- .../advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py | 2 +- examples/advanced/bionemo/task_fitting/task_fitting.ipynb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py index 2322c0a66c..531af33845 100644 --- a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py +++ b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_job.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved. +# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py index 9852060dcf..d3517087a2 100644 --- a/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py +++ b/examples/advanced/bionemo/task_fitting/src/bionemo_mlp_learner.py @@ -34,7 +34,7 @@ from nvflare.app_common.utils.fl_model_utils import FLModelUtils -class BioNeMoMLPLearner(ModelLearner): # does not support CIFAR10ScaffoldLearner +class BioNeMoMLPLearner(ModelLearner): def __init__( self, data_path: str, diff --git a/examples/advanced/bionemo/task_fitting/task_fitting.ipynb b/examples/advanced/bionemo/task_fitting/task_fitting.ipynb index ec576bf7ba..4d6e2946bc 100644 --- a/examples/advanced/bionemo/task_fitting/task_fitting.ipynb +++ b/examples/advanced/bionemo/task_fitting/task_fitting.ipynb @@ -66,7 +66,7 @@ "\n", "Here we are interested in training a neural network to predict subcellular location from an embedding.\n", "\n", - "The data we will be using comes from the paper [Light attention predicts protein location from the language of life](https://academic.oup.com/bioinformaticsadvances/article/1/1/vbab035/6432029) by Stärk et al. In this paper, the authors developed a machine learning algorithm to predict the subcellular location of proteins from sequence through protein langage models that are similar to those hosted by BioNeMo. Protein subcellular location refers to where the protein localizes in the cell, for example a protein my be expressed in the Nucleus or in the Cytoplasm. Knowing where proteins localize can provide insights into the underlying mechanisms of cellular processes and help identify potential targets for drug development. The following image includes a few examples of subcellular locations in an animal cell:\n", + "The data we will be using comes from the paper [Light attention predicts protein location from the language of life](https://academic.oup.com/bioinformaticsadvances/article/1/1/vbab035/6432029) by Stärk et al. In this paper, the authors developed a machine learning algorithm to predict the subcellular location of proteins from sequence through protein langage models that are similar to those hosted by BioNeMo. Protein subcellular location refers to where the protein localizes in the cell, for example a protein may be expressed in the Nucleus or in the Cytoplasm. Knowing where proteins localize can provide insights into the underlying mechanisms of cellular processes and help identify potential targets for drug development. The following image includes a few examples of subcellular locations in an animal cell:\n", "\n", "\n", "(Image freely available at https://pixabay.com/images/id-48542)\n",