Skip to content
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f98958f
Add BIRD benchmark preparation script
redoctopus Dec 9, 2025
ac51852
Add prompt config for text-to-sql (qwen3)
redoctopus Dec 9, 2025
5db1d9c
Moved text-to-SQL prompt config to generic/
redoctopus Dec 9, 2025
81970f1
BIRD evaluation class draft
redoctopus Dec 11, 2025
92823ae
Draft eval/metrics for BIRD benchmark
redoctopus Dec 12, 2025
b403c47
Try different timeout method
redoctopus Dec 12, 2025
86b95e4
Remove func_timeout from evaluation file (and trim other functions)
redoctopus Dec 12, 2025
98fa12e
Try fixing metrics arg for birdbench init file
redoctopus Dec 12, 2025
72d31bb
Commit of shame: minor typo correction
redoctopus Dec 13, 2025
1932ae6
s/-/_/ in config path
redoctopus Dec 13, 2025
be8b619
Fix config path for Bird eval init file
redoctopus Dec 13, 2025
7bef844
Fix BIRD eval config, update paths to data
redoctopus Dec 13, 2025
6a98d4b
Whoops load file before trying to parse it
redoctopus Dec 13, 2025
cee066f
Add id to data point to allow for ground truth comparison
redoctopus Dec 15, 2025
e652fe6
Path to string for jsom serialization
redoctopus Dec 15, 2025
50bfcb1
Fix SQL eval hanging on timeout
redoctopus Dec 16, 2025
4ba8729
Remove unnecessary sort, fixed minor bugs in birdbench
redoctopus Dec 16, 2025
20cf10f
Debugging metrics
redoctopus Dec 16, 2025
dd9adb1
Added checks for zero length lists in BIRD metrics
redoctopus Dec 16, 2025
044261e
Fix BIRD metrics update step and reporting
redoctopus Dec 16, 2025
e290cc6
BIRD eval printing
redoctopus Dec 16, 2025
a69e895
Cleaning up some loose code (birdbench)
redoctopus Dec 16, 2025
588fd86
Ruff style fixes
redoctopus Dec 16, 2025
1ad1ddb
Add BIRD benchmark documentation
redoctopus Dec 17, 2025
ecde59a
Remove dockerfile install in favor of commandline arg
redoctopus Dec 17, 2025
cf0e0d0
Fix style issues
redoctopus Dec 17, 2025
37ebe15
Refactor to avoid re-loading files during evaluation (BIRD), plus cod…
redoctopus Dec 17, 2025
d3b8fbf
Add pred to data dict, fix sqlite3 call
redoctopus Dec 18, 2025
996ea0d
Lint fixes
redoctopus Dec 18, 2025
7280bfd
BIRD bench fixes, removed extra entry saves from eval
redoctopus Dec 18, 2025
7f5b0d5
Add super() calls to BIRD metric
redoctopus Dec 18, 2025
3fbcf19
Add func_timeout to reqs
redoctopus Dec 18, 2025
74aa4d6
Minor lint fixes
redoctopus Dec 18, 2025
292a66f
Somehow missed an import
redoctopus Dec 18, 2025
23585b8
Reattempt at learning the alphabet
redoctopus Dec 18, 2025
490efe6
Add check for db_dir, move evaluation function, remove superfluous ev…
redoctopus Dec 19, 2025
4356c00
Lint fixes
redoctopus Dec 19, 2025
8cf5373
Whitespace massaging to appease the linter
redoctopus Dec 19, 2025
ac39390
Remove manual check for BIRD eval file param
redoctopus Dec 19, 2025
21be22c
Remove db_dir arg in favor of relative path
redoctopus Dec 19, 2025
66aace8
Clean up unused arg
redoctopus Dec 19, 2025
f6cfae2
BIRD: remove output dir arg in favor of data_dir
redoctopus Dec 19, 2025
6d4daec
Whitespace removal
redoctopus Dec 20, 2025
1a259df
Merge branch 'main' into birdbench
redoctopus Jan 6, 2026
d5c37eb
Slight clarification to BIRD docs re data_dir
redoctopus Jan 6, 2026
f4efad7
More robust SQL comment removal
redoctopus Jan 6, 2026
97bf47b
Increment counter for dependency fix
redoctopus Jan 6, 2026
465e781
Fix SQL comment regex
redoctopus Jan 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/evaluation/code.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,48 @@ Due to variance between runs, you can automatically repeat the evaluation and av
--benchmarks=livecodebench:3
```

### BIRD

The [BIRD benchmark](https://bird-bench.github.io/) is currently the only text-to-SQL benchmark that is supported. Evaluation is based on the SQL evaluation accuracy calculated in [this file](https://github.com/AlibabaResearch/DAMO-ConvAI/blob/main/bird/llm/src/evaluation.py) provided in the BIRD GitHub repository.

#### Data Preparation


First, the data must be downloaded and prepared, which you can do by running:
```bash
ns prepare_data birdbench
```

This will download and unpack a file into `<output_directory>/dev_20240627`, which contains the BIRD dev manifest, table information, and database schemas. By default, `output_directory` will be under `nemo_skills/dataset/birdbench/`, though this can be changed via command line argument.

The script will also process the original manifest into `<output_directory>/dev.jsonl`, which will be the input for evaluation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed now that we have data_dir? The rest of this is pretty consistent across evaluations.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed!


#### Running the Evaluation

The following command runs an evaluation of [Qwen3-8B](https://huggingface.co/Qwen/Qwen3-8B) on a Slurm cluster.

```bash
ns eval \
--cluster=<CLUSTER_NAME> \
--server_type='sglang' \
--server_gpus=8 \
--model=Qwen/Qwen3-8B \
--benchmarks=birdbench \
--output_dir=<OUTPUT_DIR> \
--installation_command="pip install func_timeout" \

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be needed once we have the updated requirements merged, so we can probably remove it here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good catch, thanks. Removing these references to installation_command.

++inference.tokens_to_generate=10000 \
++inference.temperature=0.6 \
++inference.top_p=0.95 \
++inference.top_k=20 \
++max_concurrent_requests=1024 \
++eval_config.dev_json_filepath=<DATA_DIR>/dev.json \
++eval_config.db_path=<DATA_DIR>/dev_databases
```
!!! note
BIRD evaluation requires the `func_timeout` library, so you will need to add the installation argument `--installation_command` seen in this example if it is not included in your container.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again probably unnecessary once we merge this


The arguments for evaluation also include paths for `++eval_config.dev_json_filepath` and `++eval_config.db_path`. These are used for SQL execution. You should point these to the unmodified `dev.json` file and `dev_databases` directory found in the original downloaded and unzipped data (i.e. `.../dev_20240627/dev.json`, etc.). If using a Slurm cluster, you may need to upload and mount the data directory first.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these paths are relative to the dataset, I would prefer we infer it from the dataset, rather than have to always specify these paths

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dev_databases directory isn't inherently tied to the dataset, so I don't think we can infer it directly.

Example use case: BIRD data processing is done locally, but then generation and evaluation are done on either locally or using a cluster--to have the flexibility to do either we need to be able to dynamically specify the location of the database files.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Side note: I removed dev_json_filepath in my last refactor so at least that's cleaner now :)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it the same database content locally and on the cluster? How big are the databases?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, should be the same. 1.8GB unzipped, 331MB zipped.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the data_dir argument instead, then?

https://github.com/NVIDIA-NeMo/Skills/blob/main/nemo_skills/pipeline/prepare_data.py#L34-L50

Then we just have to pass around that same argument between prepare_data and eval, but don't need to go separately prepare it on the cluster and then get the path back. Makes it easier to keep things consistent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I might be missing something here but I'm still not quite seeing how that would work--if we ran prepare_data locally and then wanted to run evaluation on the cluster, at prepare_data runtime data_dir would only have the local path to the directory and not the cluster's path, right? Additionally, if we don't want to have NeMo-Skills upload the dataset for every run, we can't guarantee it'll be in any given relative path location e.g. nemo_skills/datasets/birdbench/... since the user could upload it anywhere else.

(And a quick clarification: currently we don't need to prepare the data separately, the pipeline is more like (1) run prepare_data locally, (2) upload that directory to desired cluster, (3) run evaluation with that directory mounted & pass in that path.)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so the way we typically do exactly that is with data_dir. See the livecodebench for pypy3 docs: https://nvidia-nemo.github.io/Skills/evaluation/code/#for-pypy3-evaluation

In this case then, (1) becomes optional. (2) is done with ns prepare_data birdbench --cluster=<cluster name> --data_dir=<data_dir>, then you can do any amount of eval with (3) using ns eval --cluster=<cluster name> --data_dir=<data_dir> without it uploading--it should find the data in data_dir rather than uploading.

This helps us keep things consistent, and gives you the interface to upload with, rather than us having one-off copy workflows and arguments for each dataset.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so in this case we're forgoing local data prep in favor of doing it all on the cluster. Gotcha, that's fine with me! Even if we want to set up on multiple clusters the data prep is pretty quick to run.


### livecodebench-cpp

- Benchmark is defined in [`nemo_skills/dataset/livecodebench-cpp/__init__.py`](https://github.com/NVIDIA-NeMo/Skills/blob/main/nemo_skills/dataset/livecodebench-cpp/__init__.py)
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Here are some of the features we support:
- Evaluate your models on many popular benchmarks.
- [**Math (natural language**)](./evaluation/natural-math.md): e.g. [aime24](./evaluation/natural-math.md#aime24), [aime25](./evaluation/natural-math.md#aime25), [hmmt_feb25](./evaluation/natural-math.md#hmmt_feb25)
- [**Math (formal language)**](./evaluation/formal-math.md): e.g. [minif2f](./evaluation/formal-math.md#minif2f), [proofnet](./evaluation/formal-math.md#proofnet), [putnam-bench](./evaluation/formal-math.md#putnam-bench)
- [**Code**](./evaluation/code.md): e.g. [swe-bench](./evaluation/code.md#swe-bench), [livecodebench](./evaluation/code.md#livecodebench)
- [**Code**](./evaluation/code.md): e.g. [swe-bench](./evaluation/code.md#swe-bench), [livecodebench](./evaluation/code.md#livecodebench), [bird](./evaluation/code.md#BIRD)
Comment thread
redoctopus marked this conversation as resolved.
- [**Scientific knowledge**](./evaluation/scientific-knowledge.md): e.g., [hle](./evaluation/scientific-knowledge.md#hle), [scicode](./evaluation/scientific-knowledge.md#scicode), [gpqa](./evaluation/scientific-knowledge.md#gpqa)
- [**Instruction following**](./evaluation/instruction-following.md): e.g. [ifbench](./evaluation/instruction-following.md#ifbench), [ifeval](./evaluation/instruction-following.md#ifeval)
- [**Long-context**](./evaluation/long-context.md): e.g. [ruler](./evaluation/long-context.md#ruler), [mrcr](./evaluation/long-context.md#mrcr)
Expand Down
29 changes: 29 additions & 0 deletions nemo_skills/dataset/birdbench/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# settings that define how evaluation should be done by default (all can be changed from cmdline)
DATASET_GROUP = "code"
METRICS_TYPE = "bird"
EVAL_SPLIT = "dev"
Comment thread
gwarmstrong marked this conversation as resolved.
GENERATION_ARGS = (
"++prompt_config=generic/text_to_sql "
"++eval_type=bird "
"++inference.tokens_to_generate=10000 "
"++inference.temperature=0.6 "
"++inference.top_p=0.95 "
"++inference.top_k=20 "
"++max_concurrent_requests=1024 "
# Note: Override this path via ++eval_config.db_path
"++eval_config.db_path=/sql_data/BIRD/dev_databases"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if we could infer this from the dataset directory rather than hard code a bad value--I think this will cause unexpected issues where the failure that happens if someone doesn't specify this will be of the nature of FileNotFound, rather than the user knowing the option they should be updating

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the above (can't necessarily couple it with the dataset manifest), is there a better way to handle this? Maybe comment this out and then add a check for the value in evaluation that gives a more informative error?

)
54 changes: 54 additions & 0 deletions nemo_skills/dataset/birdbench/evaluation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Original code from https://github.com/AlibabaResearch/DAMO-ConvAI/blob/main/bird/llm/src/evaluation.py

# Original license as follows:

# MIT License
#
# Copyright (c) 2022 Alibaba Research
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import sqlite3


def execute_sql(predicted_sql, ground_truth, db_path):
# Connect to the database
with sqlite3.connect(db_path) as conn:
cursor = conn.cursor()
cursor.execute(predicted_sql)
predicted_res = cursor.fetchall()
cursor.execute(ground_truth)
ground_truth_res = cursor.fetchall()
res = 0
if set(predicted_res) == set(ground_truth_res):
res = 1
return res
129 changes: 129 additions & 0 deletions nemo_skills/dataset/birdbench/prepare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import glob
import json
import os
import re
import sqlite3
import zipfile
from pathlib import Path

import wget


def download_data(output_dir):
# Download zip directly (HF Dataset is missing SQL files and table info)
print("Downloading and extracting data file...")
url = "https://bird-bench.oss-cn-beijing.aliyuncs.com/dev.zip"
filename = wget.download(url, out=output_dir)
with zipfile.ZipFile(Path(output_dir, filename), "r") as f_in:
f_in.extractall(output_dir)

# Expand tables zipfiles
print("Extracting databases...")
dev_dir = Path(output_dir, "dev_20240627/")
dbs_zipfile = Path(dev_dir, "dev_databases.zip")
with zipfile.ZipFile(dbs_zipfile, "r") as f_dbs:
f_dbs.extractall(dev_dir)

print("Extracted all data!")
return dev_dir


def read_tables_file(base_dir):
"""
Gets each db's information by using sqlite3 to get a table dump.
"""
tables_info = {}
all_db_dirs = glob.glob("*", root_dir=os.path.join(base_dir, "dev_databases"))

for db_dir in all_db_dirs:
print(f"Reading database info from: {db_dir}")
table_info = ""

# Grab the db's sqlite file & read the dump
full_db_dir = os.path.join(base_dir, "dev_databases", db_dir)
sqlite_file = os.path.join(full_db_dir, db_dir + ".sqlite")
assert os.path.exists(sqlite_file)

with sqlite3.connect(os.path.join(full_db_dir, db_dir + ".sqlite")) as con:
con.text_factory = lambda b: b.decode(errors="ignore")
for line in con.iterdump():
if line[:6] == "INSERT":
line = line.replace("\n", " ")
line = re.sub(" +", " ", line)
table_info += line + "\n"

# Time to truncate any long INSERT chains (allow 10 max at once)
insert_chain = r"((INSERT.*$\n){10})((INSERT.*\n)*)"
table_info = re.sub(insert_chain, r"\1\n...\n", table_info, flags=re.MULTILINE)

# Also get rid of any INSERT INTO * VALUES (...) <- lots of entries (>10)
many_values = r"(?:VALUES )(((\([^)]*)\)[,;]\s*)){10}(.*)(?:;)"
table_info = re.sub(many_values, r"...", table_info, flags=re.MULTILINE)

tables_info[db_dir] = table_info

return tables_info


def format_entries(file_path, tables_info, out_file):
"""
Combines the raw BIRD data entries with corresponding table info and
ground truth solution to form dev manifest
"""
with open(out_file, "w") as f_out:
with open(file_path, "r") as f_in:
entries = json.load(f_in)

for i, entry in enumerate(entries):
new_entry = {
"question": entry["question"],
"gt_sql": entry["SQL"],
"sql_context": tables_info[entry["db_id"]],
"difficulty": entry["difficulty"],
"db_id": entry["db_id"],
"id": i,
}

f_out.write(json.dumps(new_entry))
f_out.write("\n")


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--output_dir", type=str, default=str(Path(__file__).parent))
args = parser.parse_args()

dev_dir = download_data(args.output_dir)
# If already downloaded: dev_dir = Path(args.output_dir, "dev_20240627/")
print(f"\nData downloaded to: {dev_dir}")

print("Starting processing...")

# First read tables data
tables_info = read_tables_file(dev_dir)
print("Finished reading tables.")

# Naming the input and output files the nearly same thing is likely
# confusing, but <split>.jsonl is the expected format so we'll just
# keep the result in the output directory.
format_entries(Path(dev_dir, "dev.json"), tables_info, Path(args.output_dir, "dev.jsonl"))
print("Finished formatting entries. All done!")


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions nemo_skills/evaluation/evaluator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from nemo_skills.evaluation.evaluator.audio import AudioEvaluator
from nemo_skills.evaluation.evaluator.base import BaseEvaluator
from nemo_skills.evaluation.evaluator.bfcl import eval_bfcl
from nemo_skills.evaluation.evaluator.bird import BirdEvaluator
from nemo_skills.evaluation.evaluator.code import (
CodeExecEvaluator,
eval_bigcodebench,
Expand Down Expand Up @@ -67,6 +68,7 @@
"ioi": IOIEvaluator,
"icpc": ICPCEvaluator,
"audio": AudioEvaluator,
"bird": BirdEvaluator,
}

# Validation: Ensure no overlap between class and function maps
Expand Down
Loading
Loading