diff --git a/bionemo-recipes/recipes/evo2_megatron/examples/.gitignore b/bionemo-recipes/recipes/evo2_megatron/examples/.gitignore index 055a26eb49..132f4bd7a4 100644 --- a/bionemo-recipes/recipes/evo2_megatron/examples/.gitignore +++ b/bionemo-recipes/recipes/evo2_megatron/examples/.gitignore @@ -8,6 +8,7 @@ # directories created during these notebook runs. nemo2_evo2_1b_8k/ +evo2_1b_bf16_mbridge/ preprocessed_data/ pretraining_demo/ brca1_fasta_files/ diff --git a/bionemo-recipes/recipes/evo2_megatron/examples/fine-tuning-tutorial.ipynb b/bionemo-recipes/recipes/evo2_megatron/examples/fine-tuning-tutorial.ipynb index 4b65525ce0..b3f5a5cf72 100644 --- a/bionemo-recipes/recipes/evo2_megatron/examples/fine-tuning-tutorial.ipynb +++ b/bionemo-recipes/recipes/evo2_megatron/examples/fine-tuning-tutorial.ipynb @@ -39,7 +39,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "jupyter": { "source_hidden": true @@ -61,6 +61,7 @@ " !rm -rf preprocessed_data\n", " !rm -rf preatraining_demo\n", " !rm -rf pretraining_demo\n", + " !rm -rf evo2_1b_bf16_mbridge\n", " !rm -rf training_data_config.yaml\n", " !rm -rf preprocess_config.yaml\n", " !rm -f chr20.fa.gz\n", @@ -114,10 +115,40 @@ "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.12/dist-packages/torch/cuda/__init__.py:65: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.\n", + " import pynvml # type: ignore[import]\n", + "/workspaces/bionemo-framework/bionemo-recipes/recipes/evo2_megatron/.venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n", + "/usr/local/lib/python3.12/dist-packages/torch/library.py:356: UserWarning: Warning only once for all operators, other operators may also be overridden.\n", + " Overriding a previously registered kernel for the same operator and the same dispatch key\n", + " operator: flash_attn::_flash_attn_backward(Tensor dout, Tensor q, Tensor k, Tensor v, Tensor out, Tensor softmax_lse, Tensor(a6!)? dq, Tensor(a7!)? dk, Tensor(a8!)? dv, float dropout_p, float softmax_scale, bool causal, SymInt window_size_left, SymInt window_size_right, float softcap, Tensor? alibi_slopes, bool deterministic, Tensor? rng_state=None) -> Tensor\n", + " registered at /usr/local/lib/python3.12/dist-packages/torch/_library/custom_ops.py:922\n", + " dispatch key: ADInplaceOrView\n", + " previous kernel: no debug info\n", + " new kernel: registered at /usr/local/lib/python3.12/dist-packages/torch/_library/custom_ops.py:922 (Triggered internally at /opt/pytorch/pytorch/aten/src/ATen/core/dispatch/OperatorEntry.cpp:208.)\n", + " self.m.impl(\n", + "/usr/bin/ld: cannot find -laio: No such file or directory\n", + "collect2: error: ld returned 1 exit status\n", + "/usr/bin/ld: cannot find -laio: No such file or directory\n", + "collect2: error: ld returned 1 exit status\n" + ] + } + ], "source": [ + "from bionemo.evo2.data.dataset_tokenizer import (\n", + " DEFAULT_HF_TOKENIZER_MODEL_PATH, # use the 512 size for historical reasons\n", + ")\n", + "\n", + "\n", "full_fasta_path = os.path.abspath(concat_path)\n", "output_dir = os.path.abspath(\"preprocessed_data\")\n", + "\n", + "\n", "output_yaml = f\"\"\"\n", "- datapaths: [\"{full_fasta_path}\"]\n", " output_dir: \"{output_dir}\"\n", @@ -133,10 +164,7 @@ " transcribe: \"back_transcribe\"\n", " force_uppercase: false\n", " indexed_dataset_dtype: \"uint8\"\n", - " tokenizer_type: \"Byte-Level\"\n", - " vocab_file: null\n", - " vocab_size: null\n", - " merges_file: null\n", + " hf_tokenizer_model_path: {DEFAULT_HF_TOKENIZER_MODEL_PATH}\n", " pretrained_tokenizer_model: null\n", " special_tokens: null\n", " fast_hf_tokenizer: true\n", @@ -174,15 +202,15 @@ "output_type": "stream", "text": [ "total 309M\n", - "drwxr-xr-x 3 ubuntu ubuntu 4.0K Mar 10 22:17 chr20_21_22_uint8_distinct_byte-level_test\n", - "-rw-r--r-- 1 ubuntu ubuntu 90M Mar 10 23:07 chr20_21_22_uint8_distinct_byte-level_test.bin\n", - "-rw-r--r-- 1 ubuntu ubuntu 82 Mar 10 23:07 chr20_21_22_uint8_distinct_byte-level_test.idx\n", - "drwxr-xr-x 3 ubuntu ubuntu 4.0K Mar 10 22:17 chr20_21_22_uint8_distinct_byte-level_train\n", - "-rw-r--r-- 1 ubuntu ubuntu 123M Mar 10 23:06 chr20_21_22_uint8_distinct_byte-level_train.bin\n", - "-rw-r--r-- 1 ubuntu ubuntu 82 Mar 10 23:07 chr20_21_22_uint8_distinct_byte-level_train.idx\n", - "drwxr-xr-x 3 ubuntu ubuntu 4.0K Mar 10 22:17 chr20_21_22_uint8_distinct_byte-level_val\n", - "-rw-r--r-- 1 ubuntu ubuntu 97M Mar 10 23:06 chr20_21_22_uint8_distinct_byte-level_val.bin\n", - "-rw-r--r-- 1 ubuntu ubuntu 82 Mar 10 23:07 chr20_21_22_uint8_distinct_byte-level_val.idx\n" + "drwxr-xr-x 3 ubuntu ubuntu 4.0K Jan 14 18:48 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_test\n", + "-rw-r--r-- 1 ubuntu ubuntu 90M Jan 15 00:46 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_test.bin\n", + "-rw-r--r-- 1 ubuntu ubuntu 82 Jan 15 00:48 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_test.idx\n", + "drwxr-xr-x 3 ubuntu ubuntu 4.0K Jan 14 18:48 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_train\n", + "-rw-r--r-- 1 ubuntu ubuntu 123M Jan 15 00:48 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_train.bin\n", + "-rw-r--r-- 1 ubuntu ubuntu 82 Jan 15 00:48 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_train.idx\n", + "drwxr-xr-x 3 ubuntu ubuntu 4.0K Jan 14 18:48 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_val\n", + "-rw-r--r-- 1 ubuntu ubuntu 97M Jan 15 00:44 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_val.bin\n", + "-rw-r--r-- 1 ubuntu ubuntu 82 Jan 15 00:48 chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256_val.idx\n" ] } ], @@ -197,10 +225,14 @@ "source": [ "### [Optional] specify or convert initial checkpoint\n", "The main difference between pre-training and fine-tuning is whether or not you decide to start training the model with\n", - "weights from a prior training run. For this tutorial we want to tune a `1b` checkpoint from hugging face that is known\n", - "(at the time of this writing) to be sensitive to GPU architecture so that it will work with your architecture. We have a\n", - "script that will download and convert a savanna format evo2 checkpoint from hugging face, and output that into a NeMo2\n", - "format checkpoint directory that can be used as the starting point for a fine-tuning run." + "weights from a prior training run. For this tutorial we want to tune a `1b` checkpoint that is known\n", + "(at the time of this writing) to be sensitive to GPU architecture so that it will work with your architecture.\n", + "\n", + "We use `bionemo.core.data.load` to download pre-trained NeMo2 checkpoints from NGC. These checkpoints can be used\n", + "directly with the `train_evo2` command for fine-tuning.\n", + "\n", + "**Note**: The `train_evo2` command produces MBridge format checkpoints that can be used directly with\n", + "`infer_evo2` or `predict_evo2` for inference - no conversion step is needed." ] }, { @@ -209,11 +241,33 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture\n", - "if not os.path.exists(\"nemo2_evo2_1b_8k\"):\n", - " !evo2_convert_to_nemo2 \\\n", - " --model-path hf://arcinstitute/savanna_evo2_1b_base \\\n", - " --model-size 1b --output-dir nemo2_evo2_1b_8k" + "from pathlib import Path\n", + "\n", + "from bionemo.core.data.load import load\n", + "from bionemo.evo2.data.dataset_tokenizer import (\n", + " DEFAULT_HF_TOKENIZER_MODEL_PATH_512, # use the 512 size for historical reasons\n", + ")\n", + "\n", + "\n", + "# Download the 1b BF16 checkpoint from NGC\n", + "# Available checkpoints: evo2/1b-8k-bf16:1.0, evo2/1b-8k:1.0, evo2/7b-8k:1.0, evo2/7b-1m:1.0\n", + "mbridge_ckpt_path = Path(\"evo2_1b_bf16_mbridge\")\n", + "\n", + "if not mbridge_ckpt_path.exists():\n", + " nemo2_ckpt_path = load(\"evo2/1b-8k-bf16:1.0\")\n", + " mixed_precision_recipe = \"bf16_mixed\" # also try bf16_with_fp8_current_scaling_mixed\n", + " convert_ckpt_cmd = f\"\"\"evo2_convert_nemo2_to_mbridge \\\n", + " --nemo2-ckpt-dir {nemo2_ckpt_path} \\\n", + " --mbridge-ckpt-dir {mbridge_ckpt_path} \\\n", + " --model-size 1b \\\n", + " --mixed-precision-recipe {mixed_precision_recipe} \\\n", + " --seq-length 8192 \\\n", + " --tokenizer-path {DEFAULT_HF_TOKENIZER_MODEL_PATH_512} \\\n", + " \"\"\".rstrip()\n", + " print(f\"Running command: {convert_ckpt_cmd}\")\n", + "\n", + " result = run_subprocess_safely(convert_ckpt_cmd)\n", + " print(f\"Downloaded checkpoint to: {nemo2_ckpt_path} and converted to mbridge format at {mbridge_ckpt_path}\")" ] }, { @@ -227,14 +281,16 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "from pathlib import Path\n", "\n", "\n", - "output_pfx = str(Path(os.path.abspath(\"preprocessed_data\")) / \"chr20_21_22_uint8_distinct_byte-level\")\n", + "output_pfx = str(\n", + " Path(os.path.abspath(\"preprocessed_data\")) / \"chr20_21_22_uint8_distinct_nucleotide_fast_tokenizer_256\"\n", + ")\n", "output_yaml = f\"\"\"\n", "- dataset_prefix: {output_pfx}_train\n", " dataset_split: train\n", @@ -260,11 +316,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "%%capture\n", + "import torch\n", + "\n", + "\n", "MAX_STEPS: int = 10 if FAST_CI_MODE else 100\n", "val_check_interval = min(int(MAX_STEPS // 2), 50)\n", "warmup_steps = min(MAX_STEPS, 100)\n", @@ -278,30 +337,32 @@ "else:\n", " # By default do 5 layers of activation checkpointing\n", " model_subset_option = \"--activation-checkpoint-recompute-num-layers 5\"\n", - "train_cmd = f\"\"\"train_evo2 \\\n", + "num_gpus = torch.cuda.device_count()\n", + "# The 1b model is configured in a way that you can not use TP, but you can use CP.\n", + "train_cmd = f\"\"\"torchrun --nproc_per_node={num_gpus} --no-python train_evo2 \\\n", " -d training_data_config.yaml \\\n", " --dataset-dir ./preprocessed_data \\\n", " --result-dir pretraining_demo \\\n", " --experiment-name evo2 \\\n", + " --context-parallel-size {num_gpus} \\\n", " --model-size 1b \\\n", - " --devices 1 \\\n", - " --num-nodes 1 \\\n", " --seq-length 8192 \\\n", - " --micro-batch-size 2 \\\n", + " --micro-batch-size 1 \\\n", + " --global-batch-size 8 \\\n", + " --eval-iters 5 \\\n", + " --decay-steps 100000 --warmup-steps 10 \\\n", + " --hf-tokenizer-model-path {DEFAULT_HF_TOKENIZER_MODEL_PATH_512} \\\n", + " --eval-interval {val_check_interval} \\\n", " --lr 0.000015 \\\n", " --min-lr 0.0000149 \\\n", " --warmup-steps {warmup_steps} \\\n", - " --grad-acc-batches 4 \\\n", " --max-steps {MAX_STEPS} \\\n", - " --ckpt-dir nemo2_evo2_1b_8k \\\n", + " --finetune-ckpt-dir {mbridge_ckpt_path} \\\n", " --clip-grad 250 \\\n", " --wd 0.001 \\\n", " --attention-dropout 0.01 \\\n", " --hidden-dropout 0.01 \\\n", - " --val-check-interval {val_check_interval} \\\n", - " {model_subset_option} \\\n", - " --create-tensorboard-logger \\\n", - " --ckpt-async-save\"\"\"\n", + " {model_subset_option}\"\"\"\n", "\n", "print(f\"Running command: {train_cmd}\")\n", "\n", @@ -310,11 +371,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ - "assert result[\"returncode\"] == 0, result" + "if result[\"returncode\"] != 0:\n", + " print(\"================== STDOUT ==========================\")\n", + " print(result[\"stdout\"])\n", + " print(\"================== STDERR ==========================\")\n", + " print(result[\"stderr\"])\n", + " raise AssertionError(\"Training failed. See stdout and stderr above for more details.\")" ] }, { @@ -326,7 +392,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 17, "metadata": { "jupyter": { "source_hidden": true @@ -422,12 +488,404 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 18, "metadata": {}, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n", + "/tmp/ipykernel_3593112/2519380624.py:41: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`\n", + " df[tag] = df[\"step\"].map(step_to_value)\n" + ] + }, { "data": { - "image/png": "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AAAAAAAAAACMghAdAAAAAAAAAIBREKIDAAAAWeaiiy5SXV1dppcBAAAATAuE6AAAAAAAAAAAjIIQHQAAAAAAAACAURCiAwAAAAAAAAAwCkJ0AAAAYJIEg0Hdc889OvXUU7V48WKddtppuueee9TU1KS6ujrdcMMN8fuecsopOuWUU9TT06Obb75ZK1eu1KGHHqrHHntMkrRp0ybdfPPNOuuss3T00UdryZIlWrVqlX7zm99ocHBwxPP/61//0oUXXqilS5fquOOO07e//W21trbu12u64YYbVFdXp6amJj344IP61Kc+pcMPP1wnn3yyfvWrXykcDo/43+G+++7T2WefrSVLlujoo4/WRRddpPXr1w+772OPPaa6ujo99thjWr9+vS644AIdeeSROuWUU4ad/95779Xpp5+uJUuW6IwzztBf//pXSVIgENAvfvELnXLKKVq8eLFWrVqlF154Yb9eNwAAALJXTqYXAAAAAMxU3//+9/WXv/xF1dXV+vKXv6xAIKD7779fb7311oj3DwQCuuSSS+Tz+XTKKafIZrPJ7XZLkh555BE999xz+tjHPqaPf/zj6u/v12uvvaY77rhD9fX1uuuuu1Kea8OGDfrKV74ii8WiM844Q7NmzdKGDRv0xS9+UUVFRfv92n72s5/ptdde08knn6wVK1bo2Wef1V133aXBwUFde+218fsZhqFrrrlGzz77rObPn68vf/nL8vl8Wrt2rb7+9a/re9/7ni699NJhz//000/rpZde0kknnaQvfelL6uvrS/n8rbfeqnfeeUcnn3yyrFarnnrqKV1//fUqKirSww8/rI8++kgrV67UwMCA1qxZo2984xt66qmnVFNTs9+vHQAAANmFEB0AAACYBBs2bNBf/vIXLVq0SH/4wx/kcDgkSVdeeaXOPffcER/T1tamuro6/eEPf1B+fn7K56688krdeOONstls8dsMw9D//t//W48++qjeeOMNHX300ZKkcDisH/7whwoGg3r44Yd1zDHHxO//ne98R2vWrNnv1/fuu+/qiSee0KxZsyRJV111lU4//XQ99NBD+sY3viG73S5J+stf/qJnn31Wxx57rO6999747V/72td03nnn6Wc/+5k+8YlPqLq6OuX5X3zxRd17771avnz5iOffunWrnnjiCZWVlUmSzj//fH3uc5/Tddddp4MOOkhP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Dsb69etRVVWFBQsW9PTQiIiIiIiIiIiIiKiHMIh+1Zw5c1BYWIjPPvsM+fn5CAoKwtq1a9tczoWIiIiIiIiIiIiIeh+RtrXWo0REREREREREREREfZS4pwdARERERERERERERGSqGEQnIiIiIiIiIiIiImoGg+hERERERERERERERM1gEJ2IiIiIiIiIiIiIqBkMohMRERERERERERERNYNBdCIiIiIiIiIiIiKiZjCITkRERERERERERETUDAbRiYiIiIiIiIiIiIiawSA6EREREREREREREVEzGEQnIiIiIiIiIiIiImoGg+hERERERERERERERM1gEJ2IiIiIiIiIiIiIqBkMohMRERERERERERERNYNBdCIiIiIiIiIiIiKiZjCITkRERERERERERETUDAbRiYiIiIiIiIiIiIiawSA6EREREREREREREVEzGEQnIiIiIiIiIiIiImoGg+hERERERH3IXXfdhYCAgA6du2XLFgQEBGDLli1GHhURERERkeliEJ2IiIiIiIiIiIiIqBkMohMRERERERERERERNYNBdCIiIiIiIiIiIiKiZkh7egBERERERKR36tQp3HnnnViwYAFWrVrV6PaCggJMnjwZw4cPx8aNG3H27Fls2bIFUVFRyM7ORm1tLfr374+5c+di+fLlMDMz65Zxnz59GmvWrEFMTAyqqqrg6emJOXPm4IEHHoClpaXg2ISEBKxZswZxcXFQKBSwsbGBp6cnpk2bhkceeUR33OXLl7FmzRqcOHECeXl5sLKygru7O8aNG4f//Oc/EIlE3fLYiIiIiKhvYyY6EREREZEJGTVqFDw9PbFnzx7U1NQ0uv3vv/+GSqXCvHnzAACbNm3C3r17MXjwYCxevBi33XYbtFotPvroIzzzzDPdMuZdu3bhrrvuQlRUFKZOnYply5bB0tISX375JZYtWyZ4HImJibj99ttx+PBhjBo1CsuXL8fMmTNhaWmJTZs26Y7Lzc3FwoULsX37dgQFBeGee+7B3Llz4eLigg0bNkCtVnfLYyMiIiIiYiY6EREREZEJEYlEuPnmm/H1119j//79mDNnjuD2v/76C2ZmZpg9ezYA4OGHH8brr78OiUSiO0ar1eLll1/G5s2bcfr0aYwaNarLxlteXo5XX30VEokEGzduRGBgIADgmWeewcqVK7Fz506sXbsWjz32mG78SqUSX375JaZNmya4r6KiIt33e/bsQWlpKf7zn/9g2bJlguOKi4shlfKjDBERERF1D2aiExERERGZmPos823btgn2p6SkICEhAddddx0cHBwAAB4eHoIAOlAXiL/zzjsBAJGRkV061n379qGsrAy33nqrLoAOAGKxGM899xykUim2bt3a6DwLC4tG+xwdHdt0XP1jJyIiIiLqDgyiExERERGZmIEDByI4OBgREREoLCzU7a8PqtcH2QFAqVRi3bp1uO222zBy5EgEBgYiICAACxYsAADk5eV16VgTExMBAGPHjm10m4eHB7y8vJCeno7y8nIAwOzZsyEWi/H444/jpZdewt9//43c3NxG506ZMgVWVlZ488038dRTT2Hz5s1IT0/v0sdCRERERNQUroEkIiIiIjJB8+bNQ1xcHHbt2oU777wTWq0W27dvh729Pa6//nrdcU8++SQOHDiAAQMGYM6cOXB2doZUKkVpaSl+/PFHKJXKLh1nfXBcLpc3eburqysuX76MiooK2NjYYMSIEfjpp5/wzTff4O+//8aWLVsAAMOHD8ezzz6L8ePHAwC8vLzw22+/4YsvvsChQ4ewa9cuAMCgQYPw5JNP6srZEBERERF1NQbRiYiIiIhM0Jw5c/Duu+9i27ZtuPPOO3Hy5ElkZmZi8eLFkMlkAIC4uDgcOHAAYWFh+PbbbwVlXWJiYvDjjz92+ThtbGwAAAqFosnb8/PzAQDW1ta6faNHj8batWtRXV2N2NhYHDhwAL/++iseeugh/P333/D29gYADB48GJ999hlqa2uRkJCAw4cP46effsLTTz8NV1fXLq31TkRERERUj+VciIiIiIhMkJOTE8LCwhATE4MrV67oSrncfPPNumPqy5tcf/31jeqinzp1qlvGGRQUBACIiopqdFt2djbS09Ph7e2tC7YbsrCwwLhx4/Diiy/ioYceQnV1NY4ePdroODMzM4SEhODJJ5/Eyy+/DK1Wi4MHDxr9sRARERERNYVBdCIiIiIiE1Vf+/z333/H7t274eXlJci+9vDwAACcPn1acF5SUhK+/fbbbhnjtGnTYGtriy1btiApKUm3X6vV4sMPP4RKpcItt9yi2x8dHY2amppG91NQUAAAMDc3BwCcPXtWVyqmpeOIiIiIiLoay7kQEREREZmoG264Aba2tvjhhx9QW1uLu+66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d0C3gYU1AAhcPen+//la/GlRiH7uuef+6oZaIi4uTkFBQXK7m/4Y2O12y2q1nrFm6dKluuGGGzRt2jRJjQF4dXW1nnjiCd13330ym82+cw8fPqzNmzfr+eef99vL6NGjJUn79+8/qxA9KCio2//P01qBfO0AWAOAQMcaAAQu7n+0tYMl1drkdCnP6dJmp0ul1fV+a3qFBGnc4HjZUxJkt1k1pG/kaSNc0T5YA4DAFUj3f5d6sGhoaKhGjBihgoICXXXVVZIkr9ergoIC3XbbbWesqampaRKUS/L9xzOMpk/dfuedd2SxWHT55Zf77eW7776TJB40CgAAAABAOyo/Wa+CPW7lO4uV73CpyF3tt8ZskkYlxSrz1ANBxwyIU2iw2W8dAACt0eoQvbi4WG+//ba+/fZbVVZWyutt+utUJpNJubm5Z/2+2dnZmjdvnkaOHKnU1FTl5ubq5MmTuummmyRJv//979W3b1/NnTtXkjRhwgStWLFCw4cP941zWbp0qSZMmNDkJyFer1fvvPOOJk+erODgppd94MABrV27VpdddpliY2P1ww8/aNGiRbr44os1bNiws74GAAAAAABwZnUNXm0/WKZ8R7HynC7tOFgmr+G/bqAlQnabVZkpVo0fbFVMREj7NwsAgFoZon///fe64447VFNTo/POO08//vijbDabKioqdPz4cQ0YMED9+vVrVUPXXXedSkpKtGzZMhUXF+v888/XK6+84hvncvTo0SY7z++77z6ZTCY999xzOn78uOLj4zVhwgQ9+OCDTd538+bNOnLkiKZOnXra9wwJCVFBQYFef/11VVdX65xzztHVV1+tWbNmteoaAAAAAABAI8Mw5PypSnkOl/KdLm3Z69aJOo/fupheIcqwWWS3JSgzxar+8REd0C0AAKdrVYi+ZMkSRURE6N1331V4eLjS09P16KOPavz48frwww/1xz/+Uc8880yrm7rtttuaHd+ycuXKJl8HBwdr9uzZmj179i++p91u1w8//HDG18455xz97W9/a12zAAAAAACgieLK2sa55g6XNjldOlZR47cmNMisCwfGyZ5ild1m1chzYxRkZq45AKDztSpE/+abb3TXXXcpMTFRZWVlkv4xf3zixIn6+uuv9ac//YlgGgAAAACAAHCyzqMt+9zKP7Xb/PtjlS2qG9YvSvZTc83HnheviNAu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xIm699571atXL995W7Zs0R133KHZs2dr/PjxWrp0qXbt2qWgoCCNHz9eDz30kAYOHNikn6KiIi1fvlxbtmzRTz/9pIiICPXr10+XXHKJHn300TNeAwAAANDZCNEBAACAbio2NlaStG/fPp1//vnNnhcdHa3Zs2crNzdXknTnnXf6Xhs7dqzvz2+++aYWLFig6OhoTZgwQfHx8dq1a5defPFFbdmyRa+//rpCQ0ObvPf27du1fPlyZWZm6vbbb5fD4dDHH3+sr776SqtXr/YF+cePH9e0adN08uRJXXbZZbruuut08uRJFRUV6a233tK8efMUHMzHEwAAAHQ9JsMwjM5uAgAAAMDZ++STTzRr1iz17t1bN998szIyMjRixAjFxcWd8fxferCo0+nUjTfeKJvNptdee63Je7z00ktasmSJ5s2bp3/913+V9I+d6JI0f/583XLLLb7zV61apZycHE2YMEEvvviiJGnlypVauHChHn300SYhviSVlZX5fiAAAAAAdDXmzm4AAAAAQOtceeWVevjhh2UYhv76179q5syZGjdunH7zm99owYIFKioqavF7rVq1Sg0NDXr88cdPC+HvuusuxcfHa926dafVDRo0SNOnT29ybPr06Ro0aJA+++wzlZSUNHktPDz8tPcgQAcAAEBXxu9LAgAAAN1Ydna2pk2bpry8PG3btk27du1SYWGh3njjDb399tv6j//4jybz0JuzY8cOSVJeXp4KCgpOez04OFj79u077fiYMWNkNjfdm2M2mzVmzBgVFRXp+++/V3p6uiZMmKBnn31WCxYsUEFBgTIzMzV27FjfuBcAAACgqyJEBwAAALq5yMhITZw4URMnTpQkVVZW6tlnn9Wbb76pxx57TJmZmafNMv+/ysvLJck3fqWlrFbrGY9bLBZfL5KUlJSkv//97/rzn/+sjRs36sMPP5QkDR48WHPmzPH1DgAAAHQ1hOgAAABADxMVFaUnnnhCGzdu1OHDh/Xjjz9q5MiRv1gTGRkpSfr66699f24Jl8t1xuNut9vXy8+GDBmiZcuWqb6+Xrt379bnn3+ulStX6sEHH1SfPn104YUXtvj7AgAAAB2FmegAAABAD2QymdSrV68mx8xmszwezxnPT01NlfSPsS4t9c0338jr9TY55vV69c0338hkMmnYsGGn1YSEhOiCCy7QnDlz9Nhjj8kwDH322Wdn9X0BAACAjkKIDgAAAHRTq1atUmFh4RlfW79+vfbs2aPo6GgNGTJEkhQTE6PS0lLV1taedv6MGTMUHBysJ598UkeOHDnt9YqKCn377benHS8qKtLq1aubHFu9erWKiop0+eWXKz4+XpK0a9cuVVVVnVb/8471sLAwP1cLAAAAdA7GuQAAAADd1Oeff66cnBwNHDhQY8aMUZ8+fVRdXa3vvvtOX331lcxms3Jycnzz0MeNG6ddu3bprrvu0kUXXaSQkBBdfPHFuvjiizVkyBDl5OToj3/8o6699lpddtll6t+/v06cOKFDhw5p69atmjJlihYsWNCkB7vdroULF2rjxo1KSUmRw+HQp59+qri4OD322GO+8/77v/9bf//733XxxRerf//+ioyMlNPp1Oeff67Y2FjddNNNHfrvDgAAAGgpk2EYRmc3AQAAAODs7d27Vxs2bNDmzZu1f/9+FRcXS5L69u2rCy+8ULfddluTWegnTpzQ4sWL9emnn6qkpEQej0ezZ8/WAw884DunsLBQr732mr788kuVlpYqMjJSiYmJysjI0OTJk5WcnCxJ2rJli+644w7Nnj1b48eP19KlS7Vr1y6ZzWaNHz9e/+///T8NHDjQ9747duzQmjVrtG3bNh07dkx1dXXq16+f7Ha7Zs6cqcTExA76twYAAACcHUJ0AAAAAGftn0P0fw7hAQAAgJ6GmegAAAAAAAAAADSDEB0AAAAAAAAAgGYQogMAAAAAAAAA0AxmogMAAAAAAAAA0Ax2ogMAAAAAAAAA0AxCdAAAAAAAAAAAmkGIDgAAAAAAAABAMwjRAQAAAAAAAABoBiE6AAAAAAAAAADNIEQHAAAAAAAAAKAZhOgAAAAAAAAAADSDEB0AAAAAAAAAgGb8f9dL1SQw8SszAAAAAElFTkSuQmCC", + "image/png": "iVBORw0KGgoAAAANSUhEUgAABdEAAAPdCAYAAABlRyFLAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4FGXXBvB7Nr33QkhCQklCIIRA6L2DiggKiiCKBVQUFWzYXl8/66vYFcRGB0GKqChIDT2kUgIJIYGE9EJ6353vj5jdmfSym3r/rstLZrNl0jYzZ85zH0EURRFERERERERERERERFSDoq13gIiIiIiIiIiIiIiovWIRnYiIiIiIiIiIiIioDiyiExERERERERERERHVgUV0IiIiIiIiIiIiIqI6sIhORERERERERERERFQHFtGJiIiIiIiIiIiIiOrAIjoRERERERERERERUR1YRCciIiIiIiIiIiIiqgOL6EREREREREREREREdWARnYiIiIioHbtx4wYEQcAjjzzS6q/99ttvQxAEHDt2rNVfm4iIiIiovWARnYiIiIiIiIiIiIioDiyiExERERERERERERHVgUV0IiIiIiIiIiIiIqI6sIhORERERNQBjR8/HoIgoLS0FK+99hrc3d1hYmKCwYMH49ChQwCA3NxcLFu2DC4uLjA2NsaIESMQHBysldf//fffMWHCBFhZWcHExAT+/v749NNPUVFRUeO+R48exYwZM+Di4gIjIyM4OTlhzJgxWLdunex+YWFhuO++++Du7g4jIyM4ODhgyJAheO+997Syz0REREREzaHf1jtARERERETNd//99+PixYu4++67UVxcjC1btuCuu+7CqVOnsGTJEpSVlWHu3LnIyMjAL7/8gunTpyM+Ph5WVlbNfs1PP/0UK1euhK2tLR588EGYmZlh3759WLlyJU6cOIHdu3dDEAQAwJ9//omZM2fC2toas2bNQrdu3ZCRkYHIyEhs2rQJS5YsAQBERERg5MiR0NPTw6xZs9CjRw/k5OQgKioK69atw+uvv66VrxcRERERUVOxiE5ERERE1IFlZWXhwoULMDMzAwBMmzYN999/PyZPnowpU6Zg69at0NevPOwfOHAgXnnlFfz4449YsWJFs17v+vXreOWVV+Do6IiQkBC4ubkBAN577z1MnjwZe/fuxebNm/HQQw8BAH766SeIooijR4/C39+/xr5X2bRpE0pLS7F3717MmjWrzvsREREREbU2xrkQEREREXVg7733nrqADgD33XcfDAwMkJOTg08++URdQAeA+fPnAwAiIyOb/Xpbt25FRUUFVq5cqS6gA4CRkRE++ugjAMD69etrPM7ExKTGbXZ2ds2+HxERERFRa2ERnYiIiIioAxs4cKBsW6FQwNHRETY2NnB3d5d9rFu3bgCA5OTkZr9eeHg4gMpM9upGjBgBY2NjREREqG974IEHAADDhw/HM888gz179iAzM7PGY+fNmweFQoHZs2fj0UcfxbZt25CUlNTs/SQiIiIi0hYW0YmIiIiIOjBLS8sat+nr69d5OwCUl5c3+/Xy8vIAAE5OTjU+JggCnJyc1PcBgLlz52Lv3r3w8/PD2rVrMWfOHDg6OmLSpEmyYvuwYcNw7NgxjB07Flu3bsWDDz4IV1dXDB06FEePHm32/hIRERERtRSL6ERERERE1GhVxfm0tLQaHxNFEWlpaTUK+LNmzcLx48dx+/Zt/PXXX3j88cdx7NgxTJ8+HTk5Oer7jRkzBn/99Rdu376No0ePYsWKFbh48SLuvPNOxMXF6fTzIiIiIiKqC4voRERERETUaAEBAQCAY8eO1fjYuXPnUFJSUiNipoqFhQWmT5+OdevW4ZFHHkFaWhrOnTtX434mJiYYP348Vq9ejddeew3FxcX4559/tPlpEBERERE1GovoRERERETUaA8++CD09fXx6aefyrLVy8rK8MorrwAAHnnkEfXtQUFBUCqVNZ4nPT0dAGBsbAwAOHPmDEpKSmrcr6rjvep+REREREStTb+td4CIiIiIiDqOXr164aOPPsLKlSsxYMAAzJs3D2ZmZvj9998RHR2NWbNmYeHCher7L1++HMnJyRg9ejQ8PDwgCAJOnjyJ4OBgDB8+HKNHjwYAfPTRRzh69CjGjh0LT09PGBsbIywsDIcPH0bPnj0xe/bstvqUiYiIiKiLYxGdiIiIiIiaZMWKFejduzc+/fRTbN68GWVlZfDy8sLq1auxfPlyCIKgvu+qVauwe/duhIaG4sCBAzAwMICHhwc++ugjPP3009DT0wMAPPXUU7CyssK5c+dw/PhxiKIId3d3vPbaa3jhhRdqHZRKRERERNQaBFEUxbbeCSIiIiIiIiIiIiKi9oiZ6EREREREREREREREdWARnYiIiIiIiIiIiIioDiyiExERERERERERERHVgUV0IiIiIiIiIiIiIqI6sIhORERERERERERERFQH/bbegc5CpVIhOTkZFhYWEAShrXeHiIiIiIiIiIiIiOohiiLy8/Ph4uIChaLufnMW0bUkOTkZbm5ubb0bRERERERERERERNQEiYmJcHV1rfPjLKJriYWFBYDKL7ilpWUb7w0RERERERERERER1ScvLw9ubm7q2m5dWETXkqoIF0tLSxbRiYiIiIiIiIiIiDqIhuK5OViUiIiIiIiIiIiIiKgOLKITEREREREREREREdWBRXQiIiIiIiIiIiIiojqwiE5EREREREREREREVAcW0YlI7UZmId7edxmbz96EKIptvTtERERERERERERtTr+td4CI2gdRFPHk5lBcTc0HAJgZ6WF2gGsb7xUREREREREREVHbYic6EQEArqTkqwvoALDh9M023BsiIiIiIiIiIqL2gUV0IgIAHL6SJtuOSMxBTFp+HfcmIiIiIiIiIiLqGlhEJyIAwKGr6TVu2xmS2AZ7QkRERERERERE1H6wiE5ESM8vQWRiTo3bd4cloVypav0dIiIiIiIiIiIiaidYRCciHK2lCx0AsgrLcKSOjxEREREREREREXUFLKITEQ5d0RTKLY31YaSveWvYGXKrLXaJiIiIiIiIiIioXWARnaiLKylX4uS1TPX2RB9HTO/vrN4+Gp2O9PySttg1IiIiIiIiIiKiNsciOlEXd+Z6ForLlertSX2dMC/QTb2tVInYE5bUFrtGRERERERERETU5lhEJ+riDl1JU/9bXyFgrJcDRvS0Q3drE/XtO0ISIYpiW+weERERERERERFRm2IRnagLE0VRNjh0iIctrEwMoFAImBvoqr79ekYhwhNz2mAPiYiIiIiIiIiI2haL6ERdWFRKHlJyNXnnk/o6qv997yBX2X13hiS22n4RERERERERERG1FyyiE3Vhh6+ky7Yn93VS/9vN1hSjetupt3+PTEFRWUWr7RsREREREREREVF7wCI6URd2WJKH3svBDB72ZrKPSweMFpRW4K+Lqa22b0RERERERERERO0Bi+hEXVR6Xgkib+Wqt6Vd6FWm9XOGhbG+ensHI12IiIiIiIiIiKiLYRGdqIuSDhQFgEm1FNGNDfQwa6CLevtcfDZuZhXqfN+IiIiIiIiIiIjaCxbRibqoQ5I8dGtTAwxyt671ftJIFwD4NfSWLneLiIiIiIiIiIioXWERnagLKilX4mRshnp7grcj9PVqfzvw624FbycL9favobegVIk630ciIiIiIiIiIqL2gEV0oi7o9PVMlJSr1NuT+jrWeV9BEDA30FW9nZJbgpOxmTrdPyIiIiIiIiIiovaCRXSiLuiwJMpFXyFgrJdDvfefHdAd+gpBvc0Bo0RERERERERE1FWwiE7UxYiiKBsqOtTTFpbGBvU+xs7cCJMlg0f/uZyGnKIyne0jERERERERERFRe8EiOlEXczk5Dym5JertSZLieH3mDdFEupQpVfgtIlnr+0ZERERERERERNTesIhO1MVIo1wAYHI9eehSY/s4wNHCSL3NSBciIiIiIiIiIuoKWEQn6mIOX01T/7u3ozl62Jk16nH6egrMGaTpRr+cnIdLSbla3z8iIiIiIiIiIqL2hEV0oi4kLa8EF25pCt+TGtmFXmVuoKts+9fQW1rZLyIiIiIiIiIiovaKRXSiLkQ6UBSAbFhoY/RyMEdgDxv19t6IJJRWKLWyb0RERERERERERO0Ri+hEXcjhK5ooF2tTAwxyt6nn3rWbF+im/ndOUTkORaXXc28iIiIiIiIiIqKOjUV0oi6ipFyJk7GZ6u0J3o7QUwhNfp47BnSDqaGeepsDRomIiIiIiIiIqDNjEZ2oizh9PRMl5Sr1dlPz0KuYG+njDr9u6u2gaxlIyS1u8f4RERERERERERG1RyyiE3URh65oYlf0FQLGejk0+7mkkS6iCOwOS2rRvhEREREREREREbVXHa6IHhQUhJkzZ8LFxQWCIGDv3r313v/kyZMYNWoU7OzsYGJiAh8fH3z22Wey+yiVSrz55pvw9PSEiYkJevXqhf/7v/+DKIo6/EyIWo8oijgiKaIP62kLS2ODZj/fEA8beNiZqrd3hCTy94WIiIiIiIiIiDol/bbegaYqLCyEv78/Hn30UcyZM6fB+5uZmeGZZ57BgAEDYGZmhpMnT2Lp0qUwMzPDkiVLAAAfffQR1qxZgw0bNqBfv34ICQnB4sWLYWVlheXLl+v6UyLSucvJeUjNK1FvT/JxatHzCYKAuYFu+PhANADgZlYRguOzMaynXYuel4iIiIiIiIiIqL3pcEX0GTNmYMaMGY2+f0BAAAICAtTbHh4e2L17N06cOKEuop8+fRqzZs3CnXfeqb7Ptm3bEBwcrN2dJ2ojh66kybYn921ZER0A7h3kitUHo6H6twF9R8gtFtGJiIiIiIiIiKjT6XBxLi0VHh6O06dPY9y4cerbRo4cicOHDyMmJgYAEBkZiZMnT9ZbrC8tLUVeXp7sP6L26rAkyqWPozncJVEszeVsZYxxklz1/RdTUFBa0eLnJSIiIiIiIiIiak+6TBHd1dUVRkZGCAwMxLJly/D444+rP/bqq6/igQcegI+PDwwMDBAQEIDnn38eCxYsqPP5PvjgA1hZWan/c3Nzq/O+RG0pLa8EF5Ny1duTtNCFXmWuZMBocbkSJ69laO25iYiIiIiIiIiI2oMuU0Q/ceIEQkJCsHbtWnz++efYtm2b+mM7duzAli1bsHXrVoSFhWHDhg345JNPsGHDhjqfb9WqVcjNzVX/l5iY2BqfBlGTSbvQAWByX0etPfcEb0cIgmb7amq+1p6biIiIiIiIiIioPehwmejN5enpCQDw8/NDWloa3n77bcyfPx8A8NJLL6m70avuc/PmTXzwwQd4+OGHa30+IyMjGBkZtc7OE7XAYUkeuo2pAQLcbbT23CaGeuhha4obWUUAgJg0FtGJiIiIiIiIiKhz6TKd6FIqlQqlpaXq7aKiIigU8i+Fnp4eVCpVa+8akVYVlylxMjZTvT3B2xF6CqGeRzSdl5OF+t/R7EQnIiIiIiIiIqJOpsN1ohcUFCA2Nla9HR8fj4iICNja2sLd3R2rVq1CUlISNm7cCAD45ptv4O7uDh8fHwBAUFAQPvnkEyxfvlz9HDNnzsR7770Hd3d39OvXD+Hh4fj000/x6KOPtu4nR6Rlp69norRCczFIm3noVbydLXAwqrLb/UZWEUrKlTA20NP66xAREREREREREbWFDldEDwkJwYQJE9TbK1asAAA8/PDDWL9+PVJSUpCQkKD+uEqlwqpVqxAfHw99fX306tULH330EZYuXaq+z1dffYU333wTTz/9NNLT0+Hi4oKlS5firbfear1PjEgHDkny0A30BIz1stf6a0g70ZUqEXEZhfB1sdT66xAREREREREREbUFQRRFsa13ojPIy8uDlZUVcnNzYWnJAiK1PVEUMfyDw0jLq4wuGt3bHpsfH6b114lJy8fUz4LU25/fPxD3BHTX+usQERERERERERFpU2Nrul0yE52oK7iUlKcuoAPApL6OOnkdDzszGOhpctajOVyUiIiIiIiIiIg6ERbRiTqpQ1fSZNuTdZCHDgCG+gr0tDdXb8dwuCgREREREREREXUiLKITdVJHrmry0L2czOFma6qz1/Jy1uSisxOdiIiIiIiIiIg6ExbRiTqh9PwSXEzKVW9P8NFNlEsVbydNJ/qt28UoKK3Q6esRERERERERERG1FhbRiTqhoJhM2fZEb90W0b2cLGTb19iNTkREREREREREnQSL6ESd0NFoTZSLhbE+BvWw0enreTvLi+gxLKITEREREREREVEnwSI6USdToVThREyGentMH3sY6On2V93NxhTGBprXiE4t0OnrERERERERERERtRYW0Yk6mfDEHOSVaDLJx+s4ygUAFApBFunCTnQiIiIiIiIiIuosWEQn6mSOXk2XbY/3cmiV15UW0aNZRCciIiIiIiIiok6CRXSiTuZotCbKpZ+LJRwtjVvldb0lRfSM/FJkF5a1yusSERERERERERHpEovoRJ1Iam4JrqTkqbcntEKUSxUvDhclIiIiIiIiIqJOiEV0ok7keIw8ymWCT+tEuQDyTnSARXQiIiIiIiIiIuocWEQn6kSOXtVEuVibGmCgm02rvbaTpREsjfXV29GpLKITEREREREREVHHxyI6USdRVqHCydhM9faYPg7QUwit9vqCIMBbEunCTnQiIiIiIiIiIuoMWEQn6iRCbmajoLRCvT3Bu/WiXKp4SSJdolPzIYpiq+8DERERERERERGRNrGITtRJHI/WRLkIAjDWq/WL6NJO9LySCqTnl7b6PhAREREREREREWkTi+hEncTRaM1Q0QGu1rA3N2r1ffCqNlyUuehERERERERERNTRsYhO1Akk5RQjJq1AvT2+DbrQgZpFdOaiExERERERERFRR8ciOlEncEzShQ4AE3wc22Q/bM0M4WCh6YBnJzoREREREREREXV0LKITdQJHr2ry0O3MDDGgu1Wb7Yu3pBudnehERERERERERNTRsYhO1MGVVihxKjZTvT3OywEKhdBm++MlK6IXQKUS22xfiIiIiIiIiIiIWopFdKIOLjg+G8XlSvX2OO+2yUOv4u1srv53cbkSt24Xt+HeEBERERERERERtQyL6EQd3LFoTZSLQgDG9mnbInr14aLRjHQhIiIiIiIiIqIOjEV0arFLSbnYFXoL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6Vx2sDCqUXTtaLycNLnoZRUq3MwqbMO90UjKKcat28Xq7WE97WBrZijrPD9wORUl5cpmv8bvkZoivJG+AlN8neHjbAFTQz317aE3aw42JSIiIiLStfCEHNl2dFo+Lifntc3OEBERtVM6K6KfOnUKS5cuhbu7OxSKypepinOZO3cuFixYgJdeeklXL0/UbI+M9ERPSazGprM3Ed3KGd6ZBaWy7ujp/Zyh14GjXADA21k+XDSmnUS6nIvLkm0P9ay8WDFroIv6toLSChy+kt6s5y8orcChK2nq7cl9nWBupA99PYVsYGn1DiAiIiIiotYQllDzOHR3WFIb7AkREVH7pbMiukqlgpOTEwDA2toaenp6yM7WdFr6+fkhNDRUVy9P1GyG+gq8eZevelupEvHOH5dbdUnjwctpkKadzPDr2FEuANDb0RyC5DpAdGpB2+2MRLBkqKipoR78ulsBAKb4OsHEQNMpvjeieScS/0SlorRCMw9ipn839b8De2gGxcakFbRZdBARERERdU3JOcVIyS2pcftvEUkoV6pqeQQREVHXpLMiuqenJ+Lj4ytfRKGAp6cnDh06pP746dOnYW1trauXJ2qRCT6OmOCtifM4FZuFg1Fp9TxCu/66lKL+t52ZIYZ28CgXADA11Ie7ral6u910okuK6IN72MBAr/Jt0dRQH1P7Oak/diw6vVlF7n2SPHULI32M93bUvF6172ttXUBERERERLpS1/FnVmEZgmIyWnlviIiI2i+tFtFv39b8AZ46dSp27typ3n7qqafwww8/YPLkyZg0aRI2bNiABx98UJsvT6RVb97lCwM9Tev0u39GtSgXu7GyC8tw+romYmRqP2fo6+nselerkuaiR7eDInp6XgniMzXZ7MM85UVtaaRLuVKUXdxojNuFZThxLVO9PbWfM4wl3e0B7tay7vwQ5qITERERUSsKu5mj/rehngKG+przjl1ht9pgj4iIiNonrVbmnJ2dMXv2bPz6669YuXIltm3bhvLyys7N559/Hu+88w6ysrKQm5uLN998E++++642X55Iq3o6mGPxKE/1dmJ2MX48Ga/z1/0nKhVKSZbLHZ0gyqWKt6SIHp9ZiNIK3V+UqM/ZeHnReqinnWx7TB8H2JgaqLebGumy/1IKKiTfy7slRXkAsDQ2kH1NmItOREREnVl+STnOxmXhWjtopqBK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95ympiYjLy/PiI+PNz799NOQx95//33D4XAYJ598cpfnDgAAgIGNOhcAAACgG8rKyvTMM89o1qxZuuCCC0IeS09P19VXX609e/bo3XffNe/PycmRw+EIOddms+mSSy6RpJBzg912222KiYnp8LH8/HxdffXVIff9/Oc/lyR9/PHHXX4/8+bN0/Tp081jh8OhBQsWtHudf//73/J6vbrqqqvM1eKSFB8fr+uuu67L4+3t6quv7rCzPT09XXFxce3uP/fcc5WQkNDpn1lHXnvtNRUUFOjqq6/WxIkTQx6bMWOGTjvtNL3xxhuqqanZ/zcAAACAAYc6FwAAAKAbPv74Y3m9Xnk8Ht14443tHv/mm28kSZs2bdLJJ58sSWpqatLf//53Pf3009q0aZPq6upkGIb5nJ07d7Z7nejoaI0bN67TeUyYMEF2e+gamaysLElSVVVVl9/PkUce2e6+jl6ntWt9xowZ7c4PDuFbLV++XMuXL2835+CaG0maNGlSp3N78cUX9eCDD+rTTz9VZWWlvF6v+VhHf2ad+eCDDyRJmzdv7vD/WUlJiXw+n77++msdddRRXX5dAAAADEyE6AAAAEA3VFRUSJJWrVqlVatWdXpefX29efvMM8/U0qVLdfDBB+vss89Wenq6IiMjVVVVpXvuuUcej6fd89PT09ttBhosISGh3X0REf5v94PD5u/S1ddpXaUdvAq9VUf968uXL9dNN90Uct+CBQvaheiddbffeeed+u1vf6shQ4boBz/4gbKyssxV+XfffXeHf2adaf1/9uSTT+7zvOD/ZwAAABi8CNEttmLFCt1+++365JNPtGvXLr300kvtfjCw0o033tjuh5HRo0d3azMnAAAAdF1r6HzVVVfpjjvu+M7zP/74Yy1dulRz5szR66+/HlLr8sEHH+iee+7p8Hn7CtDDofV97969W7m5uSGPlZaWtjv/xhtv7HDV9946ep8tLS3685//rGHDhmndunUhwb1hGLrtttsOaO5Lly41rw4AAAAAOkMnusXq6+s1fvx43Xfffb025qGHHqpdu3aZHytXruy1sQEAAAa7o48+WjabTWvWrOnS+Vu3bpUknXTSSe160d9//33L59dTxo8fL0kdrr5fvXq1pWOVlZWpurpaU6dObbfyfe3atWpoaGj3nNY/245W4U+ePFmSuvz/DAAAAIMbIbrF5s6dq5tvvllnnHFGh497PB799re/1fDhwxUbG6vJkye364bcXxERERo6dKj5kZaW1q3XAwAAQNcNHTpUZ511llavXq3bb789pNu81Ycffii32y1J5qrtvRc+bNiwQYsXL+75CVvkJz/5iex2u+68806VlZWZ99fX1+uWW26xdKz09HTFxMTo008/Nf8cJamyslKXXXZZh89JSUmRJBUXF7d77LTTTlNOTo7uuusurVixot3jzc3NLEwBAACAiTqXXnbppZdq48aNevrpp5WZmamXXnpJJ5xwgr744gsddNBBB/Sa33zzjTIzMxUdHa2pU6dq8eLFysnJsXjmAAAA6Mz999+vzZs365prrtETTzyhqVOnKikpScXFxVq7dq2++eYb7dq1Sy6XS5MmTdKkSZP07LPPateuXZoyZYqKior06quv6qSTTtLzzz8f7rfTJaNHj9a1116rv/zlLxo3bpzOOussRURE6MUXX9S4ceP05Zdfttvo9EDZ7Xb96le/0p133qnx48frlFNOUU1Njf7zn/8oNzdXmZmZ7Z4za9Ys3XHHHfrFL36hH/3oR4qNjVVubq7OPfdcOZ1OPf/885o7d66OO+44zZo1S+PGjZPNZlNhYaHef/99paamUpEIAAAASaxE71VFRUV69NFH9dxzz+mYY47RyJEj9dvf/lYzZszQo48+ekCvOXnyZC1ZskRvvvmm/u///k/btm3TMccco9raWotnDwAAgM6kpKRo9erVuu222xQVFaUnn3xS9957rz744AMdeuihevzxx82rBR0Oh1577TWdf/752rp1q+69915t3LhRd9xxx353e4fbLbfcovvvv1/Jycl64IEH9Oyzz+rMM8/U/fffL6njTUoP1OLFi3XLLbfIZrPp/vvv1zvvvKNzzjlHb7/9tiIjI9udP3fuXPPP884779Qf//hHPfzww+bjRx99tD7//HNdfvnlKi4u1gMPPKBHHnlEmzZt0umnn26+BwAAAMBmdHS9KSxhs9lCNhZ9/fXXdfLJJys2NjbkPI/Hox/+8Id65plntGnTJh1yyCH7fN3f/e53uvXWWzt8rKqqSrm5ubrrrrv085//3JL3AQAAAOyPd999V9///vd1zTXX6K9//Wu4pwMAAAB0C3Uuvaiurk4Oh0OffPJJu02k4uLiJEkjRozQV199tc/XSU1N7fSxpKQk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er3mfTU1NZ2+T5vNpoyMDO3YsaPDee3Lvsbf12OdbbwKAAAAfBdCdAAAAKCHJCQkyOfzqaysTEOGDAl5rLS0tMPndBagf/zxx1q6dKnmzJmj119/PaSO5YMPPtA999wTcn5iYqIkaffu3R2+3t7jz5w5s8v96F3RGmiXlpYqNzc35DHDMFRaWtph6N3Z+wcAAADChU50AAAAoIeMHz9ekrRq1ap2j61evXq/Xmvr1q2SpJNOOqldn/n777/f6dgdPVZYWKji4uL9Gn9/TZw4UZK0fPnydo99+OGHamxs7LB+BQAAAOhrCNEBAACAHjJ//nxJ0p/+9Cc1NDSY95eUlLRbOf5dWldzr1y5MuT+DRs2aPHixe3OnzFjhvLz8/Xaa6+FPMcwDP3+978PqV7pCfPmzVNERITuuuuukK72pqYm/e53v5MkLVy4sEfnAAAAAFiBOhcAAACgh8yePVvz5s3TU089pXHjxun000+Xx+PRs88+q8mTJ2vp0qWy27u2rmXSpEmaNGmSnn32We3atUtTpkxRUVGRXn31VZ100kl6/vnnQ8632+166KGHdOKJJ2r27Nk6++yzlZmZqffee0+7du3S4YcfrvXr1/fE25YkjRw5Un/961911VVX6fDDD9dZZ52l2NhYLV26VJs3b9Zpp52mn/70pz02PgAAAGAVVqIDAAAAPeixxx7Tn//8Z/l8Pt1777164403dMUVV+i6666T1PFmmB1xOBx67bXXdP7552vr1q269957tXHjRt1xxx267bbbOnzO7NmztWzZMk2ePFnPPfecHnroIeXm5mrlypVKTk627D125sorr9Qrr7yiww47TP/617907733KioqSnfeeaeef/55+s8BAADQL9gMK3cPAgAAANAl//znP3XhhRfq/vvv18UXXxzu6QAAAADoBCE6AAAA0INKSkqUkZERsup6x44dmj59urZv365t27YpOzs7jDMEAAAAsC90ogMAAAA96NZbb9Xrr7+uY445Runp6SoqKtJrr72m2tpa3XjjjQToAAAAQB9HiA4AAAD0oBNOOEEbN27U66+/rsrKSkVHR+vwww/Xr371K82bNy/c0wMAAADwHahzAQAAAAAAAACgE/ZwTwAAAAAAAAAAgL6KOheL+Hw+7dy5U/Hx8SGbRgEAAAAAAAAA+h7DMFRbW6vMzEzZ7Z2vNydE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Phxr1qwR7j916lQEBQXh3XffxfHjx9GvXz+cOXMGa9aswYwZM7By5coGX2P8+PFYsWIFpk+fjpiYGKhUKlx77bWIiopq9JyUSiV+/PFHTJ48GVdddRVmzZqFwMBA7Nq1C1u2bEFoaCjeeeedVv+biYiIiKhrYBGdiIiIiMgEHnvsMYSFheHDDz/Ezz//jKqqKvTq1QsffPABHnroISgUCs19n332WaxatQoHDhzA+vXrYW5ujqCgICxcuBD33XcfVCoVAODee++Fo6Mj9uzZg61bt0KSJAQEBOC5557Do48+2qKs7mnTpsHBwQFFRUUYMWIEQkNDG9wnJiYGGzZswAsvvIBVq1ZBpVJh5MiR2LFjB/766682FdEB4Ntvv0WvXr3w7bff4rPPPoOfnx8ee+wxzJ49u0ER3c7ODps2bcKTTz6Jbdu2YcuWLYiMjMQvv/wCT0/PRovo9R3tmzZtwurVq6FWq+Hn56e3iA4AsbGx2L17N1577TVs2LABhYWF8PHxwcMPP4wXXnih0UGoRERERNS9KCRJkjr6JIiIiIiIiIiIiIiIOiNmohMRERERERERERER6cEiOhERERERERERERGRHiyiExERERERERERERHpwSI6EREREREREREREZEeLKITEREREREREREREenBIjoRERERERERERERkR5mHX0C3YVarUZGRgbs7e2hUCg6+nSIiIiIiIiIiIiIqAmSJKG4uBg+Pj5QKvX3m7OIbiQZGRnw9/fv6NMgIiIiIiIiIiIiohZITU2Fn5+f3o+ziG4k9vb2AOq+4Q4ODh18NkRERERERERERETUlKKiIvj7+2tqu/qwiG4k9REuDg4OLKITERERERERERERdRHNxXNzsCgRERERERERERERkR4sohMRERERERERERER6cEiOhERERERERERERGRHsxEJyIiIiLq4dRqCetOZKKsqhbDgl3g72LT0adERERERNRpsIhORERERNTDfbY5ER/+e1ZzHORqg7hwd8SGu2FEqCscrMw78OyIiIiIiDoWi+hERERERD3cmcxi4fhcXhnO5Z3Hkt3noVIqEOPvpCmqR/s5wkzFVEgiIiIi6jkUkiRJHX0S3UFRUREcHR1RWFgIBweHjj4dIiIiIiKDHU0rwH2/HERafnmz97W3MsOoUDfEhrthdLg7AlwZ/UJEREREXZOh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v1kJ4f5UvwBAB0GI7maE6AAAAAAAdB+GYch6skrZuY4u9c35xaqpa776pXdQT8eUev9wpVL9AgDtqs1D9BMnTujPf/6zduzYofLyctntjf+iMJlMWrNmTUsu3SkRogMAAAAA0H3V1DVox+FSZVsdU+pfHHOt+mVQdNDZPnWzRvQJkY+XpxtWCwCQ2jhE3717t8aNG6fTp0/r8ssv1549ezRgwACVlZXp6NGjSkhIUGxsrD777LPvdROdCSE6AAAAAAA4x1ZVq/V5NuWcDdULK5qvfvHt4akx/UKVmhSu9CSzEiOofgGAttSmIfp1112nvXv3ymKxqFevXoqIiNCnn36qCRMm6J133tG9996rDz/8UKNGjfpeN9GZEKIDAAAAAICLOVf9ci5Q33TQteqXqMCezin11ESzwvx93LBaAOg+2jREDwoK0m9/+1v9/ve/V0lJicxmsz755BNNnDhRknT//fdr165dWrduXcvvoJMhRAcAAAAAAK6orW/Q9kOlyslzhOp7jzZf/SJJgy4LVGqiY0p9RF+qXwDg+3I10/VoycXtdrsiIyMlScHBwfL09FRJSYlz/+DBg7V9+/ZLvm52dramTJmi6OhomUwmrVy58juPt1gsSklJUVhYmHx9fZWcnKznnnuu0TFLlizRyJEjFRAQoIiICE2dOlX79+9vdMy4ceNkMpka/brnnnsuef0AAAAAAADN8fHy1NWJZv3u2mStmpOm7X+YqOdnDNONI2IUFdizyfP2Hq3QK+sO6Bf/b7OGPvKJZv73Fv2/nIPKLaxUC195BwBwgVdLToqPj1d+fr4kycPDQ/Hx8fr000910003SZI2bNig4ODgS75udXW1hg4dqttvv13Tpk1r9ng/Pz/Nnj1bQ4YMkZ+fnywWi2bNmiU/Pz/dfffdkqR169YpMzNTI0eOVH19vR588EFNmjRJ+/btk5+fn/Nad911lx599FHn17169brk9QMAAAAAAFyqMH8fXT80WtcPjZZhGMprVP1SotN1DRecU1Nn17rcIq3LLZI++FKRgT6OKfX+ZqUkmmWm+gUAWo3LdS6lpaUKCQmRJP3617/We++9p9zcXEnSc889pwceeEATJkyQYRhau3atHnjgAT311FMtX5jJpBUrVmjq1KmXdN60adPk5+ent95666L7i4qKFBERoXXr1ik9PV2SYxL9yiuv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64knntDw4cPl7+8vHx8fxcTEKC0tTQsXLtSBAwecx/bt21d9+/Ztv8UCAAAAnZBXey8AAAAAQMtUVlYqNTVVu3fvVmJiom655RaFhYXJZrNpy5YtevLJJ5WQkKCEhIT2XioAAADQaRGiAwAAAJ3U0qVLtXv3bt1555169dVXZTKZGu3Pz89XbW1tO60OAAAA6BqocwEAAAA6qY0bN0qSMjMzLwjQJSk+Pl7Jyck6dOiQTCaTDh8+rMOHD8tkMjl/LV68uNE52dnZmjJlisxms3x8fJSUlKQ//OEPOnXqVKPj1q5d6zzfYrFo3LhxCggIUHBwsKZPn668vLwL1mO1WnXbbbcpPj5ePj4+Cg0N1dChQzV37lwZhtF6fzAAAABAK2ISHQAAAOikwsLCJEm5ubm68sormzwuODhYixYt0tKlSyVJc+fOde4bN26c8/cvv/yyMjMzFRwcrClTpigiIkLbtm3T448/rs8//1yff/65vL29G11706ZNWrJkia699lrNmTNHX3zxhVasWKGcnBxt2rRJ/fr1kyQdO3ZMo0aNUnV1tX784x/r5z//uaqrq2W1WrVs2TI9/fTT8vLinycAAADoeEwGIx8AAABAp/Tee+/ppz/9qQICAnT33Xdr0qRJGjFihDNc/7ZzLxU9dOjQBfv27dunoUOHauDAgVqzZk2jazz55JNauHChnn76aT3wwAOSHJPo48ePlyS98sormjVrlvP45cuX65577tFPfvITvf/++5KkF154Qffdd5+WLl2q+++/v9H3LikpUWhoaIv/HAAAAIC2RJ0LAAAA0Eldf/31euaZZ2QYhp555hlNnjxZZrNZiYmJmj17tqxWq8vXWr58uerr6/XCCy9cEML/9re/VXh4uP76179ecF7//v111113Ndp21113KSkpSR988IGKiooa7fP19b3gGgToAAAA6MiYRAcAAAA6ucrKSn388cfasGGDtm3bps2bN6uurk49e/bU3/72N11//fWSvnsSffTo0dqyZYt+//vfX7RW5bXXXlN5ebmqqqokfTOJftttt+m///u/Lzj+tttu0xtvvKHVq1dr4sSJOnTokAYNGqTa2lpNmzZN1157rX7wgx84614AAACAjorSQQAAAKCTCwgI0I033qgbb7xRklReXq4HH3xQy5Yt0x133KGjR49e0GX+bSUlJZKkxx9//JK+d2Rk5HduLy8vl+QI8Ddt2qTFixfrww8/1N///ndJUnJysh599FHn2gEAAICOhjoXAAAAoIsJCgrSiy++qD59+shms2nPnj3NnhMYGChJqqiokGEYTf76tsLCwote79z2oKAg57ZBgwbpH//4h0pKSrRx40Y9/PDDOnHihH7+859r/fr1LblVAAAAoM0RogMAAABdkMlkkp+fX6Ntnp6eamhouOjxo0ePliRt2rTpkr7P+vXrZbfbG22z2+3asGGDTCaThg4desE5PXr00JgxY/TII4/o+eefl2EYWrVq1SV9XwAAAMBdCNEBAACATmr58uXaunXrRfetXLlSX375pYKDgzVo0CBJjhd42mw21dTUXHD8r371K3l5eWnOnDkqKCi4YH9ZWZl27tx5wfbc3Fy99tprjba99tprys3N1Y9//GOFh4dLkrZv366KiooLzj83sd6zZ89m7hYAAABoH3SiAwAAAJ3URx99pHvuuUeJiYlKSUlRdHS0qqurtXPnTuXk5MjDw0PLli2Tj4+PJGnChAnatm2bfvSjHyktLU3e3t5KT09Xenq6Bg0apGXLlunee+/V5Zdfruuuu04JCQmqrKzUwYMHtW7dOt1666165ZVXGq1h8uTJuu+++/Thhx9q4MCB+uKLL/T+++/LbDbrT3/6k/O4t956S8uXL1d6eroSEhIUGBioffv26cMPP1RoaKhuu+02t/7ZAQAAAK4yGRcrNgQAAADQ4e3fv1/vvfeeVq9erby8PB0/flySdNlllyk1NVVz5szRiBEjnMdXVVVp/vz5WrVqlU6ePKmGhgYtWrRIixcvdh6zdetWPfvss8rOzlZRUZGCgoIUFxenSZMmaebMmUpOTpYkrV27VuPHj9eiRYs0ceJE/eEPf9C2bdvk6empa665Rk899ZQSExOd1928ebNef/11rV+/Xl9//bVqa2sVExOjyZMn6ze/+Y3i4uLc84cGAAAAXCJCdAAAAACX7PwQ/fwQHgAAAOhq6EQHAAAAAAAAAKAJhOgAAAAAAAAAADSBEB0AAAAAAAAAgCbQiQ4AAAAAAAAAQBOYRAcAAAAAAAAAoAmE6AAAAAAAAAAANIEQHQAAAAAAAACAJhCiAwAAAAAAAADQBEJ0AAAAAAAAAACaQIgOAAAAAAAAAEATCNEBAAAAAAAAAGgCIToAAAAAAAAAAE34/3vmNpCsZ/ItAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -438,25 +896,34 @@ ], "source": [ "# Get the TensorBoard event file for the training run\n", - "log_dirs = !find pretraining_demo/evo2/dev -name \"events.out.tfevents*\"\n", + "log_dirs = !find pretraining_demo/evo2/tb_logs -name \"events.out.tfevents*\" | sort | tail -1\n", "tf_event_file = log_dirs[0]\n", "\n", "# Extract data from your event file\n", "df = tensorboard_to_dataframe(tf_event_file)\n", "# You can uncomment and modify this to plot multiple metrics once you see what's available\n", - "plot_multiple_training_metrics(df, [\"reduced_train_loss\", \"lr\", \"grad_norm\", \"val_loss\"])" + "plot_multiple_training_metrics(df, [\"lm loss\", \"learning-rate\", \"grad-norm\", \"lm loss validation\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now you have a checkpoint that you can try out in place of the converted evo2 checkpoint in the BRCA-1 tutorial \n", - "(the path is displayed in the next code cell). To test your checkpoint, please supply the following path to the saved \n", - "checkpoint produced by this notebook as the `--ckpt-dir {checkpoint_path}`\n", - "argument to the `predict_evo2` command in the zero shot BRCA tutorial. For the 1b checkpoint you should see AUC above\n", - "0.73 if you successfully fine-tuned the checkpoint for your hardware, or to check that your hardware works with the \n", - "converted checkpoint from hugging face as is.\n", + "Now you have a fine-tuned checkpoint that you can use for inference. The `train_evo2` command produces\n", + "MBridge format checkpoints that work directly with the inference scripts.\n", + "\n", + "**Option 1: Use `predict_evo2` for log-probability scoring**\n", + "```bash\n", + "predict_evo2 --ckpt-dir --input-fasta sequences.fa --output-dir results/\n", + "```\n", + "\n", + "**Option 2: Use `infer_evo2` for text generation**\n", + "```bash\n", + "infer_evo2 --ckpt-dir --prompt \"ATCGATCG\" --max-new-tokens 100\n", + "```\n", + "\n", + "The checkpoint directory path is displayed in the next cell. You can also use this checkpoint for further\n", + "fine-tuning by passing it to `--ckpt-dir` in another `train_evo2` run.\n", "\n", "In our experience running this notebook for up to an hour on a single GPU is not sufficient to recover BF16 accuracy. We\n", "have more details about what did work in the Next Steps section below." @@ -464,24 +931,40 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 19, "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "'pretraining_demo/default--val_loss=0.8664-epoch=0-consumed_samples=800.0-last'" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Checkpoint directory: /workspaces/bionemo-framework/bionemo-recipes/recipes/evo2_megatron/examples/pretraining_demo/evo2/checkpoints\n", + "\n", + "Available checkpoints: ['iter_0000050', 'iter_0000100']\n", + "\n", + "You can now run inference with:\n", + " infer_evo2 --ckpt-dir pretraining_demo/evo2/checkpoints --prompt 'ATCGATCG' --max-new-tokens 100\n", + " predict_evo2 --ckpt-dir pretraining_demo/evo2/checkpoints --input-fasta --output-dir \n" + ] } ], "source": [ - "final_ckpt_paths = !ls -d pretraining_demo/evo2/checkpoints/*-last\n", - "final_ckpt_path = final_ckpt_paths[-1]\n", - "final_ckpt_path" + "from pathlib import Path\n", + "\n", + "\n", + "# The checkpoint directory contains all saved iterations\n", + "# The inference scripts automatically find the latest iteration\n", + "ckpt_dir = Path(\"pretraining_demo/evo2/checkpoints\")\n", + "print(f\"Checkpoint directory: {ckpt_dir.absolute()}\")\n", + "\n", + "# List available checkpoints\n", + "if ckpt_dir.exists():\n", + " checkpoints = list(ckpt_dir.glob(\"iter_*\"))\n", + " print(f\"\\nAvailable checkpoints: {[c.name for c in sorted(checkpoints)]}\")\n", + "\n", + "print(\"\\nYou can now run inference with:\")\n", + "print(f\" infer_evo2 --ckpt-dir {ckpt_dir} --prompt 'ATCGATCG' --max-new-tokens 100\")\n", + "print(f\" predict_evo2 --ckpt-dir {ckpt_dir} --input-fasta --output-dir \")" ] }, { @@ -499,7 +982,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 20, "metadata": { "jupyter": { "source_hidden": true @@ -665,7 +1148,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": ".venv", "language": "python", "name": "python3" }, diff --git a/bionemo-recipes/recipes/evo2_megatron/examples/zeroshot_brca1.ipynb b/bionemo-recipes/recipes/evo2_megatron/examples/zeroshot_brca1.ipynb index 0075a0a8ef..979b8a2589 100644 --- a/bionemo-recipes/recipes/evo2_megatron/examples/zeroshot_brca1.ipynb +++ b/bionemo-recipes/recipes/evo2_megatron/examples/zeroshot_brca1.ipynb @@ -1,1245 +1,1273 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Zero-shot prediction of BRCA1 variant effects with Evo 2\n", - "Deploy this tutorial on brev.dev: \n", - "[![ Click here to deploy.](https://brev-assets.s3.us-west-1.amazonaws.com/nv-lb-dark.svg)](https://console.brev.dev/launchable/deploy?launchableID=env-2uGqxNLgVdl752F2qcTFwHHn4Rj)\n", - "\n", - "*Note - this notebook is a reproduction of The Arc Institute’s same-titled notebook [here](https://github.com/ArcInstitute/evo2/blob/main/notebooks/brca1/brca1_zero_shot_vep.ipynb), using the BioNeMo 2 implementation of Evo2.*\n", - "\n", - "Evo2 is a foundation AI model trained on 9.3 trillion DNA base pairs, predicting variant effects without prior tast-specific training. \n", - "\n", - "Without being explicitly trained on BRCA1 variants, we show Evo 2's ability to generalize across all life forms.\n", - "\n", - "The human *BRCA1* gene encodes for a protein that repairs damaged DNA ([Moynahan et al., 1999](https://www.cell.com/molecular-cell/fulltext/S1097-2765%2800%2980202-6)). Certain variants of this gene have been associated with an increased risk of breast and ovarian cancers ([Miki et al., 1994](https://www.science.org/doi/10.1126/science.7545954?url_ver=Z39.88-2003&rfr_id=ori:rid:crossref.org&rfr_dat=cr_pub%20%200pubmed)). Using Evo 2, we can predict whether a particular single nucleotide variant (SNV) of the *BRCA1* gene is likely to be harmful to the protein's function, and thus potentially increase the risk of cancer for the patient with the genetic variant." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!pip install biopython openpyxl\n", - "import os\n", - "\n", - "\n", - "# Runs a subset of the model layers to test that the notebook runs in CI, but the output will be incorrect.\n", - "FAST_CI_MODE: bool = bool(int(os.environ.get(\"FAST_CI_MODE\", \"0\")))" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "import glob\n", - "import gzip\n", - "import json\n", - "import math\n", - "import os\n", - "from pathlib import Path\n", - "\n", - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", - "import seaborn as sns\n", - "import torch\n", - "from Bio import SeqIO\n", - "from sklearn.metrics import auc, roc_auc_score, roc_curve\n", - "\n", - "from bionemo.core.utils.subprocess_utils import run_subprocess_safely" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We start by loading a dataset from [Findlay et al. (2018)](https://www.nature.com/articles/s41586-018-0461-z), which contains experimentally measured function scores of 3,893 *BRCA1* SNVs. These function scores reflect the extent by which the genetic variant has disrupted the protein's function, with lower scores indicating greater disruption. In this dataset, the SNVs are classified into three categories based on their function scores: `LOF` (loss-of-function), `INT` (intermediate), and `FUNC` (functional). We start by reading in this dataset.\n", - "\n", - "To keep the notebook streamlined, we've abstracted much of the preprocessing logic into accompanying scripts located in `brca1_utils`. This notebook can also be viewed [here](https://docs.nvidia.com/bionemo-framework/latest/main/examples/bionemo-evo2/zeroshot_brca1/)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "jupyter": { - "source_hidden": true + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Zero-shot prediction of BRCA1 variant effects with Evo 2\n", + "Deploy this tutorial on brev.dev: \n", + "[![ Click here to deploy.](https://brev-assets.s3.us-west-1.amazonaws.com/nv-lb-dark.svg)](https://console.brev.dev/launchable/deploy?launchableID=env-2uGqxNLgVdl752F2qcTFwHHn4Rj)\n", + "\n", + "*Note - this notebook is a reproduction of The Arc Institute’s same-titled notebook [here](https://github.com/ArcInstitute/evo2/blob/main/notebooks/brca1/brca1_zero_shot_vep.ipynb), using the BioNeMo 2 implementation of Evo2.*\n", + "\n", + "Evo2 is a foundation AI model trained on 9.3 trillion DNA base pairs, predicting variant effects without prior tast-specific training. \n", + "\n", + "Without being explicitly trained on BRCA1 variants, we show Evo 2's ability to generalize across all life forms.\n", + "\n", + "The human *BRCA1* gene encodes for a protein that repairs damaged DNA ([Moynahan et al., 1999](https://www.cell.com/molecular-cell/fulltext/S1097-2765%2800%2980202-6)). Certain variants of this gene have been associated with an increased risk of breast and ovarian cancers ([Miki et al., 1994](https://www.science.org/doi/10.1126/science.7545954?url_ver=Z39.88-2003&rfr_id=ori:rid:crossref.org&rfr_dat=cr_pub%20%200pubmed)). Using Evo 2, we can predict whether a particular single nucleotide variant (SNV) of the *BRCA1* gene is likely to be harmful to the protein's function, and thus potentially increase the risk of cancer for the patient with the genetic variant." + ] }, - "tags": [ - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "def download_data(data_dir=\"brca1\", commit_hash=\"3819474bee6c24938016614411f1fa025e542bbe\"):\n", - " \"\"\"Download required data files if they don't exist locally.\n", - "\n", - " Parameters:\n", - " -----------\n", - " data_dir : str\n", - " Directory to store downloaded files\n", - " commit_hash : str\n", - " GitHub commit hash for data version\n", - " \"\"\"\n", - " if not os.path.exists(data_dir):\n", - " os.makedirs(data_dir)\n", - "\n", - " excel_path = os.path.join(data_dir, \"41586_2018_461_MOESM3_ESM.xlsx\")\n", - " genome_path = os.path.join(data_dir, \"GRCh37.p13_chr17.fna.gz\")\n", - "\n", - " if not os.path.exists(excel_path):\n", - " os.system(\n", - " f\"wget https://github.com/ArcInstitute/evo2/raw/{commit_hash}/notebooks/brca1/41586_2018_461_MOESM3_ESM.xlsx -O {excel_path}\"\n", - " )\n", - "\n", - " if not os.path.exists(genome_path):\n", - " os.system(\n", - " f\"wget https://github.com/ArcInstitute/evo2/raw/{commit_hash}/notebooks/brca1/GRCh37.p13_chr17.fna.gz -O {genome_path}\"\n", - " )\n", - "\n", - " return excel_path, genome_path\n", - "\n", - "\n", - "def load_genome_sequence(genome_path):\n", - " \"\"\"Load genome sequence from FASTA file.\n", - "\n", - " Parameters:\n", - " -----------\n", - " genome_path : str\n", - " Path to the genome FASTA file\n", - "\n", - " Returns:\n", - " --------\n", - " str\n", - " Genome sequence string\n", - " \"\"\"\n", - " with gzip.open(genome_path, \"rt\") as handle:\n", - " for record in SeqIO.parse(handle, \"fasta\"):\n", - " return str(record.seq)\n", - "\n", - " raise ValueError(\"Failed to parse genome sequence\")\n", - "\n", - "\n", - "def load_brca1_data(excel_path):\n", - " \"\"\"Load and preprocess BRCA1 data from Excel file.\n", - "\n", - " Parameters:\n", - " -----------\n", - " excel_path : str\n", - " Path to the Excel file\n", - "\n", - " Returns:\n", - " --------\n", - " pandas.DataFrame\n", - " Processed BRCA1 dataframe\n", - " \"\"\"\n", - " # Load the dataframe\n", - " brca1_df = pd.read_excel(excel_path, header=2)\n", - "\n", - " # Select and rename columns\n", - " brca1_df = brca1_df[\n", - " [\n", - " \"chromosome\",\n", - " \"position (hg19)\",\n", - " \"reference\",\n", - " \"alt\",\n", - " \"function.score.mean\",\n", - " \"func.class\",\n", - " ]\n", - " ]\n", - "\n", - " brca1_df.rename(\n", - " columns={\n", - " \"chromosome\": \"chrom\",\n", - " \"position (hg19)\": \"pos\",\n", - " \"reference\": \"ref\",\n", - " \"alt\": \"alt\",\n", - " \"function.score.mean\": \"score\",\n", - " \"func.class\": \"class\",\n", - " },\n", - " inplace=True,\n", - " )\n", - "\n", - " # Convert to two-class system\n", - " brca1_df[\"class\"] = brca1_df[\"class\"].replace([\"FUNC\", \"INT\"], \"FUNC/INT\")\n", - "\n", - " return brca1_df" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "# Configuration parameters\n", - "DATA_DIR = \"brca1\"\n", - "SAMPLE_CONFIG = {\"sample_frac\": 0.05, \"balanced\": True, \"disable\": False, \"random_state\": 42}\n", - "\n", - "# 1. Download the necessary data files if not present\n", - "excel_path, genome_path = download_data(DATA_DIR)\n", - "seq_chr17 = load_genome_sequence(genome_path)\n", - "\n", - "# 2. Load and preprocess BRCA1 data\n", - "brca1_df = load_brca1_data(excel_path)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We then group the `FUNC` and `INT` classes of SNVs together into a single category (`FUNC/INT`).\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We build a function to parse the reference and variant sequences of a 8,192-bp window around the genomic position of each SNV, using the reference sequence of human chromosome 17 where *BRCA1* is located.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To make things run faster, we'll just look at a balanced sample of our data. If you want to run on the full dataset, set `disable_sample=True`" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "jupyter": { - "source_hidden": true + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "!pip install biopython openpyxl\n", + "import os\n", + "\n", + "\n", + "# Runs a subset of the model layers to test that the notebook runs in CI, but the output will be incorrect.\n", + "FAST_CI_MODE: bool = bool(int(os.environ.get(\"FAST_CI_MODE\", \"0\")))" + ] }, - "tags": [ - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "def sample_data(df, sample_frac=1.0, balanced=True, disable=False, random_state=42):\n", - " \"\"\"Sample dataframe, optionally with balanced classes.\n", - "\n", - " Parameters:\n", - " -----------\n", - " df : pandas.DataFrame\n", - " Input dataframe\n", - " sample_frac : float\n", - " Fraction of data to sample\n", - " balanced : bool\n", - " Whether to balance classes\n", - " disable : bool\n", - " Whether to disable sampling\n", - " random_state : int\n", - " Random seed for reproducibility\n", - "\n", - " Returns:\n", - " --------\n", - " pandas.DataFrame\n", - " Sampled dataframe\n", - " \"\"\"\n", - " if disable:\n", - " return df\n", - "\n", - " if balanced:\n", - " # Get the number of rows in the dataframe\n", - " num_rows_minor_class = math.ceil(len(df[df[\"class\"] == \"LOF\"]) * sample_frac)\n", - " return (\n", - " pd.concat(\n", - " [\n", - " df[df[\"class\"] == \"LOF\"].sample(n=num_rows_minor_class, random_state=random_state),\n", - " df[df[\"class\"] == \"FUNC/INT\"].sample(n=num_rows_minor_class, random_state=random_state),\n", - " ]\n", - " )\n", - " .sample(frac=1.0, random_state=random_state)\n", - " .reset_index(drop=True)\n", - " )\n", - " else:\n", - " # Calculate the number of rows to sample\n", - " return df.sample(frac=sample_frac, random_state=random_state).reset_index(drop=True)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
chromposrefaltscoreclass
01741199726TC0.159762FUNC/INT
11741209074TA-2.065569LOF
21741256913AC-0.847753FUNC/INT
31741219631TA-2.053739LOF
41741215965GA-1.671525LOF
\n", - "
" + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.12/dist-packages/torch/cuda/__init__.py:65: FutureWarning: The pynvml package is deprecated. Please install nvidia-ml-py instead. If you did not install pynvml directly, please report this to the maintainers of the package that installed pynvml for you.\n", + " import pynvml # type: ignore[import]\n" + ] + } ], - "text/plain": [ - " chrom pos ref alt score class\n", - "0 17 41199726 T C 0.159762 FUNC/INT\n", - "1 17 41209074 T A -2.065569 LOF\n", - "2 17 41256913 A C -0.847753 FUNC/INT\n", - "3 17 41219631 T A -2.053739 LOF\n", - "4 17 41215965 G A -1.671525 LOF" + "source": [ + "import glob\n", + "import gzip\n", + "import json\n", + "import math\n", + "import os\n", + "from pathlib import Path\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "import torch\n", + "from Bio import SeqIO\n", + "from sklearn.metrics import auc, roc_auc_score, roc_curve\n", + "\n", + "from bionemo.core.utils.subprocess_utils import run_subprocess_safely" ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "OUTPUT_DIR = \"brca1_fasta_files\"\n", - "\n", - "brca1_df = sample_data(\n", - " brca1_df,\n", - " sample_frac=SAMPLE_CONFIG[\"sample_frac\"],\n", - " balanced=SAMPLE_CONFIG[\"balanced\"],\n", - " disable=SAMPLE_CONFIG[\"disable\"],\n", - " random_state=SAMPLE_CONFIG[\"random_state\"],\n", - ")\n", - "\n", - "brca1_df.head(5)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we'll write these to local `.fasta` files so we can use them for prediction below." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": { - "jupyter": { - "source_hidden": true }, - "tags": [ - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "def parse_sequences(pos, ref, alt, seq_chr17, window_size=8192):\n", - " \"\"\"Parse reference and variant sequences from the reference genome sequence.\n", - "\n", - " Parameters:\n", - " -----------\n", - " pos : int\n", - " Position (1-indexed)\n", - " ref : str\n", - " Reference base\n", - " alt : str\n", - " Alternate base\n", - " seq_chr17 : str\n", - " Full chromosome 17 sequence\n", - " window_size : int\n", - " Size of the sequence window to extract\n", - "\n", - " Returns:\n", - " --------\n", - " tuple\n", - " (reference_sequence, variant_sequence)\n", - " \"\"\"\n", - " p = pos - 1 # Convert to 0-indexed position\n", - " full_seq = seq_chr17\n", - "\n", - " ref_seq_start = max(0, p - window_size // 2)\n", - " ref_seq_end = min(len(full_seq), p + window_size // 2)\n", - " ref_seq = seq_chr17[ref_seq_start:ref_seq_end]\n", - " snv_pos_in_ref = min(window_size // 2, p)\n", - " var_seq = ref_seq[:snv_pos_in_ref] + alt + ref_seq[snv_pos_in_ref + 1 :]\n", - "\n", - " # Sanity checks\n", - " assert len(var_seq) == len(ref_seq)\n", - " assert ref_seq[snv_pos_in_ref] == ref\n", - " assert var_seq[snv_pos_in_ref] == alt\n", - "\n", - " return ref_seq, var_seq\n", - "\n", - "\n", - "def generate_fasta_files(df, seq_chr17, output_dir=\"brca1_fasta_files\", window_size=8192):\n", - " \"\"\"Generate FASTA files for reference and variant sequences.\n", - "\n", - " Parameters:\n", - " -----------\n", - " df : pandas.DataFrame\n", - " Dataframe with variant information\n", - " seq_chr17 : str\n", - " Chromosome 17 sequence\n", - " output_dir : str\n", - " Output directory for FASTA files\n", - " window_size : int\n", - " Size of sequence window\n", - "\n", - " Returns:\n", - " --------\n", - " pandas.DataFrame\n", - " Dataframe with added columns for FASTA names\n", - " \"\"\"\n", - " # Create output directory\n", - " output_dir = Path(output_dir)\n", - " output_dir.mkdir(parents=True, exist_ok=True)\n", - "\n", - " # Paths for output files\n", - " ref_fasta_path = output_dir / \"brca1_reference_sequences.fasta\"\n", - " var_fasta_path = output_dir / \"brca1_variant_sequences.fasta\"\n", - "\n", - " # Track unique sequences\n", - " ref_sequences = set()\n", - " var_sequences = set()\n", - " ref_seq_to_name = {}\n", - "\n", - " # Store unique sequences with metadata for writing\n", - " ref_entries = []\n", - " var_entries = []\n", - " ref_names = []\n", - " var_names = []\n", - "\n", - " # Collect unique reference and variant sequences\n", - " for idx, row in df.iterrows():\n", - " ref_seq, var_seq = parse_sequences(row[\"pos\"], row[\"ref\"], row[\"alt\"], seq_chr17, window_size)\n", - "\n", - " # Add to sets to ensure uniqueness\n", - " if ref_seq not in ref_sequences:\n", - " ref_sequences.add(ref_seq)\n", - " ref_name = f\"BRCA1_ref_pos_{row['pos']}_{row['ref']}_class_{row['class']}\"\n", - "\n", - " ref_entries.append(f\">{ref_name}\\n{ref_seq}\\n\")\n", - " ref_names.append(ref_name)\n", - " ref_seq_to_name[ref_seq] = ref_name\n", - " else:\n", - " ref_name = ref_seq_to_name[ref_seq]\n", - " ref_names.append(ref_name)\n", - "\n", - " if var_seq not in var_sequences:\n", - " var_sequences.add(var_seq)\n", - " var_name = f\"BRCA1_var_pos_{row['pos']}_{row['ref']}to{row['alt']}_class_{row['class']}\"\n", - "\n", - " var_entries.append(f\">{var_name}\\n{var_seq}\\n\")\n", - " var_names.append(var_name)\n", - " else:\n", - " assert False, \"Duplicate variant sequence\"\n", - "\n", - " # Write unique sequences to FASTA files\n", - " with open(ref_fasta_path, \"w\") as f:\n", - " f.writelines(ref_entries)\n", - "\n", - " with open(var_fasta_path, \"w\") as f:\n", - " f.writelines(var_entries)\n", - "\n", - " # Add FASTA names to dataframe\n", - " df_with_names = df.copy()\n", - " df_with_names[\"ref_fasta_name\"] = ref_names\n", - " df_with_names[\"var_fasta_name\"] = var_names\n", - "\n", - " print(f\"Total unique reference sequences: {len(ref_sequences)}\")\n", - " print(f\"Total unique variant sequences: {len(var_sequences)}\")\n", - "\n", - " return df_with_names" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Total unique reference sequences: 79\n", - "Total unique variant sequences: 84\n" - ] - } - ], - "source": [ - "brca1_df = generate_fasta_files(brca1_df, seq_chr17, output_dir=OUTPUT_DIR)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Load Evo 2 Checkpoints" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Then, we load Evo 2 1B model, loading the Evo 2 weights from hugging face.\n", - "\n", - "*Note - for better performance, load the 7b model by setting `MODEL_SIZE=\"7b\"` which also works well GPUs that do not support FP8.*\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "MODEL_SIZE = \"1b\" # also try 7b if you have a GPU with more than 32GB of memory\n", - "\n", - "# Define checkpoint path\n", - "if MODEL_SIZE == \"1b\":\n", - " from bionemo.core.data.load import load\n", - "\n", - " # This line will download the checkpoint from NGC to your $HOME/.cache/bionemo directory and return the path.\n", - " # To do the same from the command line, use `CHECKPOINT_PATH=$(download_bionemo_data evo2/1b-8k-bf16:1.0)`\n", - " checkpoint_path = load(\"evo2/1b-8k-bf16:1.0\")\n", - "else:\n", - " checkpoint_path = Path(f\"nemo2_evo2_{MODEL_SIZE}_8k\")\n", - "\n", - " # Check if the directory does not exist or is empty\n", - " if not checkpoint_path.exists() or not any(checkpoint_path.iterdir()):\n", - " !evo2_convert_to_nemo2 --model-path hf://arcinstitute/savanna_evo2_{MODEL_SIZE}_base --model-size {MODEL_SIZE} --output-dir nemo2_evo2_{MODEL_SIZE}_8k\n", - " else:\n", - " print(\"Checkpoint directory is not empty. Skipping command.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Score Sequences" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we score the likelihoods of the reference and variant sequences of each SNV.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": { - "jupyter": { - "source_hidden": true + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We start by loading a dataset from [Findlay et al. (2018)](https://www.nature.com/articles/s41586-018-0461-z), which contains experimentally measured function scores of 3,893 *BRCA1* SNVs. These function scores reflect the extent by which the genetic variant has disrupted the protein's function, with lower scores indicating greater disruption. In this dataset, the SNVs are classified into three categories based on their function scores: `LOF` (loss-of-function), `INT` (intermediate), and `FUNC` (functional). We start by reading in this dataset.\n", + "\n", + "To keep the notebook streamlined, we've abstracted much of the preprocessing logic into accompanying scripts located in `brca1_utils`. This notebook can also be viewed [here](https://docs.nvidia.com/bionemo-framework/latest/main/examples/bionemo-evo2/zeroshot_brca1/)." + ] }, - "tags": [ - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "def check_fp8_support():\n", - " \"\"\"Check if FP8 is supported on the current GPU.\n", - "\n", - " FP8 requires compute capability 8.9+ (Ada Lovelace/Hopper architecture or newer).\n", - " \"\"\"\n", - " if not torch.cuda.is_available():\n", - " return False, \"CUDA not available\"\n", - "\n", - " device_props = torch.cuda.get_device_properties(0)\n", - " compute_capability = f\"{device_props.major}.{device_props.minor}\"\n", - " device_name = device_props.name\n", - "\n", - " # FP8 is supported on compute capability 8.9+ (Ada Lovelace/Hopper architecture)\n", - " is_supported = (device_props.major > 8) or (device_props.major == 8 and device_props.minor >= 9)\n", - "\n", - " return is_supported, f\"Device: {device_name}, Compute Capability: {compute_capability}\"" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "FP8 Support: False\n", - "Device: NVIDIA RTX A6000, Compute Capability: 8.6\n" - ] - } - ], - "source": [ - "# Define output directories for prediction results\n", - "output_dir = Path(\"brca1_fasta_files\")\n", - "output_dir.mkdir(parents=True, exist_ok=True)\n", - "\n", - "# Save reference and variant sequences to FASTA\n", - "ref_fasta_path = output_dir / \"brca1_reference_sequences.fasta\"\n", - "var_fasta_path = output_dir / \"brca1_variant_sequences.fasta\"\n", - "\n", - "predict_ref_dir = output_dir / \"reference_predictions\"\n", - "predict_var_dir = output_dir / \"variant_predictions\"\n", - "predict_ref_dir.mkdir(parents=True, exist_ok=True)\n", - "predict_var_dir.mkdir(parents=True, exist_ok=True)\n", - "\n", - "fp8_supported, gpu_info = check_fp8_support()\n", - "print(f\"FP8 Support: {fp8_supported}\")\n", - "print(gpu_info)\n", - "\n", - "# Note: If FP8 is not supported, you may want to disable it in the model config\n", - "# The Evo2 config has 'use_fp8_input_projections: True' by default\n", - "\n", - "if FAST_CI_MODE:\n", - " model_subset_option = \"--num-layers 4 --hybrid-override-pattern SDH*\"\n", - "else:\n", - " model_subset_option = \"\"\n", - "\n", - "fp8_option = \"--fp8\" if fp8_supported else \"\"\n", - "\n", - "# Update predict commands to run on the full dataset\n", - "predict_ref_command = (\n", - " f\"predict_evo2 --fasta {ref_fasta_path} --ckpt-dir {checkpoint_path} \"\n", - " f\"--output-dir {predict_ref_dir} --model-size {MODEL_SIZE} --tensor-parallel-size 1 {model_subset_option} \"\n", - " f\"--pipeline-model-parallel-size 1 --context-parallel-size 1 --output-log-prob-seqs {fp8_option}\"\n", - ")\n", - "\n", - "predict_var_command = (\n", - " f\"predict_evo2 --fasta {var_fasta_path} --ckpt-dir {checkpoint_path} \"\n", - " f\"--output-dir {predict_var_dir} --model-size {MODEL_SIZE} --tensor-parallel-size 1 {model_subset_option} \"\n", - " f\"--pipeline-model-parallel-size 1 --context-parallel-size 1 --output-log-prob-seqs {fp8_option}\"\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Score reference sequences:" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "print(f\"Running command: {predict_ref_command}\")\n", - "\n", - "result = run_subprocess_safely(predict_ref_command)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "assert result[\"returncode\"] == 0, result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Score variant sequences:" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "print(f\"Running command: {predict_var_command}\")\n", - "\n", - "result = run_subprocess_safely(predict_var_command)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "assert result[\"returncode\"] == 0, result" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We calculate the change in likelihoods for each variant relative to the likelihood of their respective wild-type sequence.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we load the prediction files and sequence id maps:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Find and load prediction files\n", - "ref_pred_files = glob.glob(os.path.join(predict_ref_dir, \"predictions__rank_*.pt\"))\n", - "var_pred_files = glob.glob(os.path.join(predict_var_dir, \"predictions__rank_*.pt\"))\n", - "\n", - "# Load sequence ID maps (maps sequence ID -> prediction index)\n", - "with open(os.path.join(predict_ref_dir, \"seq_idx_map.json\"), \"r\") as f:\n", - " ref_seq_idx_map = json.load(f)\n", - "with open(os.path.join(predict_var_dir, \"seq_idx_map.json\"), \"r\") as f:\n", - " var_seq_idx_map = json.load(f)\n", - "\n", - "# Load predictions\n", - "ref_preds = torch.load(ref_pred_files[0])\n", - "var_preds = torch.load(var_pred_files[0])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then, calculate the delta score:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ + "cell_type": "code", + "execution_count": 3, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "def download_data(data_dir=\"brca1\", commit_hash=\"3819474bee6c24938016614411f1fa025e542bbe\"):\n", + " \"\"\"Download required data files if they don't exist locally.\n", + "\n", + " Parameters:\n", + " -----------\n", + " data_dir : str\n", + " Directory to store downloaded files\n", + " commit_hash : str\n", + " GitHub commit hash for data version\n", + " \"\"\"\n", + " if not os.path.exists(data_dir):\n", + " os.makedirs(data_dir)\n", + "\n", + " excel_path = os.path.join(data_dir, \"41586_2018_461_MOESM3_ESM.xlsx\")\n", + " genome_path = os.path.join(data_dir, \"GRCh37.p13_chr17.fna.gz\")\n", + "\n", + " if not os.path.exists(excel_path):\n", + " os.system(\n", + " f\"wget https://github.com/ArcInstitute/evo2/raw/{commit_hash}/notebooks/brca1/41586_2018_461_MOESM3_ESM.xlsx -O {excel_path}\"\n", + " )\n", + "\n", + " if not os.path.exists(genome_path):\n", + " os.system(\n", + " f\"wget https://github.com/ArcInstitute/evo2/raw/{commit_hash}/notebooks/brca1/GRCh37.p13_chr17.fna.gz -O {genome_path}\"\n", + " )\n", + "\n", + " return excel_path, genome_path\n", + "\n", + "\n", + "def load_genome_sequence(genome_path):\n", + " \"\"\"Load genome sequence from FASTA file.\n", + "\n", + " Parameters:\n", + " -----------\n", + " genome_path : str\n", + " Path to the genome FASTA file\n", + "\n", + " Returns:\n", + " --------\n", + " str\n", + " Genome sequence string\n", + " \"\"\"\n", + " with gzip.open(genome_path, \"rt\") as handle:\n", + " for record in SeqIO.parse(handle, \"fasta\"):\n", + " return str(record.seq)\n", + "\n", + " raise ValueError(\"Failed to parse genome sequence\")\n", + "\n", + "\n", + "def load_brca1_data(excel_path):\n", + " \"\"\"Load and preprocess BRCA1 data from Excel file.\n", + "\n", + " Parameters:\n", + " -----------\n", + " excel_path : str\n", + " Path to the Excel file\n", + "\n", + " Returns:\n", + " --------\n", + " pandas.DataFrame\n", + " Processed BRCA1 dataframe\n", + " \"\"\"\n", + " # Load the dataframe\n", + " brca1_df = pd.read_excel(excel_path, header=2)\n", + "\n", + " # Select and rename columns\n", + " brca1_df = brca1_df[\n", + " [\n", + " \"chromosome\",\n", + " \"position (hg19)\",\n", + " \"reference\",\n", + " \"alt\",\n", + " \"function.score.mean\",\n", + " \"func.class\",\n", + " ]\n", + " ]\n", + "\n", + " brca1_df.rename(\n", + " columns={\n", + " \"chromosome\": \"chrom\",\n", + " \"position (hg19)\": \"pos\",\n", + " \"reference\": \"ref\",\n", + " \"alt\": \"alt\",\n", + " \"function.score.mean\": \"score\",\n", + " \"func.class\": \"class\",\n", + " },\n", + " inplace=True,\n", + " )\n", + "\n", + " # Convert to two-class system\n", + " brca1_df[\"class\"] = brca1_df[\"class\"].replace([\"FUNC\", \"INT\"], \"FUNC/INT\")\n", + "\n", + " return brca1_df" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "\n", + "# Configuration parameters\n", + "DATA_DIR = \"brca1\"\n", + "SAMPLE_CONFIG = {\"sample_frac\": 0.1, \"balanced\": True, \"disable\": False, \"random_state\": 42}\n", + "\n", + "# 1. Download the necessary data files if not present\n", + "excel_path, genome_path = download_data(DATA_DIR)\n", + "seq_chr17 = load_genome_sequence(genome_path)\n", + "\n", + "# 2. Load and preprocess BRCA1 data\n", + "brca1_df = load_brca1_data(excel_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We then group the `FUNC` and `INT` classes of SNVs together into a single category (`FUNC/INT`).\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We build a function to parse the reference and variant sequences of a 8,192-bp window around the genomic position of each SNV, using the reference sequence of human chromosome 17 where *BRCA1* is located.\n", + "\n" + ] + }, { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
chromposrefaltscoreclassref_fasta_namevar_fasta_nameref_log_probsvar_log_probsevo2_delta_score
01741199726TC0.159762FUNC/INTBRCA1_ref_pos_41199726_T_class_FUNC/INTBRCA1_var_pos_41199726_TtoC_class_FUNC/INT-0.952952-0.953219-0.000267
11741209074TA-2.065569LOFBRCA1_ref_pos_41209074_T_class_LOFBRCA1_var_pos_41209074_TtoA_class_LOF-0.750379-0.750438-0.000059
21741256913AC-0.847753FUNC/INTBRCA1_ref_pos_41256913_A_class_FUNC/INTBRCA1_var_pos_41256913_AtoC_class_FUNC/INT-0.798110-0.799099-0.000989
31741219631TA-2.053739LOFBRCA1_ref_pos_41219631_T_class_LOFBRCA1_var_pos_41219631_TtoA_class_LOF-1.032214-1.032696-0.000482
41741215965GA-1.671525LOFBRCA1_ref_pos_41215965_G_class_LOFBRCA1_var_pos_41215965_GtoA_class_LOF-0.860933-0.861262-0.000329
\n", - "
" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To make things run faster, we'll just look at a balanced sample of our data. If you want to run on the full dataset, set `disable_sample=True`" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "def sample_data(df, sample_frac=1.0, balanced=True, disable=False, random_state=42):\n", + " \"\"\"Sample dataframe, optionally with balanced classes.\n", + "\n", + " Parameters:\n", + " -----------\n", + " df : pandas.DataFrame\n", + " Input dataframe\n", + " sample_frac : float\n", + " Fraction of data to sample\n", + " balanced : bool\n", + " Whether to balance classes\n", + " disable : bool\n", + " Whether to disable sampling\n", + " random_state : int\n", + " Random seed for reproducibility\n", + "\n", + " Returns:\n", + " --------\n", + " pandas.DataFrame\n", + " Sampled dataframe\n", + " \"\"\"\n", + " if disable:\n", + " return df\n", + "\n", + " if balanced:\n", + " # Get the number of rows in the dataframe\n", + " num_rows_minor_class = math.ceil(len(df[df[\"class\"] == \"LOF\"]) * sample_frac)\n", + " return (\n", + " pd.concat(\n", + " [\n", + " df[df[\"class\"] == \"LOF\"].sample(n=num_rows_minor_class, random_state=random_state),\n", + " df[df[\"class\"] == \"FUNC/INT\"].sample(n=num_rows_minor_class, random_state=random_state),\n", + " ]\n", + " )\n", + " .sample(frac=1.0, random_state=random_state)\n", + " .reset_index(drop=True)\n", + " )\n", + " else:\n", + " # Calculate the number of rows to sample\n", + " return df.sample(frac=sample_frac, random_state=random_state).reset_index(drop=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
chromposrefaltscoreclass
01741276097AG0.326953FUNC/INT
11741201130AG0.056569FUNC/INT
21741215938TA-2.017579LOF
31741215932AC-1.706222LOF
41741219685GT0.037593FUNC/INT
\n", + "
" + ], + "text/plain": [ + " chrom pos ref alt score class\n", + "0 17 41276097 A G 0.326953 FUNC/INT\n", + "1 17 41201130 A G 0.056569 FUNC/INT\n", + "2 17 41215938 T A -2.017579 LOF\n", + "3 17 41215932 A C -1.706222 LOF\n", + "4 17 41219685 G T 0.037593 FUNC/INT" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } ], - "text/plain": [ - " chrom pos ref alt score class \\\n", - "0 17 41199726 T C 0.159762 FUNC/INT \n", - "1 17 41209074 T A -2.065569 LOF \n", - "2 17 41256913 A C -0.847753 FUNC/INT \n", - "3 17 41219631 T A -2.053739 LOF \n", - "4 17 41215965 G A -1.671525 LOF \n", - "\n", - " ref_fasta_name \\\n", - "0 BRCA1_ref_pos_41199726_T_class_FUNC/INT \n", - "1 BRCA1_ref_pos_41209074_T_class_LOF \n", - "2 BRCA1_ref_pos_41256913_A_class_FUNC/INT \n", - "3 BRCA1_ref_pos_41219631_T_class_LOF \n", - "4 BRCA1_ref_pos_41215965_G_class_LOF \n", - "\n", - " var_fasta_name ref_log_probs var_log_probs \\\n", - "0 BRCA1_var_pos_41199726_TtoC_class_FUNC/INT -0.952952 -0.953219 \n", - "1 BRCA1_var_pos_41209074_TtoA_class_LOF -0.750379 -0.750438 \n", - "2 BRCA1_var_pos_41256913_AtoC_class_FUNC/INT -0.798110 -0.799099 \n", - "3 BRCA1_var_pos_41219631_TtoA_class_LOF -1.032214 -1.032696 \n", - "4 BRCA1_var_pos_41215965_GtoA_class_LOF -0.860933 -0.861262 \n", - "\n", - " evo2_delta_score \n", - "0 -0.000267 \n", - "1 -0.000059 \n", - "2 -0.000989 \n", - "3 -0.000482 \n", - "4 -0.000329 " + "source": [ + "OUTPUT_DIR = \"brca1_fasta_files\"\n", + "\n", + "brca1_df = sample_data(\n", + " brca1_df,\n", + " sample_frac=SAMPLE_CONFIG[\"sample_frac\"],\n", + " balanced=SAMPLE_CONFIG[\"balanced\"],\n", + " disable=SAMPLE_CONFIG[\"disable\"],\n", + " random_state=SAMPLE_CONFIG[\"random_state\"],\n", + ")\n", + "\n", + "brca1_df.head(5)" ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# next, calculate change in likelihoods\n", - "ref_log_probs = []\n", - "var_log_probs = []\n", - "for _, row in brca1_df.iterrows():\n", - " ref_name = row[\"ref_fasta_name\"]\n", - " var_name = row[\"var_fasta_name\"]\n", - " ref_log_probs.append(ref_preds[\"log_probs_seqs\"][ref_seq_idx_map[ref_name]].item())\n", - " var_log_probs.append(var_preds[\"log_probs_seqs\"][var_seq_idx_map[var_name]].item())\n", - "brca1_df[\"ref_log_probs\"] = ref_log_probs\n", - "brca1_df[\"var_log_probs\"] = var_log_probs\n", - "# ideally probability of a broken variant is lower than a good one. So a bad var - good ref is negative.\n", - "brca1_df[\"evo2_delta_score\"] = brca1_df[\"var_log_probs\"] - brca1_df[\"ref_log_probs\"]\n", - "brca1_df.head()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This delta likelihood should be predictive of how disruptive the SNV is to the protein's function: the lower the delta, the more likely that the SNV is disruptive. We can show this by comparing the distributions of delta likelihoods for the two classes of SNVs (functional/intermediate vs loss-of-function)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "source_hidden": true }, - "tags": [ - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "def plot_strip_with_means(df, x_col=\"evo2_delta_score\", class_col=\"class\"):\n", - " \"\"\"Creates a strip plot with jittered points and median indicators for each class using Seaborn.\n", - "\n", - " Parameters:\n", - " - df (pd.DataFrame): The input DataFrame containing data.\n", - " - x_col (str): The column name representing the x-axis values (e.g., evo2_delta_score).\n", - " - class_col (str): The column name representing the class labels.\n", - "\n", - " Returns:\n", - " - matplotlib Figure: Strip plot with median indicators.\n", - " \"\"\"\n", - " # NVIDIA theme colors\n", - " NVIDIA_GREEN = \"#76B900\" # noqa: N806\n", - " BACKGROUND_COLOR = \"#F8F8F8\" # noqa: N806\n", - " GRID_COLOR = \"#DDDDDD\" # noqa: N806\n", - " FONT_COLOR = \"#333333\" # noqa: N806\n", - "\n", - " # Determine order of classes (if not already specified)\n", - " unique_classes = sorted(df[class_col].unique())\n", - "\n", - " # Set up the plot with NVIDIA theme\n", - " plt.figure(figsize=(9, 5), facecolor=BACKGROUND_COLOR)\n", - " plt.style.use(\"default\") # Reset to default to avoid any pre-existing style\n", - "\n", - " # Create strip plot\n", - " p = sns.stripplot(\n", - " data=df,\n", - " x=x_col,\n", - " y=class_col,\n", - " hue=class_col,\n", - " order=unique_classes,\n", - " palette=[NVIDIA_GREEN, \"red\"],\n", - " size=6,\n", - " jitter=0.3,\n", - " alpha=0.6,\n", - " )\n", - "\n", - " # Add median indicators using boxplot\n", - " sns.boxplot(\n", - " showmeans=True,\n", - " meanline=True,\n", - " meanprops={\"visible\": False},\n", - " medianprops={\"color\": \"black\", \"ls\": \"-\", \"lw\": 2},\n", - " whiskerprops={\"visible\": False},\n", - " zorder=10,\n", - " x=x_col,\n", - " y=class_col,\n", - " data=df,\n", - " order=unique_classes,\n", - " showfliers=False,\n", - " showbox=False,\n", - " showcaps=False,\n", - " ax=p,\n", - " )\n", - "\n", - " # Customize plot appearance\n", - " plt.title(\n", - " \"Distribution of Delta Likelihoods Scores\\nComparing Evo 2 likelihood scores for different BRCA1 SNV classes\",\n", - " color=FONT_COLOR,\n", - " fontsize=12,\n", - " loc=\"left\",\n", - " )\n", - " plt.xlabel(\"Delta Likelihood Score, Evo 2\", color=FONT_COLOR)\n", - " plt.ylabel(\"BRCA1 SNV Class\", color=FONT_COLOR)\n", - "\n", - " # Customize grid and tick colors\n", - " plt.grid(color=GRID_COLOR, axis=\"x\", linestyle=\"--\", linewidth=0.5)\n", - " plt.tick_params(colors=FONT_COLOR)\n", - "\n", - " # Set background color\n", - " plt.gca().set_facecolor(BACKGROUND_COLOR)\n", - " plt.gcf().set_facecolor(BACKGROUND_COLOR)\n", - "\n", - " plt.tight_layout()\n", - "\n", - " # return plt.gcf()" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ { - "data": { - "image/png": "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8/LLL+P69etYvXp1iXXPPPMMdDodli5dWq6MlLRu3Vpxmbe3N65fv16h2u99bpUkJiZi2rRpcHZ2xvz58+Hv74+srCx89dVX2LVrF9LS0hAUFIQhQ4bgscceM2mE3mvDhg145513jB/u7nby5El8+umnuHLlCgIDAzFhwoQSH7Li4+Px+eef4+jRoygoKECDBg0wfvx4dO3a1WS7tLQ0LFq0CPv27UNOTg7q1KmDRx55BA899JDJdllZWZg3bx52794NSZLQrVs3PPLIIxY9dzqdDsuXL8cff/yB27dvw9XVFZGRkRg/fjw6duxo3O7GjRtYsmQJjh8/jtzcXAQHByM6Ohr/+te/jNtcvHgRX3zxBU6dOgVZlnH//ffjmWeeQfPmzUs8dwsWLMC2bduwY8cO6PV6bNq0CQBw4MABLF++HBcvXoQkSWjdujUmT56M+vXrG/eRkpKCRYsW4ciRI0hPT4e3tzeaNm2K559/vkQexaZOnYo///wTADBx4kQAwIABA/Daa68BKLoa/O233+LatWtwc3NDp06dMHnyZJOrJcXvGcuWLcMnn3yCkydPon379njvvffMPr8nT57E/PnzcfXqVQQGBmLMmDGK240cORJt2rTBa6+9ZnxvAICFCxdi4cKFCAkJQZs2bbBx40YAMNbdpk0bLFiwwJjR4sWLcezYMeTn56NevXoYP348unfvbtXnv/h5+OGHH/DRRx/h6NGjcHFxwYABAzBp0iSo1WqTbuJLly41/j4Veb9ycnKCl5eXRV+s+Pn5oW7durhy5Ypx2c6dO3H58mU888wzJRp4AODh4WHyOi62efNmdOjQAe3atUNkZCQ2b96s2Mgz95qzRHx8POrVq6fYaPfz81M8VrNmzbBu3To8/vjjZq/mubi44MEHH8TmzZuRlpZWYl9btmyBu7u7yWvDnAMHDuDbb7/FhQsXIEmS8X2otAaipX8zDx8+jK+//hpXr16FXq9HYGAgevTogWeffda4zerVq/Hrr78iISEBzs7OCAsLw6OPPmpy/OTkZHz55ZfYv38/srOzER4ejpiYGAwaNMjkeJbsi4gcj428amLfvn0ICwtDixYtLNr+vffew8aNGxEdHY2YmBicPXsWK1aswI0bN/Duu++abBsXF4f//ve/GDZsGPr164cffvgBr7zyCl5++WV88cUXGDFiBABgxYoV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iYmJxMfHBxwXX331FTNnzvQvvHz//fcHXXj5008/JSsri06dOvHQQw8F3Jt+up8XKTkncpwwYQJffPFFoVpOPc6l5MKd5aJFi3j22WeD1rJixYoyfKfhpyZPRERERETEIPpIXkRERERExCBq8kRERERERAyiJk9ERERERMQgavJEREREREQMoiZPRERERETEIGryREREREREDKImT0RERERExCBq8kRERERERAyiJk9ERMrcnDlzGDZsWNhfd9GiRQwcODBkdZzu+SZMmMCjjz5a6uc/G9dddx3vv/++I68tIiLlS4TTBYiISPk0YcIEvvjiCwDcbjexsbG0bNmS/v37M3jwYFyu0n9OOGHCBLKyspg4ceJZ19mrVy+ee+45LrnkkkJj/fr14+KLLz7r1yjKzTffzPXXX19mzx9Kubm5vP7663z11VccOXKEqKgomjVrxk033UTv3r2dLu+0rrvuOg4cOFBo+3333cftt99eJq+5f/9+5s2bx+rVq0lJSaFu3boMHDiQYcOGERkZWSavKSISCmryRESkSD179uSxxx7D6/WSmprKypUrmTx5Ml9//TXPP/88ERHl+38jVatWpWrVqmX2/FFRUWX23KH24osvsmHDBkaNGkXz5s05evQo69at4+jRo2X2mvn5+SFthu6++26GDBkSsK0sM0hOTsbr9TJ27FgaN27Mjh07eP7558nNzSUxMbHMXldE5GyV7/87i4iIoyIjI4mLiwOgXr16tG3blg4dOvDggw+yePFi/x/cmZmZTJ06leXLl5OXl8e5557Lgw8+SOvWrQs955w5c/xnCHv16gXAlClT6NKlC9OmTWPp0qUcOnSIuLg4+vfvz/Dhw0vdTC5atIi///3v/Otf/wo6vmfPHkaNGkXPnj0ZPXo0+fn5zJw5kyVLlpCVlUWLFi3405/+RJcuXYI+fs6cOSxdupT58+cHbH/nnXd47733yM/Pp1+/fowYMcL/HjIyMnjllVdYsWIFeXl5XHDBBYwcOZImTZr4H//1118ze/Zs9u7dS1xcHNdffz0333yzfzwtLY3nnnuOH374gbi4OO65557Tfi+WL1/OiBEj/Gc2GzZsyLnnnhuwT15eHrNnz2bJkiWkpaVRv359br/9dq666ioA1qxZw6uvvsq2bduIjY1l0KBB3HPPPf73lpiYSPPmzXG73Xz55Ze0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w2zevJn58+fzyCOP0KtXLy6//HIAunXrRvv27Xn00UdZtWoV+/fvZ926dcyYMYNff/016PPGx8ezbds2kpOTSU9Pp6CggMaNG3Pw4EGSkpLYs2cPCxYsYOnSpWdU5759+9iyZUvAv5ycnCL3r169Oi+++CJut5sxY8aQnZ1N06ZNGTBgABMmTOCbb75h3759bNy4kTfeeIPvvvuu5N+8IJo0aULv3r2ZOHEia9euZevWrTz99NPUq1fPf4nmzTffzOrVq3n99dfZtWuX/z7BE5eRJiQk0LNnT1544QU2bNjgn8TldGfiEhMT+fjjj9m0aRP79+/nu+++Y8aMGXTp0oXo6GgaNmzIoEGDeO6551i6dCn79u3jp59+4t///jcA1157LYcOHeKll14iOTmZZcuWMWfOHG666aZiG59rr72WjIwMnnrqKX799Vf27NnDqlWreOaZZ/B4PCX6/mVnZ5OSkhLw79SJZBo2bEjHjh157rnn8Hg8AZe9duvWjRYtWjBu3Dg2b97Mxo0bGT9+PBdccEGRl2oePnyYxMREGjRoQGJiIunp6f7XFREpz3S5poiIFGnlypUMGTIEt9tNTEwMrVq1YtSoUQwaNMj/h71lWUyaNImZM2fy7LPPkp6eTlxcHOeff36RU9MPGTKENWvWcNddd5GTk8OUKVPo3bs3N954Iy+99BJ5eXlcfPHF3HHHHcydO/e0dU6ZMqXQtmnTphX7mKioKP7v//6P0aNHM3bsWCZNmsTjjz/OvHnzmDp1KocPH6ZmzZq0b9/ev9RDKDz22GO88sorPPzww+Tn59O5c2cmTZrkv8yzbdu2PP3008yePZt58+YRFxfH3XffzRVXXBHwHBMnTiQxMZHatWtz7733FpqQ5Pe6d+/OF198wYwZM8jNzaVu3br06tXLP8ELwJgxY5gxYwaTJk0iIyODBg0a8D//8z+A7963SZMm8eqrrzJs2DBiY2O58sor/RObFKVevXq89tprTJs2jVGjRpGXl0d8fDw9evTw/wwtWrSIZ599lhUrVhT7XLNnz2b27NkB266++moefvhh/9cDBgxg0qRJXH755QGNr2VZTJw4kZdffpk///nPAUsoFOX7779nz5497Nmzh2uuuSZg7HS1iog4ycrNzS0fNwqIiIhIpTR79mx+/vnnoPd1iohIyelyTREREXHUypUrg07MIiIipaMzeSIiIiIiIgbRmTwRERERERGDqMkTERERERExiJo8ERERERERg6jJExERERERMYiaPBEREREREYOoyRMRERERETGImjwRERERERGDqMkTERERERExiJo8ERERERERg/x/KkR0ANmKMC0AAAAASUVORK5CYII=", - "text/plain": [ - "
" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we'll write these to local `.fasta` files so we can use them for prediction below." ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plot_strip_with_means(brca1_df, x_col=\"evo2_delta_score\", class_col=\"class\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can also calculate the area under the receiver operating characteristic curve (AUROC) of this zero-shot prediction method. Note that the results are nearly random unless you are on one of the following configurations:\n", - "* `--fp8` on an fp8 enabled GPU with either the 1b or 7b models. The 40b likely works as well.\n", - "* the 7b model uniquely seems to work well without `--fp8` so if you are on an older device, the 7b model should produce\n", - " robust results. Change the `MODEL_SIZE` earlier in this tutorial and rerun for good results in that case.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ + }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Zero-shot prediction AUROC: 0.77\n" - ] - } - ], - "source": [ - "# Calculate AUROC of zero-shot predictions\n", - "# class 1 is LOF which is the bad thing. That means we expect this to be more negative.\n", - "y_true = brca1_df[\"class\"] == \"LOF\"\n", - "auroc = roc_auc_score(y_true, -brca1_df[\"evo2_delta_score\"])\n", - "print(f\"Zero-shot prediction AUROC: {auroc:.2}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "source_hidden": true + "cell_type": "code", + "execution_count": 7, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "def parse_sequences(pos, ref, alt, seq_chr17, window_size=8192):\n", + " \"\"\"Parse reference and variant sequences from the reference genome sequence.\n", + "\n", + " Parameters:\n", + " -----------\n", + " pos : int\n", + " Position (1-indexed)\n", + " ref : str\n", + " Reference base\n", + " alt : str\n", + " Alternate base\n", + " seq_chr17 : str\n", + " Full chromosome 17 sequence\n", + " window_size : int\n", + " Size of the sequence window to extract\n", + "\n", + " Returns:\n", + " --------\n", + " tuple\n", + " (reference_sequence, variant_sequence)\n", + " \"\"\"\n", + " p = pos - 1 # Convert to 0-indexed position\n", + " full_seq = seq_chr17\n", + "\n", + " ref_seq_start = max(0, p - window_size // 2)\n", + " ref_seq_end = min(len(full_seq), p + window_size // 2)\n", + " ref_seq = seq_chr17[ref_seq_start:ref_seq_end]\n", + " snv_pos_in_ref = min(window_size // 2, p)\n", + " var_seq = ref_seq[:snv_pos_in_ref] + alt + ref_seq[snv_pos_in_ref + 1 :]\n", + "\n", + " # Sanity checks\n", + " assert len(var_seq) == len(ref_seq)\n", + " assert ref_seq[snv_pos_in_ref] == ref\n", + " assert var_seq[snv_pos_in_ref] == alt\n", + "\n", + " return ref_seq, var_seq\n", + "\n", + "\n", + "def generate_fasta_files(df, seq_chr17, output_dir=\"brca1_fasta_files\", window_size=8192):\n", + " \"\"\"Generate FASTA files for reference and variant sequences.\n", + "\n", + " Parameters:\n", + " -----------\n", + " df : pandas.DataFrame\n", + " Dataframe with variant information\n", + " seq_chr17 : str\n", + " Chromosome 17 sequence\n", + " output_dir : str\n", + " Output directory for FASTA files\n", + " window_size : int\n", + " Size of sequence window\n", + "\n", + " Returns:\n", + " --------\n", + " pandas.DataFrame\n", + " Dataframe with added columns for FASTA names\n", + " \"\"\"\n", + " # Create output directory\n", + " output_dir = Path(output_dir)\n", + " output_dir.mkdir(parents=True, exist_ok=True)\n", + "\n", + " # Paths for output files\n", + " ref_fasta_path = output_dir / \"brca1_reference_sequences.fasta\"\n", + " var_fasta_path = output_dir / \"brca1_variant_sequences.fasta\"\n", + "\n", + " # Track unique sequences\n", + " ref_sequences = set()\n", + " var_sequences = set()\n", + " ref_seq_to_name = {}\n", + "\n", + " # Store unique sequences with metadata for writing\n", + " ref_entries = []\n", + " var_entries = []\n", + " ref_names = []\n", + " var_names = []\n", + "\n", + " # Collect unique reference and variant sequences\n", + " for idx, row in df.iterrows():\n", + " ref_seq, var_seq = parse_sequences(row[\"pos\"], row[\"ref\"], row[\"alt\"], seq_chr17, window_size)\n", + "\n", + " # Add to sets to ensure uniqueness\n", + " if ref_seq not in ref_sequences:\n", + " ref_sequences.add(ref_seq)\n", + " ref_name = f\"BRCA1_ref_pos_{row['pos']}_{row['ref']}_class_{row['class']}\"\n", + "\n", + " ref_entries.append(f\">{ref_name}\\n{ref_seq}\\n\")\n", + " ref_names.append(ref_name)\n", + " ref_seq_to_name[ref_seq] = ref_name\n", + " else:\n", + " ref_name = ref_seq_to_name[ref_seq]\n", + " ref_names.append(ref_name)\n", + "\n", + " if var_seq not in var_sequences:\n", + " var_sequences.add(var_seq)\n", + " var_name = f\"BRCA1_var_pos_{row['pos']}_{row['ref']}to{row['alt']}_class_{row['class']}\"\n", + "\n", + " var_entries.append(f\">{var_name}\\n{var_seq}\\n\")\n", + " var_names.append(var_name)\n", + " else:\n", + " assert False, \"Duplicate variant sequence\"\n", + "\n", + " # Write unique sequences to FASTA files\n", + " with open(ref_fasta_path, \"w\") as f:\n", + " f.writelines(ref_entries)\n", + "\n", + " with open(var_fasta_path, \"w\") as f:\n", + " f.writelines(var_entries)\n", + "\n", + " # Add FASTA names to dataframe\n", + " df_with_names = df.copy()\n", + " df_with_names[\"ref_fasta_name\"] = ref_names\n", + " df_with_names[\"var_fasta_name\"] = var_names\n", + "\n", + " print(f\"Total unique reference sequences: {len(ref_sequences)}\")\n", + " print(f\"Total unique variant sequences: {len(var_sequences)}\")\n", + "\n", + " return df_with_names" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total unique reference sequences: 156\n", + "Total unique variant sequences: 166\n" + ] + } + ], + "source": [ + "brca1_df = generate_fasta_files(brca1_df, seq_chr17, output_dir=OUTPUT_DIR)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Load Evo 2 Checkpoints" + ] }, - "tags": [ - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "def plot_roc_curve(df):\n", - " \"\"\"Plots an ROC curve using Seaborn with a light NVIDIA-themed design.\n", - "\n", - " The function assumes:\n", - " - `class` column as the true labels (binary, 'LOF' = 1, else 0).\n", - " - `evo2_delta_score` as the prediction score.\n", - "\n", - " Parameters:\n", - " - df (pd.DataFrame): DataFrame containing `class` and `evo2_delta_score`.\n", - "\n", - " Returns:\n", - " - matplotlib Figure: ROC Curve Visualization.\n", - " \"\"\"\n", - " # NVIDIA theme colors\n", - " NVIDIA_GREEN = \"#76B900\" # noqa: N806\n", - " BACKGROUND_COLOR = \"#F8F8F8\" # noqa: N806\n", - " GRID_COLOR = \"#DDDDDD\" # noqa: N806\n", - " FONT_COLOR = \"#333333\" # noqa: N806\n", - "\n", - " # Validate required columns\n", - " if \"class\" not in df.columns or \"evo2_delta_score\" not in df.columns:\n", - " raise ValueError(\"DataFrame must contain 'class' and 'evo2_delta_score' columns.\")\n", - "\n", - " # Convert 'class' to binary labels: Assume 'LOF' = 1, anything else = 0\n", - " y_true = (df[\"class\"] == \"LOF\").astype(int)\n", - "\n", - " # Compute ROC curve\n", - " fpr, tpr, _ = roc_curve(y_true, -df[\"evo2_delta_score\"]) # Negative to align with previous logic\n", - " roc_auc = auc(fpr, tpr)\n", - "\n", - " # Set up the plot with NVIDIA theme\n", - " plt.figure(figsize=(9, 5), facecolor=BACKGROUND_COLOR)\n", - " plt.style.use(\"default\") # Reset to default to avoid any pre-existing style\n", - "\n", - " # Plot ROC curve\n", - " plt.plot(fpr, tpr, color=NVIDIA_GREEN, lw=3, label=f\"ROC curve (AUROC = {roc_auc:.2f})\")\n", - "\n", - " # Plot diagonal reference line for random guessing\n", - " plt.plot([0, 1], [0, 1], color=\"gray\", lw=2, linestyle=\"--\")\n", - "\n", - " # Customize plot appearance\n", - " plt.xlim([0.0, 1.0])\n", - " plt.ylim([0.0, 1.05])\n", - " plt.xlabel(\"False Positive Rate\", color=FONT_COLOR, fontsize=12)\n", - " plt.ylabel(\"True Positive Rate\", color=FONT_COLOR, fontsize=12)\n", - " plt.title(\n", - " \"Zeroshot ROC Curve\\nEvaluating the discriminative performance of Evo 2 predictions\",\n", - " color=FONT_COLOR,\n", - " fontsize=16,\n", - " loc=\"left\",\n", - " )\n", - "\n", - " # Customize grid and tick colors\n", - " plt.grid(color=GRID_COLOR, linestyle=\"--\", linewidth=0.5)\n", - " plt.tick_params(colors=FONT_COLOR)\n", - "\n", - " # Set background color\n", - " plt.gca().set_facecolor(BACKGROUND_COLOR)\n", - "\n", - " # Add legend\n", - " plt.legend(loc=\"lower right\", frameon=True, facecolor=BACKGROUND_COLOR, edgecolor=GRID_COLOR)" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [ { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAw8AAAHzCAYAAABmEyGdAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvc2/+5QAAAAlwSFlzAAAPYQAAD2EBqD+naQAAx2RJREFUeJzs3Xd4FOX6N/DvZtN7JfQaIIRAKAECaYiANMECxwYq6ovisXvQA3YFjuV4FOFYQFAsICKiSFNqCikkhDQCCb0EEkLapm22vn/kt3NYsoGdZJPZTb6f6/K6zDOzu3eGeydzzzxFplQq9SAiIiIiIroFO6kDICIiIiIi28DigYiIiIiIzMLigYiIiIiIzMLigYiIiIiIzMLigYiIiIiIzMLigYiIiIiIzMLigYiIiIiIzMLigYiIiIiIzMLigYiIiIiIzGIvdQDW4plnnsHRo0dFvebQoUOtFE3bunLlCmbPno3OnTtjy5YtUodzSzt27MDy5cuN2mQyGVxcXNCtWzeMGTMG999/P3x8fG76PhqNBjt37kRcXBxOnjyJyspKuLq6onv37hg7dizuueceeHt73zKe4uJi/P7770hLS8Ply5dRVVUFFxcXdO3aFWFhYbjjjjswaNCgZv2uhw8fxp49e5CTk4PS0lKo1Wp4enqiT58+GDt2LO64445b/p5ERERElsLi4f+MGTMGnTt3vuV+Bw8eRF1dHTp16tQGUbVPGRkZePbZZzF8+HCsWrWq2e/j4uKC8ePHAwB0Oh2KioqQm5uLkydPYufOnfj888/Ro0cPk689d+4c/vnPf+LixYuQy+UYPHgwRowYgaqqKmRnZyMvLw8//fQTlixZInyGKT/++CPWrFkDtVoNFxcXDB48GD4+PqitrcXp06exefNmbN68GQ8++CD+/ve/m/27VVRU4K233kJ6ejoAoEuXLhgxYgRcXFxQWlqK3NxcpKen4+uvv8aKFSswePBgs9+biIiIqLlYPPyfefPm3XKfzZs3Y9euXXB0dGx055vanpeXF15//XWjtjNnzuCZZ55BWVkZVqxYgX//+9+NXnf58mUsXLgQCoUC4eHhWLx4sVHhWF9fjzVr1mDjxo144403sGzZMsTExDR6ny+++AI//PAD7O3t8cwzz+Dee++Fo6Oj0T65ublYvXo1Ll68aPbvVV1djYULF+LChQvo1asXXnnlFQwbNsxoH5VKhV27dmHt2rUoLS01+72JiIiIWoLFg5kyMzOxcuVKAMDLL7/c7G4o1Lr69u2L++67D6tXr0ZaWhpUKlWjC/r33nsPCoUCgwcPxkcffdRou5OTE5555hkAwMaNG7Fs2TKEhYXBy8tL2Cc9PR0//PADAODdd99FbGysyXhCQ0OxYsUKZGVlmf07fPLJJ7hw4QK6dOmCL7/8Ep6eno32cXR0xKxZsxAdHY2qqiqz35uIiIioJVg8mOHq1at4/fXXodVqcffdd2PGjBlN7nvgwAH88ccfyM/PR3V1Nby9vTFixAg8/PDD6NOnj9G+1481+Pnnn7F582bs3r0bly5dQl1dndGYitTUVPzyyy/Iy8tDdXU1vLy8EBYWhgcffNBkIVNdXY0NGzYgISEBly9fhlarhaenJ7p27YqRI0di/vz5sLdv/M+v1+uxbds2/P777zh//jzkcjlCQkLwxBNPIDQ0tMnj88MPPyA1NRVXr16Fg4MD+vbtiylTpuDOO++EXC4X9r1+bMnRo0cRGRkpbLPUmIugoCAADWMaFAoF/P39hW1Hjx5FdnY2AOCll15qVDhc7//9v/+H3bt3o7y8HFu2bMFjjz0mbPv2228BAFFRUU0WDgYymazRk4OmFBYWYs+ePQCAZ5991mThcD1fX1/4+voKPy9duhS7du3CkiVLMH369Eb7G8aLTJ061eipzfXtzz33HNatW4dDhw6hpKQEoaGhmDdvHl566SX06tULGzZsMBmLRqPB3XffjbKyMnz77bfo37+/sK2+vh6//vor9u/fjwsXLkClUiEwMBDR0dGYO3euUWFGRERE1ovFwy2oVCq89tprKC8vx9ChQ/HCCy+Y3E+j0eCdd97B/v374ejoiIEDByIgIAAXLlzAX3/9hbi4OCxfvhwRERGNXqvX67FkyRKkpqYiLCwMvXv3xtmzZ4Xtq1evxvr16yGTyTBkyBAEBgbi3Llz2L9/P+Li4vDKK68YFTRKpRILFy7EmTNn4O3tjZEjRwp95S9cuICcnBzcf//98PDwaBTLsmXLsGfPHoSFhWHcuHE4efIk0tLSkJWVhVWrVjXqW3/8+HG89NJLUCgUwsVgTU0NMjIykJOTg/j4eHzwwQdwcHAA0DC2xNHREampqfD19cWYMWOE9zJncLI5ampqAAByubzRe8bHxwMA+vTpg+Dg4Ju+j5OTEyZMmIAtW7YgMTFRKB6qqqqEJwlTp061SMwGSUlJ0Gq18PDwQFRUlEXf2xyVlZV4/PHHUVVVhbCwMAQHB8Pe3h6jRo1Cp06dcP78eeTm5posJFNSUlBWVoaBAwcaFQ4lJSV4+eWXcfr0aXh6eiI4OBiurq4oKCjAhg0bcODAAaxatcqsMUdEREQkLRYPt/Dxxx8jLy8Pfn5+eO+990zerQeAtWvXYv/+/QgJCcE777yDrl27CtsOHDiAt956C2+//TY2b97c6KK9uLgYer0e3333HXr27Gm0LSUlBevXr4ejoyM++OADjB49Wtj2xx9/4P3338dHH32EkJAQ9O3bV/i8M2fOICIiAh988IFRzDqdDllZWXB2dm70OxQVFeHo0aP4/vvvhTi0Wi0+/PBDbN++HV9//TU++eQTYX+VSoXXX38dCoUCd911F1588UXhswoLC/H8888jNTUV69atw5NPPgmgYWzJ4MGDkZqail69ejUas2AJSUlJABoKlRv/vfLz8wEAISEhZr2X4anOyZMnodFoYG9vj4KCAuh0OqPtlnLixAkAwIABA4ye2LSVpKQkhIeHY/ny5XBzczPaNnXqVKxfvx47d+40WTzs2LEDADBt2jShTa/X480338Tp06cxY8YMPPfcc8L7ajQafPnll0LXMEO3QCIiIrJeXOfhJrZu3Yrt27fDwcEBy5cvN+r+cj2FQoFNmzYJA6mvLxwA4LbbbsOsWbNQVVWFP//80+R7PPnkk40KB6Chzz0A3HPPPUaFAwDceeediIyMhEajwebNm4X2srIyAMCoUaMaXTzb2dlh+PDhwpOAG7344otGccjlcixYsABAw7gPjUYjbDtw4ACKiorg7++P559/3uizunXrJswu9Msvv6C+vt7k51mKVqtFYWEhvvjiC+zZswedO3c2+ZSooqICAMye3tTQJUin0wljC8rLy4Xtlp4mVWx8lmZvb49XXnmlUeEA/K8o2LdvX6N/z/LyciQlJcHR0RGTJ08W2lNTU5GdnY3+/ftj0aJFRu9rb2+Pp59+Gn379kVGRgbOnDnTSr8VERERWQqLhybk5OTg008/BQA8//zzTfb3B4AjR46gvr4eQ4cORUBAgMl9hg8fDqBh9h1TTE0HqtFohP7519/NvZ6hu1JGRobQZrgbvmHDBuzatQsKhaLJ2K8nl8tNdqvy8/ODh4cHVCoVKisrhXbDZ06cONHk2IHx48fDw8MDtbW1wh1/SyoqKkJkZCQiIyMRExODv/3tb/jhhx8QEhKC9evXo1u3bi3+DL1eb4FIbceAAQOaPG7du3fHsGHDUF1dLXT/Mvjrr7+g0WgQFRVlNE7D8BRo/PjxJp/a2dnZCeNBcnJyLPRbEBERUWthtyUTrl27htdeew0ajQYzZszA3XfffdP9L1++DKBhBp7rBwCbcv1dawMfHx+T3YgUCgVUKhWAhnn+TTFc6JWUlAhtI0aMwNy5c7FhwwYsXboUMpkM3bt3x9ChQxEdHY3IyEjY2TWuG/39/ZvsluXm5oaqqiohHqDhON0sNplMhq5duyI/P98oPku5fp0HtVqNc+fO4dSpU8jLy8OHH36Id999t9FrDANzTf07mGLYz87OTuhudv1TgfLycgQGBrbk1zBiGKNhbnyWdqtxB9OnT0dmZiZ27NiBSZMmCe07d+4Utl/P8N1Ys2YN1qxZc9P3Njx1ISIiIuvF4uEGGo0Gr7/+OkpLSxESEoKXX375lq8x3J3u3r07hgwZctN9e/Xq1ajNycmpecHexMKFC3HXXXchMTER2dnZyMnJwY4dO7Bjxw4MGjQIK1euhIuLi9FrZDKZxeNoTabWeTh48CDefPNN7Nu3D8OGDcM999xjtH3gwIHIzs7GsWPHzPqMvLw8AA0zOBkKq/79+8POzg46nQ7Hjx+3aPEwcOBA7N69GwUFBdBqtRYf93CrJym3ysUJEybgk08+wZEjR3D16lV06tQJ+fn5OHXqFAICAhp1rTN83tChQ2/5JOjG2ciIiIjI+rB4uMEnn3yCnJwc+Pj4YNmyZTedytPAsNp0z549LToA2NPTE46OjlCpVLh8+bIwBen1DHd2TXWX6tKlC+bMmYM5c+YAaJgZ6Z133sHx48fx448/4oknnmhRfIYxIIYYTLlZfK1h/PjxmDt3LtavX4+vv/4akydPhru7u7A9Ojoamzdvxrlz53DixImbzrhUX1+P/fv3A4DRzEeenp4ICwvD0aNHsWvXrpuuQC3WuHHjsHLlSlRVVSExMfGW08DeyDCWpba21uT2oqKiFsXn7OyMCRMmYPv27di1axceeeQR4anD1KlTGz3RMnw3oqOj8eCDD7bos4mIiEh6HPNwne3bt+O3336DXC7H0qVLhQufWwkPD4eDgwOOHj1q0e4m9vb2GDp0KID/dQsxFTPQ0FXpVgYNGiTciT958mSL4zN8pqkBtAAQFxeHqqoquLq6YuDAgUK74QJXq9W2OAZTHn74Yfj5+aGyshI//fST0baRI0cK081+/PHHRt2wbrRmzRpUVFTAzc2t0ROMhx9+GACQmJiIuLi4m8aj1+vNXiSue/fumDhxIgBg1apVtxyvUl5ejvPnzws/G4q069uujyMlJcWsOG7GMM5m165dUKlUwroUpsblGMbQHDhwoMONHyEiImqPWDz8n+PHj+Pjjz8G0LA4l7mLegENM/LMnj0bdXV1eOWVV3D69OlG+6hUKiQkJJi8qLuZ+++/H0DDzE/p6elG23bs2IHExETY29sLTxeAhov2zMxMYTpRA41Gg9TUVAC37ttujttuuw2BgYG4du0aVq5caTQT0+XLl7Fq1SoAwOzZs426wxgucC9evGj0GktxdnbG/PnzAQA///xzowvwN998Ex4eHsjLy8OiRYtQXFxstL2+vh7//e9/sXHjRshkMixevLjR7EejR4/GAw88AAB466238NNPP5ksRE6cOIEXX3xRmDXLHC+++CK6d++Oy5cvY+HChSYLD7Vaje3bt+PRRx81yqnw8HAAwO7du43WCtFoNPj8889x/Phxs+NoypAhQ9CzZ09cvHgRn3/+OSorKzF06FD06NGj0b7R0dEYNGgQ8vLysGzZMpPFtUKhwNatW1slF4iIiMiy2G3p/yxZsgQqlQouLi7Iz8/H0qVLb/maefPmCWMYnnrqKVy7dg179uzBo48+iqCgIHTt2hVyuRwlJSU4efIk6urq8PHHH5sc99CUsWPH4pFHHsH69evxwgsvCIvEXbhwAfn5+ZDL5Vi0aJGwxgPQsIry5s2b4e3tjf79+8PHxwe1tbU4duwYysvLERAQgIceekj8QbqBo6Mjli1bhpdeeglbt25FcnIyBg8ejNraWhw5cgQqlQpjxowxWpkZaChcgoODceLECcybNw/BwcFwdHSEt7c3Fi5c2OK4gIZpbDdu3IjCwkJs3LhRWGcCaLi7//nnn+Of//wn0tPTMWfOHAwePBiBgYGoqqpCTk4Oampq4OLigiVLluC2224z+RnPPPMMPDw8sG7dOqxcuRJr165FSEgIfHx8UFdXh9OnT+PKlSsAgLlz55odu6enJ7788ku88cYbOHr0KJ5++ml07doV/fr1g7OzM8rKypCXl4e6ujq4ubkZTSFsGBSfkJCAxx9/HEOHDoWTkxMKCgpQU1ODOXPmGE3r21zTp0/HF198IbxXU6uu29nZ4V//+hcWLVqEXbt24eDBgwgKCkJgYCDUajUuX76MM2fOQKvVYtq0aU0O2CciIiLrwL/U/+fq1asAgLq6Ouzatcus10ybNk0oBOzt7fH222/jjjvuwB9//IG8vDycOXMGLi4u8PPzQ2RkJKKiokQ90TBYsGABhg4dil9++QV5eXk4duwYvL29MWHCBDzwwAONFjybNm0anJyckJ2djXPnziEzMxNubm4IDAzE3/72N8yaNUuYdailBg0ahG+//RY//PADUlJSEB8fDwcHBwwYMABTpkzBnXfeafKCcNmyZfjyyy+RkZGBffv2QavVonPnzhYrHuzt7bFgwQK89dZb+OWXX3D//fcb/c59+/bFjz/+iJ07d+LgwYPCLE0uLi7o0aMHxo4di3vvvfeW6y088sgjuOOOO/Dbb78hPT0dJ0+eRHV1NVxcXNC1a1dERUVh2rRpGDBggKj4fXx8sGrVKqSkpGDPnj3Izc1Feno61Go1vLy8EBoainHjxmHKlClGU6MCwLvvvotvv/0We/bswdGjR+Hh4YHw8HA88cQTZnefupUpU6Zg9erV0Gq1cHFxabLAAhqeNK1evRo7d+7Evn37cPr0aeTl5cHT0xP+/v6YNWsWoqKiWmXiACIiIrIsmVKpZEdkIiIiIiK6JY55ICIiIiIis7B4ICIiIiIis7B4ICIiIiIis7B4ICIiIiIis7B4ICIiIiIis7B4ICIiIiIis3Cdh+vce++9KCoquuV+S5YswfTp09sgImNXrlzB7Nmz0blzZ2zZsqXNP1+MHTt2YPny5Zg6dSpef/11qcNpNls65jeLNTIyEgBw6NAhKUK7qaVLl2LXrl1t9r165plncPToUaxcuRIjRoxo9c9rCWv+d7NmiYmJ+PHHH3Hq1CnU1tYCgE38e1PLlJSU4IsvvkB6ejoqKiqg1WrN+hu0du1arFu37pbvP3z4cKxatcpS4VpMQUEBUlJSkJ6ejjNnzkChUMDV1RV9+vTBxIkTMWvWrA6xAKfhGu6XX35Bly5dhPa2POcbcumxxx7D448/3qqfJaX2n03NMHToUHTr1q3J7d27d2/DaKyPLV1Q34otXUhS+8Lcax0FBQV47bXXoNfrMWLECPj5+UEmk8HPz0/q0KgV6fV6LFmyBHl5eejduzdGjBgBe3t7DB061Oz38PX1xZgxY5rcblgU1ppoNBrMnz8fAODi4oJBgwbB19cXV69exbFjx5CdnY3du3fjP//5Dzw8PCSO1rZlZGTg2Weftdoisi2xeDBhxowZkjxZaE9iY2MRGhoKNzc3qUMhABs2bJA6hCY99dRTmDdvXptd3L3++uuor69HYGBgm3xeS1jzv5u1SkhIgEajwcMPP4wnn3xS6nCojRQVFSEvLw+BgYFYv359s+609+rVyyaflA8cOBBz585FVFQUHB0dhfbTp0/jxRdfRF5eHlauXIklS5ZIGKV02vKcf++992LixInw8vJq9c+SEosHahXu7u5wd3eXOgz6P9Z4x8zA398f/v7+bfZ5nTt3brPPailr/nezVsXFxQD4hLijMfy7d+3atUN00TGwt7dvsstVv3798Pe//x3vvvsu9u7di1deeaVDHRuDtjzne3t7w9vbu80+TyodL4ss6Pz583jwwQfh4eGB33//HU5OTib3e+yxx5Cfn4/3338f0dHRAICzZ89i3759SE9Px5UrV1BRUQFXV1cMGDAAM2fOxO233252HOZ0I2qqL6DYOAz904GGOz2GPtkGhr7ZTY15uP6x36effoqffvoJu3btwuXLl+Hs7Ixhw4bhySefRO/evU3+HllZWVi/fj2OHTsGrVaLXr16Yfbs2Zg6daqo/uGGOAyu/3/A9LgWvV6Pbdu24ffff8f58+chl8sREhKCJ554AqGhoSY/p76+Hr/++iv279+PCxcuQKVSITAwENHR0Zg7d26z7k4cOnQIGzZsQH5+Puzs7NCvXz88+OCDCAoKavI1TR2ba9eu4YcffkBKSgqKi4shk8ng5eWFHj16ICIiAg8++GCj9yopKcGmTZuQmpqKK1euQK/XIyAgAKGhoZg1axaGDBli8nN37NiB33//HefOnUNNTY2Qi02Nebi+7+jMmTPx9ddfIzk5GVVVVejWrRvuu+8+3HnnnQAavovffPMNjhw5gurqavTs2RPz5s3DxIkTG8XfVHeh6+MYPnw4vv76a6SlpaGqqgoBAQGYNGkSHn30UaM7ewBQU1ODffv2ITk5GWfOnMG1a9cANFzEREVFCecIAzG5d+O/W1VVFWbOnAmNRoNff/0VAQEBjX4/AHjttddw8OBBPP/88/jb3/5mtO3AgQP4448/kJ+fj+rqanh7e2PEiBF4+OGH0adPH5Pv15Tr49u2bRt+++03nD9/Hvb29ggNDcX8+fOb/G5oNBrs3LkTf/31F06dOgWlUgl/f3+MGTMGDz/8cKO7hNefO/7973/j+++/x4EDB1BUVAQfHx9MmzbN6CJq+fLlWL58OYDGfdXPnz+PH374AUeOHEFZWRmcnZ0xYMAAzJo1y+S59/pcnD59Or755hscPnwYpaWlmDx5Ml5//XWj/AkNDcXXX3+NjIwMKJVK9O7dG48++qhw/j927JhwHqutrUX//v2xYMEChIeHN/rsvLw8HDhwAEePHkVxcTEUCgU8PDwQEhKCOXPmYNSoUY1ec/359+WXX8a3336LAwcO4OrVq/Dw8MDo0aPx1FNPNZk/Yr7jQOuc5/Ly8rBx40ZkZWWhsrIS7u7uGDx4MGbPno3Ro0cL+xn+/hkcPXrU6O/SjX/zLGXBggU4duwY3nnnHZPnGcNnf/LJJ4iJicG//vUvo2179+7Ftm3bUFBQAKVSCV9fX4SHh2Pu3Lno2bOnxeLs378/gIZ/o4qKCrNv1NyY82vWrBHOh4GBgZg0aRLmzp3b6Lrn+tx77rnnsG7dOhw6dAglJSUIDQ01+h6mp6djy5YtOHbsmPBvPHToUDz00ENNnjfOnj2LtWvXIiMjA3V1dejWrRumTZuG++67r8nf5VZdRI8cOYKtW7ciNzcXFRUVcHNzQ+fOnTF27FjMmTMHXl5ewnsAjXPs+muvW415SE1NxS+//IK8vDxUV1fDy8sLYWFhePDBBzFo0KCbxu7u7o5vvvkGWVlZqK2tRbdu3TBjxgzcf//9kMlkRq9TqVTYvHkz9u3bhwsXLkCtVsPT0xOdOnXCiBEjMG/ePHh6ejZ5zG6FxUML9OrVC6GhocjNzUVCQoLJE8jp06eRn58PX19fjB07Vmj/6aefsH37dvTq1Qv9+vWDu7s7iouLkZGRgfT0dBw7dgzPPfdcq/8OYuMYOnQo6urqcPDgQbi4uGD8+PHN+lyNRoOXX34Zubm5GDZsGHr37o3jx48jPj4eR48exTfffNPohL93716888470Ol06NevH/r27YuSkhIsX74c586dE/X5fn5+mDp1KlJTU1FWVoYxY8bA19dX2G7qruWyZcuwZ88ehIWFYdy4cTh58iTS0tKQlZWFVatWYfDgwUb7l5SU4OWXX8bp06fh6emJ4OBguLq6oqCgABs2bMCBAwewatUqUXdFfvrpJ6xcuRIAEBISgm7duuHixYv45z//ifvvv1/UMSgtLcXjjz+Oa9euITAwEGPGjIGjoyOuXbuGkydPIj8/v1HxkJ6ejtdffx1VVVXw8fFBeHg4HBwccOXKFezZswcAGl1YAMB//vMfbN26FaGhoRg3bhwKCwsbneyaUlxcjMcffxz29vYICwtDRUUFsrKy8P7776O6uhpDhgzBiy++CH9/f4wYMQJFRUXIzc3FW2+9BQBN/mFvysmTJ7FixQp4eHhg2LBhUCgUyMnJwfr163H27NlGFwGnTp3CBx98AG9vb/Ts2RMDBw5EVVUV8vPz8d1332H//v1YvXq1cAHVnNwz8PDwQGxsLPbs2YPdu3dj3rx5jfaprKzEoUOH4ODggDvuuENo12g0eOedd7B//344Ojpi4MCBCAgIwIULF/DXX38hLi4Oy5cvR0REhKjjBQCfffYZfv75ZwwZMgTR0dE4ffo0UlJSkJaWhvfeew+xsbFG+9fU1ODVV1/F0aNH4eLiguDgYHh7e+P06dP47bffcODAAXz66acYMGBAo8+qr6/HM888g3PnziEsLAxBQUGorKxE//79MXXqVGRnZ6OwsNBo7Nr1T3CSkpLw2muvQaVSoWfPnoiNjUV5eTkyMzNx5MgRHD58GIsXLzb5e168eBHz58+Hg4MDhgwZAr1e3+guY0FBAT755BMEBAQgPDxcyMfFixfjvffeg1wuxxtvvIG+ffti5MiROH/+PI4dO4aXXnoJK1euRFhYmNH7ffXVV8jIyECfPn0wcOBAuLi4oLCwEIcOHcKhQ4dMFojXH+ennnoKxcXFGDp0KPr27Yvc3Fzs3r0bmZmZWL9+faMnxGK/461xntu2bRs++ugj6HQ6DBgwQPheG37nxx9/HI899hiAhn7+U6dORVlZGVJTUxuNW3BxcTH7c8WYPn06jh07hp07dzZ5jtm5c6ewr4Fer8fSpUuxe/duyOVyDBs2DD4+PsjPz8eOHTuwb98+LFu2rFnfQ1MuXboEAHBwcGjWxeLly5fx2GOPCbHW19cjIyMD69atQ3p6Oj799FOTN04rKyvx+OOPo6qqCmFhYQgODjZ66rFq1Sps3LgRdnZ2CA4ORlhYGIqKipCQkIBDhw7h1VdfbXQDLysrCy+//DLq6urQtWtXjBo1ChUVFfjqq69w7Ngx0b8bAHzyySf45ZdfADQUWmFhYaiursaFCxfwzTffYMSIERgxYoTw99FUjpn7pGH16tVYv349ZDIZhgwZgsDAQJw7dw779+9HXFwcXnnlFcyYMcPka1NTU7Fp0yZ069YNo0aNQmlpKbKzs7Fq1SoUFxfjhRdeEPbV6XRYtGgR0tPT4ebmhrCwMHh4eKC8vByXLl3Chg0bMGnSJBYPUpo+fTpyc3ObPIHs2LEDAHDHHXcYfXGmTJmChx9+uNHA7PPnz+OFF17Apk2bMHHiRISEhLRq/GLjmDlzJkaNGoWDBw/Cy8ur2f1Dc3JyMGDAAPz8889CX/f6+nosXrwYqamp+P777/HKK68I+5eUlOD999+HTqfDCy+8gDlz5gjbMjMz8Y9//EPU5xv6tj7zzDMoKyvD3LlzbzpotaioCEePHsX3338v3BXSarX48MMPsX37dnz99df45JNPhP31ej3efPNNnD59GjNmzMBzzz0njP/QaDT48ssvsXHjRixbtkwoBm7l1KlT+Pzzz2FnZ4d3330Xt912m7Dtzz//xHvvvSfqGPz++++4du0aZs2ahUWLFhldzGs0GmRmZhrtX1xcjNdeew3V1dWYO3cunnjiCTg4OAjby8vLceHCBZOftXv3bnzxxRdN3k26mR07duCuu+7Ciy++KHyHEhMT8eqrr2LdunXw9PTE3Llz8fDDDwu/w88//4wVK1ZgzZo1oouHzZs345FHHsHjjz8OuVwOADhz5gwWLFiA+Ph45ObmGv0eXbp0wYoVKzBixAjY2f1v9mulUomPPvoIu3fvxtdff42XX34ZgPjcu9H06dOxZ88e7Nq1y2Tx8Ndff0GtVmP8+PFGd3zXrl2L/fv3IyQkBO+88w66du0qbDtw4ADeeustvP3229i8ebPogZW//fYbVqxYgZEjRwptP/74Iz7//HMsX74cQ4cOhY+Pj7Dto48+Eu7eLV682Gjbpk2b8Nlnn+HNN9/Ejz/+KPwbGOTl5SEoKAibNm1qNE4mJiYGS5cuRWFhocmxa2VlZXjnnXegUqmwYMECo5w5fvw4XnzxRWzfvh2DBw/GzJkzG/2ee/bswR133IF//vOfjZ5AGfzyyy+N3nvz5s349NNP8dlnn6Gurg6LFy/GlClThNesWLECP//8M9atW4cVK1YYvd/999+PN954o9Ed49zcXLz00kv473//i9tuu83kU4T4+HiMGTMGn3/+uXD+USgUeO6553Dy5En8+uuvePjhh4X9xX7HW+M8d/r0afz73/+GXq/HG2+8YXSckpOTsXjxYqxduxahoaEYPXo0vL298frrryMjIwOpqaltNm5h4sSJ+Oyzz5CWloaSkpJGx//UqVPCjcPrC4HffvsNu3fvhre3Nz755BOhQNbr9Vi3bh3WrVuHt99+Gxs3bjT6XjSHXq/Hjz/+CAAYN25ckzl7M7t370Z0dDTeeecdoUi4evUqnnvuOWRnZ2PdunVYuHBho9clJSUhPDwcy5cvbzT2cdu2bdi4cSO6d++OZcuWGT01z8zMxKJFi/DRRx9h6NCh6NGjB4CG64N33nkHdXV1+Nvf/oZnnnlGODecOnUKzz//PCoqKkT9bps3b8Yvv/wCLy8vvPfee0bnL6DhXGM4x8ybNw+DBw9udo6lpKRg/fr1cHR0xAcffGD09OyPP/7A+++/j48++gghISHo27dvo9f/8MMPWLRoEe666y6h7ciRI3j++efx66+/4sEHH0SnTp0AANnZ2UhPT8eAAQOwatWqRsf/+PHjLR7/wXUeTFi+fDkiIyOb/K+qqkrY9/bbb4ezs7NwArmeRqPBn3/+CQCYNm2a0bbhw4ebnNGpV69eePTRRwE0/EFvbVLFIZPJsGTJEqM//k5OTsJjvrS0NKP9t2/fjrq6OoSGhhoVDgAwbNgwoy9Ua3nxxReNHifL5XIsWLAAQMMJT6PRCNtSU1ORnZ2N/v37Y9GiRUZfXnt7ezz99NPo27cvMjIycObMGbM+/5dffoFWq8Vtt91mVDgADcVpVFSUqN+nvLwcADBmzJhGTwHs7e0bdaH46aefUF1djcjISCxcuNDoogIAfHx8Gt01NXjggQeaVTgAQGBgIJ577jmj4jsqKgpBQUGora2Fr6+v0YUaANxzzz3w9PTEpUuXzJp++XoDBw7E//t//8/oorVv377CXfwbc7NTp04IDw83KhwAwNnZGYsWLYJcLsf+/ftFxXAz4eHh6Ny5M86fP4/c3NxG2w03LK6/cFYoFNi0aRMcHR2xfPlyo8IBAG677TbMmjULVVVVwjlLjFmzZjX6w/vQQw8hODgY1dXV2LZtm9B+7tw57N27F/7+/njrrbcaXSDdd999GDt2LC5evIiUlBSTn/fiiy82a4D9tm3bUF1djYEDB+KRRx4xyplBgwbhkUceAdD0QHVPT0+89NJLN70ICwkJaZSPd999Nzw9PXH16lWMGjXK6IIYgHCuzcrKMjqPAMDYsWNNdjUJDQ3FPffcA41Gg4SEBJOxuLi4YMmSJUbnH0OxDTQ8Zbie2O94a5znfv75Z2i1WsTExDQ6TmPHjhWKutaYTMBQ0Db136ZNm4R93dzcMH78eOh0OqEr7/WaunG4ceNGAA3/5tc/WZPJZHjssccQFBSEqqoqo+9Mc61btw65ublwcXExeYFvDicnJyxatMjo6UKnTp2E7pZbt25FfX19o9fZ29vjlVdeaXThqtPpsHbtWgDAO++806i77bBhw/Doo49CrVbj999/F9oPHjyI4uJiBAYG4u9//7vR+TkoKMioCDaHRqPBt99+CwB45ZVXGp2/gIbvsqUGWRv+3e+55x6jwgEA7rzzTkRGRkKj0WDz5s0mXx8bG9voOmfkyJEYM2YMtFotMjIyhPaysjIAQFhYmMlJawYNGtTiAd188mDCraZqvf6EajiB7N69G7t27TJK4KSkJFRUVDRZSdbW1iIlJQUFBQWorKyEWq0G0NCdBECTd3EtTYo4AgMDhb6Y1zOMdTD0Gzcw3AWfPHmyyfebPHmy8OVsDXK53ORjZD8/P3h4eKCqqgqVlZXCBU1SUhIAYPz48SYHqNnZ2WHYsGE4c+YMcnJyTObHjQz9La/vinK9qVOnNnkRYYqhf+UXX3wBvV6P0aNHw9XVtcn9U1NTATRcKIp1Y7EjxogRI0w+Fu/evTtOnTqFiIgIk8VPly5doFAocO3aNVFdJiIjI012qTLk5o03CQxycnKQlZWF4uJiKJVK6PV6AA3ni4qKCigUihY9JjaQyWSYOnUqvvnmG+zcudOoKCsoKMDJkyfh5+dn9Fj9yJEjqK+vR3h4eJP93IcPH45ff/0Vubm5Rn3IzTF16lST7VOmTMGJEydw9OhR4cI8OTkZer0eERERTc7GNnz4cCQnJyMnJ6fRuCofHx8MGzZMVHwGhu9QU/HOmDEDq1atwsWLF03eTQ4PD7/lRBC3ykdT5xEvLy94enpCoVAYnUcMKisrkZSUhDNnzqCqqkooMAxdUpo6RwcHB5ssPJrKZbHf8dY8z914w81gxowZ2LJlC7KysqDVahs9mWqJW03VeuNYvGnTppn826/RaPDXX38J8RpcvXoVhYWFwmtvJJPJMG3aNHz22WfIyMgQvjPNsWvXLnzzzTews7PDkiVLhDv4Yo0ePdpkoR4ZGQkvLy9UVlaioKCgUXfVAQMGmLyOKigowLVr19CtWzcEBweb/EzDk9icnByhzZAXEyZMMJlrU6dOxWeffWb275Wfn4+Kigp4e3s36lZpaRqNBtnZ2QBunteHDh0yKgKud+N50KBXr15ISUkx+i4PGDAAcrkc27dvR48ePRAbG2vxSUlYPJggdqrWGTNmYPfu3di5c6fRCcRw58FUsiQmJmL58uWorKxs8n1rampERN08UsXRVDVvuJhQqVRG7VevXgWAJge+tcaAuOv5+/s3OUuFm5sbqqqqjGK+fPkyAGDNmjVYs2bNTd/b3Eetlj4GU6ZMQVpaGv766y+89tprkMvl6N27N4YOHYrbbrut0Z0Ywx385swA1JJ/n6ZyxdCXuanthkLoxlxq7uc19X7l5eVYsmSJ8MehKbW1tRYpHoCGpwrffvst9u3bh+eff14orgx9rKdOnWp0UWXIx/T09Cb/CBkYnkiJceOTjBvbr//DZohl+/bt2L59+03f19R3oyW5ZIijqXg9PDyEi3hTxYM5n32r/GmqkHV1dYVCoWiUX9u2bRO6OzWlqXO02POs2O94a5znbvVvZLggValUUCgULe7acz2x3VFGjBiBrl274sKFC8jJyREuoA8dOiTcOLy+4DD8bl5eXk0Wzobf78YbaGLs379fGJv16quvYsKECc1+r5vlfOfOnVFZWSn8bbpxmymGnCksLLzluej6nDEcu6bi8fT0hLu7O6qrq2/6ngaGXO/Zs6fZ4++a6/rvdVPxG/7dm7o51dTxNPVd7t69O5599ll8/vnn+M9//oP//Oc/6Ny5szDmcMKECY2eKorF4sEChg0bJgxcNZxAysvLkZycDEdHx0Z9rktKSvDmm2+ivr4eDz30ECZPnowuXbrAxcUFdnZ2SE1NxUsvvWTRGA13QaWOw+DGLh4t1dpffrHvbzjet3qKBUD0DDeWYmdnh7feegsPP/wwkpKSkJOTg5ycHGzduhVbt25FZGQk/vWvf1nkzl5TM5GZG+fNWPrfXuz7/etf/0J2djZCQ0Px+OOPo3///vDw8BCKzZkzZ6K0tNTkd7C5unTpghEjRuDIkSOIi4vD5MmTodFohAGtN96wMHx29+7dTQ5ov15rTA97/e+u0+kANAxOvNkMYQBMjvlqSS61lDmffav8EZNfJ06cwIcffgg7Ozs8/fTTiIyMRGBgIJydnSGTyfD777/jww8/bDK3Wvu8aAvnudZkeFLw9ddfY+fOncJ3y1TXwbZy8OBBvP3229DpdDcdgGtJpvKvqe+K4fvv5+fXqPvOjTrClKfmEvtdnjNnDm6//XYkJiYiKysL2dnZ2Lt3L/bu3Yu1a9fi888/b9HTCBYPFmA4gaxZswY7duzAkCFD8Oeff0Kr1WLChAmNBh8mJiaivr4eMTExePrppxu9n+FRtLkMFym1tbUmt2s0GpN3MSwdR2syzArTVP/1K1eutHFEN2cYuBQdHW1yutPmCAgIQGFhIYqKikw+/hfbt9+gT58+wh92vV6PI0eO4O2338ahQ4ewe/du4Q9gYGAgLly4gPPnz3MO/f9TV1eHlJQU2NnZ4d///nej73pdXZ3Q/9TSpk+fjiNHjmDHjh2YPHkyEhMTUVFRgSFDhjQqAAz52LNnz1YZTHr58mWTMyMZvpfX38E3xDJkyBBhEHlbCQgIwPnz54W7nzeqrq6GQqEQ9pXagQMHoNfrMXv2bDz00EONtl+8eNGinyf2O96a57nCwkKT5znDv52jo6NVrJg8depUrFu3Dvv27cMLL7yAmpoapKSkwMnJqdGNQ0NOVVZWoqamxuTTB8Pv15wLu/j4eLz11lvQ6XT4xz/+YXLQv1g3+9tq+JtjyANzGJ6GeXp6ijoXGY5HU/FUVVWZ/dTh+jguXLgAvV7fqoW2p6cnHB0doVKpcPnyZZM3TQz/7pY87/j6+mLmzJlCHpw/fx7Lly9Hbm4uvvjiC7zxxhvNfm8OmLaQadOmwc7ODvv374dSqbzpnQfDHydTj6H0er1w99BcPj4+cHBwgEKhMNnlIDU1FVqt1mJxGIoVU+/ZWgx9nJuKSewxM2it38XQr9nwx98Shg8fDgBCX9obmRq0J5ZMJkN4eDgmTZoEoGHaUgNDX2BLDORrL6qrq6HVauHm5mbyQubPP/9s8t+/pbk3fvx4uLu7IyMjA8XFxUKXJVPdJA3TbR49erRZ3ZJupalB1ob262eTMkxZfejQIZMDLVuT4TvU1HfF0I2qR48eVlE83OwcXV9fj7i4OIt+ntjveGue5wz5fCPD39awsDCrWPCsc+fOGDlyJGpqanDw4EHhxmFsbGyj8TGdOnUSntCY+v30er3QLmYGNqDhZuAbb7wBrVaLf/zjHxabROTw4cMmzxlJSUmorKyEq6srBg4caPb7DRo0CN7e3jh37pzZg+iB/+XF/v37G00qADTMCiWGYXroiooKxMfHm/UaQ1cfsedse3t7DB06FEDTeW0494j9dxejV69ewmQJp06datF7sXiwkE6dOmHUqFGoqanBl19+iTNnziAwMNDkCH5DH8iDBw8aPRHQarVYs2aN0SAhcxjmvwca5hE2PBYEGi7+/vOf/5h8XXPjMBQrZWVlwh+31jZjxgw4OzsjOzu70UJ42dnZ+PXXX5v1voY7JmfPnm1xjNeLjo7GoEGDkJeXh2XLlpk8+SoUCmzdutXkidCUe++9V5i558aLhr1794oaLA00XECdOHGiUXtNTY0wOO36i5b7778frq6uSExMxOrVqxvFXV5ejqysLFEx2DpfX19hwPyNf7xyc3Px5ZdfNvnaluaek5MTbr/9duh0OmGhP2dnZ5OLnPn6+mL27Nmoq6vDK6+8gtOnTzfaR6VSISEhAefPnxcdy9atWxsN9Pvpp5+Ql5cHV1dXo64TAwYMwPjx41FcXIwlS5aYvJNYV1eHP//80+JPbWbOnAk3Nzfk5+dj/fr1Rhe8BQUFWL9+PQBY7C56SxmeIO3atctoXEN9fT0+/vjjJp+gNJfY73hrnOf+9re/QS6XIyEhoVFRmpqaKszA88ADD4j99VqNIb937tx5yy5Lhri//fZbo5szer1eaPPw8BD11CApKQmvv/46tFpto+k8W6q+vh4fffSRUaFfUlIiLPZ21113iepKaG9vj/nz50Ov12PJkiUm/2ZotVocOXLEaDY5w3TExcXF+PLLL42uc86cOSPMnCQmDsMY1Q8//LDR1ORAw5Sm14/nMNxQuHjxotn5bGBYh2nr1q2NZjnbsWMHEhMTYW9v32g2yeY4cuQIkpKSGsWo1+uFBUdbOouU9GW7Fdq+fbtw8WTK6NGjTc76M23aNKSmpgpTbRmeRtwoMjISAwcORH5+Pu6//34MHz4czs7OyMvLw7Vr1zB37lz88MMPomJesGABsrKysG3bNhw9ehRBQUEoKSnBiRMnMGnSJBw9erRRt5bmxmFvb4+oqCgcOHAAjz76KIYOHSqcPJpaXKmlOnXqhFdeeQVLly7Ff/7zH2zbtg19+vTBtWvXkJWVhfvuuw8bN24UfSdq/Pjx2LFjBz7//HOkp6cLg+9mzJhxy77hN2NnZ4d//etfWLRoEXbt2oWDBw8iKCgIgYGBUKvVuHz5Ms6cOQOtVotp06aZFfeAAQPw5JNP4vPPP8eSJUuEReIuXbqE48eP47777jOaSvBW4uLisHTpUvj7+wv99KuqqpCTk4Pq6mr07dtXWMEZaCgkli5ditdffx3r16/HH3/8gdDQUNjb26OoqAgFBQWYNGlSk9O1tkdyuRzz58/HZ599hvfeew9btmxBt27dUFxcjJycHNxxxx3IzMw02aXMErk3ffp0/P7770LxPGnSpCYHYj711FO4du0a9uzZg0cffRRBQUHo2rUr5HI5SkpKcPLkSdTV1eHjjz8WPe5h1qxZeO655xAWFoaAgACcOXMGp0+fhlwubzQlM9CwinZ1dTVSUlLwwAMPICgoSBhIeOXKFZw6dQpqtRobNmwwWkCvpXx9ffHWW2/h9ddfx+rVq/Hnn39iwIABKC8vx9GjR6HVajF9+nSLdPewhOnTp2Pz5s0oKCjAnDlzEBYWBjs7O2RlZaG+vh5z5sxpcmrH5hD7HW+N81y/fv3w8ssv49///jfeffddbNq0CT179hS+U3q9Ho8//vhNZ0VqrvPnz2Pp0qVNbnd2dja5plB0dDQ8PDyEi8IuXbqYvHEINFxsGxbqe/zxxzF8+HBhkbgLFy7AycnJ5BTGTSkvL8drr70GtVqNTp06CWPXTHnmmWdEjyOYMmUKkpKShPwzLBJnmDr9iSeeEPV+ADB79mwUFxdjw4YNePrpp9GnTx90794dTk5OKC0txalTp1BVVYV//OMfwmxyhuPyj3/8Axs3bkR8fDwGDRqEyspKYYrd/Px8Ud13//a3v+HChQv47bff8Pe//x0DBgxAz549UVNTI3RvXLlypXCjp3PnzggODsaJEycwb948BAcHw9HREd7e3recCnfs2LF45JFHsH79erzwwgvCInEXLlxAfn4+5HI5Fi1aZNaMZLdy6tQpfPbZZ3Bzc8OAAQPg7++P+vp6FBQUoKioCO7u7s36d7seiwcTsrOzbzpziru7u8niISYmRpipwzAOwhR7e3usWrUK33//PQ4ePCisAjhkyBAsW7YMtbW1oouHwYMHY9WqVVi7di2OHTuGpKQk9OjRAy+88ALuuusuk1MvtiSOV155BZ6enkhJScGBAweECre1igegYYrSTp06Yf369cjLy8OlS5fQq1cvvPrqqxg1ahQ2btwoeu7icePG4dVXX8Vvv/2GI0eOQKlUAmgYANiS4gFouEuxevVq7Ny5E/v27cPp06eRl5cHT09P+Pv7Y9asWYiKihJ11+ahhx5Cz549sWHDBpw8eRJnz55FUFAQli5diuDgYFHFw/33348uXbogNzcXBQUFwlSivXv3xqRJkzB9+vRGq7OOGTMG33//PX766SekpqYiJSUFcrkc/v7+mDJlilGx0VHcd9996Nq1K3788UecO3cOZ8+eRa9evfDyyy83+d0DLJN7gwcPRp8+fYSnFzcboGlvb4+3334bd9xxB/744w/k5eXhzJkzcHFxgZ+fHyIjIxEVFdWsaVCff/559OzZE7///juOHz8Oe3t7RERE4NFHHzX5u7i5ueGTTz7Bvn378OeffyI/Px8nT56Em5sb/Pz8MHnyZERFRd1yEG5zREZG4ptvvsEPP/yAI0eO4MCBA3BxcUFYWBhmzZolelHB1uTh4YG1a9di7dq1wvfN09MTo0ePxmOPPdYqT/rEfsdb4zw3a9YsBAUFYePGjcjOzsapU6fg7u6OsWPHYs6cObccaNtcZWVlN+3+6e7ubrJ4cHJywqRJk4QifurUqU32oZfJZHjjjTcQERGB33//Hfn5+airq4Ofnx+mTZuGuXPniirelUqlMNPO1atXbxr/448/Lrp46Nq1K9auXYvVq1fjyJEjqKqqQmBgICZNmoS5c+c2ewKDv//974iJicGvv/6K7OxspKamwt7eHn5+fhg+fDjGjRvXaArV4cOHY82aNfj6669x9OhRxMfHo2vXrnjiiSfwwAMP4L777hMVg0wmw6JFixAdHY3ffvsNx44dw5kzZ+Du7o6uXbti6tSpjcYnLFu2DF9++SUyMjKwb98+aLVadO7c2ax1NBYsWIChQ4fil19+QV5eHo4dOwZvb29MmDABDzzwgMUWBY6MjERNTQ2ysrJw8eJFHDt2DE5OTggMDMTcuXNx7733ihqnYopMqVRabgoQIons2rULS5cuRWRkJD788EOpwyHqEAxTLRoehRNR+7B2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9+7d+PTTT7Fp06YSuwUplUpMmTIFBQUFGDBgAFxdXZGSkiJcDBcWDxs2bMCqVavQqVMn9OrVC2lpadi5cyc++eQTrFu3rlTPETx+/BgA4OTkBAD4/vvvcejQIXTs2BFDhw7FtWvXEBkZiX/++QffffcdACAtLQ2TJ0+Gs7Mzhg8fDgcHByQnJ+PkyZMlHickJAQPHz7EkSNH8K9//QvOzs7CeXtVo0aNUKNGDRw/frxI8XDs2DE4Ojqibdu2AAC5XI5x48ZBIpEgPDwczs7OOHv2LL777jvk5ORg8ODBBp8TlUqFlJSUIufzjz/+gEKhQN++feHk5ITr169j586dSElJER727tu3L54/f46EhAR89dVXRfY9f/58HDx4ED169MDAgQPx5MkT7Ny5E7dv38by5cthYcE/10RkHvhpRERkJJ999lmRtri4OAQEBKBjx4467YGBgfjoo49w4sQJvP/++8Xu7++//0ZSUhK++eYbne0jIiKE/ycnJ2PNmjUYO3YsRo4cKbR36NABH374IXbt2qXTXpLCwqegoACXL1/GunXrYG1tjcDAQNy5cweHDh1Cr169MHPmTABA//794eLigi1btuDChQto3bo1rly5gqysLCxevFin+9a4ceNKPK6npye8vb1x5MgRBAcHv/FOTOfOnbFlyxZkZmYK38grlUrExMQgJCREuMhesWIF1Go1IiMjhQKoX79+mD17NtauXYu+ffu+sfuUSqUSikG5XI5NmzYhNTUV/fv311nv448/1tlXnz594OHhgRUrViA5ORnVq1eHj48PatWqhYSEBJ2uUABw6dIl7N+/H7Nnz0aXLl2E9latWmHKlCk4fvy4TjsRkZhYPBARGcnUqVNRq1atIu0vX1iqVCrk5OTAw8MDjo6OuHXrVonFQ+GdhXPnzsHf37/YOxQnTpyARqNB586dde56uLq6olatWkhMTNSreHi18KlevTpmz56NKlWqCN1thgwZorPO0KFDsWXLFpw5cwatW7cW4j19+jS8vLzK5Nvyzp07IzIyEidPnkSvXr0AAPHx8cjKykKnTp0AAFqtFidPnkSnTp2g1Wp1zoufnx+OHj2KW7duoVmzZq89Vnx8PHr06KHT1qNHD3z66ac6bS//fPPy8pCfn4+mTZtCq9Xizp07qF69+muP88cff8DBwQG+vr46sTZo0AC2trZI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", - "text/plain": [ - "
" + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Then, we load Evo 2 1B model, loading the Evo 2 weights from hugging face.\n", + "\n", + "*Note - for better performance, load the 7b model by setting `MODEL_SIZE=\"7b\"` which also works well GPUs that do not support FP8.*\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "MODEL_SIZE = \"1b\" # also try 7b if you have a GPU with more than 32GB of memory\n", + "\n", + "# Define checkpoint path\n", + "if MODEL_SIZE == \"1b\":\n", + " from pathlib import Path\n", + "\n", + " from bionemo.core.data.load import load\n", + "\n", + " # This line will download the checkpoint from NGC to your $HOME/.cache/bionemo directory and return the path.\n", + " # To do the same from the command line, use `CHECKPOINT_PATH=$(download_bionemo_data evo2/1b-8k-bf16:1.0)`\n", + "\n", + " # Download the 1b BF16 checkpoint from NGC\n", + " # Available checkpoints: evo2/1b-8k-bf16:1.0, evo2/1b-8k:1.0, evo2/7b-8k:1.0, evo2/7b-1m:1.0\n", + " nemo2_ckpt_path = load(\"evo2/1b-8k-bf16:1.0\")\n", + " checkpoint_path = Path(\"evo2_1b_bf16_mbridge\")\n", + " from bionemo.evo2.data.dataset_tokenizer import (\n", + " DEFAULT_HF_TOKENIZER_MODEL_PATH_512, # use the 512 size for historical reasons\n", + " )\n", + "\n", + " mixed_precision_recipe = \"bf16_mixed\" # also try bf16_with_fp8_current_scaling_mixed\n", + " convert_ckpt_cmd = f\"\"\"evo2_convert_nemo2_to_mbridge \\\n", + " --nemo2-ckpt-dir {nemo2_ckpt_path} \\\n", + " --mbridge-ckpt-dir {checkpoint_path} \\\n", + " --model-size 1b \\\n", + " --mixed-precision-recipe {mixed_precision_recipe} \\\n", + " --seq-length 8192 \\\n", + " --tokenizer-path {DEFAULT_HF_TOKENIZER_MODEL_PATH_512} \\\n", + " \"\"\".rstrip()\n", + " print(f\"Running command: {convert_ckpt_cmd}\")\n", + "\n", + " result = run_subprocess_safely(convert_ckpt_cmd)\n", + " print(f\"Downloaded checkpoint to: {nemo2_ckpt_path} and converted to mbridge format at {checkpoint_path}\")\n", + "else:\n", + " assert False, \"Implement conversion for other model sizes. Should be similar to the 1b case above.\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Score Sequences" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we score the likelihoods of the reference and variant sequences of each SNV.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "def check_fp8_support():\n", + " \"\"\"Check if FP8 is supported on the current GPU.\n", + "\n", + " FP8 requires compute capability 8.9+ (Ada Lovelace/Hopper architecture or newer).\n", + " \"\"\"\n", + " if not torch.cuda.is_available():\n", + " return False, \"CUDA not available\"\n", + "\n", + " device_props = torch.cuda.get_device_properties(0)\n", + " compute_capability = f\"{device_props.major}.{device_props.minor}\"\n", + " device_name = device_props.name\n", + "\n", + " # FP8 is supported on compute capability 8.9+ (Ada Lovelace/Hopper architecture)\n", + " is_supported = (device_props.major > 8) or (device_props.major == 8 and device_props.minor >= 9)\n", + "\n", + " return is_supported, f\"Device: {device_name}, Compute Capability: {compute_capability}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FP8 Support: True\n", + "Device: NVIDIA GeForce RTX 5090, Compute Capability: 12.0\n" + ] + } + ], + "source": [ + "# Define output directories for prediction results\n", + "output_dir = Path(\"brca1_fasta_files\")\n", + "output_dir.mkdir(parents=True, exist_ok=True)\n", + "\n", + "# Save reference and variant sequences to FASTA\n", + "ref_fasta_path = output_dir / \"brca1_reference_sequences.fasta\"\n", + "var_fasta_path = output_dir / \"brca1_variant_sequences.fasta\"\n", + "\n", + "predict_ref_dir = output_dir / \"reference_predictions\"\n", + "predict_var_dir = output_dir / \"variant_predictions\"\n", + "predict_ref_dir.mkdir(parents=True, exist_ok=True)\n", + "predict_var_dir.mkdir(parents=True, exist_ok=True)\n", + "\n", + "fp8_supported, gpu_info = check_fp8_support()\n", + "print(f\"FP8 Support: {fp8_supported}\")\n", + "print(gpu_info)\n", + "\n", + "# Note: If FP8 is not supported, you may want to disable it in the model config\n", + "# The Evo2 config has 'use_fp8_input_projections: True' by default\n", + "\n", + "if FAST_CI_MODE:\n", + " model_subset_option = \"--num-layers 4 --hybrid-override-pattern SDH*\"\n", + "else:\n", + " model_subset_option = \"\"\n", + "\n", + "# Disable FP8 for now, it is underperforming at the moment in prediction. 0.7 vs 0.76 and similar run times.\n", + "fp8_option = \"--mixed-precision-recipe bf16_with_fp8_current_scaling_mixed\" if fp8_supported else \"\"\n", + "\n", + "# Update predict commands to run on the full dataset\n", + "predict_ref_command = (\n", + " f\"predict_evo2 --fasta {ref_fasta_path} --ckpt-dir {checkpoint_path} \"\n", + " f\"--output-dir {predict_ref_dir} --tensor-parallel-size 1 {model_subset_option} \"\n", + " f\"--pipeline-model-parallel-size 1 --context-parallel-size 1 --output-log-prob-seqs {fp8_option}\"\n", + ")\n", + "\n", + "predict_var_command = (\n", + " f\"predict_evo2 --fasta {var_fasta_path} --ckpt-dir {checkpoint_path} \"\n", + " f\"--output-dir {predict_var_dir} --tensor-parallel-size 1 {model_subset_option} \"\n", + " f\"--pipeline-model-parallel-size 1 --context-parallel-size 1 --output-log-prob-seqs {fp8_option}\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Score reference sequences:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "print(f\"Running command: {predict_ref_command}\")\n", + "\n", + "result = run_subprocess_safely(predict_ref_command)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "assert result[\"returncode\"] == 0, result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Score variant sequences:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "print(f\"Running command: {predict_var_command}\")\n", + "\n", + "result = run_subprocess_safely(predict_var_command)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "assert result[\"returncode\"] == 0, result" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We calculate the change in likelihoods for each variant relative to the likelihood of their respective wild-type sequence.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we load the prediction files and sequence id maps:" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [], + "source": [ + "# Find and load prediction files\n", + "# File naming convention (epoch mode): predictions__rank_{global_rank}__dp_rank_{dp_rank}.pt\n", + "ref_pred_files = glob.glob(os.path.join(predict_ref_dir, \"predictions__rank_*.pt\"))\n", + "var_pred_files = glob.glob(os.path.join(predict_var_dir, \"predictions__rank_*.pt\"))\n", + "\n", + "# Load sequence ID maps (maps sequence ID -> prediction index)\n", + "with open(os.path.join(predict_ref_dir, \"seq_idx_map.json\"), \"r\") as f:\n", + " ref_seq_idx_map = json.load(f)\n", + "with open(os.path.join(predict_var_dir, \"seq_idx_map.json\"), \"r\") as f:\n", + " var_seq_idx_map = json.load(f)\n", + "\n", + "# Load predictions\n", + "ref_preds = torch.load(ref_pred_files[0])\n", + "var_preds = torch.load(var_pred_files[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, calculate the delta score:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
chromposrefaltscoreclassref_fasta_namevar_fasta_nameref_log_probsvar_log_probsevo2_delta_score
01741276097AG0.326953FUNC/INTBRCA1_ref_pos_41276097_A_class_FUNC/INTBRCA1_var_pos_41276097_AtoG_class_FUNC/INT-0.935537-0.935760-0.000223
11741201130AG0.056569FUNC/INTBRCA1_ref_pos_41201130_A_class_FUNC/INTBRCA1_var_pos_41201130_AtoG_class_FUNC/INT-0.929413-0.929956-0.000543
21741215938TA-2.017579LOFBRCA1_ref_pos_41215938_T_class_LOFBRCA1_var_pos_41215938_TtoA_class_LOF-0.864209-0.866731-0.002521
31741215932AC-1.706222LOFBRCA1_ref_pos_41215932_A_class_LOFBRCA1_var_pos_41215932_AtoC_class_LOF-0.864879-0.866319-0.001440
41741219685GT0.037593FUNC/INTBRCA1_ref_pos_41219685_G_class_FUNC/INTBRCA1_var_pos_41219685_GtoT_class_FUNC/INT-1.027526-1.027546-0.000020
\n", + "
" + ], + "text/plain": [ + " chrom pos ref alt score class \\\n", + "0 17 41276097 A G 0.326953 FUNC/INT \n", + "1 17 41201130 A G 0.056569 FUNC/INT \n", + "2 17 41215938 T A -2.017579 LOF \n", + "3 17 41215932 A C -1.706222 LOF \n", + "4 17 41219685 G T 0.037593 FUNC/INT \n", + "\n", + " ref_fasta_name \\\n", + "0 BRCA1_ref_pos_41276097_A_class_FUNC/INT \n", + "1 BRCA1_ref_pos_41201130_A_class_FUNC/INT \n", + "2 BRCA1_ref_pos_41215938_T_class_LOF \n", + "3 BRCA1_ref_pos_41215932_A_class_LOF \n", + "4 BRCA1_ref_pos_41219685_G_class_FUNC/INT \n", + "\n", + " var_fasta_name ref_log_probs var_log_probs \\\n", + "0 BRCA1_var_pos_41276097_AtoG_class_FUNC/INT -0.935537 -0.935760 \n", + "1 BRCA1_var_pos_41201130_AtoG_class_FUNC/INT -0.929413 -0.929956 \n", + "2 BRCA1_var_pos_41215938_TtoA_class_LOF -0.864209 -0.866731 \n", + "3 BRCA1_var_pos_41215932_AtoC_class_LOF -0.864879 -0.866319 \n", + "4 BRCA1_var_pos_41219685_GtoT_class_FUNC/INT -1.027526 -1.027546 \n", + "\n", + " evo2_delta_score \n", + "0 -0.000223 \n", + "1 -0.000543 \n", + "2 -0.002521 \n", + "3 -0.001440 \n", + "4 -0.000020 " + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# next, calculate change in likelihoods\n", + "ref_log_probs = []\n", + "var_log_probs = []\n", + "for _, row in brca1_df.iterrows():\n", + " ref_name = row[\"ref_fasta_name\"]\n", + " var_name = row[\"var_fasta_name\"]\n", + " ref_log_probs.append(ref_preds[\"log_probs_seqs\"][ref_seq_idx_map[ref_name]].item())\n", + " var_log_probs.append(var_preds[\"log_probs_seqs\"][var_seq_idx_map[var_name]].item())\n", + "brca1_df[\"ref_log_probs\"] = ref_log_probs\n", + "brca1_df[\"var_log_probs\"] = var_log_probs\n", + "# ideally probability of a broken variant is lower than a good one. So a bad var - good ref is negative.\n", + "brca1_df[\"evo2_delta_score\"] = brca1_df[\"var_log_probs\"] - brca1_df[\"ref_log_probs\"]\n", + "brca1_df.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This delta likelihood should be predictive of how disruptive the SNV is to the protein's function: the lower the delta, the more likely that the SNV is disruptive. We can show this by comparing the distributions of delta likelihoods for the two classes of SNVs (functional/intermediate vs loss-of-function)." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "def plot_strip_with_means(df, x_col=\"evo2_delta_score\", class_col=\"class\"):\n", + " \"\"\"Creates a strip plot with jittered points and median indicators for each class using Seaborn.\n", + "\n", + " Parameters:\n", + " - df (pd.DataFrame): The input DataFrame containing data.\n", + " - x_col (str): The column name representing the x-axis values (e.g., evo2_delta_score).\n", + " - class_col (str): The column name representing the class labels.\n", + "\n", + " Returns:\n", + " - matplotlib Figure: Strip plot with median indicators.\n", + " \"\"\"\n", + " # NVIDIA theme colors\n", + " NVIDIA_GREEN = \"#76B900\" # noqa: N806\n", + " BACKGROUND_COLOR = \"#F8F8F8\" # noqa: N806\n", + " GRID_COLOR = \"#DDDDDD\" # noqa: N806\n", + " FONT_COLOR = \"#333333\" # noqa: N806\n", + "\n", + " # Determine order of classes (if not already specified)\n", + " unique_classes = sorted(df[class_col].unique())\n", + "\n", + " # Set up the plot with NVIDIA theme\n", + " plt.figure(figsize=(9, 5), facecolor=BACKGROUND_COLOR)\n", + " plt.style.use(\"default\") # Reset to default to avoid any pre-existing style\n", + "\n", + " # Create strip plot\n", + " p = sns.stripplot(\n", + " data=df,\n", + " x=x_col,\n", + " y=class_col,\n", + " hue=class_col,\n", + " order=unique_classes,\n", + " palette=[NVIDIA_GREEN, \"red\"],\n", + " size=6,\n", + " jitter=0.3,\n", + " alpha=0.6,\n", + " )\n", + "\n", + " # Add median indicators using boxplot\n", + " sns.boxplot(\n", + " showmeans=True,\n", + " meanline=True,\n", + " meanprops={\"visible\": False},\n", + " medianprops={\"color\": \"black\", \"ls\": \"-\", \"lw\": 2},\n", + " whiskerprops={\"visible\": False},\n", + " zorder=10,\n", + " x=x_col,\n", + " y=class_col,\n", + " data=df,\n", + " order=unique_classes,\n", + " showfliers=False,\n", + " showbox=False,\n", + " showcaps=False,\n", + " ax=p,\n", + " )\n", + "\n", + " # Customize plot appearance\n", + " plt.title(\n", + " \"Distribution of Delta Likelihoods Scores\\nComparing Evo 2 likelihood scores for different BRCA1 SNV classes\",\n", + " color=FONT_COLOR,\n", + " fontsize=12,\n", + " loc=\"left\",\n", + " )\n", + " plt.xlabel(\"Delta Likelihood Score, Evo 2\", color=FONT_COLOR)\n", + " plt.ylabel(\"BRCA1 SNV Class\", color=FONT_COLOR)\n", + "\n", + " # Customize grid and tick colors\n", + " plt.grid(color=GRID_COLOR, axis=\"x\", linestyle=\"--\", linewidth=0.5)\n", + " plt.tick_params(colors=FONT_COLOR)\n", + "\n", + " # Set background color\n", + " plt.gca().set_facecolor(BACKGROUND_COLOR)\n", + " plt.gcf().set_facecolor(BACKGROUND_COLOR)\n", + "\n", + " plt.tight_layout()\n", + "\n", + " # return plt.gcf()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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8dFHHyEpKQlpaWmIiYnBiBEjMHnyZM7Q6WM8k0dOPffcczh+/Dhmz56N//znP9ixYwf+85//uLzPm2++iT/++AMzZ87E3LlzcfHiRTz11FO2dqvViieeeALFxcV455138Mwzz2Dp0qWYP39+hX1lZ2fjhRdeQK9evXR/bjWFvzKqU6cOJkyYgLfeeguff/457rzzTrz//vv48ccfvfZczcpfGe3YsQN9+vTBrFmz8MEHH6Bnz5548skncejQIa89V7PyV0bFxcWoX78+Jk+ejNatW3vt+ZnRihUrMGfOHEyZMgUffPABWrdujUcffRQZGRkOb7979248++yzGDNmDD788EMMGjQIf//733Hs2DHbbT777DMsWrQITzzxBN5//32EhITg0UcfRWFhoe02VflbqK38lVFxcTGGDBmCcePGef05mp0/MkpOToaqqnjiiSfw6aef4uGHH8YPP/yAd9991yfPma5gkUcOnThxAhs3bsTf/vY3dOrUCd26dcP06dOxYsUKXLhwweF9cnJy8Msvv+Chhx5Cr1690L59ezz99NPYvXs39uzZAwDYvHkzTpw4gX/9619o27Yt+vXrh3vuuQffffcdiouL7fb36quvYvjw4ejcubPXn68Z+TOjtm3bYvjw4UhISECTJk0wYsQI9OnTBzt37vTZ8zcDf2Y0bdo03H777ejQoQOaNWuG+++/H3FxcVi3bp3Pnr8Z+DOjJk2aYNq0aRg5ciTCwsJ89pzN4KuvvsINN9yA0aNHo2XLlnjiiScQHByMX375xeHtv/76a/Tt2xe333474uPjcd9996Ft27ZYtGgRAO3sw9dff43Jkydj0KBBaN26Nf7xj3/g4sWLWLt2LYCq/S3UZv7ICADuuece3HrrrUhISPDJ8zQzf2SUmJiIp59+Gn379kVsbCwGDRqESZMm4ffff/fZ8yYNizxyaM+ePQgPD0eHDh1s26666iooioJ9+/Y5vM/BgwdRUlKCq666yratRYsWaNSoke2Dz549e5CQkGA3pKlv377Izc3F8ePHbdsWL16MM2fOYMqUKXo/tRrD3xmVdejQIezZswfdu3fX4ZnVHEbKSFVV5OfnIyIiQo+nVmMYKSPSFBcX4+DBg+jdu7dtm6IouOqqq2y/3/L27t1rlweg/b737t0LADhz5gzS0tLsbhMWFoaOHTvaZVbZv4Xa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GKqq4m9/+xuysrIwd+5czJ49GykpKfjnP/9ZqcfKzc1FeHi4bn0nIvIGnskjIqJKa9GiBY4ePQoA2LlzJ/bv349ffvkFQUFBALQzOmvWrMGqVatw44032t03NDQUwcHBKC4urnBW5s4777T9v0mTJjh58iRWrFiB22+/Xdf+7969G0888QQmT55sO0N27tw5LFmyBN9++y0aNGgAALjtttuwadMmLF68GPfff79H+w4PD8ejjz4Ki8WCFi1aoH///ti6dSvGjh2LU6dOYd26dXjnnXdshe2//vUvjBs3DmvWrMHQoUPx5ZdfolevXrjrrrsAAM2bN8eJEyfw+eefY/To0Th58iQ2btyI+fPno0OHDgCAv//977jttttc9mvGjBl47rnnMGrUKLRu3Rpdu3bFkCFD0LVrVwDAyZMnsXLlSsyePRu9e/cGoJ25KvXdd9+hYcOGePTRRyGEQIsWLXDx4kW8/fbbuOuuu2zXaDZr1gwPPvig7X4fffQR2rZta/f7e+qppzBu3DicPHkSzZs39+j3CgDz5s3D+++/b7dt1qxZ6N69O6677jp8+eWXOHfunK2oW7FihW1inK1bt+LYsWP45ptv0KhRIwDAP/7xD/z5z3/G/v37bb9LV06fPo1FixbxLB4RGR6LPCIiqjQppe3/R44cQX5+PkaNGmV3m8LCQqSkpFRqvytWrMCiRYuQkpKC/Px8WK1W3a+5On/+PKZNm4b77rsPt9xyi237sWPHYLVa7YZFAtoQxoiICI/3XzpcsVR0dLStID5x4gQsFgs6duxoa69Xr56tkAO068DKT4TStWtXfP3117BarUhOTobFYkG7du1s7S1atHB7dql79+745ptvsGfPHuzZswdbt27FN998g7vvvht33XUXDh8+DIvFgh49eji8/4kTJ9C5c2cIIWzbunTpgvz8fKSmpqJx48YAYNcvQPv72LZtG4YNG1ZhnykpKZUq8m677bYKf2elBXnbtm3RokULLF++HHfccQe2b9+OjIwMDB06FID2e23YsKGtwAO0rMLDw3HixAm3Rd6FCxfw6KOPYsiQIRWuCyQiMhoWeUREVGnJyclo2rQpACA/Px/R0dGYO3duhduFhYV5vM89e/bg+eefx913340+ffogLCwMK1aswJdffqlbvwGgfv36iImJwYoVKzBmzBjUrVsXAJCXlweLxYIFCxbYFWkAbEMWPeHo+sGyRbE/BQQEoHv37ujevTv+/Oc/46OPPsKHH36IP//5z7pNUBMSEmL3c35+PgYMGIAHHnigwm0dXV/nSr169RAXF+e0/brrrrMVecuXL0ffvn1Rr169Sj2GIxcuXMBDDz2ELl26YMaMGdXeHxGRt/GaPCIiqpSkpCQcPXoUgwcPBqCdQUlPT4fFYkFcXJzdP2cTawQGBkJVVbttu3fvRqNGjTB58mR06NABzZo1czhlfnUFBwfj1VdfRVBQEKZPn267Rq1t27awWq3IyMio8DwqW4w4Ex8fD6vVin379tm2Xbp0CSdPnkTLli0BaGfldu3aZXe/Xbt2oVmzZrBYLGjevDmsVisOHjxoa09OTkZ2dnaV+1NUVIRWrVpBVVVs377d6W337NljV7Du3r0boaGhaNiwodPHaNu2LY4fP47GjRtX+L1Wpnj2xHXXXYdjx47hwIEDWL16Na677jpbW4sWLZCamorz58/bth0/fhzZ2dmIj493us/SAq9du3Z46qmnqrV0CBGRr/CVioiInCouLkZaWhouXLiAgwcPYuHChZgxYwYGDBiA66+/HgDQu3dvdOrUCX/729+wadMmnD17Frt378a7776L/fv3O9xv48aNceTIESQnJyMzMxMlJSWIi4vD+fPnsWLFCpw+fRrffPMN1qxZ41E/z5w5g0OHDtn9y8/Pd3r7OnXq4NVXX4XFYsHjjz+OvLw8NG/eHNdddx1mzpyJ1atX48yZM9i3bx8+/vhjrF+/vvK/PAeaNWuGQYMG4ZVXXsHOnTtx+PBhPP/882jQoIFtiOakSZOQlJSEDz/8ECdPnrRdJ1g6jLRFixZITEzEf/7zH+zdu9c2iYu7M3FTp07FDz/8gAMHDuDs2bNYv3493n33XfTs2RN169ZFkyZNMHLkSLz88stYs2YNzpw5g23btuG3334DAIwfPx6pqal47bXXkJycjLVr12LBggW49dZbXRY+48ePR1ZWFp599lns378fp0+fxqZNm/Diiy/CarVW6veXl5eHtLQ0u39lJ5Jp0qQJunTpgpdffhlWq9Vu2Gvv3r2RkJCA5557DgcPHsS+ffvwwgsvoEePHk6Hal64cAFTp05Fo0aNMHXqVGRmZtoel4jIyDhck4iInNq4cSPGjh0Li8WC8PBwtG7dGtOnT8fIkSNtH+yFEJg1axbee+89vPTSS8jMzER0dDS6devmdGr6sWPHYvv27bj77ruRn5+POXPmYNCgQbjlllvw2muvoaioCP3798edd96JDz74wG0/58yZU2Hb22+/7fI+oaGh+O9//4tHH30UTzzxBGbNmoWnn34aH330EebOnYsLFy6gXr166NSpk22pBz089dRTeOONN/Dkk0+iuLgY3bt3x6xZs2zDPNu1a4fnn38e8+fPx0cffYTo6Gjcc889GD16tN0+XnnlFUydOhWRkZG47777KkxIUl6fPn2wdOlSvPvuuygoKEBMTAwGDBhgm+AFAB5//HG8++67mDVrFrKystCoUSP85S9/AaBd+zZr1iy89dZbmDx5MiIiIjBmzBjbxCbONGjQAO+88w7efvttTJ8+HUVFRWjcuDH69u1r+xtavHgxXnrpJfzxxx8u9zV//nzMnz/fbtuNN96IJ5980vbzddddh1mzZuH666+3K3yFEHjllVfw+uuv48EHH7RbQsGZzZs34/Tp0zh9+jRuuukmuzZ3fSUi8idRUFBgjAsFiIiIqFaaP38+duzY4fC6TiIiqjwO1yQiIiK/2rhxo8OJWYiIqGp4Jo+IiIiIiKgG4Zk8IiIiIiKiGoRFHhERERERUQ3CIo+IiIiIiKgGYZFHRERERERUg7DIIyIiIiIiqkFY5BEREREREdUgLPKIiIiIiIhqEBZ5RERERERENQiLPCIiIiIiohrk/wFpQd1Vw8nY/QAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_strip_with_means(brca1_df, x_col=\"evo2_delta_score\", class_col=\"class\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also calculate the area under the receiver operating characteristic curve (AUROC) of this zero-shot prediction method. Note that the results are nearly random unless you are on one of the following configurations:\n", + "* `--fp8` on an fp8 enabled GPU with either the 1b or 7b models. The 40b likely works as well.\n", + "* the 7b model uniquely seems to work well without `--fp8` so if you are on an older device, the 7b model should produce\n", + " robust results. Change the `MODEL_SIZE` earlier in this tutorial and rerun for good results in that case.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Zero-shot prediction AUROC: 0.73\n" + ] + } + ], + "source": [ + "# Calculate AUROC of zero-shot predictions\n", + "# class 1 is LOF which is the bad thing. That means we expect this to be more negative.\n", + "y_true = brca1_df[\"class\"] == \"LOF\"\n", + "auroc = roc_auc_score(y_true, -brca1_df[\"evo2_delta_score\"])\n", + "print(f\"Zero-shot prediction AUROC: {auroc:.2}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "def plot_roc_curve(df):\n", + " \"\"\"Plots an ROC curve using Seaborn with a light NVIDIA-themed design.\n", + "\n", + " The function assumes:\n", + " - `class` column as the true labels (binary, 'LOF' = 1, else 0).\n", + " - `evo2_delta_score` as the prediction score.\n", + "\n", + " Parameters:\n", + " - df (pd.DataFrame): DataFrame containing `class` and `evo2_delta_score`.\n", + "\n", + " Returns:\n", + " - matplotlib Figure: ROC Curve Visualization.\n", + " \"\"\"\n", + " # NVIDIA theme colors\n", + " NVIDIA_GREEN = \"#76B900\" # noqa: N806\n", + " BACKGROUND_COLOR = \"#F8F8F8\" # noqa: N806\n", + " GRID_COLOR = \"#DDDDDD\" # noqa: N806\n", + " FONT_COLOR = \"#333333\" # noqa: N806\n", + "\n", + " # Validate required columns\n", + " if \"class\" not in df.columns or \"evo2_delta_score\" not in df.columns:\n", + " raise ValueError(\"DataFrame must contain 'class' and 'evo2_delta_score' columns.\")\n", + "\n", + " # Convert 'class' to binary labels: Assume 'LOF' = 1, anything else = 0\n", + " y_true = (df[\"class\"] == \"LOF\").astype(int)\n", + "\n", + " # Compute ROC curve\n", + " fpr, tpr, _ = roc_curve(y_true, -df[\"evo2_delta_score\"]) # Negative to align with previous logic\n", + " roc_auc = auc(fpr, tpr)\n", + "\n", + " # Set up the plot with NVIDIA theme\n", + " plt.figure(figsize=(9, 5), facecolor=BACKGROUND_COLOR)\n", + " plt.style.use(\"default\") # Reset to default to avoid any pre-existing style\n", + "\n", + " # Plot ROC curve\n", + " plt.plot(fpr, tpr, color=NVIDIA_GREEN, lw=3, label=f\"ROC curve (AUROC = {roc_auc:.2f})\")\n", + "\n", + " # Plot diagonal reference line for random guessing\n", + " plt.plot([0, 1], [0, 1], color=\"gray\", lw=2, linestyle=\"--\")\n", + "\n", + " # Customize plot appearance\n", + " plt.xlim([0.0, 1.0])\n", + " plt.ylim([0.0, 1.05])\n", + " plt.xlabel(\"False Positive Rate\", color=FONT_COLOR, fontsize=12)\n", + " plt.ylabel(\"True Positive Rate\", color=FONT_COLOR, fontsize=12)\n", + " plt.title(\n", + " \"Zeroshot ROC Curve\\nEvaluating the discriminative performance of Evo 2 predictions\",\n", + " color=FONT_COLOR,\n", + " fontsize=16,\n", + " loc=\"left\",\n", + " )\n", + "\n", + " # Customize grid and tick colors\n", + " plt.grid(color=GRID_COLOR, linestyle=\"--\", linewidth=0.5)\n", + " plt.tick_params(colors=FONT_COLOR)\n", + "\n", + " # Set background color\n", + " plt.gca().set_facecolor(BACKGROUND_COLOR)\n", + "\n", + " # Add legend\n", + " plt.legend(loc=\"lower right\", frameon=True, facecolor=BACKGROUND_COLOR, edgecolor=GRID_COLOR)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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vvjyyy/xxhtv4OjRo3jmmWfQpUsX9O3bF+7u7igrK0NeXh7q6urg5eVlNIWwYVB8YmIiHn/8cQwdOhRubm4oKChATU0N5syZYzStr6WmT5+OL774QnitplZdd3Jywr/+9S8sXLgQO3fuxIEDBxAaGoqQkBCo1WpcvnwZZ86cgVarxbRp05ocsE9ERET2gb/U/+fq1asAgLq6OuzcudOs50ybNk0oBJydnfH222/j9ttvxx9//IG8vDycOXMGHh4eCAoKQnR0NMaPHy/qjobB/PnzMXToUPzyyy/Iy8vDsWPH4O/vj4kTJ+L+++9vtODZtGnT4ObmhuzsbJw7dw6ZmZnw8vJCSEgI/va3v2HWrFnCrEMtNWjQIHz77bf44YcfkJqaioSEBLi4uKB///6YMmUK7rjjDpMnhEuXLsWXX36JjIwM7N27F1qtFp06dbJa8eDs7Iz58+fjrbfewi+//IL77rvP6DP36dMHP/74I3bs2IEDBw4IszR5eHige/fuGDt2LO65556brrfw8MMP4/bbb8dvv/2G9PR0nDx5EtXV1fDw8ECXLl0wfvx4TJs2Df379xcVf0BAAFauXInU1FTs3r0bubm5SE9Ph1qthp+fH8LDwzFu3DhMmTLFaGpUAHj33Xfx7bffYvfu3Th69Ch8fHwQGRmJJ554wuzuUzczZcoUrFq1ClqtFh4eHk0WWEDDnaZVq1Zhx44d2Lt3L06fPo28vDz4+voiODgYs2bNwvjx420ycQARERFZl0ypVLIjMhERERER3RTHPBARERERkVlYPBARERERkVlYPBARERERkVlYPBARERERkVlYPBARERERkVlYPBARERERkVm4zsN17rnnHhQVFd10u8WLF2P69OmtEJGxK1euYPbs2ejUqRM2b97c6u8vxvbt27Fs2TJMnToVr7/+utThWMyR9nlzsUZHRwMADh48KEVozVqyZAl27tzZat+rZ599FkePHsWKFSswYsQIm79fS9jzv5s9S0pKwo8//ohTp06htrYWABzi35tapqSkBF988QXS09NRUVEBrVZr1m/QmjVrsHbt2pu+/vDhw7Fy5UprhWs1BQUFSE1NRXp6Os6cOQOFQgFPT0/07t0bt912G2bNmtUuFuA0nMP98ssv6Ny5s9Demsd8Qy499thjePzxx236XlJq+9lkgaFDh6Jr165NPt6t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li3x2muvGc2f9agbN24Ir/3Rf7t27QIA5OXlYfjw4fDx8YGzszM6dOiAGTNmGD3/NGnSJJw9exYpKSn19tpMxW5LFkyj0eD06dM4efIkysvLhXZra2sEBwcjJCQESqVSwgiJiKgxKtPk4eMTbSSNYU7oLTjYiBtafPDgwVi9ejU0Gg1++uknTJkyBTKZDIsWLRLW+fe//42//vWveOutt7BixQrY2Njgm2++wfTp03Hp0iV89NFHwrrvvvsuli5dir/85S/4n//5H3h5eeG///0v1q5di61bt+LPf/5znb/e6lRUVNTb0Lk3b97EgQMH8Mknn1RZlpqairKyMowaNQpbtmzBX//611ofp7G8N6+88gpycnLw7bffQqPRICEhAW+88UaNBU6bNm1w48YNo7Z169Zh2bJlGDJkCICHd5CGDx+OBQsWoFmzZrh+/TpmzJgBlUol7NfGxgZjx47Fv//9b/Tv379eXpupWDxYMJ1Oh1OnTgmFg0KhQPfu3dGvXz/e3pYQzz2JxVwhUzBfas/W1haenp4AHl60DRo0CIcPHxYuUG/duoU5c+Zg+vTpWLhwobDdjBkzYGNjg1mzZuGFF15ASEgIMjIysHjxYixZssToQrRdu3aIjo5+7B2i27dvY968eTh06BDKy8vRqVMnLF++HCEhIZgyZQoePHiAr776Slj/r3/9K86dO4cffvgBABATE4OAgABYWVlh27ZtCAgIgKenJ/R6PbZs2SJsp9Pp0LZtW3z88ceYMGEC9Ho9lixZgnXr1uHevXvw8/PDvHnzHtvdZdeuXejatStat25dZdnGjRsxduxYRERE4K233nqq4sFS3puncfnyZXz//fdITU1Fr169AADLli3D888/j48++qjaES0VCoXwuivt27cPcXFxcHR0BPBweOaEhARhedu2bfHaa69VKeiee+45xMbGoqyszGiAHKmw25IFUyqVCA0NhUwmQ1BQEBISEhATE8M/MBIz1zjK1PAxV8gUzJe6cfHiRZw8edLoG/vdu3dDo9FgxowZVdafMmUKHB0dsWPHDgDA9u3b4ejoiNdee63a/bu6ulbbXlxcjJiYGGRnZ2Pnzp3IyMjArFmzhK7GYm3ZsgU2Njb48ccf8fnnn2P8+PH49ttvjbrI/PDDDygtLcXzzz8PAFi8eDG+/PJLfP7558jMzMT06dMxefJk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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_roc_curve(brca1_df)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "# Check if the AUC is a reasonable value for our CI suite when we run the full model\n", + "assert FAST_CI_MODE or auroc >= 0.7" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Full Sample Performance\n", + "\n", + "The above analysis may have been performed on a subset of the available data.\n", + "\n", + "For comparison, the table below presents the AUROC scores for different model sizes trained on the *full dataset* (100% sample fraction).\n", + "\n", + "| Model Size | Dataset Sample Fraction | AUROC |\n", + "|------------|------------------------|-------|\n", + "| Evo 2 1B | 100% | 0.74 |\n", + "| Evo 2 7B | 100% | 0.87 |\n" ] - }, - "metadata": {}, - "output_type": "display_data" } - ], - "source": [ - "plot_roc_curve(brca1_df)" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "# Check if the AUC is a reasonable value for our CI suite when we run the full model\n", - "assert FAST_CI_MODE or auroc >= 0.73" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Full Sample Performance\n", - "\n", - "The above analysis may have been performed on a subset of the available data.\n", - "\n", - "For comparison, the table below presents the AUROC scores for different model sizes trained on the *full dataset* (100% sample fraction).\n", - "\n", - "| Model Size | Dataset Sample Fraction | AUROC |\n", - "|------------|------------------------|-------|\n", - "| Evo 2 1B | 100% | 0.74 |\n", - "| Evo 2 7B | 100% | 0.87 |\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/bionemo-recipes/recipes/evo2_megatron/pyproject.toml b/bionemo-recipes/recipes/evo2_megatron/pyproject.toml index 355d808460..ce454bf692 100644 --- a/bionemo-recipes/recipes/evo2_megatron/pyproject.toml +++ b/bionemo-recipes/recipes/evo2_megatron/pyproject.toml @@ -35,9 +35,9 @@ test = [] [project.scripts] torchrun = "torch.distributed.run:main" -#infer_evo2 = "bionemo.evo2.run.infer:main" +infer_evo2 = "bionemo.evo2.run.infer:main" train_evo2 = "bionemo.evo2.run.train:main" -#predict_evo2 = "bionemo.evo2.run.predict:main" +predict_evo2 = "bionemo.evo2.run.predict:main" preprocess_evo2 = "bionemo.evo2.data.preprocess:main" splice_evo2 = "bionemo.evo2.data.transcript_extraction:main" evo2_convert_nemo2_to_mbridge = "bionemo.evo2.utils.checkpoint.nemo2_to_mbridge:main" diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/evo2_provider.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/evo2_provider.py index dbd61f3a5c..d73366ee73 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/evo2_provider.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/evo2_provider.py @@ -511,8 +511,20 @@ def provide(self, pre_process=None, post_process=None, vp_stage=None) -> MCoreHy rotary_percent=self.rotary_percent, rotary_base=self.rotary_base, seq_len_interpolation_factor=self.seq_len_interpolation_factor, - pre_process=pre_process or parallel_state.is_pipeline_first_stage(), - post_process=post_process or parallel_state.is_pipeline_last_stage(), + # Note: When self.pre_process/self.post_process is explicitly False (e.g., for embedding + # extraction), we must use that value regardless of what the caller passes. This is because + # _create_model in megatron.bridge always passes the pipeline stage values, but we want to + # disable post-processing when extracting embeddings. + pre_process=( + False + if self.pre_process is False + else (pre_process if pre_process is not None else parallel_state.is_pipeline_first_stage()) + ), + post_process=( + False + if self.post_process is False + else (post_process if post_process is not None else parallel_state.is_pipeline_last_stage()) + ), share_embeddings_and_output_weights=self.share_embeddings_and_output_weights, hyena_init_method=self.hyena_init_method, hyena_output_layer_init_method=self.hyena_output_layer_init_method, diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_block.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_block.py index 7ec67a4fba..93acf70e57 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_block.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_block.py @@ -199,6 +199,9 @@ def __init__( hidden_size=self.transformer_config.hidden_size, eps=self.transformer_config.layernorm_epsilon, ) + else: + # Ensure final_norm is always defined to avoid AttributeError when post_process=False + self.final_norm = None # Required for activation recomputation self.num_layers_per_pipeline_rank = len(self.layers) diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_utils.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_utils.py index d0e452ef6d..955ea7fc5a 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_utils.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/models/megatron/hyena/hyena_utils.py @@ -1005,9 +1005,11 @@ def get_filter_state(filter_name): ) # y = rearrange(y, "b d l -> b l d") else: - x1 = rearrange(x1, "1 d l -> l d") - x2 = rearrange(x2, "1 d l -> l d") - v = rearrange(v, "1 d l -> l d") + # Decode path: handle arbitrary batch size + # Input shapes: [b, d, l] where l=1 during decode + x1 = rearrange(x1, "b d l -> (b l) d") + x2 = rearrange(x2, "b d l -> (b l) d") + v = rearrange(v, "b d l -> (b l) d") x1, x2 = x2, x1 # TODO: figure why it is swapped y, iir_state = engine.step_iir( x2=x2, @@ -1301,6 +1303,8 @@ def sharded_state_dict(self, prefix="", sharded_offsets=(), metadata=None): "conv_bias": 0, }, # parameters sharded across TP sharded_offsets=sharded_offsets, + tp_group=self.pg_collection.tp, + dp_cp_group=self.pg_collection.dp_cp, ) # Submodules for name, module in self.named_children(): diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py index 947d00e15b..837286df94 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py @@ -29,7 +29,7 @@ TokenizerConfig, TrainingConfig, ) -from megatron.bridge.training.mixed_precision import MixedPrecisionConfig +from megatron.bridge.training.mixed_precision import MixedPrecisionConfig, get_mixed_precision_config from typing_extensions import TypedDict, Unpack from bionemo.evo2.data.evo2_dataset_provider import Evo2DatasetProvider @@ -166,6 +166,8 @@ def _evo2_common( checkpoint_dir = os.path.join(run_output_dir, "checkpoints") tensorboard_dir = os.path.join(run_output_dir, "tb_logs") wandb_save_dir = os.path.join(run_output_dir, "wandb") + if isinstance(precision_config, str): + precision_config = get_mixed_precision_config(precision_config) if mock: dataset_cfg_or_provider = MockEvo2DatasetProvider( random_seed=dataset_seed, diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/infer.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/infer.py index d68a102977..d6ebd79b13 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/infer.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/infer.py @@ -16,222 +16,613 @@ # See the License for the specific language governing permissions and # limitations under the License. -# FIXME get this working with megatron bridge - -# import argparse -# import sys -# import time -# from typing import Literal, Optional - -# import nemo.lightning as nl -# import torch -# from megatron.core.inference.common_inference_params import CommonInferenceParams -# from megatron.core.inference.inference_request import InferenceRequest -# from nemo.collections.llm import inference -# from nemo.utils import logging - - -# CheckpointFormats = Literal["torch_dist", "zarr"] - - -# def parse_args(): -# """Parse arguments for Evo2 inference.""" -# ap = argparse.ArgumentParser() - -# # generation args: -# default_prompt = ( -# "|d__Bacteria;" -# + "p__Pseudomonadota;" -# + "c__Gammaproteobacteria;" -# + "o__Enterobacterales;" -# + "f__Enterobacteriaceae;" -# + "g__Escherichia;" -# + "s__Escherichia|" -# ) -# ap.add_argument( -# "--prompt", -# type=str, -# default=default_prompt, -# help="Prompt to generate text from Evo2. Defaults to a phylogenetic lineage tag for E coli.", -# ) -# ap.add_argument( -# "--ckpt-dir", type=str, required=True, help="Path to checkpoint directory containing pre-trained Evo2 model." -# ) -# ap.add_argument("--temperature", type=float, default=1.0, help="Temperature during sampling for generation.") -# ap.add_argument("--top-k", type=int, default=0, help="Top K during sampling for generation.") -# ap.add_argument("--top-p", type=float, default=0.0, help="Top P during sampling for generation.") -# ap.add_argument("--max-new-tokens", type=int, default=1024, help="Maximum number of tokens to generate.") -# ap.add_argument("--seed", type=int, default=None, help="Random seed for generation.") -# # compute args: -# ap.add_argument("--tensor-parallel-size", type=int, default=1, help="Order of tensor parallelism. Defaults to 1.") -# ap.add_argument( -# "--pipeline-model-parallel-size", type=int, default=1, help="Order of pipeline parallelism. Defaults to 1." -# ) -# ap.add_argument( -# "--context-parallel-size", type=int, default=1, help="Order of context parallelism. Defaults to 1." -# ) -# # output args: -# ap.add_argument( -# "--output-file", -# type=str, -# default=None, -# help="Output file containing the generated text produced by the Evo2 model. If not provided, the output will be logged.", -# ) -# # extra: -# ap.add_argument( -# "--ckpt-format", -# type=str, -# choices=["torch_dist", "zarr"], -# default="torch_dist", -# help="Specify checkpoint format to use. Defaults to 'torch_dist', as 'zarr' is deprecated.", -# ) -# ap.add_argument( -# "--fp8", -# action="store_true", -# default=False, -# help="Whether to use vortex style FP8. Defaults to False.", -# ) -# ap.add_argument( -# "--flash-decode", -# action="store_true", -# default=False, -# help="Whether to use flash decode. Defaults to True.", -# ) -# return ap.parse_args() - - -# def infer( -# prompt: str, -# ckpt_dir: str, -# temperature: float, -# top_k: int, -# top_p: float, -# max_new_tokens: int, -# tensor_parallel_size: int, -# pipeline_model_parallel_size: int, -# context_parallel_size: int, -# output_file: Optional[str] = None, -# ckpt_format: CheckpointFormats = "torch_dist", -# seed: Optional[int] = None, -# vortex_style_fp8: bool = False, -# flash_decode: bool = False, -# return_log_probs: bool = False, -# ) -> list[InferenceRequest]: -# """Inference workflow for Evo2. - -# Args: -# prompt (str): Prompt to generate text from Evo2. -# ckpt_dir (str): Path to checkpoint directory containing pre-trained Evo2 model. -# temperature (float): Temperature during sampling for generation. -# top_k (int): Top K during sampling for generation. -# top_p (float): Top P during sampling for generation. -# max_new_tokens (int): Maximum number of tokens to generate. -# tensor_parallel_size (int): Order of tensor parallelism. -# pipeline_model_parallel_size (int): Order of pipeline parallelism. -# context_parallel_size (int): Order of context parallelism. -# output_file (str): Output file containing the generated text produced by the Evo2 model. -# ckpt_format (CheckpointFormats): Checkpoint format to use. -# seed (int): Random seed for generation. -# vortex_style_fp8 (bool): Whether to use vortex style FP8. -# flash_decode (bool): Whether to use flash decode. -# return_log_probs (bool): Whether to return log probabilities. - -# Returns: -# None -# """ -# model_parallel_size = tensor_parallel_size * pipeline_model_parallel_size * context_parallel_size -# if model_parallel_size > torch.cuda.device_count(): -# raise ValueError( -# f"Requested model parallel size {model_parallel_size} is greater than the " -# f"number of available CUDA devices {torch.cuda.device_count()}" -# ) -# # Create PTL trainer. -# trainer = nl.Trainer( -# accelerator="gpu", -# devices=model_parallel_size, -# strategy=nl.MegatronStrategy( -# tensor_model_parallel_size=tensor_parallel_size, -# pipeline_model_parallel_size=pipeline_model_parallel_size, -# context_parallel_size=context_parallel_size, -# pipeline_dtype=torch.bfloat16, -# ckpt_load_optimizer=False, # Needs to be false for a normal model checkpoint. -# ckpt_save_optimizer=False, -# ckpt_async_save=False, -# save_ckpt_format=ckpt_format, -# ckpt_load_strictness="log_all", -# ), -# log_every_n_steps=1, -# limit_val_batches=10, -# num_sanity_val_steps=0, -# plugins=nl.MegatronMixedPrecision( -# precision="bf16-mixed", -# params_dtype=torch.bfloat16, -# ), -# ) -# inference_wrapped_model, mcore_tokenizer = inference.setup_model_and_tokenizer( -# path=ckpt_dir, -# trainer=trainer, -# params_dtype=torch.bfloat16, -# inference_batch_times_seqlen_threshold=8192, # TODO -# inference_max_seq_length=8192, # TODO -# recompute_granularity=None, -# recompute_num_layers=None, -# recompute_method=None, -# vortex_style_fp8=vortex_style_fp8, -# flash_decode=flash_decode, -# enable_flash_decode=flash_decode, -# ) -# t0 = time.perf_counter_ns() -# # TODO: fix return type in NeMo inference.generate (it is a list[InferenceRequest] not a dict) -# results: list[InferenceRequest] = inference.generate( -# model=inference_wrapped_model, -# max_batch_size=1, # vortex only supports batch size 1 -# tokenizer=mcore_tokenizer, -# prompts=[prompt], -# random_seed=seed, -# inference_params=CommonInferenceParams( -# temperature=temperature, -# top_k=top_k, -# top_p=top_p, -# return_log_probs=return_log_probs, -# num_tokens_to_generate=max_new_tokens, -# ), -# ) -# dt = (time.perf_counter_ns() - t0) / 1e9 # seconds -# tokens_per_sec = (len(results[0].generated_text) + 1) / dt # +1 for the prompt - -# print(f"Inference time: {dt} seconds, {tokens_per_sec} tokens/sec", file=sys.stderr) -# if torch.distributed.get_rank() == 0: -# if output_file is None: -# logging.info(results) -# else: -# with open(output_file, "w") as f: -# f.write(f"{results[0]}\n") - -# return results - - -# def main(): -# """Main function for Evo2 inference.""" -# # Parse args. -# args = parse_args() -# infer( -# prompt=args.prompt, -# ckpt_dir=args.ckpt_dir, -# temperature=args.temperature, -# top_k=args.top_k, -# top_p=args.top_p, -# max_new_tokens=args.max_new_tokens, -# tensor_parallel_size=args.tensor_parallel_size, -# pipeline_model_parallel_size=args.pipeline_model_parallel_size, -# context_parallel_size=args.context_parallel_size, -# output_file=args.output_file, -# ckpt_format=args.ckpt_format, -# seed=args.seed, -# vortex_style_fp8=args.fp8, # Vortex only applied FP8 to some layers. -# flash_decode=args.flash_decode, -# ) - - -# if __name__ == "__main__": -# main() +r"""Text generation (inference) workflow for Evo2 using Megatron Core. + +This module provides autoregressive text generation for Evo2 models using the +MCore inference infrastructure (StaticInferenceEngine, TextGenerationController). + +Based on: https://github.com/NVIDIA/Megatron-LM/blob/main/examples/inference/gpt/gpt_static_inference.py + +Usage (CLI): + torchrun --nproc_per_node 1 -m bionemo.evo2.run.infer \ + --ckpt-dir /path/to/mbridge/checkpoint \ + --prompt "|d__Bacteria;p__Pseudomonadota|" \ + --max-new-tokens 100 + +Usage (Python API): + from bionemo.evo2.run.infer import setup_inference_engine, generate + + # Setup engine (loads model, creates inference components) + engine, tokenizer = setup_inference_engine(ckpt_dir) + + # Generate text + results = generate(engine, prompts=["ATCGATCG"], max_new_tokens=100) +""" + +import argparse +import logging +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Dict, List, Optional + +import torch +import torch.distributed as dist +from megatron.bridge.training.checkpointing import _load_model_weights_from_checkpoint +from megatron.bridge.training.config import DistributedInitConfig, RNGConfig +from megatron.bridge.training.mixed_precision import get_mixed_precision_config +from megatron.bridge.training.tokenizers.tokenizer import _HuggingFaceTokenizer +from megatron.bridge.training.utils.checkpoint_utils import ( + file_exists, + get_checkpoint_run_config_filename, + read_run_config, +) +from megatron.bridge.utils.instantiate_utils import instantiate +from megatron.core import parallel_state +from megatron.core.inference.contexts import StaticInferenceContext +from megatron.core.inference.engines.static_engine import StaticInferenceEngine +from megatron.core.inference.inference_request import InferenceRequest +from megatron.core.inference.model_inference_wrappers.abstract_model_inference_wrapper import ( + AbstractModelInferenceWrapper, +) +from megatron.core.inference.model_inference_wrappers.inference_wrapper_config import ( + InferenceWrapperConfig, +) +from megatron.core.inference.sampling_params import SamplingParams +from megatron.core.inference.text_generation_controllers.text_generation_controller import ( + TextGenerationController, +) +from megatron.core.transformer.module import Float16Module +from megatron.core.utils import get_model_config + +from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH +from bionemo.evo2.models.evo2_provider import HyenaInferenceContext +from bionemo.evo2.run.predict import initialize_inference_distributed, resolve_checkpoint_path + + +logger: logging.Logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +# ============================================================================= +# Evo2 Model Inference Wrapper +# ============================================================================= + + +class Evo2ModelInferenceWrapper(AbstractModelInferenceWrapper): + """Inference wrapper for Evo2 models. + + Extends the abstract wrapper to provide Evo2-specific input preparation + and forward pass handling for autoregressive text generation. + """ + + def __init__( + self, + model: torch.nn.Module, + inference_wrapper_config: InferenceWrapperConfig, + inference_context: Optional[StaticInferenceContext] = None, + ): + """Initialize the Evo2 inference wrapper. + + Args: + model: The Evo2 model to wrap for inference. + inference_wrapper_config: Configuration with hidden size, vocab size, etc. + inference_context: Context for managing state and sequence offsets. + """ + super().__init__(model, inference_wrapper_config, inference_context) + + def prep_inference_input(self, prompts_tokens: torch.Tensor) -> Dict[str, Any]: + """Prepare the inference input data. + + Args: + prompts_tokens: A tensor of shape [batch_size, max_seq_len] + + Returns: + Dict with tokens, attention_mask, and position_ids + """ + batch_size, seq_len = prompts_tokens.shape + device = prompts_tokens.device + + # For Evo2/Hyena models, position_ids are sequential + position_ids = torch.arange(seq_len, dtype=torch.long, device=device).unsqueeze(0).expand(batch_size, -1) + + # Evo2 uses causal attention - for flash attention backend, mask is None + attention_mask = None + + return { + "tokens": prompts_tokens, + "attention_mask": attention_mask, + "position_ids": position_ids, + } + + def get_batch_for_context_window( + self, + inference_input: Dict[str, Any], + context_start_position: int, + context_end_position: int, + ) -> Dict[str, Any]: + """Extract batch for a specific context window. + + Called iteratively during autoregressive generation. + + Args: + inference_input: Full inference input dict + context_start_position: Start of context window + context_end_position: End of context window + + Returns: + Dict with sliced tokens, positions, and attention mask + """ + tokens = inference_input["tokens"] + position_ids = inference_input["position_ids"] + attention_mask = inference_input["attention_mask"] + + tokens2use = tokens[:, context_start_position:context_end_position] + positions2use = position_ids[:, context_start_position:context_end_position] + + if attention_mask is not None: + attention_mask2use = attention_mask[ + ..., context_start_position:context_end_position, :context_end_position + ] + else: + attention_mask2use = None + + return { + "tokens": tokens2use, + "position_ids": positions2use, + "attention_mask": attention_mask2use, + } + + def _forward(self, inference_input: Dict[str, Any]) -> torch.Tensor: + """Run a forward pass of the model. + + Override to pass HyenaInferenceContext properly. + + Args: + inference_input: The input data dict. + + Returns: + The model output logits. + """ + tokens = inference_input["tokens"] + position_ids = inference_input["position_ids"] + attention_mask = inference_input["attention_mask"] + + return self.model( + tokens, + position_ids, + attention_mask, + inference_context=self.inference_context, + runtime_gather_output=True, + ) + + +# ============================================================================= +# Inference Components Container +# ============================================================================= + + +@dataclass +class Evo2InferenceComponents: + """Container for Evo2 inference components. + + This dataclass holds all the components needed for text generation, + making it easy to pass around and reuse. + """ + + inference_engine: StaticInferenceEngine + tokenizer: _HuggingFaceTokenizer + inference_wrapper: Evo2ModelInferenceWrapper + inference_context: HyenaInferenceContext + model: torch.nn.Module + + +# ============================================================================= +# Public API: Setup and Generate Functions +# ============================================================================= + + +def setup_inference_engine( + ckpt_dir: Path, + *, + max_seq_length: int = 8192, + max_batch_size: int = 1, + tensor_parallel_size: int = 1, + pipeline_model_parallel_size: int = 1, + context_parallel_size: int = 1, + mixed_precision_recipe: Optional[str] = None, + random_seed: int = 1234, +) -> Evo2InferenceComponents: + """Setup the Evo2 inference engine and related components. + + This function loads the model, creates the inference wrapper, and sets up + all necessary components for text generation. + + Args: + ckpt_dir: Path to MBridge checkpoint directory. + max_seq_length: Maximum sequence length for generation. + max_batch_size: Maximum batch size for inference. + tensor_parallel_size: Tensor parallelism degree. + pipeline_model_parallel_size: Pipeline parallelism degree (must be 1). + context_parallel_size: Context parallelism degree. + mixed_precision_recipe: Override mixed precision recipe. + random_seed: Random seed for reproducibility. + + Returns: + Evo2InferenceComponents containing all inference components. + + Example: + >>> components = setup_inference_engine(Path("/path/to/checkpoint"), max_batch_size=4) + >>> results = generate(components, prompts=["ATCG", "GCTA"], max_new_tokens=100) + """ + if pipeline_model_parallel_size != 1: + raise ValueError("Pipeline parallelism > 1 is not supported for inference.") + + # ------------------------------------------------------------------------- + # Step 1: Load configuration from checkpoint + # ------------------------------------------------------------------------- + resolved_ckpt_dir = resolve_checkpoint_path(ckpt_dir) + logger.info(f"Loading configuration from checkpoint: {resolved_ckpt_dir}") + + run_config_filename = get_checkpoint_run_config_filename(str(resolved_ckpt_dir)) + if not file_exists(run_config_filename): + raise FileNotFoundError(f"run_config.yaml not found at {run_config_filename}") + + run_config = read_run_config(run_config_filename) + model_provider = instantiate(run_config["model"]) + logger.info(f"Instantiated model provider: {type(model_provider).__name__}") + + # ------------------------------------------------------------------------- + # Step 2: Configure parallelism and precision + # ------------------------------------------------------------------------- + model_provider.tensor_model_parallel_size = tensor_parallel_size + model_provider.pipeline_model_parallel_size = pipeline_model_parallel_size + model_provider.context_parallel_size = context_parallel_size + model_provider.sequence_parallel = tensor_parallel_size > 1 + + # Enable flash decode for inference + model_provider.flash_decode = True + + # Use bf16_mixed for inference to avoid FP8 issues + if mixed_precision_recipe is not None: + mp_config = get_mixed_precision_config(mixed_precision_recipe) + else: + mp_config = get_mixed_precision_config("bf16_mixed") + + mp_config.finalize() + mp_config.setup(model_provider) + + # ------------------------------------------------------------------------- + # Step 3: Load tokenizer + # ------------------------------------------------------------------------- + tokenizer_dir = resolved_ckpt_dir / "tokenizer" + if tokenizer_dir.exists(): + tokenizer = _HuggingFaceTokenizer(tokenizer_dir) + else: + tokenizer = _HuggingFaceTokenizer(DEFAULT_HF_TOKENIZER_MODEL_PATH) + + model_provider.vocab_size = tokenizer.vocab_size + model_provider.should_pad_vocab = True + + # ------------------------------------------------------------------------- + # Step 4: Initialize distributed environment + # ------------------------------------------------------------------------- + rng_config = instantiate(run_config.get("rng")) if run_config.get("rng") else RNGConfig(seed=random_seed) + dist_config = instantiate(run_config.get("dist")) if run_config.get("dist") else DistributedInitConfig() + + from megatron.bridge.utils.common_utils import get_world_size_safe + + model_parallel_size = tensor_parallel_size * pipeline_model_parallel_size * context_parallel_size + world_size = get_world_size_safe() + data_parallel_size = world_size // model_parallel_size + + initialize_inference_distributed( + tensor_model_parallel_size=tensor_parallel_size, + pipeline_model_parallel_size=pipeline_model_parallel_size, + context_parallel_size=context_parallel_size, + micro_batch_size=max_batch_size, + global_batch_size=max_batch_size * data_parallel_size, + rng_config=rng_config, + dist_config=dist_config, + ) + logger.info("Initialized distributed environment") + + # ------------------------------------------------------------------------- + # Step 5: Create model and load weights + # ------------------------------------------------------------------------- + logger.info("Creating model...") + model_provider.finalize() + + raw_model = model_provider.provide(pre_process=True, post_process=True).eval().cuda() + + logger.info(f"Loading weights from: {resolved_ckpt_dir}") + _load_model_weights_from_checkpoint( + checkpoint_path=str(resolved_ckpt_dir), + model=[raw_model], + dist_ckpt_strictness="ignore_all", + ) + logger.info("Weights loaded successfully") + + # Wrap with Float16Module + model = Float16Module(model_provider, raw_model) + + # ------------------------------------------------------------------------- + # Step 6: Setup MCore inference infrastructure + # ------------------------------------------------------------------------- + # Create inference wrapper config + model_config = get_model_config(raw_model) + inference_wrapper_config = InferenceWrapperConfig( + hidden_size=model_config.hidden_size, + inference_max_requests=max_batch_size, + inference_max_seq_length=max_seq_length, + inference_batch_times_seqlen_threshold=max_seq_length * max_batch_size, + params_dtype=torch.bfloat16, + padded_vocab_size=tokenizer.vocab_size, + ) + + # Create Hyena-specific inference context + inference_context = HyenaInferenceContext( + max_batch_size=max_batch_size, + max_sequence_length=max_seq_length, + ) + # Don't materialize only last token - we need full logits for sampling + inference_context.materialize_only_last_token_logits = False + + # Create the inference wrapper + inference_wrapper = Evo2ModelInferenceWrapper( + model=model, + inference_wrapper_config=inference_wrapper_config, + inference_context=inference_context, + ) + + # Create the text generation controller + text_generation_controller = TextGenerationController( + inference_wrapped_model=inference_wrapper, + tokenizer=tokenizer, + ) + + # Create the static inference engine (using legacy mode for simplicity) + inference_engine = StaticInferenceEngine( + text_generation_controller=text_generation_controller, + max_batch_size=max_batch_size, + random_seed=random_seed, + legacy=True, # Use legacy static engine + ) + + return Evo2InferenceComponents( + inference_engine=inference_engine, + tokenizer=tokenizer, + inference_wrapper=inference_wrapper, + inference_context=inference_context, + model=model, + ) + + +def generate( + components: Evo2InferenceComponents, + prompts: List[str], + *, + max_new_tokens: int = 100, + temperature: float = 1.0, + top_k: int = 0, + top_p: float = 0.0, + return_log_probs: bool = False, +) -> List[InferenceRequest]: + """Generate text using the Evo2 inference engine. + + Args: + components: Inference components from setup_inference_engine. + prompts: List of prompt strings to generate from. + max_new_tokens: Maximum number of tokens to generate. + temperature: Sampling temperature (higher = more random). + top_k: Top-k sampling parameter (0 = disabled, 1 = greedy). + top_p: Nucleus sampling parameter (0 = disabled). + return_log_probs: Whether to return log probabilities. + + Returns: + List of InferenceRequest objects containing generated text and metadata. + + Example: + >>> components = setup_inference_engine(ckpt_dir) + >>> results = generate(components, ["ATCGATCG"], max_new_tokens=50, top_k=1) + >>> print(results[0].generated_text) + """ + # Reset inference context before generation + components.inference_context.reset() + + sampling_params = SamplingParams( + temperature=temperature, + top_k=max(0, top_k), + top_p=top_p if top_p > 0 else 0.0, + num_tokens_to_generate=max_new_tokens, + return_log_probs=return_log_probs, + ) + + results = components.inference_engine.generate( + prompts=prompts, + sampling_params=sampling_params, + ) + + # Reset context after generation + components.inference_context.reset() + + return results + + +# ============================================================================= +# CLI: Full Inference Workflow +# ============================================================================= + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments for Evo2 inference. + + Returns: + Parsed arguments namespace + """ + default_prompt = ( + "|d__Bacteria;" + + "p__Pseudomonadota;" + + "c__Gammaproteobacteria;" + + "o__Enterobacterales;" + + "f__Enterobacteriaceae;" + + "g__Escherichia;" + + "s__Escherichia|" + ) + + ap = argparse.ArgumentParser( + description="Generate text with Evo2 models using MCore inference", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + # Required arguments + ap.add_argument( + "--ckpt-dir", + type=Path, + required=True, + help="Path to MBridge checkpoint directory", + ) + + # Generation arguments + ap.add_argument( + "--prompt", + type=str, + default=default_prompt, + help="Prompt text for generation", + ) + ap.add_argument("--max-new-tokens", type=int, default=100, help="Maximum tokens to generate") + ap.add_argument("--temperature", type=float, default=1.0, help="Sampling temperature") + ap.add_argument("--top-k", type=int, default=0, help="Top-k sampling (0 = disabled)") + ap.add_argument("--top-p", type=float, default=0.0, help="Top-p nucleus sampling (0 = disabled)") + ap.add_argument("--seed", type=int, default=None, help="Random seed") + + # Parallelism arguments + ap.add_argument("--tensor-parallel-size", type=int, default=1, help="Tensor parallelism") + ap.add_argument("--pipeline-model-parallel-size", type=int, choices=[1], default=1, help="Pipeline parallelism") + ap.add_argument("--context-parallel-size", type=int, default=1, help="Context parallelism") + + # Output arguments + ap.add_argument("--output-file", type=Path, default=None, help="Save generated text to file") + + # Precision arguments + ap.add_argument("--mixed-precision-recipe", type=str, default=None, help="Override precision recipe") + + # Model arguments + ap.add_argument("--max-seq-length", type=int, default=8192, help="Max sequence length") + + return ap.parse_args() + + +def infer( + prompt: str, + ckpt_dir: Path, + *, + max_new_tokens: int = 100, + temperature: float = 1.0, + top_k: int = 0, + top_p: float = 0.0, + seed: Optional[int] = None, + tensor_parallel_size: int = 1, + pipeline_model_parallel_size: int = 1, + context_parallel_size: int = 1, + output_file: Optional[Path] = None, + mixed_precision_recipe: Optional[str] = None, + max_seq_length: int = 8192, +) -> str: + """Run autoregressive text generation with Evo2 using MCore inference. + + This is the main CLI entry point that sets up everything and runs inference. + For programmatic usage, prefer setup_inference_engine + generate. + + Args: + prompt: Input text prompt for generation. + ckpt_dir: Path to MBridge checkpoint directory. + max_new_tokens: Maximum number of tokens to generate. + temperature: Sampling temperature (higher = more random). + top_k: Top-k sampling parameter (0 = disabled). + top_p: Nucleus sampling parameter (0 = disabled). + seed: Random seed for reproducibility. + tensor_parallel_size: Tensor parallelism degree. + pipeline_model_parallel_size: Pipeline parallelism degree (must be 1). + context_parallel_size: Context parallelism degree. + output_file: Optional path to save generated text. + mixed_precision_recipe: Override mixed precision recipe. + max_seq_length: Maximum sequence length. + + Returns: + The generated text string. + """ + random_seed = seed or 1234 + + # Setup inference components + components = setup_inference_engine( + ckpt_dir=ckpt_dir, + max_seq_length=max_seq_length, + tensor_parallel_size=tensor_parallel_size, + pipeline_model_parallel_size=pipeline_model_parallel_size, + context_parallel_size=context_parallel_size, + mixed_precision_recipe=mixed_precision_recipe, + random_seed=random_seed, + ) + + logger.info(f"Generating from prompt: {prompt[:50]}...") + + # Generate + results = generate( + components, + prompts=[prompt], + max_new_tokens=max_new_tokens, + temperature=temperature, + top_k=top_k, + top_p=top_p, + ) + + # Extract generated text + generated_text = results[0].generated_text if results else "" + + # Output results + if parallel_state.get_data_parallel_rank() == 0: + print(f"\n=== Generated Text ===\n{generated_text}\n", file=sys.stdout) + + if output_file is not None: + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, "w") as f: + f.write(generated_text) + logger.info(f"Saved generated text to: {output_file}") + + logger.info("Inference complete!") + + # Cleanup + if dist.is_initialized(): + dist.barrier() + dist.destroy_process_group() + + return generated_text + + +# ============================================================================= +# Entry Point +# ============================================================================= + + +def main() -> None: + """CLI entry point for Evo2 text generation.""" + args = parse_args() + infer( + prompt=args.prompt, + ckpt_dir=args.ckpt_dir, + max_new_tokens=args.max_new_tokens, + temperature=args.temperature, + top_k=args.top_k, + top_p=args.top_p, + seed=args.seed, + tensor_parallel_size=args.tensor_parallel_size, + pipeline_model_parallel_size=args.pipeline_model_parallel_size, + context_parallel_size=args.context_parallel_size, + output_file=args.output_file, + mixed_precision_recipe=args.mixed_precision_recipe, + max_seq_length=args.max_seq_length, + ) + + +if __name__ == "__main__": + main() diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/infer_example_simple.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/infer_example_simple.py new file mode 100644 index 0000000000..187daf1306 --- /dev/null +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/infer_example_simple.py @@ -0,0 +1,166 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024 Arc Institute. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024 Michael Poli. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024 Stanford University. All rights reserved +# SPDX-License-Identifier: LicenseRef-Apache2 +# +# 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. + +"""Simple autoregressive generation example for Evo2 models. + +This module provides a straightforward implementation of autoregressive text generation +that directly calls the model forward pass without using the full MCore inference +infrastructure. This is useful for: + +1. Understanding how autoregressive generation works at a low level +2. Debugging and testing model behavior +3. Custom generation workflows that don't fit the MCore API + +For production use, prefer the MCore-based inference in `bionemo.evo2.run.infer`. + +Example: + >>> from bionemo.evo2.run.infer_example_simple import generate_tokens_simple + >>> from bionemo.evo2.models.evo2_provider import HyenaInferenceContext + >>> + >>> # Assuming model and tokenizer are already loaded + >>> ctx = HyenaInferenceContext(max_batch_size=1, max_sequence_length=8192) + >>> prompt_tokens = tokenizer.text_to_ids("ATCGATCG") + >>> tokens = generate_tokens_simple(model, prompt_tokens, max_new_tokens=100, inference_context=ctx) +""" + +from typing import List, Optional + +import torch + +from bionemo.evo2.models.evo2_provider import HyenaInferenceContext + + +@torch.inference_mode() +def generate_tokens_simple( + model: torch.nn.Module, + prompt_tokens: torch.Tensor, + max_new_tokens: int, + temperature: float = 1.0, + top_k: int = 0, + inference_context: Optional[HyenaInferenceContext] = None, +) -> List[int]: + """Generate tokens autoregressively using direct model forward passes. + + This function implements autoregressive generation by repeatedly calling + the model's forward pass with the previously generated token. It properly + manages the HyenaInferenceContext to cache SSM state between steps. + + Unlike the MCore-based inference in `bionemo.evo2.run.infer`, this function: + - Directly calls model.forward() instead of using inference wrappers + - Manually manages sequence_len_offset and decode_mode + - Does not use TextGenerationController or StaticInferenceEngine + + Args: + model: The Evo2 model (typically Float16Module wrapped). + prompt_tokens: Input prompt token IDs as a tensor of shape [1, seq_len]. + max_new_tokens: Maximum number of tokens to generate. + temperature: Sampling temperature. Higher values (e.g., 1.0) make output + more random, lower values make it more deterministic. Default is 1.0. + top_k: Top-k sampling parameter. If > 0, only the top k tokens are + considered for sampling. Use top_k=1 for greedy decoding. Default is 0. + inference_context: Hyena-specific context for SSM state caching. + If provided, enables efficient autoregressive generation by caching + filter states between decode steps. + + Returns: + List of generated token IDs (excluding the prompt). + + Example: + >>> # Setup + >>> ctx = HyenaInferenceContext(max_batch_size=1, max_sequence_length=8192) + >>> prompt = torch.tensor([[65, 84, 67, 71]], device="cuda") # "ATCG" + >>> + >>> # Generate with greedy decoding + >>> tokens = generate_tokens_simple( + ... model, prompt, max_new_tokens=10, top_k=1, inference_context=ctx + ... ) + >>> print(tokens) # [65, 84, 67, 71, ...] (continues the pattern) + + Note: + For production use, prefer `bionemo.evo2.run.infer.generate()` which uses + the MCore inference infrastructure with proper batching, sampling, and + distributed support. + """ + device = prompt_tokens.device + generated_tokens: List[int] = [] + prompt_len = prompt_tokens.shape[1] + + # Ensure context starts in prefill mode + if inference_context is not None: + inference_context.enable_prefill_mode() + + # Process the full prompt first (prefill phase) + # This computes and caches the SSM states for all prompt tokens + logits = model( + input_ids=prompt_tokens, + position_ids=None, + attention_mask=None, + labels=None, + runtime_gather_output=True, + inference_context=inference_context, + ) + + # Update sequence_len_offset after prefill (MCore wrapper does this automatically) + if inference_context is not None: + inference_context.increment_sequence_len_offset(prompt_len) + # Switch to decode mode after prefill is complete + inference_context.enable_decode_mode() + + # Get next token from last position logits + next_token_logits = logits[0, -1, :].clone() + + # Generate tokens autoregressively (decode phase) + for _ in range(max_new_tokens): + # Apply temperature scaling + if temperature > 0: + next_token_logits = next_token_logits / temperature + + # Apply top-k filtering + if top_k > 0: + indices_to_remove = next_token_logits < torch.topk(next_token_logits, top_k)[0][..., -1, None] + next_token_logits[indices_to_remove] = float("-inf") + + # Sample or argmax + if temperature > 0 and top_k != 1: + probs = torch.softmax(next_token_logits, dim=-1) + next_token = torch.multinomial(probs, num_samples=1).item() + else: + next_token = torch.argmax(next_token_logits).item() + + generated_tokens.append(next_token) + + # Prepare next input (single token) + next_input = torch.tensor([[next_token]], dtype=torch.long, device=device) + + # Forward pass with cached state + logits = model( + input_ids=next_input, + position_ids=None, + attention_mask=None, + labels=None, + runtime_gather_output=True, + inference_context=inference_context, + ) + + # Update sequence_len_offset after each decode step + if inference_context is not None: + inference_context.increment_sequence_len_offset(1) + + next_token_logits = logits[0, -1, :].clone() + + return generated_tokens diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/predict.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/predict.py index 968a9f52f8..e65d4cdf40 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/predict.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/predict.py @@ -16,697 +16,1407 @@ # See the License for the specific language governing permissions and # limitations under the License. -# FIXME get this working with megatron bridge - -# import argparse -# import functools -# import tempfile -# from pathlib import Path -# from typing import Any, Literal - -# import nemo.lightning as nl -# import torch -# from bionemo.evo2.data.fasta_dataset import SimpleFastaDataset -# from bionemo.evo2.models.llama import LLAMA_MODEL_OPTIONS - -# # Add import for Mamba models -# from bionemo.evo2.models.mamba import MAMBA_MODEL_OPTIONS, MambaModel -# from bionemo.evo2.models.peft import Evo2LoRA -# from bionemo.evo2.run.utils import infer_model_type, patch_eden_tokenizer -# from bionemo.llm.data import collate -# from bionemo.llm.lightning import LightningPassthroughPredictionMixin -# from bionemo.llm.utils.callbacks import PredictionWriter -# from lightning.pytorch import LightningDataModule -# from megatron.core import parallel_state -# from megatron.core.enums import Fp8Recipe -# from megatron.core.tensor_parallel.mappings import _gather_along_last_dim -# from megatron.core.utils import get_batch_on_this_cp_rank -# from nemo.collections.llm.gpt.data.megatron.hyena.evo2_dataset import Evo2Dataset -# from nemo.collections.llm.gpt.model.base import GPTModel, get_packed_seq_params -# from nemo.collections.llm.gpt.model.hyena import HYENA_MODEL_OPTIONS, HyenaModel -# from nemo.collections.nlp.modules.common.tokenizer_utils import get_nmt_tokenizer -# from nemo.lightning import NeMoLogger -# from nemo.lightning.data import WrappedDataLoader -# from nemo.utils import logging as logger -# from torch import Tensor - - -# CheckpointFormats = Literal["torch_dist", "zarr"] - -# SHUFFLE_MESSAGE = ( -# "Per token log probabilities are not supported when using context parallelism. The results will be " -# "zigzag shuffled along the sequence dimension. Raise a feature request if you need this and do " -# "not want to manually do the unshuffling yourself. You need to undo the shuffling that happened in " -# "`megatron.core.utils.get_batch_on_this_cp_rank`." -# ) - - -# def parse_args(): -# """Parse arguments for Evo2 inference.""" -# ap = argparse.ArgumentParser() -# ap.add_argument("--num-nodes", type=int, default=1, help="Number of nodes to use for prediction, defaults to 1.") -# ap.add_argument( -# "--devices", -# type=int, -# help="Number of devices to use for prediction, defaults to tensor_model_parallel_size * pipeline_model_parallel_size * context_parallel_size.", -# ) -# ap.add_argument( -# "--eden-tokenizer", -# action="store_true", -# help="Patch the tokenizer to work with the one used in training the Eden model.", -# ) -# ap.add_argument("--fasta", type=Path, required=True, help="Fasta path from which to generate logit predictions.") -# ap.add_argument("--ckpt-dir", type=Path, required=True, help="NeMo2 checkpoint directory for inference.") -# ap.add_argument("--min-length", type=int, required=False, help="Minimum sequence length for padding.") -# ap.add_argument("--prepend-bos", action="store_true", help="Prepend BOS token to sequences. Defaults to False.") -# ap.add_argument( -# "--mask-phylogenetic-tags", -# action="store_true", -# help="Mask phylogenetic tags in loss computation. Defaults to False.", -# ) -# ap.add_argument("--tensor-parallel-size", type=int, default=1, help="Order of tensor parallelism. Defaults to 1.") -# ap.add_argument( -# "--pipeline-model-parallel-size", -# type=int, -# choices=[1], -# default=1, -# help="Order of pipeline parallelism. Defaults to 1 and currently only 1 is supported.", -# ) -# ap.add_argument( -# "--context-parallel-size", type=int, default=1, help="Order of context parallelism. Defaults to 1." -# ) -# ap.add_argument( -# "--fp8-recipe", -# type=str, -# default="delayed", -# choices=list(Fp8Recipe.__members__.keys()), -# help="FP8 recipe to use for FP8 tensors in the forward and backward pass. Note that some recipes are only " -# "supported by certain architectures. For example 'mxfp8' requires at least blackwell, and 'blockwise' is only " -# "implemented for hopper (but not blackwell). 'tensorwise' and 'delayed' are currently supported by all " -# "architectures, but 'tensorwise' is preferred over 'delayed' which is the default for historical reasons.", -# ) -# ap.add_argument( -# "--no-sequence-parallel", -# action="store_true", -# help="When using TP, skip sequence parallelism. Otherwise sequence parallelism is used whenever tensor " -# "parallelism is used. sequence parallelism should save a small amount of GPU memory so it's on" -# " by default.", -# ) -# ap.add_argument("--micro-batch-size", type=int, default=1, help="Batch size for prediction. Defaults to 1.") -# ap.add_argument( -# "--write-interval", -# type=str, -# default="epoch", -# choices=["epoch", "batch"], -# help="Interval to write predictions to disk. If doing very large predictions, you may want to set this to 'batch'.", -# ) -# ap.add_argument( -# "--model-size", -# type=str, -# default="7b_arc_longcontext", -# choices=sorted( -# list(HYENA_MODEL_OPTIONS.keys()) + list(MAMBA_MODEL_OPTIONS.keys()) + list(LLAMA_MODEL_OPTIONS.keys()) -# ), -# help="Model size to use. Defaults to '7b_arc_longcontext'.", -# ) -# # output args: -# ap.add_argument( -# "--output-dir", -# type=Path, -# default=None, -# help="Output dir that will contain the generated text produced by the Evo2 model. If not provided, the output will be logged.", -# ) -# ap.add_argument( -# "--files-per-subdir", -# type=int, -# help="Number of files to write to each subdirectory. If provided, subdirectories with N files each will be created. Ignored unless --write-interval is 'batch'.", -# ) -# ap.add_argument( -# "--full-fp8", -# action="store_true", -# help="Use full FP8 precision (faster but less accurate) rather than vortex style which " -# "only applies FP8 to the projection layer of the hyena mixer, when using FP8.", -# ) -# ap.add_argument("--fp8", action="store_true", help="Use FP8 precision. Defaults to BF16.") -# # extra: -# ap.add_argument( -# "--ckpt-format", -# type=str, -# choices=["torch_dist", "zarr"], -# default="torch_dist", -# help="Specify checkpoint format to use. Defaults to 'torch_dist', as 'zarr' is deprecated.", -# ) -# ap.add_argument( -# "--output-log-prob-seqs", action="store_true", help="Output log probability of sequences. Defaults to False." -# ) -# ap.add_argument( -# "--log-prob-collapse-option", -# choices=["sum", "mean", "per_token"], -# default="mean", -# help="How to collapse the log probabilities across the sequence dimension.", -# ) -# ap.add_argument( -# "--hybrid-override-pattern", -# type=str, -# help="Override the hybrid override pattern in the config (specifies hyena layer ordering and type).", -# ) -# ap.add_argument( -# "--num-layers", type=int, help="If set, override the number of layers specified in the requested config." -# ) -# ap.add_argument( -# "--seq-len-interpolation-factor", -# type=int, -# help="If set, override the sequence length interpolation factor specified in the requested config. If you " -# "know a model was trained with a specific interpolation factor for ROPE, provide it here, it can make a big " -# "difference in accuracy.", -# ) -# ap.add_argument( -# "--lora-checkpoint-path", -# type=Path, -# required=False, -# default=None, -# help="Path to the lora states to restore from.", -# ) -# return ap.parse_args() - - -# def _gather_along_cp_dim(input_, seq_dim: int = 1): -# """Gather tensors and concatenate along the last dimension.""" -# world_size = parallel_state.get_context_parallel_world_size() -# # Bypass the function if we are using only 1 GPU. -# if world_size == 1: -# return input_ - -# dim_size = list(input_.size()) -# dim_size[0] = dim_size[0] * world_size - -# output = torch.empty(dim_size, dtype=input_.dtype, device=torch.cuda.current_device()) -# # TODO: handle zigzag packing here. Currently this just gathers along ranks, but if you want to see the sequence in -# # the original order you need to undo the zigzag packing that happens in -# # `megatron.core.utils.get_batch_on_this_cp_rank`. -# torch.distributed.all_gather_into_tensor( -# output, input_.contiguous(), group=parallel_state.get_context_parallel_group() -# ) -# tensor_list = output.chunk(world_size, dim=0) -# output = torch.cat(tensor_list, dim=seq_dim).contiguous() - -# return output - - -# def _to_cpu(inputs: dict[str, Tensor]) -> dict[str, Tensor]: -# return {k: v.cpu() for k, v in inputs.items()} - - -# def _identity(inputs: dict[str, Tensor]) -> dict[str, Tensor]: -# return inputs - - -# class BasePredictor(LightningPassthroughPredictionMixin): -# """Base predictor for GPT-style models.""" - -# def __init__( -# self, -# *args, -# output_log_prob_seqs: bool = False, -# include_tokens_with_logprob_seqs: bool = False, -# log_prob_collapse_option: Literal["sum", "mean", "per_token"] = "mean", -# **kwargs, -# ): -# """Initialize the base predictor with arguments needed for writing predictions.""" -# super().__init__(*args, **kwargs) -# self.output_log_prob_seqs = output_log_prob_seqs -# self.log_prob_collapse_option = log_prob_collapse_option -# self.include_tokens_with_logprob_seqs = include_tokens_with_logprob_seqs -# self.shuffle_warning_raised = False - -# def predict_step( -# self, batch, batch_idx: int | None = None, to_cpu: bool = True -# ) -> Tensor | dict[str, Tensor] | None: -# """Alias for forward_step, also log the pad mask since sequences may not all have the same length.""" -# if len(batch) == 0: -# return -# assert self.training is False, "predict_step should be called in eval mode" -# with torch.no_grad(): -# forward_out = self.forward_step(batch) -# if not parallel_state.is_pipeline_last_stage(): -# return None -# # Reminder: the model's predictions for input i land at output i+1. To get everything to align, we prepend the -# # EOS token to the input sequences and take the outputs for all but the first token. -# forward_out_tp_gathered = _gather_along_last_dim( -# forward_out, group=parallel_state.get_tensor_model_parallel_group() -# ) - -# forward_out_gathered = _gather_along_cp_dim(forward_out_tp_gathered) -# loss_mask_gathered = _gather_along_cp_dim(batch["loss_mask"]) -# tokens_gathered = _gather_along_cp_dim(batch["tokens"]) -# cp_group_size = max(parallel_state.get_context_parallel_world_size(), 1) -# assert self.tokenizer.vocab_size == forward_out_gathered.shape[-1] -# to_cpu_fn = _to_cpu if to_cpu else _identity -# if self.output_log_prob_seqs: -# if self.log_prob_collapse_option == "per_token" and cp_group_size > 1 and not self.shuffle_warning_raised: -# logger.warning(SHUFFLE_MESSAGE) -# self.shuffle_warning_raised = True -# softmax_logprobs = torch.log_softmax(forward_out_gathered, dim=-1) -# softmax_logprobs = softmax_logprobs[:, :-1] -# input_ids = tokens_gathered[:, 1:] -# if softmax_logprobs.shape[1] != input_ids.shape[1]: -# raise RuntimeError( -# f"Softmax logprobs shape {softmax_logprobs.shape} does not match input ids shape {input_ids.shape}" -# ) - -# logprobs = torch.gather( -# softmax_logprobs, # Gather likelihoods... -# 2, # along the vocab dimension... -# input_ids.unsqueeze(-1), # using the token ids to index. -# ).squeeze(-1) -# log_prob_per_token = logprobs * loss_mask_gathered[:, 1:].float() -# if self.log_prob_collapse_option == "per_token": -# return to_cpu_fn( -# { -# "log_probs_seqs": log_prob_per_token, -# "seq_idx": batch["seq_idx"], -# "loss_mask": loss_mask_gathered[:, 1:], -# } -# ) -# else: -# log_prob_seqs = torch.sum(log_prob_per_token, dim=1) -# if self.log_prob_collapse_option == "mean": -# log_prob_seqs = log_prob_seqs / torch.clamp(loss_mask_gathered[:, 1:].float().sum(dim=-1), min=1.0) -# return to_cpu_fn({"log_probs_seqs": log_prob_seqs, "seq_idx": batch["seq_idx"]}) -# else: -# # If the user wants to match back to logits, then they will need to do the offsetting logic themselves. -# if cp_group_size > 1 and not self.shuffle_warning_raised: -# logger.warning(SHUFFLE_MESSAGE) -# self.shuffle_warning_raised = True -# logprob_seqs_result = { -# "token_logits": forward_out_gathered, -# "pad_mask": loss_mask_gathered, -# "seq_idx": batch["seq_idx"], -# } -# if self.include_tokens_with_logprob_seqs: -# logprob_seqs_result["tokens"] = tokens_gathered -# # Note, to match up tokens with logprobs, you need to offset by 1. Eg something like this: -# # shifted_token_logits = token_logits[:, :-1] -# # shifted_pad_mask = pad_mask[:, 1:] -# # shifted_tokens = tokens[:, 1:] -# return to_cpu_fn(logprob_seqs_result) - - -# class HyenaPredictor(BasePredictor, HyenaModel): -# """A predictor for the Hyena model. This adds in the predict step and the passthrough method.""" - -# def configure_model(self, *args, **kwargs) -> None: -# """Configure the model.""" -# super().configure_model(*args, **kwargs) -# self.trainer.strategy._init_model_parallel = True - - -# class MambaPredictor(BasePredictor, MambaModel): -# """Mamba model for prediction with additional metrics.""" - - -# class LlamaPredictor(BasePredictor, GPTModel): -# """Llama model for prediction with additional metrics.""" - - -# def hyena_predict_forward_step(model, batch) -> torch.Tensor: -# """Performs a forward step for the Hyena model. - -# Args: -# model: The Hyena model -# batch: Dictionary containing input batch data with keys: -# - tokens: Input token IDs -# - position_ids: Position IDs -# - labels: Labels for loss computation -# - loss_mask: Mask for loss computation - -# Returns: -# torch.Tensor: Output from the model forward pass -# """ -# forward_args = { -# "input_ids": batch["tokens"], -# "position_ids": batch["position_ids"], -# # "labels": batch["labels"], -# # "loss_mask": batch["loss_mask"], -# } - -# forward_args["attention_mask"] = None -# if "cu_seqlens" in batch: -# forward_args["packed_seq_params"] = get_packed_seq_params(batch) -# return model(**forward_args) - - -# def hyena_predict_data_step(dataloader_iter) -> dict[str, torch.Tensor]: -# """Data step for the Hyena model prediction. Modified from the original gpt data step to include the seq_idx.""" -# from megatron.core import parallel_state - -# # Based on: https://github.com/NVIDIA/Megatron-LM/blob/main/pretrain_gpt.py#L87 -# # https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/nlp/models/language_modeling/megatron_gpt_model.py#L828-L842 - -# batch = next(dataloader_iter) - -# _batch: dict -# if isinstance(batch, tuple) and len(batch) == 3: -# _batch = batch[0] -# else: -# _batch = batch - -# required_device_keys = set() -# required_host_keys = set() - -# required_device_keys.add("attention_mask") -# if "cu_seqlens" in _batch: -# required_device_keys.add("cu_seqlens") -# required_host_keys.add("cu_seqlens_argmin") -# required_host_keys.add("max_seqlen") - -# if parallel_state.is_pipeline_first_stage(): -# required_device_keys.update(("tokens", "position_ids")) -# include_seq_idx = False -# if parallel_state.is_pipeline_last_stage(): -# include_seq_idx = True -# required_device_keys.update(("labels", "tokens", "loss_mask")) - -# _batch_required_keys = {} -# for key, val in _batch.items(): -# if key in required_device_keys: -# _batch_required_keys[key] = val.cuda(non_blocking=True) -# elif key in required_host_keys: -# _batch_required_keys[key] = val.cpu() -# else: -# _batch_required_keys[key] = None - -# # slice batch along sequence dimension for context parallelism -# output = get_batch_on_this_cp_rank(_batch_required_keys) -# if include_seq_idx: -# output["seq_idx"] = _batch["seq_idx"].cuda(non_blocking=True) -# return output - - -# class PredictDataModule(LightningDataModule): -# """Create a dataloader for prediction.""" - -# def __init__( -# self, -# dataset: torch.utils.data.Dataset, -# batch_size: int = 1, -# tokenizer=None, -# min_length: int | None = None, -# ): -# """Create a dataloader for prediction.""" -# super().__init__() -# self.dataset = dataset -# self.batch_size = batch_size -# self.tokenizer = tokenizer -# self.min_length = min_length -# default_pad_id = 0 -# self.pad_token_id = getattr(tokenizer, "pad_id", default_pad_id) if tokenizer is not None else default_pad_id - -# def setup(self, stage: str | None = None) -> None: -# """Set up the dataloader.""" -# pass - -# def predict_dataloader(self): -# """Create a dataloader for prediction.""" -# # need to use this to communicate that we are in predict mode and safe to not drop last batch -# return WrappedDataLoader( -# mode="predict", -# dataset=self.dataset, -# batch_size=self.batch_size, -# num_workers=8, -# shuffle=False, -# drop_last=False, -# collate_fn=functools.partial( -# collate.padding_collate_fn, -# padding_values={"tokens": self.pad_token_id, "position_ids": self.pad_token_id, "loss_mask": False}, -# min_length=self.min_length, -# max_length=None, -# ), -# ) - - -# def predict( -# fasta_path: Path, -# ckpt_dir: str, -# output_dir: Path, -# tensor_parallel_size: int, -# pipeline_model_parallel_size: int, -# context_parallel_size: int, -# num_nodes: int = 1, -# devices: int | None = None, -# eden_tokenizer: bool = False, -# model_size: str = "7b", -# ckpt_format: CheckpointFormats = "torch_dist", -# fp8: bool = False, -# full_fp8: bool = False, -# fp8_recipe: str = "delayed", -# work_dir: Path | None = None, -# micro_batch_size: int = 1, -# output_log_prob_seqs: bool = False, -# log_prob_collapse_option: Literal["sum", "mean", "per_token"] = "mean", -# write_interval: Literal["epoch", "batch"] = "epoch", -# prepend_bos: bool = False, -# no_sequence_parallel: bool = False, -# hybrid_override_pattern: str | None = None, -# num_layers: int | None = None, -# seq_len_interpolation_factor: int | None = None, -# files_per_subdir: int | None = None, -# lora_checkpoint_path: Path | None = None, -# mask_phylogenetic_tags: bool = False, -# min_length: int | None = None, -# extra_callbacks: list | None = None, # use this for making testing the predict loop easier. -# ): -# """Inference workflow for Evo2. - -# Returns: -# None -# """ -# if fp8 and not full_fp8 and fp8_recipe != "delayed": -# logger.warning( -# "fp8_recipe is ignored when using fp8 and not full_fp8 since it is set inside of the layer " -# "config to match vortex style FP8." -# ) -# if work_dir is None: -# work_dir = Path(tempfile.mkdtemp()) -# if files_per_subdir is None and write_interval == "batch": -# logger.warning( -# "--files-per-subdir is not set with --write-interval batch, will write all predictions to a " -# "single directory. This may cause problems if you are predicting on a very large dataset." -# ) -# sequence_parallel = tensor_parallel_size > 1 and not no_sequence_parallel -# output_dir.mkdir(parents=True, exist_ok=True) # Make sure the output directory exists, files will be written here. -# model_parallel_size = tensor_parallel_size * pipeline_model_parallel_size * context_parallel_size -# if devices is None: -# devices = model_parallel_size -# world_size = num_nodes * devices -# if world_size % model_parallel_size != 0: -# raise ValueError( -# f"world_size must be divisible by model_parallel_size, got {world_size} and" -# f" {model_parallel_size}. Please set --num-nodes and --devices such that num_nodes * devices is divisible " -# "by model_parallel_size, which is TP * CP * PP." -# ) -# global_batch_size = micro_batch_size * world_size // model_parallel_size - -# callbacks = [ -# PredictionWriter( -# output_dir=output_dir, -# write_interval=write_interval, -# batch_dim_key_defaults={"token_logits": 0}, -# seq_dim_key_defaults={"token_logits": 1}, -# files_per_subdir=files_per_subdir, -# save_all_model_parallel_ranks=False, # only write one copy of predictions. -# ) -# ] -# if extra_callbacks is not None: -# callbacks.extend(extra_callbacks) - -# # The following two config options are really only used for testing, but may also be useful for getting output from -# # specific layers of the model. -# config_modifiers_init: dict[str, Any] = { -# "distribute_saved_activations": False if sequence_parallel and tensor_parallel_size > 1 else True, -# } -# if hybrid_override_pattern is not None: -# config_modifiers_init["hybrid_override_pattern"] = hybrid_override_pattern -# if num_layers is not None: -# config_modifiers_init["num_layers"] = num_layers -# if seq_len_interpolation_factor is not None: -# config_modifiers_init["seq_len_interpolation_factor"] = seq_len_interpolation_factor - -# tokenizer = get_nmt_tokenizer("byte-level") -# if eden_tokenizer: -# patch_eden_tokenizer(tokenizer) - -# model_type = infer_model_type(model_size) - -# # Select model config based on model type -# if model_type == "hyena": -# if model_size not in HYENA_MODEL_OPTIONS: -# raise ValueError(f"Invalid model size for Hyena: {model_size}") -# config = HYENA_MODEL_OPTIONS[model_size]( -# forward_step_fn=hyena_predict_forward_step, -# data_step_fn=hyena_predict_data_step, -# # Only use vortex style FP8 in the model config if using FP8 and not full FP8. This will only apply FP8 to -# # the projection layer of the hyena mixer. -# vortex_style_fp8=fp8 and not full_fp8, -# **config_modifiers_init, -# ) - -# if lora_checkpoint_path: -# model_transform = Evo2LoRA(peft_ckpt_path=str(lora_checkpoint_path)) -# callbacks.append(model_transform) -# else: -# model_transform = None - -# model = HyenaPredictor( -# config, -# tokenizer=tokenizer, -# output_log_prob_seqs=output_log_prob_seqs, -# log_prob_collapse_option=log_prob_collapse_option, -# model_transform=model_transform, -# ) -# elif model_type == "mamba": # mamba -# if model_size not in MAMBA_MODEL_OPTIONS: -# raise ValueError(f"Invalid model size for Mamba: {model_size}") -# config = MAMBA_MODEL_OPTIONS[model_size]( -# forward_step_fn=hyena_predict_forward_step, # Can reuse the same forward steps -# data_step_fn=hyena_predict_data_step, -# **config_modifiers_init, -# ) - -# model = MambaPredictor( -# config, -# tokenizer=tokenizer, -# output_log_prob_seqs=output_log_prob_seqs, -# log_prob_collapse_option=log_prob_collapse_option, -# ) -# elif model_type == "llama": -# if model_size not in LLAMA_MODEL_OPTIONS: -# raise ValueError(f"Invalid model size for Llama: {model_size}") -# config = LLAMA_MODEL_OPTIONS[model_size]( -# forward_step_fn=hyena_predict_forward_step, -# data_step_fn=hyena_predict_data_step, -# **config_modifiers_init, -# ) -# model = LlamaPredictor( -# config, -# tokenizer=tokenizer, -# output_log_prob_seqs=output_log_prob_seqs, -# log_prob_collapse_option=log_prob_collapse_option, -# ) -# else: -# # This shouldn't be possible to reach. -# raise ValueError(f"Invalid model type: {model_type}.") - -# # Create PTL trainer. -# trainer = nl.Trainer( -# accelerator="gpu", -# num_nodes=num_nodes, -# devices=devices, -# strategy=nl.MegatronStrategy( -# drop_last_batch=False, -# tensor_model_parallel_size=tensor_parallel_size, -# pipeline_model_parallel_size=pipeline_model_parallel_size, -# context_parallel_size=context_parallel_size, -# pipeline_dtype=torch.bfloat16, -# ckpt_load_optimizer=False, # Needs to be false for a normal model checkpoint. -# ckpt_save_optimizer=False, -# ckpt_async_save=False, -# sequence_parallel=sequence_parallel, -# save_ckpt_format=ckpt_format, -# ckpt_load_strictness="log_all", -# setup_optimizers=False, -# store_optimizer_states=False, -# configure_optimizers=False, -# data_sampler=nl.MegatronDataSampler( -# micro_batch_size=micro_batch_size, -# global_batch_size=global_batch_size, -# seq_len=8192, -# output_log=False, # this is needed for predict step to work -# ), -# ), -# log_every_n_steps=1, -# limit_val_batches=10, -# num_sanity_val_steps=0, -# callbacks=callbacks, -# plugins=nl.MegatronMixedPrecision( -# precision="bf16-mixed", -# params_dtype=torch.bfloat16, -# # Only use FP8 in this plugin when using full FP8 precision and FP8. -# # Otherwise use vortex_style_fp8 in the model config. -# fp8_recipe=fp8_recipe, -# fp8="hybrid" if fp8 and full_fp8 else None, -# fp8_amax_history_len=16 if fp8 and full_fp8 else 1, -# fp8_amax_compute_algo="max" if fp8 and full_fp8 else "most_recent", -# ), -# ) - -# nemo_logger = NeMoLogger(log_dir=str(work_dir)) -# nemo_logger.setup(trainer, resume_if_exists=True) -# resume = nl.AutoResume( -# resume_if_exists=True, -# resume_ignore_no_checkpoint=False, -# resume_past_end=False, -# resume_from_path=str(ckpt_dir), -# restore_config=None, -# ) - -# resume.setup(trainer, model) # this pulls weights from the starting checkpoint. - -# if mask_phylogenetic_tags: - -# def custom_loss_masker(tokens): -# # Run the evo2 dataset mask_phylogenetic_tags function -# return Evo2Dataset.mask_phylogenetic_tags( -# tokens, -# Evo2Dataset.TAG_BOUNDS, -# Evo2Dataset.TAG_CHARS, -# tokenizer.eod if tokenizer is not None else Evo2Dataset.DEFAULT_EOD, -# Evo2Dataset.MAX_TAG_LEN, -# ) -# else: -# custom_loss_masker = None - -# dataset = SimpleFastaDataset(fasta_path, tokenizer, prepend_bos=prepend_bos, custom_loss_masker=custom_loss_masker) -# datamodule = PredictDataModule(dataset, batch_size=micro_batch_size, tokenizer=tokenizer, min_length=min_length) -# trainer.predict(model, datamodule=datamodule) # TODO return_predictions=False -# dataset.write_idx_map( -# output_dir -# ) # Finally write out the index map so we can match the predictions to the original sequences. - - -# def main(): -# """Entrypoint for Evo2 prediction (single inference step, no new tokens).""" -# args = parse_args() -# predict( -# num_nodes=args.num_nodes, -# devices=args.devices, -# fasta_path=args.fasta, -# ckpt_dir=args.ckpt_dir, -# tensor_parallel_size=args.tensor_parallel_size, -# pipeline_model_parallel_size=args.pipeline_model_parallel_size, -# context_parallel_size=args.context_parallel_size, -# output_dir=args.output_dir, -# model_size=args.model_size, -# ckpt_format=args.ckpt_format, -# fp8=args.fp8, -# full_fp8=args.full_fp8, -# fp8_recipe=args.fp8_recipe, -# micro_batch_size=args.micro_batch_size, -# output_log_prob_seqs=args.output_log_prob_seqs, -# log_prob_collapse_option=args.log_prob_collapse_option, -# prepend_bos=args.prepend_bos, -# no_sequence_parallel=args.no_sequence_parallel, -# hybrid_override_pattern=args.hybrid_override_pattern, -# seq_len_interpolation_factor=args.seq_len_interpolation_factor, -# num_layers=args.num_layers, -# files_per_subdir=args.files_per_subdir, -# write_interval=args.write_interval, -# lora_checkpoint_path=args.lora_checkpoint_path, -# mask_phylogenetic_tags=args.mask_phylogenetic_tags, -# min_length=args.min_length, -# eden_tokenizer=args.eden_tokenizer, -# ) - - -# if __name__ == "__main__": -# main() +r"""Prediction (inference) workflow for Evo2 using Megatron Bridge. + +This module provides functionality to run inference on Evo2 models using MBridge checkpoints. +It supports various parallelism strategies (TP, CP, DP) and can output either full logits +or collapsed log probabilities. + +Usage (CLI): + # Single GPU inference + torchrun --nproc_per_node 1 -m bionemo.evo2.run.predict \ + --fasta input.fasta --ckpt-dir /path/to/mbridge/checkpoint \ + --output-dir /path/to/output + + # Multi-GPU with tensor parallelism + torchrun --nproc_per_node 2 -m bionemo.evo2.run.predict \ + --fasta input.fasta --ckpt-dir /path/to/mbridge/checkpoint \ + --output-dir /path/to/output --tensor-parallel-size 2 + + # With context parallelism for long sequences + torchrun --nproc_per_node 2 -m bionemo.evo2.run.predict \ + --fasta input.fasta --ckpt-dir /path/to/mbridge/checkpoint \ + --output-dir /path/to/output --context-parallel-size 2 + +Output Format: + Batch mode (--write-interval batch): + - predictions__rank_{global_rank}__dp_rank_{dp_rank}__batch_{batch_idx}.pt + - With --files-per-subdir: subdir_{N}/predictions__rank_... + - Each file includes batch_idx tensor for reconstruction + + Epoch mode (--write-interval epoch, default): + - predictions__rank_{global_rank}__dp_rank_{dp_rank}.pt + - All batches collated into single file + + Both modes: + - seq_idx_map.json: Mapping from sequence names to indices in predictions + +Key Functions: + - predict(): Main prediction workflow + - batch_collator(): Collate predictions from multiple batches/ranks + - initialize_inference_distributed(): Set up distributed environment for inference +""" + +import argparse +import datetime +import logging +import os +from pathlib import Path +from typing import List, Literal, Optional, Tuple, TypeVar, Union + +import torch +import torch.distributed as dist +from megatron.bridge.data.samplers import build_pretraining_data_loader +from megatron.bridge.training.checkpointing import _load_model_weights_from_checkpoint +from megatron.bridge.training.config import DistributedInitConfig, RNGConfig +from megatron.bridge.training.mixed_precision import MIXED_PRECISION_RECIPES, get_mixed_precision_config +from megatron.bridge.training.tokenizers.tokenizer import _HuggingFaceTokenizer +from megatron.bridge.training.utils.checkpoint_utils import ( + file_exists, + get_checkpoint_run_config_filename, + read_run_config, +) +from megatron.bridge.utils.common_utils import ( + get_local_rank_preinit, + get_master_addr_safe, + get_master_port_safe, + get_rank_safe, + get_world_size_safe, +) +from megatron.bridge.utils.instantiate_utils import instantiate +from megatron.core import parallel_state, tensor_parallel +from megatron.core.num_microbatches_calculator import init_num_microbatches_calculator +from megatron.core.tensor_parallel.mappings import _gather_along_last_dim +from megatron.core.transformer.module import Float16Module +from megatron.core.utils import get_batch_on_this_cp_rank +from torch import Tensor + +from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH +from bionemo.evo2.data.fasta_dataset import SimpleFastaDataset + + +logger: logging.Logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +# Type alias for recursive batch structures (dicts, lists, tuples of Tensors) +ReductionT = TypeVar("ReductionT", bound=Union[Tensor, dict, list, tuple]) + + +# ============================================================================= +# Checkpoint Path Resolution +# ============================================================================= + + +def resolve_checkpoint_path(checkpoint_path: Path) -> Path: + """Resolve a checkpoint path to the actual checkpoint directory. + + MBridge checkpoints can be organized in two ways: + 1. Direct checkpoint: A directory containing run_config.yaml directly + (e.g., after conversion or for single checkpoints) + 2. Training output: A parent directory containing iter_XXXXXXX subdirectories + + This function handles both cases: + - If run_config.yaml exists in the given path, return it as-is + - Otherwise, find the latest iter_XXXXXXX subdirectory and return that + + Args: + checkpoint_path: Path to either a direct checkpoint or a training output directory. + + Returns: + Path to the checkpoint directory containing run_config.yaml. + + Raises: + FileNotFoundError: If the path doesn't exist or no valid checkpoint is found. + NotADirectoryError: If the path is not a directory. + + Examples: + >>> # Direct checkpoint path + >>> resolve_checkpoint_path(Path("/checkpoints/evo2_1b_mbridge")) + PosixPath('/checkpoints/evo2_1b_mbridge') + + >>> # Training output with iter_* subdirectories + >>> resolve_checkpoint_path(Path("/training/output")) + PosixPath('/training/output/iter_0007000') # Returns latest + """ + if not checkpoint_path.exists(): + raise FileNotFoundError(f"Checkpoint path '{checkpoint_path}' does not exist.") + if not checkpoint_path.is_dir(): + raise NotADirectoryError(f"Checkpoint path '{checkpoint_path}' must be a directory.") + + # Check if run_config.yaml exists directly in this path + run_config_path = get_checkpoint_run_config_filename(str(checkpoint_path)) + if file_exists(run_config_path): + return checkpoint_path + + # Look for iter_* subdirectories + iter_dirs = [ + (child.name, child) for child in checkpoint_path.iterdir() if child.is_dir() and child.name.startswith("iter_") + ] + + if not iter_dirs: + raise FileNotFoundError( + f"No valid checkpoint found at '{checkpoint_path}'. " + "Expected either run_config.yaml in the directory or iter_* subdirectories." + ) + + # Find the latest iteration by parsing the iteration number + def _parse_iter_num(item: tuple[str, Path]) -> int: + try: + return int(item[0].replace("iter_", "")) + except ValueError: + return -1 + + _, latest_iter_path = max(iter_dirs, key=_parse_iter_num) + + # Verify the selected iter directory has run_config.yaml + run_config_path = get_checkpoint_run_config_filename(str(latest_iter_path)) + if not file_exists(run_config_path): + raise FileNotFoundError(f"Latest checkpoint directory '{latest_iter_path}' does not contain run_config.yaml.") + + logger.info(f"Resolved checkpoint path to: {latest_iter_path}") + return latest_iter_path + + +# ============================================================================= +# Batch Collation Utilities +# ============================================================================= + + +def batch_collator( + batches: Optional[Union[Tuple[ReductionT, ...], List[ReductionT]]], + batch_dim: int = 0, + seq_dim: int = 1, + batch_dim_key_defaults: Optional[dict[str, int]] = None, + seq_dim_key_defaults: Optional[dict[str, int]] = None, + preferred_gpu: int = 0, +) -> Optional[ReductionT]: + """Collate multiple batches into a single batch by concatenating along the batch dimension. + + This function handles nested structures (dicts, lists, tuples) containing tensors. + Unlike PyTorch's default_collate, this assumes the batch dimension already exists + (as when parallelizing across microbatches or DP ranks). + + Args: + batches: Sequence of batches to collate. Each batch can be a tensor, dict, list, or tuple. + The structure must be consistent across all batches. + batch_dim: Dimension along which to concatenate tensors. Default 0. + seq_dim: Sequence dimension, used for padding to max length. Default 1. + batch_dim_key_defaults: For dict batches, override batch_dim for specific keys. + Default: {"token_logits": 1} (legacy compatibility, recommend passing {}). + seq_dim_key_defaults: For dict batches, override seq_dim for specific keys. + Default: {"token_logits": 0} (legacy compatibility, recommend passing {}). + preferred_gpu: If any tensor is on GPU, move all to this device. Default 0. + + Returns: + Collated batch with same structure as input batches, or None if input contains None. + + Raises: + ValueError: If batches is empty or contains unsupported types. + + Examples: + >>> # Collate dict batches + >>> batch1 = {"logits": torch.randn(2, 10, 512), "mask": torch.ones(2, 10)} + >>> batch2 = {"logits": torch.randn(3, 10, 512), "mask": torch.ones(3, 10)} + >>> result = batch_collator([batch1, batch2], batch_dim=0, seq_dim=1, + ... batch_dim_key_defaults={}, seq_dim_key_defaults={}) + >>> result["logits"].shape # torch.Size([5, 10, 512]) + + >>> # Collate with padding (different sequence lengths) + >>> batch1 = {"tokens": torch.randn(2, 100)} + >>> batch2 = {"tokens": torch.randn(2, 150)} + >>> result = batch_collator([batch1, batch2], batch_dim=0, seq_dim=1, + ... batch_dim_key_defaults={}, seq_dim_key_defaults={}) + >>> result["tokens"].shape # torch.Size([4, 150]) - padded to max length + """ + # Apply defaults for backward compatibility + if batch_dim_key_defaults is None: + batch_dim_key_defaults = {"token_logits": 1} + if seq_dim_key_defaults is None: + seq_dim_key_defaults = {"token_logits": 0} + + match batches: + # Base case: list starting with None + case [None, *_]: + return None + + # Base case: list of tensors + case [Tensor(), *_]: + return _collate_tensors(batches, batch_dim=batch_dim, seq_dim=seq_dim, preferred_gpu=preferred_gpu) + + # Recursive case: list of dicts + case [dict(), *_]: + return { + key: batch_collator( + [batch[key] for batch in batches], + batch_dim=batch_dim_key_defaults.get(key, batch_dim), + seq_dim=seq_dim_key_defaults.get(key, seq_dim), + batch_dim_key_defaults=batch_dim_key_defaults, + seq_dim_key_defaults=seq_dim_key_defaults, + preferred_gpu=preferred_gpu, + ) + for key in batches[0] + } + + # Recursive case: list of tuples + case [tuple(), *_]: + return tuple( + batch_collator( + [batch[i] for batch in batches], + batch_dim=batch_dim, + seq_dim=seq_dim, + batch_dim_key_defaults=batch_dim_key_defaults, + seq_dim_key_defaults=seq_dim_key_defaults, + preferred_gpu=preferred_gpu, + ) + for i in range(len(batches[0])) + ) + + # Recursive case: list of lists + case [list(), *_]: + return [ + batch_collator( + [batch[i] for batch in batches], + batch_dim=batch_dim, + seq_dim=seq_dim, + batch_dim_key_defaults=batch_dim_key_defaults, + seq_dim_key_defaults=seq_dim_key_defaults, + preferred_gpu=preferred_gpu, + ) + for i in range(len(batches[0])) + ] + + # Error cases + case []: + raise ValueError("Cannot collate an empty sequence of batches") + case _: + raise ValueError(f"Unsupported batch type: {type(batches[0]) if batches else 'empty'}") + + +def _collate_tensors( + tensors: List[Tensor], + batch_dim: int, + seq_dim: int, + preferred_gpu: int, +) -> Tensor: + """Concatenate tensors along batch dimension, padding sequence dimension if needed. + + Args: + tensors: List of tensors to concatenate + batch_dim: Dimension to concatenate along + seq_dim: Dimension to pad to max length + preferred_gpu: GPU device to use if any tensor is on GPU + + Returns: + Concatenated tensor + """ + # Move all to same device if any is on GPU + if any(t.is_cuda for t in tensors): + device = torch.device(f"cuda:{preferred_gpu}") + tensors = [t.to(device) for t in tensors] + + # For 1D tensors, just concatenate (no sequence dimension) + if tensors[0].ndim == 1: + return torch.cat(tensors, dim=0) + + # Pad to max sequence length + max_seq_len = max(t.size(seq_dim) for t in tensors) + padded_tensors = [] + + for tensor in tensors: + pad_amount = max_seq_len - tensor.size(seq_dim) + if pad_amount > 0: + # Build padding tuple: [left_last, right_last, left_second_last, right_second_last, ...] + pad_spec = [0] * (2 * tensor.ndim) + # Pad on the right of the sequence dimension + pad_spec[2 * (tensor.ndim - 1 - seq_dim) + 1] = pad_amount + padded_tensor = torch.nn.functional.pad(tensor, tuple(pad_spec)) + else: + padded_tensor = tensor + padded_tensors.append(padded_tensor) + + return torch.cat(padded_tensors, dim=batch_dim) + + +# ============================================================================= +# Distributed Initialization +# ============================================================================= + + +def initialize_inference_distributed( + tensor_model_parallel_size: int = 1, + pipeline_model_parallel_size: int = 1, + context_parallel_size: int = 1, + micro_batch_size: int = 1, + global_batch_size: int = 1, + rng_config: Optional[RNGConfig] = None, + dist_config: Optional[DistributedInitConfig] = None, +) -> None: + """Initialize distributed environment for inference. + + Sets up the minimal distributed infrastructure needed for model-parallel inference: + 1. torch.distributed process group + 2. Model parallel groups (TP, PP, CP, DP) + 3. Microbatch calculator (for batch scheduling) + 4. Random seeds for reproducibility + + This is a lightweight alternative to full Megatron initialization, skipping + training-specific components like the rerun state machine. + + Args: + tensor_model_parallel_size: Tensor parallelism degree (splits model across GPUs) + pipeline_model_parallel_size: Pipeline parallelism degree (must be 1 for inference) + context_parallel_size: Context parallelism degree (splits sequence across GPUs) + micro_batch_size: Batch size per forward pass + global_batch_size: Total batch size across all DP ranks + rng_config: Random number generator configuration. Defaults to seed=1234. + dist_config: Distributed backend configuration. Defaults to NCCL backend. + + Note: + This function must be called before creating the model. It initializes + parallel_state which is used throughout the codebase. + """ + import random + + import numpy as np + + # Apply defaults + if rng_config is None: + rng_config = RNGConfig(seed=1234) + if dist_config is None: + dist_config = DistributedInitConfig() + + assert torch.cuda.is_available(), "Inference requires CUDA." + + device_count = torch.cuda.device_count() + world_size = get_world_size_safe() + model_parallel_size = tensor_model_parallel_size * pipeline_model_parallel_size * context_parallel_size + data_parallel_size = world_size // model_parallel_size + + # Initialize microbatch calculator + init_num_microbatches_calculator( + rank=get_rank_safe(), + rampup_batch_size=None, + global_batch_size=global_batch_size, + micro_batch_size=micro_batch_size, + data_parallel_size=data_parallel_size, + decrease_batch_size_if_needed=False, + ) + + # Initialize torch.distributed + if not torch.distributed.is_initialized(): + if get_rank_safe() == 0: + print("> initializing torch distributed for inference ...", flush=True) + + if device_count > 0: + torch.cuda.set_device(get_local_rank_preinit()) + + # Ensure environment variables are set + if "MASTER_ADDR" not in os.environ: + os.environ["MASTER_ADDR"] = get_master_addr_safe() + if "MASTER_PORT" not in os.environ: + os.environ["MASTER_PORT"] = str(get_master_port_safe()) + + torch.distributed.init_process_group( + backend=dist_config.distributed_backend, + world_size=world_size, + rank=get_rank_safe(), + timeout=datetime.timedelta(minutes=dist_config.distributed_timeout_minutes), + ) + torch.distributed.barrier(device_ids=[get_local_rank_preinit()]) + else: + if get_rank_safe() == 0: + print("torch distributed is already initialized, skipping ...", flush=True) + + # Initialize model parallel groups + if device_count > 0 and not parallel_state.model_parallel_is_initialized(): + parallel_state.initialize_model_parallel( + tensor_model_parallel_size=tensor_model_parallel_size, + pipeline_model_parallel_size=pipeline_model_parallel_size, + context_parallel_size=context_parallel_size, + distributed_timeout_minutes=dist_config.distributed_timeout_minutes, + ) + if get_rank_safe() == 0: + print( + f"> initialized tensor model parallel with size {parallel_state.get_tensor_model_parallel_world_size()}" + ) + print( + f"> initialized pipeline model parallel with size {parallel_state.get_pipeline_model_parallel_world_size()}" + ) + print(f"> initialized data parallel with size {parallel_state.get_data_parallel_world_size()}") + elif get_rank_safe() == 0: + print("model parallel is already initialized", flush=True) + + # Set random seeds + if get_rank_safe() == 0: + print(f"> setting random seeds to {rng_config.seed} ...", flush=True) + + seed = rng_config.seed + (100 * parallel_state.get_pipeline_model_parallel_rank()) + if rng_config.data_parallel_random_init: + seed = seed + (10 * parallel_state.get_data_parallel_rank()) + + random.seed(seed) + np.random.seed(seed) # noqa: NPY002 + torch.manual_seed(seed) + + if device_count > 0: + tensor_parallel.model_parallel_cuda_manual_seed( + seed, + rng_config.te_rng_tracker, + rng_config.inference_rng_tracker, + ) + + +# ============================================================================= +# Context Parallelism Utilities +# ============================================================================= + + +def _gather_along_cp_dim(input_: Tensor, seq_dim: int = 1, unshuffle_zigzag: bool = True) -> Tensor: + """Gather tensors from all CP ranks and restore original sequence order. + + When using context parallelism (CP), sequences are split across multiple GPUs using a + "zigzag" pattern for load balancing. This function gathers the split tensors from all + CP ranks and optionally restores the original sequence order. + + Zigzag Pattern (CP=2 example): + Original sequence: [chunk0, chunk1, chunk2, chunk3] + CP rank 0 receives: [chunk0, chunk3] (positions 0 and 3) + CP rank 1 receives: [chunk1, chunk2] (positions 1 and 2) + + After gathering and unshuffling, the original order is restored. + + Args: + input_: Input tensor with shape [B, S/CP, ...] where S is full sequence length + seq_dim: Sequence dimension in the tensor. Default 1. + unshuffle_zigzag: If True, restore original sequence order after gathering. + Set to False only if you need the raw gathered order. Default True. + + Returns: + Gathered tensor with shape [B, S, ...] in original sequence order. + If CP=1, returns input unchanged. + + Note: + This function requires parallel_state to be initialized with CP groups. + """ + cp_size = parallel_state.get_context_parallel_world_size() + if cp_size == 1: + return input_ + + # Gather from all CP ranks + # After all_gather: [B * cp_size, seq_len_per_rank, ...] + dim_size = list(input_.size()) + dim_size[0] = dim_size[0] * cp_size + + output = torch.empty(dim_size, dtype=input_.dtype, device=torch.cuda.current_device()) + torch.distributed.all_gather_into_tensor( + output, input_.contiguous(), group=parallel_state.get_context_parallel_group() + ) + + # Chunk by batch dimension and concatenate by sequence dimension + # Result: [B, seq_len_per_rank * cp_size, ...] + tensor_list = output.chunk(cp_size, dim=0) + output = torch.cat(tensor_list, dim=seq_dim).contiguous() + + if not unshuffle_zigzag: + return output + + # Undo the zigzag pattern from get_batch_on_this_cp_rank + # The zigzag assigns chunk i and (2*cp_size - i - 1) to rank i + seq_len = output.shape[seq_dim] + num_chunks = 2 * cp_size + chunk_size = seq_len // num_chunks + + chunks = output.split(chunk_size, dim=seq_dim) + + # Build the order in which chunks appear after gathering: + # [rank0_first, rank0_second, rank1_first, rank1_second, ...] + # where rank_i has chunks (i, 2*cp_size - i - 1) + gathered_order = [] + for rank in range(cp_size): + gathered_order.append(rank) + gathered_order.append(2 * cp_size - rank - 1) + + # Create inverse mapping: original_position -> gathered_position + inverse_order = [0] * num_chunks + for pos, orig_idx in enumerate(gathered_order): + inverse_order[orig_idx] = pos + + # Reorder to original sequence order [0, 1, 2, ..., 2*cp_size-1] + reordered_chunks = [chunks[inverse_order[i]] for i in range(num_chunks)] + return torch.cat(reordered_chunks, dim=seq_dim).contiguous() + + +# ============================================================================= +# Argument Parsing +# ============================================================================= + + +def parse_args() -> argparse.Namespace: + """Parse command-line arguments for Evo2 inference. + + Returns: + Parsed arguments namespace + """ + ap = argparse.ArgumentParser( + description="Run inference on Evo2 models using MBridge checkpoints", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + # Required arguments + ap.add_argument( + "--fasta", + type=Path, + required=True, + help="Path to input FASTA file containing sequences for prediction", + ) + ap.add_argument( + "--ckpt-dir", + type=Path, + required=True, + help="Path to MBridge checkpoint directory (must contain run_config.yaml)", + ) + + # Output arguments + ap.add_argument( + "--output-dir", + type=Path, + default=None, + help="Directory for output predictions. If not set, predictions are discarded.", + ) + ap.add_argument( + "--write-interval", + type=str, + default="epoch", + choices=["epoch", "batch"], + help="When to write predictions: 'epoch' writes all at end, 'batch' writes after each batch", + ) + ap.add_argument( + "--files-per-subdir", + type=int, + help="Group output files into subdirectories. Only used with --write-interval batch.", + ) + + # Parallelism arguments + ap.add_argument("--num-nodes", type=int, default=1, help="Number of nodes for distributed inference") + ap.add_argument( + "--devices", + type=int, + help="Number of GPUs per node. Default: TP * PP * CP", + ) + ap.add_argument("--tensor-parallel-size", type=int, default=1, help="Tensor parallelism degree") + ap.add_argument( + "--pipeline-model-parallel-size", + type=int, + choices=[1], + default=1, + help="Pipeline parallelism degree (only 1 supported)", + ) + ap.add_argument("--context-parallel-size", type=int, default=1, help="Context parallelism degree") + ap.add_argument( + "--no-sequence-parallel", + action="store_true", + help="Disable sequence parallelism when using TP > 1", + ) + + # Model/precision arguments + ap.add_argument( + "--mixed-precision-recipe", + type=str, + choices=list(MIXED_PRECISION_RECIPES.keys()), + help="Override mixed precision recipe (default: use checkpoint setting)", + ) + ap.add_argument( + "--vortex-style-fp8", + action="store_true", + help="Use vortex-style FP8 (applies FP8 only to projection layers)", + ) + + # Batch/sequence arguments + ap.add_argument("--micro-batch-size", type=int, default=1, help="Batch size per forward pass") + ap.add_argument("--min-length", type=int, help="Minimum sequence length (pad shorter sequences)") + ap.add_argument("--prepend-bos", action="store_true", help="Prepend BOS token to sequences") + + # Output format arguments + ap.add_argument( + "--output-log-prob-seqs", + action="store_true", + help="Output log probabilities instead of raw logits", + ) + ap.add_argument( + "--log-prob-collapse-option", + choices=["sum", "mean", "per_token"], + default="mean", + help="How to aggregate per-token log probs: sum, mean, or keep per_token", + ) + + # Model configuration overrides (for testing) + ap.add_argument( + "--hybrid-override-pattern", + type=str, + help="Override hybrid layer pattern (e.g., 'SDH*' for testing)", + ) + ap.add_argument("--num-layers", type=int, help="Override number of layers (for testing)") + ap.add_argument( + "--seq-len-interpolation-factor", + type=int, + help="ROPE sequence length interpolation factor", + ) + + # Embedding extraction arguments + ap.add_argument( + "--embedding-layer", + type=int, + help="Extract embeddings from a specific transformer layer instead of logits. " + "Supports Python-style negative indexing (e.g., -1 for last layer, -2 for second-to-last). " + "For a 25-layer model, layer 24 and layer -1 both refer to the last layer.", + ) + + # Tokenizer arguments + ap.add_argument( + "--eden-tokenizer", + action="store_true", + help="Use Eden tokenizer patches", + ) + ap.add_argument( + "--mask-phylogenetic-tags", + action="store_true", + help="Mask phylogenetic tags in loss computation", + ) + + return ap.parse_args() + + +def on_writing_rank() -> bool: + """Returns True if the current rank is one that should own writing predictions.""" + return ( + (parallel_state.is_pipeline_last_stage()) + and (parallel_state.get_tensor_model_parallel_rank() == 0) + and (parallel_state.get_context_parallel_rank() == 0) + ) + + +# ============================================================================= +# Data Loading Utilities +# ============================================================================= + + +def _padding_collate_fn_factory( + pad_token_id: int = 0, + min_length: Optional[int] = None, +): + """Create a collate function that pads sequences to uniform length. + + Args: + pad_token_id: Token ID to use for padding + min_length: Minimum sequence length (pad shorter sequences to this) + + Returns: + Collate function compatible with DataLoader + """ + + def collate_fn(batch: list[dict[str, Tensor]]) -> dict[str, Tensor]: + return _padding_collate_fn(batch, pad_token_id, min_length) + + return collate_fn + + +def _padding_collate_fn( + batch: list[dict[str, Tensor]], + pad_token_id: int = 0, + min_length: Optional[int] = None, +) -> dict[str, Tensor]: + """Pad sequences in a batch to the same length. + + Handles the following keys specially: + - tokens: Padded with pad_token_id + - position_ids: Extended with consecutive positions + - loss_mask: Padded with 0 (masked) + - seq_idx: Not padded (scalar per sample) + - Other keys: Padded with 0 + + Args: + batch: List of sample dictionaries from the dataset + pad_token_id: Token ID for padding + min_length: Minimum length to pad to + + Returns: + Dictionary with batched and padded tensors + """ + max_len = max(sample["tokens"].shape[0] for sample in batch) + if min_length is not None: + max_len = max(max_len, min_length) + + padded_batch: dict[str, list[Tensor]] = {key: [] for key in batch[0].keys()} + + for sample in batch: + seq_len = sample["tokens"].shape[0] + pad_len = max_len - seq_len + + for key, value in sample.items(): + if key == "tokens": + padded = torch.nn.functional.pad(value, (0, pad_len), value=pad_token_id) + elif key == "position_ids": + if pad_len > 0: + padded = torch.cat([value, torch.arange(seq_len, max_len, dtype=value.dtype)]) + else: + padded = value + elif key == "loss_mask": + padded = torch.nn.functional.pad(value, (0, pad_len), value=0) + elif key == "seq_idx": + padded = value # Scalar, no padding + else: + padded = torch.nn.functional.pad(value, (0, pad_len), value=0) + padded_batch[key].append(padded) + + return {key: torch.stack(values) for key, values in padded_batch.items()} + + +# ============================================================================= +# Prediction Step +# ============================================================================= + + +def _predict_step( + model: torch.nn.Module, + batch: dict[str, Tensor], + output_log_prob_seqs: bool = False, + log_prob_collapse_option: Literal["sum", "mean", "per_token"] = "mean", + context_parallel_size: int = 1, + output_embeddings: bool = False, +) -> Optional[dict[str, Tensor]]: + """Run a single prediction step and gather outputs across parallel ranks. + + Args: + model: The Evo2 model to run inference with + batch: Input batch containing: + - tokens: Input token IDs [B, S] + - position_ids: Position indices [B, S] + - loss_mask: Mask indicating valid tokens [B, S] + - seq_idx: Original sequence indices [B] + output_log_prob_seqs: If True, return log probabilities instead of logits + log_prob_collapse_option: How to aggregate log probs ('sum', 'mean', or 'per_token') + context_parallel_size: CP size (for warning about per_token output) + output_embeddings: If True, return embeddings instead of logits (model must have + post_process=False) + + Returns: + Dictionary containing predictions: + - If output_embeddings=True: hidden_embeddings, pad_mask, seq_idx, tokens + - If output_log_prob_seqs=False: token_logits, pad_mask, seq_idx, tokens + - If output_log_prob_seqs=True with sum/mean: log_probs_seqs, seq_idx + - If output_log_prob_seqs=True with per_token: log_probs_seqs, seq_idx, loss_mask + Returns None if not on the last pipeline stage. + """ + if not parallel_state.is_pipeline_last_stage(): + return None + + # Forward pass + output_tensor = model( + input_ids=batch["tokens"], + position_ids=batch["position_ids"], + attention_mask=None, + ) + + # Gather across tensor parallel ranks + # For logits (post_process=True): gather along vocabulary dimension (last dim is sharded) + # For embeddings (post_process=False): hidden states are not sharded across TP, skip gathering + if output_embeddings: + # Hidden states are not sharded across TP ranks, just use the output directly + forward_out_tp_gathered = output_tensor + else: + # Logits have the vocab dimension sharded across TP ranks + forward_out_tp_gathered = _gather_along_last_dim( + output_tensor, group=parallel_state.get_tensor_model_parallel_group() + ) + + # Gather across context parallel ranks (sequence dimension) + forward_out_gathered = _gather_along_cp_dim(forward_out_tp_gathered) + loss_mask_gathered = _gather_along_cp_dim(batch["loss_mask"]) + tokens_gathered = _gather_along_cp_dim(batch["tokens"]) + + if output_embeddings: + # When extracting embeddings, the model output is hidden states, not logits + # Model outputs [S, B, H] (sequence-first format), transpose to [B, S, H] for consistency + hidden_embeddings = forward_out_gathered.transpose(0, 1).contiguous() + return { + "hidden_embeddings": hidden_embeddings, + "pad_mask": loss_mask_gathered, + "seq_idx": batch["seq_idx"], + "tokens": tokens_gathered, + } + elif output_log_prob_seqs: + return _compute_log_probs( + logits=forward_out_gathered, + tokens=tokens_gathered, + loss_mask=loss_mask_gathered, + seq_idx=batch["seq_idx"], + collapse_option=log_prob_collapse_option, + context_parallel_size=context_parallel_size, + ) + else: + return { + "token_logits": forward_out_gathered, + "pad_mask": loss_mask_gathered, + "seq_idx": batch["seq_idx"], + "tokens": tokens_gathered, + } + + +def _compute_log_probs( + logits: Tensor, + tokens: Tensor, + loss_mask: Tensor, + seq_idx: Tensor, + collapse_option: Literal["sum", "mean", "per_token"], + context_parallel_size: int, +) -> dict[str, Tensor]: + """Compute log probabilities from model logits. + + Computes P(token_i | token_0, ..., token_{i-1}) for each token. + + Args: + logits: Model output logits [B, S, V] + tokens: Input token IDs [B, S] + loss_mask: Mask for valid tokens [B, S] + seq_idx: Sequence indices [B] + collapse_option: How to aggregate: 'sum', 'mean', or 'per_token' + context_parallel_size: CP size (for per_token warning) + + Returns: + Dictionary with log_probs_seqs and seq_idx (and loss_mask if per_token) + """ + # Predictions for token i are at position i, labels are at i+1 + softmax_logprobs = torch.log_softmax(logits, dim=-1) + softmax_logprobs = softmax_logprobs[:, :-1] # [B, S-1, V] + target_tokens = tokens[:, 1:] # [B, S-1] + + if softmax_logprobs.shape[1] != target_tokens.shape[1]: + raise RuntimeError(f"Shape mismatch: logprobs {softmax_logprobs.shape} vs targets {target_tokens.shape}") + + # Gather log probs for actual tokens + log_probs_per_token = torch.gather(softmax_logprobs, 2, target_tokens.unsqueeze(-1)).squeeze(-1) + + # Apply loss mask (zero out padding) + loss_mask_shifted = loss_mask[:, 1:].float() + log_probs_per_token = log_probs_per_token * loss_mask_shifted + + if collapse_option == "per_token": + if context_parallel_size > 1: + logger.warning( + "Per-token log probabilities with CP>1 will have zigzag-shuffled order. " + "Use 'sum' or 'mean' to get correctly aggregated results." + ) + return { + "log_probs_seqs": log_probs_per_token, + "seq_idx": seq_idx, + "loss_mask": loss_mask_shifted.bool(), + } + + # Sum log probs across sequence + log_prob_seqs = torch.sum(log_probs_per_token, dim=1) + + if collapse_option == "mean": + # Divide by number of valid tokens + valid_token_count = torch.clamp(loss_mask_shifted.sum(dim=-1), min=1.0) + log_prob_seqs = log_prob_seqs / valid_token_count + + return {"log_probs_seqs": log_prob_seqs, "seq_idx": seq_idx} + + +# ============================================================================= +# Output Writing +# ============================================================================= + + +def _write_predictions_batch( + predictions: dict[str, Tensor], + output_dir: Path, + batch_idx: int, + global_rank: int, + dp_rank: int, + files_per_subdir: Optional[int] = None, + num_files_written: int = 0, + data_parallel_world_size: int = 1, +) -> tuple[Path, int, int]: + """Write predictions to disk as a PyTorch file (batch mode). + + File naming follows the original PredictionWriter convention: + predictions__rank_{global_rank}__dp_rank_{dp_rank}__batch_{batch_idx}.pt + + Subdirectory structure (when files_per_subdir is set): + subdir_{num}/predictions__rank_... + + The subdirectory numbering starts from 1 and increments when the number of files + written (across all DP ranks) reaches files_per_subdir. + + Args: + predictions: Dictionary of prediction tensors to save + output_dir: Base output directory + batch_idx: Batch index for file naming + global_rank: Global rank of this process + dp_rank: Data parallel rank (included in filename for multi-GPU) + files_per_subdir: If set, organize files into subdirectories + num_files_written: Number of files already written in current subdir + data_parallel_world_size: Number of data parallel ranks + + Returns: + Tuple of (output_path, updated_num_files_written, updated_num_subdirs) + """ + if (not predictions) or (not on_writing_rank()): + return output_dir, num_files_written, 0 + + output_dir.mkdir(parents=True, exist_ok=True) + + # Track subdirectory state + current_output_dir = output_dir + num_subdirs_written = 0 + + if files_per_subdir is not None: + # Calculate how many subdirs we've created based on total files written + # (counting all DP ranks) + effective_files = num_files_written * data_parallel_world_size + if effective_files >= files_per_subdir: + # Need a new subdirectory + num_subdirs_written = effective_files // files_per_subdir + 1 + current_output_dir = output_dir / f"subdir_{num_subdirs_written}" + current_output_dir.mkdir(parents=True, exist_ok=True) + num_files_written = 0 + + filename = f"predictions__rank_{global_rank}__dp_rank_{dp_rank}__batch_{batch_idx}.pt" + output_path = current_output_dir / filename + + # Add batch_idx to predictions (matching original PredictionWriter behavior) + predictions["batch_idx"] = torch.tensor([batch_idx], dtype=torch.int64) + + torch.save(predictions, output_path) + logger.info(f"Inference predictions are stored in {output_path}\n{predictions.keys()}") + + return output_path, num_files_written + 1, num_subdirs_written + + +def _write_predictions_epoch( + predictions: dict[str, Tensor], + output_dir: Path, + global_rank: int, + dp_rank: int, +) -> Path: + """Write predictions to disk as a PyTorch file (epoch mode). + + File naming follows the original PredictionWriter convention: + predictions__rank_{global_rank}__dp_rank_{dp_rank}.pt + + Args: + predictions: Dictionary of prediction tensors to save + output_dir: Base output directory + global_rank: Global rank of this process + dp_rank: Data parallel rank + + Returns: + Path to the saved file + """ + if (not predictions) or (not on_writing_rank()): + return output_dir + + output_dir.mkdir(parents=True, exist_ok=True) + + filename = f"predictions__rank_{global_rank}__dp_rank_{dp_rank}.pt" + output_path = output_dir / filename + + torch.save(predictions, output_path) + logger.info(f"Inference predictions are stored in {output_path}\n{predictions.keys()}") + + return output_path + + +# ============================================================================= +# Main Prediction Workflow +# ============================================================================= + + +def predict( + fasta_path: Path, + ckpt_dir: Path, + output_dir: Optional[Path] = None, + *, + # Parallelism settings + tensor_parallel_size: int = 1, + pipeline_model_parallel_size: int = 1, + context_parallel_size: int = 1, + no_sequence_parallel: bool = False, + # Precision settings + mixed_precision_recipe: Optional[str] = None, + # Batch/sequence settings + micro_batch_size: int = 1, + min_length: Optional[int] = None, + prepend_bos: bool = False, + # Output settings + write_interval: Literal["epoch", "batch"] = "epoch", + files_per_subdir: Optional[int] = None, + output_log_prob_seqs: bool = False, + log_prob_collapse_option: Literal["sum", "mean", "per_token"] = "mean", + # Embedding extraction + embedding_layer: Optional[int] = None, +) -> None: + """Run the complete Evo2 prediction workflow. + + This function orchestrates the full inference pipeline: + 1. Load model configuration from MBridge checkpoint + 2. Override parallelism and precision settings + 3. Initialize distributed environment + 4. Create and configure the model + 5. Load model weights + 6. Process FASTA sequences and write predictions + + Args: + fasta_path: Path to input FASTA file containing sequences for prediction. + ckpt_dir: Path to MBridge checkpoint directory (must contain run_config.yaml). + output_dir: Directory for output predictions. If None, predictions are discarded. + tensor_parallel_size: Tensor parallelism degree (splits model across GPUs). + pipeline_model_parallel_size: Pipeline parallelism degree (must be 1). + context_parallel_size: Context parallelism degree (splits sequence across GPUs). + no_sequence_parallel: Disable sequence parallelism when using TP > 1. + mixed_precision_recipe: Override mixed precision recipe (default: use checkpoint). + micro_batch_size: Batch size per forward pass. + min_length: Minimum sequence length (pad shorter sequences to this). + prepend_bos: Prepend BOS token to sequences. + write_interval: When to write predictions: 'epoch' or 'batch'. + files_per_subdir: Group output files into subdirectories (batch mode only). + output_log_prob_seqs: Output log probabilities instead of raw logits. + log_prob_collapse_option: How to aggregate log probs: 'sum', 'mean', 'per_token'. + embedding_layer: Extract embeddings from a specific layer instead of logits. + Supports Python-style negative indexing (-1 for last layer, -2 for second-to-last). + For a 25-layer model, layer 24 and -1 both refer to the last layer. + + Raises: + ValueError: If pipeline parallelism > 1 is requested. + FileNotFoundError: If checkpoint run_config.yaml is missing. + + Example: + >>> from pathlib import Path + >>> predict( + ... fasta_path=Path("sequences.fasta"), + ... ckpt_dir=Path("/path/to/mbridge/checkpoint"), + ... output_dir=Path("/path/to/output"), + ... tensor_parallel_size=2, + ... micro_batch_size=4, + ... ) + """ + if pipeline_model_parallel_size != 1: + raise ValueError("Pipeline parallelism > 1 is not currently supported for prediction.") + + # ------------------------------------------------------------------------- + # Step 1: Resolve and load configuration from checkpoint + # ------------------------------------------------------------------------- + # Handle both direct checkpoint paths and training output directories with iter_* subdirs + resolved_ckpt_dir = resolve_checkpoint_path(ckpt_dir) + logger.info(f"Loading configuration from checkpoint: {resolved_ckpt_dir}") + + run_config_filename = get_checkpoint_run_config_filename(str(resolved_ckpt_dir)) + + run_config = read_run_config(run_config_filename) + model_provider = instantiate(run_config["model"]) + logger.info(f"Instantiated model provider: {type(model_provider).__name__}") + + # ------------------------------------------------------------------------- + # Step 2: Override parallelism and precision settings + # ------------------------------------------------------------------------- + model_provider.tensor_model_parallel_size = tensor_parallel_size + model_provider.pipeline_model_parallel_size = pipeline_model_parallel_size + model_provider.context_parallel_size = context_parallel_size + model_provider.sequence_parallel = tensor_parallel_size > 1 and not no_sequence_parallel + + # Configure mixed precision + if mixed_precision_recipe is not None: + mp_config = get_mixed_precision_config(mixed_precision_recipe) + elif "mixed_precision" in run_config and run_config["mixed_precision"] is not None: + mp_value = run_config["mixed_precision"] + if isinstance(mp_value, str): + mp_config = get_mixed_precision_config(mp_value) + logger.info(f"Using mixed precision recipe from checkpoint: {mp_value}") + else: + mp_config = instantiate(mp_value) + logger.info("Using mixed precision config from checkpoint") + else: + mp_config = get_mixed_precision_config("bf16_mixed") + + mp_config.finalize() + mp_config.setup(model_provider) + + # ------------------------------------------------------------------------- + # Step 3: Load tokenizer + # ------------------------------------------------------------------------- + tokenizer_dir = resolved_ckpt_dir / "tokenizer" + if tokenizer_dir.exists(): + tokenizer = _HuggingFaceTokenizer(tokenizer_dir) + else: + tokenizer = _HuggingFaceTokenizer(DEFAULT_HF_TOKENIZER_MODEL_PATH) + + model_provider.vocab_size = tokenizer.vocab_size + model_provider.should_pad_vocab = True + + # ------------------------------------------------------------------------- + # Step 3.5: Handle embedding layer extraction + # ------------------------------------------------------------------------- + # Get the original number of layers from the checkpoint config + original_num_layers = model_provider.num_layers + output_embeddings = embedding_layer is not None + + if output_embeddings: + # Validate and resolve the embedding layer index + # Support Python-style negative indexing + if embedding_layer < 0: + # Convert negative index to positive (e.g., -1 -> last layer) + target_num_layers = original_num_layers + embedding_layer + 1 + else: + # Positive index: layer N means we need N+1 layers (0-indexed) + target_num_layers = embedding_layer + 1 + + if target_num_layers <= 0 or target_num_layers > original_num_layers: + raise ValueError( + f"Invalid embedding_layer={embedding_layer} for model with {original_num_layers} layers. " + f"Valid range: -{original_num_layers} to {original_num_layers - 1}." + ) + + # Set the model to use fewer layers and skip post-processing (output heads) + model_provider.num_layers = target_num_layers + model_provider.post_process = False + + # Also truncate the hybrid_override_pattern if it exists, since it must match num_layers + if hasattr(model_provider, "hybrid_override_pattern") and model_provider.hybrid_override_pattern is not None: + original_pattern = model_provider.hybrid_override_pattern + if len(original_pattern) > target_num_layers: + model_provider.hybrid_override_pattern = original_pattern[:target_num_layers] + logger.info( + f"Truncated hybrid_override_pattern from {len(original_pattern)} to {target_num_layers} chars" + ) + + # Disable remove_activation_post_first_layer if we only have 1 layer, since it requires at least 2 layers + if target_num_layers == 1 and hasattr(model_provider, "remove_activation_post_first_layer"): + if model_provider.remove_activation_post_first_layer: + model_provider.remove_activation_post_first_layer = False + logger.info("Disabled remove_activation_post_first_layer (requires at least 2 layers)") + + logger.info( + f"Embedding extraction mode: extracting from layer {embedding_layer} " + f"(using {target_num_layers} of {original_num_layers} layers, post_process=False)" + ) + + # Cannot use log prob output with embedding mode + if output_log_prob_seqs: + raise ValueError("Cannot use --output-log-prob-seqs with --embedding-layer. Embeddings are not logits.") + + # ------------------------------------------------------------------------- + # Step 4: Initialize distributed environment + # ------------------------------------------------------------------------- + rng_config = instantiate(run_config.get("rng")) if run_config.get("rng") else RNGConfig(seed=1234) + dist_config = instantiate(run_config.get("dist")) if run_config.get("dist") else DistributedInitConfig() + + model_parallel_size = tensor_parallel_size * pipeline_model_parallel_size * context_parallel_size + world_size = get_world_size_safe() + data_parallel_size = world_size // model_parallel_size + global_batch_size = micro_batch_size * data_parallel_size + + initialize_inference_distributed( + tensor_model_parallel_size=tensor_parallel_size, + pipeline_model_parallel_size=pipeline_model_parallel_size, + context_parallel_size=context_parallel_size, + micro_batch_size=micro_batch_size, + global_batch_size=global_batch_size, + rng_config=rng_config, + dist_config=dist_config, + ) + logger.info("Initialized distributed environment") + + # ------------------------------------------------------------------------- + # Step 5: Create model and load weights + # ------------------------------------------------------------------------- + logger.info("Creating model...") + model_provider.finalize() + + model = model_provider.provide_distributed_model( + ddp_config=None, + wrap_with_ddp=False, + data_parallel_random_init=False, + bf16=mp_config.bf16, + fp16=mp_config.fp16, + mixed_precision_wrapper=Float16Module if (mp_config.bf16 or mp_config.fp16) else None, + ) + + for model_module in model: + model_module.eval() + + # Log model layer information + # Access the underlying model to get layer count + model_for_inspection = model[0] + if hasattr(model_for_inspection, "module"): + # Handle Float16Module wrapper + model_for_inspection = model_for_inspection.module + if hasattr(model_for_inspection, "decoder") and hasattr(model_for_inspection.decoder, "layers"): + actual_num_layers = len(model_for_inspection.decoder.layers) + logger.info(f"Model initialized with {actual_num_layers} layers") + if output_embeddings: + logger.info( + f"Embedding extraction: model has {actual_num_layers} layers " + f"(from original {original_num_layers} layers)" + ) + else: + logger.warning("Could not determine number of layers from model structure") + + logger.info(f"Loading weights from: {resolved_ckpt_dir}") + _load_model_weights_from_checkpoint( + checkpoint_path=str(resolved_ckpt_dir), + model=model, + dist_ckpt_strictness="ignore_all", + ) + logger.info("Weights loaded successfully") + + # ------------------------------------------------------------------------- + # Step 6: Create dataset and dataloader + # ------------------------------------------------------------------------- + logger.info(f"Loading dataset from: {fasta_path}") + dataset = SimpleFastaDataset( + fasta_path=fasta_path, + tokenizer=tokenizer, + prepend_bos=prepend_bos, + custom_loss_masker=None, + ) + + data_parallel_rank = parallel_state.get_data_parallel_rank() + data_parallel_size = parallel_state.get_data_parallel_world_size() + + dataloader = build_pretraining_data_loader( + dataset=dataset, + consumed_samples=0, + dataloader_type="single", + micro_batch_size=micro_batch_size, + num_workers=4, + data_sharding=False, + collate_fn=_padding_collate_fn_factory( + pad_token_id=getattr(tokenizer, "pad_id", 0), + min_length=min_length, + ), + pin_memory=True, + persistent_workers=False, + data_parallel_rank=data_parallel_rank, + data_parallel_size=data_parallel_size, + drop_last=False, + ) + + # ------------------------------------------------------------------------- + # Step 7: Run prediction loop + # ------------------------------------------------------------------------- + logger.info("Starting prediction loop...") + predictions: list[dict[str, Tensor]] = [] + + # Get ranks for file naming (matching original PredictionWriter behavior) + global_rank = get_rank_safe() + num_files_written = 0 + + with torch.no_grad(): + for batch_idx, batch_data in enumerate(dataloader): + # Move to GPU + batch_gpu = { + k: v.cuda(non_blocking=True) if isinstance(v, torch.Tensor) else v for k, v in batch_data.items() + } + + # Apply context parallel slicing (seq_idx must NOT be sliced) + if context_parallel_size > 1: + seq_idx = batch_gpu.pop("seq_idx", None) + batch_gpu = get_batch_on_this_cp_rank(batch_gpu) + if seq_idx is not None: + batch_gpu["seq_idx"] = seq_idx + + # Forward pass + result = _predict_step( + model=model[0], + batch=batch_gpu, + output_log_prob_seqs=output_log_prob_seqs, + log_prob_collapse_option=log_prob_collapse_option, + context_parallel_size=context_parallel_size, + output_embeddings=output_embeddings, + ) + + if result is not None: + predictions.append({k: v.cpu() for k, v in result.items()}) + + if (batch_idx + 1) % 10 == 0: + logger.info(f"Processed batch {batch_idx + 1}/{len(dataloader)}") + + # Write at batch interval + if write_interval == "batch" and output_dir is not None and predictions: + _, num_files_written, _ = _write_predictions_batch( + predictions=predictions[0], + output_dir=output_dir, + batch_idx=batch_idx, + global_rank=global_rank, + dp_rank=data_parallel_rank, + files_per_subdir=files_per_subdir, + num_files_written=num_files_written, + data_parallel_world_size=data_parallel_size, + ) + predictions = [] + + # Write at epoch end + if write_interval == "epoch" and output_dir is not None and predictions: + combined = batch_collator( + predictions, + batch_dim=0, + seq_dim=1, + batch_dim_key_defaults={}, + seq_dim_key_defaults={}, + ) + _write_predictions_epoch( + predictions=combined, + output_dir=output_dir, + global_rank=global_rank, + dp_rank=data_parallel_rank, + ) + + # Write sequence index map + if output_dir is not None: + output_dir.mkdir(parents=True, exist_ok=True) + dataset.write_idx_map(output_dir) + + logger.info("Prediction complete!") + + # Cleanup + if dist.is_initialized(): + dist.barrier() + dist.destroy_process_group() + + +# ============================================================================= +# Entry Point +# ============================================================================= + + +def main() -> None: + """CLI entry point for Evo2 prediction.""" + args = parse_args() + predict( + fasta_path=args.fasta, + ckpt_dir=args.ckpt_dir, + output_dir=args.output_dir, + # Parallelism settings + tensor_parallel_size=args.tensor_parallel_size, + pipeline_model_parallel_size=args.pipeline_model_parallel_size, + context_parallel_size=args.context_parallel_size, + no_sequence_parallel=args.no_sequence_parallel, + # Precision settings + mixed_precision_recipe=args.mixed_precision_recipe, + # Batch/sequence settings + micro_batch_size=args.micro_batch_size, + min_length=args.min_length, + prepend_bos=args.prepend_bos, + # Output settings + write_interval=args.write_interval, + files_per_subdir=args.files_per_subdir, + output_log_prob_seqs=args.output_log_prob_seqs, + log_prob_collapse_option=args.log_prob_collapse_option, + # Embedding extraction + embedding_layer=args.embedding_layer, + ) + + +if __name__ == "__main__": + main() diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py index 46289038e2..3561a51fc7 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py @@ -236,6 +236,8 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace: # ) # FIXME not supported in megatron # parser.add_argument("--wandb-offline", action="store_true", help="Use wandb in offline mode") # TODO implement parser.add_argument("--sequence-parallel", action="store_true", help="Set to enable sequence parallelism.") # DONE + parser.add_argument("--no-fp8-wgrad", action="store_true", help="Set to disable fp8 weight gradients.") + parser.add_argument("--no-fp8-param-gather", action="store_true", help="Set to disable fp8 parameter gathering.") parser.add_argument( "--mixed-precision-recipe", type=str, @@ -598,12 +600,12 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace: # default=True, # help="Disable saving the last checkpoint.", # ) # TODO implement - parser.add_argument( - "--lora-finetune", action="store_true", help="Use LoRA fine-tuning", default=False - ) # TODO implement - parser.add_argument( - "--lora-checkpoint-path", type=str, default=None, help="LoRA checkpoint path" - ) # TODO implement + # parser.add_argument( + # "--lora-finetune", action="store_true", help="Use LoRA fine-tuning", default=False + # ) # TODO implement + # parser.add_argument( + # "--lora-checkpoint-path", type=str, default=None, help="LoRA checkpoint path" + # ) # TODO implement parser.add_argument( "--no-calculate-per-token-loss", action="store_true", @@ -647,7 +649,16 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace: default=False, help="Enable NVIDIA fault tolerance. This only works on internal NVIDIA clusters.", ) # DONE - parser.add_argument( + + # Optimizer format + optimizer_fmt_group = parser.add_mutually_exclusive_group(required=False) + optimizer_fmt_group.add_argument( + "--optim-fmt-pre-mcore-014", + action="store_true", + default=False, + help="Use the pre-Megatron-Core-v0.14 optimizer format.", + ) + optimizer_fmt_group.add_argument( "--optim-full-reshardable", action="store_true", default=False, @@ -765,6 +776,15 @@ def train(args: argparse.Namespace) -> None: cfg.checkpoint.exit_on_missing_checkpoint = False cfg.checkpoint.dist_ckpt_strictness = "assume_ok_unexpected" + if args.no_fp8_wgrad: + # change if a change is requested to the mixed precision recipe + cfg.mixed_precision.fp8_wgrad = False + if args.grad_reduce_in_fp32: + cfg.mixed_precision.grad_reduce_in_fp32 = True + cfg.ddp.grad_reduce_in_fp32 = True + if args.no_fp8_param_gather: + cfg.mixed_precision.fp8_param_gather = False + # 3. Apply Manual Overrides (for settings not exposed in recipe kwargs) if args.no_renormalize_loss: cfg.model.to_upper = "weighted" # rather than "normalized_weighted" @@ -824,7 +844,10 @@ def train(args: argparse.Namespace) -> None: cfg.optimizer.log_num_zeros_in_grad = args.log_num_zeros_in_grad cfg.optimizer.clip_grad = args.clip_grad # Optimizer checkpointing resharding - cfg.checkpoint.dist_ckpt_optim_fully_reshardable = args.optim_full_reshardable + if args.optim_fmt_pre_mcore_014: + cfg.checkpoint.dist_ckpt_save_pre_mcore_014 = True + elif args.optim_full_reshardable: + cfg.checkpoint.dist_ckpt_optim_fully_reshardable = True cfg.dataset.num_workers = args.workers @@ -832,7 +855,6 @@ def train(args: argparse.Namespace) -> None: cfg.ddp.align_param_gather = args.align_param_gather cfg.ddp.overlap_param_gather = args.overlap_param_gather cfg.ddp.overlap_grad_reduce = args.overlap_grad_reduce - cfg.ddp.grad_reduce_in_fp32 = args.grad_reduce_in_fp32 cfg.ddp.check_for_nan_in_grad = not args.no_check_for_nan_in_grad if args.use_megatron_comm_overlap_llama3_8k: # Pick the floating point appropriate config. diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/__init__.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/__init__.py new file mode 100644 index 0000000000..018372aa39 --- /dev/null +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-Apache2 +# +# 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. + +"""Evo2 tests package.""" diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/conftest.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/conftest.py index 8c5dbe4018..de8ace18ec 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/conftest.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/conftest.py @@ -16,14 +16,17 @@ # conftest.py import gc +import os +import random +import signal +import time +from pathlib import Path +import numpy as np import pytest import torch -# from bionemo.testing.torch import get_device_and_memory_allocated - - def get_device_and_memory_allocated() -> str: """Get the current device index, name, and memory usage.""" current_device_index = torch.cuda.current_device() @@ -63,16 +66,150 @@ def pytest_sessionfinish(session, exitstatus): ) +def _cleanup_child_processes(): + """Kill any orphaned child processes that might be holding GPU memory. + + This is particularly important for tests that spawn subprocesses via torchrun. + """ + import subprocess + + current_pid = os.getpid() + try: + # Find child processes + result = subprocess.run( + ["pgrep", "-P", str(current_pid)], check=False, capture_output=True, text=True, timeout=5 + ) + child_pids = result.stdout.strip().split("\n") + for pid_str in child_pids: + if pid_str: + try: + pid = int(pid_str) + os.kill(pid, signal.SIGTERM) + except (ValueError, ProcessLookupError, PermissionError): + pass + except (subprocess.TimeoutExpired, FileNotFoundError): + pass + + +def _thorough_gpu_cleanup(): + """Perform thorough GPU memory cleanup.""" + if not torch.cuda.is_available(): + return + + # Synchronize all CUDA streams to ensure all operations are complete + torch.cuda.synchronize() + + # Clear all cached memory + torch.cuda.empty_cache() + + # Reset peak memory stats + torch.cuda.reset_peak_memory_stats() + + # Run garbage collection multiple times to ensure all objects are collected + for _ in range(3): + gc.collect() + + # Another sync and cache clear after gc + torch.cuda.synchronize() + torch.cuda.empty_cache() + + # Small sleep to allow GPU memory to be fully released + time.sleep(0.1) + + +def _reset_random_seeds(): + """Reset random seeds to ensure reproducibility across tests. + + Some tests may modify global random state, which can affect subsequent tests + that depend on random splitting (like dataset preprocessing). + """ + # Reset Python's random module + random.seed(None) + + # Reset NumPy's random state (intentionally using legacy API to reset global state) + np.random.seed(None) # noqa: NPY002 + + # Reset PyTorch's random state + torch.seed() + if torch.cuda.is_available(): + torch.cuda.seed_all() + + @pytest.fixture(autouse=True) def cleanup_after_test(): - """Clean up GPU memory after each test.""" + """Clean up GPU memory and reset state after each test.""" + # Reset random seeds before the test to ensure reproducibility + _reset_random_seeds() + yield - if torch.cuda.is_available(): - torch.cuda.empty_cache() - gc.collect() + + # After the test, perform thorough cleanup + _thorough_gpu_cleanup() + + # Clean up any orphaned child processes (important for subprocess tests) + _cleanup_child_processes() + + # Final garbage collection + gc.collect() def pytest_addoption(parser: pytest.Parser): """Pytest configuration for bionemo.evo2.run tests. Adds custom command line options for dataset paths.""" parser.addoption("--dataset-dir", action="store", default=None, help="Path to preprocessed dataset directory") parser.addoption("--training-config", action="store", default=None, help="Path to training data config YAML file") + + +# ============================================================================= +# Session-scoped checkpoint fixtures for sharing across test files +# ============================================================================= + + +@pytest.fixture(scope="session") +def mbridge_checkpoint_1b_8k_bf16(tmp_path_factory) -> Path: + """Session-scoped MBridge checkpoint for the 1b-8k-bf16 model. + + This fixture converts the NeMo2 checkpoint to MBridge format once per test session, + allowing it to be shared across multiple test files (test_infer.py, test_predict.py, etc.). + + Returns: + Path to the MBridge checkpoint iteration directory (e.g., .../iter_0000001) + """ + from bionemo.core.data.load import load + from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH_512 + from bionemo.evo2.utils.checkpoint.nemo2_to_mbridge import run_nemo2_to_mbridge + + try: + nemo2_ckpt_path = load("evo2/1b-8k-bf16:1.0") + except ValueError as e: + if e.args[0].endswith("does not have an NGC URL."): + pytest.skip( + "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " + "one or more files are missing from ngc." + ) + else: + raise e + + output_dir = tmp_path_factory.mktemp("mbridge_ckpt_1b_8k_bf16_session") + mbridge_ckpt_dir = run_nemo2_to_mbridge( + nemo2_ckpt_dir=nemo2_ckpt_path, + tokenizer_path=DEFAULT_HF_TOKENIZER_MODEL_PATH_512, + mbridge_ckpt_dir=output_dir / "evo2_1b_mbridge", + model_size="1b", + seq_length=8192, + mixed_precision_recipe="bf16_mixed", + vortex_style_fp8=False, + ) + return mbridge_ckpt_dir / "iter_0000001" + + +@pytest.fixture(scope="module") +def mbridge_checkpoint_path(mbridge_checkpoint_1b_8k_bf16) -> Path: + """Module-scoped alias for the session-scoped 1b-8k-bf16 checkpoint. + + This provides backward compatibility for tests that use the name 'mbridge_checkpoint_path'. + The actual checkpoint is shared at session scope via mbridge_checkpoint_1b_8k_bf16. + + Returns: + Path to the MBridge checkpoint iteration directory + """ + return mbridge_checkpoint_1b_8k_bf16 diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_fasta_dataset.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_fasta_dataset.py index a9e9ed5cdd..851f9b7f8c 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_fasta_dataset.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_fasta_dataset.py @@ -16,80 +16,142 @@ # See the License for the specific language governing permissions and # limitations under the License. -# FIXME bring back these tests -# from pathlib import Path +"""Tests for SimpleFastaDataset.""" -# import pytest -# import torch -# from bionemo.evo2.data.fasta_dataset import SimpleFastaDataset -# from bionemo.testing.data.fasta import create_fasta_file +import json +from pathlib import Path +import pytest +import torch +from megatron.bridge.training.tokenizers.config import TokenizerConfig +from megatron.bridge.training.tokenizers.tokenizer import build_tokenizer -# # from nemo.collections.nlp.modules.common.tokenizer_utils import get_nmt_tokenizer +from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH +from bionemo.evo2.data.fasta_dataset import SimpleFastaDataset +from bionemo.evo2.data.test_utils.create_fasta_file import create_fasta_file -# def get_nmt_tokenizer(tokenizer_type: str): -# """FIXME use an automodel HF tokenizer.""" -# raise NotImplementedError("FIXME use an automodel HF tokenizer.") +@pytest.fixture +def tokenizer(): + """Return a HuggingFace tokenizer for testing.""" + return build_tokenizer( + TokenizerConfig( + tokenizer_type="HuggingFaceTokenizer", + hf_tokenizer_kwargs={"trust_remote_code": False}, + tokenizer_model=DEFAULT_HF_TOKENIZER_MODEL_PATH, + ) + ) -# @pytest.fixture -# def fasta_dataset(tmp_path: Path) -> None: -# """Fixture to create a SimpleFastaDataset for testing.""" -# test_fasta_file_path = create_fasta_file(tmp_path / "test.fasta", num_sequences=10, sequence_length=100) -# tokenizer = get_nmt_tokenizer("byte-level") -# return SimpleFastaDataset(test_fasta_file_path, tokenizer) +@pytest.fixture +def fasta_dataset(tmp_path: Path, tokenizer) -> SimpleFastaDataset: + """Fixture to create a SimpleFastaDataset for testing.""" + test_fasta_file_path = create_fasta_file(tmp_path / "test.fasta", num_sequences=10, sequence_length=100) + return SimpleFastaDataset(test_fasta_file_path, tokenizer) -# def test_simple_fasta_dataset_initialization(fasta_dataset: SimpleFastaDataset) -> None: -# """Test initialization of SimpleFastaDataset.""" -# # Check dataset length -# assert len(fasta_dataset) == 10, "Dataset length should match number of sequences" +def test_simple_fasta_dataset_initialization(fasta_dataset: SimpleFastaDataset) -> None: + """Test initialization of SimpleFastaDataset.""" + # Check dataset length + assert len(fasta_dataset) == 10, "Dataset length should match number of sequences" -# # Check seqids -# assert len(fasta_dataset.seqids) == 10, "Seqids should match number of sequences" + # Check seqids + assert len(fasta_dataset.seqids) == 10, "Seqids should match number of sequences" -# def test_simple_fasta_dataset_getitem(fasta_dataset: SimpleFastaDataset) -> None: -# """Test __getitem__ method of SimpleFastaDataset.""" -# # Test first item -# item = fasta_dataset[0] +def test_simple_fasta_dataset_getitem(fasta_dataset: SimpleFastaDataset) -> None: + """Test __getitem__ method of SimpleFastaDataset.""" + # Test first item + item = fasta_dataset[0] -# # Check keys -# expected_keys = {"tokens", "position_ids", "seq_idx", "loss_mask"} -# assert set(item.keys()) == expected_keys, "Item should have correct keys" + # Check keys + expected_keys = {"tokens", "position_ids", "seq_idx", "loss_mask"} + assert set(item.keys()) == expected_keys, "Item should have correct keys" -# # Check token type -# assert isinstance(item["tokens"], torch.Tensor), "Tokens should be a torch.Tensor" -# assert item["tokens"].dtype == torch.long, "Tokens should be long dtype" + # Check token type + assert isinstance(item["tokens"], torch.Tensor), "Tokens should be a torch.Tensor" + assert item["tokens"].dtype == torch.long, "Tokens should be long dtype" -# # Check position_ids -# assert isinstance(item["position_ids"], torch.Tensor), "Position IDs should be a torch.Tensor" -# assert item["position_ids"].dtype == torch.long, "Position IDs should be long dtype" + # Check position_ids + assert isinstance(item["position_ids"], torch.Tensor), "Position IDs should be a torch.Tensor" + assert item["position_ids"].dtype == torch.long, "Position IDs should be long dtype" -# # Validate sequence index -# assert isinstance(item["seq_idx"], torch.Tensor), "Seq_idx should be a torch.Tensor" -# assert item["seq_idx"].item() == 0, "First item should have seq_idx 0" + # Validate sequence index + assert isinstance(item["seq_idx"], torch.Tensor), "Seq_idx should be a torch.Tensor" + assert item["seq_idx"].item() == 0, "First item should have seq_idx 0" + # Check loss_mask + assert isinstance(item["loss_mask"], torch.Tensor), "Loss mask should be a torch.Tensor" + assert item["loss_mask"].dtype == torch.long, "Loss mask should be long dtype" -# def test_simple_fasta_dataset_write_idx_map(fasta_dataset: SimpleFastaDataset, tmp_path: Path) -> None: -# """Test write_idx_map method of SimpleFastaDataset.""" -# # Create output directory -# output_dir = tmp_path / "output" -# output_dir.mkdir(parents=True, exist_ok=True) + # With prepend_bos=True (default), the first token should be masked + assert item["loss_mask"][0].item() == 0, "First token (BOS) should be masked" -# # Write index map -# fasta_dataset.write_idx_map(output_dir) + # Tokens length should be sequence_length + 1 (for BOS) + # Since we create sequences of length 100, tokens should be 101 + assert len(item["tokens"]) == 101, "Tokens should include BOS token" + assert len(item["position_ids"]) == 101, "Position IDs should match tokens length" -# # Check if file was created -# idx_map_file = output_dir / "seq_idx_map.json" -# assert idx_map_file.exists(), "seq_idx_map.json should be created" -# import json +def test_simple_fasta_dataset_write_idx_map(fasta_dataset: SimpleFastaDataset, tmp_path: Path) -> None: + """Test write_idx_map method of SimpleFastaDataset.""" + # Create output directory + output_dir = tmp_path / "output" + output_dir.mkdir(parents=True, exist_ok=True) -# with open(idx_map_file, "r") as f: -# idx_map = json.load(f) + # Write index map + fasta_dataset.write_idx_map(output_dir) -# assert len(idx_map) == 10, "Index map should have an entry for each sequence" -# for idx, seqid in enumerate(fasta_dataset.seqids): -# assert idx_map[seqid] == idx, f"Index for {seqid} should match" + # Check if file was created + idx_map_file = output_dir / "seq_idx_map.json" + assert idx_map_file.exists(), "seq_idx_map.json should be created" + + with open(idx_map_file) as f: + idx_map = json.load(f) + + assert len(idx_map) == 10, "Index map should have an entry for each sequence" + for idx, seqid in enumerate(fasta_dataset.seqids): + assert idx_map[seqid] == idx, f"Index for {seqid} should match" + + +def test_simple_fasta_dataset_no_bos(tmp_path: Path, tokenizer) -> None: + """Test SimpleFastaDataset without BOS token prepending.""" + test_fasta_file_path = create_fasta_file(tmp_path / "test_no_bos.fasta", num_sequences=5, sequence_length=50) + dataset = SimpleFastaDataset(test_fasta_file_path, tokenizer, prepend_bos=False) + + item = dataset[0] + + # Without BOS, tokens length should equal sequence length + assert len(item["tokens"]) == 50, "Tokens should not include BOS token" + assert len(item["position_ids"]) == 50, "Position IDs should match tokens length" + + # All tokens should be unmasked (loss_mask all 1s) + assert item["loss_mask"].sum().item() == 50, "All tokens should be unmasked without BOS" + + +def test_simple_fasta_dataset_variable_lengths(tmp_path: Path, tokenizer) -> None: + """Test SimpleFastaDataset with variable sequence lengths.""" + sequence_lengths = [50, 100, 150, 200, 75] + test_fasta_file_path = create_fasta_file( + tmp_path / "test_variable.fasta", num_sequences=5, sequence_lengths=sequence_lengths + ) + dataset = SimpleFastaDataset(test_fasta_file_path, tokenizer) + + assert len(dataset) == 5, "Dataset should have 5 sequences" + + # Check each item has the correct length (sequence_length + 1 for BOS) + for i, expected_len in enumerate(sequence_lengths): + item = dataset[i] + assert len(item["tokens"]) == expected_len + 1, f"Sequence {i} should have length {expected_len + 1}" + + +def test_simple_fasta_dataset_iteration(fasta_dataset: SimpleFastaDataset) -> None: + """Test that we can iterate through the entire dataset.""" + count = 0 + for i in range(len(fasta_dataset)): + item = fasta_dataset[i] + assert item is not None, f"Item {i} should not be None" + assert "tokens" in item, f"Item {i} should have 'tokens' key" + count += 1 + + assert count == 10, "Should iterate through all 10 items" diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_sharded_eden_dataset_provider.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_sharded_eden_dataset_provider.py index cffed080fb..f7b8acd6a6 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_sharded_eden_dataset_provider.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_sharded_eden_dataset_provider.py @@ -26,8 +26,6 @@ from megatron.bridge.training.tokenizers.tokenizer import build_tokenizer from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH, DEFAULT_HF_TOKENIZER_MODEL_PATH_512 - -# FIXME revive this since it might make some tests/training runs easier. from bionemo.evo2.data.sharded_eden_dataset_provider import ( DatasetBuildContext, ShardedEdenDataset, diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_tokenizer.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_tokenizer.py index e914c989ab..ba526aa944 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_tokenizer.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/data/test_tokenizer.py @@ -56,6 +56,45 @@ def test_tokenizer_vocab_size(tokenizer_path: Path, expected_vocab_size: int) -> assert tokenizer.vocab_size == expected_vocab_size +@pytest.mark.parametrize( + "tokenizer_path", + [ + DEFAULT_HF_TOKENIZER_MODEL_PATH, + DEFAULT_HF_TOKENIZER_MODEL_PATH_512, + ], +) +def test_tokenizer_roundtrip_without_spaces(tokenizer_path: Path) -> None: + """Verifies tokenization followed by detokenization returns the original sequence. + + This is critical for character-level tokenizers used in DNA sequence modeling. + The tokenizer should NOT add spaces between tokens during detokenization. + """ + tokenizer = build_tokenizer( + TokenizerConfig( + tokenizer_type="HuggingFaceTokenizer", + hf_tokenizer_kwargs={"trust_remote_code": False}, + tokenizer_model=tokenizer_path, + ) + ) + # Test basic DNA sequence + original = "ATCGATCGATCG" + token_ids = tokenizer.text_to_ids(original) + reconstructed = tokenizer.detokenize(token_ids) + assert reconstructed == original, f"Expected '{original}', got '{reconstructed}'" + + # Test longer sequence with all nucleotides + original_long = "AAAAACCCCCGGGGGTTTTTATCGATCGNNNNN" + token_ids_long = tokenizer.text_to_ids(original_long) + reconstructed_long = tokenizer.detokenize(token_ids_long) + assert reconstructed_long == original_long, f"Expected '{original_long}', got '{reconstructed_long}'" + + # Test sequence with special characters (pipe-delimited tags) + original_tagged = "|info|ATCG|end|" + token_ids_tagged = tokenizer.text_to_ids(original_tagged) + reconstructed_tagged = tokenizer.detokenize(token_ids_tagged) + assert reconstructed_tagged == original_tagged, f"Expected '{original_tagged}', got '{reconstructed_tagged}'" + + def test_tokenizer_handles_long_dna_sequence(tokenizer: Evo2DatasetTokenizer) -> None: """Verifies tokenizer correctly processes a long DNA sequence into expected token IDs. diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/__init__.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/__init__.py new file mode 100644 index 0000000000..709f3b03b0 --- /dev/null +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-Apache2 +# +# 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. + +"""Hyena model tests package.""" diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_mixer_kernel.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_mixer_kernel.py index 3f4e36b091..2c18811de4 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_mixer_kernel.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_mixer_kernel.py @@ -32,6 +32,8 @@ from bionemo.evo2.models.megatron.hyena.hyena_mixer import HyenaMixer from bionemo.evo2.models.megatron.hyena.hyena_utils import ImplicitModalFilter +from ....utils import find_free_network_port + try: import subquadratic_ops_torch # noqa: F401 @@ -53,7 +55,7 @@ def init_distributed_parallel_state( if not dist.is_initialized(): # Setup minimal environment for single process distributed os.environ["MASTER_ADDR"] = "localhost" - os.environ["MASTER_PORT"] = "29500" + os.environ["MASTER_PORT"] = str(find_free_network_port()) os.environ["RANK"] = str(rank) os.environ["WORLD_SIZE"] = str(world_size) @@ -388,7 +390,7 @@ def test_subquadratic_ops_kernel( # noqa: D103 mixer_kernel.zero_grad() # Compare results between PyTorch and CUDA kernel implementations - torch.testing.assert_close(output_features, output_features_kernel, msg=f"Output mismatch for {operator_type}") + torch.testing.assert_close(output_features, output_features_kernel, rtol=0.02, atol=2e-4) torch.testing.assert_close(loss, loss_kernel, msg=f"Loss mismatch for {operator_type}") # Compare gradients diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_utils.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_utils.py index 59017a1f7c..2ba21709fa 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_utils.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/models/megatron/hyena/test_hyena_utils.py @@ -546,11 +546,17 @@ def test_fallback_functions_import_error_messages(self): def test_einops_import_error(self): """Test that the einops import error is raised with the correct message.""" - # Mock the import to fail - with patch.dict("sys.modules", {"einops": None}): - # Re-import the module to trigger the import error - with pytest.raises(ImportError, match="einops is required by the Hyena model but cannot be imported"): - import bionemo.evo2.models.megatron.hyena.hyena_utils - - # Force a reload of the module to trigger the import error - importlib.reload(bionemo.evo2.models.megatron.hyena.hyena_utils) + import bionemo.evo2.models.megatron.hyena.hyena_utils + + try: + # Mock the import to fail + with patch.dict("sys.modules", {"einops": None}): + # Re-import the module to trigger the import error + with pytest.raises(ImportError, match="einops is required by the Hyena model but cannot be imported"): + # Force a reload of the module to trigger the import error + importlib.reload(bionemo.evo2.models.megatron.hyena.hyena_utils) + finally: + # CRITICAL: Always restore the module to its proper state after the test. + # The reload above leaves the module in a corrupted state, which can cause + # subsequent tests to fail (especially test_infer.py tests). + importlib.reload(bionemo.evo2.models.megatron.hyena.hyena_utils) diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/common.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/common.py deleted file mode 100644 index 92ebd07afc..0000000000 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/common.py +++ /dev/null @@ -1,71 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Arc Institute. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Michael Poli. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Stanford University. All rights reserved -# SPDX-License-Identifier: LicenseRef-Apache2 -# -# 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. - - -def small_training_cmd( - path, - max_steps, - val_check, - global_batch_size: int | None = None, - devices: int = 1, - additional_args: str = "", -): - """Command for training.""" - cmd = ( - f"train_evo2 --mock-data --result-dir {path} --devices {devices} " - "--model-size 1b_nv --num-layers 4 --hybrid-override-pattern SDH* --limit-val-batches 1 " - "--no-activation-checkpointing --add-bias-output --create-tensorboard-logger --create-tflops-callback " - f"--max-steps {max_steps} --warmup-steps 1 --val-check-interval {val_check} " - f"--seq-length 16 --hidden-dropout 0.1 --attention-dropout 0.1 {additional_args} " - f"{'--global-batch-size ' + str(global_batch_size) if global_batch_size is not None else ''}" - ) - return cmd - - -def small_training_finetune_cmd( - path, - max_steps, - val_check, - prev_ckpt, - devices: int = 1, - global_batch_size: int | None = None, - create_tflops_callback: bool = True, - additional_args: str = "", -): - """Command for finetuning.""" - cmd = ( - f"train_evo2 --mock-data --result-dir {path} --devices {devices} " - "--model-size 1b_nv --num-layers 4 --hybrid-override-pattern SDH* --limit-val-batches 1 " - "--no-activation-checkpointing --add-bias-output --create-tensorboard-logger " - f"--max-steps {max_steps} --warmup-steps 1 --val-check-interval {val_check} " - f"--seq-length 16 --hidden-dropout 0.1 --attention-dropout 0.1 {additional_args} --ckpt-dir {prev_ckpt} " - f"{'--create-tflops-callback' if create_tflops_callback else ''} " - f"{'--global-batch-size ' + str(global_batch_size) if global_batch_size is not None else ''}" - ) - return cmd - - -def predict_cmd(ckpt_dir: str, output_dir: str, fasta_file_path: str, additional_args: str = ""): - """Command fro predict.""" - cmd = ( - f"predict_evo2 --fasta {fasta_file_path} --ckpt-dir {ckpt_dir} --output-dir {output_dir} " - "--model-size 1b_nv --num-layers 4 --hybrid-override-pattern SDH* --tensor-parallel-size 1 " - f"--pipeline-model-parallel-size 1 --context-parallel-size 1 {additional_args}" - ) - - return cmd diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_finetune.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_finetune.py deleted file mode 100644 index fce04f2d24..0000000000 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_finetune.py +++ /dev/null @@ -1,201 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Arc Institute. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Michael Poli. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Stanford University. All rights reserved -# SPDX-License-Identifier: LicenseRef-Apache2 -# -# 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. -# FIXME bring back these tests -# import re - -# import pytest -# from bionemo.testing.subprocess_utils import run_command_in_subprocess - -# from .common import small_training_cmd, small_training_finetune_cmd - - -# def extract_val_losses(log_text: str, n: int): -# """ -# Extracts validation losses every n-th occurrence (starting at 0). -# Iteration index is derived by counting val_loss appearances. - -# Args: -# log_text (str): The log output as a string. -# n (int): Interval of occurrences (e.g., n=5 -> get val_loss at 0, 5, 10...). - -# Returns: -# List of tuples: (step, validation_loss_value). -# """ -# # Regex to capture val_loss values -# pattern = re.compile(r"val_loss: ([0-9.]+)") - -# results = [] -# for idx, match in enumerate(pattern.finditer(log_text)): -# if idx % n == 0: # take every n-th val_loss occurrence -# results.append((idx, float(match.group(1)))) - -# return results - - -# @pytest.mark.timeout(2048) # Optional: fail if the test takes too long. -# @pytest.mark.slow -# @pytest.mark.parametrize("with_peft", [True, False]) -# def test_train_evo2_finetune_runs(tmp_path, with_peft: bool): -# """ -# This test runs the `train_evo2` command with mock data in a temporary directory. -# It uses the temporary directory provided by pytest as the working directory. -# The command is run in a subshell, and we assert that it returns an exit code of 0. -# """ -# num_steps = 25 -# val_steps = 10 -# global_batch_size = 128 - -# # Note: The command assumes that `train_evo2` is in your PATH. -# command = small_training_cmd( -# tmp_path / "pretrain", -# max_steps=num_steps, -# val_check=val_steps, -# global_batch_size=global_batch_size, -# additional_args=" --lr 0.1 ", -# ) -# stdout_pretrain: str = run_command_in_subprocess(command=command, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" not in stdout_pretrain - -# log_dir = tmp_path / "pretrain" / "evo2" -# checkpoints_dir = log_dir / "checkpoints" -# tensorboard_dir = log_dir / "dev" - -# # Check if logs dir exists -# assert log_dir.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps * global_batch_size}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir.exists(), "TensorBoard logs folder does not exist." - -# event_files = list(tensorboard_dir.rglob("events.out.tfevents*")) -# assert len(event_files) == 1, f"No or multiple TensorBoard event files found under {tensorboard_dir}" - -# val_losses = extract_val_losses(stdout_pretrain, val_steps) - -# for i in range(1, len(val_losses)): -# assert val_losses[i][1] <= val_losses[i - 1][1], ( -# f"Validation loss increased at step {val_losses[i][0]}: {val_losses[i][1]} > {val_losses[i - 1][1]}" -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files = list(tensorboard_dir.rglob("events.out.tfevents*")) -# assert event_files, f"No TensorBoard event files found under {tensorboard_dir}" -# assert len(matching_subfolders) == 1, "Only one checkpoint subfolder should be found." -# if with_peft: -# result_dir = tmp_path / "lora_finetune" -# additional_args = "--lora-finetune --lr 0.1 " -# else: -# result_dir = tmp_path / "finetune" -# additional_args = " --lr 0.1 " - -# command_finetune = small_training_finetune_cmd( -# result_dir, -# max_steps=num_steps, -# val_check=val_steps, -# global_batch_size=global_batch_size, -# prev_ckpt=matching_subfolders[0], -# create_tflops_callback=not with_peft, -# additional_args=additional_args, -# ) -# stdout_finetune: str = run_command_in_subprocess(command=command_finetune, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" in stdout_finetune - -# log_dir_ft = result_dir / "evo2" -# checkpoints_dir_ft = log_dir_ft / "checkpoints" -# tensorboard_dir_ft = log_dir_ft / "dev" - -# # Check if logs dir exists -# assert log_dir_ft.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir_ft.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps * global_batch_size}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders_finetune = [ -# p for p in checkpoints_dir_ft.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders_finetune, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir_ft}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir_ft.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files_ft = list(tensorboard_dir_ft.rglob("events.out.tfevents*")) -# assert len(event_files_ft) == 1, f"No or multiple TensorBoard event files found under {tensorboard_dir_ft}" - -# val_losses_ft = extract_val_losses(stdout_finetune, val_steps) - -# # Check that each validation loss is less than or equal to the previous one -# for i in range(1, len(val_losses_ft)): -# assert val_losses_ft[i][1] <= val_losses_ft[i - 1][1], ( -# f"Validation loss increased at step {val_losses_ft[i][0]}: {val_losses_ft[i][1]} > {val_losses_ft[i - 1][1]}" -# ) - -# assert len(matching_subfolders_finetune) == 1, "Only one checkpoint subfolder should be found." - -# # With LoRA, test resuming from a saved LoRA checkpoint -# if with_peft: -# result_dir = tmp_path / "lora_finetune_resume" - -# # Resume from LoRA checkpoint -# command_resume_finetune = small_training_finetune_cmd( -# result_dir, -# max_steps=num_steps, -# val_check=val_steps, -# global_batch_size=global_batch_size, -# prev_ckpt=matching_subfolders[0], -# create_tflops_callback=False, -# additional_args=f"--lora-finetune --lora-checkpoint-path {matching_subfolders_finetune[0]} --lr 0.1 ", -# ) -# stdout_finetune: str = run_command_in_subprocess(command=command_resume_finetune, path=str(tmp_path)) - -# log_dir_ft = result_dir / "evo2" -# checkpoints_dir_ft = log_dir_ft / "checkpoints" -# tensorboard_dir_ft = log_dir_ft / "dev" - -# # Check if logs dir exists -# assert log_dir_ft.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir_ft.exists(), "Checkpoints folder does not exist." - -# # Recursively search for files with tensorboard logger -# event_files_ft = list(tensorboard_dir_ft.rglob("events.out.tfevents*")) -# assert len(event_files_ft) == 1, f"No or multiple TensorBoard event files found under {tensorboard_dir_ft}" - -# val_losses_ft = extract_val_losses(stdout_finetune, val_steps) - -# # Check that each validation loss is less than or equal to the previous one -# for i in range(1, len(val_losses_ft)): -# assert val_losses_ft[i][1] <= val_losses_ft[i - 1][1], ( -# f"Validation loss increased at step {val_losses_ft[i][0]}: {val_losses_ft[i][1]} > {val_losses_ft[i - 1][1]}" -# ) diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_infer.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_infer.py index 3c4de4d918..944e899cf4 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_infer.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_infer.py @@ -16,63 +16,438 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Tests for Evo2 text generation (inference) using MBridge. -# import pytest -# import torch -# from bionemo.core.data.load import load -# from bionemo.evo2.run.infer import infer -# from bionemo.testing.megatron_parallel_state_utils import clean_parallel_state_context -# from bionemo.testing.torch import check_fp8_support - - -# RANDOM_SEED = 42 -# FIXME bring back these tests - -# @pytest.mark.parametrize("fast", [True, False]) -# def test_run_infer(fast: bool): -# # Create PTL trainer. -# tensor_parallel_size = 1 -# pipeline_model_parallel_size = 1 -# context_parallel_size = 1 -# temperature = 1.0 -# top_k = 0 -# top_p = 0.0 -# max_new_tokens = 1 - -# # Generation args. -# default_prompt = ( -# "|d__Bacteria;" -# + "p__Pseudomonadota;" -# + "c__Gammaproteobacteria;" -# + "o__Enterobacterales;" -# + "f__Enterobacteriaceae;" -# + "g__Escherichia;" -# + "s__Escherichia|" -# ) -# try: -# checkpoint_path = load("evo2/1b-8k:1.0") -# except ValueError as e: -# if e.args[0].endswith("does not have an NGC URL."): -# raise ValueError( -# "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " -# "one or more files are missing from ngc." -# ) -# else: -# raise e - -# is_fp8_supported, _, _ = check_fp8_support(torch.cuda.current_device()) - -# with clean_parallel_state_context(): -# infer( -# prompt=default_prompt, -# ckpt_dir=checkpoint_path, -# temperature=temperature, -# top_k=top_k, -# top_p=top_p, -# max_new_tokens=max_new_tokens, -# tensor_parallel_size=tensor_parallel_size, -# pipeline_model_parallel_size=pipeline_model_parallel_size, -# context_parallel_size=context_parallel_size, -# vortex_style_fp8=is_fp8_supported, -# flash_decode=fast, -# ) +NOTE: Autoregressive generation tests may fail due to: +1. FP8 execution requires sequence dimensions divisible by 8/16 +2. The vortex flash_decode path needs additional integration work + +The core forward pass (predict.py) and HyenaInferenceContext are tested +in test_evo2.py which has working test_forward_manual and test_forward_ckpt_conversion. +""" + +import copy +import os +import subprocess + +import pytest +import torch + +from bionemo.evo2.models.evo2_provider import HyenaInferenceContext + +from ..utils import find_free_network_port + + +# Capture environment at import time (consistent with test_predict.py) +PRETEST_ENV = copy.deepcopy(os.environ) + +# Note: mbridge_checkpoint_path fixture is provided by conftest.py at session scope + + +def test_infer_runs(mbridge_checkpoint_path, tmp_path): + """Test that infer.py runs without errors.""" + output_file = tmp_path / "output.txt" + + # Use a longer DNA prompt to meet FP8 dimension requirements (divisible by 8) + # 64 characters should be safe + prompt = "ATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCG" + open_port = find_free_network_port() + + cmd = [ + "torchrun", + "--nproc_per_node", + "1", + "--nnodes", + "1", + "--master_port", + str(open_port), + "-m", + "bionemo.evo2.run.infer", + "--ckpt-dir", + str(mbridge_checkpoint_path), + "--prompt", + prompt, + "--max-new-tokens", + "10", + "--output-file", + str(output_file), + "--temperature", + "1.0", # Non-zero temperature required by MCore + "--top-k", + "1", # Top-k=1 for greedy decoding + ] + + env = copy.deepcopy(PRETEST_ENV) + + result = subprocess.run( + cmd, + check=False, + capture_output=True, + text=True, + timeout=300, # 5 minutes + env=env, + ) + + assert result.returncode == 0, f"infer command failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert output_file.exists(), "Output file was not created" + + # Check that output contains generated text + generated = output_file.read_text() + assert len(generated) > 0, "Generated text is empty" + + +@pytest.mark.parametrize("temperature", [0.5, 1.0]) +def test_infer_temperature(mbridge_checkpoint_path, tmp_path, temperature): + """Test that different temperatures produce output.""" + output_file = tmp_path / f"output_temp_{temperature}.txt" + # Use a longer prompt for FP8 compatibility + prompt = "ATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCG" + open_port = find_free_network_port() + + cmd = [ + "torchrun", + "--nproc_per_node", + "1", + "--nnodes", + "1", + "--master_port", + str(open_port), + "-m", + "bionemo.evo2.run.infer", + "--ckpt-dir", + str(mbridge_checkpoint_path), + "--prompt", + prompt, + "--max-new-tokens", + "5", + "--temperature", + str(temperature), + "--output-file", + str(output_file), + ] + + env = copy.deepcopy(PRETEST_ENV) + + result = subprocess.run( + cmd, + check=False, + capture_output=True, + text=True, + timeout=300, # 5 minutes + env=env, + ) + + assert result.returncode == 0, f"infer command failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + + +def test_infer_top_k(mbridge_checkpoint_path, tmp_path): + """Test top-k sampling.""" + output_file = tmp_path / "output_topk.txt" + # Use a longer prompt for FP8 compatibility + prompt = "ATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCG" + open_port = find_free_network_port() + + cmd = [ + "torchrun", + "--nproc_per_node", + "1", + "--nnodes", + "1", + "--master_port", + str(open_port), + "-m", + "bionemo.evo2.run.infer", + "--ckpt-dir", + str(mbridge_checkpoint_path), + "--prompt", + prompt, + "--max-new-tokens", + "5", + "--top-k", + "4", # Only sample from top 4 tokens (A, C, G, T) + "--output-file", + str(output_file), + ] + + env = copy.deepcopy(PRETEST_ENV) + + result = subprocess.run( + cmd, + check=False, + capture_output=True, + text=True, + timeout=300, # 5 minutes + env=env, + ) + + assert result.returncode == 0, f"infer command failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + + +def test_infer_phylogenetic_prompt(mbridge_checkpoint_path, tmp_path): + """Test generation with a phylogenetic lineage prompt. + + Evo2 is trained with phylogenetic tags, so generation should work + well when conditioned on these tags. Using a longer prompt for FP8. + """ + output_file = tmp_path / "output_phylo.txt" + + # Phylogenetic prompt (padded to be longer for FP8 compatibility) + prompt = ( + "|d__Bacteria;" + "p__Pseudomonadota;" + "c__Gammaproteobacteria;" + "o__Enterobacterales;" + "f__Enterobacteriaceae;" + "g__Escherichia;" + "s__Escherichia|" + ) + open_port = find_free_network_port() + + cmd = [ + "torchrun", + "--nproc_per_node", + "1", + "--nnodes", + "1", + "--master_port", + str(open_port), + "-m", + "bionemo.evo2.run.infer", + "--ckpt-dir", + str(mbridge_checkpoint_path), + "--prompt", + prompt, + "--max-new-tokens", + "20", + "--temperature", + "1.0", # Non-zero temperature required by MCore + "--top-k", + "1", # Top-k=1 for greedy decoding + "--output-file", + str(output_file), + ] + + env = copy.deepcopy(PRETEST_ENV) + + result = subprocess.run( + cmd, + check=False, + capture_output=True, + text=True, + timeout=300, # 5 minutes + env=env, + ) + + assert result.returncode == 0, f"infer command failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert output_file.exists(), "Output file was not created" + + generated = output_file.read_text() + assert len(generated) > 0, "Generated text is empty" + + +# DNA prompts for reproducibility tests (from test_prompt.py) +PROMPT_1 = "GAATAGGAACAGCTCCGGTCTACAGCTCCCAGCGTGAGCGACGCAGAAGACGGTGATTTCTGCATTTCCATCTGAGGTACCGGGTTCATCTCACTAGGGAGTGCCAGACAGTGGGCGCAGGCCAGTGTGTGTGCGCACCGTGCGCGAGCCGAAGCAGGG" +PROMPT_2 = "GATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTCTCCATGCATTTGGTATTTTCGTCTGGGGGGTATGCACGCGATAGCATTGCGAGACGCTGGAGCCGGAGCACCCTATGTCGCAGTATCTGTCTTTGATTCCTGCCTCATCCTATTATTT" + + +def run_infer_subprocess( + mbridge_checkpoint_path, + prompt: str, + output_file, + max_new_tokens: int = 10, + temperature: float = 1.0, + top_k: int = 1, + seed: int = 42, +): + """Helper function to run inference as a subprocess. + + Args: + mbridge_checkpoint_path: Path to the MBridge checkpoint + prompt: Input prompt for the model + output_file: Path to write output + max_new_tokens: Maximum number of tokens to generate + temperature: Sampling temperature + top_k: Top-k sampling parameter (1 for greedy) + seed: Random seed for reproducibility + + Returns: + The generated text from the output file + """ + open_port = find_free_network_port() + + cmd = [ + "torchrun", + "--nproc_per_node", + "1", + "--nnodes", + "1", + "--master_port", + str(open_port), + "-m", + "bionemo.evo2.run.infer", + "--ckpt-dir", + str(mbridge_checkpoint_path), + "--prompt", + prompt, + "--max-new-tokens", + str(max_new_tokens), + "--output-file", + str(output_file), + "--temperature", + str(temperature), + "--top-k", + str(top_k), + "--seed", + str(seed), + ] + + env = copy.deepcopy(PRETEST_ENV) + + result = subprocess.run( + cmd, + check=False, + capture_output=True, + text=True, + timeout=300, # 5 minutes + env=env, + ) + + assert result.returncode == 0, f"infer command failed:\nstdout: {result.stdout}\nstderr: {result.stderr}" + assert output_file.exists(), "Output file was not created" + + return output_file.read_text() + + +def test_identical_prompts_should_be_identical(mbridge_checkpoint_path, tmp_path): + """Test that identical prompts produce identical sequences. + + With greedy decoding (top_k=1) and the same seed, identical prompts + should produce identical outputs. + """ + output_file_1 = tmp_path / "output_prompt1_run1.txt" + output_file_2 = tmp_path / "output_prompt1_run2.txt" + + # Run inference twice with the same prompt + generated_1 = run_infer_subprocess( + mbridge_checkpoint_path, + prompt=PROMPT_1, + output_file=output_file_1, + max_new_tokens=20, + temperature=1.0, + top_k=1, # Greedy decoding for determinism + seed=42, + ) + + generated_2 = run_infer_subprocess( + mbridge_checkpoint_path, + prompt=PROMPT_1, + output_file=output_file_2, + max_new_tokens=20, + temperature=1.0, + top_k=1, # Greedy decoding for determinism + seed=42, + ) + + assert len(generated_1) > 0, "First generation produced empty output" + assert len(generated_2) > 0, "Second generation produced empty output" + assert generated_1 == generated_2, ( + f"Identical prompts with same seed and greedy decoding produced different outputs:\n" + f"Run 1: {generated_1}\n" + f"Run 2: {generated_2}" + ) + + +def test_different_prompts_produce_different_outputs(mbridge_checkpoint_path, tmp_path): + """Test that different prompts produce different sequences. + + Different input prompts should produce different outputs, demonstrating + that the model is actually responding to the prompt content. + """ + output_file_1 = tmp_path / "output_prompt1.txt" + output_file_2 = tmp_path / "output_prompt2.txt" + + # Run inference with two different prompts + generated_1 = run_infer_subprocess( + mbridge_checkpoint_path, + prompt=PROMPT_1, + output_file=output_file_1, + max_new_tokens=20, + temperature=1.0, + top_k=1, # Greedy decoding + seed=42, + ) + + generated_2 = run_infer_subprocess( + mbridge_checkpoint_path, + prompt=PROMPT_2, + output_file=output_file_2, + max_new_tokens=20, + temperature=1.0, + top_k=1, # Greedy decoding + seed=42, + ) + + assert len(generated_1) > 0, "First generation produced empty output" + assert len(generated_2) > 0, "Second generation produced empty output" + + # The outputs should be different since the prompts are different + # We check that the generated portions (after the prompt) are not identical + assert generated_1 != generated_2, ( + f"Different prompts produced identical outputs:\n" + f"Prompt 1 output: {generated_1}\n" + f"Prompt 2 output: {generated_2}" + ) + + +class TestHyenaInferenceContext: + """Unit tests for the Hyena-specific inference context.""" + + def test_context_initialization(self): + """Test that HyenaInferenceContext can be initialized.""" + context = HyenaInferenceContext(max_batch_size=1, max_sequence_length=8192) + assert context is not None + assert context.max_batch_size == 1 + assert context.max_sequence_length == 8192 + + def test_context_reset(self): + """Test that context reset works without error.""" + context = HyenaInferenceContext(max_batch_size=1, max_sequence_length=8192) + # Add some fake filter state (simulating what hyena layers do) + context.filter_state_dict_layer_0 = {"key": torch.zeros(10)} + context.filter_state_dict_layer_1 = {"key": torch.ones(10)} + + # Verify the state was added + assert hasattr(context, "filter_state_dict_layer_0") + assert hasattr(context, "filter_state_dict_layer_1") + + # Reset should remove all filter_state_dict attributes + context.reset() + + assert not hasattr(context, "filter_state_dict_layer_0") + assert not hasattr(context, "filter_state_dict_layer_1") + + def test_context_materialize_logits_setting(self): + """Test that materialize_only_last_token_logits can be configured.""" + context = HyenaInferenceContext(max_batch_size=1, max_sequence_length=8192) + + # Default should be True for efficiency + # We can set it to False if we need full sequence logits + context.materialize_only_last_token_logits = False + assert context.materialize_only_last_token_logits is False + + context.materialize_only_last_token_logits = True + assert context.materialize_only_last_token_logits is True + + def test_context_multiple_batches(self): + """Test context with different batch sizes.""" + for batch_size in [1, 2, 4]: + context = HyenaInferenceContext(max_batch_size=batch_size, max_sequence_length=4096) + assert context.max_batch_size == batch_size + context.reset() # Should not error + + def test_context_different_sequence_lengths(self): + """Test context with different max sequence lengths.""" + for seq_len in [1024, 8192, 16384]: + context = HyenaInferenceContext(max_batch_size=1, max_sequence_length=seq_len) + assert context.max_sequence_length == seq_len + context.reset() diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_inference.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_inference.py deleted file mode 100644 index 5f48d20888..0000000000 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_inference.py +++ /dev/null @@ -1,104 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Arc Institute. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Michael Poli. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Stanford University. All rights reserved -# SPDX-License-Identifier: LicenseRef-Apache2 -# -# 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. - -# FIXME bring back these tests -# import nemo.lightning as nl -# import torch -# from bionemo.core.data.load import load -# from bionemo.testing.megatron_parallel_state_utils import clean_parallel_state_context -# from megatron.core.inference.common_inference_params import CommonInferenceParams -# from nemo.collections.llm import generate - - -# RANDOM_SEED = 42 - - -# def test_infer_model_generates_expected_single_token_output(): -# # Create PTL trainer. -# TENSOR_PARALLEL_SIZE = 1 -# PIPELINE_MODEL_PARALLEL_SIZE = 1 -# CONTEXT_PARALLEL_SIZE = 1 -# NUM_GPUS = 1 -# NUM_NODES = 1 - -# strategy = nl.MegatronStrategy( -# tensor_model_parallel_size=TENSOR_PARALLEL_SIZE, -# pipeline_model_parallel_size=PIPELINE_MODEL_PARALLEL_SIZE, -# context_parallel_size=CONTEXT_PARALLEL_SIZE, -# pipeline_dtype=torch.bfloat16, -# ckpt_load_optimizer=False, # Needs to be false for a normal model checkpoint. -# ckpt_save_optimizer=False, -# ckpt_async_save=False, -# save_ckpt_format="torch_dist", -# ckpt_load_strictness="log_all", -# ) -# trainer = nl.Trainer( -# accelerator="gpu", -# num_nodes=NUM_NODES, -# devices=NUM_GPUS, -# strategy=strategy, -# log_every_n_steps=1, -# limit_val_batches=10, -# num_sanity_val_steps=0, -# plugins=nl.MegatronMixedPrecision( -# precision="bf16-mixed", -# params_dtype=torch.bfloat16, -# ), -# ) - -# prompt = ( -# "|d__Bacteria;" -# + "p__Pseudomonadota;" -# + "c__Gammaproteobacteria;" -# + "o__Enterobacterales;" -# + "f__Enterobacteriaceae;" -# + "g__Escherichia;" -# + "s__Escherichia|" -# ) -# temperature = 1.0 -# top_k = 0 -# top_p = 0.0 -# max_new_tokens = 1 -# try: -# checkpoint_path = load("evo2/1b-8k:1.0") -# except ValueError as e: -# if e.args[0].endswith("does not have an NGC URL."): -# raise ValueError( -# "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " -# "one or more files are missing from ngc." -# ) -# else: -# raise e -# with clean_parallel_state_context(): -# results = generate( -# path=checkpoint_path, -# prompts=[prompt], -# trainer=trainer, -# inference_params=CommonInferenceParams( -# temperature, -# top_k, -# top_p, -# return_log_probs=False, -# num_tokens_to_generate=max_new_tokens, -# ), -# random_seed=RANDOM_SEED, -# text_only=True, -# ) - -# assert isinstance(results, list) -# assert results == ["T"] diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_predict.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_predict.py index 6121f660b2..e56b2f088e 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_predict.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_predict.py @@ -16,498 +16,651 @@ # See the License for the specific language governing permissions and # limitations under the License. -# FIXME bring back these tests -# import glob -# import json -# import os -# import subprocess -# import sys -# import tempfile -# from pathlib import Path - -# # import lightning as pl -# import pytest -# import torch -# from bionemo.core.data.load import load -# from bionemo.llm.lightning import batch_collator -# from bionemo.testing.data.fasta import ALU_SEQUENCE, create_fasta_file -# from bionemo.testing.subprocess_utils import run_command_in_subprocess -# from bionemo.testing.torch import check_fp8_support - -# # FIXME copy this out of lightning. This is a useful utility. -# # from lightning.fabric.plugins.environments.lightning import find_free_network_port -# from .common import predict_cmd, small_training_finetune_cmd - - -# def find_free_network_port(*args, **kwargs): -# raise NotImplementedError("FIXME find_free_network_port is not implemented Find it in megatron bridge") - - -# def is_a6000_gpu() -> bool: -# # Check if any of the visible GPUs is an A6000 -# for i in range(torch.cuda.device_count()): -# device_name = torch.cuda.get_device_name(i) -# if "A6000" in device_name: -# return True -# return False - - -# @pytest.fixture(scope="module") -# def checkpoint_1b_8k_bf16_path() -> Path: -# try: -# checkpoint_path = load("evo2/1b-8k-bf16:1.0") -# except ValueError as e: -# if e.args[0].endswith("does not have an NGC URL."): -# raise ValueError( -# "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " -# "one or more files are missing from ngc." -# ) -# else: -# raise e -# yield checkpoint_path - - -# @pytest.fixture(scope="module") -# def checkpoint_7b_1m_path() -> Path: -# try: -# checkpoint_path = load("evo2/7b-1m:1.0") -# except ValueError as e: -# if e.args[0].endswith("does not have an NGC URL."): -# raise ValueError( -# "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " -# "one or more files are missing from ngc." -# ) -# else: -# raise e -# yield checkpoint_path - - -# # FIXME rewrite this test once we have megatron bridge running. We may not need callbacks but if we do rewrite that. -# # def test_predict_does_not_instantiate_optimizer(tmp_path: Path, checkpoint_1b_8k_bf16_path: Path): -# # output_dir = tmp_path / "test_output" -# # fasta_file_path = tmp_path / "test.fasta" -# # create_fasta_file( -# # fasta_file_path, -# # 1, -# # sequence_lengths=[512], -# # repeating_dna_pattern=ALU_SEQUENCE, -# # ) - -# # class AssertNoOptimizerCallback(Callback): -# # def on_predict_batch_start(self, trainer, pl_module, batch, batch_idx, dataloader_idx=0): -# # assert not trainer.optimizers, ( -# # f"Optimizer should not be instantiated for prediction, got {trainer.optimizers}" -# # ) -# # trainer_model_opt = getattr(trainer.model, "optim", None) -# # assert trainer_model_opt is None or not trainer_model_opt.state_dict(), ( -# # f"Model optimizer found, got {trainer_model_opt} with state {trainer_model_opt.state_dict()}" -# # ) - -# # with clean_parallel_state_context(): -# # predict( -# # fasta_path=fasta_file_path, -# # ckpt_dir=str(checkpoint_1b_8k_bf16_path), -# # output_dir=output_dir, -# # tensor_parallel_size=1, -# # pipeline_model_parallel_size=1, -# # context_parallel_size=1, -# # num_nodes=1, -# # devices=1, -# # model_size="1b", -# # ckpt_format="torch_dist", -# # fp8=False, -# # full_fp8=False, -# # work_dir=tmp_path, -# # micro_batch_size=1, -# # output_log_prob_seqs=True, -# # log_prob_collapse_option="mean", -# # write_interval="epoch", -# # prepend_bos=False, -# # no_sequence_parallel=False, -# # hybrid_override_pattern="SDH*", -# # num_layers=4, -# # seq_len_interpolation_factor=None, -# # files_per_subdir=None, -# # lora_checkpoint_path=None, -# # extra_callbacks=[ -# # AssertNoOptimizerCallback(), -# # ], # use this for making testing the loop easier. -# # ) - - -# @pytest.mark.parametrize( -# "ddp,pp,wi", -# [ -# pytest.param(1, 1, "epoch", id="ddp=1,pp=1,wi=epoch"), -# pytest.param(2, 1, "epoch", id="ddp=2,pp=1,wi=epoch"), -# pytest.param(2, 1, "batch", id="ddp=2,pp=1,wi=batch"), -# pytest.param( -# 1, -# 2, -# "epoch", -# id="ddp=1,pp=2,wi=epoch", -# marks=pytest.mark.skip("Pipeline parallelism test currently hangs."), -# ), -# ], -# ) -# def test_predict_evo2_runs( -# tmp_path, -# ddp: int, -# pp: int, -# wi: str, -# checkpoint_1b_8k_bf16_path: Path, -# num_sequences: int = 5, -# target_sequence_lengths: list[int] = [3149, 3140, 1024, 3148, 3147], -# ): -# """ -# This test runs the `predict_evo2` command with mock data in a temporary directory. -# It uses the temporary directory provided by pytest as the working directory. -# The command is run in a subshell, and we assert that it returns an exit code of 0. - -# Since it's the full output this does not support CP, so we only test with TP=1. We also want coverage of the -# case where the sequence lengths are different and not necessarily divisible by CP. -# """ -# world_size = ddp * pp -# if world_size > torch.cuda.device_count(): -# pytest.skip(f"World size {world_size} is less than the number of GPUs {torch.cuda.device_count()}") -# fasta_file_path = tmp_path / "test.fasta" -# create_fasta_file( -# fasta_file_path, num_sequences, sequence_lengths=target_sequence_lengths, repeating_dna_pattern=ALU_SEQUENCE -# ) -# # Create a mock data directory. -# # a local copy of the environment -# env = dict(**os.environ) -# if is_a6000_gpu(): -# # Fix hanging issue on A6000 GPUs with multi-gpu tests -# env["NCCL_P2P_DISABLE"] = "1" - -# # Build the command string. -# # Note: The command assumes that `train_evo2` is in your PATH. -# output_dir = tmp_path / "test_output" -# command = ( -# f"torchrun --nproc_per_node {world_size} --nnodes 1 --no-python " -# f"predict_evo2 --fasta {fasta_file_path} --ckpt-dir {checkpoint_1b_8k_bf16_path} " -# f"--output-dir {output_dir} --model-size 1b " -# f"--micro-batch-size 3 --write-interval {wi} " -# f"--pipeline-model-parallel-size {pp} --num-nodes 1 --devices {world_size}" -# ) - -# # Run the command in a subshell, using the temporary directory as the current working directory. -# open_port = find_free_network_port() -# env["MASTER_PORT"] = str(open_port) -# result = subprocess.run( -# command, -# check=False, -# shell=True, # Use the shell to interpret wildcards (e.g. SDH*) -# cwd=tmp_path, # Run in the temporary directory -# capture_output=True, # Capture stdout and stderr for debugging -# env=env, # Pass in the env where we override the master port. -# text=True, # Decode output as text -# ) - -# # For debugging purposes, print the output if the test fails. -# if result.returncode != 0: -# sys.stderr.write("STDOUT:\n" + result.stdout + "\n") -# sys.stderr.write("STDERR:\n" + result.stderr + "\n") - -# # Assert that the command completed successfully. -# assert result.returncode == 0, "train_evo2 command failed." - -# # Assert that the output directory was created. -# pred_files = glob.glob(os.path.join(output_dir, "predictions__rank_*.pt")) -# if wi == "batch": -# assert len(pred_files) == 2, f"Expected 2 prediction file (for this test), got {len(pred_files)}" -# else: -# assert len(pred_files) == ddp, f"Expected {ddp} prediction file (for this test), got {len(pred_files)}" -# with open(output_dir / "seq_idx_map.json", "r") as f: -# seq_idx_map = json.load( -# f -# ) # This gives us the mapping from the sequence names to the indices in the predictions. -# preds = [torch.load(pf) for pf in pred_files] -# preds = batch_collator( -# [p for p in preds if p is not None], -# batch_dim_key_defaults={"token_logits": 0}, -# seq_dim_key_defaults={"token_logits": 1}, -# ) -# assert isinstance(preds, dict) -# assert "token_logits" in preds -# assert "pad_mask" in preds -# assert "seq_idx" in preds - -# assert len(preds["token_logits"]) == len(preds["pad_mask"]) == len(preds["seq_idx"]) == num_sequences -# assert len(seq_idx_map) == num_sequences -# for original_idx, pad_mask, token_logits in zip(preds["seq_idx"], preds["pad_mask"], preds["token_logits"]): -# # seq_idx is not sorted necessarily, so use the saved "seq_idx" to determine the original order. -# expected_len = target_sequence_lengths[original_idx] -# assert pad_mask.sum() == expected_len -# assert token_logits.shape == (max(target_sequence_lengths), 512) - - -# @pytest.fixture(scope="module") -# def baseline_predictions_7b_1m_results( -# checkpoint_7b_1m_path: Path, -# num_sequences: int = 5, -# target_sequence_lengths: list[int] = [2048, 2048, 2048, 2048, 2048], -# ) -> dict[int, float]: -# with tempfile.TemporaryDirectory() as tmp_dir: -# tmp_path = Path(tmp_dir) -# fasta_file_path = tmp_path / "test.fasta" -# create_fasta_file( -# fasta_file_path, -# num_sequences, -# sequence_lengths=target_sequence_lengths, -# repeating_dna_pattern=ALU_SEQUENCE, -# ) -# output_dir = tmp_path / "test_output" -# command = ( -# f"torchrun --nproc_per_node 1 --nnodes 1 --no-python " -# f"predict_evo2 --fasta {fasta_file_path} --ckpt-dir {checkpoint_7b_1m_path} " -# f"--num-layers 4 --hybrid-override-pattern SDH* " # subset of layers for testing -# # FIXME changing batch size from 3 to 1 required dropping rel=1e-6 to rel=1e-3 -# # even when model parallelism is not used. This should be investigated. -# f"--micro-batch-size 3 " -# f"--output-dir {output_dir} --model-size 7b_arc_longcontext " -# f"--num-nodes 1 --write-interval epoch " -# "--output-log-prob-seqs --log-prob-collapse-option sum" -# ) -# # Create a mock data directory. -# # a local copy of the environment -# env = dict(**os.environ) -# open_port = find_free_network_port() -# env["MASTER_PORT"] = str(open_port) -# result = subprocess.run( -# command, -# check=False, -# shell=True, # Use the shell to interpret wildcards (e.g. SDH*) -# cwd=tmp_path, # Run in the temporary directory -# capture_output=True, # Capture stdout and stderr for debugging -# env=env, # Pass in the env where we override the master port. -# text=True, # Decode output as text -# ) -# assert result.returncode == 0, "predict_evo2 command failed." -# # Assert that the output directory was created. -# pred_files = glob.glob(os.path.join(output_dir, "predictions__rank_*.pt")) -# preds = [torch.load(pf) for pf in pred_files] -# preds = batch_collator( -# [p for p in preds if p is not None], -# ) -# yield dict(zip([i.item() for i in preds["seq_idx"]], [p.item() for p in preds["log_probs_seqs"]])) - - -# @pytest.mark.parametrize( -# "ddp,cp,pp,tp,fp8,wi", -# [ -# pytest.param(1, 1, 1, 1, False, "epoch", id="ddp=1,cp=1,pp=1,tp=1,fp8=False,wi=epoch"), -# pytest.param(2, 1, 1, 1, False, "epoch", id="ddp=2,cp=1,pp=1,tp=1,fp8=False,wi=epoch"), -# pytest.param( -# 2, 1, 1, 1, False, "batch", id="ddp=2,cp=1,pp=1,tp=1,fp8=False,wi=batch" -# ), # simulate a large prediction run with dp parallelism -# pytest.param(1, 2, 1, 1, False, "epoch", id="ddp=1,cp=2,pp=1,tp=1,fp8=False,wi=epoch"), -# pytest.param(1, 2, 1, 1, False, "batch", id="ddp=1,cp=2,pp=1,tp=1,fp8=False,wi=batch"), -# pytest.param( -# 1, -# 1, -# 2, -# 1, -# False, -# "epoch", -# id="ddp=1,cp=1,pp=2,tp=1,fp8=False,wi=epoch", -# marks=pytest.mark.skip("Pipeline parallelism test currently hangs."), -# ), -# pytest.param( -# 1, 1, 1, 2, True, "epoch", id="ddp=1,cp=1,pp=1,tp=2,fp8=True,wi=epoch" -# ), # Cover case where FP8 was not supported with TP=2 -# pytest.param(1, 1, 1, 2, False, "epoch", id="ddp=1,cp=1,pp=1,tp=2,fp8=False,wi=epoch"), -# ], -# ids=lambda x: f"ddp={x[0]},cp={x[1]},pp={x[2]},tp={x[3]},fp8={x[4]},wi={x[5]}", -# ) -# def test_predict_evo2_equivalent_with_log_probs( -# tmp_path, -# ddp: int, -# cp: int, -# pp: int, -# tp: int, -# fp8: bool, -# wi: str, -# checkpoint_7b_1m_path: Path, -# baseline_predictions_7b_1m_results: dict[int, float], -# num_sequences: int = 5, -# target_sequence_lengths: list[int] = [2048, 2048, 2048, 2048, 2048], -# ): -# """ -# This test runs the `predict_evo2` command with mock data in a temporary directory. -# It uses the temporary directory provided by pytest as the working directory. -# The command is run in a subshell, and we assert that it returns an exit code of 0. - -# For this test, we want coverage of CP, so we make sure sequence lengths are all the same and divisible by CP. - -# The other thing this test does is check that the log probabilities are equivalent to the baseline predictions -# without model parallelism. -# """ - -# world_size = ddp * cp * pp * tp -# mp_size = cp * pp * tp -# if world_size > torch.cuda.device_count(): -# pytest.skip(f"World size {world_size} is less than the number of GPUs {torch.cuda.device_count()}") -# is_fp8_supported, _, _ = check_fp8_support(torch.cuda.current_device()) -# if not is_fp8_supported and fp8: -# pytest.skip("FP8 is not supported on this GPU.") - -# fasta_file_path = tmp_path / "test.fasta" -# create_fasta_file( -# fasta_file_path, num_sequences, sequence_lengths=target_sequence_lengths, repeating_dna_pattern=ALU_SEQUENCE -# ) -# # Create a mock data directory. -# # a local copy of the environment -# env = dict(**os.environ) -# if is_a6000_gpu(): -# # Fix hanging issue on A6000 GPUs with multi-gpu tests -# env["NCCL_P2P_DISABLE"] = "1" - -# fp8_option = "--fp8" if fp8 else "" -# # Build the command string. -# # Note: The command assumes that `train_evo2` is in your PATH. -# output_dir = tmp_path / "test_output" -# command = ( -# f"torchrun --nproc_per_node {world_size} --nnodes 1 --no-python " -# f"predict_evo2 --fasta {fasta_file_path} --ckpt-dir {checkpoint_7b_1m_path} " -# f"--micro-batch-size 3 --write-interval {wi} " -# f"--num-layers 4 --hybrid-override-pattern SDH* " # subset of layers for testing -# f"--output-dir {output_dir} --model-size 7b_arc_longcontext --tensor-parallel-size {tp} {fp8_option} " -# f"--pipeline-model-parallel-size {pp} --context-parallel-size {cp} --num-nodes 1 --devices {world_size} " -# "--output-log-prob-seqs --log-prob-collapse-option sum" -# ) - -# # Run the command in a subshell, using the temporary directory as the current working directory. -# open_port = find_free_network_port() -# env["MASTER_PORT"] = str(open_port) -# result = subprocess.run( -# command, -# check=False, -# shell=True, # Use the shell to interpret wildcards (e.g. SDH*) -# cwd=tmp_path, # Run in the temporary directory -# capture_output=True, # Capture stdout and stderr for debugging -# env=env, # Pass in the env where we override the master port. -# text=True, # Decode output as text -# ) - -# # For debugging purposes, print the output if the test fails. -# if result.returncode != 0: -# sys.stderr.write("STDOUT:\n" + result.stdout + "\n") -# sys.stderr.write("STDERR:\n" + result.stderr + "\n") - -# # Assert that the command completed successfully. -# assert result.returncode == 0, "train_evo2 command failed." - -# # Assert that the output directory was created. -# pred_files = glob.glob(os.path.join(output_dir, "predictions__rank_*.pt")) -# if wi == "batch": -# assert len(pred_files) == 2, f"Expected 2 prediction file (for this test), got {len(pred_files)}" -# else: -# assert len(pred_files) == ddp, f"Expected {ddp} prediction file (for this test), got {len(pred_files)}" -# with open(output_dir / "seq_idx_map.json", "r") as f: -# seq_idx_map = json.load( -# f -# ) # This gives us the mapping from the sequence names to the indices in the predictions. -# preds = [torch.load(pf) for pf in pred_files] -# preds = batch_collator( -# [p for p in preds if p is not None], -# ) -# assert isinstance(preds, dict) -# assert "log_probs_seqs" in preds -# assert "seq_idx" in preds -# assert len(preds["log_probs_seqs"]) == len(preds["seq_idx"]) == num_sequences -# assert len(seq_idx_map) == num_sequences -# for original_idx, log_probs in zip(preds["seq_idx"], preds["log_probs_seqs"]): -# if mp_size > 1 and not fp8: -# # FIXME changing batch size so it doesn't match also required dropping rel=1e-6 to rel=1e-3. -# # This should be investigated. -# rel = 1e-3 -# elif fp8: -# # NOTE: This is hand-tuned on a b300 to pass for now as of 9/10/2025. -# rel = 1e-2 -# else: -# rel = 1e-6 -# assert log_probs.item() == pytest.approx(baseline_predictions_7b_1m_results[original_idx.item()], rel=rel) - - +"""Tests for Evo2 prediction (inference) workflow using Megatron Bridge.""" + +import copy +import glob +import json +import os +import re +import shlex +import subprocess +from pathlib import Path + +import pytest +import torch + +from bionemo.evo2.data.test_utils.create_fasta_file import ALU_SEQUENCE, create_fasta_file +from bionemo.evo2.run.predict import batch_collator + +from ..utils import check_fp8_support, find_free_network_port, is_a6000_gpu + + +# Do this at collection time before we run any tests. +PRETEST_ENV = copy.deepcopy(os.environ) + + +@pytest.fixture(scope="module") +def mbridge_checkpoint_1b_8k_bf16_path(mbridge_checkpoint_1b_8k_bf16) -> Path: + """Module-scoped alias for the session-scoped 1b-8k-bf16 checkpoint. + + The actual checkpoint conversion is done once per session in conftest.py via + the mbridge_checkpoint_1b_8k_bf16 fixture, and shared across all test files. + + Returns: + Path to the MBridge checkpoint iteration directory (e.g., .../iter_0000001) + """ + return mbridge_checkpoint_1b_8k_bf16 + + +@pytest.mark.parametrize( + "ddp,pp,wi", + [ + pytest.param(1, 1, "epoch", id="ddp=1,pp=1,wi=epoch"), + pytest.param(2, 1, "epoch", id="ddp=2,pp=1,wi=epoch"), + pytest.param(2, 1, "batch", id="ddp=2,pp=1,wi=batch"), + pytest.param( + 1, + 2, + "epoch", + id="ddp=1,pp=2,wi=epoch", + marks=pytest.mark.skip("Pipeline parallelism test currently hangs."), + ), + ], +) +@pytest.mark.slow +def test_predict_evo2_runs( + tmp_path, + ddp: int, + pp: int, + wi: str, + mbridge_checkpoint_1b_8k_bf16_path: Path, + num_sequences: int = 5, + target_sequence_lengths: list[int] | None = None, +): + """Test that the predict_evo2 command runs successfully with MBridge checkpoints. + + This test runs the `predict_evo2` command with mock data in a temporary directory. + It uses the temporary directory provided by pytest as the working directory. + The command is run in a subshell, and we assert that it returns an exit code of 0. + + Since it's the full output this does not support CP, so we only test with TP=1. We also want coverage of the + case where the sequence lengths are different and not necessarily divisible by CP. + """ + if target_sequence_lengths is None: + target_sequence_lengths = [3149, 3140, 1024, 3148, 3147] + + world_size = ddp * pp + if world_size > torch.cuda.device_count(): + pytest.skip(f"World size {world_size} is greater than the number of GPUs {torch.cuda.device_count()}") + + fasta_file_path = tmp_path / "test.fasta" + create_fasta_file( + fasta_file_path, num_sequences, sequence_lengths=target_sequence_lengths, repeating_dna_pattern=ALU_SEQUENCE + ) + + # Create a local copy of the environment + env = copy.deepcopy(PRETEST_ENV) + if is_a6000_gpu(): + # Fix hanging issue on A6000 GPUs with multi-gpu tests + env["NCCL_P2P_DISABLE"] = "1" + + # Build the command string + output_dir = tmp_path / "test_output" + open_port = find_free_network_port() + command = ( + f"torchrun --nproc_per_node {world_size} --nnodes 1 --master_port {open_port} " + f"-m bionemo.evo2.run.predict --fasta {fasta_file_path} --ckpt-dir {mbridge_checkpoint_1b_8k_bf16_path} " + f"--output-dir {output_dir} " + f"--micro-batch-size 3 --write-interval {wi} " + f"--pipeline-model-parallel-size {pp} --num-nodes 1 --devices {world_size}" + ) + + # Run the command in a subshell + cmd_parts = shlex.split(command) + result = subprocess.run( + cmd_parts, + check=False, + cwd=tmp_path, + capture_output=True, + env=env, + text=True, + ) + + # For debugging purposes, print the output if the test fails + if result.returncode != 0: + print("STDOUT:\n" + result.stdout) + print("STDERR:\n" + result.stderr) + + # Assert that the command completed successfully + assert result.returncode == 0, f"predict_evo2 command failed with code {result.returncode}" + + # Assert that the output directory was created and contains predictions + # With DDP, each DP rank produces its own file with dp_rank in the filename + # File naming convention: + # Batch mode: predictions__rank_{global_rank}__dp_rank_{dp_rank}__batch_{batch_idx}.pt + # Epoch mode: predictions__rank_{global_rank}__dp_rank_{dp_rank}.pt + if wi == "batch": + pred_files = sorted(glob.glob(str(output_dir / "predictions__rank_*__dp_rank_*__batch_*.pt"))) + # With batch write interval, we expect multiple files (batches * dp_ranks) + assert len(pred_files) >= ddp, f"Expected at least {ddp} prediction files, got {len(pred_files)}" + else: + pred_files = sorted(glob.glob(str(output_dir / "predictions__rank_*__dp_rank_*.pt"))) + # With epoch write interval, we expect one file per DP rank + assert len(pred_files) == ddp, f"Expected {ddp} prediction files (one per DP rank), got {len(pred_files)}" + + # Check sequence index map exists + seq_idx_map_path = output_dir / "seq_idx_map.json" + assert seq_idx_map_path.exists(), f"seq_idx_map.json not found at {seq_idx_map_path}" + + with open(seq_idx_map_path) as f: + seq_idx_map = json.load(f) + + # Load and collate predictions + # Note: predict.py outputs are all batch-first (batch_dim=0), seq-second (seq_dim=1) + preds = [torch.load(pf) for pf in pred_files] + preds = batch_collator( + [p for p in preds if p is not None], + batch_dim=0, + seq_dim=1, + batch_dim_key_defaults={}, + seq_dim_key_defaults={}, + ) + assert isinstance(preds, dict) + assert "token_logits" in preds + assert "pad_mask" in preds + assert "seq_idx" in preds + + assert len(preds["token_logits"]) == len(preds["pad_mask"]) == len(preds["seq_idx"]) == num_sequences + assert len(seq_idx_map) == num_sequences + + for original_idx, pad_mask, token_logits in zip(preds["seq_idx"], preds["pad_mask"], preds["token_logits"]): + # seq_idx is not sorted necessarily, so use the saved "seq_idx" to determine the original order + expected_len = target_sequence_lengths[original_idx] + assert pad_mask.sum() == expected_len + # Vocab size should be 512 for the nucleotide tokenizer + assert token_logits.shape[-1] == 512 + + +@pytest.fixture(scope="module") +def mbridge_checkpoint_7b_1m_path(tmp_path_factory) -> Path: + """Create or load a MBridge checkpoint for 7b-1m model testing.""" + from bionemo.core.data.load import load + from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH_512 + from bionemo.evo2.utils.checkpoint.nemo2_to_mbridge import run_nemo2_to_mbridge + + try: + nemo2_checkpoint_path = load("evo2/7b-1m:1.0") + except ValueError as e: + if e.args[0].endswith("does not have an NGC URL."): + pytest.skip( + "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " + "one or more files are missing from ngc." + ) + else: + raise e + + # Create a temporary directory for the MBridge checkpoint + tmp_dir = tmp_path_factory.mktemp("mbridge_ckpt_7b") + # Note: run_nemo2_to_mbridge uses full model config from model_size + # For testing we use the full 7b model but with shorter sequences + mbridge_ckpt_dir = run_nemo2_to_mbridge( + nemo2_ckpt_dir=nemo2_checkpoint_path, + tokenizer_path=DEFAULT_HF_TOKENIZER_MODEL_PATH_512, + mbridge_ckpt_dir=tmp_dir / "mbridge_checkpoint", + model_size="7b_arc_longcontext", + seq_length=8192, # Use shorter seq length for tests + mixed_precision_recipe="bf16_mixed", + vortex_style_fp8=False, + ) + return mbridge_ckpt_dir / "iter_0000001" + + +@pytest.fixture(scope="module") +def baseline_predictions_7b_1m_results( + mbridge_checkpoint_7b_1m_path: Path, + tmp_path_factory, + num_sequences: int = 5, + target_sequence_lengths: list[int] | None = None, +) -> dict[int, float]: + """Generate baseline predictions for 7b-1m model comparison.""" + if target_sequence_lengths is None: + target_sequence_lengths = [2048, 2048, 2048, 2048, 2048] + + tmp_path = tmp_path_factory.mktemp("baseline_preds") + fasta_file_path = tmp_path / "test.fasta" + create_fasta_file( + fasta_file_path, + num_sequences, + sequence_lengths=target_sequence_lengths, + repeating_dna_pattern=ALU_SEQUENCE, + ) + output_dir = tmp_path / "test_output" + open_port = find_free_network_port() + command = ( + f"torchrun --nproc_per_node 1 --nnodes 1 --master_port {open_port} " + f"-m bionemo.evo2.run.predict --fasta {fasta_file_path} --ckpt-dir {mbridge_checkpoint_7b_1m_path} " + f"--micro-batch-size 3 " + f"--output-dir {output_dir} " + f"--num-nodes 1 --write-interval epoch " + "--output-log-prob-seqs --log-prob-collapse-option sum" + ) + + env = copy.deepcopy(PRETEST_ENV) + cmd_parts = shlex.split(command) + result = subprocess.run( + cmd_parts, + check=False, + cwd=tmp_path, + capture_output=True, + env=env, + text=True, + ) + assert result.returncode == 0, f"predict_evo2 command failed: {result.stderr}" + + # Use the updated glob pattern matching the new naming convention + # Epoch mode: predictions__rank_{global_rank}__dp_rank_{dp_rank}.pt + pred_files = glob.glob(str(output_dir / "predictions__rank_*__dp_rank_*.pt")) + preds = [torch.load(pf) for pf in pred_files] + preds = batch_collator( + [p for p in preds if p is not None], + batch_dim=0, + seq_dim=1, + batch_dim_key_defaults={}, + seq_dim_key_defaults={}, + ) + return dict(zip([i.item() for i in preds["seq_idx"]], [p.item() for p in preds["log_probs_seqs"]])) + + +@pytest.mark.parametrize( + "ddp,cp,pp,tp,fp8,wi", + [ + pytest.param(1, 1, 1, 1, False, "epoch", id="ddp=1,cp=1,pp=1,tp=1,fp8=False,wi=epoch"), + pytest.param(2, 1, 1, 1, False, "epoch", id="ddp=2,cp=1,pp=1,tp=1,fp8=False,wi=epoch"), + pytest.param( + 2, 1, 1, 1, False, "batch", id="ddp=2,cp=1,pp=1,tp=1,fp8=False,wi=batch" + ), # simulate a large prediction run with dp parallelism + pytest.param(1, 2, 1, 1, False, "epoch", id="ddp=1,cp=2,pp=1,tp=1,fp8=False,wi=epoch"), + pytest.param(1, 2, 1, 1, False, "batch", id="ddp=1,cp=2,pp=1,tp=1,fp8=False,wi=batch"), + pytest.param( + 1, + 1, + 2, + 1, + False, + "epoch", + id="ddp=1,cp=1,pp=2,tp=1,fp8=False,wi=epoch", + marks=pytest.mark.skip("Pipeline parallelism test currently hangs."), + ), + pytest.param( + 1, 1, 1, 2, True, "epoch", id="ddp=1,cp=1,pp=1,tp=2,fp8=True,wi=epoch" + ), # Cover case where FP8 was not supported with TP=2 + pytest.param(1, 1, 1, 2, False, "epoch", id="ddp=1,cp=1,pp=1,tp=2,fp8=False,wi=epoch"), + ], +) +@pytest.mark.slow +@pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip 7b-1m checkpoint tests in CI due to disk space") +def test_predict_evo2_equivalent_with_log_probs( + tmp_path, + ddp: int, + cp: int, + pp: int, + tp: int, + fp8: bool, + wi: str, + mbridge_checkpoint_7b_1m_path: Path, + baseline_predictions_7b_1m_results: dict[int, float], + num_sequences: int = 5, + target_sequence_lengths: list[int] | None = None, +): + """Test that predict_evo2 produces equivalent log probabilities with different parallelism settings. + + This test runs the `predict_evo2` command with mock data in a temporary directory. + It uses the temporary directory provided by pytest as the working directory. + The command is run in a subshell, and we assert that it returns an exit code of 0. + + For this test, we want coverage of CP, so we make sure sequence lengths are all the same and divisible by CP. + + The other thing this test does is check that the log probabilities are equivalent to the baseline predictions + without model parallelism. + """ + if target_sequence_lengths is None: + target_sequence_lengths = [2048, 2048, 2048, 2048, 2048] + + world_size = ddp * cp * pp * tp + mp_size = cp * pp * tp + if world_size > torch.cuda.device_count(): + pytest.skip(f"World size {world_size} is greater than the number of GPUs {torch.cuda.device_count()}") + is_fp8_supported, _, _ = check_fp8_support(torch.cuda.current_device()) + if not is_fp8_supported and fp8: + pytest.skip("FP8 is not supported on this GPU.") + + fasta_file_path = tmp_path / "test.fasta" + create_fasta_file( + fasta_file_path, num_sequences, sequence_lengths=target_sequence_lengths, repeating_dna_pattern=ALU_SEQUENCE + ) + + # Create a local copy of the environment + env = copy.deepcopy(PRETEST_ENV) + if is_a6000_gpu(): + # Fix hanging issue on A6000 GPUs with multi-gpu tests + env["NCCL_P2P_DISABLE"] = "1" + + fp8_option = "--mixed-precision-recipe bf16_with_fp8_current_scaling_mixed" if fp8 else "" + output_dir = tmp_path / "test_output" + open_port = find_free_network_port() + command = ( + f"torchrun --nproc_per_node {world_size} --nnodes 1 --master_port {open_port} " + f"-m bionemo.evo2.run.predict --fasta {fasta_file_path} --ckpt-dir {mbridge_checkpoint_7b_1m_path} " + f"--micro-batch-size 3 --write-interval {wi} " + f"--output-dir {output_dir} --tensor-parallel-size {tp} {fp8_option} " + f"--pipeline-model-parallel-size {pp} --context-parallel-size {cp} --num-nodes 1 --devices {world_size} " + "--output-log-prob-seqs --log-prob-collapse-option sum" + ) + + cmd_parts = shlex.split(command) + result = subprocess.run( + cmd_parts, + check=False, + cwd=tmp_path, + capture_output=True, + env=env, + text=True, + ) + + # For debugging purposes, print the output if the test fails + if result.returncode != 0: + print("STDOUT:\n" + result.stdout) + print("STDERR:\n" + result.stderr) + + # Assert that the command completed successfully + assert result.returncode == 0, f"predict_evo2 command failed with code {result.returncode}" + + # Assert that the output directory was created + # With DDP, each DP rank produces its own file with dp_rank in the filename + # File naming convention: + # Batch mode: predictions__rank_{global_rank}__dp_rank_{dp_rank}__batch_{batch_idx}.pt + # Epoch mode: predictions__rank_{global_rank}__dp_rank_{dp_rank}.pt + if wi == "batch": + pred_files = sorted(glob.glob(str(output_dir / "predictions__rank_*__dp_rank_*__batch_*.pt"))) + # With batch write interval, we expect multiple files (batches * dp_ranks) + assert len(pred_files) >= ddp, f"Expected at least {ddp} prediction files, got {len(pred_files)}" + else: + pred_files = sorted(glob.glob(str(output_dir / "predictions__rank_*__dp_rank_*.pt"))) + # With epoch write interval, we expect one file per DP rank + assert len(pred_files) == ddp, f"Expected {ddp} prediction files (one per DP rank), got {len(pred_files)}" + + with open(output_dir / "seq_idx_map.json") as f: + seq_idx_map = json.load(f) + + # Load and collate predictions from all DP ranks + preds = [torch.load(pf) for pf in pred_files] + preds = batch_collator( + [p for p in preds if p is not None], + batch_dim=0, + seq_dim=1, + batch_dim_key_defaults={}, + seq_dim_key_defaults={}, + ) + assert isinstance(preds, dict) + assert "log_probs_seqs" in preds + assert "seq_idx" in preds + assert len(preds["log_probs_seqs"]) == len(preds["seq_idx"]) == num_sequences + assert len(seq_idx_map) == num_sequences + + for original_idx, log_probs in zip(preds["seq_idx"], preds["log_probs_seqs"]): + if mp_size > 1 and not fp8: + # FIXME changing batch size so it doesn't match also required dropping rel=1e-6 to rel=1e-3. + # This should be investigated. TP=2 on some GPUs needs even more tolerance. + rel = 2e-3 + elif fp8: + # NOTE: This is hand-tuned on a b300 to pass for now as of 9/10/2025. + rel = 1e-2 + else: + rel = 1e-6 + assert log_probs.item() == pytest.approx(baseline_predictions_7b_1m_results[original_idx.item()], rel=rel) + + +# Note: The PEFT/LoRA test is commented out as it requires training infrastructure and LoRA support +# which may need additional updates for the Megatron Bridge API # @pytest.mark.timeout(512) # @pytest.mark.slow # def test_different_results_with_without_peft(tmp_path): -# try: -# base_model_checkpoint_path = load("evo2/1b-8k:1.0") -# except ValueError as e: -# if e.args[0].endswith("does not have an NGC URL."): -# raise ValueError( -# "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " -# "one or more files are missing from ngc." -# ) -# else: -# raise e - -# num_steps = 2 - -# result_dir = tmp_path / "lora_finetune" - -# # Note: The command assumes that `train_evo2` is in your PATH. -# command_finetune = small_training_finetune_cmd( -# result_dir, -# max_steps=num_steps, -# val_check=num_steps, -# prev_ckpt=base_model_checkpoint_path, -# create_tflops_callback=False, -# additional_args="--lora-finetune", -# ) -# stdout_finetune: str = run_command_in_subprocess(command=command_finetune, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" in stdout_finetune -# assert "Loading adapters from" not in stdout_finetune - -# # Check if checkpoints dir exists -# checkpoints_dir = result_dir / "evo2" / "checkpoints" -# assert checkpoints_dir.exists(), "Checkpoints folder does not exist." - -# # Create a sample FASTA file to run predictions -# fasta_file_path = tmp_path / "test.fasta" -# create_fasta_file(fasta_file_path, 3, sequence_lengths=[32, 65, 129], repeating_dna_pattern=ALU_SEQUENCE) - -# result_dir_original = tmp_path / "results_original" -# cmd_predict = predict_cmd(base_model_checkpoint_path, result_dir_original, fasta_file_path) -# stdout_predict: str = run_command_in_subprocess(command=cmd_predict, path=str(tmp_path)) - -# # Assert that the output directory was created. -# pred_files_original = glob.glob(str(result_dir_original / "predictions__rank_*.pt")) -# assert len(pred_files_original) == 1, f"Expected 1 prediction file (for this test), got {len(pred_files_original)}" - -# # Find the checkpoint dir generated by finetuning -# expected_checkpoint_suffix = f"{num_steps}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir}." -# ) - -# result_dir_peft = tmp_path / "results_peft" -# additional_args = f"--lora-checkpoint-path {matching_subfolders[0]}" -# cmd_predict = predict_cmd(base_model_checkpoint_path, result_dir_peft, fasta_file_path, additional_args) -# stdout_predict: str = run_command_in_subprocess(command=cmd_predict, path=str(tmp_path)) -# assert "Loading adapters from" in stdout_predict - -# pred_files_peft = glob.glob(str(result_dir_peft / "predictions__rank_*.pt")) -# assert len(pred_files_peft) == 1, f"Expected 1 prediction file (for this test), got {len(pred_files_peft)}" - -# results_original = torch.load(f"{result_dir_original}/predictions__rank_0__dp_rank_0.pt") -# results_peft = torch.load(f"{result_dir_peft}/predictions__rank_0__dp_rank_0.pt") - -# seq_idx_original = results_original["seq_idx"] -# seq_idx_peft = results_peft["seq_idx"] -# assert torch.equal(seq_idx_original, seq_idx_peft), f"Tensors differ: {seq_idx_original} vs {seq_idx_peft}" - -# logits_original = results_original["token_logits"] -# logits_peft = results_peft["token_logits"] -# assert (logits_original != logits_peft).any() -# assert logits_original.shape == logits_peft.shape, ( -# f"Shapes don't match: {logits_original.shape} vs {logits_peft.shape}" -# ) +# """Test that predictions differ when using PEFT/LoRA adapters.""" +# pass + + +@pytest.mark.parametrize( + "embedding_layer,expected_num_layers", + [ + pytest.param(-1, 25, id="embedding_layer=-1_expects_25_layers"), + pytest.param(-2, 24, id="embedding_layer=-2_expects_24_layers"), + pytest.param(0, 1, id="embedding_layer=0_expects_1_layer"), + pytest.param(5, 6, id="embedding_layer=5_expects_6_layers"), + ], +) +@pytest.mark.slow +def test_predict_evo2_embedding_extraction( + tmp_path, + embedding_layer: int, + expected_num_layers: int, + mbridge_checkpoint_1b_8k_bf16_path: Path, + num_sequences: int = 3, + target_sequence_lengths: list[int] | None = None, +): + """Test that embedding extraction produces outputs with expected shapes and keys. + + This test verifies: + 1. The model is initialized with the correct number of layers (logged and verified) + 2. Output contains 'hidden_embeddings' key instead of 'token_logits' + 3. Embeddings have expected shape [B, S, H] where H is hidden dimension + 4. Other expected keys (pad_mask, seq_idx, tokens) are present + + The 1b model has 25 layers, so: + - embedding_layer=-1 -> 25 layers (last layer) + - embedding_layer=-2 -> 24 layers (second-to-last) + - embedding_layer=0 -> 1 layer (first layer only) + - embedding_layer=5 -> 6 layers (layers 0-5) + """ + original_num_layers = 25 # 1b model has 25 layers + + if target_sequence_lengths is None: + target_sequence_lengths = [1024, 1024, 1024] + + world_size = 1 + if world_size > torch.cuda.device_count(): + pytest.skip(f"World size {world_size} is greater than the number of GPUs {torch.cuda.device_count()}") + + fasta_file_path = tmp_path / "test.fasta" + create_fasta_file( + fasta_file_path, num_sequences, sequence_lengths=target_sequence_lengths, repeating_dna_pattern=ALU_SEQUENCE + ) + + # Create a local copy of the environment + env = copy.deepcopy(PRETEST_ENV) + if is_a6000_gpu(): + env["NCCL_P2P_DISABLE"] = "1" + + output_dir = tmp_path / "test_output" + open_port = find_free_network_port() + command = ( + f"torchrun --nproc_per_node {world_size} --nnodes 1 --master_port {open_port} " + f"-m bionemo.evo2.run.predict --fasta {fasta_file_path} --ckpt-dir {mbridge_checkpoint_1b_8k_bf16_path} " + f"--output-dir {output_dir} " + f"--micro-batch-size 2 --write-interval epoch " + f"--embedding-layer {embedding_layer}" + ) + + cmd_parts = shlex.split(command) + result = subprocess.run( + cmd_parts, + check=False, + cwd=tmp_path, + capture_output=True, + env=env, + text=True, + ) + + # For debugging purposes, print the output if the test fails + if result.returncode != 0: + print("STDOUT:\n" + result.stdout) + print("STDERR:\n" + result.stderr) + + # Assert that the command completed successfully + assert result.returncode == 0, f"predict_evo2 command failed with code {result.returncode}" + + # Combine stdout and stderr for log checking + combined_output = result.stdout + result.stderr + + # Verify logging about model layers is present and extract the layer count + assert "Model initialized with" in combined_output, "Expected logging about model layer count" + assert "Embedding extraction" in combined_output, "Expected logging about embedding extraction mode" + + # Parse and verify the actual number of layers from the log + # Look for pattern: "Model initialized with N layers" + layer_match = re.search(r"Model initialized with (\d+) layers", combined_output) + assert layer_match is not None, "Could not parse 'Model initialized with N layers' from output" + actual_num_layers = int(layer_match.group(1)) + assert actual_num_layers == expected_num_layers, ( + f"Expected model to have {expected_num_layers} layers for embedding_layer={embedding_layer}, " + f"but got {actual_num_layers} layers" + ) + + # Verify the embedding extraction log shows correct layer info + # Look for pattern: "using N of M layers" + extraction_match = re.search(r"using (\d+) of (\d+) layers", combined_output) + assert extraction_match is not None, "Could not parse 'using N of M layers' from output" + layers_used = int(extraction_match.group(1)) + layers_original = int(extraction_match.group(2)) + assert layers_used == expected_num_layers, ( + f"Expected 'using {expected_num_layers}' layers, but log shows 'using {layers_used}'" + ) + assert layers_original == original_num_layers, ( + f"Expected original model to have {original_num_layers} layers, but log shows {layers_original}" + ) + + # Load predictions + pred_files = sorted(glob.glob(str(output_dir / "predictions__rank_*__dp_rank_*.pt"))) + assert len(pred_files) == 1, f"Expected 1 prediction file, got {len(pred_files)}" + + preds = torch.load(pred_files[0]) + assert isinstance(preds, dict) + + # Verify expected keys for embedding extraction + assert "hidden_embeddings" in preds, "Expected 'hidden_embeddings' key in embedding extraction mode" + assert "token_logits" not in preds, "Should not have 'token_logits' in embedding extraction mode" + assert "pad_mask" in preds, "Expected 'pad_mask' key" + assert "seq_idx" in preds, "Expected 'seq_idx' key" + assert "tokens" in preds, "Expected 'tokens' key" + + # Verify shapes + hidden_embeddings = preds["hidden_embeddings"] + pad_mask = preds["pad_mask"] + tokens = preds["tokens"] + + # hidden_embeddings should be [B, S, H] where H is hidden dimension (1920 for 1b model) + assert len(hidden_embeddings.shape) == 3, f"Expected 3D tensor, got shape {hidden_embeddings.shape}" + batch_size, seq_len, hidden_dim = hidden_embeddings.shape + + assert batch_size == num_sequences, f"Expected batch size {num_sequences}, got {batch_size}" + # Sequence length should match padded length + max_seq_len = max(target_sequence_lengths) + assert seq_len == max_seq_len, f"Expected seq_len {max_seq_len}, got {seq_len}" + # Hidden dim should be 1920 for 1b model + assert hidden_dim == 1920, f"Expected hidden_dim 1920 for 1b model, got {hidden_dim}" + + # Verify pad_mask and tokens have matching shapes + assert pad_mask.shape == (batch_size, seq_len), f"pad_mask shape mismatch: {pad_mask.shape}" + assert tokens.shape == (batch_size, seq_len), f"tokens shape mismatch: {tokens.shape}" + + # Verify seq_idx has correct count + assert len(preds["seq_idx"]) == num_sequences, f"Expected {num_sequences} seq_idx entries" + + # Check sequence index map exists + seq_idx_map_path = output_dir / "seq_idx_map.json" + assert seq_idx_map_path.exists(), f"seq_idx_map.json not found at {seq_idx_map_path}" + + with open(seq_idx_map_path) as f: + seq_idx_map = json.load(f) + assert len(seq_idx_map) == num_sequences + + +@pytest.mark.slow +def test_predict_evo2_embedding_layer_validation( + tmp_path, + mbridge_checkpoint_1b_8k_bf16_path: Path, +): + """Test that invalid embedding layer values are rejected with appropriate errors.""" + fasta_file_path = tmp_path / "test.fasta" + create_fasta_file(fasta_file_path, 1, sequence_lengths=[512], repeating_dna_pattern=ALU_SEQUENCE) + + env = copy.deepcopy(PRETEST_ENV) + if is_a6000_gpu(): + env["NCCL_P2P_DISABLE"] = "1" + + output_dir = tmp_path / "test_output" + open_port = find_free_network_port() + + # Test with an invalid embedding layer (too large positive index) + # The 1b model has 25 layers, so layer 100 should be invalid + command = ( + f"torchrun --nproc_per_node 1 --nnodes 1 --master_port {open_port} " + f"-m bionemo.evo2.run.predict --fasta {fasta_file_path} --ckpt-dir {mbridge_checkpoint_1b_8k_bf16_path} " + f"--output-dir {output_dir} --embedding-layer 100" + ) + + cmd_parts = shlex.split(command) + result = subprocess.run( + cmd_parts, + check=False, + cwd=tmp_path, + capture_output=True, + env=env, + text=True, + ) + + # Should fail with an error about invalid embedding layer + assert result.returncode != 0, "Expected command to fail with invalid embedding layer" + assert "Invalid embedding_layer" in result.stderr or "Invalid embedding_layer" in result.stdout, ( + "Expected error message about invalid embedding layer" + ) + + +@pytest.mark.slow +def test_predict_evo2_embedding_with_log_probs_rejected( + tmp_path, + mbridge_checkpoint_1b_8k_bf16_path: Path, +): + """Test that using both --embedding-layer and --output-log-prob-seqs is rejected.""" + fasta_file_path = tmp_path / "test.fasta" + create_fasta_file(fasta_file_path, 1, sequence_lengths=[512], repeating_dna_pattern=ALU_SEQUENCE) + + env = copy.deepcopy(PRETEST_ENV) + if is_a6000_gpu(): + env["NCCL_P2P_DISABLE"] = "1" + + output_dir = tmp_path / "test_output" + open_port = find_free_network_port() + + # Test combining embedding extraction with log prob output (should fail) + command = ( + f"torchrun --nproc_per_node 1 --nnodes 1 --master_port {open_port} " + f"-m bionemo.evo2.run.predict --fasta {fasta_file_path} --ckpt-dir {mbridge_checkpoint_1b_8k_bf16_path} " + f"--output-dir {output_dir} --embedding-layer -1 --output-log-prob-seqs" + ) + + cmd_parts = shlex.split(command) + result = subprocess.run( + cmd_parts, + check=False, + cwd=tmp_path, + capture_output=True, + env=env, + text=True, + ) + + # Should fail with an error about incompatible options + assert result.returncode != 0, "Expected command to fail with incompatible options" + assert "Cannot use --output-log-prob-seqs with --embedding-layer" in result.stderr or ( + "Cannot use --output-log-prob-seqs with --embedding-layer" in result.stdout + ), "Expected error message about incompatible options" diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_train.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_train.py index 126b11a2bd..65ae1d976f 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_train.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/run/test_train.py @@ -16,624 +16,633 @@ # See the License for the specific language governing permissions and # limitations under the License. -# FIXME bring back these tests once we get mbridge running. -# import argparse -# import io -# import os -# import shlex -# from contextlib import redirect_stderr, redirect_stdout -# from typing import Tuple - -# import pytest -# import torch -# from bionemo.evo2.run.train import parse_args, train -# from bionemo.testing.assert_optimizer_grads_match import assert_optimizer_states_match -# from bionemo.testing.lightning import extract_global_steps_from_log -# from bionemo.testing.megatron_parallel_state_utils import distributed_model_parallel_state -# from bionemo.testing.subprocess_utils import run_command_in_subprocess - -# # from nemo import lightning as nl -# from transformer_engine.pytorch.fp8 import check_fp8_support - -# from .common import small_training_cmd, small_training_finetune_cmd - - -# fp8_available, reason_for_no_fp8 = check_fp8_support() - - -# def run_train_with_std_redirect(args: argparse.Namespace) -> Tuple[str, nl.Trainer]: -# """Run a function with output capture.""" -# stdout_buf, stderr_buf = io.StringIO(), io.StringIO() -# with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf): -# with distributed_model_parallel_state(): -# trainer: nl.Trainer = train(args) - -# train_stdout = stdout_buf.getvalue() -# train_stderr = stderr_buf.getvalue() -# print("Captured STDOUT:\n", train_stdout) -# print("Captured STDERR:\n", train_stderr) -# return train_stdout, trainer - - -# def distributed_training_cmd( -# path, -# max_steps, -# val_check, -# num_devices, -# dp, -# tp, -# cp, -# pp, -# dataset_dir=None, -# training_config=None, -# additional_args: str = "", -# ): -# """Create distributed training command with specified parallelism settings. - -# Args: -# path: Result directory path -# max_steps: Maximum training steps -# val_check: Validation check interval -# num_devices: Total number of devices -# dp: Data parallel size -# tp: Tensor parallel size -# cp: Context parallel size -# pp: Pipeline parallel size -# dataset_dir: Path to preprocessed dataset directory (if None, uses --mock-data) -# training_config: Path to training data config YAML file (required if dataset_dir is provided) -# additional_args: Additional command line arguments -# """ -# micro_batch_size = 1 if dp == 2 else 2 - -# # Use real dataset if provided, otherwise fall back to mock data -# if dataset_dir and training_config: -# data_args = f"-d {training_config} --dataset-dir {dataset_dir}" -# else: -# data_args = "--mock-data" - -# cmd = ( -# f"train_evo2 {data_args} --result-dir {path} --devices {num_devices} " -# f"--tensor-parallel-size {tp} --pipeline-model-parallel-size {pp} --context-parallel-size {cp} " -# "--model-size 7b --num-layers 4 --hybrid-override-pattern SDH* --limit-val-batches 1 " -# "--no-activation-checkpointing --add-bias-output --create-tensorboard-logger --create-tflops-callback " -# f"--max-steps {max_steps} --warmup-steps 1 --val-check-interval {val_check} --limit-val-batches 1 " -# f"--seq-length 64 --hidden-dropout 0.0 --attention-dropout 0.0 --seed 42 --workers 0 " -# f"--micro-batch-size {micro_batch_size} --global-batch-size 2 " -# f"--adam-beta1 0 --adam-beta2 0 {additional_args}" -# ) -# return cmd - - -# def small_training_mamba_cmd(path, max_steps, val_check, devices: int = 1, additional_args: str = ""): -# cmd = ( -# f"train_evo2 --mock-data --result-dir {path} --devices {devices} " -# "--model-size hybrid_mamba_8b --num-layers 2 --hybrid-override-pattern M- --limit-val-batches 1 " -# "--no-activation-checkpointing --create-tensorboard-logger --create-tflops-callback " -# f"--max-steps {max_steps} --warmup-steps 1 --val-check-interval {val_check} --limit-val-batches 1 " -# f"--seq-length 8 --hidden-dropout 0.1 --attention-dropout 0.1 {additional_args}" -# ) -# return cmd - - -# def small_training_mamba_finetune_cmd( -# path, max_steps, val_check, prev_ckpt, devices: int = 1, additional_args: str = "" -# ): -# cmd = ( -# f"train_evo2 --mock-data --result-dir {path} --devices {devices} " -# "--model-size hybrid_mamba_8b --num-layers 2 --hybrid-override-pattern M- --limit-val-batches 1 " -# "--no-activation-checkpointing --create-tensorboard-logger --create-tflops-callback " -# f"--max-steps {max_steps} --warmup-steps 1 --val-check-interval {val_check} --limit-val-batches 1 " -# f"--seq-length 16 --hidden-dropout 0.1 --attention-dropout 0.1 {additional_args} --ckpt-dir {prev_ckpt}" -# ) -# return cmd - - -# def small_training_llama_cmd(path, max_steps, val_check, devices: int = 1, additional_args: str = ""): -# cmd = ( -# f"train_evo2 --no-fp32-residual-connection --mock-data --result-dir {path} --devices {devices} " -# "--model-size 8B --num-layers 2 --limit-val-batches 1 " -# "--no-activation-checkpointing --create-tensorboard-logger --create-tflops-callback " -# f"--max-steps {max_steps} --warmup-steps 1 --val-check-interval {val_check} --limit-val-batches 1 " -# f"--seq-length 8 --hidden-dropout 0.1 --attention-dropout 0.1 {additional_args}" -# ) -# return cmd - - -# def small_training_llama_finetune_cmd( -# path, max_steps, val_check, prev_ckpt, devices: int = 1, additional_args: str = "" -# ): -# cmd = ( -# f"train_evo2 --no-fp32-residual-connection --mock-data --result-dir {path} --devices {devices} " -# "--model-size 8B --num-layers 2 --limit-val-batches 1 " -# "--no-activation-checkpointing --create-tensorboard-logger --create-tflops-callback " -# f"--max-steps {max_steps} --warmup-steps 1 --val-check-interval {val_check} --limit-val-batches 1 " -# f"--seq-length 16 --hidden-dropout 0.1 --attention-dropout 0.1 {additional_args} --ckpt-dir {prev_ckpt}" -# ) -# return cmd - - -# @pytest.mark.timeout(512) # Optional: fail if the test takes too long. -# @pytest.mark.slow -# def test_train_evo2_finetune_runs(tmp_path): -# """ -# This test runs the `train_evo2` command with mock data in a temporary directory. -# It uses the temporary directory provided by pytest as the working directory. -# The command is run in a subshell, and we assert that it returns an exit code of 0. -# """ -# num_steps = 2 -# # Note: The command assumes that `train_evo2` is in your PATH. -# command = small_training_cmd(tmp_path / "pretrain", max_steps=num_steps, val_check=num_steps) -# stdout_pretrain: str = run_command_in_subprocess(command=command, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" not in stdout_pretrain - -# log_dir = tmp_path / "pretrain" / "evo2" -# checkpoints_dir = log_dir / "checkpoints" -# tensorboard_dir = log_dir / "dev" - -# # Check if logs dir exists -# assert log_dir.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files = list(tensorboard_dir.rglob("events.out.tfevents*")) -# assert event_files, f"No TensorBoard event files found under {tensorboard_dir}" -# assert len(matching_subfolders) == 1, "Only one checkpoint subfolder should be found." -# command_finetune = small_training_finetune_cmd( -# tmp_path / "finetune", max_steps=num_steps, val_check=num_steps, prev_ckpt=matching_subfolders[0] -# ) -# stdout_finetune: str = run_command_in_subprocess(command=command_finetune, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" in stdout_finetune - -# log_dir_ft = tmp_path / "finetune" / "evo2" -# checkpoints_dir_ft = log_dir_ft / "checkpoints" -# tensorboard_dir_ft = log_dir_ft / "dev" - -# # Check if logs dir exists -# assert log_dir_ft.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir_ft.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders_ft = [ -# p for p in checkpoints_dir_ft.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders_ft, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir_ft}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir_ft.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files = list(tensorboard_dir_ft.rglob("events.out.tfevents*")) -# assert event_files, f"No TensorBoard event files found under {tensorboard_dir_ft}" - -# assert len(matching_subfolders_ft) == 1, "Only one checkpoint subfolder should be found." - - -# @pytest.mark.timeout(512) # Optional: fail if the test takes too long. -# @pytest.mark.slow -# def test_train_evo2_mamba_finetune_runs(tmp_path): -# """ -# This test runs the `train_evo2` command with mock data in a temporary directory. -# It uses the temporary directory provided by pytest as the working directory. -# The command is run in a subshell, and we assert that it returns an exit code of 0. -# """ -# num_steps = 2 -# # Note: The command assumes that `train_evo2` is in your PATH. -# command = small_training_mamba_cmd(tmp_path / "pretrain", max_steps=num_steps, val_check=num_steps) -# stdout_pretrain: str = run_command_in_subprocess(command=command, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" not in stdout_pretrain - -# log_dir = tmp_path / "pretrain" / "evo2" -# checkpoints_dir = log_dir / "checkpoints" -# tensorboard_dir = log_dir / "dev" - -# # Check if logs dir exists -# assert log_dir.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files = list(tensorboard_dir.rglob("events.out.tfevents*")) -# assert event_files, f"No TensorBoard event files found under {tensorboard_dir}" - -# assert len(matching_subfolders) == 1, "Only one checkpoint subfolder should be found." -# command_finetune = small_training_mamba_finetune_cmd( -# tmp_path / "finetune", max_steps=num_steps, val_check=num_steps, prev_ckpt=matching_subfolders[0] -# ) -# stdout_finetune: str = run_command_in_subprocess(command=command_finetune, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" in stdout_finetune - -# log_dir_ft = tmp_path / "finetune" / "evo2" -# checkpoints_dir_ft = log_dir_ft / "checkpoints" -# tensorboard_dir_ft = log_dir_ft / "dev" - -# # Check if logs dir exists -# assert log_dir_ft.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir_ft.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders_ft = [ -# p for p in checkpoints_dir_ft.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders_ft, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir_ft}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir_ft.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files = list(tensorboard_dir_ft.rglob("events.out.tfevents*")) -# assert event_files, f"No TensorBoard event files found under {tensorboard_dir_ft}" - -# assert len(matching_subfolders_ft) == 1, "Only one checkpoint subfolder should be found." - - -# @pytest.mark.timeout(512) # Optional: fail if the test takes too long. -# @pytest.mark.slow -# def test_train_evo2_llama_finetune_runs(tmp_path): -# """ -# This test runs the `train_evo2` command with mock data in a temporary directory using Llama model. -# It uses the temporary directory provided by pytest as the working directory. -# The command is run in a subshell, and we assert that it returns an exit code of 0. -# """ -# num_steps = 2 -# # Note: The command assumes that `train_evo2` is in your PATH. -# command = small_training_llama_cmd(tmp_path / "pretrain", max_steps=num_steps, val_check=num_steps) -# stdout_pretrain: str = run_command_in_subprocess(command=command, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" not in stdout_pretrain - -# log_dir = tmp_path / "pretrain" / "evo2" -# checkpoints_dir = log_dir / "checkpoints" -# tensorboard_dir = log_dir / "dev" - -# # Check if logs dir exists -# assert log_dir.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps}.0-last" -# # Check if any subfolder ends with the expected suffix -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files = list(tensorboard_dir.rglob("events.out.tfevents*")) -# assert event_files, f"No TensorBoard event files found under {tensorboard_dir}" - -# assert len(matching_subfolders) == 1, "Only one checkpoint subfolder should be found." -# command_finetune = small_training_llama_finetune_cmd( -# tmp_path / "finetune", max_steps=num_steps, val_check=num_steps, prev_ckpt=matching_subfolders[0] -# ) -# stdout_finetune: str = run_command_in_subprocess(command=command_finetune, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" in stdout_finetune - -# log_dir_ft = tmp_path / "finetune" / "evo2" -# checkpoints_dir_ft = log_dir_ft / "checkpoints" -# tensorboard_dir_ft = log_dir_ft / "dev" - -# # Check if logs dir exists -# assert log_dir_ft.exists(), "Logs folder should exist." -# # Check if checkpoints dir exists -# assert checkpoints_dir_ft.exists(), "Checkpoints folder does not exist." - -# expected_checkpoint_suffix = f"{num_steps}.0-last" -# matching_subfolders_ft = [ -# p for p in checkpoints_dir_ft.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders_ft, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir_ft}." -# ) - -# # Check if directory with tensorboard logs exists -# assert tensorboard_dir_ft.exists(), "TensorBoard logs folder does not exist." -# # Recursively search for files with tensorboard logger -# event_files = list(tensorboard_dir_ft.rglob("events.out.tfevents*")) -# assert event_files, f"No TensorBoard event files found under {tensorboard_dir_ft}" - -# assert len(matching_subfolders_ft) == 1, "Only one checkpoint subfolder should be found." - - -# @pytest.mark.timeout(256) # Optional: fail if the test takes too long. -# @pytest.mark.slow -# def test_train_evo2_stops(tmp_path): -# """ -# This test runs the `train_evo2` command with mock data in a temporary directory. -# It uses the temporary directory provided by pytest as the working directory. -# The command is run in a subshell, and we assert that it returns an exit code of 0. -# """ -# max_steps = 500000 -# early_stop_steps = 4 -# val_check = 2 -# additional_args = f"--early-stop-on-step {early_stop_steps}" -# # Expected location of logs and checkpoints -# log_dir = tmp_path / "evo2" -# checkpoints_dir = log_dir / "checkpoints" - -# assert not log_dir.exists(), "Logs folder shouldn't exist yet." - -# # Note: The command assumes that `train_evo2` is in your PATH. -# command = small_training_cmd(tmp_path, max_steps=max_steps, val_check=val_check, additional_args=additional_args) -# command_parts_no_program = shlex.split(command)[1:] -# args = parse_args(args=command_parts_no_program) -# train_stdout, trainer = run_train_with_std_redirect(args) - -# assert f"Training epoch 0, iteration 0/{early_stop_steps - 1}" in train_stdout -# # Extract and validate global steps -# global_steps = extract_global_steps_from_log(train_stdout) -# assert global_steps[0] == 0 -# assert global_steps[-1] == (early_stop_steps - 1) -# assert trainer.global_step == early_stop_steps -# assert len(global_steps) == early_stop_steps - -# expected_checkpoint_suffix = f"{early_stop_steps}.0-last" -# # Check if checkpoints dir exists -# assert checkpoints_dir.exists(), "Checkpoints folder does not exist." - -# # Check if any subfolder ends with the expected suffix -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_suffix}' found in {checkpoints_dir}." -# ) - -# assert "reduced_train_loss" in trainer.logged_metrics # validation logging on by default -# assert "TFLOPS_per_GPU" in trainer.logged_metrics # ensuring that tflops logger can be added -# assert "train_step_timing in s" in trainer.logged_metrics - - -# @pytest.mark.parametrize( -# "additional_args", -# [ -# pytest.param("", id="no_fp8"), -# pytest.param( -# "--fp8", -# marks=[ -# pytest.mark.skipif(not fp8_available, reason=reason_for_no_fp8), -# ], -# id="fp8", -# ), -# ], -# ) -# @pytest.mark.timeout(512) # Optional: fail if the test takes too long. -# @pytest.mark.slow -# def test_train_evo2_stop_at_max_steps_and_continue(tmp_path, additional_args): -# max_steps_first_run = 4 -# max_steps_second_run = 6 -# val_check_interval = 2 -# # Expected location of logs and checkpoints -# log_dir = tmp_path / "evo2" -# checkpoints_dir = log_dir / "checkpoints" - -# command_first_run = small_training_cmd( -# tmp_path, max_steps_first_run, val_check_interval, additional_args=additional_args -# ) - -# # The first training command to finish at max_steps_first_run -# stdout_first_run = run_command_in_subprocess(command=command_first_run, path=str(tmp_path)) - -# assert f"Training epoch 0, iteration 0/{max_steps_first_run - 1}" in stdout_first_run -# # Extract and validate global steps -# global_steps_first_run = extract_global_steps_from_log(stdout_first_run) - -# assert global_steps_first_run[0] == 0 -# assert global_steps_first_run[-1] == max_steps_first_run - 1 -# assert len(global_steps_first_run) == max_steps_first_run - -# expected_checkpoint_first_run_suffix = f"{max_steps_first_run}.0-last" -# # Check if checkpoints dir exists -# assert checkpoints_dir.exists(), "Checkpoints folder does not exist." -# # Check if any ckpt subfolder ends with the expected suffix -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_first_run_suffix in p.name) -# ] -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_first_run_suffix}' found in {checkpoints_dir}." -# ) - -# # The second training command to continue from max_steps_first_run and finish at max_steps_second_run -# command_second_run = small_training_cmd( -# tmp_path, max_steps_second_run, val_check_interval, additional_args=additional_args -# ) -# stdout_second_run = run_command_in_subprocess(command=command_second_run, path=str(tmp_path)) -# global_steps_second_run = extract_global_steps_from_log(stdout_second_run) - -# assert global_steps_second_run[0] == max_steps_first_run -# assert global_steps_second_run[-1] == max_steps_second_run - 1 -# assert len(global_steps_second_run) == max_steps_second_run - max_steps_first_run - -# expected_checkpoint_second_run_suffix = f"{max_steps_second_run}.0-last" -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_second_run_suffix in p.name) -# ] -# assert matching_subfolders, ( -# f"No checkpoint subfolder ending with '{expected_checkpoint_second_run_suffix}' found in {checkpoints_dir}." -# ) - - -# @pytest.fixture(scope="session") -# def dataset_config(request): -# """Get dataset directory and training config from command line options or environment variables. - -# Users can provide dataset paths via: -# - Command line: pytest --dataset-dir=/path/to/data --training-config=/path/to/config.yaml -# - Environment: DATASET_DIR=/path/to/data TRAINING_CONFIG=/path/to/config.yaml pytest - -# If not provided, tests will fall back to --mock-data. -# """ -# # Try to get from pytest command line options first -# dataset_dir = request.config.getoption("--dataset-dir", default=None) -# training_config = request.config.getoption("--training-config", default=None) - -# # Fall back to environment variables -# if not dataset_dir: -# dataset_dir = os.environ.get("DATASET_DIR") -# if not training_config: -# training_config = os.environ.get("TRAINING_CONFIG") - -# return {"dataset_dir": dataset_dir, "training_config": training_config} - - -# @pytest.fixture(scope="session") -# def initial_checkpoint(): -# """Load the initial checkpoint for distributed training tests.""" -# from bionemo.core.data.load import load - -# return load("evo2/7b-8k:1.0") - - -# @pytest.fixture(scope="session") -# def base_checkpoint(tmp_path_factory, initial_checkpoint, dataset_config): -# """Create a base checkpoint by training one step with no parallelism. - -# This fixture is session-scoped, so it creates the checkpoint once and reuses it -# across all parametrized test cases, significantly improving test performance. -# """ -# num_steps = 1 -# tmp_path = tmp_path_factory.mktemp("base_checkpoint_session") -# base_path = tmp_path / "base_training" - -# # Create command with the initial checkpoint and dataset (if provided) -# cmd = distributed_training_cmd( -# path=base_path, -# max_steps=num_steps, -# val_check=num_steps, -# num_devices=1, -# dp=1, -# tp=1, -# cp=1, -# pp=1, -# dataset_dir=dataset_config["dataset_dir"], -# training_config=dataset_config["training_config"], -# additional_args=f"--ckpt-dir {initial_checkpoint}", -# ) - -# # Run training -# stdout = run_command_in_subprocess(command=cmd, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" in stdout - -# # Find the resulting checkpoint -# log_dir = base_path / "evo2" -# checkpoints_dir = log_dir / "checkpoints" -# # Lightning uses 0-indexed step counting, so after max_steps=1, we're at step 0 -# expected_checkpoint_suffix = "step=0" - -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert len(matching_subfolders) == 1, "Expected exactly one checkpoint subfolder" -# return matching_subfolders[0] - - -# @pytest.mark.parametrize( -# "dp,cp,tp,pp", -# [ -# pytest.param(2, 1, 1, 1, id="data_parallel"), -# pytest.param(1, 2, 1, 1, id="context_parallel"), -# pytest.param(1, 1, 2, 1, id="tensor_parallel"), -# pytest.param(1, 1, 1, 2, id="pipeline_parallel"), -# ], -# ) -# @pytest.mark.timeout(900) -# @pytest.mark.slow -# @pytest.mark.skipif(torch.cuda.device_count() < 2, reason="Test requires at least 2 GPUs") -# def test_distributed_training_gradient_equivalence( -# tmp_path, initial_checkpoint, base_checkpoint, dataset_config, dp, cp, tp, pp -# ): -# """Test that gradients are equivalent across different distributed training strategies.""" -# # NOTE: Megatron Core is changing its distributed checkpoint format soon. This test needs to be updated after release 0.14. -# num_steps = 1 - -# # Calculate total devices needed -# num_devices = dp * cp * tp * pp -# assert num_devices == 2, ( -# f"Test is designed for 2 GPUs but got {num_devices} for dp={dp}, cp={cp}, tp={tp}, pp={pp}" -# ) - -# # Create parallel training checkpoint -# parallel_path = tmp_path / f"parallel_dp{dp}_cp{cp}_tp{tp}_pp{pp}" - -# cmd = distributed_training_cmd( -# path=parallel_path, -# max_steps=num_steps, -# val_check=num_steps, -# num_devices=num_devices, -# dp=dp, -# tp=tp, -# cp=cp, -# pp=pp, -# dataset_dir=dataset_config["dataset_dir"], -# training_config=dataset_config["training_config"], -# additional_args=f"--ckpt-dir {initial_checkpoint}", -# ) - -# # Run distributed training -# stdout = run_command_in_subprocess(command=cmd, path=str(tmp_path)) -# assert "Restoring model weights from RestoreConfig(path='" in stdout - -# # Find the resulting checkpoint -# log_dir = parallel_path / "evo2" -# checkpoints_dir = log_dir / "checkpoints" -# # Lightning uses 0-indexed step counting, so after max_steps=1, we're at step 0 -# expected_checkpoint_suffix = "step=0" - -# matching_subfolders = [ -# p for p in checkpoints_dir.iterdir() if p.is_dir() and (expected_checkpoint_suffix in p.name) -# ] - -# assert len(matching_subfolders) == 1, "Expected exactly one checkpoint subfolder" -# parallel_checkpoint = matching_subfolders[0] - -# # Compare gradients/optimizer states between base and parallel distributed training -# print(f"Base checkpoint: {base_checkpoint}") -# print(f"Parallel checkpoint (dp={dp}, cp={cp}, tp={tp}, pp={pp}): {parallel_checkpoint}") - -# # Ensure both checkpoints exist before comparison -# assert base_checkpoint.exists(), "Base checkpoint should exist" -# assert parallel_checkpoint.exists(), "Parallel checkpoint should exist" - -# # Use the custom gradient comparison logic to verify optimizer states match -# # This implements theorem 5.3 of https://www.arxiv.org/pdf/2506.09280 for gradient equivalence -# checkpoint_dirs = [str(base_checkpoint / "weights"), str(parallel_checkpoint / "weights")] -# assert_optimizer_states_match(checkpoint_dirs) + +import copy +import os +import re +import shlex +import shutil +import subprocess +from pathlib import Path +from typing import Dict, Iterable, Optional, Tuple, Union + +import pytest +import torch +from tensorboard.backend.event_processing.event_accumulator import EventAccumulator +from torch.distributed.checkpoint.filesystem import FileSystemReader +from torch.distributed.checkpoint.state_dict_loader import load + +from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH + +from ..utils import find_free_network_port, is_a6000_gpu, is_fp4_supported, is_fp8_supported, is_mxfp8_supported + + +TensorLike = Union[torch.Tensor, Iterable[torch.Tensor]] + + +def _as_iter(x: TensorLike): + return x if (isinstance(x, Iterable) and not isinstance(x, torch.Tensor)) else [x] + + +def _fro_norm(x: TensorLike) -> torch.Tensor: + """Frobenius norm; supports sharded tensors (sum of shard ||·||_F^2).""" + it = list(_as_iter(x)) # Convert to list to avoid iterator consumption issues + if not it: + return torch.tensor(0.0, device="cpu") + s = torch.tensor(0.0, device=it[0].device) + for t in it: + s = s + t.float().pow(2).sum() + return torch.sqrt(s) + + +def machine_epsilon_for_dtype(dtype: torch.dtype) -> float: + """Return machine epsilon for dtype. For FP8, use BF16 epsilon per paper.""" + # Standard types + if dtype in (torch.float32, torch.float16, torch.bfloat16): + return float(torch.finfo(dtype).eps) + # FP8 recipes: accum/store typically BF16/FP32; use BF16 epsilon + if hasattr(torch, "float8_e4m3fn") and dtype in ( + torch.float8_e4m3fn, + getattr(torch, "float8_e5m2fn", None), + ): + return float(torch.finfo(torch.bfloat16).eps) + # Fallback + return float(torch.finfo(torch.float32).eps) + + +def relative_grad_diff(g_hat: TensorLike, g_ref: TensorLike, eps_den: float = 1e-30) -> float: + """Relative difference ||g_hat - g_ref||_F / ||g_ref||_F. + + Accepts a single tensor or an iterable of shards for each argument. + """ + # Convert to lists to avoid iterator consumption issues + gh_list = list(_as_iter(g_hat)) + gr_list = list(_as_iter(g_ref)) + + if len(gh_list) != len(gr_list): + raise ValueError(f"Shard count mismatch: {len(gh_list)} vs {len(gr_list)}") + + if not gh_list: + return 0.0 + + num_sq = torch.tensor(0.0, device=gh_list[0].device) + for a, b in zip(gh_list, gr_list): + num_sq = num_sq + (a.float() - b.float()).pow(2).sum() + num = torch.sqrt(num_sq) + den = _fro_norm(g_ref) + return float(num / (den + eps_den)) + + +def expected_rel_bound( + l: int, # noqa: E741 + *, + L: int = 32, # noqa: N803 + C: float = 1.03, # noqa: N803 + dtype: Optional[torch.dtype] = torch.bfloat16, + k: float = 4.0, +) -> float: + """Bound ~ k * (C ** (L + 1 - l)) * eps_mch, with 1-based layer index l. + + - L is hard-coded default to 32 per your request. + - C is 'close to 1'; 1.01-1.05 are reasonable defaults. + - k absorbs the hidden constant in big-O; 2-8 are common choices. + - dtype controls eps_mch; for FP8 use BF16 epsilon (see https://www.arxiv.org/pdf/2506.09280 theorem 5.3). + """ + eps_mch = machine_epsilon_for_dtype(dtype or torch.bfloat16) + depth = L + 1 - l # 1-based depth from the top (as in the theorem) + depth = max(depth, 0) + return float(k * (C**depth) * eps_mch) + + +def check_gradient( + g_hat: TensorLike, + g_ref: TensorLike, + l: int, # noqa: E741 + *, + L: int = 32, # noqa: N803 + C: float = 1.03, # noqa: N803 + dtype: Optional[torch.dtype] = None, + k: float = 4.0, +) -> Tuple[float, float, bool]: + """Compute (rel_error, bound, ok) for layer l. + + - If dtype is None, infer from g_ref (or g_hat if needed). + # See https://www.arxiv.org/pdf/2506.09280 theorem 5.3 + """ + # Infer dtype if not provided + if dtype is None: + gr_list = list(_as_iter(g_ref)) + if gr_list: + dtype = gr_list[0].dtype + else: + dtype = torch.bfloat16 # fallback + rel = relative_grad_diff(g_hat, g_ref) + bnd = expected_rel_bound(l, L=L, C=C, dtype=dtype, k=k) + return rel, bnd, (rel <= bnd) + + +def _filter_optimizer_tensors(plain_tensors: Dict[str, torch.Tensor]) -> Dict[str, torch.Tensor]: + """Return only optimizer-related tensors from a flat checkpoint tensor dict.""" + return {k: v for k, v in plain_tensors.items() if k.startswith("optimizer.") and ".exp_avg." in k} + + +def assert_grads_close(left: torch.Tensor, right: torch.Tensor): + """Assert that two gradient tensors are close using theorem 5.3 of https://www.arxiv.org/pdf/2506.09280.""" + # Implement theorem 5.3 of https://www.arxiv.org/pdf/2506.09280 + + # This is the real test: + # k=5.0 provides margin for small numerical differences in sequence parallel gradient sync + rel, bnd, ok = check_gradient( + left, right, l=0, dtype=torch.bfloat16, k=5.0 + ) # hard code to layer 0 since that's the most permissive + + # If the real test above fails, run an assert close for the useful diagnostics and raise either way. + if not ok: + rel_shuff, _, ok_shuff = check_gradient( + left, torch.roll(right, shifts=-1, dims=-1), l=0, dtype=torch.bfloat16, k=5.0 + ) + + try: + torch.testing.assert_close(left, right) + msg = ( + "AssertionError on relative norm magnitude " + f"(rel={rel}, bnd={bnd}, ok={ok}, rel_shuff={rel_shuff}, ok_shuff={ok_shuff}) " + "but torch.testing.assert_close(left, right) passes. \n" + f"Left: {left.shape}/{left.dtype} {left}\n" + f"Right: {right.shape}/{right.dtype} {right}" + ) + except AssertionError as e: + msg = ( + "AssertionError on relative norm magnitude " + f"(rel={rel}, bnd={bnd}, ok={ok}, rel_shuff={rel_shuff}, ok_shuff={ok_shuff}): {e}\n" + f"Left: {left.shape}/{left.dtype} {left}\n" + f"Right: {right.shape}/{right.dtype} {right}" + ) + raise AssertionError(msg) + + +def _assert_optimizer_tensors_equal( + left: Dict[str, torch.Tensor], + right: Dict[str, torch.Tensor], + eps=1e-4, +): + left_keys = set(left.keys()) + right_keys = set(right.keys()) + + only_left = sorted(left_keys - right_keys) + only_right = sorted(right_keys - left_keys) + assert not only_left and not only_right, ( + f"Optimizer tensor keys mismatch.\nOnly in left: {only_left}\nOnly in right: {only_right}" + ) + some_non_zero = False + assertions = [] + for key in sorted(left_keys): + lt, rt = left[key], right[key] + assert lt.shape == rt.shape and lt.dtype == rt.dtype, ( + f"Tensor meta mismatch for {key}: {lt.shape}/{lt.dtype} vs {rt.shape}/{rt.dtype}" + ) + # Reduce the rate of 0 vs near 0 rtol failures by adding a small epsilon + left_scale = torch.max(torch.abs(lt)) + right_scale = torch.max(torch.abs(rt)) + if left_scale <= eps and right_scale <= eps: + print( + f"WARNING: zero-ish scale tensors ({left_scale=} vs {right_scale=}) " + f"so they will trivially pass comparing {key=}" + ) + else: + some_non_zero = True + try: + assert_grads_close(lt, rt) + print(f"Optimizer tensors match for {key}") + except AssertionError as e: + assertions.append(AssertionError(f"AssertionError for {key}: {e}")) + assert not assertions, f"Assertion Errors found comparing keys: {assertions}" + assert some_non_zero, "No non-zero tensors found in this comparison" + + +def load_dist_checkpoint_pt( + ckpt_dir, + metadata_ckpt_dir=None, + pattern=r"optimizer", + device="cpu", + return_full_empty: bool = False, +): + """Return {full_key: tensor} for every tensor whose key matches *pattern*.""" + meta_ckpt_dir = Path(metadata_ckpt_dir or ckpt_dir) + meta_reader = FileSystemReader(str(meta_ckpt_dir)) + + # --- fast metadata pass (no tensor data yet) ----------------------------- + meta = meta_reader.read_metadata() # tiny JSON read + tmeta = meta.state_dict_metadata # key ➜ TensorMetadata + if return_full_empty: + wanted = [k for k in tmeta if hasattr(tmeta[k], "size")] + else: + wanted = [k for k in tmeta if re.search(pattern, k) and hasattr(tmeta[k], "size")] + if not wanted: + raise ValueError(f"No keys matching /{pattern}/ in {ckpt_dir}") + + # --- build "empty" placeholders ----------------------------------------- + placeholders = { + k: torch.empty(tuple(tmeta[k].size), dtype=tmeta[k].properties.dtype, device=device) for k in wanted + } + if return_full_empty: + return placeholders + # --- stream just those tensors (no process-group needed) ----------------- + data_reader = FileSystemReader(str(ckpt_dir)) + + load( + state_dict=placeholders, + storage_reader=data_reader, + no_dist=False, # switches off all collectives + ) + return placeholders # dict[str, Tensor] + + +def assert_optimizer_states_match(checkpoint_dirs): + """Compare optimizer state across provided torch_dist checkpoints. + + - Keys: ensure the set of optimizer tensor keys match across checkpoints + - Values: ensure corresponding tensors are equal (allclose) + - Structure (non-tensor common state): ensure common optimizer structures match + """ + assert len(checkpoint_dirs) > 1, "This test requires 2 or more checkpoints [ ...]." + + base_dir = checkpoint_dirs[0] + + # Compare optimizer tensors + base_plain = load_dist_checkpoint_pt(base_dir) + base_opt_tensors = _filter_optimizer_tensors(base_plain) + assert base_opt_tensors, f"No optimizer tensors found in checkpoint: {base_dir}" + assertions = [] + for other_dir in checkpoint_dirs[1:]: + try: + other_plain = load_dist_checkpoint_pt(other_dir) + other_opt_tensors = _filter_optimizer_tensors(other_plain) + assert other_opt_tensors, f"No optimizer tensors found in checkpoint: {other_dir}" + _assert_optimizer_tensors_equal(base_opt_tensors, other_opt_tensors) + print(f"Optimizer tensors match for {base_dir} and {other_dir}") + del other_plain + del other_opt_tensors + except AssertionError as e: # noqa: PERF203 + msg = f"AssertionError comparing {base_dir} to {other_dir}:\n{e}" + print(f"Optimizer tensors mismatch for {base_dir} and {other_dir}:\n{msg}") + assertions.append(AssertionError(msg)) + assert not assertions, f"AssertionErrors comparing {checkpoint_dirs}:\n{assertions}" + + +# Do this at collection time before we run any tests. +PRETEST_ENV = copy.deepcopy(os.environ) + + +def _run_train_command(cmd: str, run_dir: Path) -> str: + env = copy.deepcopy(PRETEST_ENV) + env["MASTER_PORT"] = str(find_free_network_port()) + result = subprocess.run( + shlex.split(cmd), + check=False, + capture_output=True, + text=True, + cwd=run_dir, + env=env, + ) + if result.returncode != 0: + print(f"Return code: {result.returncode}") + print(f"STDOUT:\n{result.stdout}") + print(f"STDERR:\n{result.stderr}") + assert result.returncode == 0, f"Command failed with return code {result.returncode}\nSTDERR:\n{result.stderr}" + return result.stdout + + +def _distributed_training_cmd( + *, + path: Path, + max_steps: int, + val_check: int, + num_devices: int, + dp: int, + tp: int, + cp: int, + pp: int, + finetune_ckpt_dir: Path, + additional_args: str = "", +) -> str: + micro_batch_size = 1 if dp == 2 else 2 + return ( + f"torchrun --nproc-per-node {num_devices} --no-python train_evo2 " + f"--mock-data --result-dir {path} " + f"--hf-tokenizer-model-path {DEFAULT_HF_TOKENIZER_MODEL_PATH} " + "--model-size 7b_arc_longcontext --num-layers 4 --hybrid-override-pattern SDH* " + "--no-activation-checkpointing --optim-full-reshardable " + f"--finetune-ckpt-dir {finetune_ckpt_dir} " + f"--max-steps {max_steps} --eval-interval {val_check} --eval-iters 1 " + f"--seq-length 64 --hidden-dropout 0.0 --attention-dropout 0.0 " + f"--micro-batch-size {micro_batch_size} --global-batch-size 2 " + f"--tensor-model-parallel-size {tp} --pipeline-model-parallel-size {pp} --context-parallel-size {cp} " + "--adam-beta1 0 --adam-beta2 0 --ckpt-format torch_dist --log-interval 1 --decay-steps 1000 --warmup-steps 10 " + f"--seed 42 --dataset-seed 33 {additional_args}" + ) + + +@pytest.mark.timeout(300) +@pytest.mark.slow +@pytest.mark.parametrize( + "tp_size", + [ + pytest.param(1, id="tp_1_pretrain"), + pytest.param( + 2, + id="tp_2_pretrain", + marks=pytest.mark.skipif( + torch.cuda.device_count() < 2, reason="TP=2 requires at least 2 GPUs for pretraining." + ), + ), + ], +) +def test_fine_tuning( + tmp_path: Path, + tp_size: int, + cp_size: int = 1, + dp_size: int = 1, + final_tp: int = 1, + dp_rank_check: bool = True, + precision_recipe: str = "bf16_mixed", + pp_size: int = 1, +): + """Test fine-tuning functionality, which should mirror stop/go but reset optimizer, data, and training state.""" + world_size = tp_size * pp_size * cp_size * dp_size + mbs = 32 + gbs = mbs * dp_size + num_gpus = torch.cuda.device_count() + if world_size > num_gpus: + pytest.skip(f"World size {world_size} is greater than the number of GPUs {num_gpus}") + if "nvfp4" in precision_recipe and not is_fp4_supported(): + pytest.skip("NVFP4 is not supported on this device") + if "mxfp8" in precision_recipe and not is_mxfp8_supported(): + pytest.skip("MXFP8 is not supported on this device") + if "fp8" in precision_recipe and not is_fp8_supported(): + pytest.skip("FP8 is not supported on this device") + if "bf16_with_fp8_delayed_scaling_mixed" == precision_recipe and is_fp8_supported(): + pytest.xfail(reason="FP8 delayed scaling is not currently working with Evo2, use another FP8 recipe.") + if "bf16_with_fp8_subchannel_scaling_mixed" == precision_recipe and is_fp8_supported(): + pytest.xfail(reason="FP8 subchannel scaling is not currently working with Evo2 on some GPUs.") + run_dir = tmp_path / f"run_tp{tp_size}_pp{pp_size}_cp{cp_size}_dp{dp_size}_rc{dp_rank_check}_pr{precision_recipe}" + run_dir.mkdir(parents=True, exist_ok=True) + master_port = find_free_network_port() + dp_rank_check_str = "--debug-ddp-parity-freq 5" if dp_rank_check else "" + cmd1 = f"""torchrun --nproc-per-node {world_size} --no-python --master_port {master_port} \ + train_evo2 \ + --hf-tokenizer-model-path {DEFAULT_HF_TOKENIZER_MODEL_PATH} \ + --model-size striped_hyena_1b_nv_parallel --num-layers 4 --hybrid-override-pattern SDH* \ + --max-steps 5 --eval-interval 5 \ + --eval-iters 3 --mock-data --result-dir {run_dir} \ + --micro-batch-size {mbs} --global-batch-size {gbs} --seq-length 512 \ + --tensor-model-parallel {tp_size} \ + --pipeline-model-parallel {pp_size} \ + --context-parallel {cp_size} \ + --mixed-precision-recipe {precision_recipe} \ + --overlap-param-gather \ + --overlap-grad-reduce \ + {dp_rank_check_str} \ + --use-precision-aware-optimizer --dataset-seed 33 \ + --seed 41 --spike-no-more-embedding-init \ + --no-weight-decay-embeddings --cross-entropy-loss-fusion \ + --grad-reduce-in-fp32 \ + --decay-steps 1000 --warmup-steps 10 \ + --eod-pad-in-loss-mask \ + --log-interval 1 \ + """ + + # Split the command and run it + cmd_parts = shlex.split(cmd1) + env = copy.deepcopy(PRETEST_ENV) + if is_a6000_gpu(): + # Fix hanging issue on A6000 GPUs with multi-gpu tests + env["NCCL_P2P_DISABLE"] = "1" + result = subprocess.run(cmd_parts, check=False, capture_output=True, text=True, cwd=run_dir, env=env) + + stdout = result.stdout + stderr = result.stderr + returncode = result.returncode + + # For debugging, print the output + print(f"Return code: {returncode}") + print(f"STDOUT:\n{stdout}") + print(f"STDERR:\n{stderr}") + + # Assert the command succeeded + assert returncode == 0, f"Command failed with return code {returncode}\nSTDERR:\n{stderr}" + result_dir = run_dir / "evo2" + ckpt_dir = result_dir / "checkpoints" + tb_log_dir = result_dir / "tb_logs" + assert ckpt_dir.exists() and ckpt_dir.is_dir(), "Checkpoints directory not found" + assert tb_log_dir.exists() and tb_log_dir.is_dir(), "TensorBoard logs directory not found" + iter_5_dir = ckpt_dir / "iter_0000005" + assert iter_5_dir.exists() and iter_5_dir.is_dir(), f"No iterations 5 checkpoint found in {ckpt_dir}" + assert len(list(ckpt_dir.glob("iter_*"))) == 1, f"Expected 1 iterations, found {list(ckpt_dir.glob('iter_*'))}" + # Load tensorboard logs to verify they were written correctly + + # Find the events file(s) in tb_log_dir + event_files = list(tb_log_dir.rglob("events.out.*")) + assert len(event_files) > 0, f"No tensorboard event files found in {tb_log_dir}" + + # Load events from the event files + event_acc = EventAccumulator(str(tb_log_dir)) + event_acc.Reload() + + # 1. collect the last loss, as well as the average of the last step validation losses, as well as the last step + # Note: EventAccumulator.Scalars returns a list of ScalarEvent(wall_time, step, value) + lm_loss_events = event_acc.Scalars("lm loss") + + assert len(lm_loss_events) > 0, "No 'lm loss' events found in run 1" + last_lm_loss_step = lm_loss_events[-1].step + + assert last_lm_loss_step == 5, f"Expected run 1 to end at step 5, but got {last_lm_loss_step}" + + # 2. run the above training command a second time, this time set max_steps to 10. Verify that the run resumes from the last step. + # Do this by moving the tb_logs to a different directory from the first part so the second run makes fresh logs. + tb_log_dir_run1 = result_dir / "tb_logs_run1" + if tb_log_dir.exists(): + shutil.move(str(tb_log_dir), str(tb_log_dir_run1)) + + # Modify the command to increase max steps to 10 + # We reuse the same result_dir so it picks up the checkpoint + ft_run_dir = ( + tmp_path / f"ft_run_tp{tp_size}_pp{pp_size}_cp{cp_size}_dp{dp_size}_rc{dp_rank_check}_pr{precision_recipe}" + ) + ft_run_dir.mkdir(parents=True, exist_ok=True) + ft_world_size = final_tp * pp_size * cp_size * dp_size + cmd2 = ( + cmd1.rstrip() + .replace(f"--nproc-per-node {world_size}", f"--nproc-per-node {ft_world_size}") + .replace(f"--result-dir {run_dir}", f"--result-dir {ft_run_dir}") + .replace(f"--tensor-model-parallel {tp_size}", f"--tensor-model-parallel {final_tp}") + ) + cmd2 += f" --finetune-ckpt-dir {ckpt_dir} " + cmd_parts_2 = shlex.split(cmd2) + + print("Starting Run 2 (resuming to step 10)...") + result_2 = subprocess.run(cmd_parts_2, check=False, capture_output=True, text=True, cwd=run_dir, env=env) + + print(f"Run 2 Return code: {result_2.returncode}") + if result_2.returncode != 0: + print(f"Run 2 STDERR:\n{result_2.stderr}") + + assert result_2.returncode == 0, f"Run 2 failed with return code {result_2.returncode}" + + # 3. Load the new tb logs as before, and sanity check my recommendations as well as any others that make sense. + ft_result_dir = ft_run_dir / "evo2" + ft_tb_log_dir = ft_result_dir / "tb_logs" + assert ft_tb_log_dir.exists(), "TensorBoard logs directory not found after Run 2" + + event_acc_2 = EventAccumulator(str(ft_tb_log_dir)) + event_acc_2.Reload() + + lm_loss_events_2 = event_acc_2.Scalars("lm loss") + assert len(lm_loss_events_2) > 0, "No 'lm loss' events found in run 2" + + first_step_run2 = lm_loss_events_2[0].step + first_step_run1 = lm_loss_events[0].step + last_step_run2 = lm_loss_events_2[-1].step + + # Sanity checks: + # 1. Resumption: Should start after step 5 (e.g., step 6) + assert first_step_run2 == first_step_run1, ( + f"Run 2 FT steps should match run 1, but started at {first_step_run2} vs {first_step_run1}" + ) + + # 2. Completion: Should reach step 5 like run 1 + assert last_step_run2 == 5, f"Run 2 should reach step 5, but ended at {last_step_run2}" + + # 3. Loss Continuity check (basic): The first loss of run 2 should be reasonably close to the last loss of run 1, + # or at least not exploding, though optimization steps might cause fluctuations. + first_loss_run1 = lm_loss_events[0].value + first_loss_run2 = lm_loss_events_2[0].value + last_loss_run1 = lm_loss_events[-1].value + assert first_loss_run1 > last_loss_run1, ( + f"Run 1 first loss {first_loss_run1} is less than run 1 last loss {last_loss_run1}" + ) + assert first_loss_run2 < first_loss_run1, ( + f"Run 2 first loss {first_loss_run2} is greater than run 1 first loss {first_loss_run1}" + ) + assert abs(first_loss_run2 - first_loss_run1) > abs(last_loss_run1 - first_loss_run2), ( + f"Run 2 beginning {first_loss_run2} should be closer to end of run 1 {last_loss_run1} than beginning {first_loss_run1}." + ) + assert first_loss_run2 - last_loss_run1 < 0.1, ( + f"Run 2 first loss {first_loss_run2} is not better than run 1 last loss {last_loss_run1} by no worse than 0.1" + ) + + +@pytest.fixture(scope="session") +def mbridge_checkpoint_7b_1m(tmp_path_factory) -> Path: + """Session-scoped MBridge checkpoint for the 1b-8k-bf16 model. + + This fixture converts the NeMo2 checkpoint to MBridge format once per test session, + allowing it to be shared across multiple test files (test_infer.py, test_predict.py, etc.). + + Returns: + Path to the MBridge checkpoint iteration directory (e.g., .../iter_0000001) + """ + from bionemo.core.data.load import load + from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH_512 + from bionemo.evo2.utils.checkpoint.nemo2_to_mbridge import run_nemo2_to_mbridge + + try: + nemo2_ckpt_path = load("evo2/7b-1m:1.0") + except ValueError as e: + if e.args[0].endswith("does not have an NGC URL."): + pytest.skip( + "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " + "one or more files are missing from ngc." + ) + else: + raise e + + output_dir = tmp_path_factory.mktemp("mbridge_checkpoint_7b_1m_session") + mbridge_ckpt_dir = run_nemo2_to_mbridge( + nemo2_ckpt_dir=nemo2_ckpt_path, + tokenizer_path=DEFAULT_HF_TOKENIZER_MODEL_PATH_512, + mbridge_ckpt_dir=output_dir / "evo2_7b_1m_mbridge", + model_size="7b_arc_longcontext", + seq_length=1_048_576, + mixed_precision_recipe="bf16_mixed", + vortex_style_fp8=False, + ) + # Return the parent directory (containing latest_train_state.pt), not the iter_0000001 subdirectory + # The checkpoint loading code looks for tracker files in the parent directory + return mbridge_ckpt_dir + + +@pytest.fixture(scope="session") +def base_checkpoint(tmp_path_factory: pytest.TempPathFactory, mbridge_checkpoint_7b_1m: Path) -> Path: + """Create a base checkpoint by training one step with no parallelism.""" + if torch.cuda.device_count() < 1: + pytest.skip("Test requires at least 1 GPU") + num_steps = 1 + tmp_path = tmp_path_factory.mktemp("base_checkpoint_session") + base_path = tmp_path / "base_training" + base_path.mkdir(parents=True, exist_ok=True) + + cmd = _distributed_training_cmd( + path=base_path, + max_steps=num_steps, + val_check=num_steps, + num_devices=1, + dp=1, + tp=1, + cp=1, + pp=1, + finetune_ckpt_dir=mbridge_checkpoint_7b_1m, + ) + _run_train_command(cmd, base_path) + + ckpt_dir = base_path / "evo2" / "checkpoints" / "iter_0000001" + assert ckpt_dir.exists() and ckpt_dir.is_dir(), f"Checkpoint dir not found: {ckpt_dir}" + return ckpt_dir + + +@pytest.mark.parametrize( + "dp,cp,tp,pp", + [ + pytest.param(2, 1, 1, 1, id="data_parallel"), + pytest.param(1, 2, 1, 1, id="context_parallel"), + pytest.param(1, 1, 2, 1, id="tensor_parallel"), + pytest.param(1, 1, 1, 2, id="pipeline_parallel"), + ], +) +@pytest.mark.timeout(900) +@pytest.mark.slow +@pytest.mark.skipif(torch.cuda.device_count() < 2, reason="Test requires at least 2 GPUs") +@pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space limitations") +def test_distributed_training_gradient_equivalence( + tmp_path: Path, base_checkpoint: Path, mbridge_checkpoint_7b_1m: Path, dp, cp, tp, pp +): + """Test that optimizer states match across different distributed training strategies.""" + num_steps = 1 + num_devices = dp * cp * tp * pp + assert num_devices == 2, ( + f"Test is designed for 2 GPUs but got {num_devices} for dp={dp}, cp={cp}, tp={tp}, pp={pp}" + ) + + parallel_path = tmp_path / f"parallel_dp{dp}_cp{cp}_tp{tp}_pp{pp}" + parallel_path.mkdir(parents=True, exist_ok=True) + cmd = _distributed_training_cmd( + path=parallel_path, + max_steps=num_steps, + val_check=num_steps, + num_devices=num_devices, + dp=dp, + tp=tp, + cp=cp, + pp=pp, + finetune_ckpt_dir=mbridge_checkpoint_7b_1m, # must use the same checkpoint since PP/TP will have different RNG + additional_args=" --sequence-parallel " if tp > 1 else "", + ) + _run_train_command(cmd, parallel_path) + + parallel_checkpoint = parallel_path / "evo2" / "checkpoints" / "iter_0000001" + assert parallel_checkpoint.exists() and parallel_checkpoint.is_dir(), ( + f"Checkpoint dir not found: {parallel_checkpoint}" + ) + + checkpoint_dirs = [str(base_checkpoint), str(parallel_checkpoint)] + assert_optimizer_states_match(checkpoint_dirs) diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py index 700419333b..2a8f9cea14 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py @@ -16,8 +16,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - -# FIXME bring back these tests, at least the batch_generate and forward pass correctness tests. import gc import inspect import logging @@ -38,16 +36,6 @@ from megatron.bridge.training.tokenizers.tokenizer import _HuggingFaceTokenizer, build_tokenizer from megatron.core import dist_checkpointing, parallel_state from megatron.core.dist_checkpointing.mapping import ShardedTensor - -# # FIXME copy these out or make them not depend on NeMo -# from bionemo.llm.utils.weight_utils import ( -# MegatronModelType, -# _key_in_filter, -# _munge_key_megatron_to_nemo2, -# _munge_sharded_tensor_key_megatron_to_nemo2, -# ) -# from bionemo.testing.megatron_parallel_state_utils import distributed_model_parallel_state -# from bionemo.testing.torch import check_fp8_support from megatron.core.tensor_parallel import random as tp_random from megatron.core.transformer.enums import AttnBackend from megatron.core.transformer.module import Float16Module @@ -63,6 +51,8 @@ ) from bionemo.evo2.utils.checkpoint.nemo2_to_mbridge import run_nemo2_to_mbridge +from .utils import check_fp8_support, find_free_network_port + logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # Capture all levels in the logger itself @@ -73,22 +63,6 @@ DEFAULT_NCCL_TIMEOUT = "30" # in second -def find_free_network_port(address: str = "localhost") -> int: - """Finds a free port on localhost. - - It is useful in single-node training when we don't want to connect to a real master node but - have to set the `MASTER_PORT` environment variable. - """ - import socket - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("", 0)) - s.listen(1) - port = s.getsockname()[1] - s.close() - return port - - def _reset_microbatch_calculator(): """Resets _GLOBAL_NUM_MICROBATCHES_CALCULATOR in megatron which is used in NeMo to initilised model parallel in nemo.collections.nlp.modules.common.megatron.megatron_init.initialize_model_parallel_for_nemo @@ -196,21 +170,6 @@ def distributed_model_parallel_state( clean_up_distributed_and_parallel_states() -def check_fp8_support(device_id: int = 0) -> tuple[bool, str, str]: - """Check if FP8 is supported on the current GPU. - - FP8 requires compute capability 8.9+ (Ada Lovelace/Hopper architecture or newer). - """ - if not torch.cuda.is_available(): - return False, "0.0", "CUDA not available" - device_props = torch.cuda.get_device_properties(device_id) - compute_capability = f"{device_props.major}.{device_props.minor}" - device_name = device_props.name - # FP8 is supported on compute capability 8.9+ (Ada Lovelace/Hopper architecture) - is_supported = (device_props.major > 8) or (device_props.major == 8 and device_props.minor >= 9) - return is_supported, compute_capability, f"Device: {device_name}, Compute Capability: {compute_capability}" - - ############################################################################################# # Core utility functions: Below are some utility functions that allow for loading a nemo2 # trained model back into a newly initialized megatron core model. The key insight is that @@ -295,13 +254,13 @@ def determine_memory_requirement_and_skip_if_not_met(ckpt_name: str, test_name: "memory_needed_by_test": 21, }, # checked both variants in isolation { - "test_name": "test_batch_generate", + "test_name": "test_batch_generate_mbridge", "model_size": "1b", "seq_len_cap": -1, "memory_needed_by_test": 16, - }, # checked both variants in isolation + }, # checked both variants in isolation - needs ~21GB peak on L4 { - "test_name": "test_batch_generate", + "test_name": "test_batch_generate_mbridge", "model_size": "7b", "seq_len_cap": -1, "memory_needed_by_test": 43, @@ -310,26 +269,14 @@ def determine_memory_requirement_and_skip_if_not_met(ckpt_name: str, test_name: "test_name": "test_batch_generate_coding_sequences", "model_size": "1b", "seq_len_cap": -1, - "memory_needed_by_test": 6, + "memory_needed_by_test": 12, }, # checked both variants in isolation { "test_name": "test_batch_generate_coding_sequences", "model_size": "7b", "seq_len_cap": -1, - "memory_needed_by_test": 21, + "memory_needed_by_test": 28, }, # checked both variants in isolation - { - "test_name": "test_generate_speed", - "model_size": "1b", - "seq_len_cap": -1, - "memory_needed_by_test": -1, - }, # skipped for now until Anton's changes - { - "test_name": "test_generate_speed", - "model_size": "7b", - "seq_len_cap": -1, - "memory_needed_by_test": -1, - }, # skipped for now until Anton's changes ], columns=["test_name", "model_size", "seq_len_cap", "memory_needed_by_test"], ) @@ -381,174 +328,6 @@ def load_weights_sharded_inplace_nemo2_to_mcore( dist_checkpointing.load(sharded_state_dict, str(distributed_checkpoint_dir)) -# @pytest.mark.parametrize("seq_len", [8_192, 16_384]) -# def test_golden_values_top_k_logits_and_cosine_similarity(seq_len: int): -# try: -# evo2_1b_checkpoint_weights: Path = load("evo2/1b-8k:1.0") / "weights" -# gold_standard_no_fp8 = load("evo2/1b-8k-nofp8-te-goldvalue-testdata-A6000:1.0") -# except ValueError as e: -# if e.args[0].endswith("does not have an NGC URL."): -# raise ValueError( -# "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " -# "one or more files are missing from ngc." -# ) -# else: -# raise e -# with distributed_model_parallel_state(), torch.no_grad(): -# hyena_config = llm.Hyena1bConfig(use_te=True, seq_length=seq_len) -# tokenizer = get_nmt_tokenizer( -# "byte-level", -# ) -# raw_megatron_model = hyena_config.configure_model(tokenizer).eval().cuda() -# device = raw_megatron_model.parameters().__next__().device -# load_weights_sharded_inplace_nemo2_to_mcore(raw_megatron_model, evo2_1b_checkpoint_weights, {}, "torch_dist") -# model = Float16Module(hyena_config, raw_megatron_model) -# input_seq = "GAAATTAGCGCGTCCGGAATGATACGAGGGGAAACGAAATTTTGAATTAATGGAGAAAAAAGACGAGAAACCTTAAGCAAAAAAATTTTAGCTTCGAATATTTATTAATTTCTGAGATGTTGTTAAACGATTTTCGATTCCAAGTTGTGCGCACGAACGTTATTGCAAATAAATGCTGCTTATTCGGATGTTTCCACGATCTTTGTTGCAATGGTAGTCGAGTACCCGATAACCCAATTTCGTTACATCGGCCTATCTGTAGAATATCCAATCTATGGTTCATAAAAAATCTGATCGTTTGTTTTTAAGAAATTAAACGCGTTAAATTGAACGAATTTCGAATACCGGTCTTAGCGAAGGACCTCCCCTCTTGCTTGCGTATTGCCCCGCGAAATTTCTTTTCGGCGATGAACGATACAAAAAATTCTATCGAATGTTACTTCTATTCTCTGCCTCGTCTATGACTTGGAGATTGGTCTATGTCGTTCGTTTTCTCGCGAGTTTCCAATATGTCCGTAGTATGTGAACGCTGGTATTCGTGAAGATAAATTATTGTTTTTACAATTTCTTTCAAAAATATATAATTTTAATTTATATAAT" -# input_ids = torch.tensor(tokenizer.text_to_ids(input_seq)).int().unsqueeze(0).to(device) -# position_ids = torch.arange(len(input_seq)).unsqueeze(0).to(device) -# attention_mask = None -# outputs = model(input_ids=input_ids, position_ids=position_ids, attention_mask=attention_mask) -# gold_standard_no_fp8_tensor = torch.load(gold_standard_no_fp8).to(device=outputs.device, dtype=outputs.dtype) -# top_2_logits_golden = gold_standard_no_fp8_tensor.topk(dim=-1, sorted=True, largest=True, k=2) -# ambiguous_positions = ( -# top_2_logits_golden.values[..., 0] - top_2_logits_golden.values[..., 1] -# ).abs() < 9.9e-3 # hand tunes for observed diffs from A100 and H100 -# n_ambiguous = ambiguous_positions.sum() - -# assert n_ambiguous <= 19 - -# our_char_indices = outputs.softmax(dim=-1).argmax(dim=-1).flatten().detach().cpu().numpy() -# not_amb_positions = ~ambiguous_positions.flatten().cpu().numpy() -# # Generate our string, removing the ambiguous positions. -# our_generation_str = "".join([chr(idx) for idx in our_char_indices[not_amb_positions].tolist()]) -# # Do the same to the golden values -# gold_std_char_indices = ( -# gold_standard_no_fp8_tensor.softmax(dim=-1).argmax(dim=-1).flatten().detach().cpu().numpy() -# ) -# # Make the string -# gold_std_str = "".join([chr(idx) for idx in gold_std_char_indices[not_amb_positions].tolist()]) -# array_eq = np.array(list(our_generation_str)) == np.array(list(gold_std_str)) -# # Ensure the two strings are approximately equal. -# if array_eq.mean() < 0.95: -# array_eq = np.array(list(our_generation_str)) == np.array(list(gold_std_str)) -# mismatch_positions = np.arange(outputs.shape[1])[not_amb_positions][~array_eq] -# err_str = f"Fraction of expected mismatch positions exceeds 5%: {(~array_eq).mean()}" -# err_str += f"Mismatch positions: {mismatch_positions}" -# err_str += f"Fraction of unexpected mismatch positions: {(~array_eq).mean()}" -# top_two_logits_at_mismatch = top_2_logits_golden.values[0, mismatch_positions] -# top_2_logits_pred = outputs.topk(dim=-1, sorted=True, largest=True, k=2) -# top_two_pred_logits_at_mismatch = top_2_logits_pred.values[0, mismatch_positions] -# err_str += f"Top two logits at mismatch positions: {top_two_logits_at_mismatch}" -# err_str += f"Top two pred logits at mismatch positions: {top_two_pred_logits_at_mismatch}" -# raise AssertionError(err_str) - -# # Verify that the top-4 from the logit vectors are the same. -# # A: 65 -# # C: 67 -# # G: 71 -# # T: 84 -# # Find the corresponding ATGC and compare the two vectors with those four values. -# # Ensures that the top 4 ascii characters of the output are ACGT. -# top_4_inds = outputs.topk(dim=-1, sorted=False, largest=True, k=4) -# assert set(top_4_inds.indices.flatten().cpu().numpy().tolist()).issubset((65, 67, 71, 84)) -# output_vector = outputs[0, -1, top_4_inds.indices] - -# # Then its the top 4 indices of the gold standard tensor -# top_4_inds_golden = gold_standard_no_fp8_tensor.topk(dim=-1, sorted=False, largest=True, k=4) -# assert set(top_4_inds_golden.indices.flatten().cpu().numpy().tolist()).issubset((65, 67, 71, 84)) -# gold_standard_no_fp8_vector = gold_standard_no_fp8_tensor[0, -1, top_4_inds_golden.indices] - -# # Run cosine similarity between the two vectors. -# logit_similarity = torch.nn.functional.cosine_similarity(output_vector, gold_standard_no_fp8_vector, dim=-1) -# assert torch.mean(torch.abs(logit_similarity - torch.ones_like(logit_similarity))) < 0.03 - - -# @pytest.mark.skip(reason="test fails on main, not due to #1058") -# @pytest.mark.slow -# def test_golden_values_top_k_logits_and_cosine_similarity_7b(seq_len: int = 8_192): -# try: -# evo2_7b_checkpoint_weights: Path = load("evo2/7b-8k:1.0") / "weights" -# gold_standard_no_fp8 = load("evo2/7b-8k-nofp8-te-goldvalue-testdata:1.0") -# except ValueError as e: -# if e.args[0].endswith("does not have an NGC URL."): -# raise ValueError( -# "Please re-run test with `BIONEMO_DATA_SOURCE=pbss py.test ...`, " -# "one or more files are missing from ngc." -# ) -# else: -# raise e -# with distributed_model_parallel_state(), torch.no_grad(): -# hyena_config = llm.Hyena7bConfig(use_te=True, seq_length=seq_len) -# tokenizer = get_nmt_tokenizer( -# "byte-level", -# ) -# raw_megatron_model = hyena_config.configure_model(tokenizer).eval().cuda() -# device = raw_megatron_model.parameters().__next__().device -# load_weights_sharded_inplace_nemo2_to_mcore(raw_megatron_model, evo2_7b_checkpoint_weights, {}, "torch_dist") -# model = Float16Module(hyena_config, raw_megatron_model) -# input_seq = "GAAATTAGCGCGTCCGGAATGATACGAGGGGAAACGAAATTTTGAATTAATGGAGAAAAAAGACGAGAAACCTTAAGCAAAAAAATTTTAGCTTCGAATATTTATTAATTTCTGAGATGTTGTTAAACGATTTTCGATTCCAAGTTGTGCGCACGAACGTTATTGCAAATAAATGCTGCTTATTCGGATGTTTCCACGATCTTTGTTGCAATGGTAGTCGAGTACCCGATAACCCAATTTCGTTACATCGGCCTATCTGTAGAATATCCAATCTATGGTTCATAAAAAATCTGATCGTTTGTTTTTAAGAAATTAAACGCGTTAAATTGAACGAATTTCGAATACCGGTCTTAGCGAAGGACCTCCCCTCTTGCTTGCGTATTGCCCCGCGAAATTTCTTTTCGGCGATGAACGATACAAAAAATTCTATCGAATGTTACTTCTATTCTCTGCCTCGTCTATGACTTGGAGATTGGTCTATGTCGTTCGTTTTCTCGCGAGTTTCCAATATGTCCGTAGTATGTGAACGCTGGTATTCGTGAAGATAAATTATTGTTTTTACAATTTCTTTCAAAAATATATAATTTTAATTTATATAAT" -# input_ids = torch.tensor(tokenizer.text_to_ids(input_seq)).int().unsqueeze(0).to(device) -# position_ids = torch.arange(len(input_seq)).unsqueeze(0).to(device) -# attention_mask = None -# outputs = model(input_ids=input_ids, position_ids=position_ids, attention_mask=attention_mask) -# gold_standard_no_fp8_tensor = torch.load(gold_standard_no_fp8).to(device=outputs.device, dtype=outputs.dtype) -# is_fp8_supported, compute_capability, device_info = check_fp8_support(device.index) - -# if is_fp8_supported and compute_capability == "9.0": -# # Most rigurous assertion for output equivalence currently works on devices that are new enough to -# # support FP8. -# logger.info( -# f"Device {device_info} ({compute_capability}) supports FP8 with 9.0 compute capability, the " -# "same configuration as the gold standard was generated with. Running most rigurous assertion." -# ) -# torch.testing.assert_close(outputs, gold_standard_no_fp8_tensor) -# else: -# logger.info( -# f"Device {device_info} ({compute_capability}) does not support FP8. Running less rigurous assertions." -# ) -# top_2_logits_golden = gold_standard_no_fp8_tensor.topk(dim=-1, sorted=True, largest=True, k=2) -# ambiguous_positions = ( -# top_2_logits_golden.values[..., 0] - top_2_logits_golden.values[..., 1] -# ).abs() < 9.9e-3 # hand tunes for observed diffs from A100 and H100 with 7b model -# n_ambiguous = ambiguous_positions.sum() - -# assert n_ambiguous <= 19 - -# our_char_indices = outputs.softmax(dim=-1).argmax(dim=-1).flatten().detach().cpu().numpy() -# not_amb_positions = ~ambiguous_positions.flatten().cpu().numpy() -# # Generate our string, removing the ambiguous positions. -# our_generation_str = "".join([chr(idx) for idx in our_char_indices[not_amb_positions].tolist()]) -# # Do the same to the golden values -# gold_std_char_indices = ( -# gold_standard_no_fp8_tensor.softmax(dim=-1).argmax(dim=-1).flatten().detach().cpu().numpy() -# ) -# # Make the string -# gold_std_str = "".join([chr(idx) for idx in gold_std_char_indices[not_amb_positions].tolist()]) - -# # Ensure the two strings are equal. -# assert all(np.array(list(our_generation_str)) == np.array(list(gold_std_str))) - -# # Verify that the top-4 from the logit vectors are the same. -# # A: 65 -# # C: 67 -# # G: 71 -# # T: 84 -# # Find the corresponding ATGC and compare the two vectors with those four values. -# # Ensures that the top 4 ascii characters of the output are ACGT. -# top_4_inds = outputs.topk(dim=-1, sorted=False, largest=True, k=4) -# assert set(top_4_inds.indices.flatten().cpu().numpy().tolist()).issubset((65, 67, 71, 84)) -# output_vector = outputs[0, -1, top_4_inds.indices] - -# # Then its the top 4 indices of the gold standard tensor -# top_4_inds_golden = gold_standard_no_fp8_tensor.topk(dim=-1, sorted=False, largest=True, k=4) -# assert set(top_4_inds_golden.indices.flatten().cpu().numpy().tolist()).issubset((65, 67, 71, 84)) -# gold_standard_no_fp8_vector = gold_standard_no_fp8_tensor[0, -1, top_4_inds_golden.indices] - -# # Run cosine similarity between the two vectors. -# logit_similarity = torch.nn.functional.cosine_similarity(output_vector, gold_standard_no_fp8_vector, dim=-1) -# assert torch.mean(torch.abs(logit_similarity - torch.ones_like(logit_similarity))) < 9.9e-3 - - @pytest.fixture def sequences(): """Fixture that returns a list of sequences from the prompts.csv file.""" @@ -559,86 +338,17 @@ def sequences(): return [row["Sequence"] for row in reader] -# @pytest.fixture -# def coding_sequences(): -# with (Path(__file__).parent / "data" / "cds_prompts.csv").open(newline="") as f: -# from csv import DictReader - -# reader = DictReader(f) -# return [row["Sequence"] for row in reader] - - -# def get_trainer(pipeline_parallel=1): -# import nemo.lightning as nl - -# fp8 = True -# full_fp8 = False -# return nl.Trainer( -# accelerator="gpu", -# devices=pipeline_parallel, -# strategy=nl.MegatronStrategy( -# tensor_model_parallel_size=1, -# pipeline_model_parallel_size=pipeline_parallel, -# context_parallel_size=1, -# pipeline_dtype=torch.bfloat16, -# ckpt_load_optimizer=False, -# ckpt_save_optimizer=False, -# ckpt_async_save=False, -# save_ckpt_format="torch_dist", -# ckpt_load_strictness="log_all", -# ), -# log_every_n_steps=1, -# limit_val_batches=10, -# num_sanity_val_steps=0, -# plugins=nl.MegatronMixedPrecision( -# precision="bf16-mixed", -# params_dtype=torch.bfloat16, -# # Only use FP8 in this plugin when using full FP8 precision and FP8. -# # Otherwise use vortex_style_fp8 in the model config. -# fp8="hybrid" if fp8 and full_fp8 else None, -# fp8_amax_history_len=16 if fp8 and full_fp8 else 1, -# fp8_amax_compute_algo="max" if fp8 and full_fp8 else "most_recent", -# ), -# ) - - -# # here: pass arg through to inference_batch_times_seqlen_threshold and inference_max_seq_length -# def get_model_and_tokenizer_raw(ckpt_dir_or_name: Path | str, seq_len_max: int = 8192, **kwargs): -# """ -# Load a model and tokenizer from a checkpoint directory or name. If you supply a Path argument then we assume that -# the path is already a checkpoint directory, otherwise we load the checkpoint from NGC or PBSS depending on -# the environment variable BIONEMO_DATA_SOURCE. -# """ -# trainer = get_trainer() -# from bionemo.core.data.load import load - -# if isinstance(ckpt_dir_or_name, Path): -# ckpt_dir: Path = ckpt_dir_or_name -# else: -# ckpt_dir: Path = load(ckpt_dir_or_name) -# from nemo.collections.llm import inference - -# inference_wrapped_model, mcore_tokenizer = inference.setup_model_and_tokenizer( -# path=ckpt_dir, -# trainer=trainer, -# params_dtype=torch.bfloat16, -# inference_batch_times_seqlen_threshold=seq_len_max, -# inference_max_seq_length=seq_len_max, -# recompute_granularity=None, -# recompute_num_layers=None, -# recompute_method=None, -# **kwargs, -# ) -# return inference_wrapped_model, mcore_tokenizer - - -# def get_model_and_tokenizer(ckpt_name, vortex_style_fp8=False, seq_len_max: int = 8192, **kwargs): -# return get_model_and_tokenizer_raw(ckpt_name, vortex_style_fp8=vortex_style_fp8, seq_len_max=seq_len_max, **kwargs) - - -# def get_model_and_tokenizer_ignore_vortex(ckpt_name, vortex_style_fp8=False, seq_len_max: int = 8192, **kwargs): -# # Capture and remove the vortex_style_fp8 argument for mamba models. -# return get_model_and_tokenizer_raw(ckpt_name, seq_len_max=seq_len_max, **kwargs) +@pytest.fixture +def coding_sequences(): + """Fixture that returns coding sequences from the cds_prompts.csv file.""" + cds_file = Path(__file__).parent / "data" / "cds_prompts.csv" + if not cds_file.exists(): + pytest.skip(f"CDS prompts file not found: {cds_file}") + with cds_file.open(newline="") as f: + from csv import DictReader + + reader = DictReader(f) + return [row["Sequence"] for row in reader] def _calc_matchrate(*, tokenizer, in_seq, logits): @@ -672,10 +382,28 @@ def _check_matchrate(*, ckpt_name, matchrate, assert_matchrate=True): "ckpt_name,expected_matchpercents,flash_decode", [ # Try flash decode with one and not the other to verify that both paths work. - ("evo2/1b-8k-bf16:1.0", [96.27, 67.93, 77.50, 80.30], True), - ("evo2/1b-8k:1.0", [96.27, 67.93, 77.50, 80.30], False), - ("evo2/7b-8k:1.0", [97.60, 89.63, 80.03, 84.57], False), - ("evo2/7b-1m:1.0", [97.60, 89.63, 80.03, 84.57], False), + pytest.param("evo2/1b-8k-bf16:1.0", [96.27, 67.93, 77.50, 80.30], True, id="1b-8k-bf16"), + pytest.param( + "evo2/1b-8k:1.0", + [96.27, 67.93, 77.50, 80.30], + False, + id="1b-8k", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-8k:1.0", + [97.60, 89.63, 80.03, 84.57], + False, + id="7b-8k", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-1m:1.0", + [97.60, 89.63, 80.03, 84.57], + False, + id="7b-1m", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), ], ) def test_forward_manual(sequences: list[str], ckpt_name: str, expected_matchpercents: list[float], flash_decode: bool): @@ -776,10 +504,28 @@ def test_forward_manual(sequences: list[str], ckpt_name: str, expected_matchperc "ckpt_name,expected_matchpercents,flash_decode", [ # Try flash decode with one and not the other to verify that both paths work. - ("evo2/1b-8k-bf16:1.0", [96.27, 67.93, 77.50, 80.30], True), - ("evo2/1b-8k:1.0", [96.27, 67.93, 77.50, 80.30], False), - ("evo2/7b-8k:1.0", [97.60, 89.63, 80.03, 84.57], False), - ("evo2/7b-1m:1.0", [97.60, 89.63, 80.03, 84.57], False), + pytest.param("evo2/1b-8k-bf16:1.0", [96.27, 67.93, 77.50, 80.30], True, id="1b-8k-bf16"), + pytest.param( + "evo2/1b-8k:1.0", + [96.27, 67.93, 77.50, 80.30], + False, + id="1b-8k", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-8k:1.0", + [97.60, 89.63, 80.03, 84.57], + False, + id="7b-8k", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-1m:1.0", + [97.60, 89.63, 80.03, 84.57], + False, + id="7b-1m", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), ], ) def test_forward_ckpt_conversion( @@ -868,275 +614,311 @@ def test_forward_ckpt_conversion( ) -# def mid_point_split(*, seq, num_tokens: int | None = None, fraction: float = 0.5): -# mid_point = int(fraction * len(seq)) -# prompt = seq[:mid_point] -# if num_tokens is not None: -# target = seq[mid_point : mid_point + num_tokens] # Only compare to the section of sequence directly -# else: -# target = seq[mid_point:] -# return prompt, target - - -# def calculate_sequence_identity(seq1: str, seq2: str) -> float | None: -# """Calculate sequence identity between two sequences through direct comparison.""" -# if not seq1 or not seq2: -# return None - -# # Direct comparison of sequences -# min_length = min(len(seq1), len(seq2)) -# matches = sum(a == b for a, b in zip(seq1[:min_length], seq2[:min_length])) - -# return (matches / min_length) * 100 - - -# @pytest.mark.parametrize( -# "ckpt_name,model_tokenizer_provider,expected_matchpercents", -# [ -# ("evo2/1b-8k-bf16:1.0", get_model_and_tokenizer, [96.8, 29.7, 76.6, 71.6]), -# ("evo2/1b-8k:1.0", get_model_and_tokenizer, [96.8, 29.7, 76.6, 71.6]), -# ("evo2_mamba/7b-8k:0.1", get_model_and_tokenizer_ignore_vortex, [99.2, 51.0, 73.0, 82.6]), -# ("evo2/7b-8k:1.0", get_model_and_tokenizer, [97.60, 89.63, 80.03, 84.57]), -# ("evo2/7b-1m:1.0", get_model_and_tokenizer, [97.60, 89.63, 80.03, 84.57]), -# ], -# ) -# def test_batch_generate( -# sequences: list[str], ckpt_name: str, model_tokenizer_provider: Callable, expected_matchpercents: list[float] -# ): -# assert len(sequences) > 0 -# _ = determine_memory_requirement_and_skip_if_not_met(ckpt_name, test_name=inspect.currentframe().f_code.co_name) - -# is_fp8_supported, compute_capability, device_info = check_fp8_support(torch.cuda.current_device()) -# skip = "evo2/1b-8k:" in ckpt_name and not is_fp8_supported -# if skip: -# # This checkpoint is sensitive to FP8, so we skip it if it is not supported on the current device. -# pytest.skip(f"Skipping {ckpt_name} because it is not supported on {device_info} ({compute_capability})") -# if "evo2_mamba" in ckpt_name and os.environ.get("BIONEMO_DATA_SOURCE") != "pbss": -# # TODO: add evo2_mamba/7b-8k to NGC and remove this skip -# pytest.skip(f"Skipping {ckpt_name} because it is not on NGC yet. Run with `BIONEMO_DATA_SOURCE=pbss`.") -# # only use vortex_style_fp8 for non-bf16 checkpoints with fp8 support -# vortex_style_fp8 = is_fp8_supported and "bf16" not in ckpt_name - -# num_tokens = 500 -# seq_prompts = [mid_point_split(seq=seq, num_tokens=num_tokens) for seq in sequences] -# seq_len_max = num_tokens + max([len(sq[0]) for sq in seq_prompts]) -# inference_wrapped_model, mcore_tokenizer = model_tokenizer_provider( -# ckpt_name, -# vortex_style_fp8=vortex_style_fp8, -# seq_len_max=seq_len_max, -# ) - -# results = generate( -# model=inference_wrapped_model, -# max_batch_size=1, # vortex only supports batch size 1 -# tokenizer=mcore_tokenizer, -# prompts=[sq[0] for sq in seq_prompts], -# random_seed=42, -# inference_params=CommonInferenceParams( -# temperature=1.0, -# top_k=1, -# top_p=0.0, -# return_log_probs=False, -# num_tokens_to_generate=num_tokens, -# ), -# ) - -# match_percents = [] -# for i, (result, (prompt, target)) in enumerate(zip(results, seq_prompts)): -# gen_seq = result.generated_text -# logging.info(f"{ckpt_name} {torch.distributed.get_rank()=} {gen_seq=}") -# logging.info(f"{ckpt_name} {torch.distributed.get_rank()=} {target=}") -# match_percent = calculate_sequence_identity(target, gen_seq) -# logging.info( -# f"{ckpt_name} {torch.distributed.get_rank()=} {match_percent=} expected: {expected_matchpercents[i]}" -# ) -# match_percents.append(match_percent) - -# assert len(match_percents) == len(expected_matchpercents) -# matchperc_print = [f"{mp:.1f}%" for mp in match_percents] -# matchperc_print_expected = [f"{ep:.1f}%" for ep in expected_matchpercents] -# assert all(mp >= 0.90 * ep for mp, ep in zip(match_percents, expected_matchpercents)), ( -# f"Expected at least 90% of {matchperc_print_expected=}, got {matchperc_print=}" -# ) - - -# @pytest.mark.parametrize( -# "ckpt_name,model_tokenizer_provider,expected_matchpercents", -# [ -# ("evo2/1b-8k-bf16:1.0", get_model_and_tokenizer, [86.4, 78.8, 49.7]), -# ("evo2/1b-8k:1.0", get_model_and_tokenizer, [86.4, 78.8, 49.7]), -# ("evo2_mamba/7b-8k:0.1", get_model_and_tokenizer_ignore_vortex, [86.5, 88.4, 88.2]), -# ("evo2/7b-8k:1.0", get_model_and_tokenizer, [88.8, 88.5, 82.2]), -# ("evo2/7b-1m:1.0", get_model_and_tokenizer, [88.8, 88.5, 82.2]), -# ], -# ) -# def test_batch_generate_coding_sequences( -# coding_sequences: list[str], -# ckpt_name: str, -# model_tokenizer_provider: Callable, -# expected_matchpercents: list[float], -# ): -# assert len(coding_sequences) > 0 -# determine_memory_requirement_and_skip_if_not_met(ckpt_name, test_name=inspect.currentframe().f_code.co_name) - -# is_fp8_supported, compute_capability, device_info = check_fp8_support(torch.cuda.current_device()) -# skip = "evo2/1b-8k:" in ckpt_name and not is_fp8_supported -# if skip: -# # This checkpoint is sensitive to FP8, so we skip it if it is not supported on the current device. -# pytest.skip(f"Skipping {ckpt_name} because it is not supported on {device_info} ({compute_capability})") -# if "evo2_mamba" in ckpt_name and os.environ.get("BIONEMO_DATA_SOURCE") != "pbss": -# # TODO: add evo2_mamba/7b-8k to NGC and remove this skip -# pytest.skip(f"Skipping {ckpt_name} because it is not on NGC yet. Run with `BIONEMO_DATA_SOURCE=pbss`.") -# # only use vortex_style_fp8 for non-bf16 checkpoints with fp8 support -# vortex_style_fp8 = is_fp8_supported and "bf16" not in ckpt_name - -# match_percents: list[float] = [] -# cds_lengths: list[int | None] = [] -# original_cds_lengths: list[int] = [len(seq) for seq in coding_sequences] -# seq_prompts = [mid_point_split(seq=seq, num_tokens=None, fraction=0.3) for seq in coding_sequences] -# num_tokens = max(len(sq[1]) for sq in seq_prompts) + 15 - -# inference_wrapped_model, mcore_tokenizer = model_tokenizer_provider( -# ckpt_name, vortex_style_fp8=vortex_style_fp8, enable_flash_decode=True, flash_decode=True -# ) - -# _ = generate( -# model=inference_wrapped_model, -# max_batch_size=1, # vortex only supports batch size 1 -# tokenizer=mcore_tokenizer, -# prompts=["AAACCC"], -# random_seed=42, -# inference_params=CommonInferenceParams( -# temperature=1.0, -# top_k=1, -# top_p=0.0, -# return_log_probs=False, -# num_tokens_to_generate=1, -# ), -# ) -# results = generate( -# model=inference_wrapped_model, -# max_batch_size=1, # vortex only supports batch size 1 -# tokenizer=mcore_tokenizer, -# prompts=[sq[0] for sq in seq_prompts], -# random_seed=42, -# inference_params=CommonInferenceParams( -# temperature=1.0, -# top_k=1, -# top_p=0.0, -# return_log_probs=False, -# num_tokens_to_generate=num_tokens, -# ), -# ) - -# for i, (result, (prompt, target)) in enumerate(zip(results, seq_prompts)): -# gen_seq = result.generated_text -# logging.info(f"{ckpt_name} {torch.distributed.get_rank()=} {gen_seq=}") -# logging.info(f"{ckpt_name} {torch.distributed.get_rank()=} {target=}") -# full_seq = prompt + gen_seq -# stop_codons = {"TAA", "TAG", "TGA"} -# assert full_seq[:3] == "ATG" # start codon -# cds_length = None -# for codon_start in range(0, len(full_seq), 3): -# codon = full_seq[codon_start : codon_start + 3] -# if codon in stop_codons: -# cds_length = codon_start + 3 -# break -# match_percent = calculate_sequence_identity(target, gen_seq) -# logging.info( -# f"{ckpt_name} {torch.distributed.get_rank()=} {match_percent=} expected: {expected_matchpercents[i]}" -# ) -# match_percents.append(match_percent) -# cds_lengths.append(cds_length) -# # 99% of the time, you have a stop codon within the first 96 codons if everything were random. - -# assert len(match_percents) == len(expected_matchpercents) -# assert len(cds_lengths) == len(original_cds_lengths) -# matchperc_print = [f"{mp:.1f}%" for mp in match_percents] -# matchperc_print_expected = [f"{ep:.1f}%" for ep in expected_matchpercents] -# # By chance you expect to have a stop codon within the first 96 codons if everything were random -# # so verify that we are putting the first stop codon after this point, as well as it being at least 90% of the -# # original sequence length. -# assert all( -# pcl is None or ((pcl - len(pmpt) > 96 * 3 or len(tgt) < 96 * 3) and pcl >= 0.9 * ocl) -# for pcl, ocl, (pmpt, tgt) in zip(cds_lengths, original_cds_lengths, seq_prompts) -# ), f"Expected at least 70% of {original_cds_lengths=}, got {cds_lengths=}" -# assert all(mp >= 0.90 * ep for mp, ep in zip(match_percents, expected_matchpercents)), ( -# f"Expected at least 90% of {matchperc_print_expected=}, got {matchperc_print=}" -# ) - - -# @pytest.mark.skip( -# reason="skip the test for now, and decide what to do after getting Anton's changes sorted and merged." -# ) -# @pytest.mark.slow -# @pytest.mark.parametrize( -# "ckpt_name,model_tokenizer_provider,expected_tokens_sec", -# [ -# ("evo2/1b-8k-bf16:1.0", get_model_and_tokenizer, 41.0), -# ("evo2/1b-8k:1.0", get_model_and_tokenizer, 41.0), -# ("evo2_mamba/7b-8k:0.1", get_model_and_tokenizer_ignore_vortex, 39.73), -# ("evo2/7b-8k:1.0", get_model_and_tokenizer, 32.0), -# ("evo2/7b-1m:1.0", get_model_and_tokenizer, 32.0), -# ], -# ) -# def test_generate_speed( -# ckpt_name: str, -# model_tokenizer_provider: Callable, -# expected_tokens_sec: float, -# ): -# is_fp8_supported, compute_capability, device_info = check_fp8_support(torch.cuda.current_device()) -# determine_memory_requirement_and_skip_if_not_met(ckpt_name, test_name=inspect.currentframe().f_code.co_name) - -# skip = "evo2/1b-8k:" in ckpt_name and not is_fp8_supported -# if skip: -# # This checkpoint is sensitive to FP8, so we skip it if it is not supported on the current device. -# pytest.skip(f"Skipping {ckpt_name} because it is not supported on {device_info} ({compute_capability})") -# if "evo2_mamba" in ckpt_name and os.environ.get("BIONEMO_DATA_SOURCE") != "pbss": -# # TODO: add evo2_mamba/7b-8k to NGC and remove this skip -# pytest.skip(f"Skipping {ckpt_name} because it is not on NGC yet. Run with `BIONEMO_DATA_SOURCE=pbss`.") -# # only use vortex_style_fp8 for non-bf16 checkpoints with fp8 support -# vortex_style_fp8 = is_fp8_supported and "bf16" not in ckpt_name -# inference_wrapped_model, mcore_tokenizer = model_tokenizer_provider( -# ckpt_name, -# vortex_style_fp8=vortex_style_fp8, -# fp32_residual_connection=False, -# enable_flash_decode=True, -# flash_decode=True, -# ) - -# # warm up the model with a single call before timing. This should take care of compilation etc. -# _ = generate( -# model=inference_wrapped_model, -# max_batch_size=1, # vortex only supports batch size 1 -# tokenizer=mcore_tokenizer, -# prompts=["AAACCC"], -# random_seed=42, -# inference_params=CommonInferenceParams( -# temperature=1.0, -# top_k=1, -# top_p=0.0, -# return_log_probs=False, -# num_tokens_to_generate=1, -# ), -# ) -# t0 = time.perf_counter_ns() -# results = generate( -# model=inference_wrapped_model, -# max_batch_size=1, # vortex only supports batch size 1 -# tokenizer=mcore_tokenizer, -# prompts=["A"], -# random_seed=42, -# inference_params=CommonInferenceParams( -# temperature=1.0, -# top_k=1, -# top_p=0.0, -# return_log_probs=False, -# num_tokens_to_generate=300, -# ), -# ) -# dt = (time.perf_counter_ns() - t0) / 1e9 # seconds -# tokens_per_sec = (len(results[0].generated_text) + 1) / dt # +1 for the prompt -# assert tokens_per_sec > expected_tokens_sec * 0.85, ( -# f"Expected at least {expected_tokens_sec} tokens/sec, got {tokens_per_sec}" -# ) +def mid_point_split(*, seq, num_tokens: int | None = None, fraction: float = 0.5): + """Split a sequence at a midpoint for prompt/target evaluation.""" + mid_point = int(fraction * len(seq)) + prompt = seq[:mid_point] + if num_tokens is not None: + target = seq[mid_point : mid_point + num_tokens] # Only compare to the section of sequence directly + else: + target = seq[mid_point:] + return prompt, target + + +def calculate_sequence_identity(seq1: str, seq2: str) -> float | None: + """Calculate sequence identity between two sequences through direct comparison.""" + if not seq1 or not seq2: + return None + + # Direct comparison of sequences + min_length = min(len(seq1), len(seq2)) + matches = sum(a == b for a, b in zip(seq1[:min_length], seq2[:min_length])) + + return (matches / min_length) * 100 + + +@pytest.mark.timeout(900) +@pytest.mark.slow +@pytest.mark.parametrize( + "ckpt_name,expected_matchpercents,fp8", + [ + pytest.param("evo2/1b-8k-bf16:1.0", [86.4, 78.8, 49.7], False, id="1b-bf16_bf16"), + pytest.param("evo2/1b-8k-bf16:1.0", [86.4, 78.8, 49.7], True, id="1b-bf16_fp8"), + pytest.param( + "evo2/1b-8k:1.0", + [86.4, 78.8, 49.7], + True, + id="1b_fp8", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-8k:1.0", + [88.8, 88.5, 82.2], + False, + id="7b-8k_bf16", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-1m:1.0", + [88.8, 88.5, 82.2], + False, + id="7b-1m_bf16", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + ], +) +def test_batch_generate_coding_sequences( + coding_sequences: list[str], + tmp_path: Path, + ckpt_name: str, + expected_matchpercents: list[float], + fp8: bool, +): + """Test generation on coding sequences using MCore inference infrastructure. + + This test validates that the model can generate reasonable coding sequence + continuations, checking for proper stop codon placement and sequence identity. + """ + from bionemo.evo2.run.infer import generate, setup_inference_engine + + assert len(coding_sequences) > 0 + + # Check memory availability + try: + _ = determine_memory_requirement_and_skip_if_not_met( + ckpt_name, test_name="test_batch_generate_coding_sequences" + ) + except KeyError: + gb_available = torch.cuda.mem_get_info()[0] / 1024**3 + if gb_available < 16: + pytest.skip(f"Insufficient GPU memory: {gb_available:.1f}GB available, need at least 16GB") + + is_fp8_supported, compute_capability, device_info = check_fp8_support(torch.cuda.current_device()) + if fp8 and not is_fp8_supported: + pytest.skip(f"Skipping {ckpt_name} - FP8 not supported on {device_info} ({compute_capability})") + + # Use bf16 checkpoint to avoid FP8 issues with single-token generation + if "bf16" not in ckpt_name and not fp8: + pytest.skip(f"Skipping {ckpt_name} - use bf16 checkpoint or enable FP8 for this test") + + # Prepare prompts and targets + seq_prompts = [mid_point_split(seq=seq, num_tokens=None, fraction=0.3) for seq in coding_sequences] + num_tokens = max(len(sq[1]) for sq in seq_prompts) + 15 + original_cds_lengths: list[int] = [len(seq) for seq in coding_sequences] + + vortex_style_fp8 = ckpt_name == "evo2/1b-8k:1.0" and fp8 + mixed_precision_recipe = "bf16_with_fp8_current_scaling_mixed" if fp8 and not vortex_style_fp8 else "bf16_mixed" + + with distributed_model_parallel_state(), torch.no_grad(): + # Convert checkpoint to MBridge format + nemo2_ckpt_path = load(ckpt_name) + mbridge_ckpt_dir = run_nemo2_to_mbridge( + nemo2_ckpt_dir=nemo2_ckpt_path, + tokenizer_path=DEFAULT_HF_TOKENIZER_MODEL_PATH_512, + mbridge_ckpt_dir=tmp_path / "mbridge_checkpoint", + model_size="1b" if "1b" in ckpt_name else "7b_arc_longcontext" if "7b-1m" in ckpt_name else "7b", + seq_length=8192, + mixed_precision_recipe=mixed_precision_recipe, + vortex_style_fp8=vortex_style_fp8, + ) + mbridge_ckpt_path = mbridge_ckpt_dir / "iter_0000001" + + # Extract prompts for generation + prompts = [split[0] for split in seq_prompts] + + # Setup MCore inference engine with batch size matching number of prompts + batch_size = len(prompts) // 2 + components = setup_inference_engine( + ckpt_dir=mbridge_ckpt_path, + max_seq_length=8192, + max_batch_size=batch_size, + tensor_parallel_size=1, + random_seed=42, + ) + + # Generate all sequences - engine handles iteration internally + results = generate( + components, + prompts=prompts, + max_new_tokens=num_tokens, + temperature=1.0, + top_k=1, # Greedy for determinism + ) + + # Process results + match_percents: list[float] = [] + cds_lengths: list[int | None] = [] + stop_codons = {"TAA", "TAG", "TGA"} + + for i, (result, (prompt, target)) in enumerate(zip(results, seq_prompts)): + gen_seq = result.generated_text if result else "" + logger.info(f"{ckpt_name} {gen_seq=}") + logger.info(f"{ckpt_name} {target=}") + + full_seq = prompt + gen_seq + assert full_seq[:3] == "ATG", f"Expected start codon ATG, got {full_seq[:3]}" + + # Find first stop codon + cds_length = None + for codon_start in range(0, len(full_seq), 3): + codon = full_seq[codon_start : codon_start + 3] + if codon in stop_codons: + cds_length = codon_start + 3 + break + if cds_length is None: + logger.warning(f"{ckpt_name} {gen_seq=} no stop codon found") + cds_length = len(full_seq) + match_percent: float = calculate_sequence_identity(target, gen_seq) or 0.0 + logger.info(f"{ckpt_name} {match_percent=} expected: {expected_matchpercents[i]}") + match_percents.append(match_percent) + cds_lengths.append(cds_length) + + # Verify results + assert len(match_percents) == len(expected_matchpercents) + assert len(cds_lengths) == len(original_cds_lengths) + matchperc_print = [f"{mp:.1f}%" for mp in match_percents] + matchperc_print_expected = [f"{ep:.1f}%" for ep in expected_matchpercents] + + # By chance you expect to have a stop codon within the first 96 codons if everything were random + # so verify that we are putting the first stop codon after this point, as well as it being at least 90% of the + # original sequence length. + assert all( + pcl is None or ((pcl - len(pmpt) > 96 * 3 or len(tgt) < 96 * 3) and pcl >= 0.90 * ocl) + for pcl, ocl, (pmpt, tgt) in zip(cds_lengths, original_cds_lengths, seq_prompts) + ), f"Expected at least 90% of {original_cds_lengths=}, got {cds_lengths=}" + + assert all(mp >= 0.90 * ep for mp, ep in zip(match_percents, expected_matchpercents)), ( + f"Expected at least 90% of {matchperc_print_expected=}, got {matchperc_print=}" + ) + + +# ============================================================================= +# MBridge-based generation tests using HyenaInferenceContext +# ============================================================================= + + +@pytest.mark.timeout(900) +@pytest.mark.slow +@pytest.mark.parametrize( + "ckpt_name,expected_matchpercents,fp8", + [ + pytest.param("evo2/1b-8k-bf16:1.0", [96.8, 29.7, 76.6, 71.6], False, id="1b-bf16_bf16"), + pytest.param("evo2/1b-8k-bf16:1.0", [96.8, 29.7, 76.6, 71.6], True, id="1b-bf16_fp8"), + pytest.param( + "evo2/1b-8k:1.0", + [96.8, 29.7, 76.6, 71.6], + True, + id="1b_fp8", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-8k:1.0", + [97.60, 89.63, 80.03, 84.57], + True, + id="7b-8k_fp8", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + pytest.param( + "evo2/7b-1m:1.0", + [97.60, 89.63, 80.03, 84.57], + False, + id="7b-1m_bf16", + marks=pytest.mark.skipif(bool(os.environ.get("CI")), reason="Skip in CI due to disk space"), + ), + ], +) +def test_batch_generate_mbridge( + sequences: list[str], + tmp_path: Path, + ckpt_name: str, + expected_matchpercents: list[float], + fp8: bool, +): + """Test autoregressive generation using MCore inference infrastructure. + + This test validates that the model can generate reasonable continuations + of DNA sequences using the StaticInferenceEngine and TextGenerationController. + + Note: Hyena/Evo2 SSM state caching currently only supports batch size 1, + so prompts are processed sequentially. The MCore inference engine handles + this internally through legacy mode. + + Uses the same expected values as the original NeMo test_batch_generate. + """ + from bionemo.evo2.run.infer import generate, setup_inference_engine + + assert len(sequences) > 0 + + # Check memory availability (use test_batch_generate requirements as proxy) + try: + _ = determine_memory_requirement_and_skip_if_not_met(ckpt_name, test_name="test_batch_generate_mbridge") + except KeyError: + # If no entry exists, check basic memory availability + gb_available = torch.cuda.mem_get_info()[0] / 1024**3 + if gb_available < 16: + pytest.skip(f"Insufficient GPU memory: {gb_available:.1f}GB available, need at least 16GB") + + is_fp8_supported, compute_capability, device_info = check_fp8_support(torch.cuda.current_device()) + if fp8 and not is_fp8_supported: + pytest.skip(f"Skipping {ckpt_name} - FP8 not supported on {device_info} ({compute_capability})") + + num_tokens_to_generate = 500 # Match original test + vortex_style_fp8 = ckpt_name == "evo2/1b-8k:1.0" and fp8 + mixed_precision_recipe = "bf16_with_fp8_current_scaling_mixed" if fp8 and not vortex_style_fp8 else "bf16_mixed" + + with distributed_model_parallel_state(), torch.no_grad(): + # Convert checkpoint to MBridge format + nemo2_ckpt_path = load(ckpt_name) + mbridge_ckpt_dir = run_nemo2_to_mbridge( + nemo2_ckpt_dir=nemo2_ckpt_path, + tokenizer_path=DEFAULT_HF_TOKENIZER_MODEL_PATH_512, + mbridge_ckpt_dir=tmp_path / "mbridge_checkpoint", + model_size="1b" if "1b" in ckpt_name else "7b_arc_longcontext" if "7b-1m" in ckpt_name else "7b", + seq_length=8192, + mixed_precision_recipe=mixed_precision_recipe, + vortex_style_fp8=vortex_style_fp8, + ) + mbridge_ckpt_path = mbridge_ckpt_dir / "iter_0000001" + + # Split all sequences at midpoint to get prompts and targets + seq_splits = [mid_point_split(seq=seq, num_tokens=num_tokens_to_generate, fraction=0.5) for seq in sequences] + prompts = [split[0] for split in seq_splits] + targets = [split[1] for split in seq_splits] + + # Setup MCore inference engine + # Note: max_batch_size=1 due to Hyena SSM state constraints, but engine handles iteration + components = setup_inference_engine( + ckpt_dir=mbridge_ckpt_path, + max_seq_length=8192, + max_batch_size=1, # 1 because this test takes more memory. + tensor_parallel_size=1, + random_seed=42, + ) + + # Generate all sequences - engine handles iteration internally with max_batch_size=1 + results = generate( + components, + prompts=prompts, + max_new_tokens=num_tokens_to_generate, + temperature=1.0, + top_k=1, # Greedy for determinism + ) + + # Calculate match percentages for each result + match_percents: list[float] = [] + for i, (result, target) in enumerate(zip(results, targets)): + generated_text = result.generated_text if result else "" + match_percent = calculate_sequence_identity(target, generated_text) + if match_percent is not None: + match_percents.append(match_percent) + logger.info( + f"{ckpt_name} seq[{i}] identity: {match_percent:.1f}% expected: {expected_matchpercents[i]:.1f}%" + ) + + # Use original assertion style - expect at least 90% of expected values + assert len(match_percents) == len(expected_matchpercents) + matchperc_print = [f"{mp:.1f}%" for mp in match_percents] + matchperc_print_expected = [f"{ep:.1f}%" for ep in expected_matchpercents] + assert all(mp >= 0.90 * ep for mp, ep in zip(match_percents, expected_matchpercents)), ( + f"Expected at least 90% of {matchperc_print_expected=}, got {matchperc_print=}" + ) diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_prompt.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_prompt.py deleted file mode 100644 index c1bd4a4b51..0000000000 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_prompt.py +++ /dev/null @@ -1,140 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Arc Institute. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Michael Poli. All rights reserved. -# SPDX-FileCopyrightText: Copyright (c) 2024 Stanford University. All rights reserved -# SPDX-License-Identifier: LicenseRef-Apache2 -# -# 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. - -# FIXME bring back these tests -# from dataclasses import dataclass - -# import pytest -# from bionemo.core.data.load import load -# from bionemo.evo2.run.infer import infer -# from megatron.core.inference.inference_request import InferenceRequest - - -# RANDOM_SEED = 42 -# MAX_NEW_TOKENS = 500 -# TEMPERATURE = 1.0 -# TOP_K = 0 -# TOP_P = 0.0 - -# # todo: figure out 1M checkpoints (or add to NGC) -# CHECKPOINT_NAMES = [ -# "evo2/1b-8k-bf16:1.0", -# # "evo2/7b-8k:1.0", -# # "evo2/7b-1m:1.0", -# ] - - -# PROMPT_1 = "GAATAGGAACAGCTCCGGTCTACAGCTCCCAGCGTGAGCGACGCAGAAGACGGTGATTTCTGCATTTCCATCTGAGGTACCGGGTTCATCTCACTAGGGAGTGCCAGACAGTGGGCGCAGGCCAGTGTGTGTGCGCACCGTGCGCGAGCCGAAGCAGGG" - -# PROMPT_2 = "GATCACAGGTCTATCACCCTATTAACCACTCACGGGAGCTCTCCATGCATTTGGTATTTTCGTCTGGGGGGTATGCACGCGATAGCATTGCGAGACGCTGGAGCCGGAGCACCCTATGTCGCAGTATCTGTCTTTGATTCCTGCCTCATCCTATTATTT" - - -# @dataclass -# class InferCofig: -# """Configuration for model inference parameters.""" - -# temperature: float = TEMPERATURE -# top_k: int = TOP_K -# top_p: float = TOP_P -# tensor_parallel_size: int = 1 -# pipeline_model_parallel_size: int = 1 -# context_parallel_size: int = 1 -# max_new_tokens: int = MAX_NEW_TOKENS -# ckpt_format: str = "torch_dist" -# seed: int = RANDOM_SEED -# flash_decode: bool = False - - -# _checkpoint_cache = {} - - -# @pytest.fixture(scope="session") -# def load_checkpoint(): -# """Factory function that returns a checkpoint loader with caching.""" - -# def _load_checkpoint(ckpt_name: str) -> str: -# if ckpt_name not in _checkpoint_cache: -# _checkpoint_cache[ckpt_name] = load(ckpt_name) -# return _checkpoint_cache[ckpt_name] - -# return _load_checkpoint - - -# def percent_equal_tokens(response1: list[InferenceRequest], response2: list[InferenceRequest]) -> float: -# """Percent of tokens that are equal between two responses.""" -# num_equal = [i == j for i, j in zip(response1[0].generated_tokens, response2[0].generated_tokens)] -# return sum(num_equal) / len(num_equal) - - -# # just a DRY wrapper for the infer function -# def run_inference(prompt: str, checkpoint_path: str, config: InferCofig) -> list[InferenceRequest]: -# """Run model inference with given parameters. - -# Args: -# prompt: Input prompt for the model -# checkpoint_path: Path to model checkpoint -# config: Inference configuration parameters - -# Returns: -# Model response -# """ -# return infer( -# prompt=prompt, -# ckpt_dir=checkpoint_path, -# temperature=config.temperature, -# top_k=config.top_k, -# top_p=config.top_p, -# max_new_tokens=config.max_new_tokens, -# tensor_parallel_size=config.tensor_parallel_size, -# pipeline_model_parallel_size=config.pipeline_model_parallel_size, -# context_parallel_size=config.context_parallel_size, -# output_file=None, -# ckpt_format=config.ckpt_format, -# seed=config.seed, -# flash_decode=config.flash_decode, -# ) - - -# @pytest.mark.parametrize("ckpt_name", CHECKPOINT_NAMES) -# def test_identical_prompts_should_be_identical(load_checkpoint, ckpt_name): -# """Test that identical prompts produce identical sequences for temperature 1.0.""" -# checkpoint_path = load_checkpoint(ckpt_name) - -# # with clean_parallel_state_context(): -# response_prompt1 = run_inference(PROMPT_1, checkpoint_path, InferCofig()) -# response_prompt2 = run_inference(PROMPT_1, checkpoint_path, InferCofig()) - -# sequence_similarity = percent_equal_tokens(response_prompt1, response_prompt2) -# print(f"sequence similarity {ckpt_name} identical prompts: {sequence_similarity}") -# assert sequence_similarity == 1.0 - - -# @pytest.mark.parametrize("ckpt_name", CHECKPOINT_NAMES) -# def test_different_prompts_too_similar(load_checkpoint, ckpt_name): -# """Test that different prompts for the same sequence are too similar. -# That is, different prompts should produce more varied sequences. -# """ -# checkpoint_path = load_checkpoint(ckpt_name) - -# similarity_threshold = 0.9 - -# # with clean_parallel_state_context(): -# response_prompt1 = run_inference(PROMPT_1, checkpoint_path, InferCofig()) -# response_prompt2 = run_inference(PROMPT_2, checkpoint_path, InferCofig()) -# sequence_similarity = percent_equal_tokens(response_prompt1, response_prompt2) -# assert sequence_similarity <= similarity_threshold diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py index e58eda0928..c13fa2f43d 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py @@ -26,59 +26,13 @@ from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH +from .utils import find_free_network_port, is_fp4_supported, is_fp8_supported, is_mxfp8_supported + # Do this at collection time before we run any tests. PRETEST_ENV = copy.deepcopy(os.environ) -def get_compute_capability() -> tuple[int, int]: - """Get the compute capability of the current device.""" - if not torch.cuda.is_available(): - return (0, 0) - # Returns a tuple, e.g., (9, 0) for H100 - return torch.cuda.get_device_capability() - - -# 1. FP8 Support Logic -# Supported on Ada Lovelace (8.9) and Hopper (9.0+) -def is_fp8_supported() -> bool: - """Check if FP8 is supported on the current device.""" - cc = get_compute_capability() - return cc >= (8, 9) - - -# 2. FP4 Support Logic -# Native support requires Blackwell (10.0+) -def is_fp4_supported() -> bool: - """Check if FP4 is supported on the current device.""" - cc = get_compute_capability() - return (10, 0) <= cc < (12, 0) - - -# 3. MXFP8 Support Logic -# Native support requires Blackwell (10.0+) -def is_mxfp8_supported() -> bool: - """Check if MXFP8 is supported on the current device.""" - cc = get_compute_capability() - return (10, 0) <= cc < (12, 0) - - -def find_free_network_port() -> int: - """Finds a free port on localhost. - - It is useful in single-node training when we don't want to connect to a real master node but - have to set the `MASTER_PORT` environment variable. - """ - import socket - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("", 0)) - s.listen(1) - port = s.getsockname()[1] - s.close() - return port - - @pytest.mark.parametrize( "tp_size,cp_size,dp_size,dp_rank_check,precision_recipe", [ @@ -86,7 +40,6 @@ def find_free_network_port() -> int: (1, 1, 1, False, "bf16_with_fp8_current_scaling_mixed"), (1, 1, 1, False, "bf16_with_fp8_delayed_scaling_mixed"), # XFAIL (1, 1, 1, False, "bf16_with_fp8_subchannel_scaling_mixed"), - (1, 1, 1, False, "nanov2_bf16_with_fp8_current_scaling_mixed"), (1, 1, 1, False, "bf16_with_nvfp4_mixed"), # XFAIL other than blackwell+ (1, 1, 1, False, "bf16_with_mxfp8_mixed"), # XFAIL other than blackwell+ (1, 1, 2, True, "bf16_mixed"), @@ -119,9 +72,9 @@ def test_stop_and_go( if "fp8" in precision_recipe and not is_fp8_supported(): pytest.skip("FP8 is not supported on this device") if "bf16_with_fp8_delayed_scaling_mixed" == precision_recipe and is_fp8_supported(): - pytest.xfail(reason="FP8 delayed scaling is not currently working with Evo2, use another FP8 recipe.") + pytest.skip(reason="FP8 delayed scaling is not currently working with Evo2, use another FP8 recipe.") if "bf16_with_fp8_subchannel_scaling_mixed" == precision_recipe and is_fp8_supported(): - pytest.xfail(reason="FP8 subchannel scaling is not currently working with Evo2 on some GPUs.") + pytest.skip(reason="FP8 subchannel scaling is not currently working with Evo2 on some GPUs.") run_dir = tmp_path / f"run_tp{tp_size}_pp{pp_size}_cp{cp_size}_dp{dp_size}_rc{dp_rank_check}_pr{precision_recipe}" run_dir.mkdir(parents=True, exist_ok=True) master_port = find_free_network_port() @@ -240,169 +193,3 @@ def test_stop_and_go( assert first_loss_run2 - last_loss_run1 < 0.1, ( f"Run 2 first loss {first_loss_run2} is not better than run 1 last loss {last_loss_run1} by no worse than 0.1" ) - - -@pytest.mark.slow -def test_fine_tuning( - tmp_path: Path, - tp_size: int = 1, - cp_size: int = 1, - dp_size: int = 1, - dp_rank_check: bool = True, - precision_recipe: str = "bf16_mixed", - pp_size: int = 1, -): - """Test fine-tuning functionality, which should mirror stop/go but reset optimizer, data, and training state.""" - world_size = tp_size * pp_size * cp_size * dp_size - mbs = 32 - gbs = mbs * dp_size - num_gpus = torch.cuda.device_count() - if world_size > num_gpus: - pytest.skip(f"World size {world_size} is greater than the number of GPUs {num_gpus}") - if "nvfp4" in precision_recipe and not is_fp4_supported(): - pytest.skip("NVFP4 is not supported on this device") - if "mxfp8" in precision_recipe and not is_mxfp8_supported(): - pytest.skip("MXFP8 is not supported on this device") - if "fp8" in precision_recipe and not is_fp8_supported(): - pytest.skip("FP8 is not supported on this device") - if "bf16_with_fp8_delayed_scaling_mixed" == precision_recipe and is_fp8_supported(): - pytest.xfail(reason="FP8 delayed scaling is not currently working with Evo2, use another FP8 recipe.") - if "bf16_with_fp8_subchannel_scaling_mixed" == precision_recipe and is_fp8_supported(): - pytest.xfail(reason="FP8 subchannel scaling is not currently working with Evo2 on some GPUs.") - run_dir = tmp_path / f"run_tp{tp_size}_pp{pp_size}_cp{cp_size}_dp{dp_size}_rc{dp_rank_check}_pr{precision_recipe}" - run_dir.mkdir(parents=True, exist_ok=True) - master_port = find_free_network_port() - dp_rank_check_str = "--debug-ddp-parity-freq 5" if dp_rank_check else "" - cmd1 = f"""torchrun --nproc-per-node {world_size} --no-python --master_port {master_port} \ - train_evo2 \ - --hf-tokenizer-model-path {DEFAULT_HF_TOKENIZER_MODEL_PATH} \ - --model-size striped_hyena_1b_nv_parallel --num-layers 4 --hybrid-override-pattern SDH* \ - --max-steps 5 --eval-interval 5 \ - --eval-iters 3 --mock-data --result-dir {run_dir} \ - --micro-batch-size {mbs} --global-batch-size {gbs} --seq-length 512 \ - --tensor-model-parallel {tp_size} \ - --pipeline-model-parallel {pp_size} \ - --context-parallel {cp_size} \ - --mixed-precision-recipe {precision_recipe} \ - --overlap-param-gather \ - --overlap-grad-reduce \ - {dp_rank_check_str} \ - --use-precision-aware-optimizer --dataset-seed 33 \ - --seed 41 --spike-no-more-embedding-init \ - --no-weight-decay-embeddings --cross-entropy-loss-fusion \ - --grad-reduce-in-fp32 \ - --decay-steps 1000 --warmup-steps 10 \ - --eod-pad-in-loss-mask \ - --log-interval 1 \ - """ - - # Split the command and run it - cmd_parts = shlex.split(cmd1) - env = copy.deepcopy(PRETEST_ENV) - env["NCCL_P2P_DISABLE"] = "1" - result = subprocess.run(cmd_parts, check=False, capture_output=True, text=True, cwd=run_dir, env=env) - - stdout = result.stdout - stderr = result.stderr - returncode = result.returncode - - # For debugging, print the output - print(f"Return code: {returncode}") - print(f"STDOUT:\n{stdout}") - print(f"STDERR:\n{stderr}") - - # Assert the command succeeded - assert returncode == 0, f"Command failed with return code {returncode}\nSTDERR:\n{stderr}" - result_dir = run_dir / "evo2" - ckpt_dir = result_dir / "checkpoints" - tb_log_dir = result_dir / "tb_logs" - assert ckpt_dir.exists() and ckpt_dir.is_dir(), "Checkpoints directory not found" - assert tb_log_dir.exists() and tb_log_dir.is_dir(), "TensorBoard logs directory not found" - iter_5_dir = ckpt_dir / "iter_0000005" - assert iter_5_dir.exists() and iter_5_dir.is_dir(), f"No iterations 5 checkpoint found in {ckpt_dir}" - assert len(list(ckpt_dir.glob("iter_*"))) == 1, f"Expected 1 iterations, found {list(ckpt_dir.glob('iter_*'))}" - # Load tensorboard logs to verify they were written correctly - - # Find the events file(s) in tb_log_dir - event_files = list(tb_log_dir.rglob("events.out.*")) - assert len(event_files) > 0, f"No tensorboard event files found in {tb_log_dir}" - - # Load events from the event files - event_acc = EventAccumulator(str(tb_log_dir)) - event_acc.Reload() - - # 1. collect the last loss, as well as the average of the last step validation losses, as well as the last step - # Note: EventAccumulator.Scalars returns a list of ScalarEvent(wall_time, step, value) - lm_loss_events = event_acc.Scalars("lm loss") - - assert len(lm_loss_events) > 0, "No 'lm loss' events found in run 1" - last_lm_loss_step = lm_loss_events[-1].step - - assert last_lm_loss_step == 5, f"Expected run 1 to end at step 5, but got {last_lm_loss_step}" - - # 2. run the above training command a second time, this time set max_steps to 10. Verify that the run resumes from the last step. - # Do this by moving the tb_logs to a different directory from the first part so the second run makes fresh logs. - tb_log_dir_run1 = result_dir / "tb_logs_run1" - if tb_log_dir.exists(): - shutil.move(str(tb_log_dir), str(tb_log_dir_run1)) - - # Modify the command to increase max steps to 10 - # We reuse the same result_dir so it picks up the checkpoint - ft_run_dir = ( - tmp_path / f"ft_run_tp{tp_size}_pp{pp_size}_cp{cp_size}_dp{dp_size}_rc{dp_rank_check}_pr{precision_recipe}" - ) - ft_run_dir.mkdir(parents=True, exist_ok=True) - cmd2 = cmd1.rstrip().replace(f"--result-dir {run_dir}", f"--result-dir {ft_run_dir}") - cmd2 += f" --finetune-ckpt-dir {ckpt_dir} " - cmd_parts_2 = shlex.split(cmd2) - - print("Starting Run 2 (resuming to step 10)...") - result_2 = subprocess.run(cmd_parts_2, check=False, capture_output=True, text=True, cwd=run_dir, env=env) - - print(f"Run 2 Return code: {result_2.returncode}") - if result_2.returncode != 0: - print(f"Run 2 STDERR:\n{result_2.stderr}") - - assert result_2.returncode == 0, f"Run 2 failed with return code {result_2.returncode}" - - # 3. Load the new tb logs as before, and sanity check my recommendations as well as any others that make sense. - ft_result_dir = ft_run_dir / "evo2" - ft_tb_log_dir = ft_result_dir / "tb_logs" - assert ft_tb_log_dir.exists(), "TensorBoard logs directory not found after Run 2" - - event_acc_2 = EventAccumulator(str(ft_tb_log_dir)) - event_acc_2.Reload() - - lm_loss_events_2 = event_acc_2.Scalars("lm loss") - assert len(lm_loss_events_2) > 0, "No 'lm loss' events found in run 2" - - first_step_run2 = lm_loss_events_2[0].step - first_step_run1 = lm_loss_events[0].step - last_step_run2 = lm_loss_events_2[-1].step - - # Sanity checks: - # 1. Resumption: Should start after step 5 (e.g., step 6) - assert first_step_run2 == first_step_run1, ( - f"Run 2 FT steps should match run 1, but started at {first_step_run2} vs {first_step_run1}" - ) - - # 2. Completion: Should reach step 5 like run 1 - assert last_step_run2 == 5, f"Run 2 should reach step 5, but ended at {last_step_run2}" - - # 3. Loss Continuity check (basic): The first loss of run 2 should be reasonably close to the last loss of run 1, - # or at least not exploding, though optimization steps might cause fluctuations. - first_loss_run1 = lm_loss_events[0].value - first_loss_run2 = lm_loss_events_2[0].value - last_loss_run1 = lm_loss_events[-1].value - assert first_loss_run1 > last_loss_run1, ( - f"Run 1 first loss {first_loss_run1} is less than run 1 last loss {last_loss_run1}" - ) - assert first_loss_run2 < first_loss_run1, ( - f"Run 2 first loss {first_loss_run2} is greater than run 1 first loss {first_loss_run1}" - ) - assert abs(first_loss_run2 - first_loss_run1) > abs(last_loss_run1 - first_loss_run2), ( - f"Run 2 beginning {first_loss_run2} should be closer to end of run 1 {last_loss_run1} than beginning {first_loss_run1}." - ) - assert first_loss_run2 - last_loss_run1 < 0.1, ( - f"Run 2 first loss {first_loss_run2} is not better than run 1 last loss {last_loss_run1} by no worse than 0.1" - ) diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/utils.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/utils.py new file mode 100644 index 0000000000..8793071d05 --- /dev/null +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/utils.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-Apache2 +# +# 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. + +"""Shared test utilities for evo2 tests.""" + +import socket + +import torch + + +def find_free_network_port(address: str = "localhost") -> int: + """Find a free port on localhost for distributed testing.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((address, 0)) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + return s.getsockname()[1] + + +def get_compute_capability() -> tuple[int, int]: + """Get the compute capability of the current device.""" + if not torch.cuda.is_available(): + return (0, 0) + # Returns a tuple, e.g., (9, 0) for H100 + return torch.cuda.get_device_capability() + + +def is_fp8_supported() -> bool: + """Check if FP8 is supported on the current device. + + FP8 is supported on Ada Lovelace (8.9) and Hopper (9.0+). + """ + cc = get_compute_capability() + return cc >= (8, 9) + + +def is_fp4_supported() -> bool: + """Check if FP4 is supported on the current device. + + Native support requires Blackwell (10.0+). + """ + cc = get_compute_capability() + return (10, 0) <= cc < (12, 0) + + +def is_mxfp8_supported() -> bool: + """Check if MXFP8 is supported on the current device. + + Native support requires Blackwell (10.0+). + """ + cc = get_compute_capability() + return (10, 0) <= cc < (12, 0) + + +def check_fp8_support(device_id: int = 0) -> tuple[bool, str, str]: + """Check if FP8 is supported on the current GPU. + + FP8 requires compute capability 8.9+ (Ada Lovelace/Hopper architecture or newer). + + Returns: + Tuple of (is_supported, compute_capability_string, device_info_message). + """ + if not torch.cuda.is_available(): + return False, "0.0", "CUDA not available" + device_props = torch.cuda.get_device_properties(device_id) + compute_capability = f"{device_props.major}.{device_props.minor}" + device_name = device_props.name + # FP8 is supported on compute capability 8.9+ (Ada Lovelace/Hopper architecture) + is_supported = (device_props.major > 8) or (device_props.major == 8 and device_props.minor >= 9) + return is_supported, compute_capability, f"Device: {device_name}, Compute Capability: {compute_capability}" + + +def is_a6000_gpu() -> bool: + """Check if any of the visible GPUs is an A6000.""" + for i in range(torch.cuda.device_count()): + device_name = torch.cuda.get_device_name(i) + if "A6000" in device_name: + return True + return False diff --git a/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_256/tokenizer.json b/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_256/tokenizer.json index 1a90f8c370..1dfd9641ca 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_256/tokenizer.json +++ b/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_256/tokenizer.json @@ -136,7 +136,9 @@ } } }, - "decoder": null, + "decoder": { + "type": "Fuse" + }, "model": { "type": "WordLevel", "vocab": { diff --git a/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_512/tokenizer.json b/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_512/tokenizer.json index 29d00fd74e..045b220cbc 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_512/tokenizer.json +++ b/bionemo-recipes/recipes/evo2_megatron/tokenizers/nucleotide_fast_tokenizer_512/tokenizer.json @@ -109,7 +109,9 @@ } } }, - "decoder": null, + "decoder": { + "type": "Fuse" + }, "model": { "type": "WordLevel", "vocab": {