Skip to main content

Documentation Index

Fetch the complete documentation index at: https://lancedb-bcbb4faf-docs-namespace-typescript-examples.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

https://mintcdn.com/lancedb-bcbb4faf-docs-namespace-typescript-examples/tsMoej_yo3g0KMHe/static/assets/logo/huggingface-logo.svg?fit=max&auto=format&n=tsMoej_yo3g0KMHe&q=85&s=16a86ecc43dfa9ff35068d69c809cdb5

View on Hugging Face

Source dataset card and downloadable files for lance-format/gqa-testdev-balanced-lance.
A Lance-formatted version of the canonical GQA testdev_balanced slice — 12,578 compositional VQA questions joined against the matching 398 images — sourced from lmms-lab/GQA. The original redistribution ships instructions and images as separate parquet configs; here they are pre-joined on image_id, so each row carries the question text, the short answer, the GQA reasoning-program tags, paired CLIP image and question embeddings, and the inline JPEG bytes — all available directly from the Hub at hf://datasets/lance-format/gqa-testdev-balanced-lance/data.

Key features

  • Inline JPEG bytes in the image column, duplicated across rows that share an image_id so each Q/A row is self-contained.
  • Paired CLIP embeddings in the same rowimage_emb and question_emb (512-dim, cosine-normalized) — for cross-modal retrieval as one indexed lookup.
  • Compositional reasoning metadatastructural, semantic, and detailed question-type tags plus the semantic_str reasoning program.
  • Pre-built ANN, FTS, scalar, and bitmap indices covering both embeddings, the question and short answer, the reasoning-type tags, and the image/question ids.

Splits

SplitRowsDistinct images
testdev.lance12,578398
The train_balanced (~943 k Q’s × 72 k images) and val_balanced splits are not bundled by default; pass --instr-config / --images-config to gqa/dataprep.py to extend.

Schema

ColumnTypeNotes
idint64Row index within split
imagelarge_binaryInline JPEG bytes (duplicated across rows that share an image_id)
image_idstringGQA scene-graph image id
question_idstringGQA question id
questionstringCompositional natural-language question
answerslist<string>One-element list (the GQA short answer)
answerstringCanonical short answer (used for FTS)
full_answerstring?Full-sentence answer
structuralstring?One of verify, query, compare, choose, logical
semanticstring?One of attr, cat, global, obj, rel
detailedstring?Fine-grained type (e.g. weatherVerifyC)
is_balancedboolGQA balanced subset flag
group_global, group_localstring?GQA reasoning-group ids
semantic_strstring?Compact description of the reasoning program
image_embfixed_size_list<float32, 512>CLIP image embedding (cosine-normalized)
question_embfixed_size_list<float32, 512>CLIP text embedding of the question

Pre-built indices

  • IVF_PQ on image_emb — image-side vector search (cosine)
  • IVF_PQ on question_emb — question-side vector search (cosine)
  • INVERTED (FTS) on question and answer — keyword and hybrid search
  • BITMAP on structural, semantic, detailed — fast categorical filters on the reasoning program
  • BTREE on image_id, question_id — fast lookup by GQA id

Why Lance?

  1. Blazing Fast Random Access: Optimized for fetching scattered rows, making it ideal for random sampling, real-time ML serving, and interactive applications without performance degradation.
  2. Native Multimodal Support: Store text, embeddings, and other data types together in a single file. Large binary objects are loaded lazily, and vectors are optimized for fast similarity search.
  3. Native Index Support: Lance comes with fast, on-disk, scalable vector and FTS indexes that sit right alongside the dataset on the Hub, so you can share not only your data but also your embeddings and indexes without your users needing to recompute them.
  4. Efficient Data Evolution: Add new columns and backfill data without rewriting the entire dataset. This is perfect for evolving ML features, adding new embeddings, or introducing moderation tags over time.
  5. Versatile Querying: Supports combining vector similarity search, full-text search, and SQL-style filtering in a single query, accelerated by on-disk indexes.
  6. Data Versioning: Every mutation commits a new version; previous versions remain intact on disk. Tags pin a snapshot by name, so retrieval systems and training runs can reproduce against an exact slice of history.

Load with datasets.load_dataset

You can load Lance datasets via the standard HuggingFace datasets interface, suitable when your pipeline already speaks Dataset / IterableDataset or you want a quick streaming sample.
import datasets

hf_ds = datasets.load_dataset("lance-format/gqa-testdev-balanced-lance", split="testdev", streaming=True)
for row in hf_ds.take(3):
    print(row["question"], "->", row["answer"])

Load with LanceDB

LanceDB is the embedded retrieval library built on top of the Lance format (docs), and is the interface most users interact with. It wraps the dataset as a queryable table with search and filter builders, and is the entry point used by the Search, Curate, Evolve, Versioning, and Materialize-a-subset sections below.
import lancedb

db = lancedb.connect("hf://datasets/lance-format/gqa-testdev-balanced-lance/data")
tbl = db.open_table("testdev")
print(len(tbl))

Load with Lance

pylance is the Python binding for the Lance format and works directly with the format’s lower-level APIs. Reach for it when you want to inspect dataset internals — schema, scanner, fragments, and the list of pre-built indices.
import lance

ds = lance.dataset("hf://datasets/lance-format/gqa-testdev-balanced-lance/data/testdev.lance")
print(ds.count_rows(), ds.schema.names)
print(ds.list_indices())
Tip — for production use, download locally first. Streaming from the Hub works for exploration, but heavy random access and ANN search are far faster against a local copy:
hf download lance-format/gqa-testdev-balanced-lance --repo-type dataset --local-dir ./gqa-testdev-balanced-lance
Then point Lance or LanceDB at ./gqa-testdev-balanced-lance/data.
The bundled IVF_PQ index on image_emb makes cross-modal text→image retrieval a single call: encode a question with the same CLIP model used at ingest (ViT-B/32, cosine-normalized), then pass the resulting 512-d vector to tbl.search(...) and target image_emb. The example below uses the question_emb already stored in row 42 as a runnable stand-in for “the CLIP encoding of a question”, so the snippet works without any model loaded.
import lancedb

db = lancedb.connect("hf://datasets/lance-format/gqa-testdev-balanced-lance/data")
tbl = db.open_table("testdev")

seed = (
    tbl.search()
    .select(["question_emb", "question", "answer"])
    .limit(1)
    .offset(42)
    .to_list()[0]
)

hits = (
    tbl.search(seed["question_emb"], vector_column_name="image_emb")
    .metric("cosine")
    .select(["image_id", "question", "answer", "structural"])
    .limit(10)
    .to_list()
)
print("query question:", seed["question"], "->", seed["answer"])
for r in hits:
    print(f"  {r['image_id']:>12}  [{r['structural']}]  {r['question'][:70]}")
Because the CLIP embeddings are cosine-normalized, cosine is the right metric and the first hit will often be the source row itself — a useful sanity check. Swap vector_column_name="image_emb" for question_emb to find paraphrased or topically related questions instead. The dataset also ships an INVERTED index on question and answer, so the same query can be issued as a hybrid search that combines the dense vector with a literal keyword match. This is useful when a noun like “umbrella” must appear in the question text but you still want CLIP to handle visual similarity over the candidate set.
hybrid_hits = (
    tbl.search(query_type="hybrid", vector_column_name="image_emb")
    .vector(seed["question_emb"])
    .text("umbrella")
    .select(["image_id", "question", "answer"])
    .limit(10)
    .to_list()
)
for r in hybrid_hits:
    print(f"  {r['image_id']:>12}  {r['question'][:70]}  -> {r['answer']}")
Tune metric, nprobes, and refine_factor on the vector side to trade recall against latency.

Curate

A typical curation pass for a compositional-reasoning study combines a predicate on the question text (or the GQA short answer) with a structural filter on the reasoning program, so the candidate set is both topically and structurally consistent. Stacking both inside a single filtered scan keeps the result small and explicit, and the bounded .limit(500) makes it cheap to inspect before committing the subset to anything downstream.
import lancedb

db = lancedb.connect("hf://datasets/lance-format/gqa-testdev-balanced-lance/data")
tbl = db.open_table("testdev")

candidates = (
    tbl.search()
    .where(
        "structural = 'verify' AND answer IN ('yes', 'no') AND question LIKE 'Is %'",
        prefilter=True,
    )
    .select(["question_id", "image_id", "question", "answer", "semantic"])
    .limit(500)
    .to_list()
)
print(f"{len(candidates)} verify-style yes/no candidates; first: {candidates[0]['question']}")
The result is a plain list of dictionaries, ready to inspect, persist as a manifest of question_ids, or feed into the Evolve and Train workflows below. The image column is never read, so the network traffic for a 500-row candidate scan is dominated by the question and answer strings rather than JPEG bytes.

Evolve

Lance stores each column independently, so a new column can be appended without rewriting the existing data. The lightest form is a SQL expression: derive the new column from columns that already exist, and Lance computes it once and persists it. The example below adds an is_binary_answer flag and a question_length integer, either of which can then be used directly in where clauses without recomputing the predicate on every query.
Note: Mutations require a local copy of the dataset, since the Hub mount is read-only. See the Materialize-a-subset section at the end of this card for a streaming pattern that downloads only the rows and columns you need, or use hf download to pull the full split first.
import lancedb

db = lancedb.connect("./gqa-testdev-balanced-lance/data")  # local copy required for writes
tbl = db.open_table("testdev")

tbl.add_columns({
    "is_binary_answer": "answer IN ('yes', 'no')",
    "question_length": "length(question)",
    "answer_length": "length(answer)",
})
If the values you want to attach already live in another table (offline labels, scene-graph features, or per-question predictions from an external model), merge them in by joining on question_id:
import pyarrow as pa

predictions = pa.table({
    "question_id": pa.array(["20240268", "20240269"]),
    "model_answer": pa.array(["yes", "left"]),
    "model_confidence": pa.array([0.91, 0.62]),
})
tbl.merge(predictions, on="question_id")
The original columns and indices are untouched, so existing code that does not reference the new columns continues to work unchanged. New columns become visible to every reader as soon as the operation commits. For column values that require a Python computation, Lance provides a batch-UDF API — see the Lance data evolution docs.

Train

Projection lets a training loop read only the columns each step actually needs. LanceDB tables expose this through Permutation.identity(tbl).select_columns([...]), which plugs straight into the standard torch.utils.data.DataLoader so prefetching, shuffling, and batching behave as in any PyTorch pipeline. For a VQA fine-tune, project the JPEG bytes, the question, and the short answer; columns added in the Evolve section above cost nothing per batch until they are explicitly projected.
import lancedb
from lancedb.permutation import Permutation
from torch.utils.data import DataLoader

db = lancedb.connect("hf://datasets/lance-format/gqa-testdev-balanced-lance/data")
tbl = db.open_table("testdev")

train_ds = Permutation.identity(tbl).select_columns(["image", "question", "answer"])
loader = DataLoader(train_ds, batch_size=64, shuffle=True, num_workers=4)

for batch in loader:
    # batch carries only the projected columns; decode the JPEG bytes,
    # tokenize the question, forward through the VLM, compute the loss...
    ...
Switching feature sets is a configuration change: passing ["image_emb", "question_emb", "answer"] to select_columns(...) on the next run skips JPEG decoding entirely and reads only the cached 512-d vectors, which is the right shape for a lightweight reasoning probe over frozen CLIP features.

Versioning

Every mutation to a Lance dataset, whether it adds a column, merges labels, or builds an index, commits a new version. Previous versions remain intact on disk. You can list versions and inspect the history directly from the Hub copy; creating new tags requires a local copy since tags are writes.
import lancedb

db = lancedb.connect("hf://datasets/lance-format/gqa-testdev-balanced-lance/data")
tbl = db.open_table("testdev")

print("Current version:", tbl.version)
print("History:", tbl.list_versions())
print("Tags:", tbl.tags.list())
Once you have a local copy, tag a version for reproducibility:
local_db = lancedb.connect("./gqa-testdev-balanced-lance/data")
local_tbl = local_db.open_table("testdev")
local_tbl.tags.create("clip-vitb32-v1", local_tbl.version)
A tagged version can be opened by name, or any version reopened by its number, against either the Hub copy or a local one:
tbl_v1 = db.open_table("testdev", version="clip-vitb32-v1")
tbl_v5 = db.open_table("testdev", version=5)
Pinning supports two workflows. A retrieval system locked to clip-vitb32-v1 keeps returning stable results while the dataset evolves in parallel — newly added model predictions or reasoning annotations do not change what the tag resolves to. A training experiment pinned to the same tag can be rerun later against the exact same images and questions, so changes in metrics reflect model changes rather than data drift. Neither workflow needs shadow copies or external manifest tracking.

Materialize a subset

Reads from the Hub are lazy, so exploratory queries only transfer the columns and row groups they touch. Mutating operations (Evolve, tag creation) need a writable backing store, and a training loop benefits from a local copy with fast random access. Both can be served by a subset of the dataset rather than the full split. The pattern is to stream a filtered query through .to_batches() into a new local table; only the projected columns and matching row groups cross the wire, and the bytes never fully materialize in Python memory.
import lancedb

remote_db = lancedb.connect("hf://datasets/lance-format/gqa-testdev-balanced-lance/data")
remote_tbl = remote_db.open_table("testdev")

batches = (
    remote_tbl.search()
    .where("structural = 'verify' AND answer IN ('yes', 'no')")
    .select(["question_id", "image_id", "image", "question", "answer", "image_emb", "question_emb"])
    .to_batches()
)

local_db = lancedb.connect("./gqa-yesno-subset")
local_db.create_table("testdev", batches)
The resulting ./gqa-yesno-subset is a first-class LanceDB database. Every snippet in the Evolve, Train, and Versioning sections above works against it by swapping hf://datasets/lance-format/gqa-testdev-balanced-lance/data for ./gqa-yesno-subset.

Source & license

Converted from lmms-lab/GQA. GQA is released under CC BY 4.0 by Hudson and Manning (Stanford NLP).

Citation

@inproceedings{hudson2019gqa,
  title={GQA: A New Dataset for Real-World Visual Reasoning and Compositional Question Answering},
  author={Hudson, Drew A. and Manning, Christopher D.},
  booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
  year={2019}
}