-
Notifications
You must be signed in to change notification settings - Fork 193
BIRD Benchmark (Text-to-SQL) #1132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 33 commits
f98958f
ac51852
5db1d9c
81970f1
92823ae
b403c47
86b95e4
98fa12e
72d31bb
1932ae6
be8b619
7bef844
6a98d4b
cee066f
e652fe6
50bfcb1
4ba8729
20cf10f
dd9adb1
044261e
e290cc6
a69e895
588fd86
1ad1ddb
ecde59a
cf0e0d0
37ebe15
d3b8fbf
996ea0d
7280bfd
7f5b0d5
3fbcf19
74aa4d6
292a66f
23585b8
490efe6
4356c00
8cf5373
ac39390
21be22c
66aace8
f6cfae2
6d4daec
1a259df
d5c37eb
f4efad7
97bf47b
465e781
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
||
| #### 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" \ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh good catch, thanks. Removing these references to |
||
| ++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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If these paths are relative to the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Side note: I removed
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, should be the same. 1.8GB unzipped, 331MB zipped.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use the 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 (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.)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, so the way we typically do exactly that is with In this case then, (1) becomes optional. (2) is done with 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
|
||
| 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" | ||
|
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" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| ) | ||
| 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 |
| 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() |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed!