GRAB is a framework for table question answering and table-grounded reasoning. It encodes structured tables as tripartite graphs and injects the resulting embeddings into a frozen large language model (LLM), enabling the model to reason over complex tabular data.
Paper link: https://aclanthology.org/2026.emnlp-main.2073/
| Key | Description |
|---|---|
base_llm |
Baseline: frozen LLM with linearised table in the prompt, no graph encoder. |
soft_prompt_llm |
Soft-prompt baseline: learnable resampler over a simple MLP encoder, no GNN. |
grab_single_table |
GRAB for single-table datasets. Full tripartite GNN + resampler. |
grab_multi_table |
GRAB for multi-table datasets. Adds a learnable table-ID embedding to R and C. |
| Key | Task type | Tables |
|---|---|---|
wtq |
Table QA | Single |
wikisql |
Table QA | Single |
structProbe |
Table QA | Single |
hitab |
Table QA | Single |
tabmwp |
Math over tables | Single |
hctqa |
Hierarchical table QA | Single |
multihiertt |
Multi-table QA | Multi |
scitat |
Scientific table QA | Multi |
mmqa |
Multi-modal multi-table QA | Multi |
tqa_bench |
Table QA benchmark | Multi |
atis |
Text-to-SQL (result table) | Multi |
geoquery |
Text-to-SQL (result table) | Multi |
spider_text2sql |
Text-to-SQL generation | Multi |
spider_qa |
Text-to-SQL (result table QA) | Multi |
GRAB/
├── src/
│ ├── train_parallel.py # DDP training + end-of-run evaluation
│ ├── test.py # Pure inference (no training)
│ ├── precompute_single_table.py # Precompute graph features for grab_single_table
│ ├── precompute_multi_table.py # Precompute graph features for grab_multi_table
│ ├── config.py # All argument definitions
│ ├── global_path.py # Loads .env → data_dir, model_dir, ckpt_dir, precomputed_graphs_root
│ ├── model/
│ │ ├── grab_models.py # GrabSingleTable, GrabMultiTable
│ │ ├── gnn_encoder.py # Tripartite GNN + K-token resampler
│ │ ├── baseline_llm.py # base_llm
│ │ ├── soft_prompt_llm.py # soft_prompt_llm
│ │ └── tokenizers.py # TableTokenizerRow, MultiTableTokenizerSplitRow
│ ├── dataset/ # One file per dataset + precomputed_wrapper.py
│ └── utils/
│ ├── collate.py # collate_fn + collate_graph_batch (pads graph tensors in workers)
│ ├── ckpt.py # Checkpoint save / reload
│ ├── evaluate.py # Per-dataset evaluation functions
│ └── ...
├── script/
│ ├── train/
│ │ ├── grab_single_dataset.sh # Train grab_single_table on all single-table datasets
│ │ ├── grab_multi_dataset.sh # Train grab_multi_table on all multi-table datasets
│ │ └── soft_prompt_all_datasets.sh
│ └── test/
│ └── baseline_llm_all_datasets.sh
|
├── .env # Paths (DATA_DIR, MODEL_DIR, CKPT_DIR, PRECOMPUTED_GRAPHS)
└── skip_list.json # Samples to exclude (token budget exceeded)
Edit .env in the project root. All scripts source it automatically. The four variables control where data, models, checkpoints, and precomputed graphs are read from and written to:
DATA_DIR — HuggingFace datasets saved to disk. Each dataset follows the double-name convention:
$DATA_DIR/
<dataset>/
<dataset>/ ← HF dataset artifact (train/, validation/, test/)
MODEL_DIR — Local model weights:
$MODEL_DIR/
Qwen/
Qwen3-4B-Base/
Qwen3-14B-Base/
Qwen3-Embedding-0.6B/
CKPT_DIR — Training outputs (checkpoints, predictions, scores). Defaults to ./checkpoints relative to the project root:
$CKPT_DIR/
train/
grab_single/<dataset>/
grab_multi/<dataset>/
soft_prompt/<dataset>/
test/
base_llm/<dataset>/
PRECOMPUTED_GRAPHS — Precomputed graph features for GRAB models:
$PRECOMPUTED_GRAPHS/
<dataset>/
<run_name>/ ← e.g. precomputed_graphs_row_qwen_final
train/
validation/
test/
meta.json
Most datasets can be downloaded automatically from HuggingFace by running:
python download_datasets.pyThis requires DATA_DIR to be set in .env first. The script covers: wtq, wikisql, hitab, hctqa, atis, geoquery, spider. The remaining datasets require manual download, but the script prints exact instructions for each when it finishes.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtPretrained checkpoints for all GRAB models and baselines are available on HuggingFace at GRAB-table/checkpoints.
Download the relevant .pth file and pass its path via --llm_ckpt_path when running src/test.py. Example for the structProbe dataset:
torchrun --nproc_per_node=2 src/test.py \
--model_name "base_llm" \
--dataset "structProbe" \
--multi_table "False" \
--prompt_type "qwen" \
--seed 42 \
--eval_batch_size 4 \
--num_workers 8 \
--llm_model_name "qwen3_4b" \
--llm_frozen "True" \
--llm_lora "False" \
--max_txt_len 8192 \
--max_new_tokens 128 \
--enable_thinking "False" \
--table_encoder_name "None" \
--llm_ckpt_path "/path/to/checkpoint.pth" \
--output_dir "$CKPT_DIR/test/base_llm/structProbe" \
--skip_list "src/skip_list.json"When --llm_ckpt_path points to a .pth file, the script loads the saved weights on top of the base LLM ($MODEL_DIR/Qwen/Qwen3-4B-Base for qwen3_4b) before running evaluation. Leave the argument empty (or omit it) for pure zero-shot inference.
GRAB models require precomputed graph features. Precomputed graphs for all datasets are available at GRAB-table/precomputed-graphs on HuggingFace. Download the relevant zip(s) and extract them under $PRECOMPUTED_GRAPHS/<dataset>/.
To recompute from scratch, run this once per dataset before training. Outputs are written to $PRECOMPUTED_GRAPHS/<dataset>/<run_name>/.
Single-table datasets:
torchrun --nproc_per_node=2 src/precompute_single_table.py \
--dataset wtq \
--gnn_base_model "Qwen/Qwen3-Embedding-0.6B" \
--precomputed_graphs "$PRECOMPUTED_GRAPHS/wtq/precomputed_graphs_row_qwen"Multi-table datasets:
torchrun --nproc_per_node=2 src/precompute_multi_table.py \
--dataset mmqa \
--gnn_base_model "Qwen/Qwen3-Embedding-0.6B" \
--precomputed_graphs "$PRECOMPUTED_GRAPHS/mmqa/precomputed_graphs_row_qwen"If only one precomputed folder exists under
$PRECOMPUTED_GRAPHS/<dataset>/, training auto-discovers it and--precomputed_graphscan be omitted.
All training runs use torchrun for multi-GPU DDP. Set PROJECT_ROOT and PYTHONPATH before launching:
export PYTHONPATH="$(pwd):$PYTHONPATH"
source .envtorchrun --nproc_per_node=2 src/train_parallel.py \
--model_name grab_single_table \
--dataset wtq \
--multi_table False \
--llm_model_name qwen3_4b \
--llm_frozen True \
--gnn_base_model "Qwen/Qwen3-Embedding-0.6B" \
--num_gnn_layers 1 \
--num_row_latents 32 --num_col_latents 32 --num_val_latents 32 \
--num_resampler_layers 2 \
--max_columns 512 --max_hash_groups 16384 \
--lr 1e-4 --batch_size 2 --grad_steps 8 --num_epochs 10 \
--output_dir "$CKPT_DIR/train/grab_single/wtq"torchrun --nproc_per_node=2 src/train_parallel.py \
--model_name grab_multi_table \
--dataset mmqa \
--multi_table True \
--llm_model_name qwen3_4b \
--llm_frozen True \
--gnn_base_model "Qwen/Qwen3-Embedding-0.6B" \
--num_gnn_layers 1 \
--num_row_latents 32 --num_col_latents 32 --num_val_latents 32 \
--num_resampler_layers 2 \
--max_columns 512 --max_hash_groups 16384 \
--lr 1e-4 --batch_size 2 --grad_steps 8 --num_epochs 10 \
--output_dir "$CKPT_DIR/train/grab_multi/mmqa"torchrun --nproc_per_node=2 src/train_parallel.py \
--model_name soft_prompt_llm \
--dataset wtq \
--llm_model_name qwen3_4b \
--llm_frozen True \
--num_latents 10 --num_resampler_heads 8 --gnn_hidden_size 1024 \
--lr 1e-4 --batch_size 1 --grad_steps 16 --num_epochs 10 \
--output_dir "$CKPT_DIR/train/soft_prompt/wtq"To train on all datasets at once use the scripts in script/train/:
bash script/train/grab_single_dataset.sh
bash script/train/grab_multi_dataset.sh
bash script/train/soft_prompt_all_datasets.shFor zero-shot evaluation of a frozen LLM or testing a trained checkpoint use src/test.py:
torchrun --nproc_per_node=2 src/test.py \
--model_name base_llm \
--dataset structProbe \
--llm_model_name qwen3_4b \
--llm_frozen True \
--max_txt_len 8192 --max_new_tokens 128 \
--output_dir "$CKPT_DIR/test/base_llm/structProbe"Results (predictions CSV and score.txt) are written to --output_dir.
To evaluate all datasets at once:
bash script/test/baseline_llm_all_datasets.sh| Argument | Description |
|---|---|
--model_name |
base_llm, soft_prompt_llm, grab_single_table, grab_multi_table |
--dataset |
Dataset key from the table above |
--multi_table |
True for multi-table datasets, False otherwise |
--llm_model_name |
qwen3_4b, qwen3_14b |
--llm_frozen |
Freeze LLM weights (True / False) |
--llm_lora |
Apply LoRA to the LLM (True / False) |
--precomputed_graphs |
Folder name or absolute path to precomputed graph features |
--num_gnn_layers |
Number of tripartite message-passing layers |
--num_row_latents |
Latent tokens for row cross-attention |
--num_col_latents |
Latent tokens for column cross-attention |
--num_val_latents |
Latent tokens for value cross-attention |
--num_resampler_layers |
Cross-attention layers per latent group |
--max_columns |
Max columns for learnable column-type embeddings |
--max_hash_groups |
Size of the value-node hash buffer |
--skip_list |
Path to skip_list.json (samples exceeding token budget) |
--output_dir |
Where to write checkpoints, predictions, and scores |
If you find our work useful, please consider citing our paper:
@inproceedings{varriale-etal-2026-latent,
title = "Latent Bridges for Multi-Table Question Answering",
author = "Varriale, Simone and
Cucumides, Tamara and
Geerts, Floris and
Papotti, Paolo",
editor = "Lee, Hung-Yi and
Dev, Sunipa and
Li, Junyi Jessy and
Elliott, Desmond",
booktitle = "Proceedings of the 2026 Conference on Empirical Methods in Natural Language Processing",
month = oct,
year = "2026",
address = "Budapest, Hungary",
publisher = "Association for Computational Linguistics",
url = "https://aclanthology.org/2026.emnlp-main.2073/",
doi = "10.18653/2026.emnlp-main.2073",
pages = "47201--47228",
ISBN = "979-8-89176-459-0",
abstract = "We introduce GRAB, a constructor{--}encoder{--}bridge pipeline for table question answering. Our method lifts relational data into a heterogeneous graph, encodes it via message passing, and transfers the signals to an LLM through a small set of query-conditioned latent tokens. The LLM receives these tokens alongside the serialized table, and the two play different roles: the graph shows where the relevant evidence is, while the text keeps the exact cell values that the graph does not encode. The graph supplements the text rather than replacing it, and our ablations confirm that both are required. Crucially, the LLM remains strictly frozen to preserve its general reasoning capabilities; we train only the lightweight graph encoder and latent bridge (91M parameters), allowing the entire pipeline to be trained efficiently. Our pipeline significantly improves performance on relational Question Answering, with the largest gains in demanding multi-table settings, offering an efficient, principled way to connect relational deep learning with LLMs."
}