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/squad-v2-lance.
A Lance-formatted version of SQuAD v2 — the Stanford Question Answering Dataset with both answerable and deliberately unanswerable questions over Wikipedia passages — with MiniLM question embeddings stored inline and ready for retrieval at hf://datasets/lance-format/squad-v2-lance/data.

Key features

  • Span-extraction QA over Wikipedia with 130k+ training questions and an is_impossible flag that cleanly separates answerable from unanswerable items.
  • Pre-computed 384-dim question embeddings (question_emb, sentence-transformers/all-MiniLM-L6-v2, cosine-normalized) with a bundled IVF_PQ index for semantic question retrieval.
  • Full-text inverted indices on both question and context for keyword search alongside dense retrieval.
  • One columnar dataset carrying questions, contexts, answer spans, and embeddings together — project only the columns each query needs.

Splits

SplitRows
train.lance130,319
validation.lance11,873

Schema

ColumnTypeNotes
idstringSQuAD question id (natural join key for merges)
titlestringWikipedia article title
contextstringParagraph the question was generated from
questionstringThe question text
answerslist<string>Accepted answer spans (empty for impossible questions)
answer_startslist<int32>Character offsets of each answer within context
is_impossiblebooltrue for SQuAD 2.0 unanswerable questions
question_embfixed_size_list<float32, 384>MiniLM embedding of question (cosine-normalized)

Pre-built indices

  • IVF_PQ on question_embmetric=cosine, vector similarity search
  • INVERTED on question and context — full-text search
  • BTREE on id and title — point lookups and prefix scans
  • BITMAP on is_impossible — fast filtering between answerable and unanswerable

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/squad-v2-lance", split="validation", streaming=True)
for row in hf_ds.take(3):
    print(row["question"], "->", row["answers"])

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/squad-v2-lance/data")
tbl = db.open_table("train")
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, the list of pre-built indices.
import lance

ds = lance.dataset("hf://datasets/lance-format/squad-v2-lance/data/train.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/squad-v2-lance --repo-type dataset --local-dir ./squad-v2-lance
Then point Lance or LanceDB at ./squad-v2-lance/data.
The bundled IVF_PQ index on question_emb turns semantic question retrieval into a single call. In production you would encode an incoming question through the same MiniLM encoder used at ingest and pass the resulting 384-dim vector to tbl.search(...). The example below uses the embedding from row 42 as a runnable stand-in, then restricts the result to answerable items so the response always carries a usable span.
import lancedb

db = lancedb.connect("hf://datasets/lance-format/squad-v2-lance/data")
tbl = db.open_table("train")

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

hits = (
    tbl.search(seed["question_emb"], vector_column_name="question_emb")
    .metric("cosine")
    .where("is_impossible = false", prefilter=True)
    .select(["id", "title", "question", "answers"])
    .limit(10)
    .to_list()
)
for r in hits:
    print(f"{r['title']:30s} | {r['question'][:80]}")
Because the recommended setup also builds an INVERTED index on both question and context, the same query can be issued as a hybrid search that combines the dense vector with a keyword query. LanceDB merges and reranks the two result lists in a single call, which is useful when a literal phrase must appear in the passage but the dense side should still drive ranking.
hybrid_hits = (
    tbl.search(query_type="hybrid")
    .vector(seed["question_emb"])
    .text("eiffel tower")
    .where("is_impossible = false", prefilter=True)
    .select(["id", "title", "question", "context", "answers"])
    .limit(10)
    .to_list()
)
for r in hybrid_hits:
    print(f"{r['title']:30s} | {r['question'][:80]}")
Tune metric, nprobes, and refine_factor on the vector side to trade recall against latency.

Curate

SQuAD v2 has a natural split between answerable and unanswerable questions, and the is_impossible boolean — backed by a BITMAP index — makes either subset cheap to extract. Stacking predicates inside a single filtered scan keeps the result small and explicit, and the bounded .limit(1000) makes it easy to inspect or hand off.
import lancedb

db = lancedb.connect("hf://datasets/lance-format/squad-v2-lance/data")
tbl = db.open_table("train")

impossible = (
    tbl.search()
    .where("is_impossible = true AND length(question) >= 40", prefilter=True)
    .select(["id", "title", "question", "context"])
    .limit(1000)
    .to_list()
)
print(f"{len(impossible)} hard unanswerable questions; first title: {impossible[0]['title']}")
The mirror query — long, well-grounded answerable questions — looks identical with the boolean flipped, and the question_emb vector is never read by either scan. The result is a plain list of dictionaries, ready to inspect, persist as a manifest of question ids, or hand to the Materialize-a-subset section below for export to a writable local copy.

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 a question_length, a num_answers count, and a has_answer flag — any 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 corpus.
import lancedb

db = lancedb.connect("./squad-v2-lance/data")  # local copy required for writes
tbl = db.open_table("train")

tbl.add_columns({
    "question_length": "length(question)",
    "num_answers": "array_length(answers)",
    "has_answer": "NOT is_impossible",
})
If the values you want to attach already live in another table (offline reader-model predictions, alternate embeddings, span-level labels), merge them in by joining on id:
import pyarrow as pa

scores = pa.table({
    "id": pa.array(["56be4db0acb8001400a502ec", "56be4db0acb8001400a502ed"]),
    "reader_score": pa.array([0.91, 0.42]),
})
tbl.merge(scores, on="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 (e.g., running a different embedding model over the questions), 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 reading-comprehension model the natural projection is the question, the context, and the answer spans together; for a retriever or reranker on top of frozen features, project the precomputed embedding instead.
import lancedb
from lancedb.permutation import Permutation
from torch.utils.data import DataLoader

db = lancedb.connect("hf://datasets/lance-format/squad-v2-lance/data")
tbl = db.open_table("train")

train_ds = Permutation.identity(tbl).select_columns(
    ["question", "context", "answers", "answer_starts", "is_impossible"]
)
loader = DataLoader(train_ds, batch_size=32, shuffle=True, num_workers=4)

for batch in loader:
    # batch carries only the projected columns; question_emb stays on disk.
    # tokenize question+context, build span labels from answer_starts, forward, backward...
    ...
Switching feature sets is a configuration change: passing ["question_emb"] (optionally with ["answers"] for hard-negative mining) to select_columns(...) on the next run reads only the 384-d vectors and skips the bulky context strings entirely, which is the right shape for training a retrieval head or reranker on cached embeddings. Columns added in Evolve cost nothing per batch until they are explicitly projected.

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/squad-v2-lance/data")
tbl = db.open_table("train")

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("./squad-v2-lance/data")
local_tbl = local_db.open_table("train")
local_tbl.tags.create("baseline-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("train", version="baseline-v1")
tbl_v5 = db.open_table("train", version=5)
Pinning supports two workflows. A retrieval system locked to baseline-v1 keeps returning stable results while the dataset evolves in parallel — newly added reader scores or labels do not change what the tag resolves to. A training experiment pinned to the same tag can be rerun later against the exact same questions and contexts, 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 corpus. 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/squad-v2-lance/data")
remote_tbl = remote_db.open_table("train")

batches = (
    remote_tbl.search()
    .where("is_impossible = false AND length(question) >= 30")
    .select(["id", "title", "context", "question", "answers", "answer_starts", "question_emb"])
    .to_batches()
)

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

Source & license

Converted from rajpurkar/squad_v2. SQuAD v2 is distributed under CC BY-SA 4.0.

Citation

@article{rajpurkar2018know,
  title={Know What You Don't Know: Unanswerable Questions for SQuAD},
  author={Rajpurkar, Pranav and Jia, Robin and Liang, Percy},
  journal={Proceedings of the 56th Annual Meeting of the Association for Computational Linguistics (Short Papers)},
  year={2018}
}