SICE-FM β cross-system query runtime prediction
A foundation model that predicts query execution time from a query plan, trained on one set of database systems and applied to another.
| file | what it is |
|---|---|
pretrain_tsw_tgtspark_imdb_pd0.5_model.pt |
the bank β start here. 173 MB, 1,221 tensors, sha256 24218d31β¦60da809 |
pretrain_tsw_tgtspark_imdb_pd0.5_ctx8c_model.pt |
same recipe plus a system-context module (181 MB). Only if you want that ablation β see Which file. |
Trained on PostgreSQL (170,987 plans) + DuckDB (171,000 plans). The imdb workload is excluded from pretraining everywhere, so imdb is a clean evaluation target.
β οΈ Read the filename correctly:
tgtsparkdoes not mean "trained on Spark"The
tgt<system>prefix names the system the bank is aimed at β the one deliberately held out of training β not the data it saw.tgtsparktherefore means:
- trained on: PostgreSQL + DuckDB
- target (never seen in training): Spark
The naming comes from the training harness, where a bank is built by holding one system out and training on the rest. This is the PostgreSQL + DuckDB bank. If you want to predict on PostgreSQL or DuckDB plans it still works β those are systems it trained on β but the Spark numbers are the cross-system transfer result.
Training workloads
19 databases, each contributing ~9,000 plans per system:
accidents, airline, baseball, basketball, carcinogenesis, consumer, credit,
employee, fhnk, financial, geneea, genome, hepatitis, movielens, seznam,
ssb, tournament, tpc_h, walmart
imdb is excluded from every training system, which is what makes the published imdb
results a genuine held-out evaluation rather than a memory check.
Which file
Take the first one. It is the plain model: plan-dropout 0.5, no context module
(ctx: None), no meta-learning (reptile: None). The ctx8c variant adds a system-context
encoder that helps on some targets and hurts on others β in our own STATS evaluation the
simpler bank wins on 2 of 3 targets β so it is published for completeness, not as the default.
Nothing else from the paper lives in these weights. In particular the anchor is applied during few-shot adaptation, not pretraining, so it is not part of a bank and there is no "with/without anchor" checkpoint to choose between.
Architecture
61.9M parameters, two encoders fused into a scalar head.
- Plan text β
all-MiniLM-L12-v2(12 layers, hidden 384, vocab 30,522) with LoRA adapters. The trunk is adapted, so the full checkpoint is required β a base-model ID plus a head is not enough. - Structure β a PRICE-style transformer,
d_model256, 8 heads Γ 32.
Inputs
Two descriptions of the same query, both required:
Cleaned plan text β the engine's
EXPLAINoutput with every measured quantity stripped (no latency, no actual rows, no timings). Leaving them in leaks the label.Structural features, as typed tokens β not one flat vector:
slot count width contributes join histogram 10 40 400 fanout 10 42 420 table 6 4 24 filter 11 75 825 Concatenated that is a 1669-wide vector per clause, and the attention mask covers 38 positions β 37 feature tokens (10+10+6+11) plus one CLS. These come from database statistics, not from the plan text.
(The embedding layers in the checkpoint are
nn.Linear(41, 256)andnn.Linear(43, 256)for the first two slots. The extra input is appended by the model itself; supply 40 and 42.)
Output β converting a prediction to milliseconds
The model emits a scalar in (0, 1). Labels were normalized in log space, so invert with:
import numpy as np
# carrier = postgres, so the result is in MILLISECONDS
MINI, MAXI = 0.40413088509502776, 15.00133486099299
def to_ms(pred_norm):
return np.exp(pred_norm * (MAXI - MINI) + MINI) - 0.001
Sanity check: to_ms(0.25) β 57.6 ms, to_ms(0.5) β 2214 ms. The training labels span
48.0 β 16,549 ms, i.e. roughly 0.24 β 0.65 in normalized space.
Normalization clips to [0.001, 1], so queries far outside that range saturate.
(For reference, the DuckDB fold of the same bank uses mini=12.887167490096259,
maxi=24.141273665166107 and is in nanoseconds. The carrier above is the operative one.)
Training recipe
30 epochs, Adam lr 1e-4, batch 24, seed 42, plan-dropout 0.5, mixed batching (global shuffle over the concatenated systems).
Intended use and limitations
Research on learned cost estimation and cross-system transfer. Measured limits:
- Unseen engines are where it wins β 20β46% better median q-error on an unseen workload.
- In-system tail is weaker. On a system already represented in training, the adaptation-time distillation degrades p95 (+35% end-to-end in one setting).
- Predictions are only as good as the statistics feeding the structural encoder. Queries with no usable statistics fall back to plan text alone and should not be trusted.
How to run it
You need three things. Get these right and the model will score any query plan.
| input | what it must be |
|---|---|
--plans |
a CSV with a json column, one EXPLAIN document per row |
--sql |
the matching SQL, one statement per line, line i β plan row i |
--db + --stats-dir |
a directory of PRICE statistics for that database |
./score.py \
--bank pretrain_tsw_tgtspark_imdb_pd0.5 \
--plans long_raw_postgres_imdb_job-light_c8220.csv \
--sql imdb_job-light_c8220.sql \
--db imdb \
--engine postgres \
--out preds.csv
Output (preds.csv) carries its own provenance, then one row per query:
# bank: pretrain_tsw_tgtspark_imdb_pd0.5
# trained on: duckdb, postgres
# carrier: postgres (ms); scored engine: postgres
csv_row,pred_ms,pred_carrier_ms,pred_norm,true_ms,qerror,num_clauses
1,32.481995,32.481995,0.210765,32.787000,1.009390,1
true_ms and qerror are filled only when the plan document carries a measured runtime.
An EXPLAIN without ANALYZE still predicts β it simply cannot be scored.
Getting the inputs right
- Plans must come from the engine you pass to
--engine. Plan text is cleaned with engine-specific rules; using the wrong one leaves measured columns in the text, which feeds the answer to the model.score.pyrefuses to guess rather than default. - SQL must be row-aligned with the plans, one statement per line, using the database's real table names. The statistics are built from the SQL text, never from the plan.
- Statistics must cover the schema.
score.pyreportsN succeeded, M failed; a query with no usable statistics falls back to plan text alone and is not trustworthy. If every query fails, it exits with an error rather than printing confident nonsense.
What to expect
Scoring 50 queries takes about 9 seconds on one GPU, including cold feature extraction.
A bank on its own is zero-shot β no few-shot adaptation. On 50 held-out imdb job-light
queries this bank gives median q-error 2.86 (p90 5.5). The much lower numbers in the paper
come after the few-shot adaptation phase, which is not part of these weights. Use the
zero-shot figure as your plumbing check: if you see roughly this, your inputs are correct.
Status
The weights and the input contract above are final. The reference implementation (score.py
and the feature/cleaning code it calls) is being published as a single repository; until that
lands you can reproduce the pipeline from the specification in this card.
License
Apache-2.0, inherited from the all-MiniLM-L12-v2 base model.